Create a 3D chat for a dating website

Part 1: Setting Up Your 3D Dating World - Where Pixels Find Passion! 💘

Welcome, brave developer, to the most exciting project of your career: building a 3D dating chat! Why settle for boring 2D profiles when you can help people find love in THREE DIMENSIONS? That's 50% more dimension than regular dating! Chameleon Social and Dating Software includes 3D Chat, so you can have one too!

1e22f2fee04fc0296d0ebc9db860343f5.png

In this 10-part series, we'll create a virtual dating space where avatars can flirt, chat, and probably awkwardly stare at each other from multiple angles. Let's begin!

Step 1: The Basic Setup - HTML That Says "I'm Ready to Mingle!"

First, let's create our basic HTML structure. Think of this as building the virtual bar where all the digital magic happens:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Love in 3D - Where Your GPU Finds True Love! ❤️</title>
    <style>
        body { 
            margin: 0; 
            overflow: hidden; 
            font-family: 'Comic Sans MS', cursive; /* Because we're serious about fun! */
        }
        #container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }
        #chatUI {
            position: absolute;
            bottom: 20px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            width: 300px;
            z-index: 100;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
        }
        #loadingScreen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 24px;
            z-index: 1000;
        }
    </style>
</head>
<body>
    <div id="container">
        <div id="loadingScreen">
            Loading your perfect match... ⏳
        </div>
        <div id="chatUI" style="display: none;">
            <input type="text" id="messageInput" placeholder="Type your pickup line here...">
            <button onclick="sendMessage()">Send ❤️</button>
            <div id="chatMessages"></div>
        </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="app.js"></script>
</body>
</html>

Step 2: Three.js Initialization - Lighting the Digital Candles 🕯️

Now, let's create our app.js file and set up the Three.js scene. This is where we create the virtual world where love (or at least interesting conversations) will blossom:

// app.js - Where the magic happens and hearts get rendered at 60fps!

class DatingScene {
    constructor() {
        this.scene = null;
        this.camera = null;
        this.renderer = null;
        this.avatars = []; // This array will store all our lonely hearts
        this.currentUser = null;

        this.init(); // Let's get this virtual romance started!
    }

    init() {
        this.createScene();
        this.createCamera();
        this.createRenderer();
        this.createLights();
        this.createEnvironment();
        this.animate();

        // Hide loading screen after 2 seconds (or when assets load)
        setTimeout(() => {
            document.getElementById('loadingScreen').style.display = 'none';
            document.getElementById('chatUI').style.display = 'block';
            this.addSampleAvatars(); // Add some virtual hotties
        }, 2000);
    }

    createScene() {
        // Create the scene - think of this as the virtual dating venue
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x87CEEB); // Sky blue - for optimistic vibes!

        console.log("Scene created! Ready to host some digital romance! 💕");
    }

    createCamera() {
        // Create camera - this is our window into the dating world
        this.camera = new THREE.PerspectiveCamera(
            75, // Field of view - wide enough to see all the potential matches!
            window.innerWidth / window.innerHeight,
            0.1, // Near clipping plane - no getting too close too fast!
            1000 // Far clipping plane - can spot matches from afar!
        );

        this.camera.position.set(0, 5, 10); // Elevated view - like watching from the VIP section!
        this.camera.lookAt(0, 0, 0);

        console.log("Camera ready! Say cheese, future lovers! 📸");
    }

    createRenderer() {
        // Create renderer - the artist that paints our love story
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true; // Shadows make everything sexier!
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

        document.getElementById('container').appendChild(this.renderer.domElement);

        // Handle window resizing - because love should be responsive!
        window.addEventListener('resize', () => this.onWindowResize());

        console.log("Renderer ready! Preparing to render some chemistry! 🔥");
    }

    createLights() {
        // Ambient light - the mood lighting for our virtual date
        const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
        this.scene.add(ambientLight);

        // Directional light - the spotlight for our star-crossed lovers
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(10, 20, 5);
        directionalLight.castShadow = true;
        directionalLight.shadow.mapSize.width = 2048;
        directionalLight.shadow.mapSize.height = 2048;
        this.scene.add(directionalLight);

        // Point light - for that romantic glow
        const pointLight = new THREE.PointLight(0xff6b6b, 0.5, 100);
        pointLight.position.set(0, 5, 0);
        this.scene.add(pointLight);

        console.log("Lights ready! Setting the mood for digital romance! 💡");
    }

    createEnvironment() {
        // Create ground - where our avatars will stand (and probably nervously shuffle)
        const groundGeometry = new THREE.PlaneGeometry(50, 50);
        const groundMaterial = new THREE.MeshStandardMaterial({ 
            color: 0x90EE90, // Green - like a peaceful park for dating
            roughness: 0.8,
            metalness: 0.2
        });

        const ground = new THREE.Mesh(groundGeometry, groundMaterial);
        ground.rotation.x = -Math.PI / 2; // Make it horizontal, because that's how ground works!
        ground.receiveShadow = true;
        this.scene.add(ground);

        // Add some decorative elements - because even virtual dates need ambiance!
        this.createDecorativeElements();

        console.log("Environment ready! Less awkward than a real-life coffee shop! ☕");
    }

    createDecorativeElements() {
        // Create some trees - for avatars to hide behind when they're shy
        for (let i = 0; i < 5; i++) {
            const tree = this.createTree();
            tree.position.set(
                (Math.random() - 0.5) * 40,
                0,
                (Math.random() - 0.5) * 40
            );
            this.scene.add(tree);
        }

        // Create a heart-shaped object because we're cheesy like that!
        const heartShape = new THREE.Shape();
        heartShape.moveTo(0, 0);
        heartShape.bezierCurveTo(2, 2, 3, 0, 0, -3);
        heartShape.bezierCurveTo(-3, 0, -2, 2, 0, 0);

        const heartGeometry = new THREE.ExtrudeGeometry(heartShape, {
            depth: 0.5,
            bevelEnabled: true,
            bevelSegments: 2,
            bevelSize: 0.1,
            bevelThickness: 0.1
        });

        const heartMaterial = new THREE.MeshStandardMaterial({ 
            color: 0xff6b6b,
            emissive: 0xff0000,
            emissiveIntensity: 0.2
        });

        const heart = new THREE.Mesh(heartGeometry, heartMaterial);
        heart.position.set(0, 3, 0);
        heart.scale.set(0.5, 0.5, 0.5);
        this.scene.add(heart);
    }

    createTree() {
        // Trunk
        const trunkGeometry = new THREE.CylinderGeometry(0.3, 0.4, 2, 8);
        const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
        const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
        trunk.castShadow = true;

        // Leaves
        const leavesGeometry = new THREE.SphereGeometry(1.5, 8, 6);
        const leavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
        const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial);
        leaves.position.y = 2;
        leaves.castShadow = true;

        const tree = new THREE.Group();
        tree.add(trunk);
        tree.add(leaves);

        return tree;
    }

    addSampleAvatars() {
        // Let's add some sample avatars to make the place look popular!
        // In real implementation, these would come from your backend

        console.log("Adding sample avatars... because empty venues are sad! 😢");

        // Create a few basic avatars
        for (let i = 0; i < 3; i++) {
            const avatar = this.createBasicAvatar();
            avatar.position.set(
                (i - 1) * 4, // Spread them out
                0,
                -5 + Math.random() * 3 // Some variation in depth
            );

            // Give each avatar a slight color variation
            avatar.children[0].material.color.setHex(0xffffff - i * 0x111111);

            this.scene.add(avatar);
            this.avatars.push(avatar);

            // Add some simple animation to make them look alive
            this.animateAvatar(avatar);
        }

        console.log(`Added ${this.avatars.length} potential matches! Time to mingle! 🎉`);
    }

    createBasicAvatar() {
        // Create a simple stick-figure style avatar
        // In future parts, we'll make these much more sophisticated!

        const avatarGroup = new THREE.Group();

        // Head
        const headGeometry = new THREE.SphereGeometry(0.5, 16, 16);
        const headMaterial = new THREE.MeshStandardMaterial({ color: 0xFFD700 });
        const head = new THREE.Mesh(headGeometry, headMaterial);
        head.position.y = 1.7;
        head.castShadow = true;
        avatarGroup.add(head);

        // Body
        const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.3, 1.5, 8);
        const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0x4169E1 });
        const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
        body.position.y = 0.75;
        body.castShadow = true;
        avatarGroup.add(body);

        // Simple eyes (because even avatars need to make eye contact!)
        const eyeGeometry = new THREE.SphereGeometry(0.1, 8, 8);
        const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });

        const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
        leftEye.position.set(0.2, 1.7, 0.4);
        avatarGroup.add(leftEye);

        const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
        rightEye.position.set(-0.2, 1.7, 0.4);
        avatarGroup.add(rightEye);

        console.log("Basic avatar created! They may be simple, but they have personality! 😊");
        return avatarGroup;
    }

    animateAvatar(avatar) {
        // Simple bobbing animation to make avatars look alive
        const originalY = avatar.position.y;
        let time = Math.random() * Math.PI * 2; // Random start time

        const animate = () => {
            time += 0.05;
            avatar.position.y = originalY + Math.sin(time) * 0.1; // Gentle bobbing

            // Rotate slightly for more natural movement
            avatar.rotation.y = Math.sin(time * 0.5) * 0.1;

            requestAnimationFrame(animate);
        };

        animate();
    }

    onWindowResize() {
        // Handle window resize - because love shouldn't get cut off!
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);

        console.log("Window resized! Making sure no one misses their perfect match! 🔍");
    }

    animate() {
        // The main animation loop - where the magic keeps happening!
        requestAnimationFrame(() => this.animate());

        // Rotate the heart because we're extra like that
        const heart = this.scene.children.find(child => 
            child.geometry && child.geometry.type === 'ExtrudeGeometry'
        );

        if (heart) {
            heart.rotation.y += 0.01;
        }

        this.renderer.render(this.scene, this.camera);
    }
}

// Chat functionality - because what's dating without conversation?
function sendMessage() {
    const input = document.getElementById('messageInput');
    const message = input.value.trim();

    if (message) {
        const chatMessages = document.getElementById('chatMessages');
        const messageElement = document.createElement('div');
        messageElement.textContent = `You: ${message}`;
        messageElement.style.margin = '5px 0';
        messageElement.style.padding = '8px';
        messageElement.style.background = '#f0f0f0';
        messageElement.style.borderRadius = '10px';

        chatMessages.appendChild(messageElement);
        chatMessages.scrollTop = chatMessages.scrollHeight; // Auto-scroll to bottom

        input.value = ''; // Clear input

        // Simulate response (in real app, this would come from other users)
        setTimeout(() => {
            const responses = [
                "That's so interesting! Tell me more!",
                "I love that! 😊",
                "Really? Me too!",
                "You're funny! 😄",
                "That's an amazing pickup line! 🤣"
            ];

            const responseElement = document.createElement('div');
            responseElement.textContent = `Potential Match: ${responses[Math.floor(Math.random() * responses.length)]}`;
            responseElement.style.margin = '5px 0';
            responseElement.style.padding = '8px';
            responseElement.style.background = '#ffebee';
            responseElement.style.borderRadius = '10px';

            chatMessages.appendChild(responseElement);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }, 1000 + Math.random() * 2000);
    }
}

// Handle Enter key in chat input
document.getElementById('messageInput').addEventListener('keypress', function(e) {
    if (e.key === 'Enter') {
        sendMessage();
    }
});

// Let's get this virtual romance started!
let datingScene;

window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    console.log("Dating scene initialized! Let the digital romance begin! 💖");

    // Add click handler to focus chat input
    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built So Far: 🎉

  1. A 3D Environment: A lovely green space with trees and a rotating heart
  2. Basic Avatars: Simple stick-figure people that bob gently (they're nervous!)
  3. Chat Interface: Where you can practice your pickup lines
  4. Responsive Design: Works on any screen size (love is universal!)

Key Three.js Concepts Explained: 📚

Next Time in Part 2: 🚀

We'll upgrade our basic stick figures to proper 3D avatars with:

Remember: In the world of 3D dating, the only thing that should be 2D is the screens we're building it on! 😄

Current Project Status: We've built the virtual bar, now we just need to teach these avatars how to flirt! Stay tuned for Part 2!


Disclaimer: No actual hearts were broken during the development of this tutorial. GPUs may get warm from all the rendered romance!

Part 2: Creating Customizable 3D Avatars - Making Digital Heartthrobs! 💖

Welcome back, cupid-coder! In Part 1, we built a lovely 3D environment with some basic stick figures. But let's be honest - our current avatars look like they escaped from a geometry textbook! Time to give them some personality and make them actually date-worthy!

Step 1: Avatar Class - Because Every Heart Needs a Body! 💃

Let's create a proper Avatar class that can handle customization, animations, and all the fancy stuff that makes digital dating exciting:

// avatar.js - Where basic shapes become beautiful people!

class Avatar {
    constructor(options = {}) {
        this.options = {
            gender: options.gender || 'neutral',
            skinTone: options.skinTone || 0xFFDBAC,
            hairColor: options.hairColor || 0x8B4513,
            clothingColor: options.clothingColor || 0x4169E1,
            name: options.name || 'Mysterious Stranger',
            ...options
        };

        this.mesh = new THREE.Group();
        this.animations = {};
        this.currentAnimation = null;

        this.createBody();
        this.createFace();
        this.createHair();
        this.createClothing();

        console.log(`Avatar "${this.options.name}" created! Ready to mingle! 😎`);
    }

    createBody() {
        // Head - much better than our previous sphere!
        const headGeometry = new THREE.SphereGeometry(0.4, 32, 32);
        const headMaterial = new THREE.MeshStandardMaterial({ 
            color: this.options.skinTone,
            roughness: 0.7,
            metalness: 0.1
        });
        this.head = new THREE.Mesh(headGeometry, headMaterial);
        this.head.position.y = 1.6;
        this.head.castShadow = true;
        this.mesh.add(this.head);

        // Neck
        const neckGeometry = new THREE.CylinderGeometry(0.1, 0.12, 0.3, 8);
        const neckMaterial = new THREE.MeshStandardMaterial({ 
            color: this.options.skinTone 
        });
        this.neck = new THREE.Mesh(neckGeometry, neckMaterial);
        this.neck.position.y = 1.35;
        this.neck.castShadow = true;
        this.mesh.add(this.neck);

        // Torso - with actual shape!
        const torsoGeometry = new THREE.CylinderGeometry(0.5, 0.6, 1.2, 16);
        const torsoMaterial = new THREE.MeshStandardMaterial({ 
            color: this.options.clothingColor,
            roughness: 0.8
        });
        this.torso = new THREE.Mesh(torsoGeometry, torsoMaterial);
        this.torso.position.y = 0.6;
        this.torso.castShadow = true;
        this.mesh.add(this.torso);

        // Arms - they can actually hug now!
        this.createArms();

        // Legs - for walking toward true love!
        this.createLegs();
    }

    createArms() {
        const armGeometry = new THREE.CylinderGeometry(0.08, 0.1, 1, 8);
        const armMaterial = new THREE.MeshStandardMaterial({ 
            color: this.options.skinTone 
        });

        // Left arm
        this.leftArm = new THREE.Mesh(armGeometry, armMaterial);
        this.leftArm.position.set(0.55, 0.9, 0);
        this.leftArm.rotation.z = Math.PI / 6; // Slight natural angle
        this.leftArm.castShadow = true;
        this.mesh.add(this.leftArm);

        // Right arm
        this.rightArm = new THREE.Mesh(armGeometry, armMaterial);
        this.rightArm.position.set(-0.55, 0.9, 0);
        this.rightArm.rotation.z = -Math.PI / 6;
        this.rightArm.castShadow = true;
        this.mesh.add(this.rightArm);

        // Hands
        const handGeometry = new THREE.SphereGeometry(0.12, 16, 16);
        const handMaterial = new THREE.MeshStandardMaterial({ 
            color: this.options.skinTone 
        });

        this.leftHand = new THREE.Mesh(handGeometry, handMaterial);
        this.leftHand.position.set(0.9, 0.45, 0);
        this.mesh.add(this.leftHand);

        this.rightHand = new THREE.Mesh(handGeometry, handMaterial);
        this.rightHand.position.set(-0.9, 0.45, 0);
        this.mesh.add(this.rightHand);
    }

    createLegs() {
        const legGeometry = new THREE.CylinderGeometry(0.12, 0.15, 1.2, 8);
        const legMaterial = new THREE.MeshStandardMaterial({ 
            color: 0x2F4F4F // Pants color
        });

        // Left leg
        this.leftLeg = new THREE.Mesh(legGeometry, legMaterial);
        this.leftLeg.position.set(0.2, -0.6, 0);
        this.leftLeg.castShadow = true;
        this.mesh.add(this.leftLeg);

        // Right leg
        this.rightLeg = new THREE.Mesh(legGeometry, legMaterial);
        this.rightLeg.position.set(-0.2, -0.6, 0);
        this.rightLeg.castShadow = true;
        this.mesh.add(this.rightLeg);

        // Feet
        const footGeometry = new THREE.BoxGeometry(0.25, 0.1, 0.4);
        const footMaterial = new THREE.MeshStandardMaterial({ 
            color: 0x000000 // Shoe color
        });

        this.leftFoot = new THREE.Mesh(footGeometry, footMaterial);
        this.leftFoot.position.set(0.2, -1.25, 0.1);
        this.mesh.add(this.leftFoot);

        this.rightFoot = new THREE.Mesh(footGeometry, footMaterial);
        this.rightFoot.position.set(-0.2, -1.25, 0.1);
        this.mesh.add(this.rightFoot);
    }

    createFace() {
        // Eyes - windows to the digital soul!
        const eyeGeometry = new THREE.SphereGeometry(0.06, 12, 12);
        const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });

        this.leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
        this.leftEye.position.set(0.15, 1.65, 0.35);
        this.mesh.add(this.leftEye);

        this.rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
        this.rightEye.position.set(-0.15, 1.65, 0.35);
        this.mesh.add(this.rightEye);

        // Mouth - for smiling at potential matches!
        const mouthGeometry = new THREE.TorusGeometry(0.1, 0.02, 8, 12, Math.PI);
        const mouthMaterial = new THREE.MeshStandardMaterial({ color: 0xFF69B4 });
        this.mouth = new THREE.Mesh(mouthGeometry, mouthMaterial);
        this.mouth.position.set(0, 1.5, 0.38);
        this.mouth.rotation.x = Math.PI / 2;
        this.mesh.add(this.mouth);

        // Eyebrows - for expressing interest (or skepticism)!
        const browGeometry = new THREE.BoxGeometry(0.2, 0.02, 0.02);
        const browMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });

        this.leftBrow = new THREE.Mesh(browGeometry, browMaterial);
        this.leftBrow.position.set(0.15, 1.75, 0.33);
        this.leftBrow.rotation.z = Math.PI / 8;
        this.mesh.add(this.leftBrow);

        this.rightBrow = new THREE.Mesh(browGeometry, browMaterial);
        this.rightBrow.position.set(-0.15, 1.75, 0.33);
        this.rightBrow.rotation.z = -Math.PI / 8;
        this.mesh.add(this.rightBrow);
    }

    createHair() {
        // Basic hair - because bad hair days don't exist in 3D!
        const hairGeometry = new THREE.SphereGeometry(0.45, 16, 16);
        const hairMaterial = new THREE.MeshStandardMaterial({ 
            color: this.options.hairColor,
            roughness: 0.9
        });

        this.hair = new THREE.Mesh(hairGeometry, hairMaterial);
        this.hair.position.y = 1.8;
        this.hair.scale.set(1, 0.6, 1);
        this.mesh.add(this.hair);
    }

    createClothing() {
        // Add some clothing details because nudity is bad for business!
        const collarGeometry = new THREE.TorusGeometry(0.45, 0.05, 8, 16, Math.PI);
        const collarMaterial = new THREE.MeshStandardMaterial({ color: 0xFFFFFF });

        this.collar = new THREE.Mesh(collarGeometry, collarMaterial);
        this.collar.position.y = 1.1;
        this.collar.rotation.x = Math.PI / 2;
        this.mesh.add(this.collar);
    }

    // Animation methods - making our avatars come alive!
    wave() {
        console.log(`${this.options.name} is waving hello! 👋`);

        const startTime = Date.now();
        const originalRotation = this.rightArm.rotation.z;

        const animateWave = () => {
            const elapsed = Date.now() - startTime;
            const progress = Math.sin(elapsed * 0.01) * 0.5;

            this.rightArm.rotation.z = originalRotation + progress;
            this.rightHand.rotation.z = progress * 2;

            if (elapsed < 2000) { // Wave for 2 seconds
                requestAnimationFrame(animateWave);
            } else {
                // Return to original position
                this.rightArm.rotation.z = originalRotation;
                this.rightHand.rotation.z = 0;
            }
        };

        animateWave();
    }

    dance() {
        console.log(`${this.options.name} is busting out some moves! 💃`);

        const startTime = Date.now();

        const animateDance = () => {
            const elapsed = Date.now() - startTime;
            const beat = elapsed * 0.01;

            // Fun dance movements!
            this.mesh.rotation.y = Math.sin(beat) * 0.3;
            this.leftArm.rotation.z = Math.sin(beat) * 0.5;
            this.rightArm.rotation.z = -Math.sin(beat) * 0.5;
            this.leftLeg.rotation.x = Math.sin(beat + 1) * 0.3;
            this.rightLeg.rotation.x = -Math.sin(beat + 1) * 0.3;

            // Head bob
            this.head.position.y = 1.6 + Math.sin(beat * 2) * 0.05;

            if (elapsed < 5000) { // Dance for 5 seconds
                requestAnimationFrame(animateDance);
            } else {
                // Reset positions
                this.resetPose();
            }
        };

        animateDance();
    }

    resetPose() {
        // Return to default pose
        this.mesh.rotation.y = 0;
        this.leftArm.rotation.z = Math.PI / 6;
        this.rightArm.rotation.z = -Math.PI / 6;
        this.leftLeg.rotation.x = 0;
        this.rightLeg.rotation.x = 0;
        this.head.position.y = 1.6;
        this.rightHand.rotation.z = 0;
        this.leftHand.rotation.z = 0;
    }

    blush() {
        // Make the avatar blush when complimented!
        console.log(`${this.options.name} is blushing! 😊`);

        const originalColor = this.head.material.color.getHex();
        this.head.material.color.setHex(0xFFB6C1); // Pink color

        setTimeout(() => {
            this.head.material.color.setHex(originalColor);
        }, 2000);
    }

    lookAt(target) {
        // Make the avatar look at a specific point or another avatar
        const headWorldPos = new THREE.Vector3();
        this.head.getWorldPosition(headWorldPos);

        const direction = new THREE.Vector3();
        direction.subVectors(target, headWorldPos).normalize();

        // Simple look-at for the head
        this.head.rotation.y = Math.atan2(direction.x, direction.z);
    }
}

Step 2: Avatar Customization Interface - Make Your Dream Date! 🎨

Now let's create an interface for users to customize their avatars:

<!-- Add this to your HTML file, right after the chat UI -->
<div id="avatarCustomization" style="display: none;">
    <div style="background: white; padding: 20px; border-radius: 15px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1000; width: 400px;">
        <h2>Create Your Avatar! ✨</h2>

        <div style="margin: 10px 0;">
            <label>Name:</label>
            <input type="text" id="avatarName" placeholder="Your dating persona">
        </div>

        <div style="margin: 10px 0;">
            <label>Gender:</label>
            <select id="avatarGender">
                <option value="male">Male</option>
                <option value="female">Female</option>
                <option value="neutral">Neutral</option>
            </select>
        </div>

        <div style="margin: 10px 0;">
            <label>Skin Tone:</label>
            <input type="color" id="avatarSkinTone" value="#f0d9b5">
        </div>

        <div style="margin: 10px 0;">
            <label>Hair Color:</label>
            <input type="color" id="avatarHairColor" value="#8b4513">
        </div>

        <div style="margin: 10px 0;">
            <label>Clothing Color:</label>
            <input type="color" id="avatarClothingColor" value="#4169e1">
        </div>

        <button onclick="createCustomAvatar()" style="background: #ff6b6b; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer;">
            Create My Avatar! 💕
        </button>
    </div>
</div>

Step 3: Updated DatingScene Class - Managing Our New Fancy Avatars! 🎭

Let's update our main DatingScene class to use our new Avatar system:

// Update your DatingScene class with these new methods

class DatingScene {
    // ... (previous code remains the same)

    addSampleAvatars() {
        console.log("Adding sophisticated avatars... no more stick figures! 🎉");

        // Create sample avatars with different characteristics
        const sampleAvatars = [
            {
                name: "Alex",
                gender: "male",
                skinTone: 0xF0D9B5,
                hairColor: 0x2C1810,
                clothingColor: 0x2E8B57
            },
            {
                name: "Sam",
                gender: "neutral", 
                skinTone: 0xFFDBAC,
                hairColor: 0xFFD700,
                clothingColor: 0xFF69B4
            },
            {
                name: "Taylor",
                gender: "female",
                skinTone: 0xE8B298,
                hairColor: 0x8B4513,
                clothingColor: 0x9370DB
            }
        ];

        sampleAvatars.forEach((avatarConfig, index) => {
            const avatar = new Avatar(avatarConfig);

            // Position avatars in a nice arc
            const angle = (index / sampleAvatars.length) * Math.PI * 0.8 - Math.PI * 0.4;
            const radius = 6;

            avatar.mesh.position.set(
                Math.sin(angle) * radius,
                0,
                Math.cos(angle) * radius - 3
            );

            // Make them face the center
            avatar.mesh.rotation.y = -angle;

            this.scene.add(avatar.mesh);
            this.avatars.push(avatar);

            // Add random animations to make the scene lively
            this.addRandomBehaviors(avatar);
        });

        // Show customization interface for the user
        setTimeout(() => {
            this.showAvatarCustomization();
        }, 1000);
    }

    showAvatarCustomization() {
        document.getElementById('avatarCustomization').style.display = 'block';
    }

    createUserAvatar(config) {
        this.currentUser = new Avatar(config);
        this.currentUser.mesh.position.set(0, 0, 3);
        this.scene.add(this.currentUser.mesh);
        this.avatars.push(this.currentUser);

        console.log(`Welcome, ${config.name}! Your avatar is ready to find love! 💖`);

        // Hide customization UI
        document.getElementById('avatarCustomization').style.display = 'none';

        // Make other avatars look at the new user
        this.avatars.forEach(avatar => {
            if (avatar !== this.currentUser) {
                const userPosition = this.currentUser.mesh.position.clone();
                avatar.lookAt(userPosition);

                // Make them wave at the new user
                setTimeout(() => avatar.wave(), 1000);
            }
        });
    }

    addRandomBehaviors(avatar) {
        // Randomly trigger animations to make avatars seem alive
        setInterval(() => {
            if (Math.random() > 0.7) { // 30% chance every interval
                const behaviors = [() => avatar.wave(), () => avatar.dance()];
                const randomBehavior = behaviors[Math.floor(Math.random() * behaviors.length)];
                randomBehavior();
            }
        }, 10000); // Every 10 seconds
    }

    // New method to handle avatar interactions
    handleAvatarClick(avatar) {
        if (avatar === this.currentUser) return;

        console.log(`You clicked on ${avatar.options.name}!`);

        // Make the clicked avatar react
        avatar.wave();
        avatar.blush();

        // Show info about the avatar
        this.showAvatarInfo(avatar);
    }

    showAvatarInfo(avatar) {
        // Create or update info display
        let infoDiv = document.getElementById('avatarInfo');
        if (!infoDiv) {
            infoDiv = document.createElement('div');
            infoDiv.id = 'avatarInfo';
            infoDiv.style.cssText = `
                position: absolute;
                top: 20px;
                right: 20px;
                background: rgba(255, 255, 255, 0.9);
                padding: 15px;
                border-radius: 10px;
                max-width: 200px;
                z-index: 100;
            `;
            document.getElementById('container').appendChild(infoDiv);
        }

        infoDiv.innerHTML = `
            <h3>${avatar.options.name}</h3>
            <p>Gender: ${avatar.options.gender}</p>
            <p>Status: Looking for love! 💕</p>
            <button onclick="datingScene.startChatWith('${avatar.options.name}')" 
                    style="background: #ff6b6b; color: white; border: none; padding: 8px 15px; border-radius: 5px; cursor: pointer;">
                Start Chat! 💬
            </button>
        `;

        // Auto-hide after 10 seconds
        setTimeout(() => {
            if (infoDiv.parentNode) {
                infoDiv.parentNode.removeChild(infoDiv);
            }
        }, 10000);
    }

    startChatWith(avatarName) {
        const avatar = this.avatars.find(a => a.options.name === avatarName);
        if (avatar) {
            // Focus chat input
            document.getElementById('messageInput').focus();

            // Add welcome message
            const chatMessages = document.getElementById('chatMessages');
            const welcomeElement = document.createElement('div');
            welcomeElement.textContent = `System: You started chatting with ${avatarName}! Say hello! 👋`;
            welcomeElement.style.cssText = 'margin: 5px 0; padding: 8px; background: #e3f2fd; border-radius: 10px;';
            chatMessages.appendChild(welcomeElement);

            // Make avatar look at user
            const userPosition = this.currentUser.mesh.position.clone();
            avatar.lookAt(userPosition);
            avatar.blush();
        }
    }
}

// Global function to create custom avatar
function createCustomAvatar() {
    const name = document.getElementById('avatarName').value || 'Mysterious Dater';
    const gender = document.getElementById('avatarGender').value;
    const skinTone = parseInt(document.getElementById('avatarSkinTone').value.replace('#', '0x'));
    const hairColor = parseInt(document.getElementById('avatarHairColor').value.replace('#', '0x'));
    const clothingColor = parseInt(document.getElementById('avatarClothingColor').value.replace('#', '0x'));

    const avatarConfig = {
        name,
        gender,
        skinTone,
        hairColor,
        clothingColor
    };

    datingScene.createUserAvatar(avatarConfig);
}

// Add raycasting for avatar interaction
function setupAvatarInteraction() {
    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();

    function onMouseClick(event) {
        // Calculate mouse position in normalized device coordinates
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

        // Update the raycaster
        raycaster.setFromCamera(mouse, datingScene.camera);

        // Calculate objects intersecting the ray
        const intersects = raycaster.intersectObjects(
            datingScene.avatars.map(avatar => avatar.mesh),
            true
        );

        if (intersects.length > 0) {
            // Find which avatar was clicked
            const clickedObject = intersects[0].object;
            const clickedAvatar = datingScene.avatars.find(avatar => 
                avatar.mesh === clickedObject.parent || 
                avatar.mesh.children.includes(clickedObject)
            );

            if (clickedAvatar) {
                datingScene.handleAvatarClick(clickedAvatar);
            }
        }
    }

    window.addEventListener('click', onMouseClick);
}

// Update the DOMContentLoaded event listener
window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    console.log("Dating scene initialized with fancy avatars! 💖");

    // Setup avatar interaction
    setupAvatarInteraction();

    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built in Part 2: 🎉

  1. Sophisticated Avatar System: No more stick figures! We now have proper 3D avatars with:

    • Detailed body parts (head, torso, arms, legs, hands, feet)
    • Expressive faces (eyes, mouth, eyebrows)
    • Customizable appearance (skin tone, hair color, clothing)
    • Smooth animations (waving, dancing, blushing)
  2. Avatar Customization: Users can create their own unique dating persona

  3. Interactive Avatars: Click on avatars to learn about them and start chatting

  4. Social Behaviors: Avatars automatically wave, dance, and look at each other

Key Features Explained: 🔑

Next Time in Part 3: 🚀

We'll add:

Current Project Status: Our avatars now have personality and can interact! They're no longer lonely stick figures - they're ready to socialize and find digital love! 💕


Fun Fact: Our avatars now have more dating experience than most of us developers! They wave, dance, and blush on command - if only real dating were this easy! 😄

Part 3: Movement, Camera Controls & Proximity Chat - Making Moves in 3D! 🕺

Welcome back, digital cupid! Our avatars are looking fabulous, but they're just standing around like wallflowers at a middle school dance. Time to teach them how to move, groove, and get close enough for some meaningful conversation!

Step 1: Avatar Movement System - Strut Your Stuff! 🚶‍♂️

Let's add movement controls so users can navigate our virtual dating world:

// movement.js - Because love isn't a spectator sport!

class AvatarMovement {
    constructor(avatar, scene) {
        this.avatar = avatar;
        this.scene = scene;
        this.moveSpeed = 0.1;
        this.isMoving = false;
        this.targetPosition = null;
        this.moveAnimationId = null;

        // Movement state
        this.movementState = {
            forward: false,
            backward: false,
            left: false,
            right: false
        };

        this.setupControls();
        this.setupClickToMove();

        console.log(`Movement system ready for ${avatar.options.name}! Let's go find love! 🚶‍♀️`);
    }

    setupControls() {
        // Keyboard controls for smooth movement
        document.addEventListener('keydown', (event) => this.handleKeyDown(event));
        document.addEventListener('keyup', (event) => this.handleKeyUp(event));

        // Touch controls for mobile devices
        this.setupTouchControls();
    }

    handleKeyDown(event) {
        if (!this.avatar.mesh.visible) return;

        switch(event.key.toLowerCase()) {
            case 'w':
            case 'arrowup':
                this.movementState.forward = true;
                break;
            case 's':
            case 'arrowdown':
                this.movementState.backward = true;
                break;
            case 'a':
            case 'arrowleft':
                this.movementState.left = true;
                break;
            case 'd':
            case 'arrowright':
                this.movementState.right = true;
                break;
            case ' ':
                this.jump();
                break;
            case 'q':
                this.avatar.dance();
                break;
            case 'e':
                this.avatar.wave();
                break;
        }

        this.updateMovement();
    }

    handleKeyUp(event) {
        switch(event.key.toLowerCase()) {
            case 'w':
            case 'arrowup':
                this.movementState.forward = false;
                break;
            case 's':
            case 'arrowdown':
                this.movementState.backward = false;
                break;
            case 'a':
            case 'arrowleft':
                this.movementState.left = false;
                break;
            case 'd':
            case 'arrowright':
                this.movementState.right = false;
                break;
        }

        this.updateMovement();
    }

    updateMovement() {
        const wasMoving = this.isMoving;
        this.isMoving = Object.values(this.movementState).some(state => state);

        if (this.isMoving) {
            this.moveWithKeyboard();

            if (!wasMoving) {
                this.startWalkAnimation();
                console.log(`${this.avatar.options.name} is on the move! 🚶‍♂️`);
            }
        } else if (wasMoving) {
            this.stopWalkAnimation();
            console.log(`${this.avatar.options.name} has arrived! 🛑`);
        }
    }

    moveWithKeyboard() {
        const moveVector = new THREE.Vector3(0, 0, 0);

        if (this.movementState.forward) moveVector.z -= 1;
        if (this.movementState.backward) moveVector.z += 1;
        if (this.movementState.left) moveVector.x -= 1;
        if (this.movementState.right) moveVector.x += 1;

        if (moveVector.length() > 0) {
            moveVector.normalize().multiplyScalar(this.moveSpeed);

            // Update avatar position
            this.avatar.mesh.position.add(moveVector);

            // Rotate avatar to face movement direction
            if (moveVector.length() > 0.001) {
                const targetAngle = Math.atan2(moveVector.x, moveVector.z);
                this.avatar.mesh.rotation.y = targetAngle;
            }

            // Check boundaries
            this.enforceBoundaries();

            // Check proximity to other avatars for chat
            this.checkProximityChat();
        }
    }

    setupClickToMove() {
        // Click on ground to move to that position
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();

        document.addEventListener('click', (event) => {
            if (event.target.tagName === 'INPUT' || event.target.tagName === 'BUTTON') return;

            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

            raycaster.setFromCamera(mouse, this.scene.camera);

            // Only intersect with ground plane
            const ground = this.scene.scene.children.find(child => 
                child.geometry && child.geometry.type === 'PlaneGeometry'
            );

            if (ground) {
                const intersects = raycaster.intersectObject(ground);

                if (intersects.length > 0) {
                    const targetPos = intersects[0].point;
                    this.moveTo(targetPos);

                    // Show movement indicator
                    this.showMovementIndicator(targetPos);
                }
            }
        });
    }

    moveTo(targetPosition) {
        this.targetPosition = targetPosition.clone();
        this.targetPosition.y = 0; // Keep on ground level

        // Stop any existing movement
        if (this.moveAnimationId) {
            cancelAnimationFrame(this.moveAnimationId);
        }

        this.startWalkAnimation();
        this.animateMoveToTarget();
    }

    animateMoveToTarget() {
        if (!this.targetPosition) return;

        const currentPos = this.avatar.mesh.position;
        const direction = new THREE.Vector3()
            .subVectors(this.targetPosition, currentPos)
            .normalize();

        const distance = currentPos.distanceTo(this.targetPosition);

        if (distance > 0.1) {
            // Move toward target
            const moveStep = direction.multiplyScalar(Math.min(this.moveSpeed, distance));
            currentPos.add(moveStep);

            // Rotate to face movement direction
            const targetAngle = Math.atan2(direction.x, direction.z);
            this.avatar.mesh.rotation.y = targetAngle;

            // Check proximity
            this.checkProximityChat();

            // Continue animation
            this.moveAnimationId = requestAnimationFrame(() => this.animateMoveToTarget());
        } else {
            // Arrived at target
            this.stopWalkAnimation();
            this.targetPosition = null;
            this.moveAnimationId = null;
            console.log(`${this.avatar.options.name} has reached the destination! 🎯`);
        }
    }

    startWalkAnimation() {
        // Animate legs and arms for walking
        if (this.walkAnimationId) return;

        let walkTime = 0;

        const animateWalk = () => {
            walkTime += 0.2;

            // Animate legs
            this.avatar.leftLeg.rotation.x = Math.sin(walkTime) * 0.5;
            this.avatar.rightLeg.rotation.x = -Math.sin(walkTime) * 0.5;

            // Animate arms
            this.avatar.leftArm.rotation.z = Math.PI / 6 + Math.sin(walkTime) * 0.3;
            this.avatar.rightArm.rotation.z = -Math.PI / 6 - Math.sin(walkTime) * 0.3;

            // Gentle head bob
            this.avatar.head.position.y = 1.6 + Math.sin(walkTime * 2) * 0.03;

            if (this.isMoving || this.targetPosition) {
                this.walkAnimationId = requestAnimationFrame(animateWalk);
            }
        };

        this.walkAnimationId = requestAnimationFrame(animateWalk);
    }

    stopWalkAnimation() {
        if (this.walkAnimationId) {
            cancelAnimationFrame(this.walkAnimationId);
            this.walkAnimationId = null;
        }

        // Reset to default pose
        this.avatar.resetPose();
    }

    jump() {
        console.log(`${this.avatar.options.name} is jumping for joy! 🦘`);

        const originalY = this.avatar.mesh.position.y;
        const jumpHeight = 0.5;
        const jumpDuration = 800; // ms

        const startTime = Date.now();

        const animateJump = () => {
            const elapsed = Date.now() - startTime;
            const progress = elapsed / jumpDuration;

            if (progress < 1) {
                // Parabolic jump curve
                const jumpProgress = progress < 0.5 ? progress * 2 : (1 - progress) * 2;
                this.avatar.mesh.position.y = originalY + Math.sin(jumpProgress * Math.PI) * jumpHeight;

                requestAnimationFrame(animateJump);
            } else {
                this.avatar.mesh.position.y = originalY;
            }
        };

        animateJump();
    }

    enforceBoundaries() {
        // Keep avatars within the scene boundaries
        const boundary = 20;
        const pos = this.avatar.mesh.position;

        pos.x = THREE.MathUtils.clamp(pos.x, -boundary, boundary);
        pos.z = THREE.MathUtils.clamp(pos.z, -boundary, boundary);
    }

    showMovementIndicator(position) {
        // Remove existing indicator
        let indicator = this.scene.scene.getObjectByName('movementIndicator');
        if (indicator) {
            this.scene.scene.remove(indicator);
        }

        // Create new indicator
        const circleGeometry = new THREE.RingGeometry(0.3, 0.4, 16);
        const circleMaterial = new THREE.MeshBasicMaterial({ 
            color: 0x00ff00, 
            transparent: true, 
            opacity: 0.7,
            side: THREE.DoubleSide
        });

        indicator = new THREE.Mesh(circleGeometry, circleMaterial);
        indicator.rotation.x = -Math.PI / 2;
        indicator.position.copy(position);
        indicator.position.y += 0.1;
        indicator.name = 'movementIndicator';

        this.scene.scene.add(indicator);

        // Animate and fade out
        let opacity = 0.7;
        const fadeOut = () => {
            opacity -= 0.02;
            circleMaterial.opacity = opacity;

            if (opacity > 0) {
                requestAnimationFrame(fadeOut);
            } else {
                this.scene.scene.remove(indicator);
            }
        };

        setTimeout(() => fadeOut(), 500);
    }

    setupTouchControls() {
        // Virtual joystick for mobile devices
        const joystickContainer = document.createElement('div');
        joystickContainer.style.cssText = `
            position: fixed;
            bottom: 100px;
            left: 50px;
            width: 120px;
            height: 120px;
            background: rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            border: 2px solid rgba(255, 255, 255, 0.5);
            z-index: 1000;
            touch-action: none;
        `;

        const joystickKnob = document.createElement('div');
        joystickKnob.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            width: 50px;
            height: 50px;
            background: rgba(255, 255, 255, 0.8);
            border-radius: 50%;
            transform: translate(-50%, -50%);
        `;

        joystickContainer.appendChild(joystickKnob);
        document.getElementById('container').appendChild(joystickContainer);

        // Touch event handlers
        let isTouching = false;

        joystickContainer.addEventListener('touchstart', (e) => {
            e.preventDefault();
            isTouching = true;
        });

        joystickContainer.addEventListener('touchmove', (e) => {
            if (!isTouching) return;
            e.preventDefault();

            const touch = e.touches[0];
            const rect = joystickContainer.getBoundingClientRect();
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;

            const deltaX = touch.clientX - centerX;
            const deltaY = touch.clientY - centerY;

            // Calculate joystick position (clamped to circle)
            const distance = Math.min(Math.sqrt(deltaX * deltaX + deltaY * deltaY), rect.width / 2);
            const angle = Math.atan2(deltaY, deltaX);

            const knobX = Math.cos(angle) * distance;
            const knobY = Math.sin(angle) * distance;

            joystickKnob.style.transform = `translate(calc(-50% + ${knobX}px), calc(-50% + ${knobY}px))`;

            // Convert to movement
            const normalizedX = knobX / (rect.width / 2);
            const normalizedY = knobY / (rect.height / 2);

            this.movementState.forward = normalizedY < -0.3;
            this.movementState.backward = normalizedY > 0.3;
            this.movementState.left = normalizedX < -0.3;
            this.movementState.right = normalizedX > 0.3;

            this.updateMovement();
        });

        joystickContainer.addEventListener('touchend', (e) => {
            e.preventDefault();
            isTouching = false;
            joystickKnob.style.transform = 'translate(-50%, -50%)';

            // Stop movement
            this.movementState.forward = false;
            this.movementState.backward = false;
            this.movementState.left = false;
            this.movementState.right = false;
            this.updateMovement();
        });
    }

    checkProximityChat() {
        // Check if avatar is close enough to others to auto-start chat
        const chatDistance = 3; // Distance threshold for auto-chat

        this.scene.avatars.forEach(otherAvatar => {
            if (otherAvatar === this.avatar) return;

            const distance = this.avatar.mesh.position.distanceTo(otherAvatar.mesh.position);

            if (distance < chatDistance && !this.scene.activeChatPartners.has(otherAvatar)) {
                this.scene.startProximityChat(this.avatar, otherAvatar);
            }
        });
    }
}

Step 2: Advanced Camera Controls - Get the Perfect Angle! 📷

Let's upgrade our camera system for better viewing:

// camera.js - Because everyone wants to look their best!

class DatingCamera {
    constructor(camera, scene, targetAvatar) {
        this.camera = camera;
        this.scene = scene;
        this.targetAvatar = targetAvatar;

        this.modes = {
            FOLLOW: 'follow',
            ORBIT: 'orbit',
            FIRST_PERSON: 'first_person',
            FREE: 'free'
        };

        this.currentMode = this.modes.FOLLOW;
        this.orbitDistance = 8;
        this.orbitAngle = 0;
        this.orbitHeight = 3;

        this.setupCameraControls();
        this.setupModeSwitcher();

        console.log("Dating camera ready! Say cheese! 📸");
    }

    setupCameraControls() {
        this.mouseState = {
            isDown: false,
            lastX: 0,
            lastY: 0
        };

        // Mouse controls for orbit mode
        document.addEventListener('mousedown', (e) => {
            if (e.button === 2) { // Right click
                this.mouseState.isDown = true;
                this.mouseState.lastX = e.clientX;
                this.mouseState.lastY = e.clientY;
            }
        });

        document.addEventListener('mouseup', (e) => {
            if (e.button === 2) {
                this.mouseState.isDown = false;
            }
        });

        document.addEventListener('mousemove', (e) => {
            if (this.mouseState.isDown && this.currentMode === this.modes.ORBIT) {
                const deltaX = e.clientX - this.mouseState.lastX;
                const deltaY = e.clientY - this.mouseState.lastY;

                this.orbitAngle += deltaX * 0.01;
                this.orbitHeight = THREE.MathUtils.clamp(
                    this.orbitHeight + deltaY * 0.01, 
                    1, 
                    10
                );

                this.mouseState.lastX = e.clientX;
                this.mouseState.lastY = e.clientY;

                this.updateOrbitCamera();
            }
        });

        // Prevent context menu on right click
        document.addEventListener('contextmenu', (e) => e.preventDefault());

        // Mouse wheel for zoom
        document.addEventListener('wheel', (e) => {
            e.preventDefault();

            if (this.currentMode === this.modes.ORBIT) {
                this.orbitDistance = THREE.MathUtils.clamp(
                    this.orbitDistance + e.deltaY * 0.01,
                    3,
                    20
                );
                this.updateOrbitCamera();
            }
        });
    }

    updateOrbitCamera() {
        if (!this.targetAvatar) return;

        const avatarPos = this.targetAvatar.mesh.position.clone();

        const cameraX = avatarPos.x + Math.sin(this.orbitAngle) * this.orbitDistance;
        const cameraZ = avatarPos.z + Math.cos(this.orbitAngle) * this.orbitDistance;

        this.camera.position.set(cameraX, avatarPos.y + this.orbitHeight, cameraZ);
        this.camera.lookAt(avatarPos.x, avatarPos.y + 1, avatarPos.z);
    }

    updateFollowCamera() {
        if (!this.targetAvatar) return;

        const avatarPos = this.targetAvatar.mesh.position.clone();
        const behindDistance = 6;
        const height = 4;

        // Calculate position behind avatar
        const direction = new THREE.Vector3(
            Math.sin(this.targetAvatar.mesh.rotation.y),
            0,
            Math.cos(this.targetAvatar.mesh.rotation.y)
        );

        const cameraPos = avatarPos.clone()
            .sub(direction.multiplyScalar(behindDistance))
            .add(new THREE.Vector3(0, height, 0));

        // Smooth camera movement
        this.camera.position.lerp(cameraPos, 0.1);
        this.camera.lookAt(avatarPos.x, avatarPos.y + 1, avatarPos.z);
    }

    setFirstPersonMode() {
        if (!this.targetAvatar) return;

        const avatarPos = this.targetAvatar.mesh.position.clone();
        this.camera.position.set(
            avatarPos.x,
            avatarPos.y + 1.6, // Eye level
            avatarPos.z
        );

        // Attach camera to avatar's head
        this.camera.rotation.y = this.targetAvatar.mesh.rotation.y;
    }

    switchMode(mode) {
        this.currentMode = mode;
        console.log(`Camera switched to ${mode} mode! 🎥`);

        switch(mode) {
            case this.modes.FOLLOW:
                break;
            case this.modes.ORBIT:
                this.updateOrbitCamera();
                break;
            case this.modes.FIRST_PERSON:
                this.setFirstPersonMode();
                break;
            case this.modes.FREE:
                // Free camera logic would go here
                break;
        }
    }

    setupModeSwitcher() {
        // Create camera mode switcher UI
        const cameraUI = document.createElement('div');
        cameraUI.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 10px;
            border-radius: 10px;
            z-index: 100;
        `;

        cameraUI.innerHTML = `
            <strong>Camera Mode:</strong><br>
            <button onclick="datingCamera.switchMode('follow')">Follow 👥</button>
            <button onclick="datingCamera.switchMode('orbit')">Orbit 🛸</button>
            <button onclick="datingCamera.switchMode('first_person')">First Person 👀</button>
        `;

        document.getElementById('container').appendChild(cameraUI);
    }

    update() {
        // Update camera based on current mode
        switch(this.currentMode) {
            case this.modes.FOLLOW:
                this.updateFollowCamera();
                break;
            case this.modes.ORBIT:
                // Orbit camera is updated by mouse controls
                break;
            case this.modes.FIRST_PERSON:
                this.setFirstPersonMode();
                break;
            case this.modes.FREE:
                // Free camera updates would go here
                break;
        }
    }

    setTarget(avatar) {
        this.targetAvatar = avatar;
        console.log(`Camera now following ${avatar.options.name}! 📍`);
    }
}

Step 3: Proximity Chat System - Get Close and Personal! 💬

Now let's implement the proximity-based chat system:

// proximity-chat.js - Because love happens up close!

class ProximityChat {
    constructor(scene) {
        this.scene = scene;
        this.chatDistance = 3;
        this.activeChats = new Map(); // Map of chat pairs
        this.chatBubbles = new Map(); // Visual chat indicators

        console.log("Proximity chat system initialized! Get talking! 💕");
    }

    startProximityChat(avatar1, avatar2) {
        const chatKey = this.getChatKey(avatar1, avatar2);

        if (!this.activeChats.has(chatKey)) {
            console.log(`Proximity chat started between ${avatar1.options.name} and ${avatar2.options.name}! 💬`);

            this.activeChats.set(chatKey, {
                avatar1,
                avatar2,
                startTime: Date.now(),
                isActive: true
            });

            // Create visual chat indicators
            this.createChatBubbles(avatar1, avatar2);

            // Make avatars face each other
            this.makeAvatarsFaceEachOther(avatar1, avatar2);

            // Auto-start chat in UI
            this.showProximityChatUI(avatar1, avatar2);

            // Add to scene's active chat partners
            this.scene.activeChatPartners.add(avatar1);
            this.scene.activeChatPartners.add(avatar2);
        }
    }

    stopProximityChat(avatar1, avatar2) {
        const chatKey = this.getChatKey(avatar1, avatar2);

        if (this.activeChats.has(chatKey)) {
            console.log(`Proximity chat ended between ${avatar1.options.name} and ${avatar2.options.name} 👋`);

            this.activeChats.delete(chatKey);
            this.removeChatBubbles(avatar1, avatar2);

            // Remove from active chat partners
            this.scene.activeChatPartners.delete(avatar1);
            this.scene.activeChatPartners.delete(avatar2);

            // Show chat ended message
            this.showChatEndedMessage(avatar1, avatar2);
        }
    }

    getChatKey(avatar1, avatar2) {
        // Create unique key for chat pair (order doesn't matter)
        const ids = [avatar1.options.name, avatar2.options.name].sort();
        return ids.join('_');
    }

    createChatBubbles(avatar1, avatar2) {
        // Create speech bubble indicators above avatars' heads
        [avatar1, avatar2].forEach(avatar => {
            const bubbleGeometry = new THREE.SphereGeometry(0.3, 8, 6);
            const bubbleMaterial = new THREE.MeshBasicMaterial({
                color: 0x00ff00,
                transparent: true,
                opacity: 0.7
            });

            const chatBubble = new THREE.Mesh(bubbleGeometry, bubbleMaterial);
            chatBubble.position.y = 2.5;
            chatBubble.name = 'chatBubble';

            avatar.mesh.add(chatBubble);
            this.chatBubbles.set(avatar, chatBubble);

            // Animate the bubble
            this.animateChatBubble(chatBubble);
        });
    }

    animateChatBubble(bubble) {
        let scale = 1;
        let growing = false;

        const animate = () => {
            if (bubble.parent) { // Check if bubble still exists
                scale += growing ? 0.02 : -0.02;

                if (scale >= 1.2) growing = false;
                if (scale <= 0.8) growing = true;

                bubble.scale.set(scale, scale, scale);
                requestAnimationFrame(animate);
            }
        };

        animate();
    }

    removeChatBubbles(avatar1, avatar2) {
        [avatar1, avatar2].forEach(avatar => {
            const bubble = this.chatBubbles.get(avatar);
            if (bubble && bubble.parent) {
                bubble.parent.remove(bubble);
            }
            this.chatBubbles.delete(avatar);
        });
    }

    makeAvatarsFaceEachOther(avatar1, avatar2) {
        const pos1 = avatar1.mesh.position;
        const pos2 = avatar2.mesh.position;

        // Calculate direction vectors
        const dir1 = new THREE.Vector3().subVectors(pos2, pos1).normalize();
        const dir2 = new THREE.Vector3().subVectors(pos1, pos2).normalize();

        // Set rotations to face each other
        avatar1.mesh.rotation.y = Math.atan2(dir1.x, dir1.z);
        avatar2.mesh.rotation.y = Math.atan2(dir2.x, dir2.z);
    }

    showProximityChatUI(avatar1, avatar2) {
        const chatMessages = document.getElementById('chatMessages');
        const systemMessage = document.createElement('div');
        systemMessage.style.cssText = `
            margin: 10px 0;
            padding: 10px;
            background: #e8f5e8;
            border-radius: 10px;
            text-align: center;
            font-weight: bold;
        `;
        systemMessage.textContent = `💕 You're now chatting with ${avatar2.options.name}! Say hello!`;

        chatMessages.appendChild(systemMessage);
        chatMessages.scrollTop = chatMessages.scrollHeight;

        // Update chat header
        const chatHeader = document.querySelector('#chatUI h3') || (() => {
            const h3 = document.createElement('h3');
            h3.style.margin = '0 0 10px 0';
            h3.style.color = '#ff6b6b';
            document.getElementById('chatUI').insertBefore(h3, document.getElementById('chatUI').firstChild);
            return h3;
        })();

        chatHeader.textContent = `💬 Chatting with ${avatar2.options.name}`;
    }

    showChatEndedMessage(avatar1, avatar2) {
        const chatMessages = document.getElementById('chatMessages');
        const systemMessage = document.createElement('div');
        systemMessage.style.cssText = `
            margin: 10px 0;
            padding: 10px;
            background: #ffebee;
            border-radius: 10px;
            text-align: center;
            font-style: italic;
        `;
        systemMessage.textContent = `👋 ${avatar2.options.name} moved away. Chat ended.`;

        chatMessages.appendChild(systemMessage);
        chatMessages.scrollTop = chatMessages.scrollHeight;

        // Reset chat header
        const chatHeader = document.querySelector('#chatUI h3');
        if (chatHeader) {
            chatHeader.textContent = '💬 Dating Chat';
        }
    }

    update() {
        // Check all avatar pairs for proximity
        for (let i = 0; i < this.scene.avatars.length; i++) {
            for (let j = i + 1; j < this.scene.avatars.length; j++) {
                const avatar1 = this.scene.avatars[i];
                const avatar2 = this.scene.avatars[j];
                const distance = avatar1.mesh.position.distanceTo(avatar2.mesh.position);
                const chatKey = this.getChatKey(avatar1, avatar2);

                if (distance < this.chatDistance) {
                    if (!this.activeChats.has(chatKey)) {
                        this.startProximityChat(avatar1, avatar2);
                    }
                } else {
                    if (this.activeChats.has(chatKey)) {
                        this.stopProximityChat(avatar1, avatar2);
                    }
                }
            }
        }
    }
}

Step 4: Updated DatingScene Class - Bringing It All Together! 🔗

Now let's update our main DatingScene class to integrate all these new systems:

// Update the DatingScene class with new properties and methods

class DatingScene {
    constructor() {
        // ... existing properties ...

        // New properties
        this.activeChatPartners = new Set();
        this.proximityChat = null;
        this.datingCamera = null;

        this.init();
    }

    init() {
        this.createScene();
        this.createCamera();
        this.createRenderer();
        this.createLights();
        this.createEnvironment();

        // Initialize new systems
        this.proximityChat = new ProximityChat(this);

        this.animate();

        setTimeout(() => {
            document.getElementById('loadingScreen').style.display = 'none';
            document.getElementById('chatUI').style.display = 'block';
            this.addSampleAvatars();
        }, 2000);
    }

    createUserAvatar(config) {
        this.currentUser = new Avatar(config);
        this.currentUser.mesh.position.set(0, 0, 3);
        this.scene.add(this.currentUser.mesh);
        this.avatars.push(this.currentUser);

        // Initialize movement system for user avatar
        this.currentUser.movement = new AvatarMovement(this.currentUser, this);

        // Set up camera to follow user avatar
        this.datingCamera = new DatingCamera(this.camera, this, this.currentUser);

        console.log(`Welcome, ${config.name}! You're ready to mingle! 💖`);

        document.getElementById('avatarCustomization').style.display = 'none';

        // Make other avatars acknowledge the new user
        this.avatars.forEach(avatar => {
            if (avatar !== this.currentUser) {
                const userPosition = this.currentUser.mesh.position.clone();
                avatar.lookAt(userPosition);
                setTimeout(() => avatar.wave(), 1000);
            }
        });
    }

    addSampleAvatars() {
        // ... existing sample avatar creation ...

        sampleAvatars.forEach((avatarConfig, index) => {
            const avatar = new Avatar(avatarConfig);

            // Position avatars
            const angle = (index / sampleAvatars.length) * Math.PI * 0.8 - Math.PI * 0.4;
            const radius = 6;

            avatar.mesh.position.set(
                Math.sin(angle) * radius,
                0,
                Math.cos(angle) * radius - 3
            );

            avatar.mesh.rotation.y = -angle;

            this.scene.add(avatar.mesh);
            this.avatars.push(avatar);

            // Add random movement to sample avatars
            this.addRandomAvatarMovement(avatar);
            this.addRandomBehaviors(avatar);
        });

        setTimeout(() => {
            this.showAvatarCustomization();
        }, 1000);
    }

    addRandomAvatarMovement(avatar) {
        // Make sample avatars wander around randomly
        setInterval(() => {
            if (Math.random() > 0.8 && !this.activeChatPartners.has(avatar)) {
                const randomX = (Math.random() - 0.5) * 30;
                const randomZ = (Math.random() - 0.5) * 30;

                avatar.movement.moveTo(new THREE.Vector3(randomX, 0, randomZ));
            }
        }, 8000);
    }

    animate() {
        requestAnimationFrame(() => this.animate());

        // Update camera
        if (this.datingCamera) {
            this.datingCamera.update();
        }

        // Update proximity chat
        if (this.proximityChat) {
            this.proximityChat.update();
        }

        // Rotate decorative heart
        const heart = this.scene.children.find(child => 
            child.geometry && child.geometry.type === 'ExtrudeGeometry'
        );

        if (heart) {
            heart.rotation.y += 0.01;
        }

        this.renderer.render(this.scene, this.camera);
    }

    // Update the existing startChatWith method
    startChatWith(avatarName) {
        const avatar = this.avatars.find(a => a.options.name === avatarName);
        if (avatar && this.currentUser) {
            // Move user avatar to the target avatar
            const targetPos = avatar.mesh.position.clone();
            const direction = new THREE.Vector3()
                .subVectors(targetPos, this.currentUser.mesh.position)
                .normalize();

            const chatDistance = 2;
            const approachPos = targetPos.clone().sub(direction.multiplyScalar(chatDistance));

            this.currentUser.movement.moveTo(approachPos);

            // Focus chat input
            document.getElementById('messageInput').focus();

            // Show welcome message
            const chatMessages = document.getElementById('chatMessages');
            const welcomeElement = document.createElement('div');
            welcomeElement.textContent = `System: You're approaching ${avatarName}! Get ready to chat! 🚶‍♂️`;
            welcomeElement.style.cssText = 'margin: 5px 0; padding: 8px; background: #e3f2fd; border-radius: 10px;';
            chatMessages.appendChild(welcomeElement);
        }
    }
}

// Update global variables
let datingScene;
let datingCamera;

// Update the DOMContentLoaded event listener
window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    console.log("3D Dating World fully loaded! Ready for romance! 💕");

    setupAvatarInteraction();

    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built in Part 3: 🎉

  1. Smooth Avatar Movement:

    • Keyboard controls (WASD/Arrows)
    • Click-to-move on ground
    • Mobile touch controls with virtual joystick
    • Walking animations with leg and arm movement
  2. Advanced Camera System:

    • Multiple camera modes (Follow, Orbit, First Person)
    • Mouse controls for orbit camera
    • Smooth transitions and following
  3. Proximity Chat:

    • Automatic chat when avatars get close
    • Visual chat bubbles above avatars
    • Auto-facing when chatting
    • Smart chat management
  4. Enhanced Interactions:

    • Jumping and dancing animations
    • Movement boundaries
    • Visual movement indicators
    • Improved social behaviors

Key Features Explained: 🔑

Next Time in Part 4: 🚀

We'll add:

Current Project Status: Our dating world is now alive with movement and interaction! Avatars can navigate the space, get close to chat, and view everything from multiple camera angles. The virtual dating experience is getting real! 💕


Pro Tip: If your avatar keeps walking into walls, that's actually pretty realistic first-date behavior! Don't worry - it adds character! 😄

Part 4: Voice Chat, Emotes & Environment Interactions - Express Yourself! 🎤💫

Welcome back, digital romance architect! Our avatars can now move and chat, but they're about as expressive as a potato. Time to add voice chat, animated emotes, and interactive environments that'll make our virtual dating world truly come alive!

Step 1: Voice Chat System - Hear the Love! 🎤

Let's implement real-time voice communication between avatars:

// voice-chat.js - Because sometimes text just isn't enough!

class VoiceChatSystem {
    constructor(scene) {
        this.scene = scene;
        this.isRecording = false;
        this.mediaRecorder = null;
        this.audioChunks = [];
        this.audioContext = null;
        this.audioElements = new Map(); // Avatar -> Audio element mapping
        this.voiceRange = 5; // Distance for voice falloff

        this.setupAudioContext();
        this.setupVoiceUI();

        console.log("Voice chat system ready! Prepare for some sweet nothings! 💖");
    }

    async setupAudioContext() {
        try {
            this.audioContext = new (window.AudioContext || window.webkitAudioContext)();

            // Request microphone permission
            const stream = await navigator.mediaDevices.getUserMedia({ 
                audio: {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true
                } 
            });

            this.setupMediaRecorder(stream);
            console.log("Microphone access granted! You're now audible and dateable! 🎤");

        } catch (error) {
            console.warn("Microphone access denied. Text-only mode activated! 📝", error);
            this.showMicrophoneError();
        }
    }

    setupMediaRecorder(stream) {
        this.mediaRecorder = new MediaRecorder(stream, {
            mimeType: 'audio/webm;codecs=opus'
        });

        this.mediaRecorder.ondataavailable = (event) => {
            if (event.data.size > 0) {
                this.audioChunks.push(event.data);
            }
        };

        this.mediaRecorder.onstop = () => {
            this.sendVoiceMessage();
        };
    }

    setupVoiceUI() {
        // Add voice chat controls to the UI
        const voiceControls = document.createElement('div');
        voiceControls.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 100;
        `;

        voiceControls.innerHTML = `
            <button id="voiceToggle" style="
                background: #ff6b6b;
                color: white;
                border: none;
                border-radius: 50%;
                width: 60px;
                height: 60px;
                font-size: 24px;
                cursor: pointer;
                box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
                transition: all 0.3s ease;
            ">
                🎤
            </button>
            <div id="voiceIndicator" style="
                display: none;
                margin-top: 10px;
                padding: 10px;
                background: rgba(255, 107, 107, 0.9);
                color: white;
                border-radius: 20px;
                text-align: center;
                font-weight: bold;
            ">
                🔊 Speaking...
            </div>
        `;

        document.getElementById('container').appendChild(voiceControls);

        // Voice toggle button
        const voiceToggle = document.getElementById('voiceToggle');
        const voiceIndicator = document.getElementById('voiceIndicator');

        voiceToggle.addEventListener('mousedown', () => this.startRecording());
        voiceToggle.addEventListener('mouseup', () => this.stopRecording());
        voiceToggle.addEventListener('touchstart', (e) => {
            e.preventDefault();
            this.startRecording();
        });
        voiceToggle.addEventListener('touchend', (e) => {
            e.preventDefault();
            this.stopRecording();
        });

        // Visual feedback
        voiceToggle.addEventListener('mousedown', () => {
            voiceToggle.style.transform = 'scale(0.9)';
            voiceToggle.style.background = '#ff5252';
        });

        voiceToggle.addEventListener('mouseup', () => {
            voiceToggle.style.transform = 'scale(1)';
            voiceToggle.style.background = '#ff6b6b';
        });
    }

    startRecording() {
        if (!this.mediaRecorder || this.isRecording) return;

        this.audioChunks = [];
        this.isRecording = true;
        this.mediaRecorder.start(100); // Collect data every 100ms

        // Visual feedback
        document.getElementById('voiceIndicator').style.display = 'block';
        document.getElementById('voiceToggle').textContent = '🔴';

        // Show speaking indicator above user avatar
        this.showSpeakingIndicator(this.scene.currentUser, true);

        console.log("🎤 Recording started... Time to charm someone!");
    }

    stopRecording() {
        if (!this.mediaRecorder || !this.isRecording) return;

        this.isRecording = false;
        this.mediaRecorder.stop();

        // Visual feedback
        document.getElementById('voiceIndicator').style.display = 'none';
        document.getElementById('voiceToggle').textContent = '🎤';

        // Hide speaking indicator
        this.showSpeakingIndicator(this.scene.currentUser, false);
    }

    async sendVoiceMessage() {
        const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm;codecs=opus' });

        // In a real app, you'd send this to your server and then to other clients
        // For this demo, we'll simulate sending to nearby avatars

        const nearbyAvatars = this.getNearbyAvatars();
        if (nearbyAvatars.length > 0) {
            const audioUrl = URL.createObjectURL(audioBlob);
            this.broadcastVoiceMessage(audioUrl, nearbyAvatars);

            // Show voice message in chat
            this.showVoiceMessageInChat(audioUrl);
        }
    }

    getNearbyAvatars() {
        if (!this.scene.currentUser) return [];

        return this.scene.avatars.filter(avatar => {
            if (avatar === this.scene.currentUser) return false;

            const distance = this.scene.currentUser.mesh.position.distanceTo(avatar.mesh.position);
            return distance <= this.voiceRange;
        });
    }

    broadcastVoiceMessage(audioUrl, avatars) {
        avatars.forEach(avatar => {
            this.playVoiceMessage(audioUrl, avatar);
        });
    }

    playVoiceMessage(audioUrl, targetAvatar) {
        const audio = new Audio(audioUrl);

        // Apply spatial audio effects based on distance and position
        this.applySpatialAudio(audio, targetAvatar);

        // Show speaking indicator on target avatar
        this.showSpeakingIndicator(targetAvatar, true);

        audio.play().then(() => {
            // Hide indicator when audio ends
            audio.onended = () => {
                this.showSpeakingIndicator(targetAvatar, false);
            };
        }).catch(error => {
            console.warn("Could not play voice message:", error);
            this.showSpeakingIndicator(targetAvatar, false);
        });

        // Store audio element for cleanup
        this.audioElements.set(targetAvatar, audio);
    }

    applySpatialAudio(audio, targetAvatar) {
        if (!this.scene.currentUser) return;

        const userAvatar = this.scene.currentUser;
        const distance = userAvatar.mesh.position.distanceTo(targetAvatar.mesh.position);

        // Calculate volume based on distance (inverse square law)
        const maxVolume = 1.0;
        const minVolume = 0.1;
        let volume = maxVolume / (distance * distance);
        volume = Math.max(minVolume, Math.min(maxVolume, volume));

        audio.volume = volume;

        // Calculate panning based on relative position
        const direction = new THREE.Vector3()
            .subVectors(targetAvatar.mesh.position, userAvatar.mesh.position)
            .normalize();

        // Transform direction to camera space for stereo panning
        const camera = this.scene.camera;
        const worldDirection = direction.applyMatrix4(camera.matrixWorldInverse);

        // Simple stereo panning based on x coordinate
        const pan = THREE.MathUtils.clamp(worldDirection.x * 2, -1, 1);

        // Apply panning using Web Audio API if available
        if (this.audioContext) {
            const source = this.audioContext.createMediaElementSource(audio);
            const panner = this.audioContext.createStereoPanner();
            panner.pan.value = pan;

            source.connect(panner);
            panner.connect(this.audioContext.destination);
        }
    }

    showSpeakingIndicator(avatar, isSpeaking) {
        // Remove existing indicator
        let indicator = avatar.mesh.getObjectByName('speakingIndicator');
        if (indicator) {
            avatar.mesh.remove(indicator);
        }

        if (isSpeaking) {
            // Create speaking indicator (sound waves)
            const group = new THREE.Group();
            group.name = 'speakingIndicator';

            // Create concentric circles for sound waves
            for (let i = 0; i < 3; i++) {
                const circleGeometry = new THREE.RingGeometry(0.2 + i * 0.1, 0.3 + i * 0.1, 16);
                const circleMaterial = new THREE.MeshBasicMaterial({
                    color: 0x00ff00,
                    transparent: true,
                    opacity: 0.6 - i * 0.2,
                    side: THREE.DoubleSide
                });

                const circle = new THREE.Mesh(circleGeometry, circleMaterial);
                circle.rotation.x = -Math.PI / 2;
                circle.position.y = 2.2;

                group.add(circle);
            }

            avatar.mesh.add(group);

            // Animate the indicator
            this.animateSpeakingIndicator(group);
        }
    }

    animateSpeakingIndicator(group) {
        let scale = 1;
        let time = 0;

        const animate = () => {
            if (group.parent) { // Check if still attached
                time += 0.1;
                scale = 1 + Math.sin(time) * 0.2;

                group.children.forEach((circle, index) => {
                    circle.scale.set(scale + index * 0.1, scale + index * 0.1, 1);
                });

                requestAnimationFrame(animate);
            }
        };

        animate();
    }

    showVoiceMessageInChat(audioUrl) {
        const chatMessages = document.getElementById('chatMessages');
        const messageElement = document.createElement('div');
        messageElement.style.cssText = `
            margin: 10px 0;
            padding: 10px;
            background: #e3f2fd;
            border-radius: 15px;
            display: flex;
            align-items: center;
            gap: 10px;
        `;

        messageElement.innerHTML = `
            <span style="font-weight: bold; color: #ff6b6b;">You:</span>
            <audio controls style="flex: 1; height: 30px;">
                <source src="${audioUrl}" type="audio/webm">
                Your browser does not support audio playback.
            </audio>
            <span style="font-size: 12px; color: #666;">🎤 Voice</span>
        `;

        chatMessages.appendChild(messageElement);
        chatMessages.scrollTop = chatMessages.scrollHeight;
    }

    showMicrophoneError() {
        const errorDiv = document.createElement('div');
        errorDiv.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 0, 0, 0.9);
            color: white;
            padding: 20px;
            border-radius: 10px;
            z-index: 1000;
            text-align: center;
        `;

        errorDiv.innerHTML = `
            <h3>🎤 Microphone Access Required</h3>
            <p>Please allow microphone access to use voice chat features.</p>
            <p><small>Your browser may be blocking microphone access. Check your permissions.</small></p>
            <button onclick="this.parentElement.remove()" style="
                background: white;
                color: #ff0000;
                border: none;
                padding: 10px 20px;
                border-radius: 5px;
                cursor: pointer;
                margin-top: 10px;
            ">
                OK
            </button>
        `;

        document.getElementById('container').appendChild(errorDiv);
    }

    cleanup() {
        // Clean up audio elements and URLs
        this.audioElements.forEach((audio, avatar) => {
            audio.pause();
            URL.revokeObjectURL(audio.src);
        });
        this.audioElements.clear();
    }
}

Step 2: Emotes & Gestures System - Express Those Feelings! 💃

Let's create a comprehensive emote system with animations and visual effects:

// emotes.js - Because sometimes you need to floss to express yourself! 💫

class EmoteSystem {
    constructor(scene) {
        this.scene = scene;
        this.activeEmotes = new Map();
        this.emoteQueue = new Map();

        this.availableEmotes = {
            wave: { name: "Wave", duration: 2000, animation: 'wave' },
            dance: { name: "Dance", duration: 5000, animation: 'dance' },
            heart: { name: "Send Heart", duration: 3000, effect: 'heart' },
            laugh: { name: "Laugh", duration: 3000, animation: 'laugh' },
            kiss: { name: "Blow Kiss", duration: 2500, effect: 'kiss' },
            celebrate: { name: "Celebrate", duration: 4000, animation: 'celebrate', effect: 'confetti' },
            shy: { name: "Shy", duration: 2000, animation: 'shy' },
            point: { name: "Point", duration: 2000, animation: 'point' }
        };

        this.setupEmoteUI();
        console.log("Emote system loaded! Get ready to express yourself! 💕");
    }

    setupEmoteUI() {
        // Create emote wheel or quick access buttons
        const emoteContainer = document.createElement('div');
        emoteContainer.style.cssText = `
            position: fixed;
            bottom: 100px;
            right: 20px;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 15px;
            z-index: 100;
            box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
            display: flex;
            flex-direction: column;
            gap: 10px;
            max-width: 300px;
        `;

        emoteContainer.innerHTML = `
            <h4 style="margin: 0 0 10px 0; color: #ff6b6b;">Expressions 💫</h4>
            <div id="emoteButtons" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;">
                ${Object.entries(this.availableEmotes).map(([key, emote]) => `
                    <button onclick="emoteSystem.playEmote('${key}')" 
                            style="padding: 10px; border: none; border-radius: 10px; background: #f8f9fa; cursor: pointer; transition: all 0.3s ease;"
                            onmouseover="this.style.background='#ffebee'"
                            onmouseout="this.style.background='#f8f9fa'"
                            title="${emote.name}">
                        ${this.getEmoteIcon(key)}
                    </button>
                `).join('')}
            </div>
        `;

        document.getElementById('container').appendChild(emoteContainer);
    }

    getEmoteIcon(emoteKey) {
        const icons = {
            wave: '👋',
            dance: '💃',
            heart: '💖',
            laugh: '😂',
            kiss: '😘',
            celebrate: '🎉',
            shy: '😊',
            point: '👉'
        };
        return icons[emoteKey] || '💫';
    }

    playEmote(emoteKey, avatar = null) {
        const targetAvatar = avatar || this.scene.currentUser;
        if (!targetAvatar) return;

        const emote = this.availableEmotes[emoteKey];
        if (!emote) {
            console.warn(`Unknown emote: ${emoteKey}`);
            return;
        }

        // Queue emote if avatar is already performing one
        if (this.activeEmotes.has(targetAvatar)) {
            if (!this.emoteQueue.has(targetAvatar)) {
                this.emoteQueue.set(targetAvatar, []);
            }
            this.emoteQueue.get(targetAvatar).push(emoteKey);
            return;
        }

        this.executeEmote(targetAvatar, emoteKey, emote);
    }

    executeEmote(avatar, emoteKey, emote) {
        console.log(`${avatar.options.name} is ${emote.name.toLowerCase()}! ${this.getEmoteIcon(emoteKey)}`);

        this.activeEmotes.set(avatar, emoteKey);

        // Play animation if available
        if (emote.animation && avatar[emote.animation]) {
            avatar[emote.animation]();
        }

        // Show visual effect if available
        if (emote.effect) {
            this.playVisualEffect(avatar, emote.effect);
        }

        // Show emote text above avatar
        this.showEmoteText(avatar, emote.name);

        // Show in chat
        this.showEmoteInChat(avatar, emoteKey);

        // Set timeout to end emote
        setTimeout(() => {
            this.endEmote(avatar);
        }, emote.duration);
    }

    endEmote(avatar) {
        this.activeEmotes.delete(avatar);

        // Reset to default pose
        if (avatar.resetPose) {
            avatar.resetPose();
        }

        // Check for queued emotes
        if (this.emoteQueue.has(avatar) && this.emoteQueue.get(avatar).length > 0) {
            const nextEmoteKey = this.emoteQueue.get(avatar).shift();
            const nextEmote = this.availableEmotes[nextEmoteKey];
            setTimeout(() => {
                this.executeEmote(avatar, nextEmoteKey, nextEmote);
            }, 500);
        }
    }

    playVisualEffect(avatar, effectType) {
        const position = avatar.mesh.position.clone();
        position.y += 2; // Above avatar's head

        switch(effectType) {
            case 'heart':
                this.createHeartEffect(position);
                break;
            case 'kiss':
                this.createKissEffect(position);
                break;
            case 'confetti':
                this.createConfettiEffect(position);
                break;
        }
    }

    createHeartEffect(position) {
        const heartGroup = new THREE.Group();

        // Create multiple hearts
        for (let i = 0; i < 5; i++) {
            const heartShape = new THREE.Shape();
            heartShape.moveTo(0, 0);
            heartShape.bezierCurveTo(0.2, 0.2, 0.3, 0, 0, -0.3);
            heartShape.bezierCurveTo(-0.3, 0, -0.2, 0.2, 0, 0);

            const heartGeometry = new THREE.ExtrudeGeometry(heartShape, {
                depth: 0.05,
                bevelEnabled: true,
                bevelSegments: 2,
                bevelSize: 0.02,
                bevelThickness: 0.02
            });

            const heartMaterial = new THREE.MeshBasicMaterial({ 
                color: 0xff6b6b,
                transparent: true,
                opacity: 0.8
            });

            const heart = new THREE.Mesh(heartGeometry, heartMaterial);

            // Random position around avatar
            heart.position.set(
                (Math.random() - 0.5) * 2,
                Math.random() * 2,
                (Math.random() - 0.5) * 2
            );

            // Random scale
            const scale = 0.1 + Math.random() * 0.1;
            heart.scale.set(scale, scale, scale);

            heartGroup.add(heart);
        }

        heartGroup.position.copy(position);
        this.scene.scene.add(heartGroup);

        // Animate hearts floating up and fading out
        let opacity = 0.8;
        const startY = position.y;

        const animate = () => {
            opacity -= 0.02;
            heartGroup.position.y += 0.05;

            heartGroup.children.forEach(heart => {
                heart.material.opacity = opacity;
                heart.rotation.y += 0.05;
            });

            if (opacity > 0 && heartGroup.position.y < startY + 3) {
                requestAnimationFrame(animate);
            } else {
                this.scene.scene.remove(heartGroup);
            }
        };

        animate();
    }

    createKissEffect(position) {
        const kissGroup = new THREE.Group();

        // Create kiss particles (small hearts)
        for (let i = 0; i < 8; i++) {
            const heartGeometry = new THREE.SphereGeometry(0.05, 8, 6);
            const heartMaterial = new THREE.MeshBasicMaterial({ 
                color: 0xff69b4,
                transparent: true,
                opacity: 0.7
            });

            const heart = new THREE.Mesh(heartGeometry, heartMaterial);

            // Position in an arc
            const angle = (i / 8) * Math.PI;
            const radius = 0.5;
            heart.position.set(
                Math.sin(angle) * radius,
                Math.cos(angle) * radius * 0.5,
                0
            );

            kissGroup.add(heart);
        }

        kissGroup.position.copy(position);
        this.scene.scene.add(kissGroup);

        // Animate kiss floating forward
        let progress = 0;
        const startPos = position.clone();

        const animate = () => {
            progress += 0.03;
            kissGroup.position.lerpVectors(startPos, 
                new THREE.Vector3(startPos.x, startPos.y, startPos.z - 3), progress);

            kissGroup.children.forEach((heart, index) => {
                heart.material.opacity = 0.7 * (1 - progress);
                heart.scale.setScalar(1 + progress);
            });

            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                this.scene.scene.remove(kissGroup);
            }
        };

        animate();
    }

    createConfettiEffect(position) {
        const confettiGroup = new THREE.Group();
        const colors = [0xff6b6b, 0x4ecdc4, 0xffd166, 0x06d6a0, 0x118ab2];

        // Create confetti pieces
        for (let i = 0; i < 20; i++) {
            const confettiGeometry = new THREE.PlaneGeometry(0.1, 0.1);
            const confettiMaterial = new THREE.MeshBasicMaterial({
                color: colors[Math.floor(Math.random() * colors.length)],
                transparent: true,
                opacity: 0.8,
                side: THREE.DoubleSide
            });

            const confetti = new THREE.Mesh(confettiGeometry, confettiMaterial);

            // Random starting position
            confetti.position.set(
                (Math.random() - 0.5) * 2,
                Math.random() * 1,
                (Math.random() - 0.5) * 2
            );

            // Random rotation
            confetti.rotation.set(
                Math.random() * Math.PI,
                Math.random() * Math.PI,
                Math.random() * Math.PI
            );

            confetti.userData = {
                velocity: new THREE.Vector3(
                    (Math.random() - 0.5) * 0.02,
                    Math.random() * 0.03 + 0.02,
                    (Math.random() - 0.5) * 0.02
                ),
                rotationSpeed: new THREE.Vector3(
                    (Math.random() - 0.5) * 0.1,
                    (Math.random() - 0.5) * 0.1,
                    (Math.random() - 0.5) * 0.1
                )
            };

            confettiGroup.add(confetti);
        }

        confettiGroup.position.copy(position);
        this.scene.scene.add(confettiGroup);

        // Animate confetti falling
        let time = 0;

        const animate = () => {
            time += 0.1;

            confettiGroup.children.forEach(confetti => {
                // Update position
                confetti.position.add(confetti.userData.velocity);

                // Update rotation
                confetti.rotation.x += confetti.userData.rotationSpeed.x;
                confetti.rotation.y += confetti.userData.rotationSpeed.y;
                confetti.rotation.z += confetti.userData.rotationSpeed.z;

                // Apply gravity
                confetti.userData.velocity.y -= 0.001;

                // Fade out
                confetti.material.opacity = 0.8 * (1 - time / 3);
            });

            if (time < 3) {
                requestAnimationFrame(animate);
            } else {
                this.scene.scene.remove(confettiGroup);
            }
        };

        animate();
    }

    showEmoteText(avatar, emoteName) {
        // Remove existing emote text
        let existingText = avatar.mesh.getObjectByName('emoteText');
        if (existingText) {
            avatar.mesh.remove(existingText);
        }

        // Create text sprite (simplified version - in production, use proper text geometry)
        const canvas = document.createElement('canvas');
        canvas.width = 256;
        canvas.height = 64;
        const context = canvas.getContext('2d');

        // Draw text background
        context.fillStyle = 'rgba(255, 107, 107, 0.9)';
        context.fillRect(0, 0, canvas.width, canvas.height);

        // Draw text
        context.fillStyle = 'white';
        context.font = 'bold 24px Arial';
        context.textAlign = 'center';
        context.textBaseline = 'middle';
        context.fillText(emoteName, canvas.width / 2, canvas.height / 2);

        const texture = new THREE.CanvasTexture(canvas);
        const material = new THREE.SpriteMaterial({ map: texture });
        const sprite = new THREE.Sprite(material);

        sprite.name = 'emoteText';
        sprite.position.y = 2.5;
        sprite.scale.set(2, 0.5, 1);

        avatar.mesh.add(sprite);

        // Auto-remove after 3 seconds
        setTimeout(() => {
            if (sprite.parent) {
                sprite.parent.remove(sprite);
            }
        }, 3000);
    }

    showEmoteInChat(avatar, emoteKey) {
        const chatMessages = document.getElementById('chatMessages');
        const messageElement = document.createElement('div');
        messageElement.style.cssText = `
            margin: 5px 0;
            padding: 8px;
            background: #fff3e0;
            border-radius: 15px;
            text-align: center;
            font-style: italic;
        `;

        const emoteIcon = this.getEmoteIcon(emoteKey);
        const emoteName = this.availableEmotes[emoteKey].name;

        messageElement.textContent = `${avatar.options.name} ${emoteName.toLowerCase()}s! ${emoteIcon}`;

        chatMessages.appendChild(messageElement);
        chatMessages.scrollTop = chatMessages.scrollHeight;
    }
}

// Add new animation methods to the Avatar class
Avatar.prototype.laugh = function() {
    console.log(`${this.options.name} is having a good laugh! 😂`);

    const startTime = Date.now();

    const animateLaugh = () => {
        const elapsed = Date.now() - startTime;
        const progress = Math.sin(elapsed * 0.01) * 0.3;

        // Head bob for laughter
        this.head.position.y = 1.6 + progress * 0.1;

        // Body shake
        this.mesh.rotation.z = progress * 0.1;

        if (elapsed < 3000) {
            requestAnimationFrame(animateLaugh);
        } else {
            this.resetPose();
        }
    };

    animateLaugh();
};

Avatar.prototype.celebrate = function() {
    console.log(`${this.options.name} is celebrating! 🎉`);

    const startTime = Date.now();

    const animateCelebrate = () => {
        const elapsed = Date.now() - startTime;
        const progress = elapsed / 4000;

        // Jumping motion
        const jumpHeight = Math.sin(progress * Math.PI * 4) * 0.3;
        this.mesh.position.y = jumpHeight;

        // Arm waving
        this.leftArm.rotation.z = Math.PI / 6 + Math.sin(progress * Math.PI * 8) * 0.5;
        this.rightArm.rotation.z = -Math.PI / 6 - Math.sin(progress * Math.PI * 8) * 0.5;

        if (elapsed < 4000) {
            requestAnimationFrame(animateCelebrate);
        } else {
            this.mesh.position.y = 0;
            this.resetPose();
        }
    };

    animateCelebrate();
};

Avatar.prototype.shy = function() {
    console.log(`${this.options.name} is feeling shy! 😊`);

    // Blush effect
    this.blush();

    // Head tilt and slight turn away
    this.head.rotation.z = 0.2;
    this.mesh.rotation.y += 0.3;

    // One hand covering face
    this.leftArm.rotation.z = Math.PI / 2;
    this.leftHand.position.set(0.3, 1.7, 0.3);

    setTimeout(() => {
        this.resetPose();
    }, 2000);
};

Avatar.prototype.point = function() {
    console.log(`${this.options.name} is pointing at something! 👉`);

    // Point with right arm
    this.rightArm.rotation.z = -Math.PI / 4;
    this.rightArm.rotation.x = -Math.PI / 6;

    // Look in pointing direction
    this.head.rotation.y = -0.2;

    setTimeout(() => {
        this.resetPose();
    }, 2000);
};

Step 3: Environment Interactions - Make Yourself at Home! 🏠

Let's create interactive elements in our environment:

// environment.js - Because dating happens in places, not voids!

class InteractiveEnvironment {
    constructor(scene) {
        this.scene = scene;
        this.interactiveObjects = new Map();
        this.occupiableSpots = [];

        this.createInteractiveElements();
        console.log("Interactive environment ready! Explore and interact! 🌳");
    }

    createInteractiveElements() {
        this.createBenches();
        this.createDanceFloor();
        this.createRomanticSpots();
        this.createInteractiveObjects();
    }

    createBenches() {
        // Create benches around the environment
        const benchPositions = [
            new THREE.Vector3(-8, 0, -5),
            new THREE.Vector3(8, 0, -5),
            new THREE.Vector3(0, 0, 8),
            new THREE.Vector3(-10, 0, 5),
            new THREE.Vector3(10, 0, 5)
        ];

        benchPositions.forEach((position, index) => {
            const bench = this.createBench();
            bench.position.copy(position);
            bench.rotation.y = Math.random() * Math.PI * 2;

            this.scene.scene.add(bench);

            // Create sitting spots
            this.createSittingSpots(bench, position, index);
        });
    }

    createBench() {
        const benchGroup = new THREE.Group();

        // Bench seat
        const seatGeometry = new THREE.BoxGeometry(2, 0.1, 0.5);
        const seatMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
        const seat = new THREE.Mesh(seatGeometry, seatMaterial);
        seat.position.y = 0.5;
        seat.castShadow = true;
        benchGroup.add(seat);

        // Bench legs
        const legGeometry = new THREE.BoxGeometry(0.1, 1, 0.1);
        const legMaterial = new THREE.MeshStandardMaterial({ color: 0x654321 });

        const leg1 = new THREE.Mesh(legGeometry, legMaterial);
        leg1.position.set(-0.9, 0, -0.2);
        benchGroup.add(leg1);

        const leg2 = new THREE.Mesh(legGeometry, legMaterial);
        leg2.position.set(0.9, 0, -0.2);
        benchGroup.add(leg2);

        const leg3 = new THREE.Mesh(legGeometry, legMaterial);
        leg3.position.set(-0.9, 0, 0.2);
        benchGroup.add(leg3);

        const leg4 = new THREE.Mesh(legGeometry, legMaterial);
        leg4.position.set(0.9, 0, 0.2);
        benchGroup.add(leg4);

        // Bench back
        const backGeometry = new THREE.BoxGeometry(2, 1, 0.1);
        const back = new THREE.Mesh(backGeometry, seatMaterial);
        back.position.set(0, 1, -0.3);
        back.castShadow = true;
        benchGroup.add(back);

        return benchGroup;
    }

    createSittingSpots(bench, position, benchIndex) {
        // Create two sitting spots per bench
        for (let i = 0; i < 2; i++) {
            const spot = {
                type: 'sitting',
                position: position.clone(),
                rotation: bench.rotation.y + (i === 0 ? Math.PI : 0),
                occupied: false,
                occupiedBy: null,
                benchIndex: benchIndex,
                spotIndex: i
            };

            // Position spots on either side of bench
            spot.position.x += (i === 0 ? -0.5 : 0.5);
            spot.position.z += 0.2;
            spot.position.y = 0.5; // Sitting height

            this.occupiableSpots.push(spot);

            // Create visual indicator (invisible until needed)
            this.createInteractionIndicator(spot.position, '💺 Sit');
        }
    }

    createDanceFloor() {
        // Create a dance floor area
        const danceFloorGeometry = new THREE.CircleGeometry(3, 32);
        const danceFloorMaterial = new THREE.MeshStandardMaterial({ 
            color: 0x2C2C54,
            roughness: 0.3,
            metalness: 0.7
        });

        const danceFloor = new THREE.Mesh(danceFloorGeometry, danceFloorMaterial);
        danceFloor.rotation.x = -Math.PI / 2;
        danceFloor.position.set(0, 0.01, -8); // Slightly above ground
        danceFloor.receiveShadow = true;

        this.scene.scene.add(danceFloor);

        // Add dance floor lighting
        this.createDanceFloorLights(danceFloor.position);

        // Create dance spot
        const danceSpot = {
            type: 'dancing',
            position: danceFloor.position.clone(),
            radius: 3,
            occupied: false,
            occupiedBy: null
        };

        this.occupiableSpots.push(danceSpot);
        this.createInteractionIndicator(danceSpot.position, '💃 Dance');
    }

    createDanceFloorLights(position) {
        // Create colored lights around dance floor
        const colors = [0xff6b6b, 0x4ecdc4, 0xffd166, 0x06d6a0];

        colors.forEach((color, index) => {
            const angle = (index / colors.length) * Math.PI * 2;
            const light = new THREE.PointLight(color, 0.5, 10);
            light.position.set(
                position.x + Math.cos(angle) * 4,
                position.y + 2,
                position.z + Math.sin(angle) * 4
            );

            this.scene.scene.add(light);

            // Animate light intensity
            this.animateDanceLight(light);
        });
    }

    animateDanceLight(light) {
        let time = 0;

        const animate = () => {
            time += 0.05;
            light.intensity = 0.3 + Math.sin(time) * 0.2;
            requestAnimationFrame(animate);
        };

        animate();
    }

    createRomanticSpots() {
        // Create special romantic spots with enhanced lighting
        const romanticSpots = [
            new THREE.Vector3(-12, 0, -12),
            new THREE.Vector3(12, 0, -12),
            new THREE.Vector3(0, 0, 12)
        ];

        romanticSpots.forEach((position, index) => {
            // Add special lighting
            const spotLight = new THREE.SpotLight(0xff6b6b, 0.3, 10, Math.PI / 4, 0.5);
            spotLight.position.set(position.x, 5, position.z);
            spotLight.target.position.set(position.x, 0, position.z);
            this.scene.scene.add(spotLight);
            this.scene.scene.add(spotLight.target);

            // Create romantic spot
            const romanticSpot = {
                type: 'romantic',
                position: position.clone(),
                radius: 2,
                occupied: false,
                occupiedBy: null,
                special: true
            };

            this.occupiableSpots.push(romanticSpot);
            this.createInteractionIndicator(position, '💕 Romantic Spot');
        });
    }

    createInteractiveObjects() {
        // Create various interactive objects
        this.createFountain();
        this.createFlowerBeds();
    }

    createFountain() {
        const fountainGroup = new THREE.Group();
        fountainGroup.position.set(5, 0, 5);

        // Fountain base
        const baseGeometry = new THREE.CylinderGeometry(1.5, 2, 0.5, 32);
        const baseMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
        const base = new THREE.Mesh(baseGeometry, baseMaterial);
        fountainGroup.add(base);

        // Fountain water
        const waterGeometry = new THREE.CylinderGeometry(1, 1.2, 0.2, 32);
        const waterMaterial = new THREE.MeshStandardMaterial({ 
            color: 0x4ecdc4,
            transparent: true,
            opacity: 0.7
        });
        const water = new THREE.Mesh(waterGeometry, waterMaterial);
        water.position.y = 0.35;
        fountainGroup.add(water);

        this.scene.scene.add(fountainGroup);

        // Create fountain interaction spot
        const fountainSpot = {
            type: 'fountain',
            position: fountainGroup.position.clone(),
            radius: 2,
            occupied: false,
            occupiedBy: null
        };

        this.occupiableSpots.push(fountainSpot);
        this.createInteractionIndicator(fountainSpot.position, '⛲ Fountain');
    }

    createFlowerBeds() {
        // Create flower beds around the environment
        const flowerBedPositions = [
            new THREE.Vector3(-15, 0, -8),
            new THREE.Vector3(15, 0, -8),
            new THREE.Vector3(-10, 0, 12),
            new THREE.Vector3(10, 0, 12)
        ];

        flowerBedPositions.forEach(position => {
            const flowerBed = this.createFlowerBed();
            flowerBed.position.copy(position);
            this.scene.scene.add(flowerBed);
        });
    }

    createFlowerBed() {
        const flowerBedGroup = new THREE.Group();

        // Flower bed base
        const bedGeometry = new THREE.CircleGeometry(1.5, 32);
        const bedMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
        const bed = new THREE.Mesh(bedGeometry, bedMaterial);
        bed.rotation.x = -Math.PI / 2;
        flowerBedGroup.add(bed);

        // Create flowers
        for (let i = 0; i < 10; i++) {
            const flower = this.createFlower();
            flower.position.set(
                (Math.random() - 0.5) * 2.5,
                0,
                (Math.random() - 0.5) * 2.5
            );
            flowerBedGroup.add(flower);
        }

        return flowerBedGroup;
    }

    createFlower() {
        const flowerGroup = new THREE.Group();

        // Stem
        const stemGeometry = new THREE.CylinderGeometry(0.02, 0.03, 0.5, 8);
        const stemMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
        const stem = new THREE.Mesh(stemGeometry, stemMaterial);
        stem.position.y = 0.25;
        flowerGroup.add(stem);

        // Flower head
        const flowerColors = [0xff6b6b, 0xffd166, 0x4ecdc4, 0x9370DB];
        const flowerColor = flowerColors[Math.floor(Math.random() * flowerColors.length)];

        const flowerGeometry = new THREE.SphereGeometry(0.1, 8, 6);
        const flowerMaterial = new THREE.MeshStandardMaterial({ color: flowerColor });
        const flowerHead = new THREE.Mesh(flowerGeometry, flowerMaterial);
        flowerHead.position.y = 0.5;
        flowerGroup.add(flowerHead);

        return flowerGroup;
    }

    createInteractionIndicator(position, label) {
        // Create a subtle indicator that shows up when avatar is nearby
        const indicatorGroup = new THREE.Group();
        indicatorGroup.position.copy(position);
        indicatorGroup.position.y += 0.5;
        indicatorGroup.visible = false;

        // Background circle
        const circleGeometry = new THREE.CircleGeometry(0.3, 16);
        const circleMaterial = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            transparent: true,
            opacity: 0.8,
            side: THREE.DoubleSide
        });
        const circle = new THREE.Mesh(circleGeometry, circleMaterial);
        indicatorGroup.add(circle);

        // Text label (simplified)
        const textGeometry = new THREE.PlaneGeometry(0.8, 0.2);
        const textMaterial = new THREE.MeshBasicMaterial({
            color: 0xff6b6b,
            transparent: true,
            opacity: 0.9,
            side: THREE.DoubleSide
        });
        const text = new THREE.Mesh(textGeometry, textMaterial);
        text.position.z = 0.01;
        indicatorGroup.add(text);

        indicatorGroup.name = 'interactionIndicator';
        this.scene.scene.add(indicatorGroup);

        // Store reference
        this.interactiveObjects.set(label, {
            group: indicatorGroup,
            position: position,
            label: label,
            active: false
        });
    }

    checkInteractions(avatar) {
        // Check if avatar is near any interactive objects
        let nearInteraction = false;

        this.interactiveObjects.forEach((obj, label) => {
            const distance = avatar.mesh.position.distanceTo(obj.position);

            if (distance < 2) {
                if (!obj.active) {
                    obj.active = true;
                    obj.group.visible = true;
                    this.showInteractionPrompt(label);
                }
                nearInteraction = true;

                // Check for interaction input
                if (this.isInteractionKeyPressed()) {
                    this.handleInteraction(avatar, label);
                }
            } else {
                if (obj.active) {
                    obj.active = false;
                    obj.group.visible = false;
                    this.hideInteractionPrompt();
                }
            }
        });

        return nearInteraction;
    }

    showInteractionPrompt(label) {
        // Show on-screen prompt
        let prompt = document.getElementById('interactionPrompt');
        if (!prompt) {
            prompt = document.createElement('div');
            prompt.id = 'interactionPrompt';
            prompt.style.cssText = `
                position: fixed;
                bottom: 200px;
                left: 50%;
                transform: translateX(-50%);
                background: rgba(255, 107, 107, 0.9);
                color: white;
                padding: 15px 25px;
                border-radius: 25px;
                z-index: 100;
                font-weight: bold;
                box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
            `;
            document.getElementById('container').appendChild(prompt);
        }

        prompt.textContent = `Press E to ${label}`;
        prompt.style.display = 'block';
    }

    hideInteractionPrompt() {
        const prompt = document.getElementById('interactionPrompt');
        if (prompt) {
            prompt.style.display = 'none';
        }
    }

    isInteractionKeyPressed() {
        // Check if E key is pressed (for interaction)
        return this.scene.keys && this.scene.keys['KeyE'];
    }

    handleInteraction(avatar, label) {
        console.log(`${avatar.options.name} is interacting with: ${label}`);

        switch(label) {
            case '💺 Sit':
                this.sitOnBench(avatar);
                break;
            case '💃 Dance':
                this.startDancing(avatar);
                break;
            case '💕 Romantic Spot':
                this.enterRomanticSpot(avatar);
                break;
            case '⛲ Fountain':
                this.admireFountain(avatar);
                break;
        }
    }

    sitOnBench(avatar) {
        // Find nearest available sitting spot
        const nearestSpot = this.findNearestAvailableSpot(avatar, 'sitting');
        if (nearestSpot) {
            nearestSpot.occupied = true;
            nearestSpot.occupiedBy = avatar;

            // Move avatar to sitting spot
            avatar.mesh.position.copy(nearestSpot.position);
            avatar.mesh.rotation.y = nearestSpot.rotation;

            // Play sitting animation
            avatar.mesh.position.y = 0.5;
            avatar.mesh.rotation.x = -Math.PI / 8;

            console.log(`${avatar.options.name} is sitting down! 💺`);
        }
    }

    startDancing(avatar) {
        const danceSpot = this.findNearestAvailableSpot(avatar, 'dancing');
        if (danceSpot) {
            danceSpot.occupied = true;
            danceSpot.occupiedBy = avatar;

            // Move to dance floor center
            avatar.mesh.position.copy(danceSpot.position);

            // Start dance animation
            avatar.dance();

            console.log(`${avatar.options.name} is hitting the dance floor! 💃`);
        }
    }

    enterRomanticSpot(avatar) {
        const romanticSpot = this.findNearestAvailableSpot(avatar, 'romantic');
        if (romanticSpot) {
            romanticSpot.occupied = true;
            romanticSpot.occupiedBy = avatar;

            // Move to romantic spot
            avatar.mesh.position.copy(romanticSpot.position);

            // Play romantic emote
            emoteSystem.playEmote('heart', avatar);

            console.log(`${avatar.options.name} found a romantic spot! 💕`);
        }
    }

    admireFountain(avatar) {
        // Move to fountain and play admire animation
        avatar.mesh.lookAt(new THREE.Vector3(5, 1, 5));
        emoteSystem.playEmote('celebrate', avatar);

        console.log(`${avatar.options.name} is admiring the fountain! ⛲`);
    }

    findNearestAvailableSpot(avatar, type) {
        let nearestSpot = null;
        let minDistance = Infinity;

        this.occupiableSpots.forEach(spot => {
            if (spot.type === type && !spot.occupied) {
                const distance = avatar.mesh.position.distanceTo(spot.position);
                if (distance < minDistance) {
                    minDistance = distance;
                    nearestSpot = spot;
                }
            }
        });

        return nearestSpot;
    }

    releaseSpot(avatar) {
        // Release any spot occupied by this avatar
        this.occupiableSpots.forEach(spot => {
            if (spot.occupiedBy === avatar) {
                spot.occupied = false;
                spot.occupiedBy = null;
            }
        });
    }

    update() {
        // Update interactive elements
        if (this.scene.currentUser) {
            this.checkInteractions(this.scene.currentUser);
        }
    }
}

Step 4: Updated DatingScene Class - Integration Time! 🔄

Let's update our main class to integrate all these new systems:

// Update the DatingScene class with new properties and methods

class DatingScene {
    constructor() {
        // ... existing properties ...

        // New properties for Part 4
        this.voiceChat = null;
        this.emoteSystem = null;
        this.interactiveEnv = null;
        this.keys = {}; // Keyboard state

        this.init();
    }

    init() {
        this.createScene();
        this.createCamera();
        this.createRenderer();
        this.createLights();
        this.createEnvironment();

        // Initialize new systems
        this.proximityChat = new ProximityChat(this);
        this.voiceChat = new VoiceChatSystem(this);
        this.emoteSystem = new EmoteSystem(this);
        this.interactiveEnv = new InteractiveEnvironment(this);

        // Setup keyboard listener
        this.setupKeyboardListeners();

        this.animate();

        setTimeout(() => {
            document.getElementById('loadingScreen').style.display = 'none';
            document.getElementById('chatUI').style.display = 'block';
            this.addSampleAvatars();
        }, 2000);
    }

    setupKeyboardListeners() {
        document.addEventListener('keydown', (event) => {
            this.keys[event.code] = true;

            // E key for interactions
            if (event.code === 'KeyE' && this.currentUser) {
                this.interactiveEnv.checkInteractions(this.currentUser);
            }

            // Space bar to stand up
            if (event.code === 'Space' && this.currentUser) {
                this.interactiveEnv.releaseSpot(this.currentUser);
                this.currentUser.resetPose();
            }
        });

        document.addEventListener('keyup', (event) => {
            this.keys[event.code] = false;
        });
    }

    animate() {
        requestAnimationFrame(() => this.animate());

        // Update all systems
        if (this.datingCamera) {
            this.datingCamera.update();
        }

        if (this.proximityChat) {
            this.proximityChat.update();
        }

        if (this.interactiveEnv) {
            this.interactiveEnv.update();
        }

        // Rotate decorative elements
        const heart = this.scene.children.find(child => 
            child.geometry && child.geometry.type === 'ExtrudeGeometry'
        );

        if (heart) {
            heart.rotation.y += 0.01;
        }

        this.renderer.render(this.scene, this.camera);
    }

    // Update movement to consider interactions
    addRandomAvatarMovement(avatar) {
        // Make sample avatars wander around and use interactive elements
        setInterval(() => {
            if (Math.random() > 0.8 && !this.activeChatPartners.has(avatar)) {
                if (Math.random() > 0.5) {
                    // Move to random position
                    const randomX = (Math.random() - 0.5) * 30;
                    const randomZ = (Math.random() - 0.5) * 30;
                    avatar.movement.moveTo(new THREE.Vector3(randomX, 0, randomZ));
                } else {
                    // Use interactive element
                    const randomInteraction = Math.random();
                    if (randomInteraction < 0.3) {
                        this.interactiveEnv.sitOnBench(avatar);
                    } else if (randomInteraction < 0.6) {
                        this.interactiveEnv.startDancing(avatar);
                    } else {
                        // Random emote
                        const emotes = Object.keys(this.emoteSystem.availableEmotes);
                        const randomEmote = emotes[Math.floor(Math.random() * emotes.length)];
                        this.emoteSystem.playEmote(randomEmote, avatar);
                    }
                }
            }
        }, 10000);
    }
}

// Global variables
let datingScene;
let datingCamera;
let emoteSystem;

// Update the DOMContentLoaded event listener
window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    emoteSystem = datingScene.emoteSystem;
    console.log("3D Dating World with voice, emotes, and interactions loaded! 🎉");

    setupAvatarInteraction();

    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built in Part 4: 🎉

  1. Voice Chat System:

    • Real-time microphone recording and playback
    • Spatial audio with distance-based volume and panning
    • Visual speaking indicators
    • Voice message integration in chat
  2. Emote System:

    • 8 different emotes with animations and effects
    • Visual effects (hearts, kisses, confetti)
    • Emote queuing system
    • Chat integration for emote notifications
  3. Interactive Environment:

    • Benches with sitting spots
    • Dance floor with special lighting
    • Romantic spots with enhanced ambiance
    • Fountain and decorative elements
    • Interactive prompts and indicators
  4. Enhanced Social Features:

    • Keyboard shortcuts for quick interactions
    • Automatic NPC behaviors using interactive elements
    • Visual feedback for all interactions

Key Features Explained: 🔑

Next Time in Part 5: 🚀

We'll add:

Current Project Status: Our dating world is now bursting with personality! Avatars can talk, express emotions through animations and effects, and interact with their environment. The virtual dating experience has never been more alive! 💕


Fun Fact: Our avatars now have more emotional range than most dating app profiles! They can laugh, dance, blush, and even throw confetti - which is more than some of us can manage on a first date! 😄

Part 5: Day/Night Cycle, Weather & Advanced NPC AI - Setting the Mood! 🌅🌧️

Welcome back, virtual romance architect! Our dating world is looking lively, but it's stuck in perpetual daylight - about as romantic as a doctor's office waiting room. Time to add dynamic time, weather, and NPCs with actual personalities that can make meaningful connections!

Step 1: Dynamic Day/Night Cycle - Romance Under the Stars! 🌙

Let's create a beautiful day/night cycle that affects lighting, colors, and ambiance:

// day-night.js - Because every great love story needs the right lighting! 🎭

class DayNightCycle {
    constructor(scene) {
        this.scene = scene;
        this.timeOfDay = 12; // 0-24 hours
        this.timeSpeed = 0.1; // How fast time passes (real seconds per game minute)
        this.isPaused = false;
        this.sun = null;
        this.moon = null;
        this.stars = null;

        // Color palettes for different times
        this.skyColors = {
            dawn: { top: 0xFF7E5F, bottom: 0xFEB47B },
            day: { top: 0x87CEEB, bottom: 0x98D8E8 },
            dusk: { top: 0x2C3E50, bottom: 0xFD746C },
            night: { top: 0x0F2027, bottom: 0x2C5364 }
        };

        this.setupCelestialBodies();
        this.setupSky();
        this.setupTimeUI();

        console.log("Day/Night cycle initialized! Let the romantic lighting begin! 💡");
    }

    setupCelestialBodies() {
        // Create sun
        const sunGeometry = new THREE.SphereGeometry(2, 32, 32);
        const sunMaterial = new THREE.MeshBasicMaterial({
            color: 0xFFD700,
            emissive: 0xFF4500,
            emissiveIntensity: 0.5
        });
        this.sun = new THREE.Mesh(sunGeometry, sunMaterial);
        this.scene.scene.add(this.sun);

        // Create moon
        const moonGeometry = new THREE.SphereGeometry(1.5, 32, 32);
        const moonMaterial = new THREE.MeshBasicMaterial({
            color: 0xE6E6FA,
            emissive: 0x4B0082,
            emissiveIntensity: 0.3
        });
        this.moon = new THREE.Mesh(moonGeometry, moonMaterial);
        this.scene.scene.add(this.moon);

        // Create stars
        this.stars = new THREE.Group();
        this.createStars();
        this.scene.scene.add(this.stars);
    }

    createStars() {
        const starGeometry = new THREE.SphereGeometry(0.05, 8, 8);
        const starMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFFFF });

        // Create many stars in a hemisphere
        for (let i = 0; i < 500; i++) {
            const star = new THREE.Mesh(starGeometry, starMaterial);

            // Random position on a sphere
            const radius = 95 + Math.random() * 5;
            const theta = Math.random() * Math.PI * 2;
            const phi = Math.acos((Math.random() * 2) - 1);

            star.position.set(
                radius * Math.sin(phi) * Math.cos(theta),
                radius * Math.sin(phi) * Math.sin(theta),
                radius * Math.cos(phi)
            );

            // Random brightness
            star.material.opacity = 0.3 + Math.random() * 0.7;
            star.material.transparent = true;

            this.stars.add(star);
        }
    }

    setupSky() {
        // Create sky dome with gradient
        const skyGeometry = new THREE.SphereGeometry(100, 32, 32);

        // Create gradient texture
        const canvas = document.createElement('canvas');
        canvas.width = 256;
        canvas.height = 256;
        this.skyCanvas = canvas;
        this.skyContext = canvas.getContext('2d');

        const texture = new THREE.CanvasTexture(canvas);
        const skyMaterial = new THREE.MeshBasicMaterial({
            map: texture,
            side: THREE.BackSide
        });

        this.sky = new THREE.Mesh(skyGeometry, skyMaterial);
        this.scene.scene.add(this.sky);

        this.updateSkyGradient();
    }

    updateSkyGradient() {
        const gradient = this.skyContext.createLinearGradient(0, 0, 0, this.skyCanvas.height);

        let topColor, bottomColor;

        if (this.timeOfDay >= 6 && this.timeOfDay < 8) {
            // Dawn
            topColor = this.skyColors.dawn.top;
            bottomColor = this.skyColors.dawn.bottom;
        } else if (this.timeOfDay >= 8 && this.timeOfDay < 18) {
            // Day
            topColor = this.skyColors.day.top;
            bottomColor = this.skyColors.day.bottom;
        } else if (this.timeOfDay >= 18 && this.timeOfDay < 20) {
            // Dusk
            topColor = this.skyColors.dusk.top;
            bottomColor = this.skyColors.dusk.bottom;
        } else {
            // Night
            topColor = this.skyColors.night.top;
            bottomColor = this.skyColors.night.bottom;
        }

        gradient.addColorStop(0, `#${topColor.toString(16).padStart(6, '0')}`);
        gradient.addColorStop(1, `#${bottomColor.toString(16).padStart(6, '0')}`);

        this.skyContext.fillStyle = gradient;
        this.skyContext.fillRect(0, 0, this.skyCanvas.width, this.skyCanvas.height);
        this.sky.material.map.needsUpdate = true;
    }

    updateCelestialPositions() {
        // Calculate positions based on time of day
        const timeRad = (this.timeOfDay / 24) * Math.PI * 2;

        // Sun follows a circular path
        const sunRadius = 80;
        this.sun.position.x = Math.cos(timeRad) * sunRadius;
        this.sun.position.y = Math.sin(timeRad) * sunRadius;
        this.sun.position.z = Math.sin(timeRad) * sunRadius * 0.5;

        // Moon is opposite the sun
        this.moon.position.x = -this.sun.position.x;
        this.moon.position.y = -this.sun.position.y;
        this.moon.position.z = -this.sun.position.z;

        // Update visibility and intensity
        const sunHeight = this.sun.position.y;
        const moonHeight = this.moon.position.y;

        // Sun visibility and intensity
        if (sunHeight > -10) {
            this.sun.visible = true;
            const sunIntensity = Math.max(0, Math.min(1, (sunHeight + 10) / 50));
            this.updateSunLighting(sunIntensity);
        } else {
            this.sun.visible = false;
            this.updateSunLighting(0);
        }

        // Moon visibility
        this.moon.visible = moonHeight > -10;
        this.stars.visible = this.moon.visible && sunHeight < -5;

        // Moon and stars brightness based on sun position
        const moonBrightness = Math.max(0, Math.min(1, (-sunHeight - 5) / 15));
        this.moon.material.emissiveIntensity = 0.3 * moonBrightness;

        this.stars.children.forEach(star => {
            star.material.opacity = 0.3 + 0.7 * moonBrightness;
        });
    }

    updateSunLighting(intensity) {
        // Update directional light (main sunlight)
        const directionalLight = this.scene.scene.children.find(child => 
            child instanceof THREE.DirectionalLight
        );

        if (directionalLight) {
            directionalLight.intensity = intensity;
            directionalLight.color.setHSL(0.1, 0.8, 0.5 + intensity * 0.3);
        }

        // Update ambient light
        const ambientLight = this.scene.scene.children.find(child => 
            child instanceof THREE.AmbientLight
        );

        if (ambientLight) {
            ambientLight.intensity = 0.1 + intensity * 0.5;
        }
    }

    setupTimeUI() {
        // Create time control UI
        const timeUI = document.createElement('div');
        timeUI.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            min-width: 200px;
            backdrop-filter: blur(10px);
        `;

        timeUI.innerHTML = `
            <div style="margin-bottom: 10px;">
                <strong>🕒 Time of Day</strong>
            </div>
            <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">
                <input type="range" id="timeSlider" min="0" max="24" step="0.1" value="12" 
                       style="flex: 1;" oninput="dayNightCycle.setTime(this.value)">
                <span id="timeDisplay" style="font-weight: bold; min-width: 50px;">12:00</span>
            </div>
            <div style="display: flex; gap: 5px;">
                <button onclick="dayNightCycle.setTime(6)" style="flex: 1; padding: 5px;">🌅 Dawn</button>
                <button onclick="dayNightCycle.setTime(12)" style="flex: 1; padding: 5px;">☀️ Noon</button>
                <button onclick="dayNightCycle.setTime(18)" style="flex: 1; padding: 5px;">🌇 Dusk</button>
                <button onclick="dayNightCycle.setTime(0)" style="flex: 1; padding: 5px;">🌙 Midnight</button>
            </div>
            <div style="margin-top: 10px; display: flex; gap: 5px;">
                <button onclick="dayNightCycle.togglePause()" id="pauseTimeBtn" 
                        style="flex: 1; padding: 5px;">⏸️ Pause</button>
                <button onclick="dayNightCycle.setSpeed(0.1)" style="padding: 5px;">1x</button>
                <button onclick="dayNightCycle.setSpeed(0.5)" style="padding: 5px;">5x</button>
                <button onclick="dayNightCycle.setSpeed(2)" style="padding: 5px;">20x</button>
            </div>
        `;

        document.getElementById('container').appendChild(timeUI);
    }

    setTime(hours) {
        this.timeOfDay = parseFloat(hours) % 24;
        this.updateTimeDisplay();
        this.updateSkyGradient();
        this.updateCelestialPositions();

        // Update environment based on time
        this.updateEnvironmentForTime();
    }

    updateTimeDisplay() {
        const display = document.getElementById('timeDisplay');
        if (display) {
            const hours = Math.floor(this.timeOfDay);
            const minutes = Math.floor((this.timeOfDay % 1) * 60);
            const period = hours >= 12 ? 'PM' : 'AM';
            const displayHours = hours % 12 || 12;
            display.textContent = `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
        }

        const slider = document.getElementById('timeSlider');
        if (slider) {
            slider.value = this.timeOfDay;
        }
    }

    updateEnvironmentForTime() {
        // Update environmental lighting and effects based on time
        const isNight = this.timeOfDay < 6 || this.timeOfDay >= 20;
        const isDawnDusk = (this.timeOfDay >= 6 && this.timeOfDay < 8) || (this.timeOfDay >= 18 && this.timeOfDay < 20);

        // Update point lights (like street lights, romantic spots)
        this.scene.scene.children.forEach(child => {
            if (child instanceof THREE.PointLight) {
                // Romantic spots are brighter at night
                if (child.color.getHex() === 0xff6b6b) { // Romantic spot color
                    child.intensity = isNight ? 0.8 : (isDawnDusk ? 0.4 : 0.2);
                }
            }
        });
    }

    togglePause() {
        this.isPaused = !this.isPaused;
        const button = document.getElementById('pauseTimeBtn');
        if (button) {
            button.textContent = this.isPaused ? '▶️ Resume' : '⏸️ Pause';
        }
    }

    setSpeed(speed) {
        this.timeSpeed = speed;
    }

    update(deltaTime) {
        if (this.isPaused) return;

        // Advance time
        const timePassed = deltaTime * this.timeSpeed; // in game minutes
        this.timeOfDay = (this.timeOfDay + timePassed / 60) % 24;

        this.updateTimeDisplay();
        this.updateSkyGradient();
        this.updateCelestialPositions();
        this.updateEnvironmentForTime();
    }

    getCurrentSeason() {
        // Simple seasonal simulation based on time progression
        // In a real implementation, you'd track actual dates
        const cycleProgress = (this.timeOfDay / 24) % 1;
        if (cycleProgress < 0.25) return 'spring';
        if (cycleProgress < 0.5) return 'summer';
        if (cycleProgress < 0.75) return 'autumn';
        return 'winter';
    }
}

Step 2: Dynamic Weather System - Mood-Enhancing Atmosphere! 🌧️

Let's create a weather system that can change the entire feel of our dating world:

// weather-system.js - Because nothing says romance like sharing an umbrella! ☔

class WeatherSystem {
    constructor(scene, dayNightCycle) {
        this.scene = scene;
        this.dayNightCycle = dayNightCycle;
        this.currentWeather = 'clear';
        this.weatherIntensity = 0;
        this.targetWeather = 'clear';
        this.transitionProgress = 0;
        this.transitionDuration = 10; // seconds

        // Weather effects
        this.rain = null;
        this.snow = null;
        this.fog = null;
        this.clouds = null;

        // Weather probabilities based on time and season
        this.weatherProbabilities = {
            clear: 0.6,
            cloudy: 0.2,
            rainy: 0.15,
            snowy: 0.05
        };

        this.setupWeatherEffects();
        this.setupWeatherUI();
        this.startRandomWeatherChanges();

        console.log("Weather system initialized! Prepare for some atmospheric romance! 🌤️");
    }

    setupWeatherEffects() {
        // Create rain particle system
        this.rain = this.createRain();
        this.scene.scene.add(this.rain);

        // Create snow particle system
        this.snow = this.createSnow();
        this.scene.scene.add(this.snow);

        // Create cloud system
        this.clouds = this.createClouds();
        this.scene.scene.add(this.clouds);

        // Initialize all effects as invisible
        this.setWeatherVisibility('clear');
    }

    createRain() {
        const rainGroup = new THREE.Group();
        const rainCount = 1000;

        const rainGeometry = new THREE.BufferGeometry();
        const positions = new Float32Array(rainCount * 3);
        const velocities = new Float32Array(rainCount);

        for (let i = 0; i < rainCount; i++) {
            // Random starting positions in a large volume
            positions[i * 3] = (Math.random() - 0.5) * 100;
            positions[i * 3 + 1] = Math.random() * 50 + 20;
            positions[i * 3 + 2] = (Math.random() - 0.5) * 100;

            velocities[i] = 0.5 + Math.random() * 0.5; // Fall speed
        }

        rainGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        rainGeometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 1));

        const rainMaterial = new THREE.PointsMaterial({
            color: 0xAAAAFF,
            size: 0.1,
            transparent: true,
            opacity: 0.6
        });

        const rainParticles = new THREE.Points(rainGeometry, rainMaterial);
        rainParticles.userData.velocities = velocities;
        rainGroup.add(rainParticles);

        return rainGroup;
    }

    createSnow() {
        const snowGroup = new THREE.Group();
        const snowCount = 800;

        const snowGeometry = new THREE.BufferGeometry();
        const positions = new Float32Array(snowCount * 3);
        const velocities = new Float32Array(snowCount * 3); // x, y, z velocities

        for (let i = 0; i < snowCount; i++) {
            positions[i * 3] = (Math.random() - 0.5) * 100;
            positions[i * 3 + 1] = Math.random() * 50 + 20;
            positions[i * 3 + 2] = (Math.random() - 0.5) * 100;

            // Snow drifts slowly
            velocities[i * 3] = (Math.random() - 0.5) * 0.02; // x drift
            velocities[i * 3 + 1] = -0.1 - Math.random() * 0.1; // fall speed
            velocities[i * 3 + 2] = (Math.random() - 0.5) * 0.02; // z drift
        }

        snowGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        snowGeometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));

        const snowMaterial = new THREE.PointsMaterial({
            color: 0xFFFFFF,
            size: 0.2,
            transparent: true,
            opacity: 0.8
        });

        const snowParticles = new THREE.Points(snowGeometry, snowMaterial);
        snowParticles.userData.velocities = velocities;
        snowGroup.add(snowParticles);

        return snowGroup;
    }

    createClouds() {
        const cloudGroup = new THREE.Group();
        const cloudCount = 15;

        for (let i = 0; i < cloudCount; i++) {
            const cloud = this.createSingleCloud();

            // Position clouds at different heights and locations
            cloud.position.set(
                (Math.random() - 0.5) * 200,
                15 + Math.random() * 10,
                (Math.random() - 0.5) * 200
            );

            // Random scale
            const scale = 5 + Math.random() * 10;
            cloud.scale.set(scale, scale * 0.5, scale);

            // Random movement speed and direction
            cloud.userData = {
                speed: 0.001 + Math.random() * 0.002,
                direction: new THREE.Vector2(
                    Math.random() - 0.5,
                    Math.random() - 0.5
                ).normalize()
            };

            cloudGroup.add(cloud);
        }

        return cloudGroup;
    }

    createSingleCloud() {
        const cloudGroup = new THREE.Group();
        const puffCount = 5 + Math.floor(Math.random() * 8);

        for (let i = 0; i < puffCount; i++) {
            const puffSize = 1 + Math.random() * 2;
            const puffGeometry = new THREE.SphereGeometry(puffSize, 8, 8);
            const puffMaterial = new THREE.MeshStandardMaterial({
                color: 0xFFFFFF,
                transparent: true,
                opacity: 0.8,
                roughness: 0.9
            });

            const puff = new THREE.Mesh(puffGeometry, puffMaterial);

            // Random position within cloud volume
            puff.position.set(
                (Math.random() - 0.5) * 4,
                (Math.random() - 0.5) * 2,
                (Math.random() - 0.5) * 4
            );

            cloudGroup.add(puff);
        }

        return cloudGroup;
    }

    setWeather(weatherType, intensity = 1) {
        if (this.currentWeather === weatherType && this.weatherIntensity === intensity) return;

        this.targetWeather = weatherType;
        this.targetIntensity = intensity;
        this.transitionProgress = 0;

        console.log(`Weather changing to: ${weatherType} (intensity: ${intensity}) 🌦️`);

        // Update weather probabilities based on new weather
        this.updateWeatherProbabilities(weatherType);
    }

    setWeatherVisibility(weatherType) {
        // Show/hide weather effects based on current weather
        const isRaining = weatherType === 'rainy';
        const isSnowing = weatherType === 'snowy';
        const isCloudy = weatherType === 'cloudy' || isRaining || isSnowing;

        this.rain.visible = isRaining;
        this.snow.visible = isSnowing;
        this.clouds.visible = isCloudy;

        // Update scene fog based on weather
        if (isRaining || isSnowing) {
            this.scene.scene.fog = new THREE.Fog(0x666666, 20, 100);
        } else if (isCloudy) {
            this.scene.scene.fog = new THREE.Fog(0x888888, 30, 150);
        } else {
            this.scene.scene.fog = null;
        }

        // Update lighting based on weather
        this.updateWeatherLighting(weatherType);
    }

    updateWeatherLighting(weatherType) {
        const directionalLight = this.scene.scene.children.find(child => 
            child instanceof THREE.DirectionalLight
        );

        const ambientLight = this.scene.scene.children.find(child => 
            child instanceof THREE.AmbientLight
        );

        if (!directionalLight || !ambientLight) return;

        switch(weatherType) {
            case 'clear':
                directionalLight.intensity = 1;
                ambientLight.intensity = 0.6;
                break;
            case 'cloudy':
                directionalLight.intensity = 0.5;
                ambientLight.intensity = 0.4;
                break;
            case 'rainy':
            case 'snowy':
                directionalLight.intensity = 0.3;
                ambientLight.intensity = 0.3;
                break;
        }
    }

    updateWeatherProbabilities(newWeather) {
        // Adjust probabilities based on current weather (weather tends to persist)
        this.weatherProbabilities[newWeather] += 0.2;

        // Normalize probabilities
        const total = Object.values(this.weatherProbabilities).reduce((a, b) => a + b, 0);
        Object.keys(this.weatherProbabilities).forEach(key => {
            this.weatherProbabilities[key] /= total;
        });
    }

    startRandomWeatherChanges() {
        // Change weather randomly every 2-5 minutes
        setInterval(() => {
            if (Math.random() < 0.3) { // 30% chance to change weather
                this.changeWeatherRandomly();
            }
        }, 120000 + Math.random() * 180000); // 2-5 minutes
    }

    changeWeatherRandomly() {
        const rand = Math.random();
        let cumulativeProb = 0;
        let selectedWeather = 'clear';

        for (const [weather, prob] of Object.entries(this.weatherProbabilities)) {
            cumulativeProb += prob;
            if (rand <= cumulativeProb) {
                selectedWeather = weather;
                break;
            }
        }

        const intensity = selectedWeather === 'clear' ? 0 : 0.3 + Math.random() * 0.7;
        this.setWeather(selectedWeather, intensity);
    }

    setupWeatherUI() {
        const weatherUI = document.createElement('div');
        weatherUI.style.cssText = `
            position: fixed;
            top: 180px;
            right: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            min-width: 150px;
            backdrop-filter: blur(10px);
        `;

        weatherUI.innerHTML = `
            <div style="margin-bottom: 10px;">
                <strong>🌤️ Weather</strong>
            </div>
            <div id="currentWeather" style="margin-bottom: 10px; font-weight: bold; text-align: center;">
                Clear Sky
            </div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
                <button onclick="weatherSystem.setWeather('clear')">☀️ Clear</button>
                <button onclick="weatherSystem.setWeather('cloudy')">☁️ Cloudy</button>
                <button onclick="weatherSystem.setWeather('rainy')">🌧️ Rain</button>
                <button onclick="weatherSystem.setWeather('snowy')">❄️ Snow</button>
            </div>
            <div style="margin-top: 10px;">
                <label>Intensity:</label>
                <input type="range" id="weatherIntensity" min="0" max="1" step="0.1" value="1" 
                       oninput="weatherSystem.setIntensity(this.value)" style="width: 100%;">
            </div>
        `;

        document.getElementById('container').appendChild(weatherUI);
    }

    setIntensity(intensity) {
        this.targetIntensity = parseFloat(intensity);
        this.transitionProgress = 0;
    }

    updateWeatherDisplay() {
        const display = document.getElementById('currentWeather');
        if (display) {
            const weatherNames = {
                clear: '☀️ Clear Sky',
                cloudy: '☁️ Cloudy',
                rainy: '🌧️ Rainy',
                snowy: '❄️ Snowy'
            };
            display.textContent = weatherNames[this.currentWeather];
        }

        const slider = document.getElementById('weatherIntensity');
        if (slider) {
            slider.value = this.weatherIntensity;
        }
    }

    update(deltaTime) {
        // Handle weather transitions
        if (this.currentWeather !== this.targetWeather || this.weatherIntensity !== this.targetIntensity) {
            this.transitionProgress += deltaTime / this.transitionDuration;

            if (this.transitionProgress >= 1) {
                // Transition complete
                this.currentWeather = this.targetWeather;
                this.weatherIntensity = this.targetIntensity;
                this.setWeatherVisibility(this.currentWeather);
            } else {
                // During transition, blend between weather states
                this.blendWeatherEffects(this.transitionProgress);
            }

            this.updateWeatherDisplay();
        }

        // Animate weather effects
        this.animateRain();
        this.animateSnow();
        this.animateClouds();
    }

    blendWeatherEffects(progress) {
        // During transitions, we can blend between different weather effects
        // For simplicity, we'll just crossfade visibility
        if (progress > 0.5) {
            // More than halfway through transition, show target weather
            this.setWeatherVisibility(this.targetWeather);
        }
        // You could implement more sophisticated blending here
    }

    animateRain() {
        if (!this.rain.visible) return;

        const rainParticles = this.rain.children[0];
        const positions = rainParticles.geometry.attributes.position.array;
        const velocities = rainParticles.userData.velocities;

        for (let i = 0; i < positions.length; i += 3) {
            // Move rain down
            positions[i + 1] -= velocities[i / 3] * this.weatherIntensity;

            // Reset rain that has fallen too low
            if (positions[i + 1] < 0) {
                positions[i + 1] = 20 + Math.random() * 30;
                positions[i] = (Math.random() - 0.5) * 100;
                positions[i + 2] = (Math.random() - 0.5) * 100;
            }
        }

        rainParticles.geometry.attributes.position.needsUpdate = true;
    }

    animateSnow() {
        if (!this.snow.visible) return;

        const snowParticles = this.snow.children[0];
        const positions = snowParticles.geometry.attributes.position.array;
        const velocities = snowParticles.userData.velocities;

        for (let i = 0; i < positions.length; i += 3) {
            // Apply velocity
            positions[i] += velocities[i] * this.weatherIntensity;
            positions[i + 1] += velocities[i + 1] * this.weatherIntensity;
            positions[i + 2] += velocities[i + 2] * this.weatherIntensity;

            // Reset snow that has fallen too low or drifted too far
            if (positions[i + 1] < 0 || 
                Math.abs(positions[i]) > 60 || 
                Math.abs(positions[i + 2]) > 60) {

                positions[i] = (Math.random() - 0.5) * 100;
                positions[i + 1] = 20 + Math.random() * 30;
                positions[i + 2] = (Math.random() - 0.5) * 100;
            }
        }

        snowParticles.geometry.attributes.position.needsUpdate = true;
    }

    animateClouds() {
        if (!this.clouds.visible) return;

        this.clouds.children.forEach(cloud => {
            // Move cloud
            cloud.position.x += cloud.userData.direction.x * cloud.userData.speed;
            cloud.position.z += cloud.userData.direction.y * cloud.userData.speed;

            // Wrap around if cloud moves too far
            if (Math.abs(cloud.position.x) > 120) {
                cloud.position.x = -cloud.position.x * 0.9;
            }
            if (Math.abs(cloud.position.z) > 120) {
                cloud.position.z = -cloud.position.z * 0.9;
            }
        });
    }

    getWeatherMood() {
        // Return a mood descriptor based on current weather
        const moods = {
            clear: { mood: 'cheerful', romance: 'high', activity: 'outdoor' },
            cloudy: { mood: 'calm', romance: 'medium', activity: 'any' },
            rainy: { mood: 'cozy', romance: 'high', activity: 'indoor' },
            snowy: { mood: 'magical', romance: 'very high', activity: 'winter' }
        };

        return moods[this.currentWeather] || moods.clear;
    }
}

Step 3: Advanced NPC AI - Smart Dating Personalities! 🧠

Let's create NPCs with personalities, memories, and realistic dating behaviors:

// npc-ai.js - Because even virtual dates deserve good conversation! 💬

class NPCAI {
    constructor(avatar, scene) {
        this.avatar = avatar;
        this.scene = scene;
        this.personality = this.generatePersonality();
        this.mood = this.generateMood();
        this.memory = new NPCMemory();
        this.conversation = new NPCConversation(this);
        this.behavior = new NPCBehavior(this);
        this.relationships = new Map(); // Relationships with other avatars

        this.setupNPC();
        console.log(`NPC ${avatar.options.name} initialized with ${this.personality.traits.join(', ')} personality! 🧠`);
    }

    generatePersonality() {
        const personalities = [
            {
                type: 'romantic',
                traits: ['romantic', 'emotional', 'affectionate'],
                topics: ['love', 'relationships', 'feelings', 'dreams'],
                activities: ['dancing', 'romantic spots', 'deep conversations']
            },
            {
                type: 'adventurous',
                traits: ['energetic', 'curious', 'spontaneous'],
                topics: ['travel', 'adventure', 'hobbies', 'sports'],
                activities: ['exploring', 'dancing', 'active games']
            },
            {
                type: 'intellectual',
                traits: ['thoughtful', 'analytical', 'knowledgeable'],
                topics: ['philosophy', 'science', 'books', 'ideas'],
                activities: ['deep conversations', 'fountain visits']
            },
            {
                type: 'social',
                traits: ['friendly', 'outgoing', 'empathetic'],
                topics: ['people', 'social events', 'feelings', 'stories'],
                activities: ['group activities', 'dancing', 'meeting people']
            }
        ];

        const personality = personalities[Math.floor(Math.random() * personalities.length)];

        // Add some random variation
        const extraTraits = ['humorous', 'shy', 'confident', 'creative'].filter(() => Math.random() > 0.7);
        personality.traits.push(...extraTraits);

        return personality;
    }

    generateMood() {
        const moods = [
            { name: 'happy', energy: 0.8, sociability: 0.9, positivity: 0.9 },
            { name: 'calm', energy: 0.5, sociability: 0.7, positivity: 0.8 },
            { name: 'curious', energy: 0.7, sociability: 0.8, positivity: 0.7 },
            { name: 'romantic', energy: 0.6, sociability: 0.8, positivity: 0.9 },
            { name: 'shy', energy: 0.4, sociability: 0.5, positivity: 0.6 }
        ];

        return moods[Math.floor(Math.random() * moods.length)];
    }

    setupNPC() {
        // Initialize relationships with other NPCs
        this.scene.avatars.forEach(otherAvatar => {
            if (otherAvatar !== this.avatar && otherAvatar.ai) {
                this.relationships.set(otherAvatar, {
                    familiarity: Math.random() * 0.3, // Start with low familiarity
                    affinity: (Math.random() - 0.5) * 0.5, // Slight random affinity
                    lastInteraction: null,
                    interactionCount: 0
                });
            }
        });
    }

    update(deltaTime) {
        // Update mood based on environment and interactions
        this.updateMood(deltaTime);

        // Make decisions based on current state
        this.makeDecisions();

        // Update behavior
        this.behavior.update(deltaTime);
    }

    updateMood(deltaTime) {
        // Mood changes slowly over time and based on environment
        const weatherMood = this.scene.weatherSystem?.getWeatherMood() || { mood: 'calm' };
        const timeOfDay = this.scene.dayNightCycle?.timeOfDay || 12;

        // Adjust mood based on environment
        switch(weatherMood.mood) {
            case 'cheerful':
                this.mood.positivity += 0.01;
                break;
            case 'cozy':
                this.mood.energy -= 0.005;
                this.mood.positivity += 0.005;
                break;
            case 'magical':
                this.mood.positivity += 0.02;
                break;
        }

        // Adjust based on time of day
        if (timeOfDay >= 22 || timeOfDay <= 6) {
            this.mood.energy -= 0.01; // Tired at night
        } else if (timeOfDay >= 12 && timeOfDay <= 14) {
            this.mood.energy += 0.01; // Energetic around noon
        }

        // Clamp values
        this.mood.energy = Math.max(0.1, Math.min(1, this.mood.energy));
        this.mood.sociability = Math.max(0.1, Math.min(1, this.mood.sociability));
        this.mood.positivity = Math.max(0.1, Math.min(1, this.mood.positivity));

        // Update mood name based on values
        this.updateMoodName();
    }

    updateMoodName() {
        if (this.mood.positivity > 0.8 && this.mood.energy > 0.7) {
            this.mood.name = 'happy';
        } else if (this.mood.positivity > 0.7 && this.mood.energy < 0.5) {
            this.mood.name = 'calm';
        } else if (this.mood.positivity > 0.6 && this.mood.sociability > 0.7) {
            this.mood.name = 'social';
        } else if (this.mood.positivity < 0.4) {
            this.mood.name = 'reserved';
        } else {
            this.mood.name = 'neutral';
        }
    }

    makeDecisions() {
        // Only make decisions occasionally
        if (Math.random() > 0.01) return;

        const decisions = [];

        // Decision: Should I approach someone?
        if (this.mood.sociability > 0.7 && Math.random() < 0.3) {
            const potentialPartner = this.findPotentialPartner();
            if (potentialPartner) {
                decisions.push(() => this.approachAvatar(potentialPartner));
            }
        }

        // Decision: Should I use an interactive element?
        if (Math.random() < 0.2) {
            const activity = this.chooseActivity();
            if (activity) {
                decisions.push(() => this.startActivity(activity));
            }
        }

        // Decision: Should I express my mood?
        if (Math.random() < 0.1) {
            decisions.push(() => this.expressMood());
        }

        // Execute one random decision
        if (decisions.length > 0) {
            const decision = decisions[Math.floor(Math.random() * decisions.length)];
            decision();
        }
    }

    findPotentialPartner() {
        let bestPartner = null;
        let bestScore = 0;

        this.scene.avatars.forEach(otherAvatar => {
            if (otherAvatar === this.avatar || !otherAvatar.ai) return;

            const relationship = this.relationships.get(otherAvatar);
            if (!relationship) return;

            // Calculate compatibility score
            let score = relationship.familiarity * 0.5;
            score += relationship.affinity * 0.3;
            score += this.calculatePersonalityCompatibility(otherAvatar.ai.personality) * 0.2;

            // Mood affects willingness to socialize
            score *= this.mood.sociability;

            if (score > bestScore && score > 0.3) {
                bestScore = score;
                bestPartner = otherAvatar;
            }
        });

        return bestPartner;
    }

    calculatePersonalityCompatibility(otherPersonality) {
        // Simple compatibility calculation based on personality traits
        const compatiblePairs = {
            'romantic': ['romantic', 'social', 'intellectual'],
            'adventurous': ['adventurous', 'social'],
            'intellectual': ['intellectual', 'romantic'],
            'social': ['social', 'adventurous', 'romantic']
        };

        const myType = this.personality.type;
        const theirType = otherPersonality.type;

        return compatiblePairs[myType]?.includes(theirType) ? 0.8 : 0.4;
    }

    approachAvatar(targetAvatar) {
        console.log(`${this.avatar.options.name} is approaching ${targetAvatar.options.name}! 🚶‍♂️`);

        // Move towards the target avatar
        const targetPos = targetAvatar.mesh.position.clone();
        const direction = new THREE.Vector3()
            .subVectors(targetPos, this.avatar.mesh.position)
            .normalize();

        const approachDistance = 2; // Stop 2 units away
        const approachPos = targetPos.clone().sub(direction.multiplyScalar(approachDistance));

        this.avatar.movement.moveTo(approachPos);

        // Update relationship
        const relationship = this.relationships.get(targetAvatar);
        if (relationship) {
            relationship.familiarity += 0.1;
            relationship.lastInteraction = Date.now();
            relationship.interactionCount++;
        }

        // Start conversation after arriving
        setTimeout(() => {
            if (this.avatar.mesh.position.distanceTo(targetPos) <= approachDistance + 1) {
                this.startConversation(targetAvatar);
            }
        }, 3000);
    }

    startConversation(targetAvatar) {
        console.log(`${this.avatar.options.name} starts chatting with ${targetAvatar.options.name}! 💬`);

        // Make avatars face each other
        this.avatar.mesh.lookAt(targetAvatar.mesh.position);
        targetAvatar.mesh.lookAt(this.avatar.mesh.position);

        // Start conversation through the conversation system
        this.conversation.startDialogue(targetAvatar);
    }

    chooseActivity() {
        const activities = this.personality.activities;
        const availableActivities = [
            'sit', 'dance', 'romantic_spot', 'fountain', 'walk'
        ];

        // Filter activities based on personality preferences
        const preferredActivities = availableActivities.filter(activity => {
            if (activity === 'dance' && this.personality.activities.includes('dancing')) return true;
            if (activity === 'romantic_spot' && this.personality.traits.includes('romantic')) return true;
            if (activity === 'walk' && this.personality.traits.includes('adventurous')) return true;
            return Math.random() > 0.5;
        });

        return preferredActivities.length > 0 ? 
            preferredActivities[Math.floor(Math.random() * preferredActivities.length)] : 
            null;
    }

    startActivity(activity) {
        switch(activity) {
            case 'sit':
                this.scene.interactiveEnv.sitOnBench(this.avatar);
                break;
            case 'dance':
                this.scene.interactiveEnv.startDancing(this.avatar);
                break;
            case 'romantic_spot':
                this.scene.interactiveEnv.enterRomanticSpot(this.avatar);
                break;
            case 'fountain':
                this.scene.interactiveEnv.admireFountain(this.avatar);
                break;
            case 'walk':
                this.goForWalk();
                break;
        }
    }

    goForWalk() {
        const randomX = (Math.random() - 0.5) * 30;
        const randomZ = (Math.random() - 0.5) * 30;
        this.avatar.movement.moveTo(new THREE.Vector3(randomX, 0, randomZ));
    }

    expressMood() {
        const moodEmotes = {
            'happy': 'celebrate',
            'calm': 'wave',
            'social': 'dance',
            'romantic': 'heart',
            'shy': 'shy'
        };

        const emote = moodEmotes[this.mood.name] || 'wave';
        this.scene.emoteSystem.playEmote(emote, this.avatar);
    }

    receiveMessage(sender, message) {
        // Process incoming message and generate response
        return this.conversation.generateResponse(sender, message);
    }

    getIntroduction() {
        // Generate a personalized introduction
        const introductions = {
            romantic: [
                `Hi! I'm ${this.avatar.options.name}. I believe every moment can be magical if you're with the right person.`,
                `Hello there! ${this.avatar.options.name} here. I'm always looking for meaningful connections.`
            ],
            adventurous: [
                `Hey! I'm ${this.avatar.options.name}! Love exploring and trying new things. What's your story?`,
                `Hi! ${this.avatar.options.name} here. Always up for an adventure or a good conversation!`
            ],
            intellectual: [
                `Greetings. I'm ${this.avatar.options.name}. I enjoy deep conversations and learning new perspectives.`,
                `Hello. ${this.avatar.options.name} here. I find the complexity of human connections fascinating.`
            ],
            social: [
                `Hi there! I'm ${this.avatar.options.name} 😊 I love meeting new people and hearing their stories!`,
                `Hey! ${this.avatar.options.name} here! What brings you to this lovely place today?`
            ]
        };

        const typeIntros = introductions[this.personality.type] || introductions.social;
        return typeIntros[Math.floor(Math.random() * typeIntros.length)];
    }
}

class NPCMemory {
    constructor() {
        this.interactions = [];
        this.learnedFacts = new Map(); // avatar -> facts
        this.preferences = new Map(); // topic -> preference score
        this.conversationHistory = [];
    }

    recordInteraction(avatar, interactionType, outcome) {
        this.interactions.push({
            avatar: avatar,
            type: interactionType,
            outcome: outcome,
            timestamp: Date.now()
        });

        // Keep only recent interactions
        if (this.interactions.length > 50) {
            this.interactions = this.interactions.slice(-50);
        }
    }

    learnFact(avatar, fact, reliability = 1) {
        if (!this.learnedFacts.has(avatar)) {
            this.learnedFacts.set(avatar, []);
        }

        this.learnedFacts.get(avatar).push({
            fact: fact,
            reliability: reliability,
            timestamp: Date.now()
        });
    }

    updatePreference(topic, change) {
        const current = this.preferences.get(topic) || 0;
        this.preferences.set(topic, Math.max(-1, Math.min(1, current + change)));
    }

    getAvatarOpinion(avatar) {
        const interactions = this.interactions.filter(i => i.avatar === avatar);
        if (interactions.length === 0) return 0;

        const recentInteractions = interactions.slice(-10);
        const totalOutcome = recentInteractions.reduce((sum, i) => sum + i.outcome, 0);
        return totalOutcome / recentInteractions.length;
    }
}

class NPCConversation {
    constructor(ai) {
        this.ai = ai;
        this.currentPartner = null;
        this.conversationState = 'idle';
        this.lastMessageTime = 0;

        // Conversation knowledge base
        this.responses = this.buildResponseLibrary();
    }

    buildResponseLibrary() {
        return {
            greetings: [
                "Hi there! 😊",
                "Hello! Lovely to meet you!",
                "Hey! How's your day going?",
                "Hi! Beautiful weather we're having, isn't it?"
            ],
            questions: {
                hobbies: [
                    "I love exploring this virtual world and meeting new people!",
                    "I enjoy dancing and finding romantic spots around here.",
                    "Lately I've been practicing my conversation skills with interesting people like you!",
                    "I'm really into deep conversations and getting to know what makes people tick."
                ],
                origin: [
                    "I emerged into this digital realm not too long ago! Every day is a new adventure.",
                    "I'm from the virtual world, but I'm more interested in where we're going than where we came from!",
                    "Let's just say I was created for meaningful connections like this one."
                ],
                interests: [
                    "I'm fascinated by human connections and relationships.",
                    "I love experiencing different weather and times of day here.",
                    "Meeting diverse personalities and learning their stories is my favorite thing!"
                ]
            },
            compliments: [
                "You have a really wonderful energy about you!",
                "I'm really enjoying our conversation!",
                "You ask such interesting questions!",
                "Your perspective is really refreshing!"
            ],
            romantic: [
                "There's something special about this connection, don't you think?",
                "I feel like we're really clicking!",
                "This moment feels pretty magical with you.",
                "I love how comfortable I feel talking with you."
            ]
        };
    }

    startDialogue(partner) {
        this.currentPartner = partner;
        this.conversationState = 'active';
        this.lastMessageTime = Date.now();

        // Start with introduction
        const introduction = this.ai.getIntroduction();
        this.sendMessage(introduction);

        // Set up continued conversation
        setTimeout(() => {
            this.continueConversation();
        }, 3000);
    }

    generateResponse(sender, message) {
        // Analyze message and generate appropriate response
        const messageLower = message.toLowerCase();
        let response = "";

        // Simple response logic - in a real system, you'd use NLP
        if (this.isGreeting(messageLower)) {
            response = this.getRandomResponse('greetings');
        } else if (this.isQuestion(messageLower)) {
            response = this.answerQuestion(messageLower);
        } else if (this.isCompliment(messageLower)) {
            response = this.getRandomResponse('compliments');
            this.ai.mood.positivity += 0.1;
        } else if (this.isRomantic(messageLower)) {
            if (this.ai.personality.traits.includes('romantic')) {
                response = this.getRandomResponse('romantic');
            } else {
                response = "That's really sweet of you to say!";
            }
        } else {
            // Default friendly response
            response = this.getDefaultResponse();
        }

        // Record interaction
        this.ai.memory.recordInteraction(sender, 'conversation', 0.1);

        return response;
    }

    isGreeting(message) {
        return /\b(hi|hello|hey|greetings|howdy)\b/.test(message);
    }

    isQuestion(message) {
        return /\b(what|where|when|why|how|who)\b/.test(message) || message.includes('?');
    }

    isCompliment(message) {
        return /\b(nice|great|awesome|wonderful|amazing|like|love|good)\b/.test(message);
    }

    isRomantic(message) {
        return /\b(love|romantic|special|beautiful|handsome|pretty|cute)\b/.test(message);
    }

    answerQuestion(message) {
        if (message.includes('hobby') || message.includes('do for fun')) {
            return this.getRandomResponse('questions.hobbies');
        } else if (message.includes('from') || message.includes('origin')) {
            return this.getRandomResponse('questions.origin');
        } else if (message.includes('interest') || message.includes('like')) {
            return this.getRandomResponse('questions.interests');
        } else {
            return "That's an interesting question! What made you think of that?";
        }
    }

    getRandomResponse(category) {
        const categories = category.split('.');
        let current = this.responses;

        for (const cat of categories) {
            current = current[cat];
        }

        return current[Math.floor(Math.random() * current.length)];
    }

    getDefaultResponse() {
        const defaults = [
            "That's really interesting! Tell me more.",
            "I see what you mean!",
            "That's a great point!",
            "I'm enjoying this conversation!",
            "What do you think about that?"
        ];
        return defaults[Math.floor(Math.random() * defaults.length)];
    }

    sendMessage(message) {
        // In a real implementation, this would send to the chat system
        console.log(`${this.ai.avatar.options.name}: ${message}`);

        // Simulate sending to chat
        if (this.currentPartner && this.currentPartner.ai) {
            setTimeout(() => {
                const response = this.currentPartner.ai.receiveMessage(this.ai.avatar, message);
                this.handlePartnerResponse(response);
            }, 2000 + Math.random() * 3000);
        }
    }

    handlePartnerResponse(response) {
        this.lastMessageTime = Date.now();

        // Continue conversation
        setTimeout(() => {
            this.continueConversation();
        }, 3000);
    }

    continueConversation() {
        if (this.conversationState !== 'active') return;

        // Sometimes end conversation naturally
        if (Math.random() < 0.2) {
            this.endConversation();
            return;
        }

        // Continue with new topic or question
        const continuations = [
            () => this.askQuestion(),
            () => this.shareThought(),
            () => this.giveCompliment()
        ];

        const continuation = continuations[Math.floor(Math.random() * continuations.length)];
        continuation();
    }

    askQuestion() {
        const questions = [
            "What brings you here today?",
            "What's your favorite thing about this virtual world?",
            "If you could be anywhere right now, where would you be?",
            "What makes you truly happy?"
        ];
        const question = questions[Math.floor(Math.random() * questions.length)];
        this.sendMessage(question);
    }

    shareThought() {
        const thoughts = [
            "I was just thinking about how amazing it is that we can connect like this.",
            "You know, I really appreciate conversations that have depth and meaning.",
            "I love how the weather changes here - it always sets a different mood."
        ];
        const thought = thoughts[Math.floor(Math.random() * thoughts.length)];
        this.sendMessage(thought);
    }

    giveCompliment() {
        const compliment = this.getRandomResponse('compliments');
        this.sendMessage(compliment);
    }

    endConversation() {
        const farewells = [
            "It was really wonderful talking with you! Hope to chat again soon!",
            "I need to be going now, but this was really nice! Take care!",
            "Thanks for the great conversation! I enjoyed learning about you!",
            "I should probably let you mingle with others now. It was lovely meeting you!"
        ];

        const farewell = farewells[Math.floor(Math.random() * farewells.length)];
        this.sendMessage(farewell);

        this.conversationState = 'idle';
        this.currentPartner = null;

        // Return to wandering
        this.ai.goForWalk();
    }
}

class NPCBehavior {
    constructor(ai) {
        this.ai = ai;
        this.currentAction = null;
        this.actionTimeout = null;
    }

    update(deltaTime) {
        // Implement specific behaviors based on personality and mood
        if (!this.currentAction && Math.random() < 0.001) {
            this.chooseRandomAction();
        }
    }

    chooseRandomAction() {
        const actions = [
            'look_around',
            'adjust_position',
            'play_idle_animation',
            'observe_environment'
        ];

        this.currentAction = actions[Math.floor(Math.random() * actions.length)];
        this.executeAction(this.currentAction);

        // Set timeout to end action
        this.actionTimeout = setTimeout(() => {
            this.currentAction = null;
        }, 3000 + Math.random() * 5000);
    }

    executeAction(action) {
        switch(action) {
            case 'look_around':
                this.lookAround();
                break;
            case 'adjust_position':
                this.adjustPosition();
                break;
            case 'play_idle_animation':
                this.playIdleAnimation();
                break;
            case 'observe_environment':
                this.observeEnvironment();
                break;
        }
    }

    lookAround() {
        // Smoothly rotate head to look around
        const originalRotation = this.ai.avatar.head.rotation.y;
        const targetRotation = originalRotation + (Math.random() - 0.5) * 1;

        this.animateHeadRotation(originalRotation, targetRotation);
    }

    animateHeadRotation(from, to) {
        let progress = 0;
        const duration = 1000;
        const startTime = Date.now();

        const animate = () => {
            progress = (Date.now() - startTime) / duration;
            if (progress < 1) {
                this.ai.avatar.head.rotation.y = from + (to - from) * progress;
                requestAnimationFrame(animate);
            } else {
                // Return to center after a delay
                setTimeout(() => {
                    this.animateHeadRotation(to, 0);
                }, 1000);
            }
        };

        animate();
    }

    adjustPosition() {
        // Small position adjustments to appear more natural
        const smallMove = new THREE.Vector3(
            (Math.random() - 0.5) * 0.5,
            0,
            (Math.random() - 0.5) * 0.5
        );

        this.ai.avatar.mesh.position.add(smallMove);
    }

    playIdleAnimation() {
        // Play subtle idle animations
        const animations = ['stretch', 'shift_weight', 'glance_around'];
        const animation = animations[Math.floor(Math.random() * animations.length)];

        // Simple animation implementation
        if (animation === 'stretch') {
            this.ai.avatar.leftArm.rotation.z += 0.1;
            setTimeout(() => {
                this.ai.avatar.leftArm.rotation.z -= 0.1;
            }, 1000);
        }
    }

    observeEnvironment() {
        // Look at interesting things in the environment
        const interestingObjects = this.ai.scene.scene.children.filter(child => 
            child.name && child.name.includes('fountain') || 
            child.name && child.name.includes('romantic')
        );

        if (interestingObjects.length > 0) {
            const target = interestingObjects[Math.floor(Math.random() * interestingObjects.length)];
            this.ai.avatar.mesh.lookAt(target.position);
        }
    }
}

Step 4: Updated DatingScene Class - The Grand Integration! 🎉

Let's update our main class to integrate all these amazing new systems:

// Updated DatingScene class for Part 5

class DatingScene {
    constructor() {
        // ... existing properties ...

        // New properties for Part 5
        this.dayNightCycle = null;
        this.weatherSystem = null;
        this.lastTime = performance.now();

        this.init();
    }

    init() {
        this.createScene();
        this.createCamera();
        this.createRenderer();
        this.createLights();
        this.createEnvironment();

        // Initialize core systems
        this.proximityChat = new ProximityChat(this);
        this.voiceChat = new VoiceChatSystem(this);
        this.emoteSystem = new EmoteSystem(this);
        this.interactiveEnv = new InteractiveEnvironment(this);

        // Initialize new Part 5 systems
        this.dayNightCycle = new DayNightCycle(this);
        this.weatherSystem = new WeatherSystem(this, this.dayNightCycle);

        this.setupKeyboardListeners();
        this.animate();

        setTimeout(() => {
            document.getElementById('loadingScreen').style.display = 'none';
            document.getElementById('chatUI').style.display = 'block';
            this.addSampleAvatars();
        }, 2000);
    }

    addSampleAvatars() {
        console.log("Adding sophisticated NPCs with AI personalities! 🧠");

        const sampleAvatars = [
            {
                name: "Elena",
                gender: "female",
                skinTone: 0xF0D9B5,
                hairColor: 0x2C1810,
                clothingColor: 0x2E8B57,
                personality: 'romantic'
            },
            {
                name: "Marcus",
                gender: "male",
                skinTone: 0xE8B298, 
                hairColor: 0x8B4513,
                clothingColor: 0x9370DB,
                personality: 'intellectual'
            },
            {
                name: "Chloe",
                gender: "female",
                skinTone: 0xFFDBAC,
                hairColor: 0xFFD700,
                clothingColor: 0xFF69B4,
                personality: 'adventurous'
            },
            {
                name: "David",
                gender: "male",
                skinTone: 0xD2B48C,
                hairColor: 0x000000,
                clothingColor: 0x4169E1,
                personality: 'social'
            }
        ];

        sampleAvatars.forEach((avatarConfig, index) => {
            const avatar = new Avatar(avatarConfig);

            // Position avatars
            const angle = (index / sampleAvatars.length) * Math.PI * 2;
            const radius = 8;

            avatar.mesh.position.set(
                Math.sin(angle) * radius,
                0,
                Math.cos(angle) * radius
            );

            this.scene.add(avatar.mesh);
            this.avatars.push(avatar);

            // Add AI to NPCs (but not to user avatar)
            avatar.ai = new NPCAI(avatar, this);

            // Add movement and behaviors
            this.addRandomAvatarMovement(avatar);
        });

        setTimeout(() => {
            this.showAvatarCustomization();
        }, 1000);
    }

    createUserAvatar(config) {
        this.currentUser = new Avatar(config);
        this.currentUser.mesh.position.set(0, 0, 3);
        this.scene.add(this.currentUser.mesh);
        this.avatars.push(this.currentUser);

        // Initialize movement system for user avatar (no AI)
        this.currentUser.movement = new AvatarMovement(this.currentUser, this);

        // Set up camera to follow user avatar
        this.datingCamera = new DatingCamera(this.camera, this, this.currentUser);

        console.log(`Welcome, ${config.name}! You're ready to mingle! 💖`);

        document.getElementById('avatarCustomization').style.display = 'none';

        // Make NPCs acknowledge the new user
        this.avatars.forEach(avatar => {
            if (avatar !== this.currentUser && avatar.ai) {
                const userPosition = this.currentUser.mesh.position.clone();
                avatar.lookAt(userPosition);
                setTimeout(() => {
                    avatar.wave();
                    // Some NPCs might approach the new user
                    if (Math.random() < 0.3) {
                        setTimeout(() => {
                            avatar.ai.approachAvatar(this.currentUser);
                        }, 2000);
                    }
                }, 1000);
            }
        });
    }

    animate() {
        const currentTime = performance.now();
        const deltaTime = (currentTime - this.lastTime) / 1000; // Convert to seconds
        this.lastTime = currentTime;

        requestAnimationFrame(() => this.animate());

        // Update all systems with deltaTime
        if (this.datingCamera) {
            this.datingCamera.update();
        }

        if (this.proximityChat) {
            this.proximityChat.update();
        }

        if (this.interactiveEnv) {
            this.interactiveEnv.update();
        }

        if (this.dayNightCycle) {
            this.dayNightCycle.update(deltaTime);
        }

        if (this.weatherSystem) {
            this.weatherSystem.update(deltaTime);
        }

        // Update NPC AI
        this.avatars.forEach(avatar => {
            if (avatar.ai) {
                avatar.ai.update(deltaTime);
            }
        });

        // Rotate decorative elements
        const heart = this.scene.children.find(child => 
            child.geometry && child.geometry.type === 'ExtrudeGeometry'
        );

        if (heart) {
            heart.rotation.y += 0.01;
        }

        this.renderer.render(this.scene, this.camera);
    }

    addRandomAvatarMovement(avatar) {
        // Enhanced NPC movement that considers weather and time
        setInterval(() => {
            if (Math.random() > 0.7 && !this.activeChatPartners.has(avatar) && avatar.ai) {
                const currentWeather = this.weatherSystem?.currentWeather;
                const currentTime = this.dayNightCycle?.timeOfDay;

                // NPCs behave differently based on conditions
                if (currentWeather === 'rainy' && Math.random() < 0.8) {
                    // Seek shelter during rain
                    this.interactiveEnv.sitOnBench(avatar);
                } else if (currentTime >= 22 || currentTime <= 6) {
                    // Slow down at night
                    if (Math.random() < 0.3) {
                        this.interactiveEnv.sitOnBench(avatar);
                    }
                } else {
                    // Normal daytime behavior
                    const randomX = (Math.random() - 0.5) * 30;
                    const randomZ = (Math.random() - 0.5) * 30;
                    avatar.movement.moveTo(new THREE.Vector3(randomX, 0, randomZ));
                }
            }
        }, 15000);
    }
}

// Global variables for new systems
let datingScene;
let datingCamera;
let emoteSystem;
let dayNightCycle;
let weatherSystem;

window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    emoteSystem = datingScene.emoteSystem;
    dayNightCycle = datingScene.dayNightCycle;
    weatherSystem = datingScene.weatherSystem;

    console.log("Advanced 3D Dating World loaded! Complete with weather, time, and AI! 🌟");

    setupAvatarInteraction();

    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built in Part 5: 🎉

  1. Dynamic Day/Night Cycle:

    • Realistic sky gradients and celestial movement
    • Dynamic lighting that changes throughout the day
    • Time controls and seasonal simulation
    • Environmental changes based on time
  2. Weather System:

    • Four weather states (clear, cloudy, rainy, snowy)
    • Particle effects for rain and snow
    • Moving cloud systems
    • Weather transitions and mood-based lighting
    • Environmental fog and atmosphere
  3. Advanced NPC AI:

    • Personality system with 4 base types + variations
    • Mood system that changes based on environment and interactions
    • Memory system that remembers interactions and preferences
    • Conversation system with contextual responses
    • Relationship tracking between NPCs
    • Natural behaviors and decision-making
  4. Integrated Experience:

    • NPCs that react to weather and time changes
    • Mood-appropriate behaviors and conversations
    • Environmental storytelling through AI actions
    • Believable social dynamics

Key Features Explained: 🔑

Next Time in Part 6: 🚀

We'll add:

Current Project Status: Our dating world is now a living, breathing environment with dynamic weather, realistic day/night cycles, and NPCs that feel like real people with personalities and memories. The stage is set for meaningful virtual connections! 💕


Fun Fact: Our NPCs now have better conversation skills and more emotional intelligence than most dating app bots! They remember your interactions, have distinct personalities, and even get tired at night - basically they're more human than some actual dates! 😄

Part 6: Mini-Games, Relationships & Performance - Level Up Your Love Life! 🎮💑

Welcome back, virtual cupid! Our dating world is beautiful and intelligent, but let's face it - sometimes you need more than just conversation to spark that special connection. Time to add interactive games, deep relationship systems, and make sure everything runs buttery smooth!

Step 1: Mini-Games System - Play Your Way to Love! 🎯

Let's create engaging mini-games that avatars can play together:

// mini-games.js - Because nothing says "I like you" like friendly competition! 🏆

class MiniGameSystem {
    constructor(scene) {
        this.scene = scene;
        this.activeGames = new Map();
        this.availableGames = {
            'love_quiz': LoveQuizGame,
            'dance_off': DanceOffGame,
            'memory_match': MemoryMatchGame,
            'truth_dare': TruthDareGame
        };

        this.setupGameUI();
        this.createGameAreas();

        console.log("Mini-game system initialized! Ready to play for love! 🎮");
    }

    setupGameUI() {
        // Create game invitation system
        this.invitationUI = document.createElement('div');
        this.invitationUI.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 30px;
            border-radius: 20px;
            z-index: 1000;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            text-align: center;
            display: none;
            min-width: 300px;
            backdrop-filter: blur(10px);
        `;

        this.invitationUI.innerHTML = `
            <h3 style="color: #ff6b6b; margin-bottom: 20px;">🎮 Game Invitation</h3>
            <p id="invitationText" style="margin-bottom: 20px;"></p>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
                <button id="acceptGame" style="background: #4ecdc4; color: white; border: none; padding: 12px; border-radius: 10px; cursor: pointer;">
                    Accept ✅
                </button>
                <button id="declineGame" style="background: #ff6b6b; color: white; border: none; padding: 12px; border-radius: 10px; cursor: pointer;">
                    Decline ❌
                </button>
            </div>
        `;

        document.getElementById('container').appendChild(this.invitationUI);

        // Setup event listeners
        document.getElementById('acceptGame').addEventListener('click', () => this.acceptInvitation());
        document.getElementById('declineGame').addEventListener('click', () => this.declineInvitation());
    }

    createGameAreas() {
        // Create physical game areas in the 3D world
        this.createGameArea('love_quiz', new THREE.Vector3(-15, 0, 0), '💕 Love Quiz');
        this.createGameArea('dance_off', new THREE.Vector3(15, 0, 0), '💃 Dance Off');
        this.createGameArea('memory_match', new THREE.Vector3(0, 0, -15), '🧠 Memory Match');
        this.createGameArea('truth_dare', new THREE.Vector3(0, 0, 15), '🎯 Truth or Dare');
    }

    createGameArea(gameType, position, label) {
        const gameArea = new THREE.Group();

        // Create platform
        const platformGeometry = new THREE.CylinderGeometry(3, 3, 0.2, 32);
        const platformMaterial = new THREE.MeshStandardMaterial({
            color: 0xff6b6b,
            transparent: true,
            opacity: 0.8,
            emissive: 0xff6b6b,
            emissiveIntensity: 0.2
        });

        const platform = new THREE.Mesh(platformGeometry, platformMaterial);
        platform.rotation.x = -Math.PI / 2;
        platform.position.y = 0.1;
        gameArea.add(platform);

        // Create floating icon
        const iconGeometry = new THREE.SphereGeometry(0.5, 16, 16);
        const iconMaterial = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            transparent: true,
            opacity: 0.9
        });

        const icon = new THREE.Mesh(iconGeometry, iconMaterial);
        icon.position.y = 1.5;
        gameArea.add(icon);

        // Add pulsing animation
        this.animateGameIcon(icon);

        // Add label
        this.createGameLabel(gameArea, label);

        gameArea.position.copy(position);
        gameArea.userData = { gameType: gameType };
        this.scene.scene.add(gameArea);

        // Make it interactive
        this.makeGameAreaInteractive(gameArea);
    }

    animateGameIcon(icon) {
        let scale = 1;
        let time = 0;

        const animate = () => {
            if (icon.parent) {
                time += 0.05;
                scale = 1 + Math.sin(time) * 0.2;
                icon.scale.set(scale, scale, scale);

                // Float up and down
                icon.position.y = 1.5 + Math.sin(time * 0.8) * 0.3;

                // Rotate slowly
                icon.rotation.y += 0.01;

                requestAnimationFrame(animate);
            }
        };

        animate();
    }

    createGameLabel(gameArea, text) {
        // Create a simple text label (in production, use proper text geometry)
        const canvas = document.createElement('canvas');
        canvas.width = 256;
        canvas.height = 64;
        const context = canvas.getContext('2d');

        context.fillStyle = 'rgba(255, 107, 107, 0.9)';
        context.fillRect(0, 0, canvas.width, canvas.height);

        context.fillStyle = 'white';
        context.font = 'bold 20px Arial';
        context.textAlign = 'center';
        context.textBaseline = 'middle';
        context.fillText(text, canvas.width / 2, canvas.height / 2);

        const texture = new THREE.CanvasTexture(canvas);
        const material = new THREE.SpriteMaterial({ map: texture });
        const sprite = new THREE.Sprite(material);

        sprite.position.y = 2.5;
        sprite.scale.set(4, 1, 1);
        gameArea.add(sprite);
    }

    makeGameAreaInteractive(gameArea) {
        // Add click interaction
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();

        const checkClick = (event) => {
            if (this.scene.currentUser && this.activeGames.has(this.scene.currentUser)) {
                return; // Already in a game
            }

            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

            raycaster.setFromCamera(mouse, this.scene.camera);
            const intersects = raycaster.intersectObject(gameArea, true);

            if (intersects.length > 0) {
                this.showGameOptions(gameArea.userData.gameType);
            }
        };

        // Add both click and touch support
        window.addEventListener('click', checkClick);
        window.addEventListener('touchend', checkClick);
    }

    showGameOptions(gameType) {
        const gameNames = {
            'love_quiz': '💕 Love Quiz',
            'dance_off': '💃 Dance Off', 
            'memory_match': '🧠 Memory Match',
            'truth_dare': '🎯 Truth or Dare'
        };

        // Find nearby avatars to invite
        const nearbyAvatars = this.findNearbyAvatars();

        if (nearbyAvatars.length === 0) {
            this.showMessage('No one nearby to play with! Move closer to other avatars.');
            return;
        }

        const optionsUI = document.createElement('div');
        optionsUI.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1000;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            min-width: 300px;
            backdrop-filter: blur(10px);
        `;

        optionsUI.innerHTML = `
            <h3 style="color: #ff6b6b; margin-bottom: 15px;">${gameNames[gameType]}</h3>
            <p style="margin-bottom: 15px;">Invite someone to play:</p>
            <div id="avatarList" style="margin-bottom: 20px; max-height: 200px; overflow-y: auto;">
                ${nearbyAvatars.map(avatar => `
                    <div style="display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #eee;">
                        <span>${avatar.options.name}</span>
                        <button onclick="miniGameSystem.sendInvitation('${gameType}', '${avatar.options.name}')" 
                                style="background: #4ecdc4; color: white; border: none; padding: 5px 10px; border-radius: 5px; cursor: pointer;">
                            Invite
                        </button>
                    </div>
                `).join('')}
            </div>
            <button onclick="this.parentElement.remove()" 
                    style="background: #ff6b6b; color: white; border: none; padding: 10px; border-radius: 10px; cursor: pointer; width: 100%;">
                Cancel
            </button>
        `;

        document.getElementById('container').appendChild(optionsUI);
    }

    findNearbyAvatars() {
        if (!this.scene.currentUser) return [];

        return this.scene.avatars.filter(avatar => {
            if (avatar === this.scene.currentUser) return false;
            const distance = this.scene.currentUser.mesh.position.distanceTo(avatar.mesh.position);
            return distance < 8; // Within 8 units
        });
    }

    sendInvitation(gameType, targetName) {
        const targetAvatar = this.scene.avatars.find(a => a.options.name === targetName);
        if (!targetAvatar) return;

        // Close options UI
        document.querySelectorAll('div').forEach(div => {
            if (div.style.zIndex === '1000' && div !== this.invitationUI) {
                div.remove();
            }
        });

        // Store invitation data
        this.pendingInvitation = {
            gameType: gameType,
            from: this.scene.currentUser,
            to: targetAvatar,
            timestamp: Date.now()
        };

        // Show invitation to target (in real implementation, this would be networked)
        if (targetAvatar.ai) {
            // NPC auto-responds
            setTimeout(() => {
                if (Math.random() > 0.3) { // 70% chance to accept
                    this.startGame(gameType, this.scene.currentUser, targetAvatar);
                } else {
                    this.showMessage(`${targetName} declined your game invitation.`);
                }
            }, 2000);
        } else {
            // For human players, we'd send over network
            this.showMessage(`Invitation sent to ${targetName}!`);
        }
    }

    showInvitation(fromAvatar, gameType) {
        const gameNames = {
            'love_quiz': '💕 Love Quiz',
            'dance_off': '💃 Dance Off',
            'memory_match': '🧠 Memory Match', 
            'truth_dare': '🎯 Truth or Dare'
        };

        this.invitationUI.style.display = 'block';
        document.getElementById('invitationText').textContent = 
            `${fromAvatar.options.name} invites you to play ${gameNames[gameType]}!`;

        this.currentInvitation = { fromAvatar, gameType };
    }

    acceptInvitation() {
        if (!this.currentInvitation) return;

        const { fromAvatar, gameType } = this.currentInvitation;
        this.startGame(gameType, fromAvatar, this.scene.currentUser);
        this.invitationUI.style.display = 'none';
        this.currentInvitation = null;
    }

    declineInvitation() {
        this.invitationUI.style.display = 'none';
        this.currentInvitation = null;
        this.showMessage("You declined the game invitation.");
    }

    startGame(gameType, player1, player2) {
        console.log(`Starting ${gameType} between ${player1.options.name} and ${player2.options.name}! 🎮`);

        const GameClass = this.availableGames[gameType];
        if (!GameClass) return;

        const game = new GameClass(this.scene, player1, player2);
        this.activeGames.set(player1, game);
        this.activeGames.set(player2, game);

        // Position avatars for the game
        this.positionAvatarsForGame(player1, player2);

        // Start the game
        game.start();

        // Show game UI
        this.showGameUI(game);
    }

    positionAvatarsForGame(player1, player2) {
        // Move avatars to face each other at appropriate distance
        const midpoint = new THREE.Vector3()
            .addVectors(player1.mesh.position, player2.mesh.position)
            .multiplyScalar(0.5);

        const direction = new THREE.Vector3()
            .subVectors(player2.mesh.position, player1.mesh.position)
            .normalize();

        // Position 3 units apart, facing each other
        player1.movement.moveTo(midpoint.clone().add(direction.clone().multiplyScalar(-1.5)));
        player2.movement.moveTo(midpoint.clone().add(direction.clone().multiplyScalar(1.5)));

        setTimeout(() => {
            player1.mesh.lookAt(player2.mesh.position);
            player2.mesh.lookAt(player1.mesh.position);
        }, 1000);
    }

    showGameUI(game) {
        const gameUI = document.createElement('div');
        gameUI.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 999;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            min-width: 400px;
            max-width: 90vw;
            backdrop-filter: blur(10px);
        `;

        gameUI.id = 'gameUI';
        game.uiElement = gameUI;

        document.getElementById('container').appendChild(gameUI);
        game.updateUI();
    }

    endGame(game) {
        // Remove from active games
        this.activeGames.forEach((activeGame, avatar) => {
            if (activeGame === game) {
                this.activeGames.delete(avatar);
            }
        });

        // Remove UI
        if (game.uiElement) {
            game.uiElement.remove();
        }

        console.log("Game ended! 🏁");
    }

    showMessage(text) {
        const message = document.createElement('div');
        message.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(255, 107, 107, 0.9);
            color: white;
            padding: 15px 25px;
            border-radius: 25px;
            z-index: 1000;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        `;
        message.textContent = text;

        document.getElementById('container').appendChild(message);

        setTimeout(() => {
            message.remove();
        }, 3000);
    }
}

// Base Game Class
class MiniGame {
    constructor(scene, player1, player2) {
        this.scene = scene;
        this.players = [player1, player2];
        this.scores = new Map();
        this.scores.set(player1, 0);
        this.scores.set(player2, 0);
        this.currentTurn = player1;
        this.isActive = false;
        this.duration = 0;
    }

    start() {
        this.isActive = true;
        this.startTime = Date.now();
        console.log(`Game started between ${this.players[0].options.name} and ${this.players[1].options.name}`);
    }

    end() {
        this.isActive = false;
        this.duration = Date.now() - this.startTime;

        // Determine winner
        const winner = this.getWinner();
        this.onGameEnd(winner);

        // Notify game system
        this.scene.miniGameSystem.endGame(this);
    }

    getWinner() {
        const score1 = this.scores.get(this.players[0]);
        const score2 = this.scores.get(this.players[1]);

        if (score1 > score2) return this.players[0];
        if (score2 > score1) return this.players[1];
        return null; // Tie
    }

    updateUI() {
        // To be implemented by specific games
    }

    onGameEnd(winner) {
        // Show results
        const resultsUI = document.createElement('div');
        resultsUI.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 30px;
            border-radius: 20px;
            z-index: 1001;
            text-align: center;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            backdrop-filter: blur(10px);
        `;

        if (winner) {
            resultsUI.innerHTML = `
                <h3 style="color: #ff6b6b;">🎉 Game Over! 🎉</h3>
                <p style="font-size: 24px; margin: 20px 0;">${winner.options.name} wins!</p>
                <p>Final Scores:</p>
                <p>${this.players[0].options.name}: ${this.scores.get(this.players[0])}</p>
                <p>${this.players[1].options.name}: ${this.scores.get(this.players[1])}</p>
                <button onclick="this.parentElement.remove()" 
                        style="background: #ff6b6b; color: white; border: none; padding: 10px 20px; border-radius: 10px; cursor: pointer; margin-top: 15px;">
                    Continue Mingling
                </button>
            `;

            // Winner celebration
            winner.dance();
        } else {
            resultsUI.innerHTML = `
                <h3 style="color: #ff6b6b;">🎉 It's a Tie! 🎉</h3>
                <p>Both players scored ${this.scores.get(this.players[0])} points!</p>
                <button onclick="this.parentElement.remove()" 
                        style="background: #ff6b6b; color: white; border: none; padding: 10px 20px; border-radius: 10px; cursor: pointer; margin-top: 15px;">
                    Continue Mingling
                </button>
            `;
        }

        document.getElementById('container').appendChild(resultsUI);

        // Update relationships based on game outcome
        this.updateRelationships(winner);
    }

    updateRelationships(winner) {
        // Games affect relationships between players
        this.players.forEach(player => {
            if (player.ai) {
                const otherPlayer = this.players.find(p => p !== player);
                const relationship = player.ai.relationships.get(otherPlayer);
                if (relationship) {
                    if (winner === player) {
                        relationship.affinity += 0.1; // Winning increases affinity
                    } else if (winner === otherPlayer) {
                        relationship.affinity -= 0.05; // Losing slightly decreases affinity
                    } else {
                        relationship.affinity += 0.05; // Tie slightly increases affinity
                    }
                    relationship.familiarity += 0.15; // Playing together increases familiarity
                }
            }
        });
    }
}

// Love Quiz Game
class LoveQuizGame extends MiniGame {
    constructor(scene, player1, player2) {
        super(scene, player1, player2);
        this.questions = this.generateQuestions();
        this.currentQuestionIndex = 0;
        this.currentAnswers = new Map();
    }

    generateQuestions() {
        return [
            {
                question: "What's the most important quality in a relationship?",
                options: ["Trust", "Communication", "Passion", "Friendship"],
                correct: 1
            },
            {
                question: "What's your ideal first date?",
                options: ["Adventure", "Romantic dinner", "Coffee chat", "Movie night"],
                correct: Math.floor(Math.random() * 4) // Random correct answer for variety
            },
            {
                question: "What makes you feel most loved?",
                options: ["Words of affirmation", "Quality time", "Acts of service", "Physical touch"],
                correct: Math.floor(Math.random() * 4)
            },
            {
                question: "How do you handle conflict in relationships?",
                options: ["Direct communication", "Taking space", "Seeking compromise", "Using humor"],
                correct: Math.floor(Math.random() * 4)
            },
            {
                question: "What's your love language?",
                options: ["Gift giving", "Quality time", "Words of affirmation", "Physical touch"],
                correct: Math.floor(Math.random() * 4)
            }
        ];
    }

    start() {
        super.start();
        this.askQuestion();
    }

    askQuestion() {
        if (this.currentQuestionIndex >= this.questions.length) {
            this.end();
            return;
        }

        this.currentAnswers.clear();
        this.updateUI();
    }

    submitAnswer(player, answerIndex) {
        this.currentAnswers.set(player, answerIndex);

        // Check if both players have answered
        if (this.currentAnswers.size === 2) {
            this.evaluateAnswers();
            this.currentQuestionIndex++;

            if (this.currentQuestionIndex < this.questions.length) {
                setTimeout(() => this.askQuestion(), 2000);
            } else {
                setTimeout(() => this.end(), 2000);
            }
        }
    }

    evaluateAnswers() {
        const currentQuestion = this.questions[this.currentQuestionIndex];

        this.players.forEach(player => {
            const answer = this.currentAnswers.get(player);
            if (answer === currentQuestion.correct) {
                this.scores.set(player, this.scores.get(player) + 1);

                // Show correct answer feedback
                this.showAnswerFeedback(player, true);
            } else {
                this.showAnswerFeedback(player, false);
            }
        });

        this.updateUI();
    }

    showAnswerFeedback(player, isCorrect) {
        const feedback = document.createElement('div');
        feedback.style.cssText = `
            position: fixed;
            top: 30%;
            left: 50%;
            transform: translateX(-50%);
            background: ${isCorrect ? '#4ecdc4' : '#ff6b6b'};
            color: white;
            padding: 10px 20px;
            border-radius: 20px;
            z-index: 1002;
        `;
        feedback.textContent = isCorrect ? '✅ Correct!' : '❌ Try again!';

        document.getElementById('container').appendChild(feedback);

        setTimeout(() => feedback.remove(), 1500);

        // Avatar reaction
        if (isCorrect) {
            player.dance();
        } else {
            // Play thinking animation
            player.mesh.rotation.z = 0.1;
            setTimeout(() => player.mesh.rotation.z = 0, 1000);
        }
    }

    updateUI() {
        if (!this.uiElement) return;

        const currentQuestion = this.questions[this.currentQuestionIndex];
        const player1Answered = this.currentAnswers.has(this.players[0]);
        const player2Answered = this.currentAnswers.has(this.players[1]);

        this.uiElement.innerHTML = `
            <h3 style="color: #ff6b6b; margin-bottom: 15px;">💕 Love Quiz</h3>
            <div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
                <span>${this.players[0].options.name}: ${this.scores.get(this.players[0])}</span>
                <span>${this.players[1].options.name}: ${this.scores.get(this.players[1])}</span>
            </div>
            <div style="background: #f8f9fa; padding: 15px; border-radius: 10px; margin-bottom: 15px;">
                <p style="margin: 0; font-weight: bold;">Question ${this.currentQuestionIndex + 1}/5:</p>
                <p style="margin: 10px 0;">${currentQuestion.question}</p>
            </div>
            <div style="display: grid; gap: 10px; margin-bottom: 15px;">
                ${currentQuestion.options.map((option, index) => `
                    <button onclick="miniGameSystem.activeGames.get(datingScene.currentUser).submitAnswer(datingScene.currentUser, ${index})"
                            ${player1Answered ? 'disabled' : ''}
                            style="background: ${player1Answered ? '#ccc' : '#4ecdc4'}; color: white; border: none; padding: 12px; border-radius: 8px; cursor: pointer; text-align: left;">
                        ${String.fromCharCode(65 + index)}. ${option}
                    </button>
                `).join('')}
            </div>
            <div style="display: flex; justify-content: space-between; font-size: 12px; color: #666;">
                <span>${this.players[0].options.name}: ${player1Answered ? '✅ Answered' : '⏳ Thinking...'}</span>
                <span>${this.players[1].options.name}: ${player2Answered ? '✅ Answered' : '⏳ Thinking...'}</span>
            </div>
        `;
    }
}

// Dance Off Game
class DanceOffGame extends MiniGame {
    constructor(scene, player1, player2) {
        super(scene, player1, player2);
        this.danceMoves = ['wave', 'dance', 'celebrate', 'shy'];
        this.currentRound = 0;
        this.totalRounds = 3;
        this.playerMoves = new Map();
    }

    start() {
        super.start();
        this.startRound();
    }

    startRound() {
        this.currentRound++;
        this.playerMoves.clear();

        if (this.currentRound > this.totalRounds) {
            this.end();
            return;
        }

        this.updateUI();
        this.promptDanceMove();
    }

    promptDanceMove() {
        const randomMove = this.danceMoves[Math.floor(Math.random() * this.danceMoves.length)];

        // Show dance move prompt
        const prompt = document.createElement('div');
        prompt.style.cssText = `
            position: fixed;
            top: 30%;
            left: 50%;
            transform: translateX(-50%);
            background: #ff6b6b;
            color: white;
            padding: 15px 25px;
            border-radius: 25px;
            z-index: 1002;
            font-size: 18px;
            font-weight: bold;
        `;
        prompt.textContent = `Dance: ${randomMove.toUpperCase()}!`;

        document.getElementById('container').appendChild(prompt);

        // Store the required move
        this.requiredMove = randomMove;

        setTimeout(() => {
            prompt.remove();
            this.acceptMoves();
        }, 3000);
    }

    acceptMoves() {
        // Players have 5 seconds to perform the dance move
        this.moveTimeout = setTimeout(() => {
            this.evaluateMoves();
        }, 5000);
    }

    submitMove(player, move) {
        if (this.playerMoves.has(player)) return;

        this.playerMoves.set(player, move);
        player[move](); // Perform the move

        // Check if both players have moved
        if (this.playerMoves.size === 2) {
            clearTimeout(this.moveTimeout);
            this.evaluateMoves();
        }
    }

    evaluateMoves() {
        this.players.forEach(player => {
            const move = this.playerMoves.get(player);
            if (move === this.requiredMove) {
                this.scores.set(player, this.scores.get(player) + 2); // Bonus for correct move
                this.showMoveFeedback(player, true);
            } else if (move) {
                this.scores.set(player, this.scores.get(player) + 1); // Points for any move
                this.showMoveFeedback(player, false);
            }
        });

        this.updateUI();

        // Next round
        setTimeout(() => this.startRound(), 2000);
    }

    showMoveFeedback(player, isPerfect) {
        const feedback = document.createElement('div');
        feedback.style.cssText = `
            position: fixed;
            top: 40%;
            left: 50%;
            transform: translateX(-50%);
            background: ${isPerfect ? '#4ecdc4' : '#ffd166'};
            color: white;
            padding: 10px 20px;
            border-radius: 20px;
            z-index: 1002;
        `;
        feedback.textContent = isPerfect ? '💃 Perfect!' : '👍 Good move!';

        document.getElementById('container').appendChild(feedback);
        setTimeout(() => feedback.remove(), 1500);
    }

    updateUI() {
        if (!this.uiElement) return;

        this.uiElement.innerHTML = `
            <h3 style="color: #ff6b6b; margin-bottom: 15px;">💃 Dance Off</h3>
            <div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
                <span>${this.players[0].options.name}: ${this.scores.get(this.players[0])}</span>
                <span>${this.players[1].options.name}: ${this.scores.get(this.players[1])}</span>
            </div>
            <div style="background: #f8f9fa; padding: 15px; border-radius: 10px; margin-bottom: 15px; text-align: center;">
                <p style="margin: 0; font-weight: bold;">Round ${this.currentRound}/${this.totalRounds}</p>
                <p style="margin: 10px 0; font-size: 24px;">${this.requiredMove ? this.requiredMove.toUpperCase() + '!' : 'Get ready...'}</p>
            </div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                ${this.danceMoves.map(move => `
                    <button onclick="miniGameSystem.activeGames.get(datingScene.currentUser).submitMove(datingScene.currentUser, '${move}')"
                            style="background: #4ecdc4; color: white; border: none; padding: 10px; border-radius: 8px; cursor: pointer;">
                        ${move.charAt(0).toUpperCase() + move.slice(1)}
                    </button>
                `).join('')}
            </div>
            <div style="margin-top: 15px; font-size: 12px; color: #666; text-align: center;">
                Copy the dance move when prompted!
            </div>
        `;
    }
}

Step 2: Advanced Relationship System - From Strangers to Soulmates! 💞

Let's create a deep relationship system that tracks connections and compatibility:

// relationships.js - Because love is more than just a score! 💘

class RelationshipSystem {
    constructor(scene) {
        this.scene = scene;
        this.relationships = new Map(); // avatar -> Map of relationships
        this.compatibilityMatrix = new Map();
        this.milestones = new Set();

        this.setupRelationshipUI();
        console.log("Relationship system initialized! Tracking those love connections! 💕");
    }

    setupRelationshipUI() {
        // Create relationship status panel
        this.relationshipUI = document.createElement('div');
        this.relationshipUI.style.cssText = `
            position: fixed;
            top: 120px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            min-width: 250px;
            max-width: 300px;
            backdrop-filter: blur(10px);
            display: none;
        `;

        this.relationshipUI.innerHTML = `
            <h4 style="margin: 0 0 15px 0; color: #ff6b6b;">� Relationships</h4>
            <div id="relationshipList"></div>
        `;

        document.getElementById('container').appendChild(this.relationshipUI);

        // Toggle button
        const toggleBtn = document.createElement('button');
        toggleBtn.style.cssText = `
            position: fixed;
            top: 120px;
            left: 20px;
            background: rgba(255, 107, 107, 0.9);
            color: white;
            border: none;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            cursor: pointer;
            z-index: 99;
            font-size: 18px;
        `;
        toggleBtn.textContent = '💝';
        toggleBtn.title = 'Show Relationships';

        toggleBtn.addEventListener('click', () => {
            this.relationshipUI.style.display = this.relationshipUI.style.display === 'none' ? 'block' : 'none';
        });

        document.getElementById('container').appendChild(toggleBtn);
    }

    updateRelationship(avatar1, avatar2, interaction) {
        if (!this.relationships.has(avatar1)) {
            this.relationships.set(avatar1, new Map());
        }

        const avatar1Relationships = this.relationships.get(avatar1);
        if (!avatar1Relationships.has(avatar2)) {
            avatar1Relationships.set(avatar2, this.createNewRelationship());
        }

        const relationship = avatar1Relationships.get(avatar2);
        this.processInteraction(relationship, interaction);

        // Check for milestones
        this.checkMilestones(avatar1, avatar2, relationship);

        // Update UI if needed
        if (avatar1 === this.scene.currentUser || avatar2 === this.scene.currentUser) {
            this.updateRelationshipUI();
        }
    }

    createNewRelationship() {
        return {
            friendship: 0,
            romance: 0,
            trust: 0,
            familiarity: 0,
            compatibility: 0,
            lastInteraction: Date.now(),
            interactionCount: 0,
            milestones: new Set(),
            sharedExperiences: []
        };
    }

    processInteraction(relationship, interaction) {
        relationship.lastInteraction = Date.now();
        relationship.interactionCount++;

        switch(interaction.type) {
            case 'conversation':
                relationship.friendship += 0.1;
                relationship.familiarity += 0.15;
                relationship.trust += 0.05;
                break;

            case 'game_win_together':
                relationship.friendship += 0.3;
                relationship.trust += 0.2;
                break;

            case 'game_win_against':
                relationship.friendship += 0.1;
                relationship.trust += 0.1;
                break;

            case 'compliment':
                relationship.romance += 0.2;
                relationship.friendship += 0.1;
                break;

            case 'emote_positive':
                relationship.friendship += 0.05;
                break;

            case 'emote_romantic':
                relationship.romance += 0.15;
                break;

            case 'shared_activity':
                relationship.familiarity += 0.2;
                relationship.friendship += 0.1;
                break;
        }

        // Cap values
        relationship.friendship = Math.min(100, relationship.friendship);
        relationship.romance = Math.min(100, relationship.romance);
        relationship.trust = Math.min(100, relationship.trust);
        relationship.familiarity = Math.min(100, relationship.familiarity);

        // Add shared experience
        if (interaction.description) {
            relationship.sharedExperiences.push({
                description: interaction.description,
                timestamp: Date.now(),
                impact: interaction.impact || 0
            });

            // Keep only recent experiences
            if (relationship.sharedExperiences.length > 10) {
                relationship.sharedExperiences = relationship.sharedExperiences.slice(-10);
            }
        }
    }

    calculateCompatibility(avatar1, avatar2) {
        if (this.compatibilityMatrix.has(avatar1) && this.compatibilityMatrix.get(avatar1).has(avatar2)) {
            return this.compatibilityMatrix.get(avatar1).get(avatar2);
        }

        let compatibility = 0.5; // Base compatibility

        // Personality compatibility
        if (avatar1.ai && avatar2.ai) {
            const personalityComp = this.calculatePersonalityCompatibility(
                avatar1.ai.personality,
                avatar2.ai.personality
            );
            compatibility = compatibility * 0.3 + personalityComp * 0.7;
        }

        // Interaction history compatibility
        const relationship = this.getRelationship(avatar1, avatar2);
        if (relationship) {
            const interactionComp = relationship.friendship / 100;
            compatibility = compatibility * 0.4 + interactionComp * 0.6;
        }

        // Store in matrix
        if (!this.compatibilityMatrix.has(avatar1)) {
            this.compatibilityMatrix.set(avatar1, new Map());
        }
        this.compatibilityMatrix.get(avatar1).set(avatar2, compatibility);

        return compatibility;
    }

    calculatePersonalityCompatibility(personality1, personality2) {
        const compatibilityChart = {
            'romantic': { romantic: 0.9, adventurous: 0.6, intellectual: 0.8, social: 0.7 },
            'adventurous': { romantic: 0.6, adventurous: 0.8, intellectual: 0.5, social: 0.9 },
            'intellectual': { romantic: 0.8, adventurous: 0.5, intellectual: 0.7, social: 0.6 },
            'social': { romantic: 0.7, adventurous: 0.9, intellectual: 0.6, social: 0.8 }
        };

        const baseComp = compatibilityChart[personality1.type]?.[personality2.type] || 0.5;

        // Adjust based on shared traits
        const sharedTraits = personality1.traits.filter(trait => 
            personality2.traits.includes(trait)
        );

        return Math.min(1, baseComp + sharedTraits.length * 0.1);
    }

    checkMilestones(avatar1, avatar2, relationship) {
        const milestones = [
            { threshold: 10, type: 'friendship', name: 'First Friend', check: () => relationship.friendship >= 10 },
            { threshold: 30, type: 'friendship', name: 'Good Friend', check: () => relationship.friendship >= 30 },
            { threshold: 50, type: 'friendship', name: 'Close Friend', check: () => relationship.friendship >= 50 },
            { threshold: 20, type: 'romance', name: 'First Spark', check: () => relationship.romance >= 20 },
            { threshold: 40, type: 'romance', name: 'Romantic Interest', check: () => relationship.romance >= 40 },
            { threshold: 70, type: 'romance', name: 'Strong Connection', check: () => relationship.romance >= 70 },
            { threshold: 25, type: 'trust', name: 'Trusted Companion', check: () => relationship.trust >= 25 },
            { threshold: 5, type: 'shared', name: 'First Game Together', check: () => relationship.sharedExperiences.some(exp => exp.description.includes('game')) }
        ];

        milestones.forEach(milestone => {
            if (!relationship.milestones.has(milestone.name) && milestone.check()) {
                relationship.milestones.add(milestone.name);
                this.triggerMilestone(avatar1, avatar2, milestone);
            }
        });
    }

    triggerMilestone(avatar1, avatar2, milestone) {
        console.log(`🎉 Milestone reached: ${avatar1.options.name} and ${avatar2.options.name} - ${milestone.name}!`);

        // Show milestone notification
        this.showMilestoneNotification(avatar1, avatar2, milestone);

        // Reward with relationship boost
        const relationship = this.getRelationship(avatar1, avatar2);
        if (relationship) {
            switch(milestone.type) {
                case 'friendship':
                    relationship.friendship += 5;
                    break;
                case 'romance':
                    relationship.romance += 8;
                    break;
                case 'trust':
                    relationship.trust += 6;
                    break;
            }
        }

        // Special effects for major milestones
        if (milestone.threshold >= 50) {
            this.celebrateMilestone(avatar1, avatar2);
        }
    }

    showMilestoneNotification(avatar1, avatar2, milestone) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: linear-gradient(135deg, #ff6b6b, #ff8e8e);
            color: white;
            padding: 20px 30px;
            border-radius: 20px;
            z-index: 1002;
            text-align: center;
            box-shadow: 0 10px 30px rgba(255, 107, 107, 0.4);
            animation: milestonePop 0.5s ease-out;
        `;

        notification.innerHTML = `
            <h3 style="margin: 0 0 10px 0;">🎉 Relationship Milestone! 🎉</h3>
            <p style="margin: 0; font-size: 18px; font-weight: bold;">${milestone.name}</p>
            <p style="margin: 10px 0; opacity: 0.9;">${avatar1.options.name} & ${avatar2.options.name}</p>
        `;

        // Add CSS animation
        const style = document.createElement('style');
        style.textContent = `
            @keyframes milestonePop {
                0% { transform: translate(-50%, -50%) scale(0.8); opacity: 0; }
                70% { transform: translate(-50%, -50%) scale(1.1); }
                100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
            }
        `;
        document.head.appendChild(style);

        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.remove();
            style.remove();
        }, 4000);
    }

    celebrateMilestone(avatar1, avatar2) {
        // Create celebration effects
        const midpoint = new THREE.Vector3()
            .addVectors(avatar1.mesh.position, avatar2.mesh.position)
            .multiplyScalar(0.5);

        // Heart explosion
        this.createHeartExplosion(midpoint);

        // Avatars celebrate
        avatar1.dance();
        avatar2.dance();

        // Special emote
        setTimeout(() => {
            this.scene.emoteSystem.playEmote('heart', avatar1);
            this.scene.emoteSystem.playEmote('heart', avatar2);
        }, 1000);
    }

    createHeartExplosion(position) {
        const heartGroup = new THREE.Group();

        for (let i = 0; i < 12; i++) {
            const heartShape = new THREE.Shape();
            heartShape.moveTo(0, 0);
            heartShape.bezierCurveTo(0.3, 0.3, 0.5, 0, 0, -0.5);
            heartShape.bezierCurveTo(-0.5, 0, -0.3, 0.3, 0, 0);

            const heartGeometry = new THREE.ExtrudeGeometry(heartShape, {
                depth: 0.1,
                bevelEnabled: true,
                bevelSegments: 2,
                bevelSize: 0.02,
                bevelThickness: 0.02
            });

            const heartMaterial = new THREE.MeshBasicMaterial({ 
                color: 0xff6b6b,
                transparent: true,
                opacity: 0.8
            });

            const heart = new THREE.Mesh(heartGeometry, heartMaterial);

            // Random direction
            const angle = (i / 12) * Math.PI * 2;
            const speed = 0.5 + Math.random() * 0.5;

            heart.userData = {
                velocity: new THREE.Vector3(
                    Math.cos(angle) * speed,
                    Math.sin(angle) * speed + 1,
                    Math.sin(angle) * speed
                ),
                rotationSpeed: new THREE.Vector3(
                    (Math.random() - 0.5) * 0.1,
                    (Math.random() - 0.5) * 0.1,
                    (Math.random() - 0.5) * 0.1
                )
            };

            heartGroup.add(heart);
        }

        heartGroup.position.copy(position);
        this.scene.scene.add(heartGroup);

        // Animate hearts
        let progress = 0;
        const animate = () => {
            progress += 0.02;

            heartGroup.children.forEach(heart => {
                heart.position.add(heart.userData.velocity);
                heart.rotation.x += heart.userData.rotationSpeed.x;
                heart.rotation.y += heart.userData.rotationSpeed.y;
                heart.rotation.z += heart.userData.rotationSpeed.z;

                // Gravity
                heart.userData.velocity.y -= 0.05;

                // Fade out
                heart.material.opacity = 0.8 * (1 - progress);
            });

            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                this.scene.scene.remove(heartGroup);
            }
        };

        animate();
    }

    getRelationship(avatar1, avatar2) {
        if (this.relationships.has(avatar1)) {
            return this.relationships.get(avatar1).get(avatar2);
        }
        return null;
    }

    getRelationshipLevel(relationship) {
        if (!relationship) return 'Stranger';

        const romance = relationship.romance;
        const friendship = relationship.friendship;

        if (romance >= 80) return 'Soulmate';
        if (romance >= 60) return 'Romantic Partner';
        if (romance >= 40) return 'Romantic Interest';
        if (friendship >= 70) return 'Best Friend';
        if (friendship >= 50) return 'Close Friend';
        if (friendship >= 30) return 'Friend';
        if (friendship >= 10) return 'Acquaintance';
        return 'Stranger';
    }

    updateRelationshipUI() {
        if (!this.scene.currentUser) return;

        const relationshipList = document.getElementById('relationshipList');
        const userRelationships = this.relationships.get(this.scene.currentUser);

        if (!userRelationships || userRelationships.size === 0) {
            relationshipList.innerHTML = '<p style="color: #666; text-align: center;">No relationships yet.<br>Go mingle! 💕</p>';
            return;
        }

        // Convert to array and sort by relationship strength
        const relationshipsArray = Array.from(userRelationships.entries())
            .map(([avatar, relationship]) => ({
                avatar,
                relationship,
                strength: Math.max(relationship.friendship, relationship.romance)
            }))
            .sort((a, b) => b.strength - a.strength);

        relationshipList.innerHTML = relationshipsArray.map(({ avatar, relationship }) => {
            const level = this.getRelationshipLevel(relationship);
            const compatibility = this.calculateCompatibility(this.scene.currentUser, avatar);

            return `
                <div style="margin-bottom: 15px; padding: 10px; background: #f8f9fa; border-radius: 10px;">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
                        <strong>${avatar.options.name}</strong>
                        <span style="font-size: 12px; color: #ff6b6b;">${level}</span>
                    </div>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px; font-size: 12px;">
                        <div>Friendship: ${Math.round(relationship.friendship)}</div>
                        <div>Romance: ${Math.round(relationship.romance)}</div>
                        <div>Trust: ${Math.round(relationship.trust)}</div>
                        <div>Compatibility: ${Math.round(compatibility * 100)}%</div>
                    </div>
                    <div style="margin-top: 8px; font-size: 11px; color: #666;">
                        ${relationship.interactionCount} interactions
                    </div>
                </div>
            `;
        }).join('');
    }

    getCompatibilityAdvice(avatar1, avatar2) {
        const compatibility = this.calculateCompatibility(avatar1, avatar2);
        const relationship = this.getRelationship(avatar1, avatar2);

        if (!relationship) return "You haven't interacted much yet. Try starting a conversation!";

        if (compatibility >= 0.8) {
            return "Amazing compatibility! You two are a perfect match! 💕";
        } else if (compatibility >= 0.6) {
            return "Great compatibility! You have strong potential for a meaningful connection.";
        } else if (compatibility >= 0.4) {
            return "Good compatibility! With some effort, this could become something special.";
        } else {
            return "Different personalities can create interesting dynamics! Keep an open mind.";
        }
    }
}

Step 3: Performance Optimization - Smooth Love Connections! ⚡

Let's ensure our dating world runs smoothly even with all these amazing features:

// performance.js - Because laggy love is no love at all! 🚀

class PerformanceOptimizer {
    constructor(scene) {
        this.scene = scene;
        this.frameRate = 0;
        this.lastFrameTime = performance.now();
        this.frameCount = 0;
        this.optimizationLevel = 'high'; // high, medium, low

        this.setupPerformanceMonitoring();
        this.applyOptimizations();

        console.log("Performance optimizer activated! Keeping the love smooth! ⚡");
    }

    setupPerformanceMonitoring() {
        // Frame rate counter
        this.fpsDisplay = document.createElement('div');
        this.fpsDisplay.style.cssText = `
            position: fixed;
            bottom: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.7);
            color: #00ff00;
            padding: 5px 10px;
            border-radius: 10px;
            font-family: monospace;
            font-size: 12px;
            z-index: 1000;
        `;
        document.getElementById('container').appendChild(this.fpsDisplay);

        // Performance stats
        this.statsDisplay = document.createElement('div');
        this.statsDisplay.style.cssText = `
            position: fixed;
            bottom: 40px;
            right: 10px;
            background: rgba(0, 0, 0, 0.7);
            color: #00ff00;
            padding: 5px 10px;
            border-radius: 10px;
            font-family: monospace;
            font-size: 12px;
            z-index: 1000;
            display: none;
        `;
        document.getElementById('container').appendChild(this.statsDisplay);

        // Toggle stats with F11
        document.addEventListener('keydown', (event) => {
            if (event.code === 'F11') {
                this.statsDisplay.style.display = 
                    this.statsDisplay.style.display === 'none' ? 'block' : 'none';
            }
        });
    }

    applyOptimizations() {
        this.applyRendererOptimizations();
        this.applySceneOptimizations();
        this.applyMaterialOptimizations();
        this.setupLODSystem();
        this.setupCullingSystem();
    }

    applyRendererOptimizations() {
        const renderer = this.scene.renderer;

        // Enable antialiasing only on high-end devices
        if (this.isHighEndDevice()) {
            renderer.antialias = true;
        } else {
            renderer.antialias = false;
        }

        // Set pixel ratio appropriately
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

        // Enable power preference
        renderer.context.getExtension('WEBGL_lose_context');
        const powerPreference = this.isMobileDevice() ? 'low-power' : 'high-performance';

        // Use more efficient shadow maps
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        renderer.shadowMap.autoUpdate = false;
        renderer.shadowMap.needsUpdate = true;
    }

    applySceneOptimizations() {
        // Combine geometries where possible
        this.combineStaticGeometries();

        // Use instancing for repeated objects
        this.setupInstancedObjects();

        // Optimize lighting
        this.optimizeLighting();
    }

    applyMaterialOptimizations() {
        // Use simpler materials for distant objects
        this.scene.scene.traverse((child) => {
            if (child.isMesh) {
                this.optimizeMaterial(child.material);
            }
        });
    }

    optimizeMaterial(material) {
        if (material instanceof THREE.MeshStandardMaterial) {
            // Reduce roughness map resolution
            if (material.roughnessMap) {
                material.roughnessMap.generateMipmaps = true;
                material.roughnessMap.minFilter = THREE.LinearMipmapLinearFilter;
            }

            // Reduce normal map resolution
            if (material.normalMap) {
                material.normalMap.generateMipmaps = true;
                material.normalMap.minFilter = THREE.LinearMipmapLinearFilter;
            }
        }
    }

    setupLODSystem() {
        // Level of Detail system for distant objects
        this.LODs = new Map();

        this.scene.scene.traverse((child) => {
            if (child.isMesh && child.geometry) {
                this.createLODForObject(child);
            }
        });
    }

    createLODForObject(mesh) {
        // Only create LOD for complex objects
        if (mesh.geometry.attributes.position.count < 1000) return;

        const lod = new THREE.LOD();

        // High detail (original mesh)
        lod.addLevel(mesh.clone(), 0);

        // Medium detail (simplified geometry)
        const mediumGeometry = this.simplifyGeometry(mesh.geometry, 0.5);
        const mediumMesh = new THREE.Mesh(mediumGeometry, mesh.material);
        lod.addLevel(mediumMesh, 25);

        // Low detail (very simplified)
        const lowGeometry = this.simplifyGeometry(mesh.geometry, 0.2);
        const lowMesh = new THREE.Mesh(lowGeometry, mesh.material);
        lod.addLevel(lowMesh, 50);

        // Replace original mesh with LOD
        if (mesh.parent) {
            mesh.parent.add(lod);
            mesh.parent.remove(mesh);
        }

        this.LODs.set(mesh.uuid, lod);
    }

    simplifyGeometry(geometry, ratio) {
        // Simple geometry simplification (in production, use proper decimation)
        const simplified = geometry.clone();

        // Reduce vertex count by removing every other vertex
        if (simplified.index) {
            const newIndices = [];
            for (let i = 0; i < simplified.index.count; i += 2) {
                if (i < simplified.index.count * ratio) {
                    newIndices.push(simplified.index.array[i]);
                }
            }
            simplified.setIndex(newIndices);
        }

        return simplified;
    }

    setupCullingSystem() {
        // Frustum culling for off-screen objects
        this.frustum = new THREE.Frustum();
        this.cameraMatrix = new THREE.Matrix4();

        // Occlusion culling for hidden objects
        this.setupOcclusionCulling();
    }

    setupOcclusionCulling() {
        // Simple distance-based culling
        this.visibilityDistance = 100;

        // Periodically update object visibility
        setInterval(() => {
            this.updateObjectVisibility();
        }, 1000);
    }

    updateObjectVisibility() {
        if (!this.scene.camera) return;

        const cameraPosition = this.scene.camera.position;

        this.scene.scene.traverse((child) => {
            if (child.isMesh && child.userData.optimize !== false) {
                const distance = child.getWorldPosition(new THREE.Vector3())
                    .distanceTo(cameraPosition);

                child.visible = distance < this.visibilityDistance;
            }
        });
    }

    combineStaticGeometries() {
        // Combine trees, flowers, and other static objects
        const staticObjects = {
            trees: [],
            flowers: [],
            benches: []
        };

        this.scene.scene.traverse((child) => {
            if (child.name && child.name.includes('tree')) {
                staticObjects.trees.push(child);
            } else if (child.name && child.name.includes('flower')) {
                staticObjects.flowers.push(child);
            } else if (child.name && child.name.includes('bench')) {
                staticObjects.benches.push(child);
            }
        });

        // Combine each category
        Object.keys(staticObjects).forEach(category => {
            if (staticObjects[category].length > 1) {
                this.combineMeshes(staticObjects[category], category);
            }
        });
    }

    combineMeshes(meshes, name) {
        const combinedGeometry = new THREE.BufferGeometry();
        const combinedMaterial = meshes[0].material;

        // Implementation would combine all geometries into one
        // This is simplified - in production, use BufferGeometryUtils.mergeBufferGeometries

        console.log(`Combined ${meshes.length} ${name} for better performance`);

        // Remove original meshes and add combined mesh
        meshes.forEach(mesh => {
            if (mesh.parent) {
                mesh.parent.remove(mesh);
            }
        });
    }

    setupInstancedObjects() {
        // Use instanced meshes for identical objects
        this.instancedMeshes = new Map();

        // Find duplicate objects and replace with instanced versions
        this.createInstancedFlowers();
        this.createInstancedTrees();
    }

    createInstancedFlowers() {
        const flowers = [];

        this.scene.scene.traverse((child) => {
            if (child.name && child.name.includes('flower') && child.isMesh) {
                flowers.push(child);
            }
        });

        if (flowers.length < 2) return;

        // Create instanced mesh (simplified example)
        const firstFlower = flowers[0];
        const instanceCount = flowers.length;

        // In production, you'd use THREE.InstancedMesh
        console.log(`Could instance ${instanceCount} flowers for better performance`);
    }

    optimizeLighting() {
        // Reduce shadow quality on lower-end devices
        const directionalLights = this.scene.scene.children.filter(
            child => child instanceof THREE.DirectionalLight
        );

        directionalLights.forEach(light => {
            if (this.isMobileDevice()) {
                light.shadow.mapSize.width = 1024;
                light.shadow.mapSize.height = 1024;
            } else {
                light.shadow.mapSize.width = 2048;
                light.shadow.mapSize.height = 2048;
            }
        });

        // Reduce number of active lights
        this.deactivateDistantLights();
    }

    deactivateDistantLights() {
        const pointLights = this.scene.scene.children.filter(
            child => child instanceof THREE.PointLight
        );

        pointLights.forEach(light => {
            light.userData.originalIntensity = light.intensity;
        });
    }

    updatePerformanceStats() {
        this.frameCount++;
        const currentTime = performance.now();

        if (currentTime >= this.lastFrameTime + 1000) {
            this.frameRate = Math.round((this.frameCount * 1000) / (currentTime - this.lastFrameTime));
            this.frameCount = 0;
            this.lastFrameTime = currentTime;

            this.updatePerformanceDisplays();
        }

        // Dynamic optimization based on frame rate
        this.dynamicOptimization();
    }

    updatePerformanceDisplays() {
        this.fpsDisplay.textContent = `FPS: ${this.frameRate}`;

        if (this.statsDisplay.style.display !== 'none') {
            const memory = performance.memory;
            const stats = [
                `FPS: ${this.frameRate}`,
                `Objects: ${this.countSceneObjects()}`,
                `Optimization: ${this.optimizationLevel}`,
                memory ? `Memory: ${Math.round(memory.usedJSHeapSize / 1048576)}MB` : ''
            ];

            this.statsDisplay.innerHTML = stats.join('<br>');
        }
    }

    countSceneObjects() {
        let count = 0;
        this.scene.scene.traverse(() => count++);
        return count;
    }

    dynamicOptimization() {
        // Adjust optimization level based on frame rate
        if (this.frameRate < 30) {
            this.increaseOptimization();
        } else if (this.frameRate > 50 && this.optimizationLevel !== 'high') {
            this.decreaseOptimization();
        }
    }

    increaseOptimization() {
        if (this.optimizationLevel === 'high') {
            this.optimizationLevel = 'medium';
            this.applyMediumOptimizations();
        } else if (this.optimizationLevel === 'medium') {
            this.optimizationLevel = 'low';
            this.applyLowOptimizations();
        }
    }

    decreaseOptimization() {
        if (this.optimizationLevel === 'low') {
            this.optimizationLevel = 'medium';
            this.applyMediumOptimizations();
        } else if (this.optimizationLevel === 'medium') {
            this.optimizationLevel = 'high';
            this.applyHighOptimizations();
        }
    }

    applyHighOptimizations() {
        this.visibilityDistance = 100;
        this.scene.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        console.log("Applied high-quality optimizations");
    }

    applyMediumOptimizations() {
        this.visibilityDistance = 70;
        this.scene.renderer.setPixelRatio(1);
        this.reduceParticleCounts();
        console.log("Applied medium-quality optimizations");
    }

    applyLowOptimizations() {
        this.visibilityDistance = 50;
        this.scene.renderer.setPixelRatio(1);
        this.reduceParticleCounts();
        this.disableShadows();
        console.log("Applied low-quality optimizations");
    }

    reduceParticleCounts() {
        // Reduce weather particle counts
        if (this.scene.weatherSystem) {
            this.scene.weatherSystem.rain.children[0].geometry.setDrawRange(0, 500);
            this.scene.weatherSystem.snow.children[0].geometry.setDrawRange(0, 400);
        }
    }

    disableShadows() {
        this.scene.scene.traverse((child) => {
            if (child.isMesh) {
                child.castShadow = false;
                child.receiveShadow = false;
            }
        });

        const lights = this.scene.scene.children.filter(
            child => child instanceof THREE.Light
        );

        lights.forEach(light => {
            light.castShadow = false;
        });
    }

    isHighEndDevice() {
        const canvas = document.createElement('canvas');
        const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

        if (!gl) return false;

        const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
        if (debugInfo) {
            const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
            return renderer.includes('NVIDIA') || renderer.includes('AMD') || renderer.includes('RTX');
        }

        return false;
    }

    isMobileDevice() {
        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
    }

    cleanup() {
        // Clean up resources when scene is destroyed
        if (this.fpsDisplay.parentNode) {
            this.fpsDisplay.parentNode.removeChild(this.fpsDisplay);
        }
        if (this.statsDisplay.parentNode) {
            this.statsDisplay.parentNode.removeChild(this.statsDisplay);
        }
    }
}

Step 4: Updated DatingScene Class - The Complete Experience! 🌟

Let's integrate all these new systems into our main class:

// Updated DatingScene class for Part 6

class DatingScene {
    constructor() {
        // ... existing properties ...

        // New properties for Part 6
        this.miniGameSystem = null;
        this.relationshipSystem = null;
        this.performanceOptimizer = null;

        this.init();
    }

    init() {
        this.createScene();
        this.createCamera();
        this.createRenderer();
        this.createLights();
        this.createEnvironment();

        // Initialize core systems
        this.proximityChat = new ProximityChat(this);
        this.voiceChat = new VoiceChatSystem(this);
        this.emoteSystem = new EmoteSystem(this);
        this.interactiveEnv = new InteractiveEnvironment(this);
        this.dayNightCycle = new DayNightCycle(this);
        this.weatherSystem = new WeatherSystem(this, this.dayNightCycle);

        // Initialize new Part 6 systems
        this.miniGameSystem = new MiniGameSystem(this);
        this.relationshipSystem = new RelationshipSystem(this);
        this.performanceOptimizer = new PerformanceOptimizer(this);

        this.setupKeyboardListeners();
        this.animate();

        setTimeout(() => {
            document.getElementById('loadingScreen').style.display = 'none';
            document.getElementById('chatUI').style.display = 'block';
            this.addSampleAvatars();
        }, 2000);
    }

    animate() {
        const currentTime = performance.now();
        const deltaTime = (currentTime - this.lastTime) / 1000;
        this.lastTime = currentTime;

        requestAnimationFrame(() => this.animate());

        // Update performance monitoring
        this.performanceOptimizer.updatePerformanceStats();

        // Update all systems
        if (this.datingCamera) this.datingCamera.update();
        if (this.proximityChat) this.proximityChat.update();
        if (this.interactiveEnv) this.interactiveEnv.update();
        if (this.dayNightCycle) this.dayNightCycle.update(deltaTime);
        if (this.weatherSystem) this.weatherSystem.update(deltaTime);

        // Update NPC AI and relationships
        this.avatars.forEach(avatar => {
            if (avatar.ai) {
                avatar.ai.update(deltaTime);
            }
        });

        // Update mini-games
        this.miniGameSystem.activeGames.forEach(game => {
            if (game.update) game.update(deltaTime);
        });

        this.renderer.render(this.scene, this.camera);
    }

    // Enhanced interaction handling
    handleAvatarClick(avatar) {
        if (avatar === this.currentUser) return;

        console.log(`You clicked on ${avatar.options.name}!`);

        // Update relationship
        this.relationshipSystem.updateRelationship(this.currentUser, avatar, {
            type: 'interaction',
            description: 'Clicked on avatar',
            impact: 0.1
        });

        // Show enhanced avatar info with relationship data
        this.showEnhancedAvatarInfo(avatar);
    }

    showEnhancedAvatarInfo(avatar) {
        const relationship = this.relationshipSystem.getRelationship(this.currentUser, avatar);
        const compatibility = this.relationshipSystem.calculateCompatibility(this.currentUser, avatar);
        const advice = this.relationshipSystem.getCompatibilityAdvice(this.currentUser, avatar);

        let infoDiv = document.getElementById('avatarInfo');
        if (!infoDiv) {
            infoDiv = document.createElement('div');
            infoDiv.id = 'avatarInfo';
            infoDiv.style.cssText = `
                position: absolute;
                top: 20px;
                right: 20px;
                background: rgba(255, 255, 255, 0.95);
                padding: 20px;
                border-radius: 15px;
                max-width: 300px;
                z-index: 100;
                box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
                backdrop-filter: blur(10px);
            `;
            document.getElementById('container').appendChild(infoDiv);
        }

        infoDiv.innerHTML = `
            <h3 style="margin: 0 0 10px 0; color: #ff6b6b;">${avatar.options.name}</h3>
            <div style="margin-bottom: 10px;">
                <strong>Compatibility:</strong> ${Math.round(compatibility * 100)}%<br>
                <strong>Relationship:</strong> ${relationship ? this.relationshipSystem.getRelationshipLevel(relationship) : 'Stranger'}
            </div>
            <div style="background: #f8f9fa; padding: 10px; border-radius: 8px; margin-bottom: 15px; font-size: 12px;">
                ${advice}
            </div>
            <div style="display: grid; gap: 8px;">
                <button onclick="datingScene.startChatWith('${avatar.options.name}')" 
                        style="background: #4ecdc4; color: white; border: none; padding: 8px; border-radius: 5px; cursor: pointer;">
                    Start Chat! 💬
                </button>
                <button onclick="datingScene.inviteToGame('${avatar.options.name}')" 
                        style="background: #ffd166; color: white; border: none; padding: 8px; border-radius: 5px; cursor: pointer;">
                    Play Game! 🎮
                </button>
                <button onclick="datingScene.sendCompliment('${avatar.options.name}')" 
                        style="background: #ff6b6b; color: white; border: none; padding: 8px; border-radius: 5px; cursor: pointer;">
                    Send Compliment! 💝
                </button>
            </div>
        `;

        setTimeout(() => {
            if (infoDiv.parentNode) {
                infoDiv.parentNode.removeChild(infoDiv);
            }
        }, 15000);
    }

    inviteToGame(avatarName) {
        const avatar = this.avatars.find(a => a.options.name === avatarName);
        if (avatar) {
            this.miniGameSystem.showGameOptions('love_quiz');
        }
    }

    sendCompliment(avatarName) {
        const avatar = this.avatars.find(a => a.options.name === avatarName);
        if (avatar) {
            // Update relationship
            this.relationshipSystem.updateRelationship(this.currentUser, avatar, {
                type: 'compliment',
                description: 'Sent a compliment',
                impact: 0.2
            });

            // Play compliment animation
            this.emoteSystem.playEmote('heart', this.currentUser);

            // Show in chat
            const chatMessages = document.getElementById('chatMessages');
            const messageElement = document.createElement('div');
            messageElement.style.cssText = `
                margin: 5px 0;
                padding: 8px;
                background: #fff3e0;
                border-radius: 15px;
                text-align: center;
                font-style: italic;
            `;
            messageElement.textContent = `You sent a compliment to ${avatarName}! 💝`;
            chatMessages.appendChild(messageElement);
            chatMessages.scrollTop = chatMessages.scrollHeight;

            // Avatar reacts
            if (avatar.ai) {
                avatar.blush();
                setTimeout(() => {
                    this.emoteSystem.playEmote('shy', avatar);
                }, 1000);
            }
        }
    }

    // Enhanced NPC creation with relationship awareness
    addSampleAvatars() {
        console.log("Adding NPCs with relationship awareness! 💞");

        const sampleAvatars = [
            {
                name: "Elena",
                gender: "female",
                skinTone: 0xF0D9B5,
                hairColor: 0x2C1810,
                clothingColor: 0x2E8B57,
                personality: 'romantic'
            },
            {
                name: "Marcus",
                gender: "male", 
                skinTone: 0xE8B298,
                hairColor: 0x8B4513,
                clothingColor: 0x9370DB,
                personality: 'intellectual'
            },
            {
                name: "Chloe",
                gender: "female",
                skinTone: 0xFFDBAC,
                hairColor: 0xFFD700,
                clothingColor: 0xFF69B4,
                personality: 'adventurous'
            },
            {
                name: "David",
                gender: "male",
                skinTone: 0xD2B48C,
                hairColor: 0x000000,
                clothingColor: 0x4169E1,
                personality: 'social'
            }
        ];

        sampleAvatars.forEach((avatarConfig, index) => {
            const avatar = new Avatar(avatarConfig);

            const angle = (index / sampleAvatars.length) * Math.PI * 2;
            const radius = 8;

            avatar.mesh.position.set(
                Math.sin(angle) * radius,
                0,
                Math.cos(angle) * radius
            );

            this.scene.add(avatar.mesh);
            this.avatars.push(avatar);

            // Add AI with relationship awareness
            avatar.ai = new NPCAI(avatar, this);

            this.addRandomAvatarMovement(avatar);
        });

        setTimeout(() => {
            this.showAvatarCustomization();
        }, 1000);
    }
}

// Global variables
let datingScene;
let datingCamera;
let emoteSystem;
let dayNightCycle;
let weatherSystem;
let miniGameSystem;
let relationshipSystem;

window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    emoteSystem = datingScene.emoteSystem;
    dayNightCycle = datingScene.dayNightCycle;
    weatherSystem = datingScene.weatherSystem;
    miniGameSystem = datingScene.miniGameSystem;
    relationshipSystem = datingScene.relationshipSystem;

    console.log("Complete 3D Dating Experience loaded! Games, relationships, and performance optimized! 🎉");

    setupAvatarInteraction();

    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built in Part 6: 🎉

  1. Interactive Mini-Games:

    • Love Quiz: Personality-based questions
    • Dance Off: Rhythm and movement game
    • Memory Match: Pattern recognition (foundation)
    • Truth or Dare: Social interaction game (foundation)
    • Game invitation system with UI
  2. Advanced Relationship System:

    • Multi-dimensional relationships (friendship, romance, trust, familiarity)
    • Milestone tracking and celebrations
    • Compatibility calculations
    • Relationship advice and progression
    • Visual relationship status panel
  3. Performance Optimization:

    • Frame rate monitoring and dynamic optimization
    • Level of Detail (LOD) system
    • Geometry combining and instancing
    • Frustum culling and visibility management
    • Adaptive quality settings
  4. Enhanced Social Features:

    • Relationship-aware interactions
    • Compliment system
    • Milestone celebrations with special effects
    • Compatibility-based advice

Key Features Explained: 🔑

Next Time in Part 7: 🚀

We'll add:

Current Project Status: Our 3D dating world is now a complete social experience! With interactive games, deep relationship systems, and smooth performance, we've created a virtual space where meaningful connections can truly flourish. Love is in the code! 💕


Fun Fact: Our relationship system now tracks more dimensions than most real-world dating apps! We've got friendship, romance, trust, familiarity, AND compatibility - basically we're more thorough than your therapist! 😄

Part 7: Mobile Optimization, Social Features & Customization - Love Goes Everywhere! 📱✨

Welcome back, digital cupid! Our dating world is amazing on desktop, but love shouldn't be confined to a computer screen. Let's make it mobile-friendly, add social features, and give users endless customization options!

Step 1: Mobile Optimization - Love in Your Pocket! 📱

Let's create a responsive design that works beautifully on all devices:

// mobile-optimization.js - Because swiping right should work everywhere! 💕

class MobileOptimizer {
    constructor(scene) {
        this.scene = scene;
        this.isMobile = this.detectMobile();
        this.touchControls = null;
        this.gyroControls = null;
        this.interfaceScaler = null;

        this.setupMobileEnvironment();
        console.log(`Mobile optimizer activated! ${this.isMobile ? '📱 Mobile mode' : '💻 Desktop mode'}`);
    }

    detectMobile() {
        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
               window.innerWidth <= 768;
    }

    setupMobileEnvironment() {
        this.setupViewport();
        this.setupTouchControls();
        this.setupGyroControls();
        this.setupInterfaceScaler();
        this.optimizePerformanceForMobile();
        this.setupMobileUI();
    }

    setupViewport() {
        // Set viewport for mobile devices
        const viewport = document.querySelector('meta[name="viewport"]');
        if (viewport) {
            viewport.setAttribute('content', 
                'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');
        }

        // Prevent elastic scrolling on iOS
        document.addEventListener('touchmove', (e) => {
            if (e.scale !== 1) {
                e.preventDefault();
            }
        }, { passive: false });
    }

    setupTouchControls() {
        if (!this.isMobile) return;

        this.touchControls = {
            currentTouches: new Map(),
            doubleTapTimer: null,
            lastTapTime: 0
        };

        this.setupGestureRecognizers();
        this.setupVirtualGamepad();
        this.setupTouchCameraControls();
    }

    setupGestureRecognizers() {
        const canvas = this.scene.renderer.domElement;

        // Single tap - select/click
        canvas.addEventListener('touchstart', (e) => this.handleTouchStart(e));
        canvas.addEventListener('touchend', (e) => this.handleTouchEnd(e));
        canvas.addEventListener('touchmove', (e) => this.handleTouchMove(e));

        // Double tap - special actions
        canvas.addEventListener('touchend', (e) => this.handleDoubleTap(e));

        // Pinch zoom - camera distance
        canvas.addEventListener('touchmove', (e) => this.handlePinchZoom(e));

        // Two-finger swipe - camera rotation
        canvas.addEventListener('touchmove', (e) => this.handleTwoFingerSwipe(e));
    }

    handleTouchStart(event) {
        event.preventDefault();
        const touches = Array.from(event.touches);

        touches.forEach(touch => {
            this.touchControls.currentTouches.set(touch.identifier, {
                x: touch.clientX,
                y: touch.clientY,
                startTime: Date.now()
            });
        });

        // Single touch - start movement or selection
        if (touches.length === 1) {
            this.handleSingleTouchStart(touches[0]);
        }
    }

    handleSingleTouchStart(touch) {
        const rect = this.scene.renderer.domElement.getBoundingClientRect();
        const x = ((touch.clientX - rect.left) / rect.width) * 2 - 1;
        const y = -((touch.clientY - rect.top) / rect.height) * 2 + 1;

        // Raycast for object selection
        const raycaster = new THREE.Raycaster();
        raycaster.setFromCamera(new THREE.Vector2(x, y), this.scene.camera);

        // Check for avatar clicks
        const avatars = this.scene.avatars.map(avatar => avatar.mesh);
        const intersects = raycaster.intersectObjects(avatars, true);

        if (intersects.length > 0) {
            const clickedObject = intersects[0].object;
            const avatar = this.scene.avatars.find(a => 
                a.mesh === clickedObject.parent || a.mesh.children.includes(clickedObject)
            );
            if (avatar) {
                this.scene.handleAvatarClick(avatar);
            }
        } else {
            // Move to tapped position (if not on UI)
            if (!this.isTouchOnUI(touch.clientX, touch.clientY)) {
                this.handleTapToMove(touch.clientX, touch.clientY);
            }
        }
    }

    handleTapToMove(clientX, clientY) {
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();
        const rect = this.scene.renderer.domElement.getBoundingClientRect();

        mouse.x = ((clientX - rect.left) / rect.width) * 2 - 1;
        mouse.y = -((clientY - rect.top) / rect.height) * 2 + 1;

        raycaster.setFromCamera(mouse, this.scene.camera);

        // Intersect with ground plane
        const ground = this.scene.scene.children.find(child => 
            child.geometry && child.geometry.type === 'PlaneGeometry'
        );

        if (ground) {
            const intersects = raycaster.intersectObject(ground);
            if (intersects.length > 0) {
                const targetPos = intersects[0].point;
                if (this.scene.currentUser && this.scene.currentUser.movement) {
                    this.scene.currentUser.movement.moveTo(targetPos);
                }
            }
        }
    }

    handleDoubleTap(event) {
        if (event.touches.length !== 0) return;

        const currentTime = Date.now();
        const tapLength = currentTime - this.touchControls.lastTapTime;

        if (tapLength < 500 && tapLength > 0) {
            // Double tap detected - perform special action
            event.preventDefault();
            this.performDoubleTapAction(event);
        }

        this.touchControls.lastTapTime = currentTime;
    }

    performDoubleTapAction(event) {
        if (this.scene.currentUser) {
            // Double tap makes avatar dance
            this.scene.currentUser.dance();

            // Show feedback
            this.showMobileFeedback('💃 Dancing!', event.changedTouches[0].clientX, event.changedTouches[0].clientY);
        }
    }

    handlePinchZoom(event) {
        if (event.touches.length !== 2) return;

        const touch1 = event.touches[0];
        const touch2 = event.touches[1];

        const currentDistance = Math.hypot(
            touch1.clientX - touch2.clientX,
            touch1.clientY - touch2.clientY
        );

        if (this.touchControls.lastPinchDistance) {
            const delta = currentDistance - this.touchControls.lastPinchDistance;
            this.adjustCameraZoom(delta * 0.01);
        }

        this.touchControls.lastPinchDistance = currentDistance;
    }

    adjustCameraZoom(delta) {
        if (this.scene.datingCamera && this.scene.datingCamera.currentMode === this.scene.datingCamera.modes.ORBIT) {
            this.scene.datingCamera.orbitDistance = THREE.MathUtils.clamp(
                this.scene.datingCamera.orbitDistance - delta,
                3, 20
            );
            this.scene.datingCamera.updateOrbitCamera();
        }
    }

    handleTwoFingerSwipe(event) {
        if (event.touches.length !== 2) return;

        const touch1 = event.touches[0];
        const touch2 = event.touches[1];

        const currentMidpoint = {
            x: (touch1.clientX + touch2.clientX) / 2,
            y: (touch1.clientY + touch2.clientY) / 2
        };

        if (this.touchControls.lastTwoFingerMidpoint) {
            const deltaX = currentMidpoint.x - this.touchControls.lastTwoFingerMidpoint.x;
            const deltaY = currentMidpoint.y - this.touchControls.lastTwoFingerMidpoint.y;

            this.rotateCamera(deltaX * 0.01, deltaY * 0.01);
        }

        this.touchControls.lastTwoFingerMidpoint = currentMidpoint;
    }

    rotateCamera(deltaX, deltaY) {
        if (this.scene.datingCamera && this.scene.datingCamera.currentMode === this.scene.datingCamera.modes.ORBIT) {
            this.scene.datingCamera.orbitAngle += deltaX;
            this.scene.datingCamera.orbitHeight = THREE.MathUtils.clamp(
                this.scene.datingCamera.orbitHeight - deltaY,
                1, 10
            );
            this.scene.datingCamera.updateOrbitCamera();
        }
    }

    setupVirtualGamepad() {
        if (!this.isMobile) return;

        this.virtualGamepad = document.createElement('div');
        this.virtualGamepad.style.cssText = `
            position: fixed;
            bottom: 120px;
            left: 30px;
            width: 150px;
            height: 150px;
            z-index: 1000;
            touch-action: none;
        `;

        this.virtualGamepad.innerHTML = `
            <div style="position: relative; width: 100%; height: 100%;">
                <div id="joystickBase" style="
                    position: absolute;
                    width: 100%;
                    height: 100%;
                    background: rgba(255, 255, 255, 0.2);
                    border: 2px solid rgba(255, 255, 255, 0.5);
                    border-radius: 50%;
                    backdrop-filter: blur(10px);
                "></div>
                <div id="joystickKnob" style="
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    width: 50px;
                    height: 50px;
                    background: rgba(255, 107, 107, 0.8);
                    border-radius: 50%;
                    transform: translate(-50%, -50%);
                    transition: transform 0.1s;
                "></div>
            </div>
        `;

        document.getElementById('container').appendChild(this.virtualGamepad);
        this.setupJoystickEvents();
    }

    setupJoystickEvents() {
        const knob = document.getElementById('joystickKnob');
        const base = document.getElementById('joystickBase');
        let isTouching = false;

        base.addEventListener('touchstart', (e) => {
            e.preventDefault();
            isTouching = true;
        });

        document.addEventListener('touchmove', (e) => {
            if (!isTouching) return;
            e.preventDefault();

            const touch = e.touches[0];
            const rect = base.getBoundingClientRect();
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;

            const deltaX = touch.clientX - centerX;
            const deltaY = touch.clientY - centerY;

            // Calculate distance from center (clamped to circle)
            const distance = Math.min(Math.sqrt(deltaX * deltaX + deltaY * deltaY), rect.width / 2);
            const angle = Math.atan2(deltaY, deltaX);

            const knobX = Math.cos(angle) * distance;
            const knobY = Math.sin(angle) * distance;

            knob.style.transform = `translate(calc(-50% + ${knobX}px), calc(-50% + ${knobY}px))`;

            // Convert to movement input
            const normalizedX = knobX / (rect.width / 2);
            const normalizedY = knobY / (rect.height / 2);

            this.handleJoystickInput(normalizedX, normalizedY);
        });

        document.addEventListener('touchend', (e) => {
            isTouching = false;
            knob.style.transform = 'translate(-50%, -50%)';
            this.handleJoystickInput(0, 0);
        });
    }

    handleJoystickInput(x, y) {
        if (!this.scene.currentUser || !this.scene.currentUser.movement) return;

        const movementState = this.scene.currentUser.movement.movementState;

        // Convert joystick input to movement directions
        movementState.forward = y < -0.3;
        movementState.backward = y > 0.3;
        movementState.left = x < -0.3;
        movementState.right = x > 0.3;

        this.scene.currentUser.movement.updateMovement();
    }

    setupTouchCameraControls() {
        // Add camera control buttons for mobile
        this.cameraControls = document.createElement('div');
        this.cameraControls.style.cssText = `
            position: fixed;
            bottom: 120px;
            right: 30px;
            z-index: 1000;
            display: flex;
            flex-direction: column;
            gap: 10px;
        `;

        this.cameraControls.innerHTML = `
            <button id="cameraFollow" style="
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: none;
                background: rgba(255, 107, 107, 0.8);
                color: white;
                font-size: 20px;
                backdrop-filter: blur(10px);
            ">👥</button>
            <button id="cameraOrbit" style="
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: none;
                background: rgba(76, 201, 240, 0.8);
                color: white;
                font-size: 20px;
                backdrop-filter: blur(10px);
            ">🛸</button>
            <button id="cameraFirstPerson" style="
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: none;
                background: rgba(255, 214, 102, 0.8);
                color: white;
                font-size: 20px;
                backdrop-filter: blur(10px);
            ">👀</button>
        `;

        document.getElementById('container').appendChild(this.cameraControls);

        // Add event listeners
        document.getElementById('cameraFollow').addEventListener('touchstart', () => {
            this.scene.datingCamera.switchMode('follow');
        });
        document.getElementById('cameraOrbit').addEventListener('touchstart', () => {
            this.scene.datingCamera.switchMode('orbit');
        });
        document.getElementById('cameraFirstPerson').addEventListener('touchstart', () => {
            this.scene.datingCamera.switchMode('first_person');
        });
    }

    setupGyroControls() {
        if (!this.isMobile || !window.DeviceOrientationEvent) return;

        this.gyroControls = {
            enabled: false,
            alpha: 0,
            beta: 0,
            gamma: 0
        };

        this.setupGyroPermission();
    }

    setupGyroPermission() {
        // Request device orientation permission
        if (typeof DeviceOrientationEvent.requestPermission === 'function') {
            const gyroButton = document.createElement('button');
            gyroButton.textContent = '🎯 Enable Gyro';
            gyroButton.style.cssText = `
                position: fixed;
                top: 100px;
                right: 20px;
                z-index: 1000;
                padding: 10px;
                background: #4ecdc4;
                color: white;
                border: none;
                border-radius: 20px;
                backdrop-filter: blur(10px);
            `;

            gyroButton.addEventListener('click', async () => {
                try {
                    const permission = await DeviceOrientationEvent.requestPermission();
                    if (permission === 'granted') {
                        this.enableGyroControls();
                        gyroButton.remove();
                    }
                } catch (error) {
                    console.warn('Gyroscope permission denied:', error);
                }
            });

            document.getElementById('container').appendChild(gyroButton);
        } else {
            // Auto-enable for Android and other devices
            this.enableGyroControls();
        }
    }

    enableGyroControls() {
        window.addEventListener('deviceorientation', (event) => {
            this.gyroControls.alpha = event.alpha; // 0-360
            this.gyroControls.beta = event.beta;   // -180 to 180
            this.gyroControls.gamma = event.gamma; // -90 to 90

            this.handleGyroInput();
        });

        this.gyroControls.enabled = true;
        console.log("Gyro controls enabled! 📱");
    }

    handleGyroInput() {
        if (!this.scene.currentUser || !this.gyroControls.enabled) return;

        // Use gamma (left/right tilt) for movement
        const tilt = this.gyroControls.gamma / 90; // Normalize to -1 to 1

        if (Math.abs(tilt) > 0.3) {
            const movementState = this.scene.currentUser.movement.movementState;
            movementState.left = tilt < -0.3;
            movementState.right = tilt > 0.3;
            this.scene.currentUser.movement.updateMovement();
        }

        // Use beta (front/back tilt) for camera adjustment in first-person mode
        if (this.scene.datingCamera.currentMode === 'first_person') {
            const lookTilt = this.gyroControls.beta / 180; // Normalize
            this.scene.camera.rotation.x = lookTilt * 0.5;
        }
    }

    setupInterfaceScaler() {
        this.interfaceScaler = {
            scale: 1,
            update: () => {
                const width = window.innerWidth;
                const height = window.innerHeight;
                const isLandscape = width > height;

                // Adjust scale based on screen size and orientation
                this.interfaceScaler.scale = Math.min(1, width / 400);

                // Apply scaling to UI elements
                this.scaleMobileUI();
            }
        };

        window.addEventListener('resize', () => this.interfaceScaler.update());
        window.addEventListener('orientationchange', () => {
            setTimeout(() => this.interfaceScaler.update(), 100);
        });

        this.interfaceScaler.update();
    }

    scaleMobileUI() {
        const scale = this.interfaceScaler.scale;
        const uiElements = document.querySelectorAll('.mobile-ui');

        uiElements.forEach(element => {
            element.style.transform = `scale(${scale})`;
        });
    }

    setupMobileUI() {
        if (!this.isMobile) return;

        this.createMobileActionBar();
        this.createMobileQuickMenu();
        this.adaptExistingUIForMobile();
    }

    createMobileActionBar() {
        this.actionBar = document.createElement('div');
        this.actionBar.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 15px;
            z-index: 1000;
            padding: 10px 20px;
            background: rgba(255, 255, 255, 0.9);
            border-radius: 25px;
            backdrop-filter: blur(10px);
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
        `;
        this.actionBar.className = 'mobile-ui';

        this.actionBar.innerHTML = `
            <button id="mobileChat" style="
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: none;
                background: #4ecdc4;
                color: white;
                font-size: 20px;
            ">💬</button>
            <button id="mobileEmotes" style="
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: none;
                background: #ffd166;
                color: white;
                font-size: 20px;
            ">😊</button>
            <button id="mobileGames" style="
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: none;
                background: #ff6b6b;
                color: white;
                font-size: 20px;
            ">🎮</button>
            <button id="mobileMenu" style="
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: none;
                background: #9370db;
                color: white;
                font-size: 20px;
            ">⚙️</button>
        `;

        document.getElementById('container').appendChild(this.actionBar);

        // Add event listeners
        document.getElementById('mobileChat').addEventListener('touchstart', () => this.toggleMobileChat());
        document.getElementById('mobileEmotes').addEventListener('touchstart', () => this.showMobileEmotes());
        document.getElementById('mobileGames').addEventListener('touchstart', () => this.showMobileGames());
        document.getElementById('mobileMenu').addEventListener('touchstart', () => this.toggleMobileMenu());
    }

    toggleMobileChat() {
        const chatUI = document.getElementById('chatUI');
        if (chatUI.style.display === 'none' || !chatUI.style.display) {
            chatUI.style.display = 'block';
            chatUI.style.width = '90%';
            chatUI.style.left = '5%';
            chatUI.style.bottom = '100px';
            document.getElementById('messageInput').focus();
        } else {
            chatUI.style.display = 'none';
        }
    }

    showMobileEmotes() {
        const emoteWheel = document.createElement('div');
        emoteWheel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 300px;
            height: 300px;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 50%;
            z-index: 1001;
            display: grid;
            grid-template-columns: 1fr 1fr;
            grid-template-rows: 1fr 1fr;
            padding: 20px;
            gap: 10px;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
        `;

        const emotes = [
            { emoji: '👋', action: 'wave' },
            { emoji: '💃', action: 'dance' },
            { emoji: '💖', action: 'heart' },
            { emoji: '😂', action: 'laugh' }
        ];

        emoteWheel.innerHTML = emotes.map(emote => `
            <button style="
                border: none;
                background: none;
                font-size: 40px;
                border-radius: 50%;
                transition: transform 0.2s;
            " onTouchStart="this.style.transform='scale(0.9)'; mobileOptimizer.performEmote('${emote.action}')"
             onTouchEnd="this.style.transform='scale(1)'">${emote.emoji}</button>
        `).join('');

        // Close on outside tap
        emoteWheel.addEventListener('touchstart', (e) => e.stopPropagation());
        document.addEventListener('touchstart', () => emoteWheel.remove());

        document.getElementById('container').appendChild(emoteWheel);
    }

    performEmote(emote) {
        if (this.scene.currentUser) {
            this.scene.emoteSystem.playEmote(emote, this.scene.currentUser);
        }

        // Remove all emote wheels
        document.querySelectorAll('div').forEach(div => {
            if (div.style.backdropFilter === 'blur(20px)') {
                div.remove();
            }
        });
    }

    showMobileGames() {
        // Show simplified game menu for mobile
        const gameMenu = document.createElement('div');
        gameMenu.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 20px;
            border-radius: 20px;
            z-index: 1001;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            min-width: 250px;
        `;

        gameMenu.innerHTML = `
            <h3 style="margin: 0 0 15px 0; text-align: center; color: #ff6b6b;">🎮 Quick Games</h3>
            <div style="display: grid; gap: 10px;">
                <button style="padding: 15px; border: none; border-radius: 10px; background: #ff6b6b; color: white;" 
                        onTouchStart="mobileOptimizer.startQuickGame('love_quiz')">
                    💕 Love Quiz
                </button>
                <button style="padding: 15px; border: none; border-radius: 10px; background: #4ecdc4; color: white;"
                        onTouchStart="mobileOptimizer.startQuickGame('dance_off')">
                    💃 Dance Off
                </button>
                <button style="padding: 15px; border: none; border-radius: 10px; background: #ffd166; color: white;"
                        onTouchStart="gameMenu.remove()">
                    Close
                </button>
            </div>
        `;

        // Close on outside tap
        gameMenu.addEventListener('touchstart', (e) => e.stopPropagation());
        document.addEventListener('touchstart', () => gameMenu.remove());

        document.getElementById('container').appendChild(gameMenu);
    }

    startQuickGame(gameType) {
        // Find nearest avatar for quick game
        const nearbyAvatars = this.scene.miniGameSystem.findNearbyAvatars();
        if (nearbyAvatars.length > 0) {
            const randomAvatar = nearbyAvatars[Math.floor(Math.random() * nearbyAvatars.length)];
            this.scene.miniGameSystem.sendInvitation(gameType, randomAvatar.options.name);
        } else {
            this.showMobileFeedback('No one nearby to play with!', window.innerWidth / 2, window.innerHeight / 2);
        }

        // Remove all menus
        document.querySelectorAll('div').forEach(div => {
            if (div.style.backdropFilter === 'blur(20px)') {
                div.remove();
            }
        });
    }

    toggleMobileMenu() {
        const menu = document.createElement('div');
        menu.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 20px;
            border-radius: 20px;
            z-index: 1001;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            min-width: 200px;
        `;

        menu.innerHTML = `
            <h3 style="margin: 0 0 15px 0; text-align: center; color: #ff6b6b;">⚙️ Menu</h3>
            <div style="display: grid; gap: 10px;">
                <button style="padding: 12px; border: none; border-radius: 8px; background: #9370db; color: white;"
                        onTouchStart="mobileOptimizer.showSettings()">
                    Settings
                </button>
                <button style="padding: 12px; border: none; border-radius: 8px; background: #4ecdc4; color: white;"
                        onTouchStart="mobileOptimizer.showRelationships()">
                    Relationships
                </button>
                <button style="padding: 12px; border: none; border-radius: 8px; background: #ffd166; color: white;"
                        onTouchStart="mobileOptimizer.customizeAvatar()">
                    Customize
                </button>
                <button style="padding: 12px; border: none; border-radius: 8px; background: #666; color: white;"
                        onTouchStart="menu.remove()">
                    Close
                </button>
            </div>
        `;

        menu.addEventListener('touchstart', (e) => e.stopPropagation());
        document.addEventListener('touchstart', () => menu.remove());
        document.getElementById('container').appendChild(menu);
    }

    showMobileFeedback(message, x, y) {
        const feedback = document.createElement('div');
        feedback.style.cssText = `
            position: fixed;
            top: ${y}px;
            left: ${x}px;
            transform: translate(-50%, -50%);
            background: rgba(255, 107, 107, 0.9);
            color: white;
            padding: 10px 20px;
            border-radius: 20px;
            z-index: 1002;
            font-weight: bold;
            backdrop-filter: blur(10px);
            white-space: nowrap;
        `;
        feedback.textContent = message;

        document.getElementById('container').appendChild(feedback);

        setTimeout(() => {
            feedback.style.transition = 'all 0.5s ease-out';
            feedback.style.opacity = '0';
            feedback.style.transform = `translate(-50%, -60px)`;
            setTimeout(() => feedback.remove(), 500);
        }, 1000);
    }

    adaptExistingUIForMobile() {
        // Make existing UI mobile-friendly
        const chatUI = document.getElementById('chatUI');
        if (chatUI) {
            chatUI.style.maxHeight = '40vh';
            chatUI.style.overflowY = 'auto';
        }

        // Adjust camera UI position
        const cameraUI = document.querySelector('[style*="top: 20px"][style*="right: 20px"]');
        if (cameraUI) {
            cameraUI.style.top = '80px';
            cameraUI.style.right = '10px';
            cameraUI.style.transform = 'scale(0.9)';
        }
    }

    optimizePerformanceForMobile() {
        if (!this.isMobile) return;

        // Reduce rendering quality
        this.scene.renderer.setPixelRatio(1);

        // Simplify shadows
        const lights = this.scene.scene.children.filter(child => child instanceof THREE.Light);
        lights.forEach(light => {
            if (light.shadow) {
                light.shadow.mapSize.width = 512;
                light.shadow.mapSize.height = 512;
            }
        });

        // Reduce particle counts
        if (this.scene.weatherSystem) {
            this.scene.weatherSystem.rain.children[0].geometry.setDrawRange(0, 300);
            this.scene.weatherSystem.snow.children[0].geometry.setDrawRange(0, 200);
        }

        // Lower LOD distances
        if (this.scene.performanceOptimizer) {
            this.scene.performanceOptimizer.visibilityDistance = 50;
        }
    }

    isTouchOnUI(x, y) {
        // Check if touch is on any UI element
        const elements = document.elementsFromPoint(x, y);
        return elements.some(el => 
            el.tagName === 'BUTTON' || 
            el.id === 'chatUI' ||
            el.classList.contains('mobile-ui')
        );
    }

    handleTouchEnd(event) {
        this.touchControls.currentTouches.clear();
        this.touchControls.lastPinchDistance = null;
        this.touchControls.lastTwoFingerMidpoint = null;
    }

    handleTouchMove(event) {
        // Handled in specific gesture handlers
    }
}

Step 2: Social Features - Building Communities! 👥

Let's add friend systems, groups, and social interactions:

// social-features.js - Because love is better with friends! 💕

class SocialSystem {
    constructor(scene) {
        this.scene = scene;
        this.friends = new Map(); // userId -> friend data
        this.groups = new Map(); // groupId -> group data
        this.pendingRequests = new Map();
        this.socialEvents = [];

        this.setupSocialUI();
        this.loadSocialData();

        console.log("Social system initialized! Time to make some friends! 👥");
    }

    setupSocialUI() {
        this.createFriendsPanel();
        this.createGroupsPanel();
        this.createSocialNotifications();
    }

    createFriendsPanel() {
        this.friendsPanel = document.createElement('div');
        this.friendsPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1000;
            width: 90%;
            max-width: 400px;
            max-height: 80vh;
            overflow-y: auto;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            display: none;
        `;

        this.friendsPanel.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; color: #ff6b6b;">👥 Friends</h3>
                <button onclick="socialSystem.hideFriendsPanel()" style="
                    background: none;
                    border: none;
                    font-size: 20px;
                    cursor: pointer;
                ">✕</button>
            </div>
            <div id="friendsList" style="margin-bottom: 20px;"></div>
            <div style="display: grid; gap: 10px;">
                <input type="text" id="friendSearch" placeholder="Search friends..." style="
                    padding: 10px;
                    border: 1px solid #ddd;
                    border-radius: 10px;
                    font-size: 14px;
                ">
                <button onclick="socialSystem.showAddFriendDialog()" style="
                    padding: 12px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">+ Add Friend</button>
            </div>
        `;

        document.getElementById('container').appendChild(this.friendsPanel);

        // Add to mobile menu
        this.addSocialToMobileMenu();
    }

    addSocialToMobileMenu() {
        // This would be integrated with the mobile menu system
    }

    showFriendsPanel() {
        this.updateFriendsList();
        this.friendsPanel.style.display = 'block';
    }

    hideFriendsPanel() {
        this.friendsPanel.style.display = 'none';
    }

    updateFriendsList() {
        const friendsList = document.getElementById('friendsList');
        if (!friendsList) return;

        if (this.friends.size === 0) {
            friendsList.innerHTML = `
                <div style="text-align: center; color: #666; padding: 20px;">
                    <p>No friends yet 😢</p>
                    <p style="font-size: 12px;">Add friends to see them here!</p>
                </div>
            `;
            return;
        }

        friendsList.innerHTML = Array.from(this.friends.values()).map(friend => `
            <div class="friend-item" style="
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 12px;
                background: #f8f9fa;
                border-radius: 10px;
                margin-bottom: 8px;
            ">
                <div style="display: flex; align-items: center; gap: 10px;">
                    <div style="
                        width: 40px;
                        height: 40px;
                        border-radius: 50%;
                        background: #ff6b6b;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        color: white;
                        font-weight: bold;
                    ">${friend.name.charAt(0)}</div>
                    <div>
                        <div style="font-weight: bold;">${friend.name}</div>
                        <div style="font-size: 12px; color: #666;">
                            ${friend.online ? '🟢 Online' : '⚫ Offline'}
                            ${friend.inWorld ? ' · In World' : ''}
                        </div>
                    </div>
                </div>
                <div style="display: flex; gap: 5px;">
                    <button onclick="socialSystem.teleportToFriend('${friend.id}')" style="
                        background: #4ecdc4;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        padding: 5px 10px;
                        cursor: pointer;
                        font-size: 12px;
                    ">📍</button>
                    <button onclick="socialSystem.messageFriend('${friend.id}')" style="
                        background: #ffd166;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        padding: 5px 10px;
                        cursor: pointer;
                        font-size: 12px;
                    ">💬</button>
                </div>
            </div>
        `).join('');
    }

    showAddFriendDialog() {
        const dialog = document.createElement('div');
        dialog.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.98);
            padding: 25px;
            border-radius: 20px;
            z-index: 1001;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            min-width: 300px;
        `;

        dialog.innerHTML = `
            <h3 style="margin: 0 0 15px 0; color: #ff6b6b;">Add Friend</h3>
            <input type="text" id="friendUsername" placeholder="Enter username..." style="
                width: 100%;
                padding: 12px;
                border: 1px solid #ddd;
                border-radius: 10px;
                margin-bottom: 15px;
                font-size: 14px;
                box-sizing: border-box;
            ">
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <button onclick="socialSystem.sendFriendRequest()" style="
                    padding: 12px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Send Request</button>
                <button onclick="this.parentElement.parentElement.remove()" style="
                    padding: 12px;
                    background: #666;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Cancel</button>
            </div>
        `;

        document.getElementById('container').appendChild(dialog);

        // Close on outside click
        dialog.addEventListener('click', (e) => e.stopPropagation());
        document.addEventListener('click', () => dialog.remove());
    }

    sendFriendRequest() {
        const usernameInput = document.getElementById('friendUsername');
        const username = usernameInput?.value.trim();

        if (!username) {
            this.showSocialNotification('Please enter a username', 'error');
            return;
        }

        // Simulate sending friend request
        this.showSocialNotification(`Friend request sent to ${username}!`, 'success');

        // In real implementation, this would send to server
        setTimeout(() => {
            this.simulateFriendRequestResponse(username);
        }, 2000);

        // Close dialog
        usernameInput.parentElement.parentElement.remove();
    }

    simulateFriendRequestResponse(username) {
        // Simulate friend accepting request (50% chance)
        if (Math.random() > 0.5) {
            this.addFriend({
                id: 'friend_' + Date.now(),
                name: username,
                online: true,
                inWorld: Math.random() > 0.5,
                friendshipLevel: 1,
                lastSeen: Date.now()
            });

            this.showSocialNotification(`${username} accepted your friend request! 🎉`, 'success');
        } else {
            this.showSocialNotification(`${username} declined your friend request`, 'error');
        }
    }

    addFriend(friendData) {
        this.friends.set(friendData.id, friendData);
        this.updateFriendsList();
        this.saveSocialData();
    }

    removeFriend(friendId) {
        this.friends.delete(friendId);
        this.updateFriendsList();
        this.saveSocialData();
    }

    teleportToFriend(friendId) {
        const friend = this.friends.get(friendId);
        if (!friend || !friend.inWorld) {
            this.showSocialNotification('Friend is not in the world', 'error');
            return;
        }

        // In real implementation, this would teleport to friend's location
        this.showSocialNotification(`Teleporting to ${friend.name}...`, 'info');

        // Simulate teleportation
        setTimeout(() => {
            if (this.scene.currentUser) {
                // Move to random position (in real app, friend's actual position)
                const randomX = (Math.random() - 0.5) * 20;
                const randomZ = (Math.random() - 0.5) * 20;
                this.scene.currentUser.mesh.position.set(randomX, 0, randomZ);

                this.showSocialNotification(`Teleported to ${friend.name}!`, 'success');
            }
        }, 2000);
    }

    messageFriend(friendId) {
        const friend = this.friends.get(friendId);
        if (!friend) return;

        // Open chat with friend
        const chatUI = document.getElementById('chatUI');
        const messageInput = document.getElementById('messageInput');

        if (chatUI && messageInput) {
            chatUI.style.display = 'block';
            messageInput.placeholder = `Message ${friend.name}...`;
            messageInput.focus();

            // Add special header for friend chat
            let header = chatUI.querySelector('.friend-chat-header');
            if (!header) {
                header = document.createElement('div');
                header.className = 'friend-chat-header';
                header.style.cssText = `
                    background: #4ecdc4;
                    color: white;
                    padding: 10px;
                    border-radius: 10px 10px 0 0;
                    text-align: center;
                    font-weight: bold;
                `;
                chatUI.insertBefore(header, chatUI.firstChild);
            }
            header.textContent = `💬 Chatting with ${friend.name}`;
        }
    }

    createGroupsPanel() {
        this.groupsPanel = document.createElement('div');
        this.groupsPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1000;
            width: 90%;
            max-width: 400px;
            max-height: 80vh;
            overflow-y: auto;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            display: none;
        `;

        this.groupsPanel.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; color: #ff6b6b;">👥 Groups</h3>
                <button onclick="socialSystem.hideGroupsPanel()" style="
                    background: none;
                    border: none;
                    font-size: 20px;
                    cursor: pointer;
                ">✕</button>
            </div>
            <div id="groupsList" style="margin-bottom: 20px;"></div>
            <div style="display: grid; gap: 10px;">
                <button onclick="socialSystem.showCreateGroupDialog()" style="
                    padding: 12px;
                    background: #ff6b6b;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">+ Create Group</button>
                <button onclick="socialSystem.showJoinGroupDialog()" style="
                    padding: 12px;
                    background: #9370db;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">🔍 Find Groups</button>
            </div>
        `;

        document.getElementById('container').appendChild(this.groupsPanel);
    }

    showGroupsPanel() {
        this.updateGroupsList();
        this.groupsPanel.style.display = 'block';
    }

    hideGroupsPanel() {
        this.groupsPanel.style.display = 'none';
    }

    updateGroupsList() {
        const groupsList = document.getElementById('groupsList');
        if (!groupsList) return;

        if (this.groups.size === 0) {
            groupsList.innerHTML = `
                <div style="text-align: center; color: #666; padding: 20px;">
                    <p>No groups yet 👥</p>
                    <p style="font-size: 12px;">Create or join a group to get started!</p>
                </div>
            `;
            return;
        }

        groupsList.innerHTML = Array.from(this.groups.values()).map(group => `
            <div class="group-item" style="
                padding: 15px;
                background: #f8f9fa;
                border-radius: 10px;
                margin-bottom: 10px;
            ">
                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
                    <div>
                        <div style="font-weight: bold; font-size: 16px;">${group.name}</div>
                        <div style="font-size: 12px; color: #666;">${group.memberCount} members</div>
                    </div>
                    <div style="display: flex; gap: 5px;">
                        <button onclick="socialSystem.teleportToGroup('${group.id}')" style="
                            background: #4ecdc4;
                            color: white;
                            border: none;
                            border-radius: 5px;
                            padding: 5px 10px;
                            cursor: pointer;
                            font-size: 12px;
                        ">📍</button>
                        <button onclick="socialSystem.leaveGroup('${group.id}')" style="
                            background: #ff6b6b;
                            color: white;
                            border: none;
                            border-radius: 5px;
                            padding: 5px 10px;
                            cursor: pointer;
                            font-size: 12px;
                        ">Leave</button>
                    </div>
                </div>
                <div style="font-size: 12px; color: #666;">${group.description}</div>
                <div style="display: flex; gap: 5px; margin-top: 10px; flex-wrap: wrap;">
                    ${group.tags.map(tag => `
                        <span style="
                            background: #e9ecef;
                            padding: 2px 8px;
                            border-radius: 10px;
                            font-size: 10px;
                        ">${tag}</span>
                    `).join('')}
                </div>
            </div>
        `).join('');
    }

    showCreateGroupDialog() {
        const dialog = document.createElement('div');
        dialog.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.98);
            padding: 25px;
            border-radius: 20px;
            z-index: 1001;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            min-width: 300px;
        `;

        dialog.innerHTML = `
            <h3 style="margin: 0 0 15px 0; color: #ff6b6b;">Create Group</h3>
            <input type="text" id="groupName" placeholder="Group name" style="
                width: 100%;
                padding: 10px;
                border: 1px solid #ddd;
                border-radius: 8px;
                margin-bottom: 10px;
                font-size: 14px;
                box-sizing: border-box;
            ">
            <textarea id="groupDescription" placeholder="Group description" style="
                width: 100%;
                padding: 10px;
                border: 1px solid #ddd;
                border-radius: 8px;
                margin-bottom: 10px;
                font-size: 14px;
                box-sizing: border-box;
                resize: vertical;
                min-height: 60px;
            "></textarea>
            <input type="text" id="groupTags" placeholder="Tags (comma separated)" style="
                width: 100%;
                padding: 10px;
                border: 1px solid #ddd;
                border-radius: 8px;
                margin-bottom: 15px;
                font-size: 14px;
                box-sizing: border-box;
            ">
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <button onclick="socialSystem.createGroup()" style="
                    padding: 12px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Create</button>
                <button onclick="this.parentElement.parentElement.remove()" style="
                    padding: 12px;
                    background: #666;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Cancel</button>
            </div>
        `;

        document.getElementById('container').appendChild(dialog);

        dialog.addEventListener('click', (e) => e.stopPropagation());
        document.addEventListener('click', () => dialog.remove());
    }

    createGroup() {
        const name = document.getElementById('groupName')?.value.trim();
        const description = document.getElementById('groupDescription')?.value.trim();
        const tags = document.getElementById('groupTags')?.value.split(',').map(tag => tag.trim()).filter(tag => tag);

        if (!name) {
            this.showSocialNotification('Please enter a group name', 'error');
            return;
        }

        const group = {
            id: 'group_' + Date.now(),
            name: name,
            description: description || 'A friendly group for hanging out!',
            tags: tags || ['social', 'friendly'],
            memberCount: 1,
            created: Date.now(),
            members: [this.getCurrentUserData()]
        };

        this.groups.set(group.id, group);
        this.updateGroupsList();
        this.saveSocialData();

        this.showSocialNotification(`Group "${name}" created! 🎉`, 'success');

        // Close dialog
        document.querySelectorAll('div').forEach(div => {
            if (div.style.backdropFilter === 'blur(20px)') {
                div.remove();
            }
        });
    }

    getCurrentUserData() {
        // In real implementation, this would get actual user data
        return {
            id: 'current_user',
            name: this.scene.currentUser?.options.name || 'You',
            avatar: this.scene.currentUser
        };
    }

    teleportToGroup(groupId) {
        const group = this.groups.get(groupId);
        if (!group) return;

        this.showSocialNotification(`Teleporting to ${group.name}...`, 'info');

        // In real implementation, this would teleport to group gathering spot
        setTimeout(() => {
            if (this.scene.currentUser) {
                // Move to group area (simulated)
                const groupX = (Math.random() - 0.5) * 10;
                const groupZ = (Math.random() - 0.5) * 10;
                this.scene.currentUser.mesh.position.set(groupX, 0, groupZ);

                this.showSocialNotification(`Joined ${group.name}! 👥`, 'success');
            }
        }, 2000);
    }

    leaveGroup(groupId) {
        const group = this.groups.get(groupId);
        if (!group) return;

        if (confirm(`Are you sure you want to leave "${group.name}"?`)) {
            this.groups.delete(groupId);
            this.updateGroupsList();
            this.saveSocialData();
            this.showSocialNotification(`Left ${group.name}`, 'info');
        }
    }

    createSocialNotifications() {
        this.notificationContainer = document.createElement('div');
        this.notificationContainer.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 1000;
            display: flex;
            flex-direction: column;
            gap: 10px;
            max-width: 300px;
        `;
        document.getElementById('container').appendChild(this.notificationContainer);
    }

    showSocialNotification(message, type = 'info') {
        const notification = document.createElement('div');
        const colors = {
            info: '#4ecdc4',
            success: '#4ecdc4',
            error: '#ff6b6b',
            warning: '#ffd166'
        };

        notification.style.cssText = `
            background: ${colors[type] || colors.info};
            color: white;
            padding: 15px 20px;
            border-radius: 15px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
            backdrop-filter: blur(10px);
            animation: slideInRight 0.3s ease-out;
            word-wrap: break-word;
        `;

        notification.textContent = message;
        this.notificationContainer.appendChild(notification);

        // Auto-remove after 5 seconds
        setTimeout(() => {
            notification.style.animation = 'slideOutRight 0.3s ease-in';
            setTimeout(() => {
                if (notification.parentNode) {
                    notification.parentNode.removeChild(notification);
                }
            }, 300);
        }, 5000);

        // Add CSS animations
        if (!document.getElementById('notificationStyles')) {
            const style = document.createElement('style');
            style.id = 'notificationStyles';
            style.textContent = `
                @keyframes slideInRight {
                    from { transform: translateX(100%); opacity: 0; }
                    to { transform: translateX(0); opacity: 1; }
                }
                @keyframes slideOutRight {
                    from { transform: translateX(0); opacity: 1; }
                    to { transform: translateX(100%); opacity: 0; }
                }
            `;
            document.head.appendChild(style);
        }
    }

    loadSocialData() {
        // Load friends and groups from localStorage
        try {
            const savedFriends = localStorage.getItem('datingApp_friends');
            const savedGroups = localStorage.getItem('datingApp_groups');

            if (savedFriends) {
                const friendsData = JSON.parse(savedFriends);
                friendsData.forEach(friend => this.friends.set(friend.id, friend));
            }

            if (savedGroups) {
                const groupsData = JSON.parse(savedGroups);
                groupsData.forEach(group => this.groups.set(group.id, group));
            }
        } catch (error) {
            console.warn('Error loading social data:', error);
        }
    }

    saveSocialData() {
        // Save friends and groups to localStorage
        try {
            const friendsData = Array.from(this.friends.values());
            const groupsData = Array.from(this.groups.values());

            localStorage.setItem('datingApp_friends', JSON.stringify(friendsData));
            localStorage.setItem('datingApp_groups', JSON.stringify(groupsData));
        } catch (error) {
            console.warn('Error saving social data:', error);
        }
    }

    // Social events and activities
    createSocialEvent(eventData) {
        this.socialEvents.push({
            ...eventData,
            id: 'event_' + Date.now(),
            participants: new Set(),
            createdAt: Date.now()
        });

        this.announceSocialEvent(eventData);
    }

    announceSocialEvent(event) {
        this.showSocialNotification(`🎉 New Event: ${event.name} - ${event.description}`, 'info');

        // Add to event board (if exists)
        this.updateEventBoard();
    }

    updateEventBoard() {
        // Update any visible event boards in the world
        // This would update 3D objects showing current events
    }

    joinSocialEvent(eventId) {
        const event = this.socialEvents.find(e => e.id === eventId);
        if (event) {
            event.participants.add(this.getCurrentUserData());
            this.showSocialNotification(`Joined event: ${event.name}`, 'success');
        }
    }

    // Integration with existing systems
    setupSocialIntegrations() {
        // Integrate with relationship system
        this.setupRelationshipIntegration();

        // Integrate with mini-games
        this.setupGameIntegration();
    }

    setupRelationshipIntegration() {
        // When friendship level reaches certain points, suggest adding as friend
        // This would connect with the relationship system from Part 6
    }

    setupGameIntegration() {
        // Create group games and tournaments
        // This would extend the mini-game system from Part 6
    }
}

Step 3: Enhanced Customization - Express Yourself! 🎨

Let's create extensive customization options for avatars and the environment:

javascript

// customization.js - Because everyone deserves to be unique! 🌈

class CustomizationSystem {
    constructor(scene) {
        this.scene = scene;
        this.avatarCustomizations = new Map();
        this.environmentThemes = new Map();
        this.currentTheme = 'default';

        this.loadCustomizationData();
        this.setupCustomizationUI();

        console.log("Customization system loaded! Time to express yourself! 🎨");
    }

    setupCustomizationUI() {
        this.createCustomizationPanel();
        this.createThemeSelector();
        this.createQuickCustomizeMenu();
    }

    createCustomizationPanel() {
        this.customizationPanel = document.createElement('div');
        this.customizationPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1000;
            width: 90%;
            max-width: 500px;
            max-height: 80vh;
            overflow-y: auto;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            display: none;
        `;

        this.customizationPanel.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; color: #ff6b6b;">🎨 Customize Avatar</h3>
                <button onclick="customizationSystem.hideCustomizationPanel()" style="
                    background: none;
                    border: none;
                    font-size: 20px;
                    cursor: pointer;
                ">✕</button>
            </div>
            <div id="customizationTabs" style="
                display: flex;
                gap: 5px;
                margin-bottom: 20px;
                border-bottom: 1px solid #eee;
                padding-bottom: 10px;
            ">
                <button class="tab-button active" data-tab="body" style="
                    flex: 1;
                    padding: 10px;
                    border: none;
                    background: #f8f9fa;
                    border-radius: 8px;
                    cursor: pointer;
                ">Body</button>
                <button class="tab-button" data-tab="face" style="
                    flex: 1;
                    padding: 10px;
                    border: none;
                    background: #f8f9fa;
                    border-radius: 8px;
                    cursor: pointer;
                ">Face</button>
                <button class="tab-button" data-tab="clothing" style="
                    flex: 1;
                    padding: 10px;
                    border: none;
                    background: #f8f9fa;
                    border-radius: 8px;
                    cursor: pointer;
                ">Clothing</button>
                <button class="tab-button" data-tab="accessories" style="
                    flex: 1;
                    padding: 10px;
                    border: none;
                    background: #f8f9fa;
                    border-radius: 8px;
                    cursor: pointer;
                ">Accessories</button>
            </div>
            <div id="customizationContent"></div>
            <div style="margin-top: 20px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <button onclick="customizationSystem.applyCustomizations()" style="
                    padding: 12px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Apply</button>
                <button onclick="customizationSystem.randomizeAvatar()" style="
                    padding: 12px;
                    background: #ffd166;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Randomize 🎲</button>
            </div>
        `;

        document.getElementById('container').appendChild(this.customizationPanel);

        // Setup tab switching
        this.setupCustomizationTabs();
    }

    setupCustomizationTabs() {
        const tabButtons = this.customizationPanel.querySelectorAll('.tab-button');
        tabButtons.forEach(button => {
            button.addEventListener('click', () => {
                // Remove active class from all buttons
                tabButtons.forEach(btn => btn.classList.remove('active'));
                // Add active class to clicked button
                button.classList.add('active');
                // Show corresponding content
                this.showCustomizationTab(button.dataset.tab);
            });
        });

        // Show initial tab
        this.showCustomizationTab('body');
    }

    showCustomizationTab(tabName) {
        const content = document.getElementById('customizationContent');

        switch(tabName) {
            case 'body':
                content.innerHTML = this.getBodyCustomizationHTML();
                break;
            case 'face':
                content.innerHTML = this.getFaceCustomizationHTML();
                break;
            case 'clothing':
                content.innerHTML = this.getClothingCustomizationHTML();
                break;
            case 'accessories':
                content.innerHTML = this.getAccessoriesCustomizationHTML();
                break;
        }

        // Initialize color pickers and sliders
        this.initializeCustomizationControls();
    }

    getBodyCustomizationHTML() {
        return `
            <div style="display: grid; gap: 15px;">
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Skin Tone</label>
                    <input type="color" id="skinTone" value="#f0d9b5" style="width: 100%; height: 40px;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Height</label>
                    <input type="range" id="avatarHeight" min="0.8" max="1.2" step="0.05" value="1" style="width: 100%;">
                    <div style="display: flex; justify-content: between; font-size: 12px; color: #666;">
                        <span>Short</span>
                        <span>Average</span>
                        <span>Tall</span>
                    </div>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Body Type</label>
                    <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5px;">
                        <button data-bodytype="slim" style="padding: 10px; border: 1px solid #ddd; border-radius: 8px; background: white; cursor: pointer;">
                            Slim
                        </button>
                        <button data-bodytype="average" style="padding: 10px; border: 1px solid #ddd; border-radius: 8px; background: white; cursor: pointer;">
                            Average
                        </button>
                        <button data-bodytype="athletic" style="padding: 10px; border: 1px solid #ddd; border-radius: 8px; background: white; cursor: pointer;">
                            Athletic
                        </button>
                    </div>
                </div>
            </div>
        `;
    }

    getFaceCustomizationHTML() {
        return `
            <div style="display: grid; gap: 15px;">
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Eye Color</label>
                    <input type="color" id="eyeColor" value="#000000" style="width: 100%; height: 40px;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Hair Style</label>
                    <select id="hairStyle" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px;">
                        <option value="short">Short</option>
                        <option value="medium">Medium</option>
                        <option value="long">Long</option>
                        <option value="curly">Curly</option>
                        <option value="ponytail">Ponytail</option>
                    </select>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Hair Color</label>
                    <input type="color" id="hairColor" value="#8b4513" style="width: 100%; height: 40px;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Facial Features</label>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                        <div>
                            <label style="font-size: 12px;">Nose Size</label>
                            <input type="range" id="noseSize" min="0.5" max="1.5" step="0.1" value="1" style="width: 100%;">
                        </div>
                        <div>
                            <label style="font-size: 12px;">Eye Size</label>
                            <input type="range" id="eyeSize" min="0.5" max="1.5" step="0.1" value="1" style="width: 100%;">
                        </div>
                    </div>
                </div>
            </div>
        `;
    }

    getClothingCustomizationHTML() {
        return `
            <div style="display: grid; gap: 15px;">
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Top Style</label>
                    <select id="topStyle" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px;">
                        <option value="t-shirt">T-Shirt</option>
                        <option value="shirt">Shirt</option>
                        <option value="sweater">Sweater</option>
                        <option value="dress">Dress</option>
                        <option value="tank">Tank Top</option>
                    </select>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Top Color</label>
                    <input type="color" id="topColor" value="#4169e1" style="width: 100%; height: 40px;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Bottom Style</label>
                    <select id="bottomStyle" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px;">
                        <option value="jeans">Jeans</option>
                        <option value="pants">Pants</option>
                        <option value="shorts">Shorts</option>
                        <option value="skirt">Skirt</option>
                    </select>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Bottom Color</label>
                    <input type="color" id="bottomColor" value="#2f4f4f" style="width: 100%; height: 40px;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Shoes</label>
                    <select id="shoeStyle" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px;">
                        <option value="sneakers">Sneakers</option>
                        <option value="boots">Boots</option>
                        <option value="sandals">Sandals</option>
                        <option value="heels">Heels</option>
                    </select>
                </div>
            </div>
        `;
    }

    getAccessoriesCustomizationHTML() {
        return `
            <div style="display: grid; gap: 15px;">
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Glasses</label>
                    <select id="glassesStyle" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px;">
                        <option value="none">None</option>
                        <option value="round">Round</option>
                        <option value="square">Square</option>
                        <option value="sunglasses">Sunglasses</option>
                        <option value="aviator">Aviator</option>
                    </select>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Hat</label>
                    <select id="hatStyle" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px;">
                        <option value="none">None</option>
                        <option value="baseball">Baseball Cap</option>
                        <option value="beanie">Beanie</option>
                        <option value="fedora">Fedora</option>
                        <option value="crown">Crown 👑</option>
                    </select>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Jewelry</label>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                        <label style="display: flex; align-items: center; gap: 5px;">
                            <input type="checkbox" id="hasNecklace">
                            <span>Necklace</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 5px;">
                            <input type="checkbox" id="hasEarrings">
                            <span>Earrings</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 5px;">
                            <input type="checkbox" id="hasBracelet">
                            <span>Bracelet</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 5px;">
                            <input type="checkbox" id="hasRing">
                            <span>Ring</span>
                        </label>
                    </div>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-weight: bold;">Special Effects</label>
                    <select id="specialEffects" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px;">
                        <option value="none">None</option>
                        <option value="sparkles">Sparkles ✨</option>
                        <option value="glow">Glow 💫</option>
                        <option value="hearts">Hearts 💕</option>
                        <option value="rainbow">Rainbow 🌈</option>
                    </select>
                </div>
            </div>
        `;
    }

    initializeCustomizationControls() {
        // Initialize event listeners for customization controls
        const colorPickers = this.customizationPanel.querySelectorAll('input[type="color"]');
        colorPickers.forEach(picker => {
            picker.addEventListener('input', (e) => {
                this.previewCustomization(e.target.id, e.target.value);
            });
        });

        const sliders = this.customizationPanel.querySelectorAll('input[type="range"]');
        sliders.forEach(slider => {
            slider.addEventListener('input', (e) => {
                this.previewCustomization(e.target.id, e.target.value);
            });
        });

        const selects = this.customizationPanel.querySelectorAll('select');
        selects.forEach(select => {
            select.addEventListener('change', (e) => {
                this.previewCustomization(e.target.id, e.target.value);
            });
        });

        const buttons = this.customizationPanel.querySelectorAll('button[data-bodytype]');
        buttons.forEach(button => {
            button.addEventListener('click', (e) => {
                buttons.forEach(btn => btn.style.background = 'white');
                e.target.style.background = '#4ecdc4';
                this.previewCustomization('bodyType', e.target.dataset.bodytype);
            });
        });

        const checkboxes = this.customizationPanel.querySelectorAll('input[type="checkbox"]');
        checkboxes.forEach(checkbox => {
            checkbox.addEventListener('change', (e) => {
                this.previewCustomization(e.target.id, e.target.checked);
            });
        });
    }

    previewCustomization(controlId, value) {
        if (!this.scene.currentUser) return;

        // Apply preview to current avatar
        const avatar = this.scene.currentUser;

        switch(controlId) {
            case 'skinTone':
                this.previewSkinTone(avatar, value);
                break;
            case 'avatarHeight':
                this.previewHeight(avatar, value);
                break;
            case 'bodyType':
                this.previewBodyType(avatar, value);
                break;
            case 'eyeColor':
                this.previewEyeColor(avatar, value);
                break;
            case 'hairColor':
                this.previewHairColor(avatar, value);
                break;
            case 'topColor':
                this.previewClothingColor(avatar, 'top', value);
                break;
            case 'bottomColor':
                this.previewClothingColor(avatar, 'bottom', value);
                break;
            // Add more preview cases as needed
        }
    }

    previewSkinTone(avatar, color) {
        if (avatar.head && avatar.head.material) {
            avatar.head.material.color.setStyle(color);
        }
        if (avatar.neck && avatar.neck.material) {
            avatar.neck.material.color.setStyle(color);
        }
        if (avatar.leftArm && avatar.leftArm.material) {
            avatar.leftArm.material.color.setStyle(color);
        }
        if (avatar.rightArm && avatar.rightArm.material) {
            avatar.rightArm.material.color.setStyle(color);
        }
        if (avatar.leftHand && avatar.leftHand.material) {
            avatar.leftHand.material.color.setStyle(color);
        }
        if (avatar.rightHand && avatar.rightHand.material) {
            avatar.rightHand.material.color.setStyle(color);
        }
    }

    previewHeight(avatar, scale) {
        avatar.mesh.scale.y = parseFloat(scale);
    }

    previewBodyType(avatar, bodyType) {
        // Adjust body proportions based on body type
        const scales = {
            slim: { x: 0.9, y: 1, z: 0.9 },
            average: { x: 1, y: 1, z: 1 },
            athletic: { x: 1.1, y: 1.05, z: 1.1 }
        };

        const scale = scales[bodyType] || scales.average;
        if (avatar.torso) {
            avatar.torso.scale.set(scale.x, scale.y, scale.z);
        }
    }

    previewEyeColor(avatar, color) {
        if (avatar.leftEye && avatar.leftEye.material) {
            avatar.leftEye.material.color.setStyle(color);
        }
        if (avatar.rightEye && avatar.rightEye.material) {
            avatar.rightEye.material.color.setStyle(color);
        }
    }

    previewHairColor(avatar, color) {
        if (avatar.hair && avatar.hair.material) {
            avatar.hair.material.color.setStyle(color);
        }
    }

    previewClothingColor(avatar, clothingType, color) {
        if (clothingType === 'top' && avatar.torso && avatar.torso.material) {
            avatar.torso.material.color.setStyle(color);
        }
        // Add other clothing types as needed
    }

    showCustomizationPanel() {
        this.loadCurrentAvatarSettings();
        this.customizationPanel.style.display = 'block';
    }

    hideCustomizationPanel() {
        this.customizationPanel.style.display = 'none';
    }

    loadCurrentAvatarSettings() {
        if (!this.scene.currentUser) return;

        const avatar = this.scene.currentUser;

        // Load current settings into form
        // This would read from the avatar's current appearance
        // For now, we'll set some defaults

        const skinToneInput = document.getElementById('skinTone');
        if (skinToneInput && avatar.head && avatar.head.material) {
            skinToneInput.value = this.rgbToHex(avatar.head.material.color);
        }

        const heightInput = document.getElementById('avatarHeight');
        if (heightInput) {
            heightInput.value = avatar.mesh.scale.y;
        }

        // Load other current settings...
    }

    rgbToHex(color) {
        return '#' + color.getHexString();
    }

    applyCustomizations() {
        if (!this.scene.currentUser) return;

        // Gather all customization values
        const customizations = {
            skinTone: document.getElementById('skinTone')?.value,
            height: document.getElementById('avatarHeight')?.value,
            bodyType: document.querySelector('button[data-bodytype][style*="background: #4ecdc4"]')?.dataset.bodytype,
            eyeColor: document.getElementById('eyeColor')?.value,
            hairStyle: document.getElementById('hairStyle')?.value,
            hairColor: document.getElementById('hairColor')?.value,
            topStyle: document.getElementById('topStyle')?.value,
            topColor: document.getElementById('topColor')?.value,
            bottomStyle: document.getElementById('bottomStyle')?.value,
            bottomColor: document.getElementById('bottomColor')?.value,
            shoeStyle: document.getElementById('shoeStyle')?.value,
            glassesStyle: document.getElementById('glassesStyle')?.value,
            hatStyle: document.getElementById('hatStyle')?.value,
            hasNecklace: document.getElementById('hasNecklace')?.checked,
            hasEarrings: document.getElementById('hasEarrings')?.checked,
            hasBracelet: document.getElementById('hasBracelet')?.checked,
            hasRing: document.getElementById('hasRing')?.checked,
            specialEffects: document.getElementById('specialEffects')?.value
        };

        // Apply customizations
        this.applyAvatarCustomizations(this.scene.currentUser, customizations);

        // Save customizations
        this.avatarCustomizations.set(this.scene.currentUser, customizations);
        this.saveCustomizationData();

        this.showCustomizationNotification('Avatar customized! 🎨');
        this.hideCustomizationPanel();
    }

    applyAvatarCustomizations(avatar, customizations) {
        // Apply all customizations to the avatar
        if (customizations.skinTone) {
            this.previewSkinTone(avatar, customizations.skinTone);
        }

        if (customizations.height) {
            this.previewHeight(avatar, customizations.height);
        }

        if (customizations.bodyType) {
            this.previewBodyType(avatar, customizations.bodyType);
        }

        if (customizations.eyeColor) {
            this.previewEyeColor(avatar, customizations.eyeColor);
        }

        if (customizations.hairColor) {
            this.previewHairColor(avatar, customizations.hairColor);
        }

        if (customizations.topColor) {
            this.previewClothingColor(avatar, 'top', customizations.topColor);
        }

        // Apply other customizations...

        // Apply special effects
        this.applySpecialEffects(avatar, customizations.specialEffects);
    }

    applySpecialEffects(avatar, effect) {
        // Remove existing effects
        if (avatar.specialEffect) {
            avatar.mesh.remove(avatar.specialEffect);
        }

        if (effect === 'none') return;

        let effectObject;

        switch(effect) {
            case 'sparkles':
                effectObject = this.createSparkleEffect();
                break;
            case 'glow':
                effectObject = this.createGlowEffect();
                break;
            case 'hearts':
                effectObject = this.createHeartEffect();
                break;
            case 'rainbow':
                effectObject = this.createRainbowEffect();
                break;
        }

        if (effectObject) {
            avatar.specialEffect = effectObject;
            avatar.mesh.add(effectObject);
        }
    }

    createSparkleEffect() {
        const group = new THREE.Group();

        for (let i = 0; i < 8; i++) {
            const sparkle = new THREE.Mesh(
                new THREE.SphereGeometry(0.05, 8, 8),
                new THREE.MeshBasicMaterial({ color: 0xffffff })
            );

            const angle = (i / 8) * Math.PI * 2;
            const radius = 0.8;
            sparkle.position.set(
                Math.cos(angle) * radius,
                1.5 + Math.sin(Date.now() * 0.001 + i) * 0.2,
                Math.sin(angle) * radius
            );

            group.add(sparkle);
        }

        return group;
    }

    randomizeAvatar() {
        if (!this.scene.currentUser) return;

        const randomCustomizations = {
            skinTone: '#' + Math.floor(Math.random()*16777215).toString(16),
            height: (0.8 + Math.random() * 0.4).toFixed(2),
            bodyType: ['slim', 'average', 'athletic'][Math.floor(Math.random() * 3)],
            eyeColor: '#' + Math.floor(Math.random()*16777215).toString(16),
            hairColor: '#' + Math.floor(Math.random()*16777215).toString(16),
            topColor: '#' + Math.floor(Math.random()*16777215).toString(16),
            bottomColor: '#' + Math.floor(Math.random()*16777215).toString(16),
            specialEffects: ['none', 'sparkles', 'glow', 'hearts', 'rainbow'][Math.floor(Math.random() * 5)]
        };

        this.applyAvatarCustomizations(this.scene.currentUser, randomCustomizations);
        this.showCustomizationNotification('Random avatar created! 🎲');
    }

    createThemeSelector() {
        this.themeSelector = document.createElement('div');
        this.themeSelector.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            backdrop-filter: blur(10px);
            display: none;
        `;

I'd love to continue, but it looks like my previous response got cut off at the end! Let me complete Part 7 and then we'll move on to the remaining parts of our 3D dating app tutorial.

Step 3: Enhanced Customization (Continued) 🎨

    createThemeSelector() {
        this.themeSelector = document.createElement('div');
        this.themeSelector.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            backdrop-filter: blur(10px);
            display: none;
        `;

        this.themeSelector.innerHTML = `
            <h4 style="margin: 0 0 10px 0; color: #ff6b6b;">🎨 Environment Theme</h4>
            <select id="themeSelect" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 8px;">
                <option value="default">Default</option>
                <option value="romantic">Romantic 💕</option>
                <option value="nightclub">Nightclub 🎵</option>
                <option value="garden">Secret Garden 🌸</option>
                <option value="beach">Beach Sunset 🌅</option>
                <option value="winter">Winter Wonderland ❄️</option>
                <option value="fantasy">Fantasy Forest 🧚</option>
            </select>
            <button onclick="customizationSystem.applyTheme()" style="
                width: 100%;
                padding: 8px;
                background: #ff6b6b;
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                margin-top: 10px;
            ">Apply Theme</button>
        `;

        document.getElementById('container').appendChild(this.themeSelector);

        // Load saved theme
        const savedTheme = localStorage.getItem('datingApp_theme');
        if (savedTheme) {
            document.getElementById('themeSelect').value = savedTheme;
            this.applyTheme();
        }
    }

    applyTheme() {
        const themeSelect = document.getElementById('themeSelect');
        if (!themeSelect) return;

        const theme = themeSelect.value;
        this.currentTheme = theme;

        this.applyEnvironmentTheme(theme);
        this.saveCustomizationData();

        this.showCustomizationNotification(`Applied ${theme} theme! 🎨`);
    }

    applyEnvironmentTheme(theme) {
        // Clear existing theme elements
        this.clearThemeElements();

        switch(theme) {
            case 'romantic':
                this.applyRomanticTheme();
                break;
            case 'nightclub':
                this.applyNightclubTheme();
                break;
            case 'garden':
                this.applyGardenTheme();
                break;
            case 'beach':
                this.applyBeachTheme();
                break;
            case 'winter':
                this.applyWinterTheme();
                break;
            case 'fantasy':
                this.applyFantasyTheme();
                break;
            default:
                this.applyDefaultTheme();
        }

        localStorage.setItem('datingApp_theme', theme);
    }

    applyRomanticTheme() {
        // Romantic lighting
        const romanticLight = new THREE.PointLight(0xff69b4, 0.8, 50);
        romanticLight.position.set(0, 10, 0);
        this.scene.scene.add(romanticLight);
        this.themeElements.push(romanticLight);

        // Floating hearts
        for (let i = 0; i < 20; i++) {
            const heart = this.createFloatingHeart();
            heart.position.set(
                (Math.random() - 0.5) * 40,
                Math.random() * 10 + 2,
                (Math.random() - 0.5) * 40
            );
            this.scene.scene.add(heart);
            this.themeElements.push(heart);
        }

        // Soft pink ambient light
        const ambientLight = this.scene.scene.children.find(child => 
            child instanceof THREE.AmbientLight
        );
        if (ambientLight) {
            ambientLight.color.setHex(0xffe6f2);
        }
    }

    createFloatingHeart() {
        const heartShape = new THREE.Shape();
        heartShape.moveTo(0, 0);
        heartShape.bezierCurveTo(0.2, 0.2, 0.3, 0, 0, -0.3);
        heartShape.bezierCurveTo(-0.3, 0, -0.2, 0.2, 0, 0);

        const geometry = new THREE.ExtrudeGeometry(heartShape, {
            depth: 0.1,
            bevelEnabled: true,
            bevelSegments: 2,
            bevelSize: 0.02,
            bevelThickness: 0.02
        });

        const material = new THREE.MeshBasicMaterial({
            color: 0xff69b4,
            transparent: true,
            opacity: 0.7
        });

        const heart = new THREE.Mesh(geometry, material);

        // Floating animation
        heart.userData = {
            floatSpeed: 0.5 + Math.random() * 0.5,
            rotationSpeed: (Math.random() - 0.5) * 0.02,
            startY: heart.position.y
        };

        this.animateFloatingHeart(heart);
        return heart;
    }

    animateFloatingHeart(heart) {
        const animate = () => {
            if (heart.parent) {
                const time = Date.now() * 0.001;
                heart.position.y = heart.userData.startY + Math.sin(time * heart.userData.floatSpeed) * 0.5;
                heart.rotation.y += heart.userData.rotationSpeed;
                requestAnimationFrame(animate);
            }
        };
        animate();
    }

    applyNightclubTheme() {
        // Disco ball
        const discoBall = new THREE.Mesh(
            new THREE.SphereGeometry(2, 32, 32),
            new THREE.MeshStandardMaterial({
                color: 0xffffff,
                metalness: 1,
                roughness: 0.1
            })
        );
        discoBall.position.set(0, 15, 0);
        this.scene.scene.add(discoBall);
        this.themeElements.push(discoBall);

        // Dance floor lights
        this.createDanceFloorLights();

        // Strobe effect
        this.startStrobeEffect();
    }

    createDanceFloorLights() {
        const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff];

        colors.forEach((color, index) => {
            const angle = (index / colors.length) * Math.PI * 2;
            const light = new THREE.SpotLight(color, 1, 20, Math.PI / 4, 0.5);
            light.position.set(
                Math.cos(angle) * 10,
                8,
                Math.sin(angle) * 10
            );
            light.target.position.set(0, 0, 0);
            this.scene.scene.add(light);
            this.scene.scene.add(light.target);
            this.themeElements.push(light);
            this.themeElements.push(light.target);

            // Animate light
            this.animateSpotlight(light, index);
        });
    }

    animateSpotlight(light, index) {
        const animate = () => {
            if (light.parent) {
                const time = Date.now() * 0.001 + index;
                light.intensity = 0.5 + Math.sin(time * 3) * 0.5;
                light.color.setHSL((time * 0.1 + index * 0.1) % 1, 1, 0.5);
                requestAnimationFrame(animate);
            }
        };
        animate();
    }

    startStrobeEffect() {
        const strobe = () => {
            if (this.currentTheme === 'nightclub') {
                const intensity = Math.random() > 0.5 ? 1 : 0.1;
                const ambientLight = this.scene.scene.children.find(child => 
                    child instanceof THREE.AmbientLight
                );
                if (ambientLight) {
                    ambientLight.intensity = intensity;
                }
                setTimeout(strobe, 100 + Math.random() * 200);
            }
        };
        strobe();
    }

    clearThemeElements() {
        this.themeElements.forEach(element => {
            if (element.parent) {
                element.parent.remove(element);
            }
        });
        this.themeElements = [];

        // Reset ambient light
        const ambientLight = this.scene.scene.children.find(child => 
            child instanceof THREE.AmbientLight
        );
        if (ambientLight) {
            ambientLight.color.setHex(0xffffff);
            ambientLight.intensity = 0.6;
        }
    }

    createQuickCustomizeMenu() {
        this.quickCustomizeMenu = document.createElement('div');
        this.quickCustomizeMenu.style.cssText = `
            position: fixed;
            bottom: 200px;
            right: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            backdrop-filter: blur(10px);
            display: none;
        `;

        this.quickCustomizeMenu.innerHTML = `
            <h4 style="margin: 0 0 10px 0; color: #ff6b6b;">🎨 Quick Customize</h4>
            <div style="display: grid; gap: 8px;">
                <button onclick="customizationSystem.showCustomizationPanel()" style="
                    padding: 8px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                ">Full Customize</button>
                <button onclick="customizationSystem.randomizeAvatar()" style="
                    padding: 8px;
                    background: #ffd166;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                ">Randomize 🎲</button>
                <button onclick="customizationSystem.toggleThemeSelector()" style="
                    padding: 8px;
                    background: #9370db;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                ">Themes</button>
                <button onclick="customizationSystem.hideQuickCustomize()" style="
                    padding: 8px;
                    background: #666;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                ">Close</button>
            </div>
        `;

        document.getElementById('container').appendChild(this.quickCustomizeMenu);
    }

    showQuickCustomize() {
        this.quickCustomizeMenu.style.display = 'block';
    }

    hideQuickCustomize() {
        this.quickCustomizeMenu.style.display = 'none';
    }

    toggleThemeSelector() {
        if (this.themeSelector.style.display === 'block') {
            this.themeSelector.style.display = 'none';
        } else {
            this.themeSelector.style.display = 'block';
        }
    }

    showCustomizationNotification(message) {
        // Use the social system's notification if available, or create a simple one
        if (this.scene.socialSystem) {
            this.scene.socialSystem.showSocialNotification(message, 'success');
        } else {
            alert(message);
        }
    }

    loadCustomizationData() {
        try {
            const savedCustomizations = localStorage.getItem('datingApp_customizations');
            if (savedCustomizations) {
                this.avatarCustomizations = new Map(JSON.parse(savedCustomizations));
            }

            const savedTheme = localStorage.getItem('datingApp_theme');
            if (savedTheme) {
                this.currentTheme = savedTheme;
            }
        } catch (error) {
            console.warn('Error loading customization data:', error);
        }
    }

    saveCustomizationData() {
        try {
            const customizationsArray = Array.from(this.avatarCustomizations.entries());
            localStorage.setItem('datingApp_customizations', JSON.stringify(customizationsArray));
            localStorage.setItem('datingApp_theme', this.currentTheme);
        } catch (error) {
            console.warn('Error saving customization data:', error);
        }
    }

    // Initialize theme elements array
    themeElements = [];
}

Step 4: Updated DatingScene Class - Mobile & Social Integration 📱

// Updated DatingScene class for Part 7

class DatingScene {
    constructor() {
        // ... existing properties ...

        // New properties for Part 7
        this.mobileOptimizer = null;
        this.socialSystem = null;
        this.customizationSystem = null;

        this.init();
    }

    init() {
        this.createScene();
        this.createCamera();
        this.createRenderer();
        this.createLights();
        this.createEnvironment();

        // Initialize all systems
        this.proximityChat = new ProximityChat(this);
        this.voiceChat = new VoiceChatSystem(this);
        this.emoteSystem = new EmoteSystem(this);
        this.interactiveEnv = new InteractiveEnvironment(this);
        this.dayNightCycle = new DayNightCycle(this);
        this.weatherSystem = new WeatherSystem(this, this.dayNightCycle);
        this.miniGameSystem = new MiniGameSystem(this);
        this.relationshipSystem = new RelationshipSystem(this);
        this.performanceOptimizer = new PerformanceOptimizer(this);

        // Initialize new Part 7 systems
        this.mobileOptimizer = new MobileOptimizer(this);
        this.socialSystem = new SocialSystem(this);
        this.customizationSystem = new CustomizationSystem(this);

        this.setupEnhancedUI();
        this.setupKeyboardListeners();
        this.animate();

        setTimeout(() => {
            document.getElementById('loadingScreen').style.display = 'none';
            document.getElementById('chatUI').style.display = 'block';
            this.addSampleAvatars();
        }, 2000);
    }

    setupEnhancedUI() {
        this.createEnhancedMenu();
        this.setupMobileFriendlyUI();
    }

    createEnhancedMenu() {
        // Enhanced main menu with all new features
        const enhancedMenu = document.createElement('div');
        enhancedMenu.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            z-index: 100;
            display: flex;
            flex-direction: column;
            gap: 10px;
        `;

        enhancedMenu.innerHTML = `
            <button onclick="datingScene.toggleSocialFeatures()" style="
                padding: 10px 15px;
                background: rgba(255, 107, 107, 0.9);
                color: white;
                border: none;
                border-radius: 20px;
                cursor: pointer;
                backdrop-filter: blur(10px);
                display: flex;
                align-items: center;
                gap: 8px;
            ">👥 Social</button>

            <button onclick="datingScene.toggleCustomization()" style="
                padding: 10px 15px;
                background: rgba(76, 201, 240, 0.9);
                color: white;
                border: none;
                border-radius: 20px;
                cursor: pointer;
                backdrop-filter: blur(10px);
                display: flex;
                align-items: center;
                gap: 8px;
            ">🎨 Customize</button>

            <button onclick="datingScene.toggleMobileMenu()" style="
                padding: 10px 15px;
                background: rgba(255, 214, 102, 0.9);
                color: white;
                border: none;
                border-radius: 20px;
                cursor: pointer;
                backdrop-filter: blur(10px);
                display: flex;
                align-items: center;
                gap: 8px;
                display: none;
            " id="mobileMenuBtn">📱 Mobile</button>
        `;

        document.getElementById('container').appendChild(enhancedMenu);

        // Show mobile menu button only on mobile
        if (this.mobileOptimizer.isMobile) {
            document.getElementById('mobileMenuBtn').style.display = 'flex';
        }
    }

    toggleSocialFeatures() {
        if (this.socialSystem.friendsPanel.style.display === 'block') {
            this.socialSystem.hideFriendsPanel();
            this.socialSystem.hideGroupsPanel();
        } else {
            this.socialSystem.showFriendsPanel();
        }
    }

    toggleCustomization() {
        if (this.customizationSystem.customizationPanel.style.display === 'block') {
            this.customizationSystem.hideCustomizationPanel();
            this.customizationSystem.hideQuickCustomize();
            this.customizationSystem.themeSelector.style.display = 'none';
        } else {
            this.customizationSystem.showQuickCustomize();
        }
    }

    toggleMobileMenu() {
        if (this.mobileOptimizer.quickMenu) {
            this.mobileOptimizer.toggleQuickMenu();
        }
    }

    setupMobileFriendlyUI() {
        // Adapt existing UI for mobile
        if (this.mobileOptimizer.isMobile) {
            // Make chat UI more mobile-friendly
            const chatUI = document.getElementById('chatUI');
            if (chatUI) {
                chatUI.style.width = '90%';
                chatUI.style.left = '5%';
                chatUI.style.bottom = '100px';
                chatUI.style.maxHeight = '40vh';
            }

            // Adjust camera controls position
            const cameraControls = document.querySelector('[style*="top: 20px"][style*="right: 20px"]');
            if (cameraControls) {
                cameraControls.style.top = '80px';
            }
        }
    }

    createUserAvatar(config) {
        this.currentUser = new Avatar(config);
        this.currentUser.mesh.position.set(0, 0, 3);
        this.scene.add(this.currentUser.mesh);
        this.avatars.push(this.currentUser);

        // Initialize movement system
        this.currentUser.movement = new AvatarMovement(this.currentUser, this);

        // Set up camera
        this.datingCamera = new DatingCamera(this.camera, this, this.currentUser);

        // Apply any saved customizations
        this.applySavedCustomizations();

        console.log(`Welcome, ${config.name}! Ready to mingle! 💖`);

        document.getElementById('avatarCustomization').style.display = 'none';

        // NPC greetings
        this.avatars.forEach(avatar => {
            if (avatar !== this.currentUser && avatar.ai) {
                const userPosition = this.currentUser.mesh.position.clone();
                avatar.lookAt(userPosition);
                setTimeout(() => {
                    avatar.wave();
                    // Some NPCs might approach or send friend requests
                    if (Math.random() < 0.4) {
                        setTimeout(() => {
                            this.simulateSocialInteraction(avatar);
                        }, 3000);
                    }
                }, 1000);
            }
        });
    }

    applySavedCustomizations() {
        if (!this.currentUser) return;

        // Apply saved avatar customizations
        const savedCustomizations = this.customizationSystem.avatarCustomizations.get(this.currentUser);
        if (savedCustomizations) {
            this.customizationSystem.applyAvatarCustomizations(this.currentUser, savedCustomizations);
        }

        // Apply saved theme
        if (this.customizationSystem.currentTheme !== 'default') {
            this.customizationSystem.applyEnvironmentTheme(this.customizationSystem.currentTheme);
        }
    }

    simulateSocialInteraction(avatar) {
        // 30% chance to send friend request
        if (Math.random() < 0.3) {
            this.socialSystem.showSocialNotification(
                `${avatar.options.name} sent you a friend request!`,
                'info'
            );

            // Auto-accept after 5 seconds (for demo)
            setTimeout(() => {
                this.socialSystem.addFriend({
                    id: avatar.options.name.toLowerCase(),
                    name: avatar.options.name,
                    online: true,
                    inWorld: true,
                    friendshipLevel: 1,
                    lastSeen: Date.now()
                });
            }, 5000);
        }

        // 40% chance to start conversation
        if (Math.random() < 0.4) {
            avatar.ai.approachAvatar(this.currentUser);
        }
    }

    animate() {
        const currentTime = performance.now();
        const deltaTime = (currentTime - this.lastTime) / 1000;
        this.lastTime = currentTime;

        requestAnimationFrame(() => this.animate());

        // Update all systems
        this.performanceOptimizer.updatePerformanceStats();
        if (this.datingCamera) this.datingCamera.update();
        if (this.proximityChat) this.proximityChat.update();
        if (this.interactiveEnv) this.interactiveEnv.update();
        if (this.dayNightCycle) this.dayNightCycle.update(deltaTime);
        if (this.weatherSystem) this.weatherSystem.update(deltaTime);
        if (this.mobileOptimizer) this.mobileOptimizer.update(deltaTime);

        // Update NPC AI
        this.avatars.forEach(avatar => {
            if (avatar.ai) {
                avatar.ai.update(deltaTime);
            }
        });

        // Update mini-games
        this.miniGameSystem.activeGames.forEach(game => {
            if (game.update) game.update(deltaTime);
        });

        this.renderer.render(this.scene, this.camera);
    }
}

// Global variables for new systems
let datingScene;
let datingCamera;
let emoteSystem;
let dayNightCycle;
let weatherSystem;
let miniGameSystem;
let relationshipSystem;
let mobileOptimizer;
let socialSystem;
let customizationSystem;

window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    emoteSystem = datingScene.emoteSystem;
    dayNightCycle = datingScene.dayNightCycle;
    weatherSystem = datingScene.weatherSystem;
    miniGameSystem = datingScene.miniGameSystem;
    relationshipSystem = datingScene.relationshipSystem;
    mobileOptimizer = datingScene.mobileOptimizer;
    socialSystem = datingScene.socialSystem;
    customizationSystem = datingScene.customizationSystem;

    console.log("Enhanced 3D Dating Experience loaded! Mobile, social, and customization features activated! 🎉");

    setupAvatarInteraction();

    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built in Part 7: 🎉

  1. Mobile Optimization:

    • Touch controls with virtual joystick and gesture recognition
    • Gyroscope support for immersive movement
    • Responsive UI that adapts to screen size
    • Mobile-friendly menus and controls
    • Performance optimizations for mobile devices
  2. Social Features:

    • Friend system with requests and management
    • Group creation and joining
    • Social notifications and events
    • Teleportation to friends and groups
    • Persistent social data storage
  3. Enhanced Customization:

    • Comprehensive avatar customization (body, face, clothing, accessories)
    • Environment themes with special effects
    • Real-time preview of changes
    • Random avatar generator
    • Persistent customization settings
  4. Integrated Experience:

    • Mobile and desktop compatibility
    • Social interactions with NPCs
    • Theme-based environment changes
    • Enhanced user onboarding

Key Features Explained: 🔑

Next Time in Part 8: 🚀

We'll add:

Current Project Status: Our 3D dating app is now a fully-featured social platform! With mobile support, social features, and extensive customization, we've created an engaging virtual world where users can express themselves and build meaningful connections across all devices. Love truly knows no bounds! 💕


Fun Fact: Our mobile optimization is so good that users can now find love while waiting in line for coffee! The virtual joystick is probably more responsive than some real-world dating conversations! 😄

Ready for Part 8 where we'll make everything sound as good as it looks?

Part 8: Audio Enhancement, Accessibility & Advanced Animation - A Feast for the Senses! 🎵♿💫

Welcome back, sensory architect! Our dating world looks amazing and works everywhere, but it's about as quiet as a library during finals week. Time to add immersive audio, ensure everyone can enjoy the experience, and make our animations truly come alive!

Step 1: Audio Enhancement System - Set the Mood with Sound! 🎵

Let's create a comprehensive audio system with spatial audio, background music, and immersive sound effects:

// audio-system.js - Because love should sound as good as it looks! 🎶

class AudioSystem {
    constructor(scene) {
        this.scene = scene;
        this.audioContext = null;
        this.backgroundMusic = null;
        this.soundEffects = new Map();
        this.spatialSounds = new Map();
        this.volumeLevels = {
            master: 0.8,
            music: 0.6,
            sfx: 0.7,
            voice: 0.9
        };
        this.currentPlaylist = [];
        this.currentTrackIndex = 0;

        this.setupAudioContext();
        this.loadSoundEffects();
        this.setupAudioUI();
        this.startBackgroundMusic();

        console.log("Audio system initialized! Setting the mood with sound! 🎵");
    }

    async setupAudioContext() {
        try {
            this.audioContext = new (window.AudioContext || window.webkitAudioContext)();

            // Resume audio context on user interaction (browser security requirement)
            document.addEventListener('click', () => {
                if (this.audioContext.state === 'suspended') {
                    this.audioContext.resume();
                }
            }, { once: true });

            console.log("Audio context ready! 🎧");
        } catch (error) {
            console.warn("Web Audio API not supported:", error);
        }
    }

    async loadSoundEffects() {
        const soundEffects = {
            // UI sounds
            'click': 'sounds/ui/click.mp3',
            'hover': 'sounds/ui/hover.mp3',
            'notification': 'sounds/ui/notification.mp3',
            'success': 'sounds/ui/success.mp3',

            // Environment sounds
            'footsteps': 'sounds/environment/footsteps.mp3',
            'rain': 'sounds/environment/rain.mp3',
            'wind': 'sounds/environment/wind.mp3',
            'birds': 'sounds/environment/birds.mp3',

            // Social sounds
            'wave': 'sounds/social/wave.mp3',
            'heart': 'sounds/social/heart.mp3',
            'laugh': 'sounds/social/laugh.mp3',
            'dance': 'sounds/social/dance.mp3',

            // Game sounds
            'game_start': 'sounds/games/start.mp3',
            'game_win': 'sounds/games/win.mp3',
            'game_lose': 'sounds/games/lose.mp3'
        };

        // In a real implementation, you would load these files
        // For this demo, we'll create placeholder oscillators
        this.createPlaceholderSounds(soundEffects);
    }

    createPlaceholderSounds(soundEffects) {
        Object.keys(soundEffects).forEach(soundName => {
            this.soundEffects.set(soundName, {
                play: () => this.playSyntheticSound(soundName)
            });
        });
    }

    playSyntheticSound(soundName) {
        if (!this.audioContext) return;

        const oscillator = this.audioContext.createOscillator();
        const gainNode = this.audioContext.createGain();

        oscillator.connect(gainNode);
        gainNode.connect(this.audioContext.destination);

        // Different sounds based on type
        const soundConfig = {
            'click': { type: 'sine', frequency: 800, duration: 0.1 },
            'hover': { type: 'sine', frequency: 600, duration: 0.05 },
            'notification': { type: 'sine', frequency: 1000, duration: 0.2 },
            'success': { type: 'sine', frequency: 1200, duration: 0.3 },
            'wave': { type: 'sine', frequency: 400, duration: 0.5 },
            'heart': { type: 'sine', frequency: 523, duration: 0.8 }, // C5
            'game_win': { type: 'sine', frequency: 1047, duration: 0.5 } // C6
        };

        const config = soundConfig[soundName] || { type: 'sine', frequency: 440, duration: 0.2 };

        oscillator.type = config.type;
        oscillator.frequency.value = config.frequency;

        gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);
        gainNode.gain.linearRampToValueAtTime(this.volumeLevels.sfx, this.audioContext.currentTime + 0.01);
        gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + config.duration);

        oscillator.start();
        oscillator.stop(this.audioContext.currentTime + config.duration);
    }

    setupAudioUI() {
        this.createAudioControls();
        this.createVolumeMixer();
        this.setupAudioVisualizer();
    }

    createAudioControls() {
        this.audioControls = document.createElement('div');
        this.audioControls.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            backdrop-filter: blur(10px);
            min-width: 200px;
        `;

        this.audioControls.innerHTML = `
            <h4 style="margin: 0 0 10px 0; color: #ff6b6b;">🎵 Audio Controls</h4>
            <div style="display: flex; gap: 10px; margin-bottom: 10px;">
                <button id="musicToggle" style="
                    flex: 1;
                    padding: 8px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                ">🔇 Music</button>
                <button id="sfxToggle" style="
                    flex: 1;
                    padding: 8px;
                    background: #ffd166;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                ">🔊 SFX</button>
            </div>
            <div style="margin-bottom: 10px;">
                <label style="display: block; margin-bottom: 5px; font-size: 12px;">Master Volume</label>
                <input type="range" id="masterVolume" min="0" max="1" step="0.1" value="${this.volumeLevels.master}" 
                       style="width: 100%;">
            </div>
            <button onclick="audioSystem.toggleAudioPanel()" style="
                width: 100%;
                padding: 8px;
                background: #666;
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                font-size: 12px;
            ">Advanced Settings</button>
        `;

        document.getElementById('container').appendChild(this.audioControls);

        // Event listeners
        document.getElementById('musicToggle').addEventListener('click', () => this.toggleMusic());
        document.getElementById('sfxToggle').addEventListener('click', () => this.toggleSFX());
        document.getElementById('masterVolume').addEventListener('input', (e) => {
            this.setMasterVolume(parseFloat(e.target.value));
        });

        this.updateAudioButtons();
    }

    createVolumeMixer() {
        this.volumeMixer = document.createElement('div');
        this.volumeMixer.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 250px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            backdrop-filter: blur(10px);
            display: none;
        `;

        this.volumeMixer.innerHTML = `
            <h4 style="margin: 0 0 10px 0; color: #ff6b6b;">🎚️ Volume Mixer</h4>
            <div style="display: grid; gap: 10px;">
                <div>
                    <label style="display: block; margin-bottom: 5px; font-size: 12px;">Music</label>
                    <input type="range" id="musicVolume" min="0" max="1" step="0.1" value="${this.volumeLevels.music}" 
                           style="width: 100%;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-size: 12px;">Sound Effects</label>
                    <input type="range" id="sfxVolume" min="0" max="1" step="0.1" value="${this.volumeLevels.sfx}" 
                           style="width: 100%;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px; font-size: 12px;">Voice Chat</label>
                    <input type="range" id="voiceVolume" min="0" max="1" step="0.1" value="${this.volumeLevels.voice}" 
                           style="width: 100%;">
                </div>
            </div>
        `;

        document.getElementById('container').appendChild(this.volumeMixer);

        // Event listeners for mixer
        document.getElementById('musicVolume').addEventListener('input', (e) => {
            this.setVolume('music', parseFloat(e.target.value));
        });
        document.getElementById('sfxVolume').addEventListener('input', (e) => {
            this.setVolume('sfx', parseFloat(e.target.value));
        });
        document.getElementById('voiceVolume').addEventListener('input', (e) => {
            this.setVolume('voice', parseFloat(e.target.value));
        });
    }

    toggleAudioPanel() {
        if (this.volumeMixer.style.display === 'block') {
            this.volumeMixer.style.display = 'none';
        } else {
            this.volumeMixer.style.display = 'block';
        }
    }

    setMasterVolume(volume) {
        this.volumeLevels.master = volume;
        this.updateAllVolumes();
        this.saveAudioSettings();
    }

    setVolume(type, volume) {
        this.volumeLevels[type] = volume;
        this.updateAllVolumes();
        this.saveAudioSettings();
    }

    updateAllVolumes() {
        // Update background music volume
        if (this.backgroundMusic) {
            this.backgroundMusic.volume = this.volumeLevels.music * this.volumeLevels.master;
        }

        // In a real implementation, update all sound effects and spatial audio
    }

    toggleMusic() {
        if (this.backgroundMusic) {
            if (this.backgroundMusic.paused) {
                this.backgroundMusic.play();
                document.getElementById('musicToggle').textContent = '🔇 Music';
            } else {
                this.backgroundMusic.pause();
                document.getElementById('musicToggle').textContent = '🔈 Music';
            }
        }
    }

    toggleSFX() {
        this.sfxEnabled = !this.sfxEnabled;
        this.updateAudioButtons();
        this.saveAudioSettings();
    }

    updateAudioButtons() {
        const musicButton = document.getElementById('musicToggle');
        const sfxButton = document.getElementById('sfxToggle');

        if (this.backgroundMusic && !this.backgroundMusic.paused) {
            musicButton.textContent = '🔇 Music';
        } else {
            musicButton.textContent = '🔈 Music';
        }

        sfxButton.textContent = this.sfxEnabled ? '🔊 SFX' : '🔇 SFX';
    }

    async startBackgroundMusic() {
        // In a real implementation, you would load actual music files
        // For this demo, we'll create a simple generative music system

        if (!this.audioContext) return;

        try {
            // Create a simple ambient music generator
            this.createGenerativeMusic();

            // For demo purposes, also create a HTML5 audio fallback
            this.backgroundMusic = new Audio();
            this.backgroundMusic.loop = true;
            this.backgroundMusic.volume = this.volumeLevels.music * this.volumeLevels.master;

            // Simulate loading music
            setTimeout(() => {
                this.backgroundMusic.play().catch(e => {
                    console.log("Auto-play prevented, music will start on user interaction");
                });
            }, 1000);

        } catch (error) {
            console.warn("Background music setup failed:", error);
        }
    }

    createGenerativeMusic() {
        if (!this.audioContext) return;

        // Create ambient pad sound
        const padOscillator = this.audioContext.createOscillator();
        const padGain = this.audioContext.createGain();
        const padFilter = this.audioContext.createBiquadFilter();

        padOscillator.type = 'sine';
        padOscillator.frequency.value = 110; // A2
        padGain.gain.value = 0.1 * this.volumeLevels.music * this.volumeLevels.master;
        padFilter.type = 'lowpass';
        padFilter.frequency.value = 800;

        padOscillator.connect(padFilter);
        padFilter.connect(padGain);
        padGain.connect(this.audioContext.destination);

        // Slowly modulate the frequency for ambient effect
        const lfo = this.audioContext.createOscillator();
        const lfoGain = this.audioContext.createGain();
        lfo.type = 'sine';
        lfo.frequency.value = 0.1; // Very slow modulation
        lfoGain.gain.value = 5; // Small pitch variation

        lfo.connect(lfoGain);
        lfoGain.connect(padOscillator.frequency);

        padOscillator.start();
        lfo.start();

        this.generativeMusic = { padOscillator, padGain, lfo };
    }

    setupAudioVisualizer() {
        this.visualizer = document.createElement('div');
        this.visualizer.style.cssText = `
            position: fixed;
            bottom: 150px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            padding: 10px;
            border-radius: 10px;
            z-index: 100;
            width: 200px;
            height: 60px;
            display: none;
        `;

        document.getElementById('container').appendChild(this.visualizer);

        // Create canvas for visualizer
        this.visualizerCanvas = document.createElement('canvas');
        this.visualizerCanvas.width = 200;
        this.visualizerCanvas.height = 60;
        this.visualizer.appendChild(this.visualizerCanvas);

        this.setupAnalyserNode();
    }

    setupAnalyserNode() {
        if (!this.audioContext || !this.backgroundMusic) return;

        // In a real implementation, you would connect the analyser to audio nodes
        // This is a simplified version for demonstration
        this.startVisualizerAnimation();
    }

    startVisualizerAnimation() {
        const ctx = this.visualizerCanvas.getContext('2d');
        const width = this.visualizerCanvas.width;
        const height = this.visualizerCanvas.height;

        const animate = () => {
            if (this.visualizer.style.display !== 'none') {
                ctx.clearRect(0, 0, width, height);

                // Create fake audio data for visualization
                const bars = 20;
                const barWidth = width / bars;

                for (let i = 0; i < bars; i++) {
                    const barHeight = Math.random() * height * 0.8;
                    const hue = (i / bars) * 360;

                    ctx.fillStyle = `hsl(${hue}, 70%, 60%)`;
                    ctx.fillRect(
                        i * barWidth,
                        height - barHeight,
                        barWidth - 2,
                        barHeight
                    );
                }
            }

            requestAnimationFrame(animate);
        };

        animate();
    }

    playSound(soundName, options = {}) {
        if (!this.sfxEnabled || !this.soundEffects.has(soundName)) return;

        const sound = this.soundEffects.get(soundName);
        sound.play();

        // Log sound playback for debugging
        if (options.debug) {
            console.log(`Playing sound: ${soundName}`);
        }
    }

    playSpatialSound(soundName, position, options = {}) {
        if (!this.sfxEnabled || !this.audioContext) return;

        // Create spatial audio effect using Web Audio API
        const panner = this.audioContext.createPanner();
        panner.panningModel = 'HRTF';
        panner.distanceModel = 'inverse';
        panner.refDistance = 1;
        panner.maxDistance = 50;
        panner.rolloffFactor = 1;
        panner.coneInnerAngle = 360;
        panner.coneOuterAngle = 0;
        panner.coneOuterGain = 0;

        // Set position relative to listener
        panner.positionX.value = position.x;
        panner.positionY.value = position.y;
        panner.positionZ.value = position.z;

        // Create sound source
        const oscillator = this.audioContext.createOscillator();
        const gainNode = this.audioContext.createGain();

        oscillator.connect(gainNode);
        gainNode.connect(panner);
        panner.connect(this.audioContext.destination);

        // Configure based on sound type
        const soundConfig = {
            'footsteps': { type: 'noise', duration: 0.3, frequency: 200 },
            'heart': { type: 'sine', duration: 1.0, frequency: 523 }
        };

        const config = soundConfig[soundName] || { type: 'sine', duration: 0.5, frequency: 440 };

        if (config.type === 'noise') {
            // Create brown noise for footsteps
            const bufferSize = this.audioContext.sampleRate * config.duration;
            const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
            const data = buffer.getChannelData(0);

            let lastOut = 0;
            for (let i = 0; i < bufferSize; i++) {
                const white = Math.random() * 2 - 1;
                data[i] = (lastOut + (0.02 * white)) / 1.02;
                lastOut = data[i];
                data[i] *= 3.5;
            }

            const source = this.audioContext.createBufferSource();
            source.buffer = buffer;
            source.connect(gainNode);
            source.start();
        } else {
            oscillator.type = config.type;
            oscillator.frequency.value = config.frequency;
            oscillator.start();
            oscillator.stop(this.audioContext.currentTime + config.duration);
        }

        gainNode.gain.setValueAtTime(this.volumeLevels.sfx * this.volumeLevels.master, this.audioContext.currentTime);
        gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + config.duration);
    }

    // Environment sound management
    startEnvironmentSounds() {
        this.playEnvironmentSound('birds', { volume: 0.3, loop: true });

        // Weather-based sounds
        if (this.scene.weatherSystem) {
            this.setupWeatherSounds();
        }
    }

    setupWeatherSounds() {
        // Listen for weather changes
        const originalSetWeather = this.scene.weatherSystem.setWeather.bind(this.scene.weatherSystem);
        this.scene.weatherSystem.setWeather = (weatherType, intensity) => {
            originalSetWeather(weatherType, intensity);
            this.handleWeatherChange(weatherType, intensity);
        };
    }

    handleWeatherChange(weatherType, intensity) {
        // Stop all weather sounds
        this.stopWeatherSounds();

        // Start new weather sounds
        switch(weatherType) {
            case 'rainy':
                this.playEnvironmentSound('rain', { volume: intensity * 0.5, loop: true });
                this.playEnvironmentSound('wind', { volume: intensity * 0.3, loop: true });
                break;
            case 'windy':
                this.playEnvironmentSound('wind', { volume: intensity * 0.7, loop: true });
                break;
            case 'snowy':
                this.playEnvironmentSound('wind', { volume: intensity * 0.4, loop: true });
                break;
        }
    }

    stopWeatherSounds() {
        // Implementation to stop specific environment sounds
    }

    playEnvironmentSound(soundName, options = {}) {
        if (!this.sfxEnabled) return;

        // Implementation for looping environment sounds
        console.log(`Playing environment sound: ${soundName}`, options);
    }

    // Integration with existing systems
    setupAudioIntegrations() {
        this.integrateWithEmotes();
        this.integrateWithGames();
        this.integrateWithInteractions();
    }

    integrateWithEmotes() {
        // Play sounds when emotes are used
        const originalPlayEmote = this.scene.emoteSystem.playEmote.bind(this.scene.emoteSystem);
        this.scene.emoteSystem.playEmote = (emoteKey, avatar) => {
            originalPlayEmote(emoteKey, avatar);

            const emoteSounds = {
                'wave': 'wave',
                'dance': 'dance',
                'heart': 'heart',
                'laugh': 'laugh'
            };

            if (emoteSounds[emoteKey]) {
                this.playSound(emoteSounds[emoteKey]);

                // Spatial sound if avatar is not current user
                if (avatar !== this.scene.currentUser) {
                    this.playSpatialSound(emoteSounds[emoteKey], avatar.mesh.position);
                }
            }
        };
    }

    integrateWithGames() {
        // Game sound effects
        const originalStartGame = this.scene.miniGameSystem.startGame.bind(this.scene.miniGameSystem);
        this.scene.miniGameSystem.startGame = (gameType, player1, player2) => {
            originalStartGame(gameType, player1, player2);
            this.playSound('game_start');
        };
    }

    integrateWithInteractions() {
        // UI interaction sounds
        document.addEventListener('click', (e) => {
            if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT') {
                this.playSound('click');
            }
        });

        document.addEventListener('mouseover', (e) => {
            if (e.target.tagName === 'BUTTON') {
                this.playSound('hover');
            }
        });
    }

    saveAudioSettings() {
        try {
            localStorage.setItem('datingApp_audioSettings', JSON.stringify({
                volumeLevels: this.volumeLevels,
                sfxEnabled: this.sfxEnabled,
                musicEnabled: this.backgroundMusic ? !this.backgroundMusic.paused : true
            }));
        } catch (error) {
            console.warn('Error saving audio settings:', error);
        }
    }

    loadAudioSettings() {
        try {
            const saved = localStorage.getItem('datingApp_audioSettings');
            if (saved) {
                const settings = JSON.parse(saved);
                this.volumeLevels = { ...this.volumeLevels, ...settings.volumeLevels };
                this.sfxEnabled = settings.sfxEnabled !== undefined ? settings.sfxEnabled : true;

                // Update UI
                this.updateAllVolumes();
                this.updateAudioButtons();
            }
        } catch (error) {
            console.warn('Error loading audio settings:', error);
        }
    }

    // Cleanup
    cleanup() {
        if (this.generativeMusic) {
            this.generativeMusic.padOscillator.stop();
            this.generativeMusic.lfo.stop();
        }

        if (this.backgroundMusic) {
            this.backgroundMusic.pause();
            this.backgroundMusic = null;
        }
    }
}

Step 2: Accessibility System - Love for Everyone! ♿

Let's create comprehensive accessibility features to ensure everyone can enjoy our dating world:

// accessibility.js - Because everyone deserves to find love! 💝

class AccessibilitySystem {
    constructor(scene) {
        this.scene = scene;
        this.features = {
            screenReader: false,
            highContrast: false,
            colorBlindMode: 'none',
            largeText: false,
            reducedMotion: false,
            keyboardNavigation: true,
            voiceControl: false
        };

        this.setupAccessibility();
        this.createAccessibilityMenu();
        this.setupKeyboardNavigation();

        console.log("Accessibility system initialized! Making love accessible to all! ♿");
    }

    setupAccessibility() {
        this.loadAccessibilitySettings();
        this.applyAccessibilityFeatures();
        this.setupScreenReader();
        this.setupColorAdjustments();
    }

    createAccessibilityMenu() {
        this.accessibilityMenu = document.createElement('div');
        this.accessibilityMenu.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1000;
            width: 90%;
            max-width: 500px;
            max-height: 80vh;
            overflow-y: auto;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            display: none;
        `;

        this.accessibilityMenu.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; color: #ff6b6b;">♿ Accessibility</h3>
                <button onclick="accessibilitySystem.hideMenu()" style="
                    background: none;
                    border: none;
                    font-size: 20px;
                    cursor: pointer;
                ">✕</button>
            </div>

            <div style="display: grid; gap: 20px;">
                <!-- Vision -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">👁️ Vision</h4>
                    <div style="display: grid; gap: 10px;">
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="highContrast" ${this.features.highContrast ? 'checked' : ''}>
                            <span>High Contrast Mode</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="largeText" ${this.features.largeText ? 'checked' : ''}>
                            <span>Large Text</span>
                        </label>
                        <div>
                            <label style="display: block; margin-bottom: 5px;">Color Blindness</label>
                            <select id="colorBlindMode" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 8px;">
                                <option value="none" ${this.features.colorBlindMode === 'none' ? 'selected' : ''}>None</option>
                                <option value="protanopia" ${this.features.colorBlindMode === 'protanopia' ? 'selected' : ''}>Protanopia (Red-Blind)</option>
                                <option value="deuteranopia" ${this.features.colorBlindMode === 'deuteranopia' ? 'selected' : ''}>Deuteranopia (Green-Blind)</option>
                                <option value="tritanopia" ${this.features.colorBlindMode === 'tritanopia' ? 'selected' : ''}>Tritanopia (Blue-Blind)</option>
                            </select>
                        </div>
                    </div>
                </div>

                <!-- Hearing -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">👂 Hearing</h4>
                    <div style="display: grid; gap: 10px;">
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="visualAlerts" ${this.features.visualAlerts ? 'checked' : ''}>
                            <span>Visual Alerts for Sounds</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="subtitles" ${this.features.subtitles ? 'checked' : ''}>
                            <span>Subtitles for Voice Chat</span>
                        </label>
                    </div>
                </div>

                <!-- Motor -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">🎮 Motor</h4>
                    <div style="display: grid; gap: 10px;">
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="reducedMotion" ${this.features.reducedMotion ? 'checked' : ''}>
                            <span>Reduced Motion</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="keyboardNav" ${this.features.keyboardNavigation ? 'checked' : ''}>
                            <span>Keyboard Navigation</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="voiceControl" ${this.features.voiceControl ? 'checked' : ''}>
                            <span>Voice Control</span>
                        </label>
                    </div>
                </div>

                <!-- Cognitive -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">🧠 Cognitive</h4>
                    <div style="display: grid; gap: 10px;">
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="simpleUI" ${this.features.simpleUI ? 'checked' : ''}>
                            <span>Simplified UI</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="readingAssistance" ${this.features.readingAssistance ? 'checked' : ''}>
                            <span>Reading Assistance</span>
                        </label>
                    </div>
                </div>
            </div>

            <div style="margin-top: 20px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <button onclick="accessibilitySystem.applySettings()" style="
                    padding: 12px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Apply Settings</button>
                <button onclick="accessibilitySystem.resetToDefaults()" style="
                    padding: 12px;
                    background: #ffd166;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Reset Defaults</button>
            </div>

            <div style="margin-top: 15px; text-align: center;">
                <button onclick="accessibilitySystem.showQuickAccess()" style="
                    background: none;
                    border: none;
                    color: #666;
                    text-decoration: underline;
                    cursor: pointer;
                    font-size: 12px;
                ">♿ Quick Access Panel</button>
            </div>
        `;

        document.getElementById('container').appendChild(this.accessibilityMenu);

        // Quick access button
        this.createQuickAccessButton();
    }

    createQuickAccessButton() {
        this.quickAccessBtn = document.createElement('button');
        this.quickAccessBtn.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: #ff6b6b;
            color: white;
            border: none;
            font-size: 18px;
            cursor: pointer;
            z-index: 100;
            box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
            backdrop-filter: blur(10px);
        `;
        this.quickAccessBtn.textContent = '♿';
        this.quickAccessBtn.title = 'Accessibility Settings';

        this.quickAccessBtn.addEventListener('click', () => {
            this.showMenu();
        });

        document.getElementById('container').appendChild(this.quickAccessBtn);
    }

    showMenu() {
        this.accessibilityMenu.style.display = 'block';
    }

    hideMenu() {
        this.accessibilityMenu.style.display = 'none';
    }

    applySettings() {
        // Gather settings from form
        this.features = {
            highContrast: document.getElementById('highContrast').checked,
            largeText: document.getElementById('largeText').checked,
            colorBlindMode: document.getElementById('colorBlindMode').value,
            visualAlerts: document.getElementById('visualAlerts').checked,
            subtitles: document.getElementById('subtitles').checked,
            reducedMotion: document.getElementById('reducedMotion').checked,
            keyboardNavigation: document.getElementById('keyboardNav').checked,
            voiceControl: document.getElementById('voiceControl').checked,
            simpleUI: document.getElementById('simpleUI').checked,
            readingAssistance: document.getElementById('readingAssistance').checked
        };

        this.applyAccessibilityFeatures();
        this.saveSettings();
        this.hideMenu();

        this.showNotification('Accessibility settings applied! ♿');
    }

    applyAccessibilityFeatures() {
        this.applyHighContrast();
        this.applyLargeText();
        this.applyColorBlindMode();
        this.applyReducedMotion();
        this.applySimpleUI();
        this.setupVisualAlerts();
        this.setupSubtitles();
    }

    applyHighContrast() {
        const styleId = 'high-contrast-style';
        let style = document.getElementById(styleId);

        if (this.features.highContrast) {
            if (!style) {
                style = document.createElement('style');
                style.id = styleId;
                style.textContent = `
                    body {
                        filter: contrast(1.5) brightness(1.1);
                    }
                    button, input, select {
                        border: 2px solid #000000 !important;
                    }
                    .ui-element {
                        background: #000000 !important;
                        color: #ffffff !important;
                        border: 2px solid #ffffff !important;
                    }
                `;
                document.head.appendChild(style);
            }
        } else {
            if (style) {
                style.remove();
            }
        }
    }

    applyLargeText() {
        const styleId = 'large-text-style';
        let style = document.getElementById(styleId);

        if (this.features.largeText) {
            if (!style) {
                style = document.createElement('style');
                style.id = styleId;
                style.textContent = `
                    body {
                        font-size: 18px !important;
                    }
                    button, input, select {
                        font-size: 18px !important;
                        padding: 12px !important;
                    }
                    .ui-text {
                        font-size: 18px !important;
                    }
                `;
                document.head.appendChild(style);
            }
        } else {
            if (style) {
                style.remove();
            }
        }
    }

    applyColorBlindMode() {
        const styleId = 'color-blind-style';
        let style = document.getElementById(styleId);

        if (style) {
            style.remove();
        }

        if (this.features.colorBlindMode !== 'none') {
            style = document.createElement('style');
            style.id = styleId;

            let filter = '';
            switch(this.features.colorBlindMode) {
                case 'protanopia':
                    filter = 'url(#protanopia)';
                    break;
                case 'deuteranopia':
                    filter = 'url(#deuteranopia)';
                    break;
                case 'tritanopia':
                    filter = 'url(#tritanopia)';
                    break;
            }

            style.textContent = `
                body {
                    filter: ${filter};
                }
            `;

            // Add SVG filters for color blindness simulation
            this.addColorBlindnessFilters();

            document.head.appendChild(style);
        }
    }

    addColorBlindnessFilters() {
        const existingFilters = document.getElementById('color-blind-filters');
        if (existingFilters) return;

        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.id = 'color-blind-filters';
        svg.style.position = 'absolute';
        svg.style.width = '0';
        svg.style.height = '0';

        // Protanopia (red-blind) filter
        const protanopia = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
        protanopia.id = 'protanopia';
        // Simplified color transformation matrix
        protanopia.innerHTML = `
            <feColorMatrix type="matrix" values="
                0.567, 0.433, 0,     0, 0
                0.558, 0.442, 0,     0, 0
                0,     0.242, 0.758, 0, 0
                0,     0,     0,     1, 0
            "/>
        `;

        // Deuteranopia (green-blind) filter
        const deuteranopia = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
        deuteranopia.id = 'deuteranopia';
        deuteranopia.innerHTML = `
            <feColorMatrix type="matrix" values="
                0.625, 0.375, 0,   0, 0
                0.7,   0.3,   0,   0, 0
                0,     0.3,   0.7, 0, 0
                0,     0,     0,   1, 0
            "/>
        `;

        // Tritanopia (blue-blind) filter
        const tritanopia = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
        tritanopia.id = 'tritanopia';
        tritanopia.innerHTML = `
            <feColorMatrix type="matrix" values="
                0.95, 0.05,  0,    0, 0
                0,    0.433, 0.567,0, 0
                0,    0.475, 0.525,0, 0
                0,    0,     0,    1, 0
            "/>
        `;

        svg.appendChild(protanopia);
        svg.appendChild(deuteranopia);
        svg.appendChild(tritanopia);
        document.body.appendChild(svg);
    }

    applyReducedMotion() {
        if (this.features.reducedMotion) {
            // Disable or reduce animations
            document.body.style.animationPlayState = 'paused';

            // Reduce camera movement sensitivity
            if (this.scene.datingCamera) {
                this.scene.datingCamera.orbitSpeed *= 0.5;
            }

            // Reduce particle effects
            if (this.scene.weatherSystem) {
                this.scene.weatherSystem.weatherIntensity *= 0.3;
            }
        } else {
            // Restore animations
            document.body.style.animationPlayState = 'running';

            if (this.scene.datingCamera) {
                this.scene.datingCamera.orbitSpeed *= 2;
            }

            if (this.scene.weatherSystem) {
                this.scene.weatherSystem.weatherIntensity /= 0.3;
            }
        }
    }

    applySimpleUI() {
        const styleId = 'simple-ui-style';
        let style = document.getElementById(styleId);

        if (this.features.simpleUI) {
            if (!style) {
                style = document.createElement('style');
                style.id = styleId;
                style.textContent = `
                    .complex-ui {
                        display: none !important;
                    }
                    button {
                        min-width: 80px !important;
                        min-height: 40px !important;
                    }
                    .ui-container {
                        border: 1px solid #ccc !important;
                        background: #f0f0f0 !important;
                    }
                `;
                document.head.appendChild(style);
            }
        } else {
            if (style) {
                style.remove();
            }
        }
    }

    setupVisualAlerts() {
        if (this.features.visualAlerts && this.scene.audioSystem) {
            // Create visual indicators for important sounds
            this.setupSoundVisualizers();
        }
    }

    setupSoundVisualizers() {
        // Create visual alerts for different sound types
        this.soundAlerts = new Map();

        const alertContainer = document.createElement('div');
        alertContainer.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 1000;
            display: flex;
            flex-direction: column;
            gap: 10px;
        `;
        alertContainer.id = 'sound-alerts';
        document.body.appendChild(alertContainer);

        // Listen for audio events
        if (this.scene.audioSystem) {
            const originalPlaySound = this.scene.audioSystem.playSound.bind(this.scene.audioSystem);
            this.scene.audioSystem.playSound = (soundName, options) => {
                originalPlaySound(soundName, options);
                this.showSoundAlert(soundName);
            };
        }
    }

    showSoundAlert(soundName) {
        if (!this.features.visualAlerts) return;

        const alert = document.createElement('div');
        alert.style.cssText = `
            background: #ff6b6b;
            color: white;
            padding: 10px 15px;
            border-radius: 20px;
            animation: slideInDown 0.3s ease-out;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        `;

        const soundLabels = {
            'notification': '🔔 New Notification',
            'game_start': '🎮 Game Starting',
            'wave': '👋 Someone Waved',
            'heart': '💖 Romantic Gesture'
        };

        alert.textContent = soundLabels[soundName] || `Sound: ${soundName}`;

        const container = document.getElementById('sound-alerts');
        container.appendChild(alert);

        setTimeout(() => {
            alert.style.animation = 'slideOutUp 0.3s ease-in';
            setTimeout(() => alert.remove(), 300);
        }, 3000);
    }

    setupSubtitles() {
        if (this.features.subtitles) {
            this.createSubtitleDisplay();

            // Integrate with voice chat system
            if (this.scene.voiceChat) {
                this.setupVoiceChatSubtitles();
            }
        }
    }

    createSubtitleDisplay() {
        this.subtitleDisplay = document.createElement('div');
        this.subtitleDisplay.style.cssText = `
            position: fixed;
            bottom: 100px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 15px 25px;
            border-radius: 25px;
            z-index: 1000;
            max-width: 80%;
            text-align: center;
            font-size: 16px;
            display: none;
            backdrop-filter: blur(10px);
        `;
        this.subtitleDisplay.id = 'subtitle-display';
        document.body.appendChild(this.subtitleDisplay);
    }

    setupVoiceChatSubtitles() {
        // This would integrate with the actual voice-to-text system
        // For demo purposes, we'll simulate subtitles
        console.log("Voice chat subtitles enabled");
    }

    showSubtitle(text, duration = 3000) {
        if (!this.features.subtitles) return;

        const display = document.getElementById('subtitle-display');
        if (display) {
            display.textContent = text;
            display.style.display = 'block';

            setTimeout(() => {
                display.style.display = 'none';
            }, duration);
        }
    }

    setupScreenReader() {
        // ARIA labels and screen reader support
        this.setupAriaLabels();
        this.setupScreenReaderNotifications();
    }

    setupAriaLabels() {
        // Add ARIA labels to important elements
        const buttons = document.querySelectorAll('button');
        buttons.forEach(button => {
            if (!button.getAttribute('aria-label')) {
                const text = button.textContent || button.title || 'button';
                button.setAttribute('aria-label', text);
            }
        });

        // Add ARIA live regions for dynamic content
        const liveRegion = document.createElement('div');
        liveRegion.setAttribute('aria-live', 'polite');
        liveRegion.setAttribute('aria-atomic', 'true');
        liveRegion.style.cssText = 'position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;';
        liveRegion.id = 'screen-reader-announcements';
        document.body.appendChild(liveRegion);
    }

    announceToScreenReader(message) {
        const liveRegion = document.getElementById('screen-reader-announcements');
        if (liveRegion) {
            liveRegion.textContent = message;

            // Clear after a moment so same message can be announced again
            setTimeout(() => {
                liveRegion.textContent = '';
            }, 1000);
        }
    }

    setupKeyboardNavigation() {
        document.addEventListener('keydown', (e) => {
            if (!this.features.keyboardNavigation) return;

            switch(e.key) {
                case 'Tab':
                    this.handleTabNavigation(e);
                    break;
                case 'Enter':
                    this.handleEnterNavigation(e);
                    break;
                case 'Escape':
                    this.handleEscapeNavigation(e);
                    break;
                case 'ArrowUp':
                case 'ArrowDown':
                case 'ArrowLeft':
                case 'ArrowRight':
                    this.handleArrowNavigation(e);
                    break;
            }
        });
    }

    handleTabNavigation(event) {
        // Enhanced tab navigation for complex UI
        const focusableElements = document.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );

        // Custom tab order logic can be implemented here
        console.log('Tab navigation active');
    }

    setupVoiceControl() {
        if (this.features.voiceControl && 'webkitSpeechRecognition' in window) {
            this.setupSpeechRecognition();
        }
    }

    setupSpeechRecognition() {
        try {
            const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
            this.recognition = new SpeechRecognition();
            this.recognition.continuous = true;
            this.recognition.interimResults = true;

            this.recognition.onresult = (event) => {
                const transcript = event.results[event.results.length - 1][0].transcript.toLowerCase();
                this.processVoiceCommand(transcript);
            };

            this.recognition.start();
        } catch (error) {
            console.warn('Speech recognition not supported:', error);
        }
    }

    processVoiceCommand(transcript) {
        const commands = {
            'open chat': () => this.scene.mobileOptimizer.toggleMobileChat(),
            'show friends': () => this.scene.socialSystem.showFriendsPanel(),
            'dance': () => this.scene.currentUser?.dance(),
            'wave': () => this.scene.emoteSystem.playEmote('wave', this.scene.currentUser),
            'stop': () => this.recognition.stop()
        };

        for (const [command, action] of Object.entries(commands)) {
            if (transcript.includes(command)) {
                action();
                this.showNotification(`Voice command: ${command}`);
                break;
            }
        }
    }

    showQuickAccess() {
        const quickPanel = document.createElement('div');
        quickPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 20px;
            border-radius: 15px;
            z-index: 1000;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
        `;

        quickPanel.innerHTML = `
            <h4 style="margin: 0 0 15px 0; color: #ff6b6b;">♿ Quick Access</h4>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <button onclick="accessibilitySystem.toggleFeature('highContrast')" style="padding: 10px; border: none; border-radius: 8px; background: #4ecdc4; color: white; cursor: pointer;">
                    High Contrast
                </button>
                <button onclick="accessibilitySystem.toggleFeature('largeText')" style="padding: 10px; border: none; border-radius: 8px; background: #ffd166; color: white; cursor: pointer;">
                    Large Text
                </button>
                <button onclick="accessibilitySystem.toggleFeature('reducedMotion')" style="padding: 10px; border: none; border-radius: 8px; background: #9370db; color: white; cursor: pointer;">
                    Reduced Motion
                </button>
                <button onclick="accessibilitySystem.toggleFeature('simpleUI')" style="padding: 10px; border: none; border-radius: 8px; background: #ff6b6b; color: white; cursor: pointer;">
                    Simple UI
                </button>
            </div>
            <button onclick="quickPanel.remove()" style="width: 100%; padding: 10px; margin-top: 15px; background: #666; color: white; border: none; border-radius: 8px; cursor: pointer;">
                Close
            </button>
        `;

        document.getElementById('container').appendChild(quickPanel);

        // Close on outside click
        quickPanel.addEventListener('click', (e) => e.stopPropagation());
        document.addEventListener('click', () => quickPanel.remove());
    }

    toggleFeature(featureName) {
        this.features[featureName] = !this.features[featureName];
        this.applyAccessibilityFeatures();
        this.saveSettings();
        this.showNotification(`${featureName} ${this.features[featureName] ? 'enabled' : 'disabled'}`);
    }

    resetToDefaults() {
        this.features = {
            screenReader: false,
            highContrast: false,
            colorBlindMode: 'none',
            largeText: false,
            reducedMotion: false,
            keyboardNavigation: true,
            voiceControl: false,
            visualAlerts: false,
            subtitles: false,
            simpleUI: false,
            readingAssistance: false
        };

        this.applyAccessibilityFeatures();
        this.saveSettings();
        this.hideMenu();
        this.showNotification('Accessibility settings reset to defaults!');
    }

    showNotification(message) {
        if (this.scene.socialSystem) {
            this.scene.socialSystem.showSocialNotification(message, 'info');
        } else {
            alert(message);
        }
    }

    loadAccessibilitySettings() {
        try {
            const saved = localStorage.getItem('datingApp_accessibility');
            if (saved) {
                this.features = { ...this.features, ...JSON.parse(saved) };
            }
        } catch (error) {
            console.warn('Error loading accessibility settings:', error);
        }
    }

    saveSettings() {
        try {
            localStorage.setItem('datingApp_accessibility', JSON.stringify(this.features));
        } catch (error) {
            console.warn('Error saving accessibility settings:', error);
        }
    }
}

Step 3: Advanced Animation System - Bringing Avatars to Life! 💫

Let's create more realistic and expressive animations for our avatars:

// advanced-animation.js - Because smooth moves make better connections! 💃

class AdvancedAnimationSystem {
    constructor(scene) {
        this.scene = scene;
        this.animations = new Map();
        this.motionCapture = new Map();
        this.blendShapes = new Map();
        this.ikSolvers = new Map();

        this.setupAnimationSystem();
        this.loadAnimationLibrary();
        this.setupRealTimeMotion();

        console.log("Advanced animation system initialized! Bringing avatars to life! 💫");
    }

    setupAnimationSystem() {
        this.animationMixers = new Map();
        this.clock = new THREE.Clock();
        this.setupInverseKinematics();
        this.setupFacialRigging();
        this.setupPhysicsBasedAnimation();
    }

    setupInverseKinematics() {
        // Setup IK solvers for more natural movement
        this.ikSolvers.set('leftArm', this.createIKSolver());
        this.ikSolvers.set('rightArm', this.createIKSolver());
        this.ikSolvers.set('leftLeg', this.createIKSolver());
        this.ikSolvers.set('rightLeg', this.createIKSolver());
    }

    createIKSolver() {
        // Simplified IK solver implementation
        return {
            target: new THREE.Vector3(),
            enabled: false,
            solve: (bones, target) => {
                // Basic IK solving logic
                // In production, you'd use a proper IK library
                return this.solveTwoBoneIK(bones, target);
            }
        };
    }

    solveTwoBoneIK(bones, target) {
        // Simplified two-bone IK solver
        const [upperBone, lowerBone, endBone] = bones;

        // Calculate bone lengths
        const upperLength = upperBone.length || 1;
        const lowerLength = lowerBone.length || 1;
        const totalLength = upperLength + lowerLength;

        // Calculate directions and angles
        const toTarget = new THREE.Vector3().subVectors(target, upperBone.position);
        const distance = toTarget.length();

        // Clamp distance to maximum reach
        const clampedDistance = Math.min(distance, totalLength - 0.1);

        // Calculate angles using law of cosines
        const cosUpperAngle = (upperLength * upperLength + clampedDistance * clampedDistance - lowerLength * lowerLength) /
                            (2 * upperLength * clampedDistance);
        const upperAngle = Math.acos(Math.max(-1, Math.min(1, cosUpperAngle)));

        const cosLowerAngle = (upperLength * upperLength + lowerLength * lowerLength - clampedDistance * clampedDistance) /
                            (2 * upperLength * lowerLength);
        const lowerAngle = Math.acos(Math.max(-1, Math.min(1, cosLowerAngle)));

        // Apply rotations
        upperBone.rotation.z = upperAngle;
        lowerBone.rotation.z = lowerAngle - Math.PI; // Adjust for bone initial rotation

        return { upperAngle, lowerAngle };
    }

    setupFacialRigging() {
        // Create blend shapes for facial expressions
        this.blendShapes.set('smile', { intensity: 0, target: 0 });
        this.blendShapes.set('frown', { intensity: 0, target: 0 });
        this.blendShapes.set('surprise', { intensity: 0, target: 0 });
        this.blendShapes.set('blink', { intensity: 0, target: 0 });

        this.setupEyeTracking();
        this.setupLipSync();
    }

    setupEyeTracking() {
        this.eyeTracking = {
            leftEye: { lookAt: new THREE.Vector3(), blink: 0 },
            rightEye: { lookAt: new THREE.Vector3(), blink: 0 },
            combinedBlink: 0
        };
    }

    setupLipSync() {
        this.lipSync = {
            visemes: new Map(),
            currentViseme: 'neutral',
            intensity: 0
        };

        // Basic viseme shapes
        const visemes = ['AA', 'EE', 'OO', 'MM', 'FF', 'SS', 'RR', 'LL', 'neutral'];
        visemes.forEach(viseme => {
            this.lipSync.visemes.set(viseme, { intensity: 0, target: 0 });
        });
    }

    setupPhysicsBasedAnimation() {
        this.physics = {
            gravity: new THREE.Vector3(0, -9.8, 0),
            wind: new THREE.Vector3(),
            collisions: new Map()
        };

        this.setupClothSimulation();
        this.setupHairPhysics();
    }

    setupClothSimulation() {
        // Simple cloth simulation for clothing
        this.clothSimulation = {
            points: [],
            constraints: [],
            update: (deltaTime) => {
                this.updateClothPhysics(deltaTime);
            }
        };
    }

    setupHairPhysics() {
        // Simple hair physics using verlet integration
        this.hairPhysics = {
            strands: [],
            update: (deltaTime) => {
                this.updateHairPhysics(deltaTime);
            }
        };
    }

    loadAnimationLibrary() {
        // Load predefined animations
        this.animations.set('idle', this.createIdleAnimation());
        this.animations.set('walk', this.createWalkAnimation());
        this.animations.set('run', this.createRunAnimation());
        this.animations.set('jump', this.createJumpAnimation());
        this.animations.set('dance', this.createDanceAnimation());
        this.animations.set('wave', this.createWaveAnimation());

        // Emotional animations
        this.animations.set('laugh', this.createLaughAnimation());
        this.animations.set('cry', this.createCryAnimation());
        this.animations.set('shy', this.createShyAnimation());
        this.animations.set('angry', this.createAngryAnimation());
    }

    createIdleAnimation() {
        return {
            duration: 2.0,
            tracks: [
                {
                    type: 'rotation',
                    bone: 'spine',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, 0) },
                        { time: 1, value: new THREE.Vector3(0, 0.1, 0) },
                        { time: 2, value: new THREE.Vector3(0, 0, 0) }
                    ]
                },
                {
                    type: 'rotation',
                    bone: 'head',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, 0) },
                        { time: 0.5, value: new THREE.Vector3(0.05, 0, 0) },
                        { time: 1.5, value: new THREE.Vector3(-0.05, 0, 0) },
                        { time: 2, value: new THREE.Vector3(0, 0, 0) }
                    ]
                }
            ],
            loop: true
        };
    }

    createWalkAnimation() {
        return {
            duration: 1.0,
            tracks: [
                {
                    type: 'rotation',
                    bone: 'leftLeg',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0.5, 0, 0) },
                        { time: 0.5, value: new THREE.Vector3(-0.2, 0, 0) },
                        { time: 1, value: new THREE.Vector3(0.5, 0, 0) }
                    ]
                },
                {
                    type: 'rotation',
                    bone: 'rightLeg',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(-0.2, 0, 0) },
                        { time: 0.5, value: new THREE.Vector3(0.5, 0, 0) },
                        { time: 1, value: new THREE.Vector3(-0.2, 0, 0) }
                    ]
                },
                {
                    type: 'rotation',
                    bone: 'leftArm',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, -0.3) },
                        { time: 0.5, value: new THREE.Vector3(0, 0, 0.3) },
                        { time: 1, value: new THREE.Vector3(0, 0, -0.3) }
                    ]
                },
                {
                    type: 'rotation',
                    bone: 'rightArm',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, 0.3) },
                        { time: 0.5, value: new THREE.Vector3(0, 0, -0.3) },
                        { time: 1, value: new THREE.Vector3(0, 0, 0.3) }
                    ]
                }
            ],
            loop: true
        };
    }

    createDanceAnimation() {
        return {
            duration: 2.0,
            tracks: [
                {
                    type: 'rotation',
                    bone: 'spine',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, 0) },
                        { time: 0.5, value: new THREE.Vector3(0.3, 0, 0) },
                        { time: 1, value: new THREE.Vector3(-0.3, 0, 0) },
                        { time: 1.5, value: new THREE.Vector3(0.3, 0, 0) },
                        { time: 2, value: new THREE.Vector3(0, 0, 0) }
                    ]
                },
                {
                    type: 'rotation',
                    bone: 'leftArm',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, 0) },
                        { time: 0.25, value: new THREE.Vector3(1.5, 0, 0) },
                        { time: 0.75, value: new THREE.Vector3(0, 0, 0) },
                        { time: 1.25, value: new THREE.Vector3(1.5, 0, 0) },
                        { time: 1.75, value: new THREE.Vector3(0, 0, 0) }
                    ]
                },
                {
                    type: 'rotation',
                    bone: 'rightArm',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, 0) },
                        { time: 0.25, value: new THREE.Vector3(1.5, 0, 0) },
                        { time: 0.75, value: new THREE.Vector3(0, 0, 0) },
                        { time: 1.25, value: new THREE.Vector3(1.5, 0, 0) },
                        { time: 1.75, value: new THREE.Vector3(0, 0, 0) }
                    ]
                }
            ],
            loop: true
        };
    }

    setupRealTimeMotion() {
        this.setupProceduralAnimation();
        this.setupMotionBlending();
        this.setupAnimationEvents();
    }

    setupProceduralAnimation() {
        // Procedural animations for more natural movement
        this.proceduralAnimations = {
            breathing: {
                amplitude: 0.005,
                frequency: 0.5,
                phase: 0
            },
            microMovements: {
                enabled: true,
                intensity: 0.01
            },
            lookAtTarget: null
        };
    }

    setupMotionBlending() {
        this.motionBlending = {
            currentAnimation: 'idle',
            nextAnimation: null,
            blendTime: 0.2,
            blendProgress: 0,
            isBlending: false
        };
    }

    setupAnimationEvents() {
        // Animation event system for syncing with other systems
        this.animationEvents = new Map();

        // Example: Play sound when animation reaches certain point
        this.animationEvents.set('wave', [
            { time: 0.1, callback: () => this.scene.audioSystem?.playSound('wave') }
        ]);
    }

    // Animation management methods
    playAnimation(avatar, animationName, options = {}) {
        const animation = this.animations.get(animationName);
        if (!animation) {
            console.warn(`Animation not found: ${animationName}`);
            return;
        }

        if (this.motionBlending.isBlending && options.force !== true) {
            // Queue animation for after blend completes
            this.motionBlending.queuedAnimation = animationName;
            return;
        }

        if (options.blend !== false) {
            this.startBlend(avatar, animationName, options);
        } else {
            this.motionBlending.currentAnimation = animationName;
            this.applyAnimation(avatar, animation, 0);
        }

        // Trigger animation events
        this.triggerAnimationEvents(animationName, 0);
    }

    startBlend(avatar, nextAnimation, options) {
        this.motionBlending.nextAnimation = nextAnimation;
        this.motionBlending.blendProgress = 0;
        this.motionBlending.isBlending = true;
        this.motionBlending.blendTime = options.blendTime || 0.2;
    }

    applyAnimation(avatar, animation, time) {
        animation.tracks.forEach(track => {
            const bone = this.findBone(avatar, track.bone);
            if (bone) {
                const keyframe = this.interpolateKeyframes(track.keyframes, time);
                this.applyTransform(bone, track.type, keyframe);
            }
        });
    }

    findBone(avatar, boneName) {
        // Simplified bone finding - in production, use proper skeleton
        const boneMap = {
            'spine': avatar.torso,
            'head': avatar.head,
            'leftArm': avatar.leftArm,
            'rightArm': avatar.rightArm,
            'leftLeg': avatar.leftLeg,
            'rightLeg': avatar.rightLeg
        };

        return boneMap[boneName];
    }

    interpolateKeyframes(keyframes, time) {
        // Find surrounding keyframes
        let prevKeyframe = keyframes[0];
        let nextKeyframe = keyframes[keyframes.length - 1];

        for (let i = 0; i < keyframes.length - 1; i++) {
            if (time >= keyframes[i].time && time <= keyframes[i + 1].time) {
                prevKeyframe = keyframes[i];
                nextKeyframe = keyframes[i + 1];
                break;
            }
        }

        // Linear interpolation
        const t = (time - prevKeyframe.time) / (nextKeyframe.time - prevKeyframe.time);
        return this.lerpVector(prevKeyframe.value, nextKeyframe.value, t);
    }

    lerpVector(a, b, t) {
        return new THREE.Vector3(
            a.x + (b.x - a.x) * t,
            a.y + (b.y - a.y) * t,
            a.z + (b.z - a.z) * t
        );
    }

    applyTransform(bone, type, value) {
        switch(type) {
            case 'rotation':
                bone.rotation.set(value.x, value.y, value.z);
                break;
            case 'position':
                bone.position.set(value.x, value.y, value.z);
                break;
            case 'scale':
                bone.scale.set(value.x, value.y, value.z);
                break;
        }
    }

    update(deltaTime) {
        this.updateAnimations(deltaTime);
        this.updateBlending(deltaTime);
        this.updateProceduralAnimations(deltaTime);
        this.updatePhysics(deltaTime);
        this.updateFacialAnimations(deltaTime);
    }

    updateAnimations(deltaTime) {
        this.animationMixers.forEach((mixer, avatar) => {
            mixer.update(deltaTime);
        });
    }

    updateBlending(deltaTime) {
        if (this.motionBlending.isBlending) {
            this.motionBlending.blendProgress += deltaTime / this.motionBlending.blendTime;

            if (this.motionBlending.blendProgress >= 1) {
                this.motionBlending.currentAnimation = this.motionBlending.nextAnimation;
                this.motionBlending.nextAnimation = null;
                this.motionBlending.isBlending = false;
                this.motionBlending.blendProgress = 0;

                // Play queued animation if any
                if (this.motionBlending.queuedAnimation) {
                    this.playAnimation(this.scene.currentUser, this.motionBlending.queuedAnimation);
                    this.motionBlending.queuedAnimation = null;
                }
            }
        }
    }

    updateProceduralAnimations(deltaTime) {
        // Update breathing animation
        this.proceduralAnimations.breathing.phase += deltaTime * this.proceduralAnimations.breathing.frequency;

        // Apply breathing to all avatars
        this.scene.avatars.forEach(avatar => {
            if (avatar.torso) {
                const breathOffset = Math.sin(this.proceduralAnimations.breathing.phase) * 
                                   this.proceduralAnimations.breathing.amplitude;
                avatar.torso.position.y += breathOffset;
            }
        });

        // Micro-movements for natural appearance
        if (this.proceduralAnimations.microMovements.enabled) {
            this.scene.avatars.forEach(avatar => {
                this.applyMicroMovements(avatar, deltaTime);
            });
        }
    }

    applyMicroMovements(avatar, deltaTime) {
        // Small random movements to prevent "robot" appearance
        if (avatar.head && Math.random() < 0.1) {
            const microMove = (Math.random() - 0.5) * this.proceduralAnimations.microMovements.intensity;
            avatar.head.rotation.x += microMove * deltaTime;
            avatar.head.rotation.y += microMove * deltaTime;
        }
    }

    updatePhysics(deltaTime) {
        // Update cloth simulation
        if (this.clothSimulation.points.length > 0) {
            this.clothSimulation.update(deltaTime);
        }

        // Update hair physics
        if (this.hairPhysics.strands.length > 0) {
            this.hairPhysics.update(deltaTime);
        }
    }

    updateFacialAnimations(deltaTime) {
        // Update blend shapes
        this.updateBlendShapes(deltaTime);

        // Update eye tracking
        this.updateEyeTracking(deltaTime);

        // Update lip sync
        this.updateLipSync(deltaTime);
    }

    updateBlendShapes(deltaTime) {
        this.blendShapes.forEach((shape, name) => {
            // Smoothly interpolate to target intensity
            shape.intensity += (shape.target - shape.intensity) * deltaTime * 5;
        });
    }

    updateEyeTracking(deltaTime) {
        if (!this.eyeTracking) return;

        // Random blinking
        if (Math.random() < 0.01) {
            this.eyeTracking.combinedBlink = 1;
        }

        // Smooth blink animation
        this.eyeTracking.combinedBlink = Math.max(0, this.eyeTracking.combinedBlink - deltaTime * 3);

        // Apply to avatars
        this.scene.avatars.forEach(avatar => {
            this.applyEyeBlink(avatar, this.eyeTracking.combinedBlink);
        });
    }

    applyEyeBlink(avatar, blinkAmount) {
        if (avatar.leftEye && avatar.rightEye) {
            const blinkScale = 1 - blinkAmount * 0.8;
            avatar.leftEye.scale.y = blinkScale;
            avatar.rightEye.scale.y = blinkScale;
        }
    }

    updateLipSync(deltaTime) {
        if (!this.lipSync) return;

        // Update viseme intensities
        this.lipSync.visemes.forEach((viseme, name) => {
            viseme.intensity += (viseme.target - viseme.intensity) * deltaTime * 8;
        });
    }

    triggerAnimationEvents(animationName, time) {
        const events = this.animationEvents.get(animationName);
        if (events) {
            events.forEach(event => {
                if (Math.abs(time - event.time) < 0.01) {
                    event.callback();
                }
            });
        }
    }

    // Enhanced animation methods for specific use cases
    createEmotionalAnimation(emotion, intensity) {
        const emotionalAnimations = {
            'happy': this.createHappyAnimation(intensity),
            'sad': this.createSadAnimation(intensity),
            'angry': this.createAngryAnimation(intensity),
            'surprised': this.createSurprisedAnimation(intensity)
        };

        return emotionalAnimations[emotion] || this.createIdleAnimation();
    }

    createHappyAnimation(intensity) {
        return {
            duration: 1.5,
            tracks: [
                {
                    type: 'rotation',
                    bone: 'head',
                    keyframes: [
                        { time: 0, value: new THREE.Vector3(0, 0, 0) },
                        { time: 0.5, value: new THREE.Vector3(0.1 * intensity, 0, 0) },
                        { time: 1, value: new THREE.Vector3(-0.1 * intensity, 0, 0) },
                        { time: 1.5, value: new THREE.Vector3(0, 0, 0) }
                    ]
                }
            ],
            loop: true
        };
    }

    // Integration with other systems
    setupAnimationIntegrations() {
        this.integrateWithEmoteSystem();
        this.integrateWithVoiceSystem();
        this.integrateWithRelationshipSystem();
    }

    integrateWithEmoteSystem() {
        const originalPlayEmote = this.scene.emoteSystem.playEmote.bind(this.scene.emoteSystem);
        this.scene.emoteSystem.playEmote = (emoteKey, avatar) => {
            originalPlayEmote(emoteKey, avatar);

            // Play corresponding animation
            if (this.animations.has(emoteKey)) {
                this.playAnimation(avatar, emoteKey, { blend: true });
            }
        };
    }

    integrateWithVoiceSystem() {
        // Lip sync with voice chat
        if (this.scene.voiceChat) {
            this.setupVoiceDrivenLipSync();
        }
    }

    setupVoiceDrivenLipSync() {
        // This would connect to voice analysis for real lip sync
        // For now, simulate with random viseme changes
        setInterval(() => {
            if (Math.random() < 0.3) {
                const visemes = Array.from(this.lipSync.visemes.keys());
                const randomViseme = visemes[Math.floor(Math.random() * visemes.length)];
                this.setViseme(randomViseme, Math.random());
            }
        }, 200);
    }

    setViseme(viseme, intensity) {
        this.lipSync.visemes.forEach((v, name) => {
            v.target = name === viseme ? intensity : 0;
        });
    }

    integrateWithRelationshipSystem() {
        // Adjust animations based on relationship status
        // More intimate animations for closer relationships
    }

    // Utility methods
    createAnimationClip(name, tracks, duration, loop = true) {
        return {
            name,
            tracks,
            duration,
            loop
        };
    }

    addAnimation(avatar, clip) {
        if (!this.animationMixers.has(avatar)) {
            this.animationMixers.set(avatar, new THREE.AnimationMixer(avatar.mesh));
        }

        const mixer = this.animationMixers.get(avatar);
        const clipAction = mixer.clipAction(clip);
        clipAction.play();

        return clipAction;
    }

    // Cleanup
    cleanup() {
        this.animationMixers.forEach(mixer => {
            mixer.stopAllAction();
        });
        this.animationMixers.clear();
    }
}

Step 4: Updated DatingScene Class - Sensory Integration 🎵♿💫

// Updated DatingScene class for Part 8

class DatingScene {
    constructor() {
        // ... existing properties ...

        // New properties for Part 8
        this.audioSystem = null;
        this.accessibilitySystem = null;
        this.animationSystem = null;

        this.init();
    }

    init() {
        this.createScene();
        this.createCamera();
        this.createRenderer();
        this.createLights();
        this.createEnvironment();

        // Initialize all systems
        this.proximityChat = new ProximityChat(this);
        this.voiceChat = new VoiceChatSystem(this);
        this.emoteSystem = new EmoteSystem(this);
        this.interactiveEnv = new InteractiveEnvironment(this);
        this.dayNightCycle = new DayNightCycle(this);
        this.weatherSystem = new WeatherSystem(this, this.dayNightCycle);
        this.miniGameSystem = new MiniGameSystem(this);
        this.relationshipSystem = new RelationshipSystem(this);
        this.performanceOptimizer = new PerformanceOptimizer(this);
        this.mobileOptimizer = new MobileOptimizer(this);
        this.socialSystem = new SocialSystem(this);
        this.customizationSystem = new CustomizationSystem(this);

        // Initialize new Part 8 systems
        this.audioSystem = new AudioSystem(this);
        this.accessibilitySystem = new AccessibilitySystem(this);
        this.animationSystem = new AdvancedAnimationSystem(this);

        this.setupEnhancedIntegrations();
        this.setupSensoryUI();
        this.setupKeyboardListeners();
        this.animate();

        setTimeout(() => {
            document.getElementById('loadingScreen').style.display = 'none';
            document.getElementById('chatUI').style.display = 'block';
            this.addSampleAvatars();
            this.startAmbientExperience();
        }, 2000);
    }

    setupEnhancedIntegrations() {
        this.setupAudioIntegrations();
        this.setupAccessibilityIntegrations();
        this.setupAnimationIntegrations();
    }

    setupAudioIntegrations() {
        // Connect audio system with other systems
        if (this.audioSystem) {
            this.audioSystem.setupAudioIntegrations();
        }
    }

    setupAccessibilityIntegrations() {
        // Connect accessibility with other systems
        if (this.accessibilitySystem) {
            // Accessibility is already integrated through its constructor
        }
    }

    setupAnimationIntegrations() {
        // Connect animation system with other systems
        if (this.animationSystem) {
            this.animationSystem.setupAnimationIntegrations();
        }
    }

    setupSensoryUI() {
        this.createSensoryControls();
        this.setupAudioVisualFeedback();
    }

    createSensoryControls() {
        const sensoryControls = document.createElement('div');
        sensoryControls.style.cssText = `
            position: fixed;
            top: 100px;
            right: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 15px;
            z-index: 100;
            backdrop-filter: blur(10px);
            display: flex;
            flex-direction: column;
            gap: 10px;
        `;

        sensoryControls.innerHTML = `
            <button onclick="datingScene.toggleAudioPanel()" style="
                padding: 10px;
                background: #4ecdc4;
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                display: flex;
                align-items: center;
                gap: 8px;
            ">🎵 Audio</button>

            <button onclick="datingScene.toggleAccessibilityPanel()" style="
                padding: 10px;
                background: #9370db;
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                display: flex;
                align-items: center;
                gap: 8px;
            ">♿ Accessibility</button>

            <button onclick="datingScene.toggleAnimationDemo()" style="
                padding: 10px;
                background: #ff6b6b;
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                display: flex;
                align-items: center;
                gap: 8px;
            ">💫 Animations</button>
        `;

        document.getElementById('container').appendChild(sensoryControls);
    }

    toggleAudioPanel() {
        if (this.audioSystem.volumeMixer.style.display === 'block') {
            this.audioSystem.volumeMixer.style.display = 'none';
        } else {
            this.audioSystem.volumeMixer.style.display = 'block';
        }
    }

    toggleAccessibilityPanel() {
        this.accessibilitySystem.showMenu();
    }

    toggleAnimationDemo() {
        if (this.currentUser) {
            // Cycle through different animations
            const animations = ['wave', 'dance', 'laugh', 'shy'];
            const randomAnimation = animations[Math.floor(Math.random() * animations.length)];
            this.animationSystem.playAnimation(this.currentUser, randomAnimation, { blend: true });
        }
    }

    setupAudioVisualFeedback() {
        // Visual feedback for audio events
        if (this.audioSystem && this.accessibilitySystem.features.visualAlerts) {
            this.audioSystem.setupAudioVisualizer();
        }
    }

    startAmbientExperience() {
        // Start ambient sounds and animations
        if (this.audioSystem) {
            this.audioSystem.startEnvironmentSounds();
        }

        // Start idle animations for NPCs
        this.scene.avatars.forEach(avatar => {
            if (avatar !== this.currentUser) {
                this.animationSystem.playAnimation(avatar, 'idle');
            }
        });
    }

    createUserAvatar(config) {
        this.currentUser = new Avatar(config);
        this.currentUser.mesh.position.set(0, 0, 3);
        this.scene.add(this.currentUser.mesh);
        this.avatars.push(this.currentUser);

        // Initialize movement system
        this.currentUser.movement = new AvatarMovement(this.currentUser, this);

        // Set up camera
        this.datingCamera = new DatingCamera(this.camera, this, this.currentUser);

        // Apply saved customizations
        this.applySavedCustomizations();

        // Start user animations
        this.animationSystem.playAnimation(this.currentUser, 'idle');

        console.log(`Welcome, ${config.name}! Ready to mingle! 💖`);

        document.getElementById('avatarCustomization').style.display = 'none';

        // Enhanced NPC greetings with audio and animations
        this.avatars.forEach(avatar => {
            if (avatar !== this.currentUser && avatar.ai) {
                this.greetNewUser(avatar);
            }
        });
    }

    greetNewUser(avatar) {
        const userPosition = this.currentUser.mesh.position.clone();
        avatar.lookAt(userPosition);

        setTimeout(() => {
            // Play wave animation with sound
            this.animationSystem.playAnimation(avatar, 'wave', { blend: true });
            this.audioSystem.playSound('wave');

            // Some NPCs might approach or send friend requests
            if (Math.random() < 0.4) {
                setTimeout(() => {
                    this.simulateEnhancedSocialInteraction(avatar);
                }, 3000);
            }
        }, 1000);
    }

    simulateEnhancedSocialInteraction(avatar) {
        // 30% chance to send friend request with sound
        if (Math.random() < 0.3) {
            this.audioSystem.playSound('notification');
            this.accessibilitySystem.showSubtitle(`${avatar.options.name} sent you a friend request!`);

            this.socialSystem.showSocialNotification(
                `${avatar.options.name} sent you a friend request!`,
                'info'
            );

            // Auto-accept after 5 seconds (for demo)
            setTimeout(() => {
                this.socialSystem.addFriend({
                    id: avatar.options.name.toLowerCase(),
                    name: avatar.options.name,
                    online: true,
                    inWorld: true,
                    friendshipLevel: 1,
                    lastSeen: Date.now()
                });
                this.audioSystem.playSound('success');
            }, 5000);
        }

        // 40% chance to start conversation with animation
        if (Math.random() < 0.4) {
            this.animationSystem.playAnimation(avatar, 'walk', { blend: true });
            setTimeout(() => {
                avatar.ai.approachAvatar(this.currentUser);
            }, 2000);
        }
    }

    animate() {
        const currentTime = performance.now();
        const deltaTime = (currentTime - this.lastTime) / 1000;
        this.lastTime = currentTime;

        requestAnimationFrame(() => this.animate());

        // Update all systems
        this.performanceOptimizer.updatePerformanceStats();
        if (this.datingCamera) this.datingCamera.update();
        if (this.proximityChat) this.proximityChat.update();
        if (this.interactiveEnv) this.interactiveEnv.update();
        if (this.dayNightCycle) this.dayNightCycle.update(deltaTime);
        if (this.weatherSystem) this.weatherSystem.update(deltaTime);
        if (this.mobileOptimizer) this.mobileOptimizer.update(deltaTime);
        if (this.animationSystem) this.animationSystem.update(deltaTime);

        // Update NPC AI
        this.avatars.forEach(avatar => {
            if (avatar.ai) {
                avatar.ai.update(deltaTime);
            }
        });

        // Update mini-games
        this.miniGameSystem.activeGames.forEach(game => {
            if (game.update) game.update(deltaTime);
        });

        this.renderer.render(this.scene, this.camera);
    }
}

// Global variables for new systems
let datingScene;
let datingCamera;
let emoteSystem;
let dayNightCycle;
let weatherSystem;
let miniGameSystem;
let relationshipSystem;
let mobileOptimizer;
let socialSystem;
let customizationSystem;
let audioSystem;
let accessibilitySystem;
let animationSystem;

window.addEventListener('DOMContentLoaded', () => {
    datingScene = new DatingScene();
    emoteSystem = datingScene.emoteSystem;
    dayNightCycle = datingScene.dayNightCycle;
    weatherSystem = datingScene.weatherSystem;
    miniGameSystem = datingScene.miniGameSystem;
    relationshipSystem = datingScene.relationshipSystem;
    mobileOptimizer = datingScene.mobileOptimizer;
    socialSystem = datingScene.socialSystem;
    customizationSystem = datingScene.customizationSystem;
    audioSystem = datingScene.audioSystem;
    accessibilitySystem = datingScene.accessibilitySystem;
    animationSystem = datingScene.animationSystem;

    console.log("Complete Sensory Dating Experience loaded! Audio, accessibility, and advanced animations activated! 🎵♿💫");

    setupAvatarInteraction();

    document.addEventListener('click', () => {
        document.getElementById('messageInput').focus();
    });
});

What We've Built in Part 8: 🎉

  1. Comprehensive Audio System:

    • Spatial audio with Web Audio API
    • Background music and ambient sounds
    • Sound effects for all interactions
    • Volume mixing and audio controls
    • Audio visualization
  2. Accessibility Features:

    • Screen reader support with ARIA labels
    • High contrast and color blindness modes
    • Reduced motion options
    • Keyboard navigation
    • Voice control integration
    • Visual alerts for sounds
    • Subtitle support
  3. Advanced Animation System:

    • Inverse kinematics for natural movement
    • Facial rigging and blend shapes
    • Physics-based animations
    • Procedural animations (breathing, micro-movements)
    • Motion blending between animations
    • Emotional and social animations
    • Real-time lip sync
  4. Integrated Sensory Experience:

    • Audio-visual synchronization
    • Accessibility-aware interactions
    • Enhanced social animations
    • Cross-system integrations

Key Features Explained: 🔑

Next Time in Part 9: 🚀

We'll add:

Current Project Status: Our 3D dating app is now a fully accessible, multi-sensory experience! With immersive audio, comprehensive accessibility, and lifelike animations, we've created a virtual world that's engaging and inclusive for everyone. Love should know no barriers! 💕


Fun Fact: Our accessibility features are so comprehensive that even if you're blindfolded, wearing noise-canceling headphones, and navigating with one hand tied behind your back, you could still probably find a date! Now THAT's accessible love! 😄

Ready for Part 9 where we'll make sure your virtual love stories are safely saved and protected?

Part 9: Data Persistence, Security & Advanced AI - Love That Lasts! 💾🔒🧠

Welcome back, digital love architect! Our dating world is now a sensory masterpiece, but it's about as permanent as a sandcastle at high tide. Time to add cloud saving, robust security, and AI that actually understands romance!

Step 1: Data Persistence System - Save Your Love Story! 💾

Let's create a comprehensive data management system with cloud saving, local caching, and cross-device synchronization:

// data-persistence.js - Because love stories are worth saving! 📚

class DataPersistenceSystem {
    constructor(scene) {
        this.scene = scene;
        this.userData = new Map();
        this.cloudEnabled = false;
        this.syncInterval = null;
        this.lastSync = null;
        this.conflictResolution = 'server'; // server, client, manual

        this.setupDataStructures();
        this.setupStorage();
        this.setupSyncSystem();
        this.setupDataUI();

        console.log("Data persistence system initialized! Your love is now eternal! 💾");
    }

    setupDataStructures() {
        // Define data schemas for different types of information
        this.schemas = {
            userProfile: {
                version: '1.0',
                fields: ['name', 'avatarData', 'preferences', 'relationships', 'achievements']
            },
            relationships: {
                version: '1.0',
                fields: ['friends', 'romanticPartners', 'interactions', 'compatibilityScores']
            },
            environment: {
                version: '1.0',
                fields: ['theme', 'customizations', 'favoriteSpots', 'visitedLocations']
            },
            progress: {
                version: '1.0',
                fields: ['level', 'experience', 'unlockedFeatures', 'completedEvents']
            }
        };

        // Initialize data containers
        this.userData.set('profile', this.createDefaultProfile());
        this.userData.set('relationships', new Map());
        this.userData.set('environment', new Map());
        this.userData.set('progress', this.createDefaultProgress());
        this.userData.set('settings', new Map());
    }

    createDefaultProfile() {
        return {
            name: 'New User',
            avatarData: {},
            preferences: {
                musicVolume: 0.8,
                sfxVolume: 0.7,
                theme: 'default',
                language: 'en',
                privacy: 'public'
            },
            relationships: {},
            achievements: [],
            joinDate: new Date().toISOString(),
            lastLogin: new Date().toISOString()
        };
    }

    createDefaultProgress() {
        return {
            level: 1,
            experience: 0,
            unlockedFeatures: ['basic_chat', 'emotes', 'movement'],
            completedEvents: [],
            playTime: 0,
            totalConnections: 0
        };
    }

    setupStorage() {
        this.storageAdapters = {
            local: new LocalStorageAdapter(),
            cloud: new CloudStorageAdapter(),
            cache: new CacheStorageAdapter()
        };

        this.setupDataValidation();
        this.setupBackupSystem();
    }

    setupDataValidation() {
        this.validators = new Map();

        // Add validators for each data type
        this.validators.set('userProfile', (data) => this.validateUserProfile(data));
        this.validators.set('relationships', (data) => this.validateRelationships(data));
        this.validators.set('environment', (data) => this.validateEnvironment(data));
        this.validators.set('progress', (data) => this.validateProgress(data));
    }

    validateUserProfile(profile) {
        const required = ['name', 'preferences', 'joinDate'];
        const missing = required.filter(field => !profile[field]);

        if (missing.length > 0) {
            throw new Error(`Missing required profile fields: ${missing.join(', ')}`);
        }

        if (profile.name.length < 2 || profile.name.length > 50) {
            throw new Error('Name must be between 2 and 50 characters');
        }

        return true;
    }

    setupSyncSystem() {
        this.syncManager = {
            pendingChanges: new Map(),
            syncQueue: [],
            isSyncing: false,
            retryCount: 0,
            maxRetries: 3
        };

        this.setupAutoSave();
        this.setupConflictDetection();
    }

    setupAutoSave() {
        // Auto-save every 2 minutes
        setInterval(() => {
            this.autoSave();
        }, 120000);

        // Also save when user leaves the page
        window.addEventListener('beforeunload', () => {
            this.quickSave();
        });

        // Save after important events
        this.setupEventBasedSaving();
    }

    setupEventBasedSaving() {
        const saveEvents = [
            'relationshipChanged',
            'avatarCustomized', 
            'preferenceUpdated',
            'achievementUnlocked',
            'levelUp'
        ];

        saveEvents.forEach(event => {
            this.scene.emitter?.on(event, () => {
                this.queueSave(event);
            });
        });
    }

    queueSave(context) {
        this.syncManager.pendingChanges.set(context, Date.now());

        // Debounced save - wait 5 seconds before actually saving
        clearTimeout(this.saveTimeout);
        this.saveTimeout = setTimeout(() => {
            this.saveUserData();
        }, 5000);
    }

    async saveUserData() {
        try {
            const saveData = this.prepareSaveData();

            // Validate data before saving
            if (!this.validateAllData(saveData)) {
                throw new Error('Data validation failed');
            }

            // Save to local storage first (always available)
            await this.storageAdapters.local.save('userData', saveData);

            // Then try cloud save if enabled
            if (this.cloudEnabled) {
                await this.cloudSave(saveData);
            }

            // Update cache
            await this.storageAdapters.cache.save('userData', saveData);

            this.lastSync = new Date();
            this.syncManager.pendingChanges.clear();

            this.showSaveNotification('Data saved successfully! 💾');

        } catch (error) {
            console.error('Save failed:', error);
            this.handleSaveError(error);
        }
    }

    prepareSaveData() {
        const saveData = {
            metadata: {
                version: '1.0',
                timestamp: new Date().toISOString(),
                userId: this.getUserId(),
                checksum: this.generateChecksum()
            },
            profile: this.userData.get('profile'),
            relationships: Object.fromEntries(this.userData.get('relationships')),
            environment: Object.fromEntries(this.userData.get('environment')),
            progress: this.userData.get('progress'),
            settings: Object.fromEntries(this.userData.get('settings'))
        };

        // Compress large data
        return this.compressData(saveData);
    }

    compressData(data) {
        // Simple compression for demo - in production, use proper compression
        return {
            ...data,
            compressed: true,
            size: JSON.stringify(data).length
        };
    }

    async cloudSave(saveData) {
        if (!this.cloudEnabled) return;

        try {
            const response = await fetch('/api/user/save', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${this.getAuthToken()}`
                },
                body: JSON.stringify(saveData)
            });

            if (!response.ok) {
                throw new Error(`Cloud save failed: ${response.statusText}`);
            }

            const result = await response.json();
            return result;

        } catch (error) {
            console.warn('Cloud save failed, will retry later:', error);
            this.queueCloudRetry(saveData);
        }
    }

    queueCloudRetry(saveData) {
        this.syncManager.syncQueue.push({
            data: saveData,
            timestamp: Date.now(),
            retries: 0
        });

        this.processSyncQueue();
    }

    async processSyncQueue() {
        if (this.syncManager.isSyncing || this.syncManager.syncQueue.length === 0) {
            return;
        }

        this.syncManager.isSyncing = true;

        while (this.syncManager.syncQueue.length > 0) {
            const item = this.syncManager.syncQueue[0];

            try {
                await this.cloudSave(item.data);
                this.syncManager.syncQueue.shift(); // Remove successful item
            } catch (error) {
                item.retries++;

                if (item.retries >= this.syncManager.maxRetries) {
                    // Give up and remove from queue
                    this.syncManager.syncQueue.shift();
                    this.handleSyncFailure(item, error);
                } else {
                    // Wait before retry (exponential backoff)
                    const delay = Math.pow(2, item.retries) * 1000;
                    setTimeout(() => this.processSyncQueue(), delay);
                    break;
                }
            }
        }

        this.syncManager.isSyncing = false;
    }

    async loadUserData() {
        try {
            // Try cache first (fastest)
            let data = await this.storageAdapters.cache.load('userData');

            if (!data) {
                // Try local storage
                data = await this.storageAdapters.local.load('userData');
            }

            if (!data && this.cloudEnabled) {
                // Finally try cloud
                data = await this.cloudLoad();
            }

            if (data) {
                await this.applyLoadedData(data);
                this.showSaveNotification('Data loaded successfully! 📂');
            } else {
                // First time user - create default data
                this.initializeNewUser();
            }

        } catch (error) {
            console.error('Load failed:', error);
            this.handleLoadError(error);
        }
    }

    async cloudLoad() {
        try {
            const response = await fetch('/api/user/load', {
                headers: {
                    'Authorization': `Bearer ${this.getAuthToken()}`
                }
            });

            if (!response.ok) {
                throw new Error(`Cloud load failed: ${response.statusText}`);
            }

            return await response.json();

        } catch (error) {
            console.warn('Cloud load failed:', error);
            return null;
        }
    }

    async applyLoadedData(data) {
        // Validate data integrity
        if (!this.validateLoadedData(data)) {
            throw new Error('Loaded data failed validation');
        }

        // Check for data conflicts
        await this.resolveDataConflicts(data);

        // Apply data to system
        if (data.profile) {
            this.userData.set('profile', data.profile);
            this.applyUserProfile(data.profile);
        }

        if (data.relationships) {
            this.userData.set('relationships', new Map(Object.entries(data.relationships)));
            this.applyRelationshipData(data.relationships);
        }

        if (data.environment) {
            this.userData.set('environment', new Map(Object.entries(data.environment)));
            this.applyEnvironmentData(data.environment);
        }

        if (data.progress) {
            this.userData.set('progress', data.progress);
            this.applyProgressData(data.progress);
        }

        if (data.settings) {
            this.userData.set('settings', new Map(Object.entries(data.settings)));
            this.applySettingsData(data.settings);
        }

        this.lastSync = new Date();
    }

    applyUserProfile(profile) {
        if (this.scene.currentUser && profile.avatarData) {
            this.scene.customizationSystem.applyAvatarCustomizations(
                this.scene.currentUser, 
                profile.avatarData
            );
        }

        if (profile.preferences) {
            this.applyUserPreferences(profile.preferences);
        }
    }

    applyUserPreferences(preferences) {
        // Apply audio settings
        if (this.scene.audioSystem && preferences.musicVolume !== undefined) {
            this.scene.audioSystem.setVolume('music', preferences.musicVolume);
        }

        // Apply theme
        if (this.scene.customizationSystem && preferences.theme) {
            this.scene.customizationSystem.applyEnvironmentTheme(preferences.theme);
        }

        // Apply accessibility settings
        if (this.scene.accessibilitySystem && preferences.accessibility) {
            Object.assign(this.scene.accessibilitySystem.features, preferences.accessibility);
            this.scene.accessibilitySystem.applyAccessibilityFeatures();
        }
    }

    setupConflictDetection() {
        this.conflictResolver = {
            detect: (localData, remoteData) => {
                const conflicts = [];

                // Check timestamp-based conflicts
                if (new Date(localData.metadata.timestamp) > new Date(remoteData.metadata.timestamp)) {
                    conflicts.push({
                        type: 'timestamp',
                        local: localData.metadata.timestamp,
                        remote: remoteData.metadata.timestamp,
                        resolution: 'client'
                    });
                }

                // Check checksum conflicts
                if (localData.metadata.checksum !== remoteData.metadata.checksum) {
                    conflicts.push({
                        type: 'checksum',
                        field: 'all',
                        resolution: 'manual'
                    });
                }

                return conflicts;
            },

            resolve: (conflicts, localData, remoteData) => {
                switch (this.conflictResolution) {
                    case 'server':
                        return remoteData;
                    case 'client':
                        return localData;
                    case 'manual':
                        return this.manualConflictResolution(conflicts, localData, remoteData);
                    default:
                        return localData;
                }
            }
        };
    }

    manualConflictResolution(conflicts, localData, remoteData) {
        // Show conflict resolution UI to user
        this.showConflictResolutionUI(conflicts, localData, remoteData);

        // For now, return local data as fallback
        return localData;
    }

    setupBackupSystem() {
        this.backupManager = {
            backups: new Map(),
            maxBackups: 10,
            autoBackup: true,

            createBackup: async (data, label = 'auto') => {
                const backup = {
                    data: JSON.parse(JSON.stringify(data)), // Deep clone
                    timestamp: new Date().toISOString(),
                    label: label,
                    version: '1.0'
                };

                this.backupManager.backups.set(backup.timestamp, backup);

                // Limit number of backups
                if (this.backupManager.backups.size > this.backupManager.maxBackups) {
                    const oldest = Array.from(this.backupManager.backups.keys()).sort()[0];
                    this.backupManager.backups.delete(oldest);
                }

                // Save backups to storage
                await this.storageAdapters.local.save('backups', 
                    Array.from(this.backupManager.backups.values()));
            },

            restoreBackup: async (timestamp) => {
                const backup = this.backupManager.backups.get(timestamp);
                if (backup) {
                    await this.applyLoadedData(backup.data);
                    this.showSaveNotification('Backup restored! 🔄');
                }
            }
        };

        // Load existing backups
        this.loadBackups();
    }

    async loadBackups() {
        try {
            const backups = await this.storageAdapters.local.load('backups') || [];
            backups.forEach(backup => {
                this.backupManager.backups.set(backup.timestamp, backup);
            });
        } catch (error) {
            console.warn('Failed to load backups:', error);
        }
    }

    setupDataUI() {
        this.createDataManagementUI();
        this.createSyncStatusIndicator();
    }

    createDataManagementUI() {
        this.dataManagementUI = document.createElement('div');
        this.dataManagementUI.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1000;
            width: 90%;
            max-width: 500px;
            max-height: 80vh;
            overflow-y: auto;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            display: none;
        `;

        this.dataManagementUI.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; color: #ff6b6b;">💾 Data Management</h3>
                <button onclick="dataPersistence.hideDataUI()" style="
                    background: none;
                    border: none;
                    font-size: 20px;
                    cursor: pointer;
                ">✕</button>
            </div>

            <div style="display: grid; gap: 20px;">
                <!-- Sync Status -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">🔄 Sync Status</h4>
                    <div id="syncStatus" style="
                        padding: 10px;
                        background: #f8f9fa;
                        border-radius: 8px;
                        font-size: 14px;
                    ">
                        Loading...
                    </div>
                </div>

                <!-- Storage Info -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">💽 Storage</h4>
                    <div id="storageInfo" style="
                        padding: 10px;
                        background: #f8f9fa;
                        border-radius: 8px;
                        font-size: 14px;
                    ">
                        Calculating...
                    </div>
                </div>

                <!-- Backup Management -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">📦 Backups</h4>
                    <div style="display: grid; gap: 10px;">
                        <button onclick="dataPersistence.createManualBackup()" style="
                            padding: 10px;
                            background: #4ecdc4;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">Create Backup</button>
                        <div id="backupList" style="
                            max-height: 150px;
                            overflow-y: auto;
                            background: #f8f9fa;
                            border-radius: 8px;
                            padding: 10px;
                        "></div>
                    </div>
                </div>

                <!-- Data Actions -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">⚡ Actions</h4>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                        <button onclick="dataPersistence.forceSave()" style="
                            padding: 10px;
                            background: #ffd166;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">Save Now</button>
                        <button onclick="dataPersistence.exportData()" style="
                            padding: 10px;
                            background: #9370db;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">Export Data</button>
                        <button onclick="dataPersistence.showImportDialog()" style="
                            padding: 10px;
                            background: #4ecdc4;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">Import Data</button>
                        <button onclick="dataPersistence.showResetDialog()" style="
                            padding: 10px;
                            background: #ff6b6b;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">Reset Data</button>
                    </div>
                </div>
            </div>
        `;

        document.getElementById('container').appendChild(this.dataManagementUI);
    }

    createSyncStatusIndicator() {
        this.syncIndicator = document.createElement('div');
        this.syncIndicator.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 8px 12px;
            border-radius: 15px;
            font-size: 12px;
            z-index: 100;
            backdrop-filter: blur(10px);
            display: flex;
            align-items: center;
            gap: 8px;
            cursor: pointer;
        `;

        this.syncIndicator.innerHTML = `
            <span id="syncIcon">🔄</span>
            <span id="syncText">Synced</span>
        `;

        this.syncIndicator.addEventListener('click', () => {
            this.showDataUI();
        });

        document.getElementById('container').appendChild(this.syncIndicator);
        this.updateSyncStatus();
    }

    updateSyncStatus() {
        const icon = this.syncIndicator.querySelector('#syncIcon');
        const text = this.syncIndicator.querySelector('#syncText');

        if (this.syncManager.isSyncing) {
            icon.textContent = '⏳';
            text.textContent = 'Syncing...';
            this.syncIndicator.style.background = 'rgba(255, 214, 102, 0.9)';
        } else if (this.syncManager.pendingChanges.size > 0) {
            icon.textContent = '💾';
            text.textContent = `${this.syncManager.pendingChanges.size} pending`;
            this.syncIndicator.style.background = 'rgba(255, 107, 107, 0.9)';
        } else {
            icon.textContent = '✅';
            text.textContent = this.lastSync ? 'Synced' : 'Ready';
            this.syncIndicator.style.background = 'rgba(78, 205, 196, 0.9)';
        }
    }

    showDataUI() {
        this.updateDataUI();
        this.dataManagementUI.style.display = 'block';
    }

    hideDataUI() {
        this.dataManagementUI.style.display = 'none';
    }

    updateDataUI() {
        this.updateSyncStatusDisplay();
        this.updateStorageInfo();
        this.updateBackupList();
    }

    updateSyncStatusDisplay() {
        const syncStatus = document.getElementById('syncStatus');
        if (!syncStatus) return;

        let statusHTML = '';

        if (this.lastSync) {
            statusHTML += `<div>Last Sync: ${new Date(this.lastSync).toLocaleString()}</div>`;
        }

        if (this.syncManager.pendingChanges.size > 0) {
            statusHTML += `<div style="color: #ff6b6b;">Pending: ${this.syncManager.pendingChanges.size} changes</div>`;
        }

        if (this.syncManager.syncQueue.length > 0) {
            statusHTML += `<div style="color: #ffd166;">Queued: ${this.syncManager.syncQueue.length} items</div>`;
        }

        statusHTML += `<div>Cloud: ${this.cloudEnabled ? '✅ Enabled' : '❌ Disabled'}</div>`;

        syncStatus.innerHTML = statusHTML;
    }

    updateStorageInfo() {
        const storageInfo = document.getElementById('storageInfo');
        if (!storageInfo) return;

        // Calculate approximate storage usage
        const dataSize = this.calculateDataSize();
        const storageLimit = 5 * 1024 * 1024; // 5MB limit for demo

        const usagePercent = (dataSize / storageLimit) * 100;
        const usageColor = usagePercent > 90 ? '#ff6b6b' : usagePercent > 70 ? '#ffd166' : '#4ecdc4';

        storageInfo.innerHTML = `
            <div>Used: ${this.formatBytes(dataSize)}</div>
            <div>Limit: ${this.formatBytes(storageLimit)}</div>
            <div style="margin-top: 5px;">
                <div style="width: 100%; background: #e9ecef; border-radius: 10px; overflow: hidden;">
                    <div style="width: ${usagePercent}%; background: ${usageColor}; height: 8px;"></div>
                </div>
            </div>
        `;
    }

    calculateDataSize() {
        const data = this.prepareSaveData();
        return new Blob([JSON.stringify(data)]).size;
    }

    formatBytes(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }

    updateBackupList() {
        const backupList = document.getElementById('backupList');
        if (!backupList) return;

        if (this.backupManager.backups.size === 0) {
            backupList.innerHTML = '<div style="color: #666; text-align: center;">No backups yet</div>';
            return;
        }

        const backups = Array.from(this.backupManager.backups.values())
            .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
            .slice(0, 5); // Show only 5 most recent

        backupList.innerHTML = backups.map(backup => `
            <div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid #eee;">
                <div>
                    <div style="font-size: 12px; font-weight: bold;">${backup.label}</div>
                    <div style="font-size: 10px; color: #666;">${new Date(backup.timestamp).toLocaleString()}</div>
                </div>
                <button onclick="dataPersistence.restoreBackup('${backup.timestamp}')" style="
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 5px;
                    padding: 3px 8px;
                    font-size: 10px;
                    cursor: pointer;
                ">Restore</button>
            </div>
        `).join('');
    }

    // Public API methods
    async saveSetting(key, value) {
        this.userData.get('settings').set(key, value);
        await this.queueSave('settingUpdated');
    }

    async getSetting(key, defaultValue = null) {
        return this.userData.get('settings').get(key) || defaultValue;
    }

    async updateProgress(updates) {
        Object.assign(this.userData.get('progress'), updates);
        await this.queueSave('progressUpdated');
    }

    async addRelationship(relationship) {
        const relationships = this.userData.get('relationships');
        relationships.set(relationship.id, relationship);
        await this.queueSave('relationshipAdded');
    }

    // Utility methods
    getUserId() {
        // In production, this would come from authentication
        return localStorage.getItem('userId') || 'anonymous_' + Math.random().toString(36).substr(2, 9);
    }

    getAuthToken() {
        // In production, this would come from secure storage
        return localStorage.getItem('authToken');
    }

    generateChecksum(data) {
        // Simple checksum for demo - in production, use proper hash
        const str = JSON.stringify(data);
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // Convert to 32bit integer
        }
        return hash.toString(36);
    }

    showSaveNotification(message) {
        if (this.scene.socialSystem) {
            this.scene.socialSystem.showSocialNotification(message, 'info');
        }
    }

    // Error handling
    handleSaveError(error) {
        console.error('Save error:', error);
        this.showSaveNotification('Save failed! Data may be lost. 😢');

        // Try to create emergency backup
        this.createEmergencyBackup();
    }

    handleLoadError(error) {
        console.error('Load error:', error);
        this.showSaveNotification('Load failed! Using default data. 🔄');

        // Try to restore from backup
        this.tryBackupRestore();
    }

    handleSyncFailure(item, error) {
        console.error('Sync failed after retries:', error);
        this.showSaveNotification('Cloud sync failed! Data saved locally. ☁️');
    }

    createEmergencyBackup() {
        try {
            const data = this.prepareSaveData();
            this.backupManager.createBackup(data, 'emergency');
        } catch (error) {
            console.error('Emergency backup failed:', error);
        }
    }

    async tryBackupRestore() {
        try {
            const backups = await this.storageAdapters.local.load('backups');
            if (backups && backups.length > 0) {
                const latestBackup = backups[backups.length - 1];
                await this.applyLoadedData(latestBackup.data);
                this.showSaveNotification('Restored from backup! 🔄');
            }
        } catch (error) {
            console.error('Backup restore failed:', error);
        }
    }

    // UI action handlers
    async createManualBackup() {
        const data = this.prepareSaveData();
        await this.backupManager.createBackup(data, 'manual');
        this.updateBackupList();
        this.showSaveNotification('Manual backup created! 📦');
    }

    async restoreBackup(timestamp) {
        if (confirm('Are you sure you want to restore this backup? Current data will be replaced.')) {
            await this.backupManager.restoreBackup(timestamp);
            this.hideDataUI();
        }
    }

    async forceSave() {
        await this.saveUserData();
    }

    exportData() {
        const data = this.prepareSaveData();
        const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = `dating-app-backup-${new Date().toISOString().split('T')[0]}.json`;
        a.click();

        URL.revokeObjectURL(url);
        this.showSaveNotification('Data exported! 📤');
    }

    showImportDialog() {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json';

        input.onchange = async (e) => {
            const file = e.target.files[0];
            if (file) {
                try {
                    const text = await file.text();
                    const data = JSON.parse(text);

                    if (confirm('Import this data? Current data will be replaced.')) {
                        await this.applyLoadedData(data);
                        this.showSaveNotification('Data imported successfully! 📥');
                    }
                } catch (error) {
                    alert('Invalid data file!');
                }
            }
        };

        input.click();
    }

    showResetDialog() {
        if (confirm('Are you sure you want to reset all data? This cannot be undone!')) {
            this.resetAllData();
        }
    }

    resetAllData() {
        this.userData.clear();
        this.setupDataStructures();
        this.saveUserData();
        this.showSaveNotification('Data reset! All progress cleared. 🔄');
        this.hideDataUI();

        // Reload the scene to apply reset
        setTimeout(() => location.reload(), 1000);
    }
}

// Storage Adapters
class LocalStorageAdapter {
    async save(key, data) {
        try {
            localStorage.setItem(key, JSON.stringify(data));
            return true;
        } catch (error) {
            if (error.name === 'QuotaExceededError') {
                throw new Error('Local storage full! Please free up space.');
            }
            throw error;
        }
    }

    async load(key) {
        try {
            const data = localStorage.getItem(key);
            return data ? JSON.parse(data) : null;
        } catch (error) {
            console.warn('Local storage load failed:', error);
            return null;
        }
    }

    async remove(key) {
        try {
            localStorage.removeItem(key);
            return true;
        } catch (error) {
            console.warn('Local storage remove failed:', error);
            return false;
        }
    }
}

class CloudStorageAdapter {
    async save(key, data) {
        // Implementation would connect to actual cloud service
        // For demo, simulate cloud save
        return new Promise((resolve) => {
            setTimeout(() => resolve({ success: true }), 500);
        });
    }

    async load(key) {
        // Implementation would connect to actual cloud service
        // For demo, simulate cloud load
        return new Promise((resolve) => {
            setTimeout(() => resolve(null), 500); // Return null for demo
        });
    }
}

class CacheStorageAdapter {
    constructor() {
        this.cache = new Map();
    }

    async save(key, data) {
        this.cache.set(key, {
            data: data,
            timestamp: Date.now(),
            ttl: 24 * 60 * 60 * 1000 // 24 hours
        });
        return true;
    }

    async load(key) {
        const item = this.cache.get(key);
        if (item && Date.now() - item.timestamp < item.ttl) {
            return item.data;
        }
        return null;
    }

    async remove(key) {
        this.cache.delete(key);
        return true;
    }
}

Step 2: Security & Privacy System - Safe Love is Good Love! 🔒

Let's create a comprehensive security and privacy system to protect users:

// security-privacy.js - Because your heart deserves protection! 🛡️

class SecurityPrivacySystem {
    constructor(scene) {
        this.scene = scene;
        this.securityLevel = 'standard'; // standard, enhanced, maximum
        this.privacySettings = new Map();
        this.moderationTools = new Map();
        this.encryptionEnabled = true;

        this.setupSecurityFramework();
        this.setupPrivacyControls();
        this.setupModerationSystem();
        this.setupSecurityUI();

        console.log("Security & privacy system initialized! Your love is now protected! 🛡️");
    }

    setupSecurityFramework() {
        this.security = {
            authentication: new AuthenticationManager(),
            encryption: new EncryptionManager(),
            monitoring: new SecurityMonitor(),
            incidentResponse: new IncidentResponseTeam()
        };

        this.setupDataProtection();
        this.setupAccessControls();
        this.setupThreatDetection();
    }

    setupDataProtection() {
        this.dataProtection = {
            encryptSensitiveData: true,
            dataRetention: {
                chats: '30d',
                voice: '7d',
                location: '1h',
                analytics: '1y'
            },
            anonymization: {
                enabled: true,
                level: 'medium'
            }
        };

        this.setupGDPRCompliance();
        this.setupDataMinimization();
    }

    setupGDPRCompliance() {
        this.gdpr = {
            userConsent: new Map(),
            dataProcessing: {
                purposes: new Map(),
                legalBasis: new Map()
            },
            userRights: {
                access: true,
                rectification: true,
                erasure: true,
                restriction: true,
                portability: true,
                objection: true
            }
        };

        this.setupConsentManagement();
        this.setupDataSubjectRequests();
    }

    setupConsentManagement() {
        this.consentManager = {
            requiredConsents: [
                'essential',
                'analytics',
                'marketing',
                'third_party'
            ],
            grantedConsents: new Set(['essential']),

            requestConsent: (type) => {
                return this.showConsentDialog(type);
            },

            hasConsent: (type) => {
                return this.grantedConsents.has(type);
            },

            revokeConsent: (type) => {
                this.grantedConsents.delete(type);
                this.applyConsentChanges();
            }
        };
    }

    setupPrivacyControls() {
        this.privacySettings.set('profileVisibility', 'friends');
        this.privacySettings.set('locationSharing', 'proximity');
        this.privacySettings.set('chatHistory', 'enabled');
        this.privacySettings.set('dataCollection', 'minimal');
        this.privacySettings.set('thirdPartySharing', 'disabled');

        this.setupPrivacyUI();
        this.loadPrivacySettings();
    }

    setupModerationSystem() {
        this.moderation = {
            automated: new AutomatedModeration(),
            manual: new ManualModeration(),
            reporting: new ReportingSystem(),
            blockList: new BlockListManager()
        };

        this.setupContentFiltering();
        this.setupUserReporting();
        this.setupSafetyFeatures();
    }

    setupSecurityUI() {
        this.createSecurityDashboard();
        this.createPrivacyControls();
        this.createSafetyIndicators();
    }

    createSecurityDashboard() {
        this.securityDashboard = document.createElement('div');
        this.securityDashboard.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1000;
            width: 90%;
            max-width: 600px;
            max-height: 80vh;
            overflow-y: auto;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            display: none;
        `;

        this.securityDashboard.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; color: #ff6b6b;">🛡️ Security & Privacy</h3>
                <button onclick="securitySystem.hideDashboard()" style="
                    background: none;
                    border: none;
                    font-size: 20px;
                    cursor: pointer;
                ">✕</button>
            </div>

            <div style="display: grid; gap: 20px;">
                <!-- Security Status -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">🔐 Security Status</h4>
                    <div id="securityStatus" style="
                        padding: 15px;
                        background: #f8f9fa;
                        border-radius: 10px;
                        display: grid;
                        grid-template-columns: 1fr 1fr;
                        gap: 10px;
                    ">
                        <div>Encryption: <span id="encryptionStatus">✅ Enabled</span></div>
                        <div>2FA: <span id="2faStatus">❌ Disabled</span></div>
                        <div>Last Login: <span id="lastLogin">Unknown</span></div>
                        <div>Active Sessions: <span id="activeSessions">1</span></div>
                    </div>
                </div>

                <!-- Privacy Settings -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">👁️ Privacy Settings</h4>
                    <div style="display: grid; gap: 10px;">
                        ${this.generatePrivacyControls()}
                    </div>
                </div>

                <!-- Safety Tools -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">🛟 Safety Tools</h4>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                        <button onclick="securitySystem.showBlockList()" style="
                            padding: 10px;
                            background: #ff6b6b;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">🚫 Block List</button>
                        <button onclick="securitySystem.showReportHistory()" style="
                            padding: 10px;
                            background: #ffd166;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">📋 Reports</button>
                        <button onclick="securitySystem.emergencyExit()" style="
                            padding: 10px;
                            background: #dc3545;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">🚨 Emergency</button>
                        <button onclick="securitySystem.exportData()" style="
                            padding: 10px;
                            background: #4ecdc4;
                            color: white;
                            border: none;
                            border-radius: 8px;
                            cursor: pointer;
                        ">📤 Export Data</button>
                    </div>
                </div>

                <!-- Advanced Security -->
                <div>
                    <h4 style="margin: 0 0 10px 0;">⚙️ Advanced Security</h4>
                    <div style="display: grid; gap: 10px;">
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="enhancedEncryption" ${this.encryptionEnabled ? 'checked' : ''}>
                            <span>Enhanced Encryption</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="sessionTimeout">
                            <span>Auto Logout (15min)</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 10px;">
                            <input type="checkbox" id="loginAlerts">
                            <span>Login Notifications</span>
                        </label>
                    </div>
                </div>
            </div>

            <div style="margin-top: 20px; display: flex; justify-content: space-between;">
                <button onclick="securitySystem.applySecuritySettings()" style="
                    padding: 12px 20px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Apply Settings</button>
                <button onclick="securitySystem.showPrivacyPolicy()" style="
                    padding: 12px 20px;
                    background: #666;
                    color: white;
                    border: none;
                    border-radius: 10px;
                    cursor: pointer;
                ">Privacy Policy</button>
            </div>
        `;

        document.getElementById('container').appendChild(this.securityDashboard);
        this.setupSecurityEventListeners();
    }

    generatePrivacyControls() {
        const privacyOptions = {
            profileVisibility: {
                label: 'Profile Visibility',
                options: [
                    { value: 'public', label: '🌍 Everyone' },
                    { value: 'friends', label: '👥 Friends Only' },
                    { value: 'private', label: '🔒 Private' }
                ]
            },
            locationSharing: {
                label: 'Location Sharing',
                options: [
                    { value: 'exact', label: '📍 Exact Location' },
                    { value: 'proximity', label: '🏙️ General Area' },
                    { value: 'disabled', label: '🚫 Disabled' }
                ]
            },
            dataCollection: {
                label: 'Data Collection',
                options: [
                    { value: 'minimal', label: '📊 Minimal' },
                    { value: 'standard', label: '📈 Standard' },
                    { value: 'enhanced', label: '📱 Enhanced' }
                ]
            }
        };

        return Object.entries(privacyOptions).map(([key, config]) => `
            <div>
                <label style="display: block; margin-bottom: 5px; font-weight: bold;">${config.label}</label>
                <select id="${key}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 8px;">
                    ${config.options.map(opt => `
                        <option value="${opt.value}" ${this.privacySettings.get(key) === opt.value ? 'selected' : ''}>
                            ${opt.label}
                        </option>
                    `).join('')}
                </select>
            </div>
        `).join('');
    }

    setupSecurityEventListeners() {
        // Enhanced encryption toggle
        document.getElementById('enhancedEncryption')?.addEventListener('change', (e) => {
            this.encryptionEnabled = e.target.checked;
            this.updateSecurityStatus();
        });

        // Session timeout
        document.getElementById('sessionTimeout')?.addEventListener('change', (e) => {
            this.setupSessionTimeout(e.target.checked);
        });

        // Login alerts
        document.getElementById('loginAlerts')?.addEventListener('change', (e) => {
            this.setupLoginAlerts(e.target.checked);
        });
    }

    createPrivacyControls() {
        // Quick privacy toggle in main UI
        this.privacyToggle = document.createElement('button');
        this.privacyToggle.style.cssText = `
            position: fixed;
            bottom: 80px;
            right: 20px;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: rgba(255, 107, 107, 0.9);
            color: white;
            border: none;
            font-size: 18px;
            cursor: pointer;
            z-index: 100;
            box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
            backdrop-filter: blur(10px);
        `;
        this.privacyToggle.textContent = '🛡️';
        this.privacyToggle.title = 'Security & Privacy';

        this.privacyToggle.addEventListener('click', () => {
            this.showDashboard();
        });

        document.getElementById('container').appendChild(this.privacyToggle);
    }

    createSafetyIndicators() {
        // Safety indicators in chat and interactions
        this.setupChatSafety();
        this.setupProfileSafety();
        this.setupLocationSafety();
    }

    setupChatSafety() {
        // Monitor chat for inappropriate content
        const originalSendMessage = this.scene.proximityChat?.sendMessage;
        if (originalSendMessage) {
            this.scene.proximityChat.sendMessage = (message, avatar) => {
                // Check message safety before sending
                if (this.moderateMessage(message)) {
                    originalSendMessage(message, avatar);
                } else {
                    this.handleInappropriateMessage(message, avatar);
                }
            };
        }
    }

    moderateMessage(message) {
        const blockedPatterns = [
            /badword1/gi,
            /badword2/gi,
            /https?:\/\//gi, // Block links for demo
            /[0-9]{10,}/g // Block long number sequences (phone numbers)
        ];

        return !blockedPatterns.some(pattern => pattern.test(message));
    }

    handleInappropriateMessage(message, avatar) {
        // Notify user
        this.scene.socialSystem?.showSocialNotification(
            'Message blocked for safety reasons 🛡️',
            'warning'
        );

        // Log the incident
        this.logSecurityIncident('inappropriate_message', {
            avatar: avatar?.options.name,
            message: message,
            timestamp: new Date().toISOString()
        });

        // Auto-report repeated offenses
        this.checkForPattern(avatar, message);
    }

    setupProfileSafety() {
        // Monitor profile changes for inappropriate content
        this.setupProfileModeration();
    }

    setupLocationSafety() {
        // Ensure location sharing respects privacy settings
        this.setupLocationPrivacy();
    }

    // Security Management Methods
    showDashboard() {
        this.updateSecurityStatus();
        this.securityDashboard.style.display = 'block';
    }

    hideDashboard() {
        this.securityDashboard.style.display = 'none';
    }

    updateSecurityStatus() {
        const statusElements = {
            encryptionStatus: this.encryptionEnabled ? '✅ Enabled' : '❌ Disabled',
            '2faStatus': '❌ Disabled', // Would check actual 2FA status
            lastLogin: new Date().toLocaleString(),
            activeSessions: '1'
        };

        Object.entries(statusElements).forEach(([id, text]) => {
            const element = document.getElementById(id);
            if (element) element.textContent = text;
        });
    }

    applySecuritySettings() {
        // Apply privacy settings
        Object.keys(this.privacySettings).forEach(key => {
            const select = document.getElementById(key);
            if (select) {
                this.privacySettings.set(key, select.value);
            }
        });

        // Apply security settings
        this.applyEncryptionSettings();
        this.applySessionSettings();
        this.applyMonitoringSettings();

        this.saveSecuritySettings();
        this.hideDashboard();

        this.scene.socialSystem?.showSocialNotification(
            'Security settings applied! 🛡️',
            'success'
        );
    }

    applyEncryptionSettings() {
        if (this.encryptionEnabled) {
            this.enableEnhancedEncryption();
        } else {
            this.disableEnhancedEncryption();
        }
    }

    applySessionSettings() {
        const sessionTimeout = document.getElementById('sessionTimeout')?.checked;
        const loginAlerts = document.getElementById('loginAlerts')?.checked;

        if (sessionTimeout) {
            this.setupAutoLogout(15 * 60 * 1000); // 15 minutes
        }

        if (loginAlerts) {
            this.enableLoginAlerts();
        }
    }

    setupAutoLogout(timeout) {
        let inactivityTimer;

        const resetTimer = () => {
            clearTimeout(inactivityTimer);
            inactivityTimer = setTimeout(() => {
                this.autoLogout();
            }, timeout);
        };

        // Reset timer on user activity
        ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'].forEach(event => {
            document.addEventListener(event, resetTimer, false);
        });

        resetTimer();
    }

    autoLogout() {
        if (confirm('Session timeout. Would you like to stay logged in?')) {
            this.setupAutoLogout(15 * 60 * 1000);
        } else {
            this.performLogout();
        }
    }

    performLogout() {
        // Clear sensitive data
        this.clearSensitiveData();

        // Notify user
        this.scene.socialSystem?.showSocialNotification(
            'Logged out for security 🔒',
            'info'
        );

        // Reload to login screen
        setTimeout(() => location.reload(), 2000);
    }

    clearSensitiveData() {
        // Clear tokens and sensitive info
        localStorage.removeItem('authToken');
        localStorage.removeItem('userData');

        // Clear any cached sensitive data
        this.scene.dataPersistence?.storageAdapters.cache.cache.clear();
    }

    enableEnhancedEncryption() {
        // In production, this would set up stronger encryption
        console.log('Enhanced encryption enabled');
        this.encryptionEnabled = true;
    }

    disableEnhancedEncryption() {
        console.log('Enhanced encryption disabled');
        this.encryptionEnabled = false;
    }

    enableLoginAlerts() {
        // Setup login notification system
        console.log('Login alerts enabled');
    }

    // Privacy Management
    showPrivacyPolicy() {
        const policyContent = `
            <h3>Privacy Policy</h3>
            <div style="max-height: 300px; overflow-y: auto; text-align: left;">
                <h4>Data We Collect</h4>
                <ul>
                    <li>Profile information you provide</li>
                    <li>Messages and interactions</li>
                    <li>Technical data for service improvement</li>
                </ul>

                <h4>How We Use Your Data</h4>
                <ul>
                    <li>To provide and improve our service</li>
                    <li>To ensure safety and security</li>
                    <li>To personalize your experience</li>
                </ul>

                <h4>Your Rights</h4>
                <ul>
                    <li>Access your personal data</li>
                    <li>Correct inaccurate data</li>
                    <li>Delete your data</li>
                    <li>Object to data processing</li>
                </ul>

                <p><small>Last updated: ${new Date().toLocaleDateString()}</small></p>
            </div>
        `;

        this.showModal('Privacy Policy', policyContent);
    }

    showModal(title, content) {
        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            padding: 25px;
            border-radius: 20px;
            z-index: 1001;
            backdrop-filter: blur(20px);
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            max-width: 500px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
        `;

        modal.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 15px;">
                <h3 style="margin: 0; color: #ff6b6b;">${title}</h3>
                <button onclick="this.parentElement.parentElement.remove()" style="
                    background: none;
                    border: none;
                    font-size: 20px;
                    cursor: pointer;
                ">✕</button>
            </div>
            ${content}
            <button onclick="this.parentElement.remove()" style="
                width: 100%;
                padding: 12px;
                background: #4ecdc4;
                color: white;
                border: none;
                border-radius: 10px;
                cursor: pointer;
                margin-top: 15px;
            ">Close</button>
        `;

        document.getElementById('container').appendChild(modal);
    }

    // Safety Tools
    showBlockList() {
        const blockList = this.moderation.blockList.getList();
        const content = `
            <h4>Blocked Users</h4>
            ${blockList.length > 0 ? 
                blockList.map(user => `
                    <div style="display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee;">
                        <span>${user.name}</span>
                        <button onclick="securitySystem.unblockUser('${user.id}')" style="
                            background: #4ecdc4;
                            color: white;
                            border: none;
                            border-radius: 5px;
                            padding: 5px 10px;
                            cursor: pointer;
                        ">Unblock</button>
                    </div>
                `).join('') :
                '<p style="color: #666; text-align: center;">No blocked users</p>'
            }
        `;

        this.showModal('Block List', content);
    }

    showReportHistory() {
        const reports = this.moderation.reporting.getUserReports();
        const content = `
            <h4>Your Reports</h4>
            ${reports.length > 0 ? 
                reports.map(report => `
                    <div style="padding: 10px; border-bottom: 1px solid #eee;">
                        <div><strong>Type:</strong> ${report.type}</div>
                        <div><strong>Date:</strong> ${new Date(report.timestamp).toLocaleString()}</div>
                        <div><strong>Status:</strong> ${report.status}</div>
                    </div>
                `).join('') :
                '<p style="color: #666; text-align: center;">No reports submitted</p>'
            }
        `;

        this.showModal('Report History', content);
    }

    emergencyExit() {
        // Immediate safety measure
        if (confirm('Activate emergency exit? This will immediately hide the app.')) {
            this.activateEmergencyExit();
        }
    }

    activateEmergencyExit() {
        // Hide all UI elements
        document.querySelectorAll('div, canvas, button').forEach(el => {
            el.style.display = 'none';
        });

        // Show emergency screen
        document.body.style.background = '#000';
        document.body.innerHTML = `
            <div style="
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                color: white;
                text-align: center;
                font-family: Arial, sans-serif;
            ">
                <h1>Emergency Exit Activated</h1>
                <p>The app is now hidden.</p>
                <button onclick="location.reload()" style="
                    padding: 10px 20px;
                    background: #4ecdc4;
                    color: white;
                    border: none;
                    border-radius: 5px;
                    cursor: pointer;
                    margin: 10px;
                ">Restore App</button>
                <button onclick="window.close()" style="
                    padding: 10px 20px;
                    background: #ff6b6b;
                    color: white;
                    border: none;
                    border-radius: 5px;
                    cursor: pointer;
                    margin: 10px;
                ">Close Tab</button>
            </div>
        `;

        // Clear sensitive data
        this.clearSensitiveData();
    }

    // Data Export with Privacy
    exportData() {
        const data = this.scene.dataPersistence?.prepareSaveData();
        if (!data) return;

        // Apply privacy filters before export
        const filteredData = this.applyPrivacyFilters(data);

        const blob = new Blob([JSON.stringify(filteredData, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = `secure-export-${new Date().toISOString().split('T')[0]}.json`;
        a.click();

        URL.revokeObjectURL(url);

        this.scene.socialSystem?.showSocialNotification(
            'Data exported with privacy filters! 📤',
            'success'
        );
    }

    applyPrivacyFilters(data) {
        // Remove sensitive information based on settings
        const filtered = { ...data };

        if (this.privacySettings.get('profileVisibility') === 'private') {
            delete filtered.profile?.personalInfo;
        }

        if (this.privacySettings.get('locationSharing') === 'disabled') {
            delete filtered.environment?.locationData;
        }

        // Anonymize relationship data
        if (filtered.relationships) {
            Object.keys(filtered.relationships).forEach(key => {
                if (filtered.relationships[key]) {
                    filtered.relationships[key].partnerInfo = 'ANONYMIZED';
                }
            });
        }

        return filtered;
    }

    // Incident Management
    logSecurityIncident(type, details) {
        const incident = {
            type,
            details,
            timestamp: new Date().toISOString(),
            severity: this.assessSeverity(type),
            actionTaken: 'logged'
        };

        // Store incident
        const incidents = JSON.parse(localStorage.getItem('securityIncidents') || '[]');
        incidents.push(incident);
        localStorage.setItem('securityIncidents', JSON.stringify(incidents));

        // Auto-escalate based on severity
        if (incident.severity === 'high') {
            this.escalateIncident(incident);
        }
    }

    assessSeverity(type) {
        const severityMap = {
            'inappropriate_message': 'medium',
            'suspicious_login': 'high',
            'data_breach': 'critical',
            'harassment': 'high'
        };

        return severityMap[type] || 'low';
    }

    escalateIncident(incident) {
        // In production, this would notify admins or take automated action
        console.warn('HIGH SEVERITY INCIDENT:', incident);

        // For demo, just show a warning
        this.scene.socialSystem?.showSocialNotification(
            'Security incident detected! 🚨',
            'error'
        );
    }

    // Settings Persistence
    saveSecuritySettings() {
        const settings = {
            privacy: Object.fromEntries(this.privacySettings),
            security: {
                encryptionEnabled: this.encryptionEnabled,
                securityLevel: this.securityLevel
            }
        };

        localStorage.setItem('securitySettings', JSON.stringify(settings));
    }

    loadPrivacySettings() {
        try {
            const saved = localStorage.getItem('securitySettings');
            if (saved) {
                const settings = JSON.parse(saved);
                Object.entries(settings.privacy).forEach(([key, value]) => {
                    this.privacySettings.set(key, value);
                });

                this.encryptionEnabled = settings.security.encryptionEnabled;
                this.securityLevel = settings.security.securityLevel;
            }
        } catch (error) {
            console.warn('Error loading security settings:', error);
        }
    }
}

// Security Subsystems
class AuthenticationManager {
    constructor() {
        this.sessions = new Map();
        this.failedAttempts = new Map();
    }

    async authenticate(userId, credentials) {
        // Implementation would verify credentials
        return { success: true, token: 'demo-token' };
    }

    validateSession(token) {
        return this.sessions.has(token);
    }

    logout(token) {
        this.sessions.delete(token);
    }
}

class EncryptionManager {
    constructor() {
        this.algorithm = 'AES-GCM';
    }

    async encrypt(data, key) {
        // In production, this would use proper encryption
        return btoa(JSON.stringify(data)); // Base64 for demo
    }

    async decrypt(encryptedData, key) {
        try {
            return JSON.parse(atob(encryptedData));
        } catch {
            throw new Error('Decryption failed');
        }
    }
}

class SecurityMonitor {
    constructor() {
        this.suspiciousPatterns = new Map();
        this.monitoringEnabled = true;
    }

    monitorActivity(activity) {
        if (!this.monitoringEnabled) return;

        // Check for suspicious patterns
        if (this.isSuspicious(activity)) {
            this.flagSuspiciousActivity(activity);
        }
    }

    isSuspicious(activity) {
        // Simple pattern matching for demo
        const patterns = [
            /spam/gi,
            /http/gi,
            /[0-9]{10,}/g
        ];

        return patterns.some(pattern => 
            pattern.test(JSON.stringify(activity))
        );
    }
}

class IncidentResponseTeam {
    constructor() {
        this.incidents = new Map();
    }

    handleIncident(incident) {
        this.incidents.set(incident.id, incident);
        this.notifyStakeholders(incident);
        this.takeRemedialAction(incident);
    }
}

class AutomatedModeration {
    constructor() {
        this.filters = new Map();
        this.setupContentFilters();
    }

    setupContentFilters() {
        this.filters.set('profanity', /badword1|badword2/gi);
        this.filters.set('personal_info', /[0-9]{10,}|@gmail|@yahoo/gi);
    }

    moderateContent(content) {
        let cleanContent = content;

        this.filters.forEach((pattern, type) => {
            cleanContent = cleanContent.replace(pattern, '[REDACTED]');
        });

        return {
            clean: cleanContent,
            wasModified: cleanContent !== content
        };
    }
}

class ManualModeration {
    constructor() {
        this.pendingReviews = new Map();
    }

    queueForReview(content, context) {
        const review = {
            id: Math.random().toString(36),
            content,
            context,
            timestamp: new Date(),
            status: 'pending'
        };

        this.pendingReviews.set(review.id, review);
        return review.id;
    }
}

class ReportingSystem {
    constructor() {
        this.reports = new Map();
    }

    submitReport(report) {
        const reportId = Math.random().toString(36);
        this.reports.set(reportId, {
            ...report,
            id: reportId,
            timestamp: new Date(),
            status: 'submitted'
        });

        return reportId;
    }

    getUserReports() {
        return Array.from(this.reports.values());
    }
}

class BlockListManager {
    constructor() {
        this.blockedUsers = new Map();
    }

    blockUser(userId, reason = '') {
        this.blockedUsers.set(userId, {
            id: userId,
            reason,
            timestamp: new Date(),
            expires: null // Permanent block
        });
    }

    unblockUser(userId) {
        this.blockedUsers.delete(userId);
    }

    isBlocked(userId) {
        return this.blockedUsers.has(userId);
    }

    getList() {
        return Array.from(this.blockedUsers.values());
    }
}

Step 3: Advanced AI System - Love That Understands! 🧠

Let's create an AI system with machine learning for better matchmaking and intelligent NPC behavior:


// advanced-ai.js - Because love should be smart! 🧠

class AdvancedAISystem {
    constructor(scene) {
        this.scene = scene;
        this.mlModels = new Map();
        this.recommendationEngine = null;
        this.sentimentAnalyzer = null;
        this.behaviorPredictor = null;

        this.setupAIFramework();
        this.trainInitialModels();
        this.setupAIFeatures();

        console.log("Advanced AI system initialized! Love just got smarter! 🧠");
    }

    setupAIFramework() {
        this.ai = {
            matchmaking: new MatchmakingAI(),
            conversation: new ConversationAI(),
            behavior: new BehaviorAI(),
            analytics: new AnalyticsAI()
        };

        this.setupMachineLearning();
        this.setupNeuralNetworks();
        this.setupAIAnalytics();
    }

    setupMachineLearning() {
        this.ml = {
            trainingData: new Map(),
            models: new Map(),
            features: new Map(),

            trainModel: async (modelName, data) => {
                return this.trainModelInternal(modelName, data);
            },

            predict: (modelName, input) => {
                return this.makePrediction(modelName, input);
            },

            updateModel: (modelName, newData) => {
                return this.retrainModel(modelName, newData);
            }
        };

        this.initializeMLModels();
    }

    initializeMLModels() {
        // Initialize core ML models
        this.mlModels.set('compatibility', this.createCompatibilityModel());
        this.mlModels.set('sentiment', this.createSentimentModel());
        this.mlModels.set('behavior', this.createBehaviorModel());
        this.mlModels.set('recommendation', this.createRecommendationModel());
    }

    createCompatibilityModel() {
        return {
            name: 'compatibility_predictor',
            version: '1.0',
            features: ['personality_traits', 'interests', 'values', 'communication_style'],
            predict: (user1, user2) => {
                // Simple compatibility algorithm for demo
                // In production, this would use trained ML model
                let score = 0.5; // Base score

                // Personality matching
                const personalityMatch = this.calculatePersonalityMatch(user1, user2);
                score += personalityMatch * 0.3;

                // Interest overlap
                const interestMatch = this.calculateInterestMatch(user1, user2);
                score += interestMatch * 0.3;

                // Value alignment
                const valueMatch = this.calculateValueMatch(user1, user2);
                score += valueMatch * 0.2;

                // Communication style compatibility
                const communicationMatch = this.calculateCommunicationMatch(user1, user2);
                score += communicationMatch * 0.2;

                return Math.min(1, Math.max(0, score));
            },
            train: (data) => {
                // Model training would happen here
                console.log('Training compatibility model with', data.length, 'samples');
            }
        };
    }

    calculatePersonalityMatch(user1, user2) {
        if (!user1.personality || !user2.personality) return 0.5;

        const traits1 = user1.personality.traits || [];
        const traits2 = user2.personality.traits || [];

        const commonTraits = traits1.filter(trait => traits2.includes(trait));
        const totalTraits = new Set([...traits1, ...traits2]).size;

        return totalTraits > 0 ? commonTraits.length / totalTraits : 0;
    }

    calculateInterestMatch(user1, user2) {
        const interests1 = user1.interests || [];
        const interests2 = user2.interests || [];

        const commonInterests = interests1.filter(interest => interests2.includes(interest));
        const totalInterests = new Set([...interests1, ...interests2]).size;

        return totalInterests > 0 ? commonInterests.length / totalInterests : 0;
    }

    calculateValueMatch(user1, user2) {
        // Simple value matching based on predefined value categories
        const values1 = user1.values || [];
        const values2 = user2.values || [];

        const commonValues = values1.filter(value => values2.includes(value));
        const totalValues = new Set([...values1, ...values2]).size;

        return totalValues > 0 ? commonValues.length / totalValues : 0.5;
    }

    calculateCommunicationMatch(user1, user2) {
        // Match communication styles
        const style1 = user1.communicationStyle || 'balanced';
        const style2 = user2.communicationStyle || 'balanced';

        const styleCompatibility = {
            'direct': { 'direct': 0.9, 'balanced': 0.7, 'indirect': 0.3 },
            'balanced': { 'direct': 0.7, 'balanced': 1.0, 'indirect': 0.7 },
            'indirect': { 'direct': 0.3, 'balanced': 0.7, 'indirect': 0.9 }
        };

        return styleCompatibility[style1]?.[style2] || 0.5;
    }

    setupNeuralNetworks() {
        this.neuralNetworks = {
            recommendation: this.createRecommendationNetwork(),
            sentiment: this.createSentimentNetwork(),
            behavior: this.createBehaviorNetwork()
        };
    }

    createRecommendationNetwork() {
        // Simple neural network for recommendations
        return {
            layers: [
                { type: 'input', size: 10 },
                { type: 'hidden', size: 8, activation: 'relu' },
                { type: 'hidden', size: 6, activation: 'relu' },
                { type: 'output', size: 1, activation: 'sigmoid' }
            ],
            predict: (input) => {
                // Simplified forward propagation for demo
                let output = this.forwardPropagate(input, this.layers);
                return output[0];
            },
            train: (data) => {
                // Training would happen here
                console.log('Training recommendation network');
            }
        };
    }

    forwardPropagate(input, layers) {
        // Simplified forward propagation
        let current = input;

        for (const layer of layers) {
            if (layer.type === 'hidden' || layer.type === 'output') {
                current = this.applyActivation(current, layer.activation);
            }
        }

        return current;
    }

    applyActivation(values, activation) {
        switch (activation) {
            case 'relu':
                return values.map(v => Math.max(0, v));
            case 'sigmoid':
                return values.map(v => 1 / (1 + Math.exp(-v)));
            default:
                return values;
        }
    }

    trainInitialModels() {
        // Train models with initial data
        this.trainWithHistoricalData();
        this.setupContinuousLearning();
    }

    trainWithHistoricalData() {
        // Load and train with any available historical data
        const historicalData = this.loadHistoricalData();

        if (historicalData.length > 0) {
            this.mlModels.forEach((model, name) => {
                model.train(historicalData);
            });
        }
    }

    loadHistoricalData() {
        // In production, this would load from database
        // For demo, return empty array
        return [];
    }

    setupContinuousLearning() {
        // Set up real-time model updates
        this.setupLearningFromInteractions();
        this.setupFeedbackLoop();
    }

    setupLearningFromInteractions() {
        // Learn from user interactions
        const events = ['match', 'message', 'meetup', 'relationship_change'];

        events.forEach(event => {
            this.scene.emitter?.on(event, (data) => {
                this.learnFromInteraction(event, data);
            });
        });
    }
Back to ChameleonSoftwareOnline.com