3D chat in WebGL for a dating website

Part 1: Setting Up the WebGL Foundation for a 3D Chat Environment

Welcome to the first part of our 10-part tutorial series on creating a 3D chat environment for a dating website using plain WebGL! In this series, we'll build everything from scratch without relying on libraries like three.js. This approach will give you deep understanding of how 3D graphics work in the browser.

Table of Contents for Part 1

  1. Introduction to WebGL Basics
  2. Setting Up the HTML Structure
  3. Initializing the WebGL Context
  4. Creating Basic Shaders
  5. Setting Up a Simple 3D Scene
  6. Rendering Our First Cube

1. Introduction to WebGL Basics

WebGL (Web Graphics Library) is a JavaScript API for rendering interactive 2D and 3D graphics within any compatible web browser without the use of plug-ins. It's based on OpenGL ES 2.0 and provides a low-level, hardware-accelerated 3D graphics API.

For our dating website chat, we'll create an immersive 3D environment where users can interact in a virtual space. Let's start with the fundamentals.

2. Setting Up the HTML Structure

First, let's create the basic HTML structure that will host our WebGL canvas and chat interface:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Dating Chat - Part 1</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            font-family: Arial, sans-serif;
            background-color: #1a1a1a;
            color: #ffffff;
        }

        #container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }

        #webgl-canvas {
            display: block;
            width: 100%;
            height: 100%;
        }

        #chat-ui {
            position: absolute;
            bottom: 20px;
            left: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            border-radius: 10px;
            padding: 15px;
            backdrop-filter: blur(10px);
        }

        #message-input {
            width: 100%;
            padding: 10px;
            border: none;
            border-radius: 5px;
            background: rgba(255, 255, 255, 0.1);
            color: white;
        }
    </style>
</head>
<body>
    <div id="container">
        <canvas id="webgl-canvas"></canvas>
        <div id="chat-ui">
            <input type="text" id="message-input" placeholder="Type your message...">
        </div>
    </div>

    <script>
        // Our WebGL code will go here
    </script>
</body>
</html>

3. Initializing the WebGL Context

Now let's initialize our WebGL context and set up the basic rendering environment:

// Main application class
class DatingChat3D {
    constructor() {
        this.canvas = null;
        this.gl = null;
        this.program = null;

        // Scene properties
        this.cubeRotation = 0.0;
        this.aspectRatio = 1.0;

        this.init();
    }

    // Initialize the WebGL context and setup
    init() {
        this.setupWebGL();
        this.setupShaders();
        this.setupBuffers();
        this.setupScene();
        this.render();
    }

    // Set up WebGL context and canvas
    setupWebGL() {
        // Get the canvas element
        this.canvas = document.getElementById('webgl-canvas');

        if (!this.canvas) {
            console.error('Canvas element not found!');
            return;
        }

        // Try to get WebGL context, fall back to experimental if needed
        const gl = this.canvas.getContext('webgl') || 
                   this.canvas.getContext('experimental-webgl');

        if (!gl) {
            alert('WebGL is not supported by your browser!');
            return;
        }

        this.gl = gl;

        // Set canvas size to match display size
        this.resizeCanvas();
        window.addEventListener('resize', () => this.resizeCanvas());

        // Set clear color to a romantic purple (for our dating theme)
        gl.clearColor(0.1, 0.1, 0.2, 1.0);

        // Enable depth testing for proper 3D rendering
        gl.enable(gl.DEPTH_TEST);
        gl.depthFunc(gl.LEQUAL);

        console.log('WebGL context initialized successfully');
    }

    // Resize canvas to match display size
    resizeCanvas() {
        const displayWidth = this.canvas.clientWidth;
        const displayHeight = this.canvas.clientHeight;

        // Check if canvas needs resizing
        if (this.canvas.width !== displayWidth || 
            this.canvas.height !== displayHeight) {

            this.canvas.width = displayWidth;
            this.canvas.height = displayHeight;
            this.aspectRatio = displayWidth / displayHeight;

            // Update viewport
            this.gl.viewport(0, 0, displayWidth, displayHeight);
        }
    }

    // We'll implement these in the next sections
    setupShaders() { /* Coming next */ }
    setupBuffers() { /* Coming next */ }
    setupScene() { /* Coming next */ }
    render() { /* Coming next */ }
}

// Start the application when the page loads
window.addEventListener('load', () => {
    new DatingChat3D();
});

4. Creating Basic Shaders

Shaders are essential for WebGL. They run on the GPU and determine how vertices and pixels are processed. Let's create our vertex and fragment shaders:

// Vertex shader source code
const vertexShaderSource = `
    // Attribute: per-vertex data (position)
    attribute vec4 aVertexPosition;
    attribute vec4 aVertexColor;

    // Uniform: global data (transformation matrices)
    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    // Varying: data passed to fragment shader
    varying lowp vec4 vColor;

    void main(void) {
        // Transform vertex position by model-view and projection matrices
        gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;

        // Pass color to fragment shader
        vColor = aVertexColor;
    }
`;

// Fragment shader source code
const fragmentShaderSource = `
    // Precision qualifier for float operations
    precision mediump float;

    // Color input from vertex shader
    varying lowp vec4 vColor;

    void main(void) {
        // Set fragment color to the interpolated color from vertices
        gl_FragColor = vColor;
    }
`;

// Add these methods to the DatingChat3D class:

setupShaders() {
    const gl = this.gl;

    // Create and compile vertex shader
    const vertexShader = this.compileShader(gl.VERTEX_SHADER, vertexShaderSource);

    // Create and compile fragment shader
    const fragmentShader = this.compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource);

    // Create shader program
    this.program = gl.createProgram();
    gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    gl.linkProgram(this.program);

    // Check if linking was successful
    if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
        console.error('Unable to initialize the shader program: ' + 
                     gl.getProgramInfoLog(this.program));
        return;
    }

    // Store attribute and uniform locations for later use
    this.attribLocations = {
        vertexPosition: gl.getAttribLocation(this.program, 'aVertexPosition'),
        vertexColor: gl.getAttribLocation(this.program, 'aVertexColor'),
    };

    this.uniformLocations = {
        projectionMatrix: gl.getUniformLocation(this.program, 'uProjectionMatrix'),
        modelViewMatrix: gl.getUniformLocation(this.program, 'uModelViewMatrix'),
    };

    console.log('Shaders compiled and linked successfully');
}

// Helper method to compile individual shaders
compileShader(type, source) {
    const gl = this.gl;
    const shader = gl.createShader(type);

    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    // Check compilation status
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('An error occurred compiling the shaders: ' + 
                     gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }

    return shader;
}

5. Setting Up a Simple 3D Scene

Now let's set up our 3D scene with perspective projection and create geometry buffers:

setupBuffers() {
    const gl = this.gl;

    // Define vertices for a cube (8 vertices, each with x, y, z)
    // We'll use a simple cube as our test object
    const vertices = new Float32Array([
        // Front face
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0,
         1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,

        // Back face
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0, -1.0, -1.0,

        // Top face
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,

        // Right face
         1.0, -1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0,  1.0,  1.0,
         1.0, -1.0,  1.0,

        // Left face
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
    ]);

    // Define colors for each vertex (RGBA)
    // Using romantic colors for our dating theme
    const colors = new Float32Array([
        // Front face (pink)
        1.0, 0.5, 0.8, 1.0,
        1.0, 0.5, 0.8, 1.0,
        1.0, 0.5, 0.8, 1.0,
        1.0, 0.5, 0.8, 1.0,

        // Back face (light purple)
        0.7, 0.5, 1.0, 1.0,
        0.7, 0.5, 1.0, 1.0,
        0.7, 0.5, 1.0, 1.0,
        0.7, 0.5, 1.0, 1.0,

        // Top face (light blue)
        0.5, 0.8, 1.0, 1.0,
        0.5, 0.8, 1.0, 1.0,
        0.5, 0.8, 1.0, 1.0,
        0.5, 0.8, 1.0, 1.0,

        // Bottom face (light green)
        0.5, 1.0, 0.8, 1.0,
        0.5, 1.0, 0.8, 1.0,
        0.5, 1.0, 0.8, 1.0,
        0.5, 1.0, 0.8, 1.0,

        // Right face (yellow)
        1.0, 1.0, 0.5, 1.0,
        1.0, 1.0, 0.5, 1.0,
        1.0, 1.0, 0.5, 1.0,
        1.0, 1.0, 0.5, 1.0,

        // Left face (orange)
        1.0, 0.7, 0.5, 1.0,
        1.0, 0.7, 0.5, 1.0,
        1.0, 0.7, 0.5, 1.0,
        1.0, 0.7, 0.5, 1.0,
    ]);

    // Define indices for drawing triangles
    const indices = new Uint16Array([
        0,  1,  2,      0,  2,  3,    // front
        4,  5,  6,      4,  6,  7,    // back
        8,  9,  10,     8,  10, 11,   // top
        12, 13, 14,     12, 14, 15,   // bottom
        16, 17, 18,     16, 18, 19,   // right
        20, 21, 22,     20, 22, 23,   // left
    ]);

    // Create and bind vertex buffer
    this.vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    // Create and bind color buffer
    this.colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);

    // Create and bind index buffer
    this.indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

    // Store the number of indices for rendering
    this.indicesCount = indices.length;

    console.log('Geometry buffers created successfully');
}

setupScene() {
    // Set up perspective projection matrix
    this.projectionMatrix = this.createProjectionMatrix();

    // Initial model-view matrix (camera position)
    this.modelViewMatrix = this.createModelViewMatrix();
}

// Create a perspective projection matrix
createProjectionMatrix() {
    const fieldOfView = 45 * Math.PI / 180;   // 45 degrees in radians
    const zNear = 0.1;                        // Near clipping plane
    const zFar = 100.0;                       // Far clipping plane

    const projectionMatrix = new Float32Array(16);
    const f = 1.0 / Math.tan(fieldOfView / 2);
    const rangeInv = 1.0 / (zNear - zFar);

    projectionMatrix[0] = f / this.aspectRatio;
    projectionMatrix[5] = f;
    projectionMatrix[10] = (zNear + zFar) * rangeInv;
    projectionMatrix[11] = -1;
    projectionMatrix[14] = 2 * zNear * zFar * rangeInv;
    projectionMatrix[15] = 0;

    return projectionMatrix;
}

// Create initial model-view matrix (camera at 0,0,-5 looking at origin)
createModelViewMatrix() {
    const modelViewMatrix = new Float32Array(16);

    // Identity matrix
    modelViewMatrix[0] = 1;
    modelViewMatrix[5] = 1;
    modelViewMatrix[10] = 1;
    modelViewMatrix[15] = 1;

    // Move camera back 5 units
    modelViewMatrix[14] = -5;

    return modelViewMatrix;
}

6. Rendering Our First Cube

Finally, let's implement the render loop to draw our rotating cube:

render() {
    const gl = this.gl;

    // Clear the canvas with our clear color
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Use our shader program
    gl.useProgram(this.program);

    // Set up vertex position attribute
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
    gl.vertexAttribPointer(
        this.attribLocations.vertexPosition,
        3,                  // 3 components per vertex (x, y, z)
        gl.FLOAT,           // data type
        false,              // normalize
        0,                  // stride
        0                   // offset
    );
    gl.enableVertexAttribArray(this.attribLocations.vertexPosition);

    // Set up vertex color attribute
    gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
    gl.vertexAttribPointer(
        this.attribLocations.vertexColor,
        4,                  // 4 components per color (r, g, b, a)
        gl.FLOAT,           // data type
        false,              // normalize
        0,                  // stride
        0                   // offset
    );
    gl.enableVertexAttribArray(this.attribLocations.vertexColor);

    // Update rotation for animation
    this.cubeRotation += 0.01;
    this.updateModelViewMatrix();

    // Set uniform matrices
    gl.uniformMatrix4fv(
        this.uniformLocations.projectionMatrix,
        false,
        this.projectionMatrix
    );
    gl.uniformMatrix4fv(
        this.uniformLocations.modelViewMatrix,
        false,
        this.modelViewMatrix
    );

    // Bind index buffer and draw
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
    gl.drawElements(
        gl.TRIANGLES,
        this.indicesCount,
        gl.UNSIGNED_SHORT,
        0
    );

    // Request next frame
    requestAnimationFrame(() => this.render());
}

// Update model-view matrix with rotation
updateModelViewMatrix() {
    // Reset to identity matrix with camera at (0,0,-5)
    this.modelViewMatrix = this.createModelViewMatrix();

    // Apply rotation around Y and X axes
    this.rotateY(this.modelViewMatrix, this.cubeRotation);
    this.rotateX(this.modelViewMatrix, this.cubeRotation * 0.7);
}

// Helper method to rotate matrix around Y axis
rotateY(matrix, angle) {
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);

    const m0 = matrix[0], m4 = matrix[4], m8 = matrix[8], m12 = matrix[12];

    matrix[0] = m0 * cos + matrix[2] * sin;
    matrix[4] = m4 * cos + matrix[6] * sin;
    matrix[8] = m8 * cos + matrix[10] * sin;
    matrix[12] = m12 * cos + matrix[14] * sin;

    matrix[2] = -m0 * sin + matrix[2] * cos;
    matrix[6] = -m4 * sin + matrix[6] * cos;
    matrix[10] = -m8 * sin + matrix[10] * cos;
    matrix[14] = -m12 * sin + matrix[14] * cos;
}

// Helper method to rotate matrix around X axis
rotateX(matrix, angle) {
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);

    const m1 = matrix[1], m5 = matrix[5], m9 = matrix[9], m13 = matrix[13];

    matrix[1] = m1 * cos - matrix[2] * sin;
    matrix[5] = m5 * cos - matrix[6] * sin;
    matrix[9] = m9 * cos - matrix[10] * sin;
    matrix[13] = m13 * cos - matrix[14] * sin;

    matrix[2] = m1 * sin + matrix[2] * cos;
    matrix[6] = m5 * sin + matrix[6] * cos;
    matrix[10] = m9 * sin + matrix[10] * cos;
    matrix[14] = m13 * sin + matrix[14] * cos;
}

Complete Example

Putting it all together, here's the complete code for Part 1:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Dating Chat - Part 1: WebGL Foundation</title>
    <style>
        body { margin: 0; padding: 0; overflow: hidden; font-family: Arial; background: #1a1a1a; color: white; }
        #container { position: relative; width: 100vw; height: 100vh; }
        #webgl-canvas { display: block; width: 100%; height: 100%; }
        #chat-ui { position: absolute; bottom: 20px; left: 20px; right: 20px; background: rgba(0,0,0,0.7); border-radius: 10px; padding: 15px; backdrop-filter: blur(10px); }
        #message-input { width: 100%; padding: 10px; border: none; border-radius: 5px; background: rgba(255,255,255,0.1); color: white; }
    </style>
</head>
<body>
    <div id="container">
        <canvas id="webgl-canvas"></canvas>
        <div id="chat-ui">
            <input type="text" id="message-input" placeholder="Type your message...">
        </div>
    </div>

    <script>
        // All the JavaScript code from above goes here
        // Vertex shader, fragment shader, and DatingChat3D class
    </script>
</body>
</html>

What We've Accomplished

In this first part, we've:

  1. Set up the basic HTML structure with a WebGL canvas and chat UI
  2. Initialized the WebGL context with proper error handling
  3. Created vertex and fragment shaders that handle basic 3D transformation and coloring
  4. Built geometry buffers for a colorful cube
  5. Implemented perspective projection for realistic 3D viewing
  6. Created a render loop that animates our cube

Next Steps

In Part 2, we'll:

This foundation gives us the core 3D rendering capabilities we need to build our dating chat environment. The rotating cube demonstrates that our WebGL pipeline is working correctly, and we're ready to build more complex 3D features in the upcoming parts.

Remember to test your code in a modern browser that supports WebGL! If you encounter issues, check the browser console for error messages.

Part 2: Adding User Avatars and Camera Controls

Welcome to Part 2 of our 10-part tutorial series on creating a 3D chat environment for a dating website! In this installment, we'll build upon our WebGL foundation by adding user avatars, implementing camera controls, and creating a more interactive 3D environment.

Table of Contents for Part 2

  1. Creating User Avatar Models
  2. Implementing Camera Navigation
  3. Adding Multiple Objects to the Scene
  4. Basic User Interaction
  5. Scene Management

1. Creating User Avatar Models

Let's start by creating simple yet distinctive avatar models that will represent users in our 3D chat environment. We'll create male and female avatar bases:

// Avatar geometry generator
class AvatarGenerator {
    constructor(gl) {
        this.gl = gl;
    }

    // Generate a simple humanoid avatar
    generateAvatar(gender = 'neutral', colorScheme = null) {
        // Default color schemes for different genders
        const schemes = {
            male: {
                skin: [0.9, 0.7, 0.5, 1.0],    // Light brown skin
                hair: [0.3, 0.2, 0.1, 1.0],    // Dark brown hair
                clothing: [0.2, 0.4, 0.8, 1.0] // Blue clothing
            },
            female: {
                skin: [0.9, 0.6, 0.6, 1.0],    // Light pink skin
                hair: [0.8, 0.6, 0.4, 1.0],    // Blonde hair
                clothing: [0.8, 0.2, 0.6, 1.0] // Pink clothing
            },
            neutral: {
                skin: [0.8, 0.7, 0.6, 1.0],    // Neutral skin
                hair: [0.5, 0.5, 0.5, 1.0],    // Gray hair
                clothing: [0.4, 0.6, 0.4, 1.0] // Green clothing
            }
        };

        const colors = colorScheme || schemes[gender];

        return {
            vertices: this.generateAvatarVertices(gender),
            colors: this.generateAvatarColors(colors),
            indices: this.generateAvatarIndices()
        };
    }

    // Generate vertices for a simple avatar (head + body)
    generateAvatarVertices(gender) {
        const vertices = [];

        // Head (sphere-like, simplified)
        this.generateHeadVertices(vertices, gender);

        // Body (torso)
        this.generateBodyVertices(vertices, gender);

        // Arms
        this.generateArmVertices(vertices, -1.2, gender); // Left arm
        this.generateArmVertices(vertices, 1.2, gender);  // Right arm

        return new Float32Array(vertices);
    }

    generateHeadVertices(vertices, gender) {
        const headSize = gender === 'female' ? 0.8 : 0.9;
        const headHeight = 1.8;

        // Head - front face
        vertices.push(
            -headSize, headHeight, headSize,     // top left
            headSize, headHeight, headSize,      // top right
            headSize, headHeight - 1.2, headSize, // bottom right
            -headSize, headHeight - 1.2, headSize // bottom left
        );

        // Head - back face
        vertices.push(
            -headSize, headHeight, -headSize,
            -headSize, headHeight - 1.2, -headSize,
            headSize, headHeight - 1.2, -headSize,
            headSize, headHeight, -headSize
        );

        // Head - sides and top/bottom
        // Left, right, top, bottom faces...
        this.generateCubeVertices(vertices, 
            -headSize, headHeight - 1.2, -headSize,
            headSize * 2, 1.2, headSize * 2
        );
    }

    generateBodyVertices(vertices, gender) {
        const shoulderWidth = gender === 'female' ? 1.6 : 1.8;
        const hipWidth = gender === 'female' ? 1.4 : 1.6;
        const chestHeight = 1.6;
        const waistHeight = 0.8;

        // Torso - trapezoid shape
        vertices.push(
            // Front face
            -shoulderWidth/2, chestHeight, 0.4,
            shoulderWidth/2, chestHeight, 0.4,
            hipWidth/2, waistHeight, 0.4,
            -hipWidth/2, waistHeight, 0.4,

            // Back face
            -shoulderWidth/2, chestHeight, -0.4,
            -hipWidth/2, waistHeight, -0.4,
            hipWidth/2, waistHeight, -0.4,
            shoulderWidth/2, chestHeight, -0.4
        );
    }

    generateArmVertices(vertices, side, gender) {
        const armLength = gender === 'female' ? 1.2 : 1.4;
        const armWidth = 0.3;
        const shoulderHeight = 1.5;

        const x = side > 0 ? 1.0 : -1.0;

        vertices.push(
            // Upper arm - front
            x * 0.8, shoulderHeight, armWidth,
            x * 1.2, shoulderHeight, armWidth,
            x * 1.2, shoulderHeight - armLength, armWidth,
            x * 0.8, shoulderHeight - armLength, armWidth,

            // Upper arm - back
            x * 0.8, shoulderHeight, -armWidth,
            x * 0.8, shoulderHeight - armLength, -armWidth,
            x * 1.2, shoulderHeight - armLength, -armWidth,
            x * 1.2, shoulderHeight, -armWidth
        );
    }

    generateCubeVertices(vertices, x, y, z, width, height, depth) {
        const vertices = [
            // Front face
            x, y + height, z + depth,
            x + width, y + height, z + depth,
            x + width, y, z + depth,
            x, y, z + depth,

            // Back face
            x, y + height, z,
            x, y, z,
            x + width, y, z,
            x + width, y + height, z,

            // Top face
            x, y + height, z,
            x + width, y + height, z,
            x + width, y + height, z + depth,
            x, y + height, z + depth,

            // Bottom face
            x, y, z,
            x, y, z + depth,
            x + width, y, z + depth,
            x + width, y, z,

            // Right face
            x + width, y + height, z,
            x + width, y + height, z + depth,
            x + width, y, z + depth,
            x + width, y, z,

            // Left face
            x, y + height, z,
            x, y, z,
            x, y, z + depth,
            x, y + height, z + depth,
        ];

        return vertices;
    }

    generateAvatarColors(colorScheme) {
        const colors = [];
        const parts = [
            colorScheme.skin, // head (6 faces)
            colorScheme.skin, colorScheme.skin, colorScheme.skin,
            colorScheme.skin, colorScheme.skin, colorScheme.skin,

            colorScheme.clothing, // body (6 faces)
            colorScheme.clothing, colorScheme.clothing, colorScheme.clothing,
            colorScheme.clothing, colorScheme.clothing, colorScheme.clothing,

            colorScheme.skin, // left arm (6 faces)
            colorScheme.skin, colorScheme.skin, colorScheme.skin,
            colorScheme.skin, colorScheme.skin, colorScheme.skin,

            colorScheme.skin, // right arm (6 faces)
            colorScheme.skin, colorScheme.skin, colorScheme.skin,
            colorScheme.skin, colorScheme.skin, colorScheme.skin
        ];

        // Each face has 4 vertices, each vertex needs 4 color components
        for (const color of parts) {
            for (let i = 0; i < 4; i++) {
                colors.push(...color);
            }
        }

        return new Float32Array(colors);
    }

    generateAvatarIndices() {
        const indices = [];
        let vertexOffset = 0;

        // Each avatar part has 6 faces, each face has 2 triangles (6 indices)
        const parts = [6, 6, 6, 6]; // head, body, left arm, right arm

        for (const faceCount of parts) {
            for (let face = 0; face < faceCount; face++) {
                const base = vertexOffset + face * 4;
                indices.push(
                    base, base + 1, base + 2,
                    base, base + 2, base + 3
                );
            }
            vertexOffset += faceCount * 4;
        }

        return new Uint16Array(indices);
    }
}

2. Implementing Camera Navigation

Now let's implement a flexible camera system that allows users to navigate the 3D environment:

// Camera controller class
class CameraController {
    constructor(canvas) {
        this.canvas = canvas;

        // Camera state
        this.eye = [0, 2, 8];        // Camera position
        this.center = [0, 0, 0];     // Look at point
        this.up = [0, 1, 0];         // Up vector

        // Camera parameters
        this.fov = 45 * Math.PI / 180;
        this.aspect = 1;
        this.near = 0.1;
        this.far = 1000;

        // Mouse control
        this.isDragging = false;
        this.lastMouseX = 0;
        this.lastMouseY = 0;

        // Rotation angles
        this.yaw = 0;     // Left-right rotation
        this.pitch = 0;   // Up-down rotation
        this.distance = 8; // Distance from center

        this.setupEventListeners();
    }

    setupEventListeners() {
        // Mouse down event
        this.canvas.addEventListener('mousedown', (e) => {
            this.isDragging = true;
            this.lastMouseX = e.clientX;
            this.lastMouseY = e.clientY;
        });

        // Mouse move event
        this.canvas.addEventListener('mousemove', (e) => {
            if (!this.isDragging) return;

            const deltaX = e.clientX - this.lastMouseX;
            const deltaY = e.clientY - this.lastMouseY;

            this.yaw -= deltaX * 0.01;
            this.pitch -= deltaY * 0.01;

            // Limit pitch to avoid flipping
            this.pitch = Math.max(-Math.PI/2 + 0.1, 
                         Math.min(Math.PI/2 - 0.1, this.pitch));

            this.lastMouseX = e.clientX;
            this.lastMouseY = e.clientY;
        });

        // Mouse up event
        this.canvas.addEventListener('mouseup', () => {
            this.isDragging = false;
        });

        // Mouse leave event
        this.canvas.addEventListener('mouseleave', () => {
            this.isDragging = false;
        });

        // Wheel event for zoom
        this.canvas.addEventListener('wheel', (e) => {
            e.preventDefault();
            this.distance += e.deltaY * 0.01;
            this.distance = Math.max(2, Math.min(20, this.distance));
        });

        // Keyboard controls
        document.addEventListener('keydown', (e) => {
            const moveSpeed = 0.2;
            switch(e.key) {
                case 'w':
                case 'ArrowUp':
                    this.moveForward(moveSpeed);
                    break;
                case 's':
                case 'ArrowDown':
                    this.moveForward(-moveSpeed);
                    break;
                case 'a':
                case 'ArrowLeft':
                    this.moveRight(-moveSpeed);
                    break;
                case 'd':
                case 'ArrowRight':
                    this.moveRight(moveSpeed);
                    break;
                case ' ':
                    this.moveUp(moveSpeed);
                    break;
                case 'Shift':
                    this.moveUp(-moveSpeed);
                    break;
            }
        });
    }

    // Calculate camera position based on orbit angles
    updateOrbitCamera() {
        // Calculate camera position based on spherical coordinates
        this.eye[0] = this.center[0] + this.distance * Math.cos(this.pitch) * Math.sin(this.yaw);
        this.eye[1] = this.center[1] + this.distance * Math.sin(this.pitch);
        this.eye[2] = this.center[2] + this.distance * Math.cos(this.pitch) * Math.cos(this.yaw);
    }

    moveForward(distance) {
        const direction = [
            Math.sin(this.yaw),
            0,
            Math.cos(this.yaw)
        ];

        this.center[0] += direction[0] * distance;
        this.center[2] += direction[2] * distance;
    }

    moveRight(distance) {
        const direction = [
            Math.cos(this.yaw),
            0,
            -Math.sin(this.yaw)
        ];

        this.center[0] += direction[0] * distance;
        this.center[2] += direction[2] * distance;
    }

    moveUp(distance) {
        this.center[1] += distance;
    }

    getViewMatrix() {
        this.updateOrbitCamera();
        return this.lookAt(this.eye, this.center, this.up);
    }

    getProjectionMatrix() {
        const projectionMatrix = new Float32Array(16);
        const f = 1.0 / Math.tan(this.fov / 2);
        const rangeInv = 1.0 / (this.near - this.far);

        projectionMatrix[0] = f / this.aspect;
        projectionMatrix[5] = f;
        projectionMatrix[10] = (this.near + this.far) * rangeInv;
        projectionMatrix[11] = -1;
        projectionMatrix[14] = 2 * this.near * this.far * rangeInv;
        projectionMatrix[15] = 0;

        return projectionMatrix;
    }

    // Create a look-at view matrix
    lookAt(eye, center, up) {
        const z = this.normalize([
            eye[0] - center[0],
            eye[1] - center[1],
            eye[2] - center[2]
        ]);

        const x = this.normalize(this.cross(up, z));
        const y = this.normalize(this.cross(z, x));

        return new Float32Array([
            x[0], y[0], z[0], 0,
            x[1], y[1], z[1], 0,
            x[2], y[2], z[2], 0,
            -this.dot(x, eye), -this.dot(y, eye), -this.dot(z, eye), 1
        ]);
    }

    // Vector math utilities
    normalize(vector) {
        const length = Math.sqrt(vector[0]**2 + vector[1]**2 + vector[2]**2);
        return [vector[0]/length, vector[1]/length, vector[2]/length];
    }

    cross(a, b) {
        return [
            a[1] * b[2] - a[2] * b[1],
            a[2] * b[0] - a[0] * b[2],
            a[0] * b[1] - a[1] * b[0]
        ];
    }

    dot(a, b) {
        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    }

    updateAspectRatio(width, height) {
        this.aspect = width / height;
    }
}

3. Adding Multiple Objects to the Scene

Let's create a scene manager to handle multiple objects and avatars:

// Scene object class
class SceneObject {
    constructor(vertices, colors, indices, position = [0, 0, 0]) {
        this.vertices = vertices;
        this.colors = colors;
        this.indices = indices;
        this.position = position;
        this.rotation = [0, 0, 0];
        this.scale = [1, 1, 1];
        this.vertexCount = indices.length;

        this.buffers = null;
    }

    createBuffers(gl) {
        this.buffers = {
            vertex: gl.createBuffer(),
            color: gl.createBuffer(),
            index: gl.createBuffer()
        };

        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.vertex);
        gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.color);
        gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers.index);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
    }

    getModelMatrix() {
        const matrix = new Float32Array(16);

        // Start with identity matrix
        matrix[0] = 1; matrix[5] = 1; matrix[10] = 1; matrix[15] = 1;

        // Apply translation
        matrix[12] = this.position[0];
        matrix[13] = this.position[1];
        matrix[14] = this.position[2];

        // Apply rotation (simplified - in practice, use proper rotation matrices)
        this.applyRotation(matrix, this.rotation);

        // Apply scale
        matrix[0] *= this.scale[0];
        matrix[5] *= this.scale[1];
        matrix[10] *= this.scale[2];

        return matrix;
    }

    applyRotation(matrix, rotation) {
        // Simplified rotation - in practice, use proper matrix multiplication
        const [rx, ry, rz] = rotation;

        // This is a simplified version - proper implementation would use
        // matrix multiplication for each axis rotation
        if (ry !== 0) {
            this.rotateY(matrix, ry);
        }
        if (rx !== 0) {
            this.rotateX(matrix, rx);
        }
        if (rz !== 0) {
            this.rotateZ(matrix, rz);
        }
    }

    rotateY(matrix, angle) {
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);

        const m0 = matrix[0], m1 = matrix[1], m2 = matrix[2], m3 = matrix[3];
        const m8 = matrix[8], m9 = matrix[9], m10 = matrix[10], m11 = matrix[11];

        matrix[0] = m0 * cos + m8 * -sin;
        matrix[1] = m1 * cos + m9 * -sin;
        matrix[2] = m2 * cos + m10 * -sin;
        matrix[3] = m3 * cos + m11 * -sin;

        matrix[8] = m0 * sin + m8 * cos;
        matrix[9] = m1 * sin + m9 * cos;
        matrix[10] = m2 * sin + m10 * cos;
        matrix[11] = m3 * sin + m11 * cos;
    }

    rotateX(matrix, angle) {
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);

        const m4 = matrix[4], m5 = matrix[5], m6 = matrix[6], m7 = matrix[7];
        const m8 = matrix[8], m9 = matrix[9], m10 = matrix[10], m11 = matrix[11];

        matrix[4] = m4 * cos + m8 * sin;
        matrix[5] = m5 * cos + m9 * sin;
        matrix[6] = m6 * cos + m10 * sin;
        matrix[7] = m7 * cos + m11 * sin;

        matrix[8] = m4 * -sin + m8 * cos;
        matrix[9] = m5 * -sin + m9 * cos;
        matrix[10] = m6 * -sin + m10 * cos;
        matrix[11] = m7 * -sin + m11 * cos;
    }

    rotateZ(matrix, angle) {
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);

        const m0 = matrix[0], m1 = matrix[1], m2 = matrix[2], m3 = matrix[3];
        const m4 = matrix[4], m5 = matrix[5], m6 = matrix[6], m7 = matrix[7];

        matrix[0] = m0 * cos + m4 * sin;
        matrix[1] = m1 * cos + m5 * sin;
        matrix[2] = m2 * cos + m6 * sin;
        matrix[3] = m3 * cos + m7 * sin;

        matrix[4] = m0 * -sin + m4 * cos;
        matrix[5] = m1 * -sin + m5 * cos;
        matrix[6] = m2 * -sin + m6 * cos;
        matrix[7] = m3 * -sin + m7 * cos;
    }
}

// Scene manager class
class SceneManager {
    constructor(gl) {
        this.gl = gl;
        this.objects = [];
        this.avatars = [];
        this.avatarGenerator = new AvatarGenerator(gl);
    }

    addObject(object) {
        if (!object.buffers) {
            object.createBuffers(this.gl);
        }
        this.objects.push(object);
    }

    addAvatar(gender = 'neutral', position = [0, 0, 0], name = 'User') {
        const avatarData = this.avatarGenerator.generateAvatar(gender);
        const avatar = new SceneObject(
            avatarData.vertices,
            avatarData.colors,
            avatarData.indices,
            position
        );

        avatar.name = name;
        avatar.gender = gender;
        avatar.type = 'avatar';

        this.addObject(avatar);
        this.avatars.push(avatar);

        return avatar;
    }

    // Create a simple environment (floor, etc.)
    createEnvironment() {
        // Create floor
        const floor = this.createFloor();
        this.addObject(floor);

        // Create some decorative objects
        const decorations = this.createDecorations();
        decorations.forEach(deco => this.addObject(deco));
    }

    createFloor() {
        const size = 20;
        const vertices = new Float32Array([
            -size, 0, -size,
            size, 0, -size,
            size, 0, size,
            -size, 0, size
        ]);

        const colors = new Float32Array([
            // Chessboard pattern
            0.8, 0.8, 0.8, 1.0,
            0.6, 0.6, 0.6, 1.0,
            0.8, 0.8, 0.8, 1.0,
            0.6, 0.6, 0.6, 1.0
        ]);

        const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);

        const floor = new SceneObject(vertices, colors, indices, [0, -0.5, 0]);
        floor.type = 'floor';

        return floor;
    }

    createDecorations() {
        const decorations = [];

        // Create some simple decorative cubes
        for (let i = -2; i <= 2; i += 2) {
            for (let j = -2; j <= 2; j += 2) {
                if (i === 0 && j === 0) continue; // Skip center

                const decoration = this.createCube(
                    [i, 0.5, j],
                    [0.3, 0.3, 0.3],
                    [Math.random(), Math.random(), Math.random(), 1.0]
                );
                decorations.push(decoration);
            }
        }

        return decorations;
    }

    createCube(position, size, color) {
        const [sx, sy, sz] = size;
        const vertices = new Float32Array([
            // Front face
            -sx, -sy, sz, sx, -sy, sz, sx, sy, sz, -sx, sy, sz,
            // Back face
            -sx, -sy, -sz, -sx, sy, -sz, sx, sy, -sz, sx, -sy, -sz,
            // Top face
            -sx, sy, -sz, -sx, sy, sz, sx, sy, sz, sx, sy, -sz,
            // Bottom face
            -sx, -sy, -sz, sx, -sy, -sz, sx, -sy, sz, -sx, -sy, sz,
            // Right face
            sx, -sy, -sz, sx, sy, -sz, sx, sy, sz, sx, -sy, sz,
            // Left face
            -sx, -sy, -sz, -sx, -sy, sz, -sx, sy, sz, -sx, sy, -sz,
        ]);

        const colors = new Float32Array(Array(24).fill(color).flat());

        const indices = new Uint16Array([
            0, 1, 2, 0, 2, 3,       // front
            4, 5, 6, 4, 6, 7,       // back
            8, 9, 10, 8, 10, 11,    // top
            12, 13, 14, 12, 14, 15, // bottom
            16, 17, 18, 16, 18, 19, // right
            20, 21, 22, 20, 22, 23  // left
        ]);

        const cube = new SceneObject(vertices, colors, indices, position);
        cube.type = 'decoration';

        return cube;
    }

    render(gl, program, attribLocations, uniformLocations, viewMatrix, projectionMatrix) {
        for (const object of this.objects) {
            // Set up vertex attributes
            gl.bindBuffer(gl.ARRAY_BUFFER, object.buffers.vertex);
            gl.vertexAttribPointer(
                attribLocations.vertexPosition,
                3, gl.FLOAT, false, 0, 0
            );
            gl.enableVertexAttribArray(attribLocations.vertexPosition);

            // Set up color attributes
            gl.bindBuffer(gl.ARRAY_BUFFER, object.buffers.color);
            gl.vertexAttribPointer(
                attribLocations.vertexColor,
                4, gl.FLOAT, false, 0, 0
            );
            gl.enableVertexAttribArray(attribLocations.vertexColor);

            // Calculate model-view matrix
            const modelMatrix = object.getModelMatrix();
            const modelViewMatrix = this.multiplyMatrices(viewMatrix, modelMatrix);

            // Set uniforms
            gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, projectionMatrix);
            gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, modelViewMatrix);

            // Draw
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.buffers.index);
            gl.drawElements(gl.TRIANGLES, object.vertexCount, gl.UNSIGNED_SHORT, 0);
        }
    }

    multiplyMatrices(a, b) {
        const result = new Float32Array(16);

        for (let i = 0; i < 4; i++) {
            for (let j = 0; j < 4; j++) {
                result[i * 4 + j] = 0;
                for (let k = 0; k < 4; k++) {
                    result[i * 4 + j] += a[i * 4 + k] * b[k * 4 + j];
                }
            }
        }

        return result;
    }
}

4. Basic User Interaction

Let's add some basic interaction features:

// User interaction manager
class InteractionManager {
    constructor(canvas, sceneManager, camera) {
        this.canvas = canvas;
        this.sceneManager = sceneManager;
        this.camera = camera;
        this.selectedAvatar = null;

        this.setupInteractionListeners();
    }

    setupInteractionListeners() {
        // Double click to select avatar
        this.canvas.addEventListener('dblclick', (e) => {
            this.handleAvatarSelection(e);
        });

        // Add chat message when Enter is pressed
        document.getElementById('message-input').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                this.sendChatMessage(e.target.value);
                e.target.value = '';
            }
        });
    }

    handleAvatarSelection(event) {
        // Convert mouse coordinates to normalized device coordinates
        const rect = this.canvas.getBoundingClientRect();
        const x = ((event.clientX - rect.left) / this.canvas.width) * 2 - 1;
        const y = -((event.clientY - rect.top) / this.canvas.height) * 2 + 1;

        // Simple avatar selection (in practice, use proper ray casting)
        const avatars = this.sceneManager.avatars;
        if (avatars.length > 0) {
            // For now, just select the first avatar
            this.selectedAvatar = avatars[0];
            this.showAvatarInfo(this.selectedAvatar);
        }
    }

    showAvatarInfo(avatar) {
        // Create or update avatar info panel
        let infoPanel = document.getElementById('avatar-info');
        if (!infoPanel) {
            infoPanel = document.createElement('div');
            infoPanel.id = 'avatar-info';
            infoPanel.style.cssText = `
                position: absolute;
                top: 20px;
                right: 20px;
                background: rgba(0,0,0,0.8);
                padding: 15px;
                border-radius: 10px;
                color: white;
                max-width: 200px;
            `;
            document.getElementById('container').appendChild(infoPanel);
        }

        infoPanel.innerHTML = `
            <h3>${avatar.name}</h3>
            <p>Gender: ${avatar.gender}</p>
            <p>Status: Online</p>
            <button onclick="interactionManager.startChat()">Start Chat</button>
        `;
    }

    startChat() {
        if (this.selectedAvatar) {
            const input = document.getElementById('message-input');
            input.placeholder = `Chat with ${this.selectedAvatar.name}...`;
            input.focus();
        }
    }

    sendChatMessage(message) {
        if (message.trim() === '') return;

        // In a real application, this would send the message to a server
        console.log(`Message to ${this.selectedAvatar?.name || 'general'}: ${message}`);

        // For now, just display in console
        this.displayChatMessage('You', message);

        // Simulate response
        if (this.selectedAvatar) {
            setTimeout(() => {
                const responses = [
                    "Hello there!",
                    "How are you doing?",
                    "Nice to meet you!",
                    "What brings you here?",
                    "I love this 3D chat!",
                    "The weather is nice today, isn't it?"
                ];
                const response = responses[Math.floor(Math.random() * responses.length)];
                this.displayChatMessage(this.selectedAvatar.name, response);
            }, 1000);
        }
    }

    displayChatMessage(sender, message) {
        // Create chat message display
        let chatDisplay = document.getElementById('chat-display');
        if (!chatDisplay) {
            chatDisplay = document.createElement('div');
            chatDisplay.id = 'chat-display';
            chatDisplay.style.cssText = `
                position: absolute;
                bottom: 80px;
                left: 20px;
                right: 20px;
                max-height: 200px;
                overflow-y: auto;
                background: rgba(0,0,0,0.7);
                border-radius: 10px;
                padding: 10px;
                color: white;
            `;
            document.getElementById('container').appendChild(chatDisplay);
        }

        const messageElement = document.createElement('div');
        messageElement.innerHTML = `<strong>${sender}:</strong> ${message}`;
        messageElement.style.marginBottom = '5px';
        chatDisplay.appendChild(messageElement);

        // Scroll to bottom
        chatDisplay.scrollTop = chatDisplay.scrollHeight;
    }
}

5. Updated Main Application Class

Now let's update our main application class to integrate all these new features:

class DatingChat3D {
    constructor() {
        this.canvas = null;
        this.gl = null;
        this.program = null;
        this.camera = null;
        this.sceneManager = null;
        this.interactionManager = null;

        this.init();
    }

    init() {
        this.setupWebGL();
        this.setupShaders();
        this.setupCamera();
        this.setupScene();
        this.setupInteraction();
        this.render();
    }

    setupWebGL() {
        this.canvas = document.getElementById('webgl-canvas');

        if (!this.canvas) {
            console.error('Canvas element not found!');
            return;
        }

        const gl = this.canvas.getContext('webgl') || 
                   this.canvas.getContext('experimental-webgl');

        if (!gl) {
            alert('WebGL is not supported by your browser!');
            return;
        }

        this.gl = gl;
        this.resizeCanvas();
        window.addEventListener('resize', () => this.resizeCanvas());

        gl.clearColor(0.1, 0.1, 0.2, 1.0);
        gl.enable(gl.DEPTH_TEST);
        gl.depthFunc(gl.LEQUAL);

        console.log('WebGL context initialized successfully');
    }

    setupShaders() {
        const gl = this.gl;

        const vertexShader = this.compileShader(gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = this.compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource);

        this.program = gl.createProgram();
        gl.attachShader(this.program, vertexShader);
        gl.attachShader(this.program, fragmentShader);
        gl.linkProgram(this.program);

        if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
            console.error('Unable to initialize the shader program: ' + 
                         gl.getProgramInfoLog(this.program));
            return;
        }

        this.attribLocations = {
            vertexPosition: gl.getAttribLocation(this.program, 'aVertexPosition'),
            vertexColor: gl.getAttribLocation(this.program, 'aVertexColor'),
        };

        this.uniformLocations = {
            projectionMatrix: gl.getUniformLocation(this.program, 'uProjectionMatrix'),
            modelViewMatrix: gl.getUniformLocation(this.program, 'uModelViewMatrix'),
        };
    }

    setupCamera() {
        this.camera = new CameraController(this.canvas);
    }

    setupScene() {
        this.sceneManager = new SceneManager(this.gl);

        // Create environment
        this.sceneManager.createEnvironment();

        // Add some sample avatars
        this.sceneManager.addAvatar('female', [-3, 0, 0], 'Sarah');
        this.sceneManager.addAvatar('male', [3, 0, 0], 'Mike');
        this.sceneManager.addAvatar('neutral', [0, 0, -3], 'Alex');

        console.log('Scene setup completed with', this.sceneManager.avatars.length, 'avatars');
    }

    setupInteraction() {
        this.interactionManager = new InteractionManager(
            this.canvas,
            this.sceneManager,
            this.camera
        );
    }

    resizeCanvas() {
        const displayWidth = this.canvas.clientWidth;
        const displayHeight = this.canvas.clientHeight;

        if (this.canvas.width !== displayWidth || this.canvas.height !== displayHeight) {
            this.canvas.width = displayWidth;
            this.canvas.height = displayHeight;

            this.gl.viewport(0, 0, displayWidth, displayHeight);

            if (this.camera) {
                this.camera.updateAspectRatio(displayWidth, displayHeight);
            }
        }
    }

    render() {
        const gl = this.gl;

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        gl.useProgram(this.program);

        // Get matrices from camera
        const viewMatrix = this.camera.getViewMatrix();
        const projectionMatrix = this.camera.getProjectionMatrix();

        // Render scene
        this.sceneManager.render(
            gl,
            this.program,
            this.attribLocations,
            this.uniformLocations,
            viewMatrix,
            projectionMatrix
        );

        // Animate avatars (simple rotation)
        this.animateAvatars();

        requestAnimationFrame(() => this.render());
    }

    animateAvatars() {
        const time = Date.now() * 0.001;

        for (let i = 0; i < this.sceneManager.avatars.length; i++) {
            const avatar = this.sceneManager.avatars[i];
            // Gentle bouncing animation
            avatar.position[1] = Math.sin(time + i) * 0.1;
            avatar.rotation[1] = time * 0.5;
        }
    }

    compileShader(type, source) {
        const gl = this.gl;
        const shader = gl.createShader(type);

        gl.shaderSource(shader, source);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            console.error('An error occurred compiling the shaders: ' + 
                         gl.getShaderInfoLog(shader));
            gl.deleteShader(shader);
            return null;
        }

        return shader;
    }
}

// Start the application
window.addEventListener('load', () => {
    window.datingChat = new DatingChat3D();
});

What We've Accomplished in Part 2

In this second part, we've significantly enhanced our 3D dating chat environment:

  1. Created User Avatar Models with gender variations and color schemes
  2. Implemented Camera Controls with orbit navigation and keyboard/mouse input
  3. Built a Scene Management System to handle multiple 3D objects
  4. Added Basic User Interaction including avatar selection and chat functionality
  5. Created an Environment with floors and decorative objects

Key Features Added:

Next Steps

In Part 3, we'll focus on:

The foundation we've built now allows users to navigate a 3D space, view other users as avatars, and start basic conversations - exactly what we need for a dating website chat environment!

Part 3: Implementing Lighting, Textures, and Real-time Communication

Welcome to Part 3 of our 10-part tutorial series! In this installment, we'll enhance our 3D dating chat with realistic lighting, textures, and real-time communication features. These additions will make our environment much more immersive and functional.

Table of Contents for Part 3

  1. Implementing Phong Lighting
  2. Adding Texture Support
  3. Enhanced Avatar Models
  4. WebSocket Communication
  5. Real-time Chat Features

1. Implementing Phong Lighting

Let's start by upgrading our shaders to support Phong lighting model with ambient, diffuse, and specular components:

// Updated vertex shader with lighting support
const vertexShaderSource = `
    attribute vec4 aVertexPosition;
    attribute vec3 aVertexNormal;
    attribute vec2 aTextureCoord;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;
    uniform mat4 uNormalMatrix;

    varying vec3 vNormal;
    varying vec3 vEyeVector;
    varying vec2 vTextureCoord;

    void main(void) {
        // Transform vertex position
        vec4 vertexPosition = uModelViewMatrix * aVertexPosition;
        gl_Position = uProjectionMatrix * vertexPosition;

        // Calculate normal in view space
        vNormal = mat3(uNormalMatrix) * aVertexNormal;

        // Calculate eye vector (camera to vertex)
        vEyeVector = -vertexPosition.xyz;

        // Pass texture coordinates to fragment shader
        vTextureCoord = aTextureCoord;
    }
`;

// Enhanced fragment shader with Phong lighting
const fragmentShaderSource = `
    precision mediump float;

    // Lighting uniforms
    uniform vec3 uLightPosition;
    uniform vec3 uLightColor;
    uniform vec3 uAmbientColor;
    uniform float uShininess;

    // Material properties
    uniform vec3 uMaterialDiffuse;
    uniform vec3 uMaterialSpecular;
    uniform sampler2D uSampler;
    uniform bool uUseTexture;

    // Inputs from vertex shader
    varying vec3 vNormal;
    varying vec3 vEyeVector;
    varying vec2 vTextureCoord;

    void main(void) {
        // Normalize interpolated normal
        vec3 normal = normalize(vNormal);

        // Calculate light direction
        vec3 lightDir = normalize(uLightPosition - vEyeVector);

        // Ambient component
        vec3 ambient = uAmbientColor;

        // Diffuse component (Lambertian reflection)
        float diff = max(dot(normal, lightDir), 0.0);
        vec3 diffuse = diff * uLightColor;

        // Specular component (Phong reflection)
        vec3 viewDir = normalize(-vEyeVector);
        vec3 reflectDir = reflect(-lightDir, normal);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), uShininess);
        vec3 specular = spec * uLightColor * uMaterialSpecular;

        // Combine lighting components
        vec3 lighting = ambient + diffuse + specular;

        // Get base color from texture or material
        vec4 baseColor;
        if (uUseTexture) {
            baseColor = texture2D(uSampler, vTextureCoord);
        } else {
            baseColor = vec4(uMaterialDiffuse, 1.0);
        }

        // Apply lighting to base color
        gl_FragColor = vec4(baseColor.rgb * lighting, baseColor.a);
    }
`;

Now let's create a lighting system manager:

// Lighting system class
class LightingSystem {
    constructor(gl) {
        this.gl = gl;
        this.lights = [];
        this.ambientColor = [0.2, 0.2, 0.2];

        this.setupDefaultLighting();
    }

    setupDefaultLighting() {
        // Add a main directional light
        this.addLight({
            position: [5.0, 10.0, 5.0],
            color: [1.0, 1.0, 1.0],
            type: 'directional'
        });

        // Add a fill light
        this.addLight({
            position: [-5.0, 5.0, -5.0],
            color: [0.3, 0.3, 0.4],
            type: 'directional'
        });
    }

    addLight(light) {
        this.lights.push(light);
    }

    setLightUniforms(program, uniformLocations) {
        const gl = this.gl;

        // For now, we'll use the first light (extend for multiple lights later)
        const mainLight = this.lights[0];

        gl.uniform3fv(uniformLocations.lightPosition, mainLight.position);
        gl.uniform3fv(uniformLocations.lightColor, mainLight.color);
        gl.uniform3fv(uniformLocations.ambientColor, this.ambientColor);
        gl.uniform1f(uniformLocations.shininess, 32.0);
    }

    updateLightPosition(lightIndex, position) {
        if (this.lights[lightIndex]) {
            this.lights[lightIndex].position = position;
        }
    }
}

// Enhanced material system
class Material {
    constructor(diffuse = [0.8, 0.8, 0.8], specular = [0.5, 0.5, 0.5], shininess = 32.0) {
        this.diffuse = diffuse;
        this.specular = specular;
        this.shininess = shininess;
        this.texture = null;
        this.useTexture = false;
    }

    setTexture(texture) {
        this.texture = texture;
        this.useTexture = true;
    }

    setUniforms(gl, uniformLocations) {
        gl.uniform3fv(uniformLocations.materialDiffuse, this.diffuse);
        gl.uniform3fv(uniformLocations.materialSpecular, this.specular);
        gl.uniform1f(uniformLocations.shininess, this.shininess);
        gl.uniform1i(uniformLocations.useTexture, this.useTexture);

        if (this.useTexture && this.texture) {
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, this.texture);
            gl.uniform1i(uniformLocations.sampler, 0);
        }
    }
}

2. Adding Texture Support

Let's implement texture loading and management:

// Texture loader class
class TextureLoader {
    constructor(gl) {
        this.gl = gl;
        this.textures = new Map();
    }

    // Load texture from URL
    loadTexture(url, callback = null) {
        if (this.textures.has(url)) {
            if (callback) callback(this.textures.get(url));
            return this.textures.get(url);
        }

        const texture = this.gl.createTexture();
        this.gl.bindTexture(this.gl.TEXTURE_2D, texture);

        // Placeholder texture while loading
        const level = 0;
        const internalFormat = this.gl.RGBA;
        const width = 1;
        const height = 1;
        const border = 0;
        const srcFormat = this.gl.RGBA;
        const srcType = this.gl.UNSIGNED_BYTE;
        const pixel = new Uint8Array([255, 255, 255, 255]);
        this.gl.texImage2D(this.gl.TEXTURE_2D, level, internalFormat, width, height, border, srcFormat, srcType, pixel);

        const image = new Image();
        image.onload = () => {
            this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
            this.gl.texImage2D(this.gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, image);

            // WebGL1 requires power of 2 dimensions for mipmapping
            if (this.isPowerOf2(image.width) && this.isPowerOf2(image.height)) {
                this.gl.generateMipmap(this.gl.TEXTURE_2D);
            } else {
                this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
                this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
                this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
            }

            this.textures.set(url, texture);
            if (callback) callback(texture);
        };

        image.onerror = () => {
            console.error(`Failed to load texture: ${url}`);
            if (callback) callback(null);
        };

        image.src = url;
        return texture;
    }

    isPowerOf2(value) {
        return (value & (value - 1)) === 0;
    }

    // Create procedural textures
    createProceduralTexture(name, width = 64, height = 64, generator = null) {
        const texture = this.gl.createTexture();
        this.gl.bindTexture(this.gl.TEXTURE_2D, texture);

        // Generate texture data
        const data = new Uint8Array(width * height * 4);

        if (generator) {
            generator(data, width, height);
        } else {
            // Default checkerboard pattern
            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    const offset = (y * width + x) * 4;
                    const pattern = (x ^ y) & 8;
                    data[offset] = pattern ? 200 : 100;     // R
                    data[offset + 1] = pattern ? 200 : 100; // G
                    data[offset + 2] = pattern ? 200 : 100; // B
                    data[offset + 3] = 255;                 // A
                }
            }
        }

        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, data);
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT);
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT);

        this.textures.set(name, texture);
        return texture;
    }

    // Create skin-like texture
    createSkinTexture() {
        return this.createProceduralTexture('skin', 64, 64, (data, width, height) => {
            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    const offset = (y * width + x) * 4;

                    // Base skin color with some variation
                    const baseR = 220 + Math.sin(x * 0.3) * 10;
                    const baseG = 180 + Math.cos(y * 0.2) * 15;
                    const baseB = 150 + Math.sin((x + y) * 0.1) * 10;

                    // Add some freckles/speckles
                    const noise = Math.random() * 20;
                    const speckle = Math.random() < 0.02 ? 30 : 0;

                    data[offset] = Math.min(255, baseR + noise - speckle);
                    data[offset + 1] = Math.min(255, baseG + noise - speckle);
                    data[offset + 2] = Math.min(255, baseB + noise - speckle);
                    data[offset + 3] = 255;
                }
            }
        });
    }

    // Create clothing texture
    createClothingTexture(color = [200, 100, 100]) {
        const name = `clothing_${color.join('_')}`;
        if (this.textures.has(name)) {
            return this.textures.get(name);
        }

        return this.createProceduralTexture(name, 64, 64, (data, width, height) => {
            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    const offset = (y * width + x) * 4;

                    // Simple fabric-like pattern
                    const pattern = Math.sin(x * 0.5) * Math.cos(y * 0.5) > 0.2 ? 1.0 : 0.9;

                    data[offset] = color[0] * pattern;
                    data[offset + 1] = color[1] * pattern;
                    data[offset + 2] = color[2] * pattern;
                    data[offset + 3] = 255;
                }
            }
        });
    }
}

3. Enhanced Avatar Models

Let's upgrade our avatar generator to support normals, texture coordinates, and more detailed models:

// Enhanced avatar generator with normals and UVs
class AdvancedAvatarGenerator {
    constructor(gl) {
        this.gl = gl;
        this.textureLoader = new TextureLoader(gl);
    }

    generateAvatar(gender = 'neutral', details = {}) {
        const avatarData = {
            vertices: [],
            normals: [],
            textureCoords: [],
            indices: [],
            materials: {}
        };

        // Generate different body parts
        this.generateHead(avatarData, gender, details);
        this.generateBody(avatarData, gender, details);
        this.generateArms(avatarData, gender, details);
        this.generateLegs(avatarData, gender, details);

        // Create materials
        this.setupAvatarMaterials(avatarData, gender, details);

        return avatarData;
    }

    generateHead(avatarData, gender, details) {
        const headRadius = gender === 'female' ? 0.4 : 0.45;
        const headHeight = 1.7;
        const segments = 12;

        // Generate sphere-like head
        for (let lat = 0; lat <= segments; lat++) {
            const theta = lat * Math.PI / segments;
            const sinTheta = Math.sin(theta);
            const cosTheta = Math.cos(theta);

            for (let lon = 0; lon <= segments; lon++) {
                const phi = lon * 2 * Math.PI / segments;
                const sinPhi = Math.sin(phi);
                const cosPhi = Math.cos(phi);

                const x = cosPhi * sinTheta;
                const y = cosTheta;
                const z = sinPhi * sinTheta;

                avatarData.vertices.push(
                    x * headRadius,
                    y * headRadius + headHeight,
                    z * headRadius
                );

                // Normal is just the sphere normal
                avatarData.normals.push(x, y, z);

                // UV coordinates
                avatarData.textureCoords.push(
                    1 - (lon / segments),
                    1 - (lat / segments)
                );
            }
        }

        // Generate indices for head sphere
        for (let lat = 0; lat < segments; lat++) {
            for (let lon = 0; lon < segments; lon++) {
                const first = (lat * (segments + 1)) + lon;
                const second = first + segments + 1;

                avatarData.indices.push(first, second, first + 1);
                avatarData.indices.push(second, second + 1, first + 1);
            }
        }

        avatarData.materials.head = new Material([0.9, 0.7, 0.5]);
    }

    generateBody(avatarData, gender, details) {
        const shoulderWidth = gender === 'female' ? 0.8 : 1.0;
        const chestHeight = 1.5;
        const hipWidth = gender === 'female' ? 0.7 : 0.9;
        const waistHeight = 1.0;

        // Torso as a tapered cylinder
        const segments = 8;
        const topRadius = shoulderWidth / 2;
        const bottomRadius = hipWidth / 2;
        const height = chestHeight - waistHeight;

        // Generate vertices for tapered cylinder
        for (let i = 0; i <= segments; i++) {
            const angle = (i / segments) * Math.PI * 2;
            const sin = Math.sin(angle);
            const cos = Math.cos(angle);

            // Top ring
            avatarData.vertices.push(
                cos * topRadius,
                chestHeight,
                sin * topRadius
            );

            // Bottom ring
            avatarData.vertices.push(
                cos * bottomRadius,
                waistHeight,
                sin * bottomRadius
            );

            // Normals (approximate for tapered cylinder)
            const normalX = cos;
            const normalY = 0;
            const normalZ = sin;
            const normalLength = Math.sqrt(normalX * normalX + normalZ * normalZ);

            avatarData.normals.push(
                normalX / normalLength,
                normalY,
                normalZ / normalLength
            );
            avatarData.normals.push(
                normalX / normalLength,
                normalY,
                normalZ / normalLength
            );

            // UV coordinates
            avatarData.textureCoords.push(i / segments, 0);
            avatarData.textureCoords.push(i / segments, 1);
        }

        // Generate indices for torso
        const baseIndex = avatarData.vertices.length / 3 - (segments + 1) * 2;
        for (let i = 0; i < segments; i++) {
            const topLeft = baseIndex + i * 2;
            const topRight = baseIndex + ((i + 1) % segments) * 2;
            const bottomLeft = topLeft + 1;
            const bottomRight = topRight + 1;

            avatarData.indices.push(topLeft, bottomLeft, topRight);
            avatarData.indices.push(topRight, bottomLeft, bottomRight);
        }

        avatarData.materials.body = new Material([0.2, 0.4, 0.8]);
    }

    generateArms(avatarData, gender, details) {
        // Similar to body but thinner cylinders
        // Implementation details omitted for brevity
        // Would generate cylinder geometry for arms
    }

    generateLegs(avatarData, gender, details) {
        // Generate cylinder geometry for legs
        // Implementation details omitted for brevity
    }

    setupAvatarMaterials(avatarData, gender, details) {
        // Create skin texture
        const skinTexture = this.textureLoader.createSkinTexture();
        avatarData.materials.head.setTexture(skinTexture);

        // Create clothing texture based on gender
        const clothingColor = gender === 'female' ? [200, 100, 150] : 
                            gender === 'male' ? [100, 100, 200] : [150, 150, 150];
        const clothingTexture = this.textureLoader.createClothingTexture(clothingColor);
        avatarData.materials.body.setTexture(clothingTexture);
    }
}

4. WebSocket Communication

Now let's implement real-time communication using WebSockets:

// WebSocket communication manager
class ChatConnection {
    constructor() {
        this.socket = null;
        this.connected = false;
        this.messageHandlers = new Map();
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;

        this.setupEventHandlers();
    }

    connect(serverUrl = 'wss://echo.websocket.org') { // Using public test server
        return new Promise((resolve, reject) => {
            try {
                this.socket = new WebSocket(serverUrl);

                this.socket.onopen = (event) => {
                    console.log('WebSocket connection established');
                    this.connected = true;
                    this.reconnectAttempts = 0;
                    this.onConnectionStateChange(true);
                    resolve(event);
                };

                this.socket.onmessage = (event) => {
                    this.handleMessage(event);
                };

                this.socket.onclose = (event) => {
                    console.log('WebSocket connection closed');
                    this.connected = false;
                    this.onConnectionStateChange(false);
                    this.attemptReconnect();
                };

                this.socket.onerror = (error) => {
                    console.error('WebSocket error:', error);
                    this.connected = false;
                    this.onConnectionStateChange(false);
                    reject(error);
                };

            } catch (error) {
                console.error('Failed to create WebSocket connection:', error);
                reject(error);
            }
        });
    }

    attemptReconnect() {
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
            this.reconnectAttempts++;
            const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);

            console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);

            setTimeout(() => {
                this.connect();
            }, delay);
        } else {
            console.error('Max reconnection attempts reached');
        }
    }

    handleMessage(event) {
        try {
            const message = JSON.parse(event.data);
            const { type, data } = message;

            // Call registered handlers for this message type
            if (this.messageHandlers.has(type)) {
                this.messageHandlers.get(type).forEach(handler => {
                    try {
                        handler(data);
                    } catch (error) {
                        console.error('Error in message handler:', error);
                    }
                });
            }

        } catch (error) {
            console.error('Error parsing message:', error, event.data);
        }
    }

    sendMessage(type, data) {
        if (!this.connected || !this.socket) {
            console.warn('Cannot send message - not connected');
            return false;
        }

        try {
            const message = JSON.stringify({ type, data, timestamp: Date.now() });
            this.socket.send(message);
            return true;
        } catch (error) {
            console.error('Error sending message:', error);
            return false;
        }
    }

    onMessage(type, handler) {
        if (!this.messageHandlers.has(type)) {
            this.messageHandlers.set(type, []);
        }
        this.messageHandlers.get(type).push(handler);
    }

    onConnectionStateChange(connected) {
        // Update UI to show connection status
        const statusElement = document.getElementById('connection-status') || 
                             this.createConnectionStatusElement();

        statusElement.textContent = connected ? '๐ŸŸข Online' : '๐Ÿ”ด Offline';
        statusElement.style.background = connected ? 'rgba(0, 255, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)';
    }

    createConnectionStatusElement() {
        const statusElement = document.createElement('div');
        statusElement.id = 'connection-status';
        statusElement.style.cssText = `
            position: absolute;
            top: 20px;
            left: 20px;
            padding: 5px 10px;
            border-radius: 15px;
            color: white;
            font-size: 12px;
            background: rgba(255, 0, 0, 0.2);
        `;
        document.getElementById('container').appendChild(statusElement);
        return statusElement;
    }

    // Specific message types for our chat application
    sendChatMessage(message, targetUser = null) {
        return this.sendMessage('chat_message', {
            content: message,
            target: targetUser,
            sender: this.getCurrentUser()
        });
    }

    sendAvatarUpdate(position, rotation) {
        return this.sendMessage('avatar_update', {
            position,
            rotation,
            user: this.getCurrentUser()
        });
    }

    sendUserJoin(userData) {
        return this.sendMessage('user_join', userData);
    }

    sendUserLeave() {
        return this.sendMessage('user_leave', { user: this.getCurrentUser() });
    }

    getCurrentUser() {
        // In a real app, this would come from authentication
        return {
            id: 'user_' + Math.random().toString(36).substr(2, 9),
            name: 'User' + Math.floor(Math.random() * 1000),
            gender: Math.random() > 0.5 ? 'male' : 'female'
        };
    }
}

5. Real-time Chat Features

Let's create a comprehensive chat manager that handles real-time messaging:

// Real-time chat manager
class RealTimeChat {
    constructor(connection, sceneManager) {
        this.connection = connection;
        this.sceneManager = sceneManager;
        this.messages = [];
        this.typingUsers = new Set();
        this.currentUser = null;

        this.setupMessageHandlers();
        this.setupChatUI();
    }

    setupMessageHandlers() {
        // Handle incoming chat messages
        this.connection.onMessage('chat_message', (data) => {
            this.addMessage(data.sender, data.content, data.timestamp, false);

            // Show message above avatar if it's from another user
            if (data.sender.id !== this.currentUser.id) {
                this.showAvatarMessage(data.sender.id, data.content);
            }
        });

        // Handle user join events
        this.connection.onMessage('user_join', (data) => {
            this.addSystemMessage(`${data.name} joined the chat`);

            // Add avatar for new user
            this.sceneManager.addAvatar(data.gender, this.getRandomPosition(), data.name);
        });

        // Handle user leave events
        this.connection.onMessage('user_leave', (data) => {
            this.addSystemMessage(`${data.user.name} left the chat`);

            // Remove avatar (in a real app, you'd want to keep history)
            this.removeUserAvatar(data.user.id);
        });

        // Handle avatar updates
        this.connection.onMessage('avatar_update', (data) => {
            this.updateUserAvatar(data.user.id, data.position, data.rotation);
        });

        // Handle typing indicators
        this.connection.onMessage('typing_start', (data) => {
            this.typingUsers.add(data.user.name);
            this.updateTypingIndicator();
        });

        this.connection.onMessage('typing_stop', (data) => {
            this.typingUsers.delete(data.user.name);
            this.updateTypingIndicator();
        });
    }

    setupChatUI() {
        this.createEnhancedChatInterface();
        this.setupInputHandlers();
    }

    createEnhancedChatInterface() {
        const chatContainer = document.getElementById('chat-ui');

        // Create messages container
        const messagesContainer = document.createElement('div');
        messagesContainer.id = 'chat-messages';
        messagesContainer.style.cssText = `
            max-height: 200px;
            overflow-y: auto;
            margin-bottom: 10px;
            padding: 10px;
            background: rgba(0, 0, 0, 0.5);
            border-radius: 5px;
            font-size: 12px;
        `;

        // Create typing indicator
        const typingIndicator = document.createElement('div');
        typingIndicator.id = 'typing-indicator';
        typingIndicator.style.cssText = `
            font-style: italic;
            color: #aaa;
            min-height: 16px;
            margin-bottom: 5px;
        `;

        // Update chat container structure
        chatContainer.innerHTML = '';
        chatContainer.appendChild(messagesContainer);
        chatContainer.appendChild(typingIndicator);

        // Create input container
        const inputContainer = document.createElement('div');
        inputContainer.style.display = 'flex';
        inputContainer.style.gap = '5px';

        const messageInput = document.createElement('input');
        messageInput.type = 'text';
        messageInput.id = 'message-input';
        messageInput.placeholder = 'Type your message...';
        messageInput.style.flex = '1';

        const sendButton = document.createElement('button');
        sendButton.textContent = 'Send';
        sendButton.onclick = () => this.sendCurrentMessage();

        inputContainer.appendChild(messageInput);
        inputContainer.appendChild(sendButton);
        chatContainer.appendChild(inputContainer);

        // Store references
        this.messagesContainer = messagesContainer;
        this.typingIndicator = typingIndicator;
        this.messageInput = messageInput;
    }

    setupInputHandlers() {
        let typingTimer;

        this.messageInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                this.sendCurrentMessage();
            } else {
                // Send typing start indicator
                this.connection.sendMessage('typing_start', { user: this.currentUser });

                // Clear existing timer
                clearTimeout(typingTimer);

                // Set timer to send typing stop indicator
                typingTimer = setTimeout(() => {
                    this.connection.sendMessage('typing_stop', { user: this.currentUser });
                }, 1000);
            }
        });

        this.messageInput.addEventListener('blur', () => {
            this.connection.sendMessage('typing_stop', { user: this.currentUser });
        });
    }

    sendCurrentMessage() {
        const message = this.messageInput.value.trim();
        if (message) {
            this.connection.sendChatMessage(message);
            this.addMessage(this.currentUser, message, Date.now(), true);
            this.messageInput.value = '';

            // Stop typing indicator
            this.connection.sendMessage('typing_stop', { user: this.currentUser });
        }
    }

    addMessage(sender, content, timestamp, isOwn = false) {
        const message = {
            sender,
            content,
            timestamp,
            isOwn
        };

        this.messages.push(message);
        this.displayMessage(message);

        // Keep only last 100 messages
        if (this.messages.length > 100) {
            this.messages.shift();
            this.updateMessagesDisplay();
        }
    }

    addSystemMessage(content) {
        this.addMessage({ name: 'System', id: 'system' }, content, Date.now(), false);
    }

    displayMessage(message) {
        const messageElement = document.createElement('div');
        messageElement.style.cssText = `
            margin-bottom: 5px;
            padding: 5px;
            border-radius: 3px;
            background: ${message.isOwn ? 'rgba(100, 100, 255, 0.3)' : 'rgba(255, 255, 255, 0.1)'};
            word-wrap: break-word;
        `;

        const time = new Date(message.timestamp).toLocaleTimeString();
        messageElement.innerHTML = `
            <strong style="color: ${message.isOwn ? '#88f' : '#fff'}">${message.sender.name}:</strong>
            ${this.escapeHtml(message.content)}
            <span style="float: right; font-size: 10px; color: #aaa;">${time}</span>
        `;

        this.messagesContainer.appendChild(messageElement);
        this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
    }

    updateMessagesDisplay() {
        this.messagesContainer.innerHTML = '';
        this.messages.forEach(message => this.displayMessage(message));
    }

    updateTypingIndicator() {
        if (this.typingUsers.size > 0) {
            const names = Array.from(this.typingUsers);
            let text = '';

            if (names.length === 1) {
                text = `${names[0]} is typing...`;
            } else if (names.length === 2) {
                text = `${names[0]} and ${names[1]} are typing...`;
            } else {
                text = `${names.slice(0, -1).join(', ')}, and ${names[names.length - 1]} are typing...`;
            }

            this.typingIndicator.textContent = text;
        } else {
            this.typingIndicator.textContent = '';
        }
    }

    showAvatarMessage(userId, message) {
        // Find avatar and show message above it
        const avatar = this.sceneManager.avatars.find(av => av.userId === userId);
        if (avatar) {
            this.createSpeechBubble(avatar, message);
        }
    }

    createSpeechBubble(avatar, message) {
        // Create a speech bubble element
        const bubble = document.createElement('div');
        bubble.textContent = message;
        bubble.style.cssText = `
            position: absolute;
            background: rgba(255, 255, 255, 0.9);
            color: black;
            padding: 8px 12px;
            border-radius: 15px;
            max-width: 200px;
            word-wrap: break-word;
            font-size: 12px;
            pointer-events: none;
            z-index: 100;
            transform: translate(-50%, -100%);
            margin-top: -10px;
        `;

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

        // Position bubble above avatar
        this.updateBubblePosition(bubble, avatar);

        // Remove bubble after 5 seconds
        setTimeout(() => {
            if (bubble.parentNode) {
                bubble.parentNode.removeChild(bubble);
            }
        }, 5000);

        // Update position continuously (in case avatar moves)
        const updateInterval = setInterval(() => {
            if (!bubble.parentNode) {
                clearInterval(updateInterval);
                return;
            }
            this.updateBubblePosition(bubble, avatar);
        }, 100);
    }

    updateBubblePosition(bubble, avatar) {
        // Convert 3D position to 2D screen coordinates
        // This is a simplified version - in practice, you'd use proper projection
        const canvas = document.getElementById('webgl-canvas');
        const rect = canvas.getBoundingClientRect();

        const x = (avatar.position[0] / 10 + 0.5) * rect.width;
        const y = (1 - (avatar.position[1] / 10 + 0.5)) * rect.height;

        bubble.style.left = (rect.left + x) + 'px';
        bubble.style.top = (rect.top + y - 50) + 'px';
    }

    getRandomPosition() {
        const angle = Math.random() * Math.PI * 2;
        const distance = 2 + Math.random() * 3;
        return [
            Math.cos(angle) * distance,
            0,
            Math.sin(angle) * distance
        ];
    }

    removeUserAvatar(userId) {
        const index = this.sceneManager.avatars.findIndex(av => av.userId === userId);
        if (index !== -1) {
            this.sceneManager.avatars.splice(index, 1);
            // Note: In a complete implementation, you'd also remove from scene objects
        }
    }

    updateUserAvatar(userId, position, rotation) {
        const avatar = this.sceneManager.avatars.find(av => av.userId === userId);
        if (avatar) {
            avatar.position = position;
            avatar.rotation = rotation;
        }
    }

    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    setCurrentUser(user) {
        this.currentUser = user;
        this.connection.sendUserJoin(user);
    }
}

Updated Main Application Integration

Finally, let's update our main application to integrate all these new features:

class DatingChat3D {
    constructor() {
        this.canvas = null;
        this.gl = null;
        this.program = null;
        this.camera = null;
        this.sceneManager = null;
        this.interactionManager = null;
        this.lightingSystem = null;
        this.textureLoader = null;
        this.chatConnection = null;
        this.realTimeChat = null;

        this.init();
    }

    async init() {
        this.setupWebGL();
        this.setupShaders();
        this.setupCamera();
        this.setupLighting();
        this.setupTextures();
        this.setupScene();
        this.setupNetwork();
        this.setupInteraction();
        this.render();
    }

    setupLighting() {
        this.lightingSystem = new LightingSystem(this.gl);
    }

    setupTextures() {
        this.textureLoader = new TextureLoader(this.gl);
    }

    async setupNetwork() {
        this.chatConnection = new ChatConnection();
        this.realTimeChat = new RealTimeChat(this.chatConnection, this.sceneManager);

        try {
            await this.chatConnection.connect();

            // Set current user after successful connection
            const currentUser = this.chatConnection.getCurrentUser();
            this.realTimeChat.setCurrentUser(currentUser);

            // Add current user's avatar
            this.sceneManager.addAvatar(currentUser.gender, [0, 0, 0], currentUser.name);

        } catch (error) {
            console.error('Failed to establish chat connection:', error);
        }
    }

    // Update shader setup to include new uniforms
    setupShaders() {
        const gl = this.gl;

        const vertexShader = this.compileShader(gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = this.compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource);

        this.program = gl.createProgram();
        gl.attachShader(this.program, vertexShader);
        gl.attachShader(this.program, fragmentShader);
        gl.linkProgram(this.program);

        if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
            console.error('Unable to initialize the shader program: ' + 
                         gl.getProgramInfoLog(this.program));
            return;
        }

        // Enhanced attribute and uniform locations
        this.attribLocations = {
            vertexPosition: gl.getAttribLocation(this.program, 'aVertexPosition'),
            vertexNormal: gl.getAttribLocation(this.program, 'aVertexNormal'),
            textureCoord: gl.getAttribLocation(this.program, 'aTextureCoord'),
        };

        this.uniformLocations = {
            projectionMatrix: gl.getUniformLocation(this.program, 'uProjectionMatrix'),
            modelViewMatrix: gl.getUniformLocation(this.program, 'uModelViewMatrix'),
            normalMatrix: gl.getUniformLocation(this.program, 'uNormalMatrix'),
            lightPosition: gl.getUniformLocation(this.program, 'uLightPosition'),
            lightColor: gl.getUniformLocation(this.program, 'uLightColor'),
            ambientColor: gl.getUniformLocation(this.program, 'uAmbientColor'),
            materialDiffuse: gl.getUniformLocation(this.program, 'uMaterialDiffuse'),
            materialSpecular: gl.getUniformLocation(this.program, 'uMaterialSpecular'),
            shininess: gl.getUniformLocation(this.program, 'uShininess'),
            sampler: gl.getUniformLocation(this.program, 'uSampler'),
            useTexture: gl.getUniformLocation(this.program, 'uUseTexture'),
        };
    }

    render() {
        const gl = this.gl;

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        gl.useProgram(this.program);

        // Set lighting uniforms
        this.lightingSystem.setLightUniforms(this.program, this.uniformLocations);

        // Get matrices from camera
        const viewMatrix = this.camera.getViewMatrix();
        const projectionMatrix = this.camera.getProjectionMatrix();

        // Calculate normal matrix (transpose of inverse of model-view matrix)
        const normalMatrix = this.calculateNormalMatrix(viewMatrix);
        gl.uniformMatrix4fv(this.uniformLocations.normalMatrix, false, normalMatrix);

        // Render scene
        this.sceneManager.render(
            gl,
            this.program,
            this.attribLocations,
            this.uniformLocations,
            viewMatrix,
            projectionMatrix
        );

        // Send avatar updates to server
        this.sendAvatarUpdates();

        requestAnimationFrame(() => this.render());
    }

    calculateNormalMatrix(modelViewMatrix) {
        // Calculate normal matrix (transpose of inverse)
        // For simplicity, we'll return identity for now
        // In practice, you'd calculate the proper normal matrix
        return new Float32Array([
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ]);
    }

    sendAvatarUpdates() {
        if (this.chatConnection && this.chatConnection.connected) {
            const currentAvatar = this.sceneManager.avatars[0]; // Assuming first avatar is current user
            if (currentAvatar) {
                this.chatConnection.sendAvatarUpdate(
                    currentAvatar.position,
                    currentAvatar.rotation
                );
            }
        }
    }

    // ... rest of the class remains similar with updates for new features
}

What We've Accomplished in Part 3

In this third part, we've significantly enhanced our 3D dating chat with:

  1. Advanced Lighting System with Phong shading for realistic materials
  2. Texture Support for detailed avatar skins and clothing
  3. Enhanced Avatar Models with proper normals and UV coordinates
  4. Real-time WebSocket Communication for live chat features
  5. Comprehensive Chat System with typing indicators and speech bubbles

Key Features Added:

Next Steps

In Part 4, we'll focus on:

Our 3D dating chat is now becoming a fully-featured social platform with realistic graphics and real-time communication!

Part 4: Facial Expressions, Animations, and Social Features

Welcome to Part 4 of our 10-part tutorial series! In this installment, we'll bring our avatars to life with facial expressions, animations, and social interactions. We'll also implement private spaces and enhance the user experience with gestures and emotes.

Table of Contents for Part 4

  1. Facial Expression System
  2. Avatar Animation System
  3. Gesture and Emote System
  4. Private Chat Spaces
  5. User Profiles and Matching

1. Facial Expression System

Let's start by creating a sophisticated facial expression system that can blend between different emotions:

// Facial expression system with blend shapes
class FacialExpressionSystem {
    constructor() {
        this.expressions = new Map();
        this.currentExpression = 'neutral';
        this.blendWeight = 0.0;
        this.targetExpression = 'neutral';
        this.blendSpeed = 2.0; // seconds to blend

        this.setupBaseExpressions();
    }

    setupBaseExpressions() {
        // Define base expressions using blend shapes
        this.expressions.set('neutral', {
            eyebrowLeft: { y: 0.0, rotation: 0.0 },
            eyebrowRight: { y: 0.0, rotation: 0.0 },
            eyeLeft: { open: 1.0, smile: 0.0 },
            eyeRight: { open: 1.0, smile: 0.0 },
            mouth: { smile: 0.0, open: 0.0, shape: 0.0 }
        });

        this.expressions.set('happy', {
            eyebrowLeft: { y: 0.1, rotation: -0.1 },
            eyebrowRight: { y: 0.1, rotation: 0.1 },
            eyeLeft: { open: 0.8, smile: 0.6 },
            eyeRight: { open: 0.8, smile: 0.6 },
            mouth: { smile: 0.8, open: 0.2, shape: 0.5 }
        });

        this.expressions.set('sad', {
            eyebrowLeft: { y: -0.2, rotation: 0.3 },
            eyebrowRight: { y: -0.2, rotation: -0.3 },
            eyeLeft: { open: 0.9, smile: -0.2 },
            eyeRight: { open: 0.9, smile: -0.2 },
            mouth: { smile: -0.3, open: 0.1, shape: -0.4 }
        });

        this.expressions.set('angry', {
            eyebrowLeft: { y: -0.1, rotation: -0.4 },
            eyebrowRight: { y: -0.1, rotation: 0.4 },
            eyeLeft: { open: 0.7, smile: -0.3 },
            eyeRight: { open: 0.7, smile: -0.3 },
            mouth: { smile: -0.6, open: 0.1, shape: -0.2 }
        });

        this.expressions.set('surprised', {
            eyebrowLeft: { y: 0.3, rotation: 0.0 },
            eyebrowRight: { y: 0.3, rotation: 0.0 },
            eyeLeft: { open: 1.2, smile: 0.0 },
            eyeRight: { open: 1.2, smile: 0.0 },
            mouth: { smile: 0.0, open: 0.6, shape: 0.8 }
        });

        this.expressions.set('wink', {
            eyebrowLeft: { y: 0.1, rotation: -0.1 },
            eyebrowRight: { y: 0.0, rotation: 0.0 },
            eyeLeft: { open: 0.0, smile: 0.8 },
            eyeRight: { open: 1.0, smile: 0.0 },
            mouth: { smile: 0.6, open: 0.1, shape: 0.3 }
        });
    }

    setExpression(expressionName, blendTime = 0.5) {
        if (!this.expressions.has(expressionName)) {
            console.warn(`Expression '${expressionName}' not found`);
            return;
        }

        this.targetExpression = expressionName;
        this.blendSpeed = 1.0 / blendTime;
        this.blendWeight = 0.0;
    }

    update(deltaTime) {
        if (this.currentExpression !== this.targetExpression) {
            this.blendWeight += deltaTime * this.blendSpeed;

            if (this.blendWeight >= 1.0) {
                this.blendWeight = 1.0;
                this.currentExpression = this.targetExpression;
            }
        }
    }

    getCurrentExpression() {
        if (this.currentExpression === this.targetExpression || this.blendWeight >= 1.0) {
            return this.expressions.get(this.targetExpression);
        }

        const fromExpr = this.expressions.get(this.currentExpression);
        const toExpr = this.expressions.get(this.targetExpression);

        return this.blendExpressions(fromExpr, toExpr, this.blendWeight);
    }

    blendExpressions(exprA, exprB, weight) {
        const result = {};

        for (const [part, valuesA] of Object.entries(exprA)) {
            const valuesB = exprB[part];
            result[part] = {};

            for (const [param, valueA] of Object.entries(valuesA)) {
                const valueB = valuesB[param];
                result[part][param] = valueA + (valueB - valueA) * weight;
            }
        }

        return result;
    }

    // Auto-expression based on chat content
    analyzeTextForExpression(text) {
        text = text.toLowerCase();

        if (text.includes('๐Ÿ˜‚') || text.includes('๐Ÿ˜Š') || text.includes('haha') || text.includes('lol')) {
            return 'happy';
        } else if (text.includes('๐Ÿ˜ข') || text.includes('๐Ÿ˜”') || text.includes('sad')) {
            return 'sad';
        } else if (text.includes('๐Ÿ˜ ') || text.includes('angry') || text.includes('mad')) {
            return 'angry';
        } else if (text.includes('๐Ÿ˜ฎ') || text.includes('wow') || text.includes('omg')) {
            return 'surprised';
        } else if (text.includes('๐Ÿ˜‰') || text.includes('wink')) {
            return 'wink';
        }

        return 'neutral';
    }
}

// Enhanced avatar with facial expressions
class ExpressiveAvatar {
    constructor(avatarData, userId, name) {
        this.avatarData = avatarData;
        this.userId = userId;
        this.name = name;
        this.expressionSystem = new FacialExpressionSystem();
        this.expressionTimer = 0;
        this.currentEmotion = 'neutral';

        // Facial animation properties
        this.blinkTimer = 0;
        this.blinkInterval = 3.0 + Math.random() * 2.0; // Random blink interval
        this.isBlinking = false;

        // Mouth movement for speech
        this.speaking = false;
        this.mouthOpenTime = 0;
    }

    update(deltaTime) {
        // Update expression system
        this.expressionSystem.update(deltaTime);

        // Handle blinking
        this.updateBlinking(deltaTime);

        // Handle mouth movement for speech
        if (this.speaking) {
            this.updateMouthMovement(deltaTime);
        }

        // Random subtle expression changes
        this.expressionTimer += deltaTime;
        if (this.expressionTimer > 10.0 && this.currentEmotion === 'neutral') {
            // Occasionally show subtle expressions
            if (Math.random() < 0.1) {
                const subtleExpressions = ['happy', 'sad', 'surprised'];
                const randomExpr = subtleExpressions[Math.floor(Math.random() * subtleExpressions.length)];
                this.setExpression(randomExpr, 1.0 + Math.random() * 2.0);

                // Return to neutral after a while
                setTimeout(() => {
                    this.setExpression('neutral', 1.0);
                }, 2000 + Math.random() * 3000);
            }
            this.expressionTimer = 0;
        }
    }

    updateBlinking(deltaTime) {
        this.blinkTimer += deltaTime;

        if (!this.isBlinking && this.blinkTimer >= this.blinkInterval) {
            // Start blink
            this.isBlinking = true;
            this.expressionSystem.setExpression('blink', 0.1);
            this.blinkTimer = 0;
        } else if (this.isBlinking && this.blinkTimer >= 0.2) {
            // End blink
            this.isBlinking = false;
            this.expressionSystem.setExpression(this.currentEmotion, 0.1);
            this.blinkInterval = 3.0 + Math.random() * 2.0; // New random interval
            this.blinkTimer = 0;
        }
    }

    updateMouthMovement(deltaTime) {
        this.mouthOpenTime += deltaTime;
        // Simple mouth animation for speech - open/close cycle
        const mouthCycle = Math.sin(this.mouthOpenTime * 10) * 0.5 + 0.5;

        const currentExpr = this.expressionSystem.getCurrentExpression();
        currentExpr.mouth.open = mouthCycle * 0.3;
    }

    setExpression(expressionName, duration = 2.0) {
        this.currentEmotion = expressionName;
        this.expressionSystem.setExpression(expressionName, 0.3);

        // Auto-return to neutral after duration
        if (expressionName !== 'neutral') {
            setTimeout(() => {
                if (this.currentEmotion === expressionName) {
                    this.setExpression('neutral', 1.0);
                }
            }, duration * 1000);
        }
    }

    startSpeaking() {
        this.speaking = true;
        this.mouthOpenTime = 0;
    }

    stopSpeaking() {
        this.speaking = false;
        const currentExpr = this.expressionSystem.getCurrentExpression();
        currentExpr.mouth.open = 0.0;
    }

    getFacialTransforms() {
        const expression = this.expressionSystem.getCurrentExpression();
        const transforms = [];

        // Apply expression to facial bones/vertices
        // This would modify the avatar's geometry in practice
        return {
            expression,
            vertexOffsets: this.calculateVertexOffsets(expression)
        };
    }

    calculateVertexOffsets(expression) {
        // Calculate vertex displacements based on expression
        // In a real implementation, this would use blend shapes or bone transforms
        const offsets = new Float32Array(this.avatarData.vertices.length);

        // Simplified: just return zero offsets for now
        // In practice, you'd have a mapping from expressions to vertex displacements
        return offsets;
    }
}

2. Avatar Animation System

Now let's create a comprehensive animation system for avatar movements:

// Animation system for avatar movements
class AnimationSystem {
    constructor() {
        this.animations = new Map();
        this.currentAnimation = null;
        this.animationTime = 0;
        this.looping = true;
        this.blendWeight = 1.0;
        this.blendTime = 0.3;

        this.setupBaseAnimations();
    }

    setupBaseAnimations() {
        // Idle animation - subtle breathing and weight shifting
        this.animations.set('idle', {
            duration: 4.0,
            keyframes: [
                { time: 0.0, positions: { y: 0.0, rotation: 0.0 } },
                { time: 2.0, positions: { y: 0.02, rotation: 0.05 } },
                { time: 4.0, positions: { y: 0.0, rotation: 0.0 } }
            ]
        });

        // Walking animation
        this.animations.set('walk', {
            duration: 1.0,
            keyframes: [
                { time: 0.0, positions: { legLeft: -0.3, legRight: 0.3, armSwing: 0.0 } },
                { time: 0.5, positions: { legLeft: 0.3, legRight: -0.3, armSwing: 0.4 } },
                { time: 1.0, positions: { legLeft: -0.3, legRight: 0.3, armSwing: 0.0 } }
            ]
        });

        // Wave animation
        this.animations.set('wave', {
            duration: 1.5,
            looping: false,
            keyframes: [
                { time: 0.0, positions: { armRight: 0.0 } },
                { time: 0.3, positions: { armRight: 1.2 } },
                { time: 0.6, positions: { armRight: 0.8 } },
                { time: 0.9, positions: { armRight: 1.2 } },
                { time: 1.2, positions: { armRight: 0.8 } },
                { time: 1.5, positions: { armRight: 0.0 } }
            ]
        });

        // Dance animation
        this.animations.set('dance', {
            duration: 2.0,
            keyframes: [
                { time: 0.0, positions: { bounce: 0.0, twist: 0.0, armsUp: 0.0 } },
                { time: 0.5, positions: { bounce: 0.2, twist: 0.3, armsUp: 0.5 } },
                { time: 1.0, positions: { bounce: 0.0, twist: -0.3, armsUp: 0.8 } },
                { time: 1.5, positions: { bounce: 0.2, twist: 0.3, armsUp: 0.5 } },
                { time: 2.0, positions: { bounce: 0.0, twist: 0.0, armsUp: 0.0 } }
            ]
        });

        // Sit animation
        this.animations.set('sit', {
            duration: 1.0,
            looping: false,
            keyframes: [
                { time: 0.0, positions: { bodyHeight: 0.0, legBend: 0.0 } },
                { time: 1.0, positions: { bodyHeight: -0.8, legBend: 1.4 } }
            ]
        });
    }

    playAnimation(name, blendTime = 0.3) {
        if (!this.animations.has(name)) {
            console.warn(`Animation '${name}' not found`);
            return;
        }

        this.currentAnimation = this.animations.get(name);
        this.animationTime = 0;
        this.looping = this.currentAnimation.looping !== false;
        this.blendTime = blendTime;
        this.blendWeight = 0.0;
    }

    stopAnimation() {
        this.currentAnimation = null;
        this.animationTime = 0;
    }

    update(deltaTime) {
        if (!this.currentAnimation) return;

        // Update animation time
        this.animationTime += deltaTime;

        // Update blend weight
        if (this.blendWeight < 1.0) {
            this.blendWeight = Math.min(1.0, this.blendWeight + deltaTime / this.blendTime);
        }

        // Handle animation looping
        if (this.looping && this.animationTime >= this.currentAnimation.duration) {
            this.animationTime %= this.currentAnimation.duration;
        } else if (!this.looping && this.animationTime >= this.currentAnimation.duration) {
            this.currentAnimation = null;
            this.animationTime = 0;
        }
    }

    getCurrentPose() {
        if (!this.currentAnimation) {
            return this.getDefaultPose();
        }

        const { keyframes } = this.currentAnimation;

        // Find current and next keyframe
        let currentFrame = keyframes[0];
        let nextFrame = keyframes[1];

        for (let i = 0; i < keyframes.length - 1; i++) {
            if (this.animationTime >= keyframes[i].time && this.animationTime <= keyframes[i + 1].time) {
                currentFrame = keyframes[i];
                nextFrame = keyframes[i + 1];
                break;
            }
        }

        // Interpolate between keyframes
        const frameDelta = nextFrame.time - currentFrame.time;
        const progress = frameDelta > 0 ? (this.animationTime - currentFrame.time) / frameDelta : 0;

        const pose = {};

        for (const [bone, value] of Object.entries(currentFrame.positions)) {
            const nextValue = nextFrame.positions[bone];
            pose[bone] = value + (nextValue - value) * progress;
        }

        // Apply blend weight
        if (this.blendWeight < 1.0) {
            const defaultPose = this.getDefaultPose();
            for (const [bone, value] of Object.entries(pose)) {
                const defaultValue = defaultPose[bone] || 0;
                pose[bone] = defaultValue + (value - defaultValue) * this.blendWeight;
            }
        }

        return pose;
    }

    getDefaultPose() {
        return {
            y: 0.0,
            rotation: 0.0,
            legLeft: 0.0,
            legRight: 0.0,
            armSwing: 0.0,
            armRight: 0.0,
            bounce: 0.0,
            twist: 0.0,
            armsUp: 0.0,
            bodyHeight: 0.0,
            legBend: 0.0
        };
    }

    isPlaying() {
        return this.currentAnimation !== null;
    }
}

// Enhanced scene object with animation support
class AnimatedAvatar extends SceneObject {
    constructor(avatarData, userId, name, position = [0, 0, 0]) {
        super(avatarData.vertices, avatarData.colors, avatarData.indices, position);

        this.userId = userId;
        this.name = name;
        this.animationSystem = new AnimationSystem();
        this.expressionSystem = new ExpressiveAvatar(avatarData, userId, name);
        this.movementSpeed = 2.0;
        this.targetPosition = [...position];
        this.isMoving = false;

        // Social state
        this.isSpeaking = false;
        this.currentGesture = null;
        this.lastActivityTime = Date.now();
    }

    update(deltaTime) {
        // Update animation system
        this.animationSystem.update(deltaTime);

        // Update expression system
        this.expressionSystem.update(deltaTime);

        // Handle movement towards target
        this.updateMovement(deltaTime);

        // Auto-play idle animation if no other animation is playing
        if (!this.animationSystem.isPlaying() && !this.isMoving) {
            this.animationSystem.playAnimation('idle');
        }
    }

    updateMovement(deltaTime) {
        if (!this.isMoving) return;

        const dx = this.targetPosition[0] - this.position[0];
        const dz = this.targetPosition[2] - this.position[2];
        const distance = Math.sqrt(dx * dx + dz * dz);

        if (distance < 0.1) {
            // Reached target
            this.position[0] = this.targetPosition[0];
            this.position[2] = this.targetPosition[2];
            this.isMoving = false;
            this.animationSystem.playAnimation('idle');
        } else {
            // Move towards target
            const directionX = dx / distance;
            const directionZ = dz / distance;

            this.position[0] += directionX * this.movementSpeed * deltaTime;
            this.position[2] += directionZ * this.movementSpeed * deltaTime;

            // Update rotation to face movement direction
            this.rotation[1] = Math.atan2(directionX, directionZ);

            // Play walk animation if not already playing
            if (!this.animationSystem.isPlaying() || this.animationSystem.currentAnimation !== 'walk') {
                this.animationSystem.playAnimation('walk');
            }
        }
    }

    moveTo(position) {
        this.targetPosition = [...position];
        this.isMoving = true;
    }

    playGesture(gestureName) {
        if (this.animationSystem.animations.has(gestureName)) {
            this.currentGesture = gestureName;
            this.animationSystem.playAnimation(gestureName);

            // Set appropriate facial expression
            switch(gestureName) {
                case 'wave':
                    this.expressionSystem.setExpression('happy', 3.0);
                    break;
                case 'dance':
                    this.expressionSystem.setExpression('happy', 5.0);
                    break;
                case 'sit':
                    this.expressionSystem.setExpression('neutral', 2.0);
                    break;
            }
        }
    }

    startSpeaking() {
        this.isSpeaking = true;
        this.expressionSystem.startSpeaking();
    }

    stopSpeaking() {
        this.isSpeaking = false;
        this.expressionSystem.stopSpeaking();
    }

    reactToMessage(message) {
        const expression = this.expressionSystem.expressionSystem.analyzeTextForExpression(message);
        this.expressionSystem.setExpression(expression, 3.0);

        // Sometimes add a gesture based on message content
        if (message.includes('๐Ÿ‘‹') || message.includes('hello') || message.includes('hi')) {
            if (Math.random() < 0.3) {
                setTimeout(() => this.playGesture('wave'), 500);
            }
        } else if (message.includes('๐Ÿ’ƒ') || message.includes('๐Ÿ•บ') || message.includes('dance')) {
            if (Math.random() < 0.5) {
                setTimeout(() => this.playGesture('dance'), 1000);
            }
        }
    }

    getAnimationPose() {
        return this.animationSystem.getCurrentPose();
    }

    getFacialTransforms() {
        return this.expressionSystem.getFacialTransforms();
    }
}

3. Gesture and Emote System

Let's create a comprehensive gesture and emote system:

// Gesture and emote manager
class GestureSystem {
    constructor() {
        this.availableGestures = new Map();
        this.activeEmotes = new Map();
        this.gestureCooldowns = new Map();

        this.setupGestures();
    }

    setupGestures() {
        this.availableGestures.set('wave', {
            name: 'Wave',
            animation: 'wave',
            duration: 2.0,
            cooldown: 5.0,
            description: 'Wave hello to everyone'
        });

        this.availableGestures.set('dance', {
            name: 'Dance',
            animation: 'dance',
            duration: 5.0,
            cooldown: 10.0,
            description: 'Show off your dance moves'
        });

        this.availableGestures.set('sit', {
            name: 'Sit',
            animation: 'sit',
            duration: 0.0, // Persistent
            cooldown: 2.0,
            description: 'Take a seat'
        });

        this.availableGestures.set('stand', {
            name: 'Stand',
            animation: 'idle',
            duration: 1.0,
            cooldown: 2.0,
            description: 'Stand up'
        });

        this.availableGestures.set('clap', {
            name: 'Clap',
            animation: 'clap',
            duration: 3.0,
            cooldown: 5.0,
            description: 'Applaud something great'
        });

        this.availableGestures.set('blowkiss', {
            name: 'Blow Kiss',
            animation: 'blowkiss',
            duration: 2.0,
            cooldown: 8.0,
            description: 'Send a kiss to someone special'
        });
    }

    canPerformGesture(userId, gestureId) {
        if (!this.availableGestures.has(gestureId)) return false;

        const lastUsed = this.gestureCooldowns.get(`${userId}_${gestureId}`);
        if (lastUsed) {
            const gesture = this.availableGestures.get(gestureId);
            return Date.now() - lastUsed > gesture.cooldown * 1000;
        }

        return true;
    }

    performGesture(userId, gestureId) {
        if (!this.canPerformGesture(userId, gestureId)) {
            return false;
        }

        const gesture = this.availableGestures.get(gestureId);
        this.gestureCooldowns.set(`${userId}_${gestureId}`, Date.now());

        // Store active emote for persistent gestures
        if (gesture.duration === 0) {
            this.activeEmotes.set(userId, gestureId);
        } else {
            // Remove after duration for temporary gestures
            setTimeout(() => {
                this.activeEmotes.delete(userId);
            }, gesture.duration * 1000);
        }

        return true;
    }

    stopGesture(userId) {
        this.activeEmotes.delete(userId);
    }

    getActiveEmote(userId) {
        return this.activeEmotes.get(userId);
    }

    getAvailableGestures() {
        return Array.from(this.availableGestures.values());
    }
}

// UI for gesture selection
class GestureUI {
    constructor(gestureSystem, chatConnection) {
        this.gestureSystem = gestureSystem;
        this.chatConnection = chatConnection;
        this.isVisible = false;

        this.createGestureUI();
    }

    createGestureUI() {
        this.gesturePanel = document.createElement('div');
        this.gesturePanel.style.cssText = `
            position: absolute;
            bottom: 80px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 10px;
            padding: 15px;
            color: white;
            display: none;
            max-width: 300px;
            z-index: 1000;
        `;

        const title = document.createElement('h3');
        title.textContent = 'Gestures & Emotes';
        title.style.margin = '0 0 10px 0';
        this.gesturePanel.appendChild(title);

        const gestureGrid = document.createElement('div');
        gestureGrid.style.display = 'grid';
        gestureGrid.style.gridTemplateColumns = 'repeat(2, 1fr)';
        gestureGrid.style.gap = '5px';

        const gestures = this.gestureSystem.getAvailableGestures();
        gestures.forEach(gesture => {
            const button = document.createElement('button');
            button.textContent = gesture.name;
            button.title = gesture.description;
            button.style.cssText = `
                padding: 8px;
                border: none;
                border-radius: 5px;
                background: rgba(255, 255, 255, 0.1);
                color: white;
                cursor: pointer;
                transition: background 0.2s;
            `;

            button.addEventListener('mouseenter', () => {
                button.style.background = 'rgba(255, 255, 255, 0.2)';
            });

            button.addEventListener('mouseleave', () => {
                button.style.background = 'rgba(255, 255, 255, 0.1)';
            });

            button.addEventListener('click', () => {
                this.performGesture(gesture.id);
                this.hide();
            });

            gestureGrid.appendChild(button);
        });

        this.gesturePanel.appendChild(gestureGrid);
        document.getElementById('container').appendChild(this.gesturePanel);

        // Create toggle button
        this.createToggleButton();
    }

    createToggleButton() {
        this.toggleButton = document.createElement('button');
        this.toggleButton.textContent = '๐ŸŽญ';
        this.toggleButton.title = 'Gestures & Emotes';
        this.toggleButton.style.cssText = `
            position: absolute;
            bottom: 80px;
            right: 20px;
            width: 40px;
            height: 40px;
            border: none;
            border-radius: 50%;
            background: rgba(100, 100, 255, 0.8);
            color: white;
            font-size: 18px;
            cursor: pointer;
            z-index: 1001;
        `;

        this.toggleButton.addEventListener('click', () => {
            this.toggle();
        });

        document.getElementById('container').appendChild(this.toggleButton);
    }

    toggle() {
        this.isVisible = !this.isVisible;
        this.gesturePanel.style.display = this.isVisible ? 'block' : 'none';
    }

    show() {
        this.isVisible = true;
        this.gesturePanel.style.display = 'block';
    }

    hide() {
        this.isVisible = false;
        this.gesturePanel.style.display = 'none';
    }

    performGesture(gestureId) {
        // Send gesture to server
        this.chatConnection.sendMessage('gesture_perform', {
            gestureId,
            user: this.chatConnection.getCurrentUser()
        });
    }
}

4. Private Chat Spaces

Now let's implement private chat spaces for more intimate conversations:

// Private space system
class PrivateSpaceSystem {
    constructor(sceneManager, chatConnection) {
        this.sceneManager = sceneManager;
        this.chatConnection = chatConnection;
        this.spaces = new Map();
        this.activeSpace = null;
        this.spaceInvites = new Map();

        this.setupDefaultSpaces();
        this.setupSpaceHandlers();
    }

    setupDefaultSpaces() {
        // Create some default private spaces
        this.createSpace({
            id: 'garden',
            name: 'Secret Garden',
            position: [8, 0, 8],
            radius: 4.0,
            capacity: 4,
            description: 'A peaceful garden for quiet conversations',
            color: [0.2, 0.6, 0.3]
        });

        this.createSpace({
            id: 'lounge',
            name: 'Cozy Lounge',
            position: [-8, 0, -8],
            radius: 5.0,
            capacity: 6,
            description: 'Comfortable seating for group chats',
            color: [0.6, 0.3, 0.2]
        });

        this.createSpace({
            id: 'rooftop',
            name: 'Rooftop Terrace',
            position: [0, 3, 10],
            radius: 3.0,
            capacity: 2,
            description: 'Romantic spot with a view',
            color: [0.3, 0.4, 0.8]
        });
    }

    setupSpaceHandlers() {
        this.chatConnection.onMessage('space_join', (data) => {
            this.handleUserJoinSpace(data.user, data.spaceId);
        });

        this.chatConnection.onMessage('space_leave', (data) => {
            this.handleUserLeaveSpace(data.user, data.spaceId);
        });

        this.chatConnection.onMessage('space_invite', (data) => {
            this.handleSpaceInvite(data.fromUser, data.spaceId, data.message);
        });
    }

    createSpace(spaceConfig) {
        const space = {
            ...spaceConfig,
            occupants: new Set(),
            isPrivate: spaceConfig.isPrivate || false
        };

        this.spaces.set(spaceConfig.id, space);
        this.createSpaceVisual(space);

        return space;
    }

    createSpaceVisual(space) {
        // Create visual representation of the space
        const spaceVisual = this.sceneManager.createSpaceVisual(
            space.position,
            space.radius,
            space.color,
            space.name
        );
        space.visual = spaceVisual;
        this.sceneManager.addObject(spaceVisual);
    }

    joinSpace(spaceId, userId = null) {
        const space = this.spaces.get(spaceId);
        if (!space) return false;

        const user = userId || this.chatConnection.getCurrentUser();

        if (space.occupants.size >= space.capacity) {
            this.showNotification('This space is full');
            return false;
        }

        // Leave current space if any
        if (this.activeSpace) {
            this.leaveSpace(this.activeSpace);
        }

        space.occupants.add(user.id);
        this.activeSpace = spaceId;

        // Move user's avatar to space
        this.moveAvatarToSpace(user.id, space);

        // Notify others
        this.chatConnection.sendMessage('space_join', {
            user,
            spaceId
        });

        this.showNotification(`Joined ${space.name}`);
        this.updateSpaceUI();

        return true;
    }

    leaveSpace(spaceId) {
        const space = this.spaces.get(spaceId);
        if (!space) return;

        const user = this.chatConnection.getCurrentUser();
        space.occupants.delete(user.id);

        if (this.activeSpace === spaceId) {
            this.activeSpace = null;
        }

        // Move avatar back to main area
        this.moveAvatarToMainArea(user.id);

        // Notify others
        this.chatConnection.sendMessage('space_leave', {
            user,
            spaceId
        });

        this.showNotification(`Left ${space.name}`);
        this.updateSpaceUI();
    }

    moveAvatarToSpace(userId, space) {
        const avatar = this.sceneManager.avatars.find(av => av.userId === userId);
        if (avatar) {
            // Calculate position within space (circle)
            const angle = Math.random() * Math.PI * 2;
            const distance = Math.random() * space.radius * 0.7;
            const x = space.position[0] + Math.cos(angle) * distance;
            const z = space.position[2] + Math.sin(angle) * distance;

            avatar.moveTo([x, space.position[1], z]);
        }
    }

    moveAvatarToMainArea(userId) {
        const avatar = this.sceneManager.avatars.find(av => av.userId === userId);
        if (avatar) {
            // Return to random position in main area
            const angle = Math.random() * Math.PI * 2;
            const distance = 2 + Math.random() * 3;
            const x = Math.cos(angle) * distance;
            const z = Math.sin(angle) * distance;

            avatar.moveTo([x, 0, z]);
        }
    }

    inviteToSpace(targetUserId, spaceId, message = '') {
        const space = this.spaces.get(spaceId);
        if (!space) return false;

        if (space.occupants.size >= space.capacity) {
            this.showNotification('Space is full, cannot invite');
            return false;
        }

        this.chatConnection.sendMessage('space_invite', {
            fromUser: this.chatConnection.getCurrentUser(),
            targetUserId,
            spaceId,
            message
        });

        return true;
    }

    handleUserJoinSpace(user, spaceId) {
        const space = this.spaces.get(spaceId);
        if (space) {
            space.occupants.add(user.id);
            this.moveAvatarToSpace(user.id, space);
            this.updateSpaceUI();
        }
    }

    handleUserLeaveSpace(user, spaceId) {
        const space = this.spaces.get(spaceId);
        if (space) {
            space.occupants.delete(user.id);
            this.moveAvatarToMainArea(user.id);
            this.updateSpaceUI();
        }
    }

    handleSpaceInvite(fromUser, spaceId, message) {
        const space = this.spaces.get(spaceId);
        if (!space) return;

        this.showInviteNotification(fromUser, space, message);
    }

    showInviteNotification(fromUser, space, message) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.9);
            border: 2px solid #4CAF50;
            border-radius: 10px;
            padding: 20px;
            color: white;
            z-index: 2000;
            min-width: 300px;
            text-align: center;
        `;

        notification.innerHTML = `
            <h3>Space Invitation</h3>
            <p><strong>${fromUser.name}</strong> invited you to:</p>
            <h4>${space.name}</h4>
            <p>${space.description}</p>
            ${message ? `<p>"${message}"</p>` : ''}
            <p>Occupants: ${space.occupants.size}/${space.capacity}</p>
            <div style="margin-top: 15px;">
                <button id="accept-invite" style="margin-right: 10px; padding: 8px 16px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">Accept</button>
                <button id="decline-invite" style="padding: 8px 16px; background: #f44336; color: white; border: none; border-radius: 5px; cursor: pointer;">Decline</button>
            </div>
        `;

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

        document.getElementById('accept-invite').addEventListener('click', () => {
            this.joinSpace(space.id);
            notification.remove();
        });

        document.getElementById('decline-invite').addEventListener('click', () => {
            notification.remove();
        });
    }

    updateSpaceUI() {
        // Update space information in UI
        const spaceInfo = document.getElementById('space-info') || this.createSpaceInfoUI();

        if (this.activeSpace) {
            const space = this.spaces.get(this.activeSpace);
            spaceInfo.innerHTML = `
                <strong>${space.name}</strong><br>
                ${space.occupants.size}/${space.capacity} people<br>
                <button onclick="privateSpaceSystem.leaveSpace('${this.activeSpace}')" style="margin-top: 5px; padding: 3px 8px; font-size: 10px;">Leave</button>
            `;
        } else {
            spaceInfo.innerHTML = 'Main Area';
        }
    }

    createSpaceInfoUI() {
        const spaceInfo = document.createElement('div');
        spaceInfo.id = 'space-info';
        spaceInfo.style.cssText = `
            position: absolute;
            top: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.7);
            padding: 10px;
            border-radius: 5px;
            color: white;
            font-size: 12px;
        `;
        document.getElementById('container').appendChild(spaceInfo);
        return spaceInfo;
    }

    showNotification(message) {
        // Simple notification system
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            top: 60px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 20px;
            z-index: 1000;
            transition: opacity 0.3s;
        `;

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

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }
}

5. User Profiles and Matching

Finally, let's implement user profiles and a simple matching system:

// User profile system
class ProfileSystem {
    constructor(chatConnection) {
        this.chatConnection = chatConnection;
        this.profiles = new Map();
        this.currentUserProfile = null;

        this.setupProfileHandlers();
        this.createProfileUI();
    }

    setupProfileHandlers() {
        this.chatConnection.onMessage('profile_update', (data) => {
            this.updateProfile(data.userId, data.profile);
        });

        this.chatConnection.onMessage('profile_request', (data) => {
            this.sendProfile(data.requestingUser);
        });
    }

    createProfile(profileData) {
        const profile = {
            userId: profileData.userId,
            name: profileData.name,
            age: profileData.age,
            gender: profileData.gender,
            bio: profileData.bio || '',
            interests: profileData.interests || [],
            location: profileData.location || '',
            lookingFor: profileData.lookingFor || [],
            photos: profileData.photos || [],
            isOnline: true,
            lastActive: Date.now(),
            compatibility: 0
        };

        this.profiles.set(profileData.userId, profile);

        if (profileData.userId === this.chatConnection.getCurrentUser().id) {
            this.currentUserProfile = profile;
        }

        return profile;
    }

    updateProfile(userId, profileData) {
        const existingProfile = this.profiles.get(userId);
        if (existingProfile) {
            Object.assign(existingProfile, profileData);
        } else {
            this.createProfile({ userId, ...profileData });
        }
    }

    sendProfile(requestingUserId) {
        if (this.currentUserProfile) {
            this.chatConnection.sendMessage('profile_update', {
                userId: this.currentUserProfile.userId,
                profile: this.currentUserProfile
            });
        }
    }

    calculateCompatibility(profileA, profileB) {
        let score = 0;
        const maxScore = 100;

        // Age compatibility (prefer similar age)
        const ageDiff = Math.abs(profileA.age - profileB.age);
        if (ageDiff <= 5) score += 20;
        else if (ageDiff <= 10) score += 10;

        // Interest matching
        const commonInterests = profileA.interests.filter(interest => 
            profileB.interests.includes(interest)
        );
        score += Math.min(commonInterests.length * 10, 30);

        // Looking for matching
        if (profileA.lookingFor.includes(profileB.gender) && 
            profileB.lookingFor.includes(profileA.gender)) {
            score += 30;
        }

        // Location proximity (simplified)
        if (profileA.location === profileB.location) {
            score += 20;
        }

        return Math.min(score, maxScore);
    }

    findPotentialMatches(limit = 10) {
        const currentUser = this.currentUserProfile;
        if (!currentUser) return [];

        const matches = [];

        for (const [userId, profile] of this.profiles) {
            if (userId === currentUser.userId) continue;
            if (!profile.isOnline) continue;

            const compatibility = this.calculateCompatibility(currentUser, profile);
            matches.push({
                profile,
                compatibility,
                userId
            });
        }

        // Sort by compatibility and return top matches
        return matches.sort((a, b) => b.compatibility - a.compatibility)
                     .slice(0, limit);
    }

    createProfileUI() {
        this.profilePanel = document.createElement('div');
        this.profilePanel.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 20px;
            color: white;
            width: 400px;
            max-width: 90vw;
            max-height: 80vh;
            overflow-y: auto;
            display: none;
            z-index: 2000;
        `;

        document.getElementById('container').appendChild(this.profilePanel);

        // Create profile button
        this.createProfileButton();
    }

    createProfileButton() {
        const profileButton = document.createElement('button');
        profileButton.textContent = '๐Ÿ‘ค';
        profileButton.title = 'My Profile';
        profileButton.style.cssText = `
            position: absolute;
            top: 20px;
            right: 70px;
            width: 40px;
            height: 40px;
            border: none;
            border-radius: 50%;
            background: rgba(255, 100, 100, 0.8);
            color: white;
            font-size: 18px;
            cursor: pointer;
            z-index: 1001;
        `;

        profileButton.addEventListener('click', () => {
            this.showProfile(this.currentUserProfile);
        });

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

    showProfile(profile, isEditable = false) {
        this.profilePanel.innerHTML = '';
        this.profilePanel.style.display = 'block';

        if (isEditable) {
            this.renderEditableProfile(profile);
        } else {
            this.renderProfileView(profile);
        }
    }

    renderProfileView(profile) {
        this.profilePanel.innerHTML = `
            <div style="text-align: center;">
                <h2>${profile.name}, ${profile.age}</h2>
                <div style="background: #333; padding: 10px; border-radius: 10px; margin: 10px 0;">
                    ${profile.bio || 'No bio yet'}
                </div>

                <h3>Interests</h3>
                <div style="display: flex; flex-wrap: wrap; gap: 5px; margin: 10px 0;">
                    ${profile.interests.map(interest => 
                        `<span style="background: #444; padding: 3px 8px; border-radius: 12px; font-size: 12px;">${interest}</span>`
                    ).join('')}
                </div>

                <h3>Looking For</h3>
                <div style="display: flex; flex-wrap: wrap; gap: 5px; margin: 10px 0;">
                    ${profile.lookingFor.map(item => 
                        `<span style="background: #555; padding: 3px 8px; border-radius: 12px; font-size: 12px;">${item}</span>`
                    ).join('')}
                </div>

                <div style="margin-top: 20px;">
                    <button onclick="profileSystem.showMatches()" style="margin-right: 10px; padding: 8px 16px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">Find Matches</button>
                    <button onclick="profileSystem.hideProfile()" style="padding: 8px 16px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer;">Close</button>
                </div>
            </div>
        `;

        if (profile.userId === this.currentUserProfile.userId) {
            const editButton = document.createElement('button');
            editButton.textContent = 'Edit Profile';
            editButton.style.cssText = `
                position: absolute;
                top: 20px;
                right: 20px;
                padding: 5px 10px;
                background: #2196F3;
                color: white;
                border: none;
                border-radius: 3px;
                cursor: pointer;
                font-size: 12px;
            `;
            editButton.addEventListener('click', () => {
                this.showProfile(profile, true);
            });
            this.profilePanel.appendChild(editButton);
        }
    }

    showMatches() {
        const matches = this.findPotentialMatches(5);

        this.profilePanel.innerHTML = `
            <h2>Potential Matches</h2>
            <div style="max-height: 300px; overflow-y: auto;">
                ${matches.length > 0 ? matches.map(match => `
                    <div style="background: #222; padding: 10px; margin: 5px 0; border-radius: 5px; cursor: pointer;" onclick="profileSystem.showProfile(profileSystem.profiles.get('${match.userId}'))">
                        <strong>${match.profile.name}, ${match.profile.age}</strong>
                        <div style="float: right; background: #4CAF50; padding: 2px 8px; border-radius: 10px; font-size: 12px;">
                            ${match.compatibility}% match
                        </div>
                        <div style="font-size: 12px; color: #aaa;">
                            ${match.profile.bio ? match.profile.bio.substring(0, 50) + '...' : 'No bio'}
                        </div>
                    </div>
                `).join('') : 
                '<p>No matches found at the moment. Try updating your profile!</p>'
                }
            </div>
            <button onclick="profileSystem.showProfile(profileSystem.currentUserProfile)" style="margin-top: 15px; padding: 8px 16px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer;">Back to Profile</button>
        `;
    }

    hideProfile() {
        this.profilePanel.style.display = 'none';
    }
}

Updated Main Application Integration

Finally, let's update our main application to integrate all these new social features:

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

        this.gestureSystem = null;
        this.privateSpaceSystem = null;
        this.profileSystem = null;
        this.gestureUI = null;

        this.init();
    }

    async init() {
        this.setupWebGL();
        this.setupShaders();
        this.setupCamera();
        this.setupLighting();
        this.setupTextures();
        this.setupScene();
        this.setupNetwork();
        this.setupSocialFeatures(); // New
        this.setupInteraction();
        this.render();
    }

    setupSocialFeatures() {
        // Initialize social systems
        this.gestureSystem = new GestureSystem();
        this.privateSpaceSystem = new PrivateSpaceSystem(this.sceneManager, this.chatConnection);
        this.profileSystem = new ProfileSystem(this.chatConnection);

        // Setup gesture UI
        this.gestureUI = new GestureUI(this.gestureSystem, this.chatConnection);

        // Setup gesture handlers
        this.setupGestureHandlers();

        // Create current user profile
        const currentUser = this.chatConnection.getCurrentUser();
        this.profileSystem.createProfile({
            userId: currentUser.id,
            name: currentUser.name,
            age: Math.floor(Math.random() * 20) + 20, // Random age 20-40
            gender: currentUser.gender,
            bio: "Hello! I'm new here and looking to meet interesting people.",
            interests: ['Music', 'Movies', 'Travel', 'Technology', 'Sports'],
            lookingFor: ['Friendship', 'Dating', 'Serious Relationship'],
            location: 'Online'
        });
    }

    setupGestureHandlers() {
        this.chatConnection.onMessage('gesture_perform', (data) => {
            const avatar = this.sceneManager.avatars.find(av => av.userId === data.user.id);
            if (avatar && this.gestureSystem.canPerformGesture(data.user.id, data.gestureId)) {
                avatar.playGesture(data.gestureId);
                this.gestureSystem.performGesture(data.user.id, data.gestureId);
            }
        });
    }

    // Update render loop to handle animations
    render() {
        const gl = this.gl;

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        gl.useProgram(this.program);

        // Set lighting uniforms
        this.lightingSystem.setLightUniforms(this.program, this.uniformLocations);

        // Get matrices from camera
        const viewMatrix = this.camera.getViewMatrix();
        const projectionMatrix = this.camera.getProjectionMatrix();

        // Calculate normal matrix
        const normalMatrix = this.calculateNormalMatrix(viewMatrix);
        gl.uniformMatrix4fv(this.uniformLocations.normalMatrix, false, normalMatrix);

        // Update animations
        const currentTime = Date.now();
        const deltaTime = this.lastFrameTime ? (currentTime - this.lastFrameTime) / 1000 : 0.016;
        this.lastFrameTime = currentTime;

        this.updateAnimations(deltaTime);

        // Render scene
        this.sceneManager.render(
            gl,
            this.program,
            this.attribLocations,
            this.uniformLocations,
            viewMatrix,
            projectionMatrix
        );

        // Send avatar updates to server
        this.sendAvatarUpdates();

        requestAnimationFrame(() => this.render());
    }

    updateAnimations(deltaTime) {
        // Update all avatar animations
        for (const avatar of this.sceneManager.avatars) {
            if (avatar.update) {
                avatar.update(deltaTime);
            }
        }
    }

    // ... rest of the class
}

// Make systems globally accessible for UI callbacks
window.gestureSystem = null;
window.privateSpaceSystem = null;
window.profileSystem = null;

// Update initialization to set global references
window.addEventListener('load', async () => {
    window.datingChat = new DatingChat3D();

    // Set global references after initialization
    setTimeout(() => {
        window.gestureSystem = window.datingChat.gestureSystem;
        window.privateSpaceSystem = window.datingChat.privateSpaceSystem;
        window.profileSystem = window.datingChat.profileSystem;
    }, 1000);
});

What We've Accomplished in Part 4

In this fourth part, we've transformed our 3D dating chat into a vibrant social platform with:

  1. Advanced Facial Expression System with blend shapes and automatic emotion detection
  2. Comprehensive Animation System for natural avatar movements and gestures
  3. Gesture and Emote System with UI for social interactions
  4. Private Chat Spaces for intimate conversations with invitation system
  5. User Profile System with matching algorithm and compatibility scoring

Key Features Added:

Next Steps

In Part 5, we'll focus on:

Our dating platform now has the social features needed for meaningful interactions, making it a truly engaging 3D chat experience!

Part 5: Advanced Audio, Video, and Environment Interaction

Welcome to Part 5 of our 10-part tutorial series! In this installment, we'll add spatial audio, video integration, interactive environments, and optimize performance for a truly immersive dating experience.

Table of Contents for Part 5

  1. Spatial Audio System
  2. Video Integration
  3. Interactive Environment
  4. Performance Optimization
  5. Mobile Support

1. Spatial Audio System

Let's implement a spatial audio system that makes conversations feel natural by adjusting volume and panning based on avatar positions:

// Spatial audio manager for 3D sound positioning
class SpatialAudioSystem {
    constructor() {
        this.audioContext = null;
        this.audioElements = new Map();
        this.listenerPosition = [0, 0, 0];
        this.maxDistance = 20;
        this.rolloffFactor = 1;
        this.ambientSounds = new Map();

        this.initAudioContext();
    }

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

            // Resume audio context on user interaction (browser requirement)
            document.addEventListener('click', () => {
                if (this.audioContext.state === 'suspended') {
                    this.audioContext.resume();
                }
            }, { once: true });

            console.log('Audio context initialized successfully');
        } catch (error) {
            console.error('Failed to initialize audio context:', error);
        }
    }

    // Create spatial audio for a user's voice
    createUserAudio(userId, stream = null) {
        if (!this.audioContext) return null;

        const audioConfig = {
            source: null,
            panner: null,
            gain: null,
            stream: stream,
            isPlaying: false
        };

        // Create audio graph: Source -> Panner -> Gain -> Destination
        if (stream) {
            audioConfig.source = this.audioContext.createMediaStreamSource(stream);
        } else {
            audioConfig.source = this.audioContext.createBufferSource();
        }

        audioConfig.panner = this.audioContext.createPanner();
        audioConfig.gain = this.audioContext.createGain();

        // Configure panner for 3D spatial audio
        audioConfig.panner.panningModel = 'HRTF';
        audioConfig.panner.distanceModel = 'inverse';
        audioConfig.panner.refDistance = 1;
        audioConfig.panner.maxDistance = this.maxDistance;
        audioConfig.panner.rolloffFactor = this.rolloffFactor;
        audioConfig.panner.coneInnerAngle = 360;
        audioConfig.panner.coneOuterAngle = 0;
        audioConfig.panner.coneOuterGain = 0;

        // Connect audio nodes
        audioConfig.source.connect(audioConfig.panner);
        audioConfig.panner.connect(audioConfig.gain);
        audioConfig.gain.connect(this.audioContext.destination);

        // Set initial volume
        audioConfig.gain.gain.value = 0;

        this.audioElements.set(userId, audioConfig);
        return audioConfig;
    }

    // Update audio position based on avatar position
    updateAudioPosition(userId, position) {
        const audioConfig = this.audioElements.get(userId);
        if (!audioConfig || !audioConfig.panner) return;

        // Convert our coordinate system to audio coordinates
        // WebAudio uses right-handed coordinate system
        audioConfig.panner.positionX.setValueAtTime(position[0], this.audioContext.currentTime);
        audioConfig.panner.positionY.setValueAtTime(position[1], this.audioContext.currentTime);
        audioConfig.panner.positionZ.setValueAtTime(-position[2], this.audioContext.currentTime); // Invert Z for right-handed

        // Calculate volume based on distance
        const distance = this.calculateDistance(this.listenerPosition, position);
        const volume = this.calculateVolumeAtDistance(distance);

        audioConfig.gain.gain.setTargetAtTime(volume, this.audioContext.currentTime, 0.1);
    }

    // Update listener position (camera position)
    updateListenerPosition(position, forward = [0, 0, -1], up = [0, 1, 0]) {
        if (!this.audioContext || !this.audioContext.listener) return;

        this.listenerPosition = position;

        // Set listener position and orientation
        this.audioContext.listener.positionX.setValueAtTime(position[0], this.audioContext.currentTime);
        this.audioContext.listener.positionY.setValueAtTime(position[1], this.audioContext.currentTime);
        this.audioContext.listener.positionZ.setValueAtTime(-position[2], this.audioContext.currentTime);

        // Set listener orientation (forward vector, up vector)
        this.audioContext.listener.forwardX.setValueAtTime(forward[0], this.audioContext.currentTime);
        this.audioContext.listener.forwardY.setValueAtTime(forward[1], this.audioContext.currentTime);
        this.audioContext.listener.forwardZ.setValueAtTime(-forward[2], this.audioContext.currentTime);

        this.audioContext.listener.upX.setValueAtTime(up[0], this.audioContext.currentTime);
        this.audioContext.listener.upY.setValueAtTime(up[1], this.audioContext.currentTime);
        this.audioContext.listener.upZ.setValueAtTime(-up[2], this.audioContext.currentTime);
    }

    calculateDistance(pos1, pos2) {
        const dx = pos1[0] - pos2[0];
        const dy = pos1[1] - pos2[1];
        const dz = pos1[2] - pos2[2];
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    calculateVolumeAtDistance(distance) {
        if (distance <= 1) return 1.0; // Full volume within 1 unit

        // Inverse distance model
        const volume = 1.0 / (1 + this.rolloffFactor * (distance - 1));
        return Math.max(0, Math.min(1, volume));
    }

    // Play ambient background sounds
    async addAmbientSound(name, url, position = [0, 0, 0], volume = 0.3, loop = true) {
        if (!this.audioContext) return;

        try {
            // Load audio buffer
            const response = await fetch(url);
            const arrayBuffer = await response.arrayBuffer();
            const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);

            // Create audio source
            const source = this.audioContext.createBufferSource();
            const panner = this.audioContext.createPanner();
            const gain = this.audioContext.createGain();

            source.buffer = audioBuffer;
            source.loop = loop;

            // Configure spatial audio
            panner.panningModel = 'HRTF';
            panner.distanceModel = 'inverse';
            panner.refDistance = 1;
            panner.maxDistance = 50;
            panner.rolloffFactor = 0.5;

            panner.positionX.setValueAtTime(position[0], this.audioContext.currentTime);
            panner.positionY.setValueAtTime(position[1], this.audioContext.currentTime);
            panner.positionZ.setValueAtTime(-position[2], this.audioContext.currentTime);

            gain.gain.value = volume;

            // Connect and play
            source.connect(panner);
            panner.connect(gain);
            gain.connect(this.audioContext.destination);

            source.start();

            this.ambientSounds.set(name, { source, panner, gain });

        } catch (error) {
            console.error('Failed to load ambient sound:', error);
        }
    }

    // Voice activity detection
    setupVoiceActivityDetection(stream, userId) {
        if (!this.audioContext) return;

        const audioConfig = this.createUserAudio(userId, stream);
        if (!audioConfig) return;

        // Create analyzer for voice activity detection
        const analyzer = this.audioContext.createAnalyser();
        analyzer.fftSize = 256;
        analyzer.smoothingTimeConstant = 0.8;

        audioConfig.source.connect(analyzer);

        const dataArray = new Uint8Array(analyzer.frequencyBinCount);
        let silenceStart = Date.now();
        const silenceThreshold = 1500; // 1.5 seconds

        const checkVoiceActivity = () => {
            analyzer.getByteFrequencyData(dataArray);

            // Calculate average volume
            let sum = 0;
            for (let i = 0; i < dataArray.length; i++) {
                sum += dataArray[i];
            }
            const average = sum / dataArray.length;

            const isSpeaking = average > 30; // Threshold for voice detection

            if (isSpeaking) {
                silenceStart = Date.now();
                this.onVoiceActivity(userId, true);
            } else if (Date.now() - silenceStart > silenceThreshold) {
                this.onVoiceActivity(userId, false);
            }

            requestAnimationFrame(checkVoiceActivity);
        };

        checkVoiceActivity();
    }

    onVoiceActivity(userId, isSpeaking) {
        // Update avatar speaking state
        const avatar = window.datingChat?.sceneManager?.avatars.find(av => av.userId === userId);
        if (avatar) {
            if (isSpeaking) {
                avatar.startSpeaking();
            } else {
                avatar.stopSpeaking();
            }
        }

        // Visual feedback in UI
        this.updateSpeakingIndicator(userId, isSpeaking);
    }

    updateSpeakingIndicator(userId, isSpeaking) {
        let indicator = document.getElementById(`voice-indicator-${userId}`);

        if (isSpeaking && !indicator) {
            indicator = document.createElement('div');
            indicator.id = `voice-indicator-${userId}`;
            indicator.innerHTML = '๐ŸŽค';
            indicator.style.cssText = `
                position: absolute;
                background: rgba(255, 0, 0, 0.7);
                border-radius: 50%;
                width: 20px;
                height: 20px;
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 10px;
                z-index: 100;
            `;
            document.getElementById('container').appendChild(indicator);
        } else if (!isSpeaking && indicator) {
            indicator.remove();
        }
    }

    // Audio controls
    setMasterVolume(volume) {
        if (!this.audioContext) return;

        // Update all gain nodes
        for (const audioConfig of this.audioElements.values()) {
            if (audioConfig.gain) {
                audioConfig.gain.gain.value = volume;
            }
        }

        for (const ambientSound of this.ambientSounds.values()) {
            if (ambientSound.gain) {
                ambientSound.gain.gain.value = volume;
            }
        }
    }

    muteUser(userId) {
        const audioConfig = this.audioElements.get(userId);
        if (audioConfig && audioConfig.gain) {
            audioConfig.gain.gain.value = 0;
        }
    }

    unmuteUser(userId) {
        const audioConfig = this.audioElements.get(userId);
        if (audioConfig && audioConfig.gain) {
            // Volume will be recalculated based on position
            this.updateAudioPosition(userId, [0, 0, 0]);
        }
    }
}

2. Video Integration

Now let's add video streaming capabilities for face-to-face conversations:

// Video streaming system for webcam integration
class VideoStreamSystem {
    constructor() {
        this.localStream = null;
        this.remoteStreams = new Map();
        this.videoElements = new Map();
        this.isVideoEnabled = false;

        this.setupVideoUI();
    }

    async startLocalVideo() {
        try {
            // Get user media with video and audio
            this.localStream = await navigator.mediaDevices.getUserMedia({
                video: {
                    width: { ideal: 640 },
                    height: { ideal: 480 },
                    frameRate: { ideal: 30 }
                },
                audio: true
            });

            // Create local video element
            this.createVideoElement('local', this.localStream, true);

            // Setup voice activity detection
            if (window.audioSystem) {
                window.audioSystem.setupVoiceActivityDetection(this.localStream, 'local');
            }

            this.isVideoEnabled = true;
            this.updateVideoUI();

            // Send stream to other users (in real app, via WebRTC)
            this.broadcastLocalStream();

            return this.localStream;

        } catch (error) {
            console.error('Error accessing camera:', error);
            this.showCameraError();
            return null;
        }
    }

    stopLocalVideo() {
        if (this.localStream) {
            this.localStream.getTracks().forEach(track => track.stop());
            this.localStream = null;
        }

        const localVideo = this.videoElements.get('local');
        if (localVideo) {
            localVideo.remove();
            this.videoElements.delete('local');
        }

        this.isVideoEnabled = false;
        this.updateVideoUI();
    }

    createVideoElement(userId, stream, isLocal = false) {
        // Remove existing video element if any
        const existingVideo = this.videoElements.get(userId);
        if (existingVideo) {
            existingVideo.remove();
        }

        // Create video element
        const video = document.createElement('video');
        video.srcObject = stream;
        video.autoplay = true;
        video.playsInline = true;
        video.muted = isLocal; // Mute local video to avoid feedback

        video.style.cssText = `
            position: absolute;
            ${isLocal ? 
                'bottom: 20px; right: 20px; width: 160px; height: 120px;' : 
                'top: 20px; left: 20px; width: 200px; height: 150px;'
            }
            border: 2px solid ${isLocal ? '#4CAF50' : '#2196F3'};
            border-radius: 10px;
            background: #000;
            z-index: 1000;
            object-fit: cover;
        `;

        // Add user name label
        const label = document.createElement('div');
        label.textContent = isLocal ? 'You' : `User ${userId}`;
        label.style.cssText = `
            position: absolute;
            top: -25px;
            left: 5px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 2px 8px;
            border-radius: 10px;
            font-size: 12px;
        `;
        video.appendChild(label);

        // Add controls
        const controls = this.createVideoControls(video, userId, isLocal);
        video.appendChild(controls);

        document.getElementById('container').appendChild(video);
        this.videoElements.set(userId, video);

        return video;
    }

    createVideoControls(video, userId, isLocal) {
        const controls = document.createElement('div');
        controls.style.cssText = `
            position: absolute;
            bottom: 5px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 5px;
            opacity: 0;
            transition: opacity 0.3s;
        `;

        video.addEventListener('mouseenter', () => {
            controls.style.opacity = '1';
        });

        video.addEventListener('mouseleave', () => {
            controls.style.opacity = '0';
        });

        if (!isLocal) {
            const pinBtn = document.createElement('button');
            pinBtn.innerHTML = '๐Ÿ“Œ';
            pinBtn.title = 'Pin video';
            pinBtn.style.cssText = `
                background: rgba(0, 0, 0, 0.7);
                border: none;
                border-radius: 3px;
                color: white;
                padding: 2px 5px;
                cursor: pointer;
                font-size: 10px;
            `;
            pinBtn.addEventListener('click', () => this.togglePinVideo(userId));
            controls.appendChild(pinBtn);
        }

        const fullscreenBtn = document.createElement('button');
        fullscreenBtn.innerHTML = 'โ›ถ';
        fullscreenBtn.title = 'Fullscreen';
        fullscreenBtn.style.cssText = `
            background: rgba(0, 0, 0, 0.7);
            border: none;
            border-radius: 3px;
            color: white;
            padding: 2px 5px;
            cursor: pointer;
            font-size: 10px;
        `;
        fullscreenBtn.addEventListener('click', () => this.toggleFullscreen(video));
        controls.appendChild(fullscreenBtn);

        return controls;
    }

    togglePinVideo(userId) {
        const video = this.videoElements.get(userId);
        if (video) {
            video.classList.toggle('pinned');
            if (video.classList.contains('pinned')) {
                video.style.position = 'fixed';
                video.style.top = '50%';
                video.style.left = '50%';
                video.style.transform = 'translate(-50%, -50%)';
                video.style.width = '40vw';
                video.style.height = '30vw';
                video.style.zIndex = '10000';
            } else {
                video.style.position = 'absolute';
                video.style.top = '20px';
                video.style.left = '20px';
                video.style.transform = 'none';
                video.style.width = '200px';
                video.style.height = '150px';
                video.style.zIndex = '1000';
            }
        }
    }

    toggleFullscreen(video) {
        if (!document.fullscreenElement) {
            video.requestFullscreen().catch(err => {
                console.error('Error attempting to enable fullscreen:', err);
            });
        } else {
            document.exitFullscreen();
        }
    }

    // Handle incoming remote streams
    addRemoteStream(userId, stream) {
        this.remoteStreams.set(userId, stream);
        this.createVideoElement(userId, stream, false);

        // Setup spatial audio for remote user
        if (window.audioSystem) {
            window.audioSystem.createUserAudio(userId, stream);
        }
    }

    removeRemoteStream(userId) {
        this.remoteStreams.delete(userId);
        const video = this.videoElements.get(userId);
        if (video) {
            video.remove();
            this.videoElements.delete(userId);
        }
    }

    setupVideoUI() {
        // Create video control panel
        this.videoControlPanel = document.createElement('div');
        this.videoControlPanel.style.cssText = `
            position: absolute;
            bottom: 130px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 10px;
            padding: 10px;
            display: flex;
            gap: 10px;
            z-index: 1001;
        `;

        // Video toggle button
        this.videoToggleBtn = document.createElement('button');
        this.videoToggleBtn.innerHTML = '๐Ÿ“น';
        this.videoToggleBtn.title = 'Start Video';
        this.videoToggleBtn.style.cssText = `
            width: 40px;
            height: 40px;
            border: none;
            border-radius: 50%;
            background: #f44336;
            color: white;
            font-size: 16px;
            cursor: pointer;
            transition: background 0.3s;
        `;

        this.videoToggleBtn.addEventListener('click', () => {
            if (this.isVideoEnabled) {
                this.stopLocalVideo();
            } else {
                this.startLocalVideo();
            }
        });

        // Audio toggle button
        this.audioToggleBtn = document.createElement('button');
        this.audioToggleBtn.innerHTML = '๐ŸŽค';
        this.audioToggleBtn.title = 'Mute Audio';
        this.audioToggleBtn.style.cssText = `
            width: 40px;
            height: 40px;
            border: none;
            border-radius: 50%;
            background: #4CAF50;
            color: white;
            font-size: 16px;
            cursor: pointer;
        `;

        this.audioToggleBtn.addEventListener('click', () => {
            this.toggleAudio();
        });

        this.videoControlPanel.appendChild(this.videoToggleBtn);
        this.videoControlPanel.appendChild(this.audioToggleBtn);
        document.getElementById('container').appendChild(this.videoControlPanel);

        this.updateVideoUI();
    }

    updateVideoUI() {
        if (this.isVideoEnabled) {
            this.videoToggleBtn.innerHTML = '๐Ÿ“น';
            this.videoToggleBtn.style.background = '#4CAF50';
            this.videoToggleBtn.title = 'Stop Video';
        } else {
            this.videoToggleBtn.innerHTML = '๐Ÿ“น';
            this.videoToggleBtn.style.background = '#f44336';
            this.videoToggleBtn.title = 'Start Video';
        }
    }

    toggleAudio() {
        if (this.localStream) {
            const audioTracks = this.localStream.getAudioTracks();
            const isMuted = audioTracks[0]?.enabled === false;

            audioTracks.forEach(track => {
                track.enabled = isMuted;
            });

            this.audioToggleBtn.style.background = isMuted ? '#4CAF50' : '#f44336';
            this.audioToggleBtn.title = isMuted ? 'Mute Audio' : 'Unmute Audio';
        }
    }

    broadcastLocalStream() {
        // In a real application, this would use WebRTC to send the stream to other users
        // For now, we'll simulate this locally
        console.log('Local video stream ready for broadcasting');

        // Simulate receiving our own stream back (for testing)
        setTimeout(() => {
            this.addRemoteStream('remote-test', this.localStream);
        }, 1000);
    }

    showCameraError() {
        const errorDiv = document.createElement('div');
        errorDiv.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 0, 0, 0.9);
            color: white;
            padding: 20px;
            border-radius: 10px;
            text-align: center;
            z-index: 10000;
        `;
        errorDiv.innerHTML = `
            <h3>Camera Access Required</h3>
            <p>Please allow camera access to use video features.</p>
            <button onclick="this.parentElement.remove()" style="padding: 8px 16px; background: white; color: black; border: none; border-radius: 5px; cursor: pointer;">OK</button>
        `;
        document.getElementById('container').appendChild(errorDiv);
    }

    // Video recording for profile videos
    async startRecording() {
        if (!this.localStream) return null;

        try {
            const mediaRecorder = new MediaRecorder(this.localStream, {
                mimeType: 'video/webm;codecs=vp9'
            });

            const chunks = [];
            mediaRecorder.ondataavailable = (event) => {
                if (event.data.size > 0) {
                    chunks.push(event.data);
                }
            };

            mediaRecorder.onstop = () => {
                const blob = new Blob(chunks, { type: 'video/webm' });
                this.onRecordingComplete(blob);
            };

            mediaRecorder.start();
            return mediaRecorder;

        } catch (error) {
            console.error('Error starting recording:', error);
            return null;
        }
    }

    onRecordingComplete(blob) {
        // Create video preview and upload option
        const url = URL.createObjectURL(blob);

        const preview = document.createElement('video');
        preview.src = url;
        preview.controls = true;
        preview.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            max-width: 80vw;
            max-height: 80vh;
            background: black;
            z-index: 10000;
        `;

        const controls = document.createElement('div');
        controls.style.cssText = `
            position: absolute;
            bottom: -50px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 10px;
        `;

        const saveBtn = document.createElement('button');
        saveBtn.textContent = 'Save to Profile';
        saveBtn.style.cssText = `
            padding: 8px 16px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        `;
        saveBtn.addEventListener('click', () => {
            this.uploadProfileVideo(blob);
            preview.remove();
        });

        const cancelBtn = document.createElement('button');
        cancelBtn.textContent = 'Cancel';
        cancelBtn.style.cssText = `
            padding: 8px 16px;
            background: #f44336;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        `;
        cancelBtn.addEventListener('click', () => {
            preview.remove();
        });

        controls.appendChild(saveBtn);
        controls.appendChild(cancelBtn);

        preview.appendChild(controls);
        document.getElementById('container').appendChild(preview);
    }

    async uploadProfileVideo(blob) {
        // In a real app, upload to server
        console.log('Uploading profile video:', blob.size, 'bytes');

        // Simulate upload
        const progress = document.createElement('div');
        progress.style.cssText = `
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px;
            border-radius: 5px;
            z-index: 10000;
        `;
        progress.textContent = 'Uploading video...';
        document.getElementById('container').appendChild(progress);

        setTimeout(() => {
            progress.textContent = 'Video uploaded successfully!';
            setTimeout(() => progress.remove(), 2000);
        }, 2000);
    }
}

3. Interactive Environment

Let's create an interactive environment with clickable objects and dynamic elements:

// Interactive environment manager
class InteractiveEnvironment {
    constructor(sceneManager, camera) {
        this.sceneManager = sceneManager;
        this.camera = camera;
        this.interactiveObjects = new Map();
        this.hoveredObject = null;
        this.raycaster = new Raycaster();

        this.setupEventListeners();
        this.createInteractiveElements();
    }

    setupEventListeners() {
        const canvas = document.getElementById('webgl-canvas');

        // Mouse move for hover effects
        canvas.addEventListener('mousemove', (event) => {
            this.handleMouseMove(event);
        });

        // Click for interactions
        canvas.addEventListener('click', (event) => {
            this.handleClick(event);
        });

        // Touch events for mobile
        canvas.addEventListener('touchstart', (event) => {
            event.preventDefault();
            this.handleTouch(event);
        });
    }

    createInteractiveElements() {
        // Create interactive objects in the environment
        this.createSeatingAreas();
        this.createSocialZones();
        this.createDecorationObjects();
        this.createPortalEffects();
    }

    createSeatingAreas() {
        const chairPositions = [
            [-3, 0, -2], [3, 0, -2], [-5, 0, 2], [5, 0, 2]
        ];

        chairPositions.forEach((position, index) => {
            const chair = this.createChair(position, index);
            this.interactiveObjects.set(`chair_${index}`, chair);
            this.sceneManager.addObject(chair.object3D);
        });
    }

    createChair(position, id) {
        const chair = {
            type: 'chair',
            position: position,
            isOccupied: false,
            object3D: this.createChairGeometry(position),
            onClick: () => this.onChairClick(id),
            onHover: () => this.onChairHover(id),
            getWorldPosition: () => position
        };

        return chair;
    }

    createChairGeometry(position) {
        // Simple chair geometry
        const vertices = new Float32Array([
            // Seat
            -0.5, 0.5, -0.5,  0.5, 0.5, -0.5,  0.5, 0.5, 0.5,  -0.5, 0.5, 0.5,
            // Legs
            -0.4, 0.0, -0.4,  -0.4, 0.5, -0.4,  0.4, 0.5, -0.4,  0.4, 0.0, -0.4,
            -0.4, 0.0, 0.4,   -0.4, 0.5, 0.4,   0.4, 0.5, 0.4,   0.4, 0.0, 0.4
        ]);

        const colors = new Float32Array(Array(24).fill([0.6, 0.4, 0.2, 1.0]).flat());
        const indices = new Uint16Array([0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11]);

        const chairObject = new SceneObject(vertices, colors, indices, position);
        chairObject.type = 'chair';
        chairObject.interactiveId = `chair_${id}`;

        return chairObject;
    }

    createSocialZones() {
        const zones = [
            { position: [0, 0, -5], radius: 3, type: 'dance_floor', name: 'Dance Floor' },
            { position: [-8, 0, 0], radius: 2, type: 'quiet_corner', name: 'Quiet Corner' },
            { position: [8, 0, 0], radius: 2, type: 'game_zone', name: 'Game Zone' }
        ];

        zones.forEach((zone, index) => {
            const zoneObj = this.createSocialZone(zone, index);
            this.interactiveObjects.set(`zone_${index}`, zoneObj);
        });
    }

    createSocialZone(zoneConfig, id) {
        const zone = {
            type: zoneConfig.type,
            position: zoneConfig.position,
            radius: zoneConfig.radius,
            name: zoneConfig.name,
            object3D: this.createZoneVisual(zoneConfig),
            onClick: () => this.onZoneClick(zoneConfig.type),
            onHover: () => this.onZoneHover(zoneConfig.name),
            getWorldPosition: () => zoneConfig.position
        };

        return zone;
    }

    createZoneVisual(zoneConfig) {
        // Create visual representation of the zone
        // This would be a circular area on the floor
        const segments = 16;
        const vertices = [];
        const colors = [];

        for (let i = 0; i <= segments; i++) {
            const angle = (i / segments) * Math.PI * 2;
            const x = Math.cos(angle) * zoneConfig.radius;
            const z = Math.sin(angle) * zoneConfig.radius;

            vertices.push(x, 0.01, z); // Slightly above floor

            // Different colors for different zones
            let color;
            switch(zoneConfig.type) {
                case 'dance_floor': color = [1.0, 0.5, 0.5, 0.3]; break;
                case 'quiet_corner': color = [0.5, 0.8, 1.0, 0.3]; break;
                case 'game_zone': color = [0.5, 1.0, 0.5, 0.3]; break;
                default: color = [1.0, 1.0, 1.0, 0.3];
            }
            colors.push(...color);
        }

        // Center point
        vertices.push(0, 0.01, 0);
        colors.push(...[1.0, 1.0, 1.0, 0.5]);

        const indices = [];
        for (let i = 0; i < segments; i++) {
            indices.push(i, (i + 1) % segments, segments);
        }

        const zoneObject = new SceneObject(
            new Float32Array(vertices),
            new Float32Array(colors),
            new Uint16Array(indices),
            zoneConfig.position
        );
        zoneObject.type = 'zone';

        return zoneObject;
    }

    handleMouseMove(event) {
        const intersectedObject = this.raycastFromMouse(event);

        if (intersectedObject !== this.hoveredObject) {
            // Remove hover from previous object
            if (this.hoveredObject && this.hoveredObject.onHoverEnd) {
                this.hoveredObject.onHoverEnd();
            }

            // Apply hover to new object
            if (intersectedObject && intersectedObject.onHover) {
                intersectedObject.onHover();
                document.body.style.cursor = 'pointer';
            } else {
                document.body.style.cursor = 'default';
            }

            this.hoveredObject = intersectedObject;
        }
    }

    handleClick(event) {
        const intersectedObject = this.raycastFromMouse(event);

        if (intersectedObject && intersectedObject.onClick) {
            intersectedObject.onClick();
        }
    }

    handleTouch(event) {
        if (event.touches.length === 1) {
            const touch = event.touches[0];
            const mouseEvent = new MouseEvent('click', {
                clientX: touch.clientX,
                clientY: touch.clientY
            });
            this.handleClick(mouseEvent);
        }
    }

    raycastFromMouse(event) {
        const canvas = document.getElementById('webgl-canvas');
        const rect = canvas.getBoundingClientRect();

        // Normalized device coordinates
        const x = ((event.clientX - rect.left) / canvas.width) * 2 - 1;
        const y = -((event.clientY - rect.top) / canvas.height) * 2 + 1;

        // Simple 2D to 3D projection (simplified)
        // In a real implementation, you'd use proper raycasting with your 3D scene
        const cameraPos = this.camera.eye;
        const mouseWorldPos = this.screenToWorld(x, y);

        // Find closest interactive object
        let closestObject = null;
        let closestDistance = Infinity;

        for (const [id, obj] of this.interactiveObjects) {
            const objPos = obj.getWorldPosition();
            const distance = this.calculateDistance(mouseWorldPos, objPos);

            if (distance < 2.0 && distance < closestDistance) { // 2.0 is interaction range
                closestDistance = distance;
                closestObject = obj;
            }
        }

        return closestObject;
    }

    screenToWorld(x, y) {
        // Simplified screen to world projection
        // This would be more complex in a real 3D engine
        const cameraPos = this.camera.eye;
        const cameraDir = this.camera.getLookDirection();

        // Project mouse to ground plane (y=0)
        if (cameraDir[1] !== 0) {
            const t = -cameraPos[1] / cameraDir[1];
            return [
                cameraPos[0] + cameraDir[0] * t,
                0,
                cameraPos[2] + cameraDir[2] * t
            ];
        }

        return [x * 10, 0, y * 10]; // Fallback
    }

    calculateDistance(pos1, pos2) {
        const dx = pos1[0] - pos2[0];
        const dy = pos1[1] - pos2[1];
        const dz = pos1[2] - pos2[2];
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    onChairClick(chairId) {
        const chair = this.interactiveObjects.get(`chair_${chairId}`);
        if (chair && !chair.isOccupied) {
            chair.isOccupied = true;

            // Move user's avatar to chair
            const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
            if (currentUser) {
                const avatar = window.datingChat.sceneManager.avatars.find(av => av.userId === currentUser.id);
                if (avatar) {
                    avatar.moveTo(chair.position);
                    avatar.playGesture('sit');
                }
            }

            this.showNotification(`Sitting in chair ${chairId + 1}`);
        }
    }

    onChairHover(chairId) {
        const chair = this.interactiveObjects.get(`chair_${chairId}`);
        if (chair) {
            // Visual feedback for hover
            console.log(`Hovering over chair ${chairId + 1}`);
        }
    }

    onZoneClick(zoneType) {
        switch(zoneType) {
            case 'dance_floor':
                this.activateDanceFloor();
                break;
            case 'quiet_corner':
                this.activateQuietCorner();
                break;
            case 'game_zone':
                this.activateGameZone();
                break;
        }
    }

    onZoneHover(zoneName) {
        this.showTooltip(zoneName);
    }

    activateDanceFloor() {
        // Start dance party mode
        this.showNotification('Dance floor activated!');

        // Make all avatars dance
        window.datingChat.sceneManager.avatars.forEach(avatar => {
            if (avatar.playGesture) {
                avatar.playGesture('dance');
            }
        });

        // Change ambient lighting
        this.startLightShow();
    }

    activateQuietCorner() {
        this.showNotification('Welcome to the quiet corner');

        // Reduce audio volume in this area
        if (window.audioSystem) {
            window.audioSystem.setMasterVolume(0.3);
        }
    }

    activateGameZone() {
        this.showNotification('Game zone - coming soon!');
        // Would launch mini-games in future implementation
    }

    startLightShow() {
        // Simple color cycling effect
        let hue = 0;
        const lightInterval = setInterval(() => {
            const color = this.HSLToRGB(hue, 100, 50);
            document.body.style.background = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
            hue = (hue + 1) % 360;
        }, 100);

        // Stop after 30 seconds
        setTimeout(() => {
            clearInterval(lightInterval);
            document.body.style.background = '#1a1a1a';
        }, 30000);
    }

    HSLToRGB(h, s, l) {
        s /= 100;
        l /= 100;
        const k = n => (n + h / 30) % 12;
        const a = s * Math.min(l, 1 - l);
        const f = n => l - a * Math.max(-1, Math.min(k๐Ÿ‘Ž - 3, Math.min(9 - k๐Ÿ‘Ž, 1)));
        return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
    }

    showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 15px 25px;
            border-radius: 25px;
            z-index: 1000;
            font-size: 14px;
        `;
        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }

    showTooltip(text) {
        let tooltip = document.getElementById('environment-tooltip');
        if (!tooltip) {
            tooltip = document.createElement('div');
            tooltip.id = 'environment-tooltip';
            tooltip.style.cssText = `
                position: absolute;
                background: rgba(0, 0, 0, 0.8);
                color: white;
                padding: 5px 10px;
                border-radius: 10px;
                font-size: 12px;
                pointer-events: none;
                z-index: 1000;
                transition: opacity 0.3s;
            `;
            document.getElementById('container').appendChild(tooltip);
        }

        tooltip.textContent = text;
        tooltip.style.opacity = '1';

        // Position near mouse
        document.addEventListener('mousemove', (e) => {
            tooltip.style.left = (e.clientX + 10) + 'px';
            tooltip.style.top = (e.clientY + 10) + 'px';
        });

        // Hide after delay
        clearTimeout(this.tooltipTimeout);
        this.tooltipTimeout = setTimeout(() => {
            tooltip.style.opacity = '0';
        }, 2000);
    }
}

4. Performance Optimization

Let's implement performance optimizations for better frame rates and larger user counts:

// Performance optimization system
class PerformanceOptimizer {
    constructor(sceneManager, renderer) {
        this.sceneManager = sceneManager;
        this.renderer = renderer;
        this.frameRate = 0;
        this.lastFrameTime = 0;
        this.frameCount = 0;
        this.qualityLevel = 'high'; // high, medium, low

        this.setupPerformanceMonitoring();
        this.applyOptimizations();
    }

    setupPerformanceMonitoring() {
        // Frame rate counter
        const fpsDisplay = document.createElement('div');
        fpsDisplay.id = 'fps-display';
        fpsDisplay.style.cssText = `
            position: absolute;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.7);
            color: #0f0;
            padding: 5px 10px;
            border-radius: 5px;
            font-family: monospace;
            font-size: 12px;
            z-index: 1000;
        `;
        document.getElementById('container').appendChild(fpsDisplay);

        // Update FPS counter
        const updateFPS = () => {
            const now = performance.now();
            if (this.lastFrameTime > 0) {
                this.frameRate = Math.round(1000 / (now - this.lastFrameTime));
            }
            this.lastFrameTime = now;

            fpsDisplay.textContent = `FPS: ${this.frameRate}`;

            // Color coding based on performance
            if (this.frameRate < 30) {
                fpsDisplay.style.color = '#f00';
            } else if (this.frameRate < 50) {
                fpsDisplay.style.color = '#ff0';
            } else {
                fpsDisplay.style.color = '#0f0';
            }

            requestAnimationFrame(updateFPS);
        };

        updateFPS();

        // Adaptive quality adjustment
        setInterval(() => {
            this.adaptiveQualityAdjustment();
        }, 5000);
    }

    adaptiveQualityAdjustment() {
        if (this.frameRate < 25 && this.qualityLevel !== 'low') {
            this.setQualityLevel('low');
        } else if (this.frameRate < 40 && this.qualityLevel === 'high') {
            this.setQualityLevel('medium');
        } else if (this.frameRate > 55 && this.qualityLevel !== 'high') {
            this.setQualityLevel('high');
        }
    }

    setQualityLevel(level) {
        if (this.qualityLevel === level) return;

        this.qualityLevel = level;
        console.log(`Setting quality level to: ${level}`);

        switch(level) {
            case 'high':
                this.applyHighQualitySettings();
                break;
            case 'medium':
                this.applyMediumQualitySettings();
                break;
            case 'low':
                this.applyLowQualitySettings();
                break;
        }

        this.showQualityNotification(level);
    }

    applyHighQualitySettings() {
        // High quality settings
        this.renderer.setRenderQuality(1.0);
        this.sceneManager.setDetailLevel('high');
        this.enableAdvancedLighting(true);
        this.setShadowQuality('high');
        this.setTextureQuality('high');
    }

    applyMediumQualitySettings() {
        // Medium quality settings
        this.renderer.setRenderQuality(0.75);
        this.sceneManager.setDetailLevel('medium');
        this.enableAdvancedLighting(false);
        this.setShadowQuality('medium');
        this.setTextureQuality('medium');
    }

    applyLowQualitySettings() {
        // Low quality settings
        this.renderer.setRenderQuality(0.5);
        this.sceneManager.setDetailLevel('low');
        this.enableAdvancedLighting(false);
        this.setShadowQuality('low');
        this.setTextureQuality('low');
    }

    applyOptimizations() {
        // Initial optimizations
        this.enableFrustumCulling();
        this.enableOcclusionCulling();
        this.setupLODSystem();
        this.optimizeGeometry();
        this.batchRenderCalls();
    }

    enableFrustumCulling() {
        // Only render objects within camera view
        console.log('Frustum culling enabled');
    }

    enableOcclusionCulling() {
        // Don't render objects behind other objects
        console.log('Occlusion culling enabled');
    }

    setupLODSystem() {
        // Level of Detail system - use simpler models for distant objects
        console.log('LOD system initialized');
    }

    optimizeGeometry() {
        // Reduce vertex counts for better performance
        this.sceneManager.objects.forEach(obj => {
            if (obj.optimizeGeometry) {
                obj.optimizeGeometry(this.qualityLevel);
            }
        });
    }

    batchRenderCalls() {
        // Combine similar objects into single draw calls
        console.log('Render call batching enabled');
    }

    enableAdvancedLighting(enabled) {
        // Toggle advanced lighting features
        if (window.datingChat?.lightingSystem) {
            window.datingChat.lightingSystem.setAdvancedLighting(enabled);
        }
    }

    setShadowQuality(quality) {
        // Adjust shadow quality
        console.log(`Shadow quality set to: ${quality}`);
    }

    setTextureQuality(quality) {
        // Adjust texture resolution
        const resolution = quality === 'high' ? 1024 : quality === 'medium' ? 512 : 256;
        console.log(`Texture resolution set to: ${resolution}`);
    }

    showQualityNotification(level) {
        const notification = document.createElement('div');
        notification.textContent = `Quality: ${level.toUpperCase()}`;
        notification.style.cssText = `
            position: absolute;
            bottom: 180px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 5px 10px;
            border-radius: 10px;
            font-size: 12px;
            z-index: 1000;
        `;
        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 2000);
    }

    // Memory management
    cleanupUnusedResources() {
        // Clean up unused textures, geometries, etc.
        console.log('Cleaning up unused resources');
    }

    // Avatar culling - only render nearby avatars
    updateAvatarVisibility(cameraPosition, maxDistance = 50) {
        this.sceneManager.avatars.forEach(avatar => {
            const distance = this.calculateDistance(cameraPosition, avatar.position);
            avatar.visible = distance <= maxDistance;
        });
    }

    calculateDistance(pos1, pos2) {
        const dx = pos1[0] - pos2[0];
        const dy = pos1[1] - pos2[1];
        const dz = pos1[2] - pos2[2];
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }
}

5. Mobile Support

Let's add mobile device support with touch controls and responsive design:

// Mobile device support and touch controls
class MobileSupport {
    constructor(camera, sceneManager) {
        this.camera = camera;
        this.sceneManager = sceneManager;
        this.isMobile = this.detectMobile();
        this.touchStart = { x: 0, y: 0 };
        this.touchMove = { x: 0, y: 0 };

        if (this.isMobile) {
            this.enableMobileMode();
        }
    }

    detectMobile() {
        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
               window.innerWidth <= 768;
    }

    enableMobileMode() {
        console.log('Mobile mode enabled');

        this.setupTouchControls();
        this.optimizeForMobile();
        this.createMobileUI();
        this.adjustCameraForMobile();
    }

    setupTouchControls() {
        const canvas = document.getElementById('webgl-canvas');

        // Touch camera rotation
        canvas.addEventListener('touchstart', (e) => {
            if (e.touches.length === 1) {
                this.touchStart.x = e.touches[0].clientX;
                this.touchStart.y = e.touches[0].clientY;
            }
        });

        canvas.addEventListener('touchmove', (e) => {
            if (e.touches.length === 1) {
                e.preventDefault();

                this.touchMove.x = e.touches[0].clientX;
                this.touchMove.y = e.touches[0].clientY;

                const deltaX = this.touchMove.x - this.touchStart.x;
                const deltaY = this.touchMove.y - this.touchStart.y;

                // Rotate camera based on touch movement
                this.camera.yaw -= deltaX * 0.01;
                this.camera.pitch -= deltaY * 0.01;

                // Limit pitch
                this.camera.pitch = Math.max(-Math.PI/2 + 0.1, 
                                     Math.min(Math.PI/2 - 0.1, this.camera.pitch));

                this.touchStart.x = this.touchMove.x;
                this.touchStart.y = this.touchMove.y;
            }
        });

        // Pinch to zoom
        let initialDistance = 0;

        canvas.addEventListener('touchstart', (e) => {
            if (e.touches.length === 2) {
                initialDistance = this.getTouchDistance(e.touches[0], e.touches[1]);
            }
        });

        canvas.addEventListener('touchmove', (e) => {
            if (e.touches.length === 2) {
                e.preventDefault();

                const currentDistance = this.getTouchDistance(e.touches[0], e.touches[1]);
                const zoomDelta = (initialDistance - currentDistance) * 0.01;

                this.camera.distance += zoomDelta;
                this.camera.distance = Math.max(2, Math.min(20, this.camera.distance));

                initialDistance = currentDistance;
            }
        });
    }

    getTouchDistance(touch1, touch2) {
        const dx = touch1.clientX - touch2.clientX;
        const dy = touch1.clientY - touch2.clientY;
        return Math.sqrt(dx * dx + dy * dy);
    }

    optimizeForMobile() {
        // Reduce quality for mobile devices
        if (window.performanceOptimizer) {
            window.performanceOptimizer.setQualityLevel('medium');
        }

        // Reduce maximum avatar count
        this.sceneManager.maxAvatars = 10;

        // Simpler shaders for mobile
        this.useMobileShaders();
    }

    useMobileShaders() {
        // Simplified shaders for better mobile performance
        const mobileVertexShader = `
            attribute vec4 aVertexPosition;
            attribute vec4 aVertexColor;

            uniform mat4 uModelViewMatrix;
            uniform mat4 uProjectionMatrix;

            varying lowp vec4 vColor;

            void main(void) {
                gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
                vColor = aVertexColor;
            }
        `;

        const mobileFragmentShader = `
            precision mediump float;
            varying lowp vec4 vColor;

            void main(void) {
                gl_FragColor = vColor;
            }
        `;

        // Use these simpler shaders on mobile
        if (this.isMobile) {
            window.datingChat.vertexShaderSource = mobileVertexShader;
            window.datingChat.fragmentShaderSource = mobileFragmentShader;
        }
    }

    createMobileUI() {
        // Create virtual joystick for movement
        this.createVirtualJoystick();

        // Mobile-specific buttons
        this.createMobileButtons();

        // Adjust UI sizing for mobile
        this.adjustUISizing();
    }

    createVirtualJoystick() {
        const joystickContainer = document.createElement('div');
        joystickContainer.id = 'virtual-joystick';
        joystickContainer.style.cssText = `
            position: absolute;
            bottom: 100px;
            left: 30px;
            width: 100px;
            height: 100px;
            background: rgba(255, 255, 255, 0.2);
            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%;
            transform: translate(-50%, -50%);
            width: 40px;
            height: 40px;
            background: rgba(255, 255, 255, 0.8);
            border-radius: 50%;
        `;

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

        this.setupJoystickEvents(joystickContainer, joystickKnob);
    }

    setupJoystickEvents(container, knob) {
        let isTouching = false;
        const centerX = container.offsetLeft + container.offsetWidth / 2;
        const centerY = container.offsetTop + container.offsetHeight / 2;
        const maxDistance = container.offsetWidth / 2 - knob.offsetWidth / 2;

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

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

            const touch = e.touches[0];
            const deltaX = touch.clientX - centerX;
            const deltaY = touch.clientY - centerY;
            const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
            const angle = Math.atan2(deltaY, deltaX);

            // Limit knob movement
            const limitedDistance = Math.min(distance, maxDistance);
            const knobX = Math.cos(angle) * limitedDistance;
            const knobY = Math.sin(angle) * limitedDistance;

            knob.style.transform = `translate(${knobX}px, ${knobY}px)`;

            // Move camera based on joystick
            if (limitedDistance > 10) {
                const moveSpeed = limitedDistance / maxDistance * 0.1;
                const moveX = Math.cos(angle) * moveSpeed;
                const moveZ = Math.sin(angle) * moveSpeed;

                this.camera.moveRight(moveX);
                this.camera.moveForward(moveZ);
            }
        });

        document.addEventListener('touchend', () => {
            isTouching = false;
            knob.style.transform = 'translate(-50%, -50%)';
        });
    }

    createMobileButtons() {
        // Quick action buttons for mobile
        const actionButtons = [
            { icon: '๐Ÿ’ฌ', action: () => this.focusChatInput(), label: 'Chat' },
            { icon: '๐ŸŽญ', action: () => this.showGestureQuickMenu(), label: 'Emotes' },
            { icon: '๐Ÿ‘ฅ', action: () => this.showNearbyUsers(), label: 'People' },
            { icon: 'โš™๏ธ', action: () => this.showMobileSettings(), label: 'Settings' }
        ];

        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = `
            position: absolute;
            bottom: 100px;
            right: 20px;
            display: flex;
            flex-direction: column;
            gap: 10px;
            z-index: 1000;
        `;

        actionButtons.forEach(btn => {
            const button = document.createElement('button');
            button.innerHTML = btn.icon;
            button.title = btn.label;
            button.style.cssText = `
                width: 50px;
                height: 50px;
                border: none;
                border-radius: 50%;
                background: rgba(100, 100, 255, 0.8);
                color: white;
                font-size: 20px;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
            `;

            button.addEventListener('click', btn.action);
            buttonContainer.appendChild(button);
        });

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

    adjustUISizing() {
        // Make UI elements larger for touch
        const style = document.createElement('style');
        style.textContent = `
            @media (max-width: 768px) {
                #chat-ui {
                    bottom: 200px !important;
                    left: 10px !important;
                    right: 10px !important;
                    font-size: 16px !important;
                }

                #message-input {
                    padding: 12px !important;
                    font-size: 16px !important; /* Prevents zoom on iOS */
                }

                button {
                    min-height: 44px !important; /* Apple's recommended touch target size */
                    min-width: 44px !important;
                }
            }
        `;
        document.head.appendChild(style);
    }

    adjustCameraForMobile() {
        // Adjust camera settings for mobile
        this.camera.fov = 60 * Math.PI / 180; // Wider field of view
        this.camera.distance = 6; // Closer to scene
    }

    focusChatInput() {
        const chatInput = document.getElementById('message-input');
        if (chatInput) {
            chatInput.focus();

            // Scroll to bottom of chat
            const chatMessages = document.getElementById('chat-messages');
            if (chatMessages) {
                chatMessages.scrollTop = chatMessages.scrollHeight;
            }
        }
    }

    showGestureQuickMenu() {
        // Simplified gesture menu for mobile
        const quickGestures = ['wave', 'dance', 'clap', 'blowkiss'];

        const menu = document.createElement('div');
        menu.style.cssText = `
            position: absolute;
            bottom: 200px;
            right: 20px;
            background: rgba(0, 0, 0, 0.9);
            border-radius: 10px;
            padding: 10px;
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 5px;
            z-index: 1001;
        `;

        quickGestures.forEach(gesture => {
            const btn = document.createElement('button');
            btn.textContent = this.getGestureIcon(gesture);
            btn.style.cssText = `
                width: 60px;
                height: 60px;
                border: none;
                border-radius: 10px;
                background: rgba(255, 255, 255, 0.1);
                color: white;
                font-size: 24px;
                cursor: pointer;
            `;

            btn.addEventListener('click', () => {
                if (window.gestureSystem) {
                    window.gestureSystem.performGesture(
                        window.datingChat.chatConnection.getCurrentUser().id,
                        gesture
                    );
                }
                menu.remove();
            });

            menu.appendChild(btn);
        });

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

        // Auto-close after 5 seconds
        setTimeout(() => {
            if (menu.parentNode) {
                menu.remove();
            }
        }, 5000);
    }

    getGestureIcon(gesture) {
        const icons = {
            'wave': '๐Ÿ‘‹',
            'dance': '๐Ÿ’ƒ',
            'clap': '๐Ÿ‘',
            'blowkiss': '๐Ÿ˜˜'
        };
        return icons[gesture] || 'โ“';
    }

    showNearbyUsers() {
        // Show nearby users list
        const nearby = window.datingChat.sceneManager.avatars
            .filter(avatar => avatar.userId !== window.datingChat.chatConnection.getCurrentUser().id)
            .slice(0, 5);

        const userList = document.createElement('div');
        userList.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.9);
            border-radius: 10px;
            padding: 20px;
            color: white;
            z-index: 1001;
            min-width: 200px;
        `;

        userList.innerHTML = `
            <h3>Nearby Users</h3>
            ${nearby.length > 0 ? 
                nearby.map(avatar => `
                    <div style="padding: 10px; border-bottom: 1px solid #333;">
                        <strong>${avatar.name}</strong>
                        <button onclick="mobileSupport.startChatWith('${avatar.userId}')" style="float: right; padding: 5px; background: #4CAF50; color: white; border: none; border-radius: 3px;">Chat</button>
                    </div>
                `).join('') : 
                '<p>No users nearby</p>'
            }
            <button onclick="this.parentElement.remove()" style="margin-top: 10px; padding: 8px 16px; background: #666; color: white; border: none; border-radius: 5px; width: 100%;">Close</button>
        `;

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

    startChatWith(userId) {
        // Start private chat with user
        const avatar = window.datingChat.sceneManager.avatars.find(av => av.userId === userId);
        if (avatar) {
            // Focus chat input and set recipient
            this.focusChatInput();
            // In a real app, you'd set up a private chat session
            console.log(`Starting chat with ${avatar.name}`);
        }
    }

    showMobileSettings() {
        const settings = document.createElement('div');
        settings.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.9);
            border-radius: 10px;
            padding: 20px;
            color: white;
            z-index: 1001;
            min-width: 250px;
        `;

        settings.innerHTML = `
            <h3>Mobile Settings</h3>
            <div style="margin: 10px 0;">
                <label>Quality:</label>
                <select id="mobile-quality" style="width: 100%; padding: 5px; margin-top: 5px;">
                    <option value="low">Low</option>
                    <option value="medium" selected>Medium</option>
                    <option value="high">High</option>
                </select>
            </div>
            <div style="margin: 10px 0;">
                <label>Touch Sensitivity:</label>
                <input type="range" id="touch-sensitivity" min="1" max="10" value="5" style="width: 100%;">
            </div>
            <button onclick="this.parentElement.remove()" style="margin-top: 10px; padding: 8px 16px; background: #666; color: white; border: none; border-radius: 5px; width: 100%;">Close</button>
        `;

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

Updated Main Application Integration

Finally, let's update our main application to integrate all these new features:

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

        this.audioSystem = null;
        this.videoSystem = null;
        this.interactiveEnvironment = null;
        this.performanceOptimizer = null;
        this.mobileSupport = null;

        this.init();
    }

    async init() {
        this.setupWebGL();
        this.setupShaders();
        this.setupCamera();
        this.setupLighting();
        this.setupTextures();
        this.setupScene();
        this.setupNetwork();
        this.setupSocialFeatures();
        this.setupAdvancedFeatures(); // New
        this.setupInteraction();
        this.render();
    }

    setupAdvancedFeatures() {
        // Initialize audio system
        this.audioSystem = new SpatialAudioSystem();
        window.audioSystem = this.audioSystem;

        // Initialize video system
        this.videoSystem = new VideoStreamSystem();
        window.videoSystem = this.videoSystem;

        // Initialize interactive environment
        this.interactiveEnvironment = new InteractiveEnvironment(this.sceneManager, this.camera);

        // Initialize performance optimizer
        this.performanceOptimizer = new PerformanceOptimizer(this.sceneManager, this);
        window.performanceOptimizer = this.performanceOptimizer;

        // Initialize mobile support
        this.mobileSupport = new MobileSupport(this.camera, this.sceneManager);
        window.mobileSupport = this.mobileSupport;

        // Add ambient sounds
        this.setupAmbientSounds();
    }

    setupAmbientSounds() {
        // Add background ambient sounds
        setTimeout(() => {
            if (this.audioSystem) {
                // Gentle background music
                this.audioSystem.addAmbientSound(
                    'background',
                    'https://example.com/background-music.mp3', // Replace with actual URL
                    [0, 5, 0],
                    0.3,
                    true
                );

                // Environmental sounds
                this.audioSystem.addAmbientSound(
                    'nature',
                    'https://example.com/nature-sounds.mp3', // Replace with actual URL
                    [10, 0, 10],
                    0.2,
                    true
                );
            }
        }, 5000);
    }

    render() {
        const gl = this.gl;

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        gl.useProgram(this.program);

        // Set lighting uniforms
        this.lightingSystem.setLightUniforms(this.program, this.uniformLocations);

        // Get matrices from camera
        const viewMatrix = this.camera.getViewMatrix();
        const projectionMatrix = this.camera.getProjectionMatrix();

        // Calculate normal matrix
        const normalMatrix = this.calculateNormalMatrix(viewMatrix);
        gl.uniformMatrix4fv(this.uniformLocations.normalMatrix, false, normalMatrix);

        // Update animations
        const currentTime = Date.now();
        const deltaTime = this.lastFrameTime ? (currentTime - this.lastFrameTime) / 1000 : 0.016;
        this.lastFrameTime = currentTime;

        this.updateAnimations(deltaTime);

        // Update audio listener position
        if (this.audioSystem) {
            this.audioSystem.updateListenerPosition(
                this.camera.eye,
                this.camera.getLookDirection(),
                this.camera.up
            );

            // Update audio positions for all avatars
            this.sceneManager.avatars.forEach(avatar => {
                this.audioSystem.updateAudioPosition(avatar.userId, avatar.position);
            });
        }

        // Performance optimization - cull distant avatars
        if (this.performanceOptimizer) {
            this.performanceOptimizer.updateAvatarVisibility(this.camera.eye);
        }

        // Render scene
        this.sceneManager.render(
            gl,
            this.program,
            this.attribLocations,
            this.uniformLocations,
            viewMatrix,
            projectionMatrix
        );

        // Send avatar updates to server
        this.sendAvatarUpdates();

        requestAnimationFrame(() => this.render());
    }

    // ... rest of the class
}

// Make systems globally accessible
window.addEventListener('load', async () => {
    window.datingChat = new DatingChat3D();

    // Set global references
    setTimeout(() => {
        window.audioSystem = window.datingChat.audioSystem;
        window.videoSystem = window.datingChat.videoSystem;
        window.performanceOptimizer = window.datingChat.performanceOptimizer;
        window.mobileSupport = window.datingChat.mobileSupport;
    }, 1000);
});

What We've Accomplished in Part 5

In this fifth part, we've significantly enhanced our 3D dating chat with professional-grade features:

  1. Spatial Audio System with 3D sound positioning and voice activity detection
  2. Video Integration with webcam streaming and recording capabilities
  3. Interactive Environment with clickable objects and social zones
  4. Performance Optimization with adaptive quality and mobile support
  5. Mobile Device Support with touch controls and responsive design

Key Features Added:

Next Steps

In Part 6, we'll focus on:

Our platform now offers a complete, professional-grade 3D dating experience with audio, video, and interactive elements that rival commercial applications!

Part 6: AI Matchmaking, Mini-Games, and Advanced Customization

Welcome to Part 6 of our 10-part tutorial series! In this installment, we'll implement AI-powered matchmaking, interactive mini-games for dates, advanced avatar customization, and security features to create a complete dating platform.

Table of Contents for Part 6

  1. AI-Powered Matchmaking System
  2. Interactive Mini-Games
  3. Advanced Avatar Customization
  4. Security and Moderation
  5. Analytics and User Insights

1. AI-Powered Matchmaking System

Let's create an intelligent matchmaking system that uses machine learning to suggest compatible partners:

// AI Matchmaking Engine
class AIMatchmaker {
    constructor() {
        this.userProfiles = new Map();
        this.compatibilityModel = null;
        this.behavioralData = new Map();
        this.conversationAnalysis = new Map();

        this.initModel();
        this.setupLearningSystem();
    }

    initModel() {
        // Initialize compatibility scoring model
        this.compatibilityModel = {
            weights: {
                interests: 0.25,
                personality: 0.35,
                behavior: 0.20,
                conversation: 0.20
            },
            thresholds: {
                highMatch: 0.8,
                goodMatch: 0.6,
                fairMatch: 0.4
            }
        };

        console.log('AI Matchmaker initialized');
    }

    setupLearningSystem() {
        // Collect and learn from user interactions
        setInterval(() => {
            this.updateModelWeights();
        }, 300000); // Update every 5 minutes
    }

    // Add user profile to matchmaking system
    addUserProfile(userId, profile) {
        this.userProfiles.set(userId, {
            ...profile,
            matchScores: new Map(),
            interactionHistory: [],
            preferences: this.initializePreferences(profile)
        });

        // Calculate initial matches
        this.calculateMatchesForUser(userId);
    }

    initializePreferences(profile) {
        return {
            ageRange: [profile.age - 5, profile.age + 5],
            maxDistance: 50, // km
            desiredTraits: this.extractDesiredTraits(profile),
            dealBreakers: []
        };
    }

    extractDesiredTraits(profile) {
        // Analyze profile to determine preferred traits in partners
        const traits = [];

        if (profile.interests.includes('Sports')) traits.push('active');
        if (profile.interests.includes('Books')) traits.push('intellectual');
        if (profile.interests.includes('Travel')) traits.push('adventurous');
        if (profile.interests.includes('Art')) traits.push('creative');

        return traits;
    }

    // Calculate compatibility between two users
    calculateCompatibility(userAId, userBId) {
        const userA = this.userProfiles.get(userAId);
        const userB = this.userProfiles.get(userBId);

        if (!userA || !userB) return 0;

        const scores = {
            interests: this.calculateInterestSimilarity(userA, userB),
            personality: this.calculatePersonalityMatch(userA, userB),
            behavior: this.calculateBehavioralCompatibility(userAId, userBId),
            conversation: this.calculateConversationPotential(userA, userB)
        };

        // Weighted sum
        let totalScore = 0;
        for (const [factor, weight] of Object.entries(this.compatibilityModel.weights)) {
            totalScore += scores[factor] * weight;
        }

        // Apply preferences and deal breakers
        totalScore = this.applyPreferences(userA, userB, totalScore);

        return Math.min(1, Math.max(0, totalScore));
    }

    calculateInterestSimilarity(userA, userB) {
        const interestsA = new Set(userA.interests);
        const interestsB = new Set(userB.interests);

        const intersection = [...interestsA].filter(x => interestsB.has(x)).length;
        const union = new Set([...interestsA, ...interestsB]).size;

        return union > 0 ? intersection / union : 0;
    }

    calculatePersonalityMatch(userA, userB) {
        // Simplified personality matching based on profile data
        let score = 0.5; // Base score

        // Age compatibility
        const ageDiff = Math.abs(userA.age - userB.age);
        if (ageDiff <= 3) score += 0.2;
        else if (ageDiff <= 7) score += 0.1;

        // Gender preferences
        if (userA.lookingFor.includes(userB.gender) && 
            userB.lookingFor.includes(userA.gender)) {
            score += 0.3;
        }

        return Math.min(1, score);
    }

    calculateBehavioralCompatibility(userAId, userBId) {
        const behaviorA = this.behavioralData.get(userAId);
        const behaviorB = this.behavioralData.get(userBId);

        if (!behaviorA || !behaviorB) return 0.5;

        // Compare online patterns, interaction styles, etc.
        let score = 0.5;

        // Activity pattern similarity
        const activityDiff = Math.abs(behaviorA.activityLevel - behaviorA.activityLevel);
        score -= activityDiff * 0.1;

        // Response time compatibility
        const responseDiff = Math.abs(behaviorA.avgResponseTime - behaviorB.avgResponseTime);
        score -= responseDiff * 0.05;

        return Math.max(0, score);
    }

    calculateConversationPotential(userA, userB) {
        const convA = this.conversationAnalysis.get(userA.userId);
        const convB = this.conversationAnalysis.get(userB.userId);

        if (!convA || !convB) return 0.5;

        // Analyze conversation styles and topics
        let score = 0.5;

        // Topic overlap
        const commonTopics = this.findCommonTopics(convA.topics, convB.topics);
        score += commonTopics.length * 0.1;

        // Communication style compatibility
        const styleMatch = this.compareCommunicationStyles(convA.style, convB.style);
        score += styleMatch * 0.2;

        return Math.min(1, score);
    }

    applyPreferences(userA, userB, score) {
        const prefsA = userA.preferences;

        // Check age range
        if (userB.age < prefsA.ageRange[0] || userB.age > prefsA.ageRange[1]) {
            score *= 0.7;
        }

        // Check deal breakers
        if (this.hasDealBreakers(userA, userB)) {
            score *= 0.3;
        }

        // Boost for desired traits
        const traitMatches = prefsA.desiredTraits.filter(trait => 
            this.userHasTrait(userB, trait)
        ).length;

        score += traitMatches * 0.05;

        return score;
    }

    hasDealBreakers(userA, userB) {
        // Check if userB has any of userA's deal breakers
        // This would be expanded based on actual deal breaker definitions
        return false;
    }

    userHasTrait(user, trait) {
        // Determine if user exhibits a specific trait
        const traitMapping = {
            'active': ['Sports', 'Fitness', 'Outdoors'],
            'intellectual': ['Books', 'Science', 'Technology'],
            'adventurous': ['Travel', 'Adventure', 'Exploring'],
            'creative': ['Art', 'Music', 'Writing']
        };

        const relatedInterests = traitMapping[trait] || [];
        return relatedInterests.some(interest => user.interests.includes(interest));
    }

    // Calculate matches for a specific user
    calculateMatchesForUser(userId) {
        const user = this.userProfiles.get(userId);
        if (!user) return;

        const matches = [];

        for (const [otherUserId, otherUser] of this.userProfiles) {
            if (otherUserId === userId) continue;

            const compatibility = this.calculateCompatibility(userId, otherUserId);

            if (compatibility >= this.compatibilityModel.thresholds.fairMatch) {
                matches.push({
                    userId: otherUserId,
                    profile: otherUser,
                    compatibility,
                    matchType: this.getMatchType(compatibility),
                    reasons: this.getMatchReasons(user, otherUser, compatibility)
                });
            }
        }

        // Sort by compatibility score
        matches.sort((a, b) => b.compatibility - a.compatibility);

        user.matchScores = new Map(matches.map(m => [m.userId, m.compatibility]));
        user.topMatches = matches.slice(0, 10); // Top 10 matches

        this.notifyUserOfNewMatches(userId, matches.slice(0, 3));
    }

    getMatchType(compatibility) {
        if (compatibility >= this.compatibilityModel.thresholds.highMatch) {
            return 'high';
        } else if (compatibility >= this.compatibilityModel.thresholds.goodMatch) {
            return 'good';
        } else {
            return 'fair';
        }
    }

    getMatchReasons(userA, userB, compatibility) {
        const reasons = [];

        // Common interests
        const commonInterests = userA.interests.filter(interest => 
            userB.interests.includes(interest)
        );

        if (commonInterests.length > 2) {
            reasons.push(`You both enjoy ${commonInterests.slice(0, 2).join(', ')}`);
        }

        // Age compatibility
        const ageDiff = Math.abs(userA.age - userB.age);
        if (ageDiff <= 2) {
            reasons.push('Similar age');
        }

        // High compatibility
        if (compatibility > 0.8) {
            reasons.push('Very high compatibility score');
        }

        return reasons;
    }

    // Track user behavior for better matching
    recordUserInteraction(userId, interaction) {
        if (!this.behavioralData.has(userId)) {
            this.behavioralData.set(userId, {
                activityLevel: 0,
                avgResponseTime: 0,
                interactionCount: 0,
                preferredTimes: []
            });
        }

        const data = this.behavioralData.get(userId);
        data.interactionCount++;
        data.activityLevel = Math.min(1, data.interactionCount / 100);

        // Update average response time
        if (interaction.responseTime) {
            data.avgResponseTime = (data.avgResponseTime * (data.interactionCount - 1) + interaction.responseTime) / data.interactionCount;
        }

        // Record interaction time pattern
        const hour = new Date().getHours();
        if (!data.preferredTimes.includes(hour)) {
            data.preferredTimes.push(hour);
        }
    }

    // Analyze conversation content
    analyzeConversation(userId, message) {
        const analysis = this.conversationAnalysis.get(userId) || {
            topics: new Set(),
            sentiment: 0,
            messageLengths: [],
            style: 'neutral',
            keywordFrequency: new Map()
        };

        // Extract topics
        const topics = this.extractTopics(message);
        topics.forEach(topic => analysis.topics.add(topic));

        // Analyze sentiment (simplified)
        analysis.sentiment = this.analyzeSentiment(message);

        // Track message length
        analysis.messageLengths.push(message.length);

        // Update communication style
        analysis.style = this.determineCommunicationStyle(analysis);

        this.conversationAnalysis.set(userId, analysis);
    }

    extractTopics(message) {
        const topics = [];
        const topicKeywords = {
            'sports': ['game', 'sports', 'team', 'play', 'win'],
            'music': ['music', 'song', 'band', 'concert', 'listen'],
            'travel': ['travel', 'vacation', 'trip', 'beach', 'mountains'],
            'food': ['food', 'restaurant', 'cook', 'recipe', 'dinner'],
            'movies': ['movie', 'film', 'watch', 'netflix', 'cinema']
        };

        const lowerMessage = message.toLowerCase();
        for (const [topic, keywords] of Object.entries(topicKeywords)) {
            if (keywords.some(keyword => lowerMessage.includes(keyword))) {
                topics.push(topic);
            }
        }

        return topics;
    }

    analyzeSentiment(message) {
        // Simplified sentiment analysis
        const positiveWords = ['love', 'great', 'awesome', 'amazing', 'happy', 'good', 'nice', 'wonderful'];
        const negativeWords = ['hate', 'bad', 'terrible', 'awful', 'sad', 'angry', 'horrible'];

        let score = 0;
        const words = message.toLowerCase().split(' ');

        words.forEach(word => {
            if (positiveWords.includes(word)) score += 1;
            if (negativeWords.includes(word)) score -= 1;
        });

        return Math.max(-1, Math.min(1, score / words.length));
    }

    determineCommunicationStyle(analysis) {
        const avgLength = analysis.messageLengths.reduce((a, b) => a + b, 0) / analysis.messageLengths.length;

        if (avgLength > 100) return 'detailed';
        if (avgLength < 30) return 'concise';
        return 'balanced';
    }

    updateModelWeights() {
        // Learn from successful matches and adjust weights
        // This is a simplified version - real implementation would use more sophisticated ML
        console.log('Updating matchmaking model weights...');
    }

    notifyUserOfNewMatches(userId, matches) {
        if (matches.length === 0) return;

        // In a real app, this would send a notification to the user
        console.log(`User ${userId} has ${matches.length} new matches:`, matches);

        // Update UI with new matches
        this.updateMatchSuggestionsUI(userId, matches);
    }

    updateMatchSuggestionsUI(userId, matches) {
        const matchContainer = document.getElementById('ai-match-suggestions');
        if (!matchContainer) return;

        matchContainer.innerHTML = `
            <h3>AI Match Suggestions</h3>
            ${matches.map(match => `
                <div class="match-suggestion" data-user-id="${match.userId}">
                    <div class="match-score">${Math.round(match.compatibility * 100)}%</div>
                    <div class="match-info">
                        <strong>${match.profile.name}, ${match.profile.age}</strong>
                        <div class="match-reasons">
                            ${match.reasons.map(reason => `<span class="reason">${reason}</span>`).join('')}
                        </div>
                    </div>
                    <button onclick="aiMatchmaker.startIntroduction('${userId}', '${match.userId}')" class="match-action">
                        Connect
                    </button>
                </div>
            `).join('')}
        `;
    }

    startIntroduction(userAId, userBId) {
        // Facilitate introduction between two users
        console.log(`Introducing ${userAId} to ${userBId}`);

        // Send introduction message
        const introduction = this.generateIntroduction(userAId, userBId);

        // In a real app, this would send messages to both users
        window.datingChat?.chatConnection?.sendMessage('ai_introduction', {
            userAId,
            userBId,
            introduction
        });
    }

    generateIntroduction(userAId, userBId) {
        const userA = this.userProfiles.get(userAId);
        const userB = this.userProfiles.get(userBId);

        const commonInterests = userA.interests.filter(interest => 
            userB.interests.includes(interest)
        );

        const interestsText = commonInterests.length > 0 ? 
            `You both enjoy ${commonInterests.slice(0, 2).join(' and ')}` : 
            'I think you might hit it off!';

        return `Hi! I noticed ${interestsText}. Why not start a conversation?`;
    }

    // Get match suggestions for a user
    getMatchSuggestions(userId, count = 5) {
        const user = this.userProfiles.get(userId);
        return user?.topMatches?.slice(0, count) || [];
    }
}

2. Interactive Mini-Games

Let's create engaging mini-games that couples can play together on dates:

// Mini-game system for interactive dates
class MiniGameSystem {
    constructor() {
        this.activeGames = new Map();
        this.availableGames = new Map();
        this.gameInvitations = new Map();

        this.registerGames();
        this.setupGameUI();
    }

    registerGames() {
        this.availableGames.set('icebreaker', {
            name: 'Ice Breaker',
            description: 'Get to know each other with fun questions',
            minPlayers: 2,
            maxPlayers: 2,
            duration: 5, // minutes
            setup: this.setupIceBreaker.bind(this)
        });

        this.availableGames.set('trivia', {
            name: 'Trivia Challenge',
            description: 'Test your knowledge together',
            minPlayers: 2,
            maxPlayers: 2,
            duration: 10,
            setup: this.setupTrivia.bind(this)
        });

        this.availableGames.set('puzzle', {
            name: 'Collaborative Puzzle',
            description: 'Work together to solve puzzles',
            minPlayers: 2,
            maxPlayers: 2,
            duration: 15,
            setup: this.setupPuzzle.bind(this)
        });

        this.availableGames.set('would_you_rather', {
            name: 'Would You Rather',
            description: 'Fun hypothetical questions',
            minPlayers: 2,
            maxPlayers: 2,
            duration: 8,
            setup: this.setupWouldYouRather.bind(this)
        });
    }

    setupGameUI() {
        this.createGamePanel();
        this.createGameInvitationHandler();
    }

    createGamePanel() {
        this.gamePanel = document.createElement('div');
        this.gamePanel.id = 'mini-game-panel';
        this.gamePanel.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 20px;
            color: white;
            width: 400px;
            max-width: 90vw;
            max-height: 80vh;
            overflow-y: auto;
            display: none;
            z-index: 2000;
        `;

        document.getElementById('container').appendChild(this.gamePanel);
    }

    // Ice Breaker Game
    setupIceBreaker(gameId, players) {
        const questions = [
            "What's your favorite travel destination and why?",
            "If you could have any superpower, what would it be?",
            "What's the most adventurous thing you've ever done?",
            "What's your idea of a perfect day?",
            "If you could meet anyone from history, who would it be?",
            "What's something you're passionate about?",
            "What's your favorite way to relax after a long day?",
            "If you could instantly master any skill, what would it be?",
            "What's the best piece of advice you've ever received?",
            "What's something that always makes you smile?"
        ];

        const game = {
            id: gameId,
            type: 'icebreaker',
            players,
            currentQuestion: 0,
            questions: this.shuffleArray([...questions]),
            answers: new Map(),
            startTime: Date.now()
        };

        this.activeGames.set(gameId, game);
        this.showIceBreakerGame(game);

        return game;
    }

    showIceBreakerGame(game) {
        this.gamePanel.innerHTML = '';
        this.gamePanel.style.display = 'block';

        const currentQuestion = game.questions[game.currentQuestion];

        this.gamePanel.innerHTML = `
            <div style="text-align: center;">
                <h2>โ„๏ธ Ice Breaker</h2>
                <div style="background: #333; padding: 20px; border-radius: 10px; margin: 20px 0;">
                    <h3>Question ${game.currentQuestion + 1}/${game.questions.length}</h3>
                    <p style="font-size: 18px; line-height: 1.4;">${currentQuestion}</p>
                </div>

                <div style="margin: 20px 0;">
                    <textarea id="icebreaker-answer" placeholder="Type your answer here..." 
                              style="width: 100%; height: 100px; padding: 10px; border-radius: 5px; border: 1px solid #555; background: #222; color: white;"></textarea>
                </div>

                <div style="display: flex; gap: 10px; justify-content: center;">
                    <button onclick="miniGameSystem.submitAnswer('${game.id}')" 
                            style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
                        Submit Answer
                    </button>
                    <button onclick="miniGameSystem.leaveGame('${game.id}')" 
                            style="padding: 10px 20px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer;">
                        Leave Game
                    </button>
                </div>

                <div style="margin-top: 20px; font-size: 14px; color: #aaa;">
                    Waiting for other player...
                </div>
            </div>
        `;
    }

    submitAnswer(gameId) {
        const game = this.activeGames.get(gameId);
        if (!game) return;

        const answerInput = document.getElementById('icebreaker-answer');
        const answer = answerInput.value.trim();

        if (!answer) {
            alert('Please enter your answer!');
            return;
        }

        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        game.answers.set(currentUser.id, {
            answer,
            timestamp: Date.now()
        });

        // Check if both players have answered
        if (game.answers.size === game.players.length) {
            this.showIceBreakerResults(game);
        } else {
            this.updateGameUI(game, 'Waiting for other player to answer...');
        }
    }

    showIceBreakerResults(game) {
        const answers = Array.from(game.answers.entries());
        const currentQuestion = game.questions[game.currentQuestion];

        this.gamePanel.innerHTML = `
            <div style="text-align: center;">
                <h2>โ„๏ธ Ice Breaker - Results</h2>
                <div style="background: #333; padding: 20px; border-radius: 10px; margin: 20px 0;">
                    <h3>Question: ${currentQuestion}</h3>
                    ${answers.map(([userId, data]) => `
                        <div style="margin: 15px 0; padding: 15px; background: #444; border-radius: 8px;">
                            <strong>${this.getUserName(userId)}:</strong>
                            <p style="margin: 10px 0 0 0; font-style: italic;">"${data.answer}"</p>
                        </div>
                    `).join('')}
                </div>

                <button onclick="miniGameSystem.nextIceBreakerQuestion('${game.id}')" 
                        style="padding: 10px 20px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer;">
                    Next Question
                </button>
            </div>
        `;
    }

    nextIceBreakerQuestion(gameId) {
        const game = this.activeGames.get(gameId);
        if (!game) return;

        game.currentQuestion++;
        game.answers.clear();

        if (game.currentQuestion < game.questions.length) {
            this.showIceBreakerGame(game);
        } else {
            this.endGame(gameId, 'Game completed! Hope you learned something new about each other!');
        }
    }

    // Trivia Game
    setupTrivia(gameId, players) {
        const triviaQuestions = [
            {
                question: "What is the capital of France?",
                options: ["London", "Berlin", "Paris", "Madrid"],
                correct: 2
            },
            {
                question: "Which planet is known as the Red Planet?",
                options: ["Venus", "Mars", "Jupiter", "Saturn"],
                correct: 1
            },
            {
                question: "What is the largest mammal in the world?",
                options: ["Elephant", "Blue Whale", "Giraffe", "Polar Bear"],
                correct: 1
            },
            {
                question: "Who painted the Mona Lisa?",
                options: ["Van Gogh", "Picasso", "Da Vinci", "Monet"],
                correct: 2
            }
        ];

        const game = {
            id: gameId,
            type: 'trivia',
            players,
            questions: this.shuffleArray([...triviaQuestions]),
            currentQuestion: 0,
            scores: new Map(players.map(p => [p, 0])),
            startTime: Date.now()
        };

        this.activeGames.set(gameId, game);
        this.showTriviaQuestion(game);

        return game;
    }

    showTriviaQuestion(game) {
        const question = game.questions[game.currentQuestion];

        this.gamePanel.innerHTML = `
            <div style="text-align: center;">
                <h2>๐ŸŽฏ Trivia Challenge</h2>
                <div style="background: #333; padding: 20px; border-radius: 10px; margin: 20px 0;">
                    <h3>Question ${game.currentQuestion + 1}/${game.questions.length}</h3>
                    <p style="font-size: 18px; margin-bottom: 20px;">${question.question}</p>

                    <div style="display: grid; gap: 10px;">
                        ${question.options.map((option, index) => `
                            <button onclick="miniGameSystem.submitTriviaAnswer('${game.id}', ${index})" 
                                    style="padding: 15px; background: #444; color: white; border: 2px solid #555; border-radius: 8px; cursor: pointer; font-size: 16px;">
                                ${option}
                            </button>
                        `).join('')}
                    </div>
                </div>

                <div style="display: flex; justify-content: space-between; margin-top: 20px;">
                    ${Array.from(game.scores.entries()).map(([userId, score]) => `
                        <div style="text-align: center;">
                            <div style="font-size: 12px; color: #aaa;">${this.getUserName(userId)}</div>
                            <div style="font-size: 18px; font-weight: bold;">${score}</div>
                        </div>
                    `).join('')}
                </div>
            </div>
        `;
    }

    submitTriviaAnswer(gameId, answerIndex) {
        const game = this.activeGames.get(gameId);
        if (!game) return;

        const question = game.questions[game.currentQuestion];
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();

        const isCorrect = answerIndex === question.correct;

        if (isCorrect) {
            game.scores.set(currentUser.id, game.scores.get(currentUser.id) + 1);
        }

        this.showTriviaResult(game, isCorrect, question.options[question.correct]);
    }

    showTriviaResult(game, isCorrect, correctAnswer) {
        this.gamePanel.innerHTML = `
            <div style="text-align: center;">
                <h2>๐ŸŽฏ Trivia Challenge</h2>
                <div style="background: #333; padding: 20px; border-radius: 10px; margin: 20px 0;">
                    <h3>${isCorrect ? 'โœ… Correct!' : 'โŒ Incorrect'}</h3>
                    ${!isCorrect ? `<p>The correct answer was: <strong>${correctAnswer}</strong></p>` : ''}

                    <div style="margin-top: 20px;">
                        <h4>Current Scores:</h4>
                        ${Array.from(game.scores.entries()).map(([userId, score]) => `
                            <div style="margin: 5px 0;">
                                ${this.getUserName(userId)}: ${score} points
                            </div>
                        `).join('')}
                    </div>
                </div>

                <button onclick="miniGameSystem.nextTriviaQuestion('${game.id}')" 
                        style="padding: 10px 20px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer;">
                    ${game.currentQuestion < game.questions.length - 1 ? 'Next Question' : 'See Final Results'}
                </button>
            </div>
        `;
    }

    nextTriviaQuestion(gameId) {
        const game = this.activeGames.get(gameId);
        if (!game) return;

        game.currentQuestion++;

        if (game.currentQuestion < game.questions.length) {
            this.showTriviaQuestion(game);
        } else {
            this.showTriviaFinalResults(game);
        }
    }

    showTriviaFinalResults(game) {
        const scores = Array.from(game.scores.entries());
        const winner = scores.reduce((a, b) => a[1] > b[1] ? a : b);

        this.gamePanel.innerHTML = `
            <div style="text-align: center;">
                <h2>๐ŸŽฏ Trivia Challenge - Final Results</h2>
                <div style="background: #333; padding: 20px; border-radius: 10px; margin: 20px 0;">
                    <h3>๐Ÿ† ${this.getUserName(winner[0])} Wins!</h3>

                    <div style="margin: 20px 0;">
                        ${scores.map(([userId, score]) => `
                            <div style="padding: 10px; margin: 5px 0; background: #444; border-radius: 5px;">
                                ${this.getUserName(userId)}: ${score} points
                            </div>
                        `).join('')}
                    </div>

                    <p style="color: #aaa;">Great game! Ready for a rematch?</p>
                </div>

                <button onclick="miniGameSystem.leaveGame('${game.id}')" 
                        style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
                    Finish Game
                </button>
            </div>
        `;
    }

    // Game invitation system
    sendGameInvitation(targetUserId, gameType) {
        const gameId = `game_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();

        const invitation = {
            id: gameId,
            gameType,
            fromUser: currentUser,
            toUser: targetUserId,
            timestamp: Date.now(),
            status: 'pending'
        };

        this.gameInvitations.set(gameId, invitation);

        // Send via chat connection
        window.datingChat?.chatConnection?.sendMessage('game_invitation', invitation);

        this.showSentInvitation(invitation);
    }

    showSentInvitation(invitation) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.9);
            border: 2px solid #4CAF50;
            border-radius: 10px;
            padding: 20px;
            color: white;
            z-index: 2000;
            text-align: center;
        `;

        notification.innerHTML = `
            <h3>Game Invitation Sent!</h3>
            <p>Waiting for ${this.getUserName(invitation.toUser)} to accept...</p>
            <button onclick="this.parentElement.remove()" 
                    style="padding: 8px 16px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer;">
                Cancel
            </button>
        `;

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

        // Auto-remove after 30 seconds
        setTimeout(() => {
            if (notification.parentElement) {
                notification.remove();
            }
        }, 30000);
    }

    handleGameInvitation(invitation) {
        this.showGameInvitationAlert(invitation);
    }

    showGameInvitationAlert(invitation) {
        const gameInfo = this.availableGames.get(invitation.gameType);

        const alertDiv = document.createElement('div');
        alertDiv.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border: 2px solid #2196F3;
            border-radius: 15px;
            padding: 25px;
            color: white;
            z-index: 2000;
            text-align: center;
            min-width: 300px;
        `;

        alertDiv.innerHTML = `
            <h3>๐ŸŽฎ Game Invitation</h3>
            <p><strong>${this.getUserName(invitation.fromUser.id)}</strong> invited you to play:</p>
            <div style="background: #333; padding: 15px; border-radius: 8px; margin: 15px 0;">
                <h4>${gameInfo.name}</h4>
                <p style="color: #aaa; font-size: 14px;">${gameInfo.description}</p>
                <p style="color: #aaa; font-size: 12px;">Duration: ${gameInfo.duration} minutes</p>
            </div>

            <div style="display: flex; gap: 10px; justify-content: center;">
                <button onclick="miniGameSystem.acceptGameInvitation('${invitation.id}')" 
                        style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
                    Accept
                </button>
                <button onclick="miniGameSystem.declineGameInvitation('${invitation.id}')" 
                        style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 5px; cursor: pointer;">
                    Decline
                </button>
            </div>
        `;

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

    acceptGameInvitation(invitationId) {
        const invitation = this.gameInvitations.get(invitationId);
        if (!invitation) return;

        // Remove invitation alert
        document.querySelectorAll('div').forEach(div => {
            if (div.innerHTML.includes('Game Invitation')) {
                div.remove();
            }
        });

        // Start the game
        const game = this.availableGames.get(invitation.gameType).setup(
            invitation.id,
            [invitation.fromUser.id, invitation.toUser]
        );

        // Notify other player
        window.datingChat?.chatConnection?.sendMessage('game_accepted', {
            invitationId,
            gameId: game.id
        });
    }

    declineGameInvitation(invitationId) {
        const invitation = this.gameInvitations.get(invitationId);
        if (invitation) {
            this.gameInvitations.delete(invitationId);

            // Notify other player
            window.datingChat?.chatConnection?.sendMessage('game_declined', {
                invitationId
            });
        }

        // Remove invitation alert
        document.querySelectorAll('div').forEach(div => {
            if (div.innerHTML.includes('Game Invitation')) {
                div.remove();
            }
        });
    }

    leaveGame(gameId) {
        const game = this.activeGames.get(gameId);
        if (game) {
            this.activeGames.delete(gameId);
            this.gamePanel.style.display = 'none';

            // Notify other players
            window.datingChat?.chatConnection?.sendMessage('game_left', {
                gameId,
                userId: window.datingChat?.chatConnection?.getCurrentUser().id
            });
        }
    }

    endGame(gameId, message) {
        const game = this.activeGames.get(gameId);
        if (game) {
            this.activeGames.delete(gameId);

            this.gamePanel.innerHTML = `
                <div style="text-align: center;">
                    <h2>Game Complete</h2>
                    <p>${message}</p>
                    <button onclick="miniGameSystem.leaveGame('${gameId}')" 
                            style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
                        Close
                    </button>
                </div>
            `;
        }
    }

    // Utility methods
    getUserName(userId) {
        const avatar = window.datingChat?.sceneManager?.avatars.find(av => av.userId === userId);
        return avatar?.name || `User${userId.substr(0, 4)}`;
    }

    shuffleArray(array) {
        const shuffled = [...array];
        for (let i = shuffled.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
        }
        return shuffled;
    }

    updateGameUI(game, status) {
        const statusElement = this.gamePanel.querySelector('.game-status');
        if (statusElement) {
            statusElement.textContent = status;
        }
    }

    createGameInvitationHandler() {
        if (window.datingChat?.chatConnection) {
            window.datingChat.chatConnection.onMessage('game_invitation', (data) => {
                this.handleGameInvitation(data);
            });

            window.datingChat.chatConnection.onMessage('game_accepted', (data) => {
                console.log('Game accepted:', data);
            });

            window.datingChat.chatConnection.onMessage('game_declined', (data) => {
                alert('The other player declined your game invitation.');
            });
        }
    }
}

3. Advanced Avatar Customization

Let's create a comprehensive avatar customization system:

// Advanced avatar customization system
class AvatarCustomizer {
    constructor(sceneManager) {
        this.sceneManager = sceneManager;
        this.customizationData = new Map();
        this.availableAssets = {
            hairstyles: new Map(),
            clothing: new Map(),
            accessories: new Map(),
            skins: new Map(),
            faces: new Map()
        };

        this.loadAssetCatalog();
        this.setupCustomizationUI();
    }

    loadAssetCatalog() {
        // Hairstyles
        this.availableAssets.hairstyles.set('short_spiky', {
            id: 'short_spiky',
            name: 'Short Spiky',
            type: 'hairstyle',
            vertices: this.generateHairVertices('short_spiky'),
            colors: [0.2, 0.1, 0.05, 1.0],
            price: 0,
            unlocked: true
        });

        this.availableAssets.hairstyles.set('long_wavy', {
            id: 'long_wavy',
            name: 'Long Wavy',
            type: 'hairstyle',
            vertices: this.generateHairVertices('long_wavy'),
            colors: [0.3, 0.2, 0.1, 1.0],
            price: 50,
            unlocked: false
        });

        this.availableAssets.hairstyles.set('ponytail', {
            id: 'ponytail',
            name: 'Ponytail',
            type: 'hairstyle',
            vertices: this.generateHairVertices('ponytail'),
            colors: [0.25, 0.15, 0.08, 1.0],
            price: 25,
            unlocked: false
        });

        // Clothing
        this.availableAssets.clothing.set('casual_tshirt', {
            id: 'casual_tshirt',
            name: 'Casual T-Shirt',
            type: 'clothing',
            vertices: this.generateClothingVertices('tshirt'),
            colors: [0.8, 0.2, 0.2, 1.0],
            price: 0,
            unlocked: true
        });

        this.availableAssets.clothing.set('elegant_dress', {
            id: 'elegant_dress',
            name: 'Elegant Dress',
            type: 'clothing',
            vertices: this.generateClothingVertices('dress'),
            colors: [0.6, 0.2, 0.8, 1.0],
            price: 100,
            unlocked: false
        });

        this.availableAssets.clothing.set('formal_suit', {
            id: 'formal_suit',
            name: 'Formal Suit',
            type: 'clothing',
            vertices: this.generateClothingVertices('suit'),
            colors: [0.1, 0.1, 0.3, 1.0],
            price: 150,
            unlocked: false
        });

        // Accessories
        this.availableAssets.accessories.set('glasses', {
            id: 'glasses',
            name: 'Stylish Glasses',
            type: 'accessory',
            vertices: this.generateAccessoryVertices('glasses'),
            colors: [0.9, 0.9, 0.9, 1.0],
            price: 30,
            unlocked: false
        });

        this.availableAssets.accessories.set('hat', {
            id: 'hat',
            name: 'Baseball Cap',
            type: 'accessory',
            vertices: this.generateAccessoryVertices('hat'),
            colors: [0.9, 0.1, 0.1, 1.0],
            price: 20,
            unlocked: false
        });

        // Skin tones
        this.availableAssets.skins.set('light', { r: 0.9, g: 0.7, b: 0.5, price: 0 });
        this.availableAssets.skins.set('medium', { r: 0.7, g: 0.5, b: 0.3, price: 0 });
        this.availableAssets.skins.set('dark', { r: 0.4, g: 0.3, b: 0.2, price: 0 });
        this.availableAssets.skins.set('tan', { r: 0.8, g: 0.6, b: 0.4, price: 25 });

        // Facial features
        this.availableAssets.faces.set('default', {
            eyes: { size: 1.0, color: [0.3, 0.5, 0.8] },
            nose: { size: 1.0, shape: 'default' },
            mouth: { size: 1.0, shape: 'default' }
        });
    }

    setupCustomizationUI() {
        this.createCustomizationPanel();
        this.createColorPickers();
        this.createAssetStore();
    }

    createCustomizationPanel() {
        this.customizationPanel = document.createElement('div');
        this.customizationPanel.id = 'avatar-customization';
        this.customizationPanel.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 20px;
            color: white;
            width: 800px;
            max-width: 95vw;
            max-height: 90vh;
            overflow-y: auto;
            display: none;
            z-index: 2000;
        `;

        document.getElementById('container').appendChild(this.customizationPanel);

        // Create customization button
        this.createCustomizationButton();
    }

    createCustomizationButton() {
        const customButton = document.createElement('button');
        customButton.innerHTML = '๐ŸŽจ';
        customButton.title = 'Customize Avatar';
        customButton.style.cssText = `
            position: absolute;
            top: 20px;
            right: 120px;
            width: 40px;
            height: 40px;
            border: none;
            border-radius: 50%;
            background: rgba(255, 100, 255, 0.8);
            color: white;
            font-size: 18px;
            cursor: pointer;
            z-index: 1001;
        `;

        customButton.addEventListener('click', () => {
            this.showCustomizationPanel();
        });

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

    showCustomizationPanel() {
        this.customizationPanel.style.display = 'block';
        this.renderCustomizationUI();
    }

    renderCustomizationUI() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const currentCustomization = this.customizationData.get(currentUser.id) || this.getDefaultCustomization();

        this.customizationPanel.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 20px;">
                <h2 style="margin: 0;">Avatar Customization</h2>
                <button onclick="avatarCustomizer.hideCustomizationPanel()" 
                        style="padding: 5px 10px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer;">
                    Close
                </button>
            </div>

            <div style="display: grid; grid-template-columns: 300px 1fr; gap: 20px;">
                <!-- Preview Section -->
                <div style="background: #222; border-radius: 10px; padding: 20px; text-align: center;">
                    <h3>Preview</h3>
                    <div id="avatar-preview" style="width: 200px; height: 300px; background: #333; margin: 0 auto; border-radius: 10px;">
                        <!-- Avatar preview will be rendered here -->
                    </div>
                    <button onclick="avatarCustomizer.applyCustomization()" 
                            style="margin-top: 15px; padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; width: 100%;">
                        Apply Changes
                    </button>
                    <button onclick="avatarCustomizer.saveCustomization()" 
                            style="margin-top: 10px; padding: 8px 16px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; width: 100%;">
                        Save Preset
                    </button>
                </div>

                <!-- Customization Options -->
                <div>
                    <div style="margin-bottom: 20px;">
                        <h3>Appearance</h3>
                        ${this.renderCategoryOptions('hairstyles', currentCustomization)}
                        ${this.renderCategoryOptions('clothing', currentCustomization)}
                        ${this.renderCategoryOptions('accessories', currentCustomization)}
                    </div>

                    <div style="margin-bottom: 20px;">
                        <h3>Colors</h3>
                        ${this.renderColorPickers(currentCustomization)}
                    </div>

                    <div>
                        <h3>Facial Features</h3>
                        ${this.renderFacialFeatures(currentCustomization)}
                    </div>
                </div>
            </div>

            <!-- Asset Store -->
            <div style="margin-top: 30px; border-top: 1px solid #333; padding-top: 20px;">
                <h3>Asset Store</h3>
                <div id="asset-store" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px;">
                    ${this.renderAssetStore()}
                </div>
            </div>
        `;

        this.renderAvatarPreview(currentCustomization);
    }

    renderCategoryOptions(category, currentCustomization) {
        const assets = Array.from(this.availableAssets[category].values());
        const currentAsset = currentCustomization[category];

        return `
            <div style="margin-bottom: 15px;">
                <label style="display: block; margin-bottom: 5px; font-weight: bold;">${this.capitalizeFirst(category)}</label>
                <select onchange="avatarCustomizer.updateCustomization('${category}', this.value)" 
                        style="width: 100%; padding: 8px; border-radius: 5px; background: #333; color: white; border: 1px solid #555;">
                    ${assets.map(asset => `
                        <option value="${asset.id}" ${currentAsset === asset.id ? 'selected' : ''}
                                ${!asset.unlocked ? 'disabled' : ''}>
                            ${asset.name} ${!asset.unlocked ? `(${asset.price} coins)` : ''}
                        </option>
                    `).join('')}
                </select>
            </div>
        `;
    }

    renderColorPickers(currentCustomization) {
        return `
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <div>
                    <label style="display: block; margin-bottom: 5px;">Skin Tone</label>
                    <input type="color" value="${this.rgbToHex(currentCustomization.skinColor)}" 
                           onchange="avatarCustomizer.updateColor('skinColor', this.value)"
                           style="width: 100%; height: 40px; border: none; border-radius: 5px; cursor: pointer;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px;">Hair Color</label>
                    <input type="color" value="${this.rgbToHex(currentCustomization.hairColor)}" 
                           onchange="avatarCustomizer.updateColor('hairColor', this.value)"
                           style="width: 100%; height: 40px; border: none; border-radius: 5px; cursor: pointer;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px;">Eye Color</label>
                    <input type="color" value="${this.rgbToHex(currentCustomization.eyeColor)}" 
                           onchange="avatarCustomizer.updateColor('eyeColor', this.value)"
                           style="width: 100%; height: 40px; border: none; border-radius: 5px; cursor: pointer;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px;">Clothing Color</label>
                    <input type="color" value="${this.rgbToHex(currentCustomization.clothingColor)}" 
                           onchange="avatarCustomizer.updateColor('clothingColor', this.value)"
                           style="width: 100%; height: 40px; border: none; border-radius: 5px; cursor: pointer;">
                </div>
            </div>
        `;
    }

    renderFacialFeatures(currentCustomization) {
        return `
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                <div>
                    <label style="display: block; margin-bottom: 5px;">Eye Size</label>
                    <input type="range" min="0.5" max="1.5" step="0.1" value="${currentCustomization.eyeSize}"
                           onchange="avatarCustomizer.updateFacialFeature('eyeSize', this.value)"
                           style="width: 100%;">
                </div>
                <div>
                    <label style="display: block; margin-bottom: 5px;">Mouth Size</label>
                    <input type="range" min="0.5" max="1.5" step="0.1" value="${currentCustomization.mouthSize}"
                           onchange="avatarCustomizer.updateFacialFeature('mouthSize', this.value)"
                           style="width: 100%;">
                </div>
            </div>
        `;
    }

    renderAssetStore() {
        const allAssets = [];

        for (const [category, assets] of Object.entries(this.availableAssets)) {
            if (category === 'skins' || category === 'faces') continue;

            for (const asset of assets.values()) {
                if (!asset.unlocked) {
                    allAssets.push(asset);
                }
            }
        }

        return allAssets.map(asset => `
            <div style="background: #333; padding: 10px; border-radius: 8px; text-align: center;">
                <div style="font-size: 24px; margin-bottom: 5px;">๐ŸŽจ</div>
                <div style="font-size: 12px; font-weight: bold; margin-bottom: 5px;">${asset.name}</div>
                <div style="font-size: 10px; color: #aaa; margin-bottom: 8px;">${this.capitalizeFirst(asset.type)}</div>
                <div style="font-size: 11px; color: gold; margin-bottom: 8px;">${asset.price} coins</div>
                <button onclick="avatarCustomizer.purchaseAsset('${asset.type}', '${asset.id}')" 
                        style="padding: 5px 10px; background: #FFD700; color: black; border: none; border-radius: 3px; cursor: pointer; font-size: 10px; width: 100%;">
                    Purchase
                </button>
            </div>
        `).join('');
    }

    renderAvatarPreview(customization) {
        const previewDiv = document.getElementById('avatar-preview');
        if (!previewDiv) return;

        // In a real implementation, this would render a 3D preview
        // For now, we'll create a simple 2D representation
        previewDiv.innerHTML = `
            <div style="position: relative; width: 100%; height: 100%; background: linear-gradient(to bottom, #667eea 0%, #764ba2 100%); border-radius: 10px; overflow: hidden;">
                <!-- Simple avatar representation -->
                <div style="position: absolute; top: 20%; left: 50%; transform: translateX(-50%); width: 80px; height: 80px; background: ${this.rgbToCss(customization.skinColor)}; border-radius: 50%;"></div>
                <div style="position: absolute; top: 15%; left: 50%; transform: translateX(-50%); width: 100px; height: 40px; background: ${this.rgbToCss(customization.hairColor)}; border-radius: 50px 50px 0 0;"></div>
                <div style="position: absolute; top: 35%; left: 50%; transform: translateX(-50%); width: 120px; height: 80px; background: ${this.rgbToCss(customization.clothingColor)}; border-radius: 10px;"></div>
                <div style="position: absolute; top: 30%; left: 40%; width: 10px; height: 10px; background: ${this.rgbToCss(customization.eyeColor)}; border-radius: 50%;"></div>
                <div style="position: absolute; top: 30%; left: 60%; width: 10px; height: 10px; background: ${this.rgbToCss(customization.eyeColor)}; border-radius: 50%;"></div>
                <div style="position: absolute; top: 40%; left: 50%; transform: translateX(-50%); width: 30px; height: 5px; background: #333; border-radius: 2px;"></div>
            </div>
        `;
    }

    updateCustomization(category, value) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        let customization = this.customizationData.get(currentUser.id) || this.getDefaultCustomization();

        customization[category] = value;
        this.customizationData.set(currentUser.id, customization);

        this.renderAvatarPreview(customization);
    }

    updateColor(colorType, hexValue) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        let customization = this.customizationData.get(currentUser.id) || this.getDefaultCustomization();

        customization[colorType] = this.hexToRgb(hexValue);
        this.customizationData.set(currentUser.id, customization);

        this.renderAvatarPreview(customization);
    }

    updateFacialFeature(feature, value) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        let customization = this.customizationData.get(currentUser.id) || this.getDefaultCustomization();

        customization[feature] = parseFloat(value);
        this.customizationData.set(currentUser.id, customization);

        this.renderAvatarPreview(customization);
    }

    applyCustomization() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const customization = this.customizationData.get(currentUser.id);

        if (customization) {
            // Update the user's avatar in the scene
            const avatar = window.datingChat.sceneManager.avatars.find(av => av.userId === currentUser.id);
            if (avatar) {
                this.applyCustomizationToAvatar(avatar, customization);
            }

            // Save to server
            window.datingChat?.chatConnection?.sendMessage('avatar_customization', {
                userId: currentUser.id,
                customization
            });

            this.showNotification('Avatar customization applied!');
        }
    }

    applyCustomizationToAvatar(avatar, customization) {
        // Apply customization to the avatar's 3D model
        // This would update vertices, colors, and textures based on the customization

        // Update colors
        if (avatar.materials) {
            // Update skin color
            if (avatar.materials.head) {
                avatar.materials.head.diffuse = [customization.skinColor.r, customization.skinColor.g, customization.skinColor.b];
            }

            // Update clothing color
            if (avatar.materials.body) {
                avatar.materials.body.diffuse = [customization.clothingColor.r, customization.clothingColor.g, customization.clothingColor.b];
            }
        }

        // Update geometry based on selected assets
        this.applyAssetGeometry(avatar, customization);
    }

    applyAssetGeometry(avatar, customization) {
        // Apply hairstyle geometry
        const hairstyle = this.availableAssets.hairstyles.get(customization.hairstyles);
        if (hairstyle && hairstyle.vertices) {
            // Update avatar's hair vertices
        }

        // Apply clothing geometry
        const clothing = this.availableAssets.clothing.get(customization.clothing);
        if (clothing && clothing.vertices) {
            // Update avatar's clothing vertices
        }

        // Apply accessories
        const accessory = this.availableAssets.accessories.get(customization.accessories);
        if (accessory && accessory.vertices) {
            // Add accessory to avatar
        }
    }

    purchaseAsset(category, assetId) {
        const asset = this.availableAssets[category]?.get(assetId);
        if (!asset) return;

        // Check if user has enough coins
        const userCoins = this.getUserCoins();
        if (userCoins >= asset.price) {
            // Deduct coins and unlock asset
            this.deductUserCoins(asset.price);
            asset.unlocked = true;

            // Update UI
            this.renderCustomizationUI();
            this.showNotification(`Purchased ${asset.name}!`);
        } else {
            this.showNotification(`Not enough coins! You need ${asset.price} coins.`);
        }
    }

    getUserCoins() {
        // In a real app, this would come from user data
        return 100; // Example balance
    }

    deductUserCoins(amount) {
        // In a real app, this would update user data on server
        console.log(`Deducted ${amount} coins from user`);
    }

    saveCustomization() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const customization = this.customizationData.get(currentUser.id);

        if (customization) {
            // Save to localStorage or server
            localStorage.setItem(`avatar_preset_${currentUser.id}`, JSON.stringify(customization));
            this.showNotification('Customization preset saved!');
        }
    }

    loadCustomization() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const saved = localStorage.getItem(`avatar_preset_${currentUser.id}`);

        if (saved) {
            const customization = JSON.parse(saved);
            this.customizationData.set(currentUser.id, customization);
            this.applyCustomization();
        }
    }

    getDefaultCustomization() {
        return {
            hairstyles: 'short_spiky',
            clothing: 'casual_tshirt',
            accessories: 'none',
            skinColor: { r: 0.9, g: 0.7, b: 0.5 },
            hairColor: { r: 0.2, g: 0.1, b: 0.05 },
            eyeColor: { r: 0.3, g: 0.5, b: 0.8 },
            clothingColor: { r: 0.8, g: 0.2, b: 0.2 },
            eyeSize: 1.0,
            mouthSize: 1.0
        };
    }

    // Utility methods
    capitalizeFirst(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    rgbToHex(rgb) {
        if (typeof rgb === 'string') return rgb;
        return `#${((1 << 24) + (Math.round(rgb.r * 255) << 16) + (Math.round(rgb.g * 255) << 8) + Math.round(rgb.b * 255)).toString(16).slice(1)}`;
    }

    hexToRgb(hex) {
        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16) / 255,
            g: parseInt(result[2], 16) / 255,
            b: parseInt(result[3], 16) / 255
        } : { r: 1, g: 1, b: 1 };
    }

    rgbToCss(rgb) {
        if (typeof rgb === 'string') return rgb;
        return `rgb(${Math.round(rgb.r * 255)}, ${Math.round(rgb.g * 255)}, ${Math.round(rgb.b * 255)})`;
    }

    hideCustomizationPanel() {
        this.customizationPanel.style.display = 'none';
    }

    showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 20px;
            z-index: 1000;
        `;
        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }

    // Geometry generation methods (simplified)
    generateHairVertices(style) {
        // Generate vertices for different hairstyles
        // This would return Float32Array of vertices
        return new Float32Array([]);
    }

    generateClothingVertices(type) {
        // Generate vertices for different clothing types
        return new Float32Array([]);
    }

    generateAccessoryVertices(type) {
        // Generate vertices for accessories
        return new Float32Array([]);
    }
}

4. Security and Moderation

Let's implement security features and moderation tools:

// Security and moderation system
class SecuritySystem {
    constructor() {
        this.reportedUsers = new Map();
        this.blockedUsers = new Map();
        this.moderators = new Set();
        this.suspiciousActivities = [];
        this.automatedFilters = new AutomatedContentFilter();

        this.setupSecurityUI();
        this.startMonitoring();
    }

    setupSecurityUI() {
        this.createSecurityPanel();
        this.createQuickReportButton();
    }

    createSecurityPanel() {
        this.securityPanel = document.createElement('div');
        this.securityPanel.id = 'security-panel';
        this.securityPanel.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 20px;
            color: white;
            width: 500px;
            max-width: 90vw;
            max-height: 80vh;
            overflow-y: auto;
            display: none;
            z-index: 2000;
        `;

        document.getElementById('container').appendChild(this.securityPanel);
    }

    createQuickReportButton() {
        const reportButton = document.createElement('button');
        reportButton.innerHTML = '๐Ÿšฉ';
        reportButton.title = 'Report User';
        reportButton.style.cssText = `
            position: absolute;
            top: 20px;
            right: 170px;
            width: 40px;
            height: 40px;
            border: none;
            border-radius: 50%;
            background: rgba(255, 50, 50, 0.8);
            color: white;
            font-size: 18px;
            cursor: pointer;
            z-index: 1001;
        `;

        reportButton.addEventListener('click', () => {
            this.showQuickReportDialog();
        });

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

    showQuickReportDialog() {
        const nearbyUsers = this.getNearbyUsers();

        this.securityPanel.innerHTML = `
            <div style="text-align: center;">
                <h2>๐Ÿšฉ Report User</h2>
                <p>Select a user to report and choose the reason:</p>

                <div style="margin: 20px 0;">
                    <label style="display: block; margin-bottom: 10px; font-weight: bold;">Select User:</label>
                    <select id="report-user-select" style="width: 100%; padding: 10px; border-radius: 5px; background: #333; color: white; border: 1px solid #555;">
                        <option value="">Choose a user...</option>
                        ${nearbyUsers.map(user => `
                            <option value="${user.userId}">${user.name} (${user.distance}m away)</option>
                        `).join('')}
                    </select>
                </div>

                <div style="margin: 20px 0;">
                    <label style="display: block; margin-bottom: 10px; font-weight: bold;">Reason:</label>
                    <select id="report-reason" style="width: 100%; padding: 10px; border-radius: 5px; background: #333; color: white; border: 1px solid #555;">
                        <option value="harassment">Harassment or Bullying</option>
                        <option value="inappropriate">Inappropriate Content</option>
                        <option value="spam">Spam or Advertising</option>
                        <option value="impersonation">Impersonation</option>
                        <option value="other">Other</option>
                    </select>
                </div>

                <div style="margin: 20px 0;">
                    <label style="display: block; margin-bottom: 10px; font-weight: bold;">Additional Details (optional):</label>
                    <textarea id="report-details" placeholder="Please provide any additional information..." 
                              style="width: 100%; height: 100px; padding: 10px; border-radius: 5px; background: #222; color: white; border: 1px solid #555;"></textarea>
                </div>

                <div style="display: flex; gap: 10px; justify-content: center;">
                    <button onclick="securitySystem.submitReport()" 
                            style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 5px; cursor: pointer;">
                        Submit Report
                    </button>
                    <button onclick="securitySystem.hideSecurityPanel()" 
                            style="padding: 10px 20px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer;">
                        Cancel
                    </button>
                </div>

                <div style="margin-top: 20px; font-size: 12px; color: #aaa;">
                    Reports are anonymous. Our moderation team will review this report within 24 hours.
                </div>
            </div>
        `;

        this.securityPanel.style.display = 'block';
    }

    submitReport() {
        const userId = document.getElementById('report-user-select').value;
        const reason = document.getElementById('report-reason').value;
        const details = document.getElementById('report-details').value;

        if (!userId) {
            alert('Please select a user to report.');
            return;
        }

        const report = {
            reportedUserId: userId,
            reporterId: window.datingChat?.chatConnection?.getCurrentUser().id,
            reason,
            details,
            timestamp: Date.now(),
            status: 'pending'
        };

        // Store report locally
        if (!this.reportedUsers.has(userId)) {
            this.reportedUsers.set(userId, []);
        }
        this.reportedUsers.get(userId).push(report);

        // Send to server
        window.datingChat?.chatConnection?.sendMessage('user_report', report);

        // Auto-block if multiple reports or serious offense
        if (this.shouldAutoBlock(userId, reason)) {
            this.blockUser(userId);
        }

        this.hideSecurityPanel();
        this.showNotification('Thank you for your report. We will review it shortly.');
    }

    shouldAutoBlock(userId, reason) {
        const userReports = this.reportedUsers.get(userId) || [];

        // Auto-block for harassment or if user has multiple reports
        if (reason === 'harassment' || userReports.length >= 3) {
            return true;
        }

        return false;
    }

    blockUser(userId) {
        if (!this.blockedUsers.has(userId)) {
            this.blockedUsers.set(userId, {
                timestamp: Date.now(),
                reason: 'Multiple reports or serious violation'
            });

            // Hide blocked user's avatar
            const avatar = window.datingChat?.sceneManager?.avatars.find(av => av.userId === userId);
            if (avatar) {
                avatar.visible = false;
            }

            // Mute audio/video
            if (window.audioSystem) {
                window.audioSystem.muteUser(userId);
            }
            if (window.videoSystem) {
                window.videoSystem.removeRemoteStream(userId);
            }

            this.showNotification('User has been blocked.');

            // Notify server
            window.datingChat?.chatConnection?.sendMessage('user_blocked', {
                blockedUserId: userId,
                blockerId: window.datingChat?.chatConnection?.getCurrentUser().id
            });
        }
    }

    unblockUser(userId) {
        this.blockedUsers.delete(userId);

        // Show unblocked user's avatar
        const avatar = window.datingChat?.sceneManager?.avatars.find(av => av.userId === userId);
        if (avatar) {
            avatar.visible = true;
        }

        this.showNotification('User has been unblocked.');
    }

    getNearbyUsers() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const currentAvatar = window.datingChat?.sceneManager?.avatars.find(av => av.userId === currentUser.id);

        if (!currentAvatar) return [];

        return window.datingChat.sceneManager.avatars
            .filter(avatar => avatar.userId !== currentUser.id)
            .map(avatar => {
                const distance = this.calculateDistance(currentAvatar.position, avatar.position);
                return {
                    userId: avatar.userId,
                    name: avatar.name,
                    distance: Math.round(distance)
                };
            })
            .filter(user => user.distance < 10) // Only users within 10 units
            .sort((a, b) => a.distance - b.distance);
    }

    calculateDistance(pos1, pos2) {
        const dx = pos1[0] - pos2[0];
        const dy = pos1[1] - pos2[1];
        const dz = pos1[2] - pos2[2];
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    startMonitoring() {
        // Monitor chat for inappropriate content
        setInterval(() => {
            this.monitorChatActivity();
        }, 5000);

        // Monitor user behavior
        setInterval(() => {
            this.monitorUserBehavior();
        }, 10000);
    }

    monitorChatActivity() {
        // Check recent messages for inappropriate content
        const recentMessages = this.getRecentMessages();

        recentMessages.forEach(message => {
            if (this.automatedFilters.isInappropriate(message.content)) {
                this.flagSuspiciousActivity({
                    type: 'inappropriate_chat',
                    userId: message.senderId,
                    content: message.content,
                    timestamp: message.timestamp
                });
            }
        });
    }

    monitorUserBehavior() {
        // Monitor for suspicious behavior patterns
        const avatars = window.datingChat?.sceneManager?.avatars || [];

        avatars.forEach(avatar => {
            // Check for rapid movement (potential bot behavior)
            if (this.isRapidMovement(avatar)) {
                this.flagSuspiciousActivity({
                    type: 'rapid_movement',
                    userId: avatar.userId,
                    timestamp: Date.now()
                });
            }

            // Check for inappropriate proximity
            if (this.isInvadingPersonalSpace(avatar)) {
                this.flagSuspiciousActivity({
                    type: 'personal_space_invasion',
                    userId: avatar.userId,
                    timestamp: Date.now()
                });
            }
        });
    }

    isRapidMovement(avatar) {
        // Check if avatar is moving too rapidly (potential bot)
        // This would track movement history and detect patterns
        return false;
    }

    isInvadingPersonalSpace(avatar) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const currentAvatar = window.datingChat?.sceneManager?.avatars.find(av => av.userId === currentUser.id);

        if (!currentAvatar || avatar.userId === currentUser.id) return false;

        const distance = this.calculateDistance(currentAvatar.position, avatar.position);
        return distance < 1.0; // Too close
    }

    flagSuspiciousActivity(activity) {
        this.suspiciousActivities.push(activity);

        // Auto-take action for serious violations
        if (this.shouldAutoAction(activity)) {
            this.takeAutomatedAction(activity);
        }

        // Notify moderators
        if (this.moderators.size > 0) {
            this.notifyModerators(activity);
        }
    }

    shouldAutoAction(activity) {
        // Auto-action for clear violations
        return activity.type === 'inappropriate_chat' && 
               this.automatedFilters.getSeverity(activity.content) === 'high';
    }

    takeAutomatedAction(activity) {
        switch (activity.type) {
            case 'inappropriate_chat':
                this.muteUserTemporarily(activity.userId, 300000); // 5 minutes
                break;
            case 'personal_space_invasion':
                this.teleportUserAway(activity.userId);
                break;
        }
    }

    muteUserTemporarily(userId, duration) {
        if (window.audioSystem) {
            window.audioSystem.muteUser(userId);

            setTimeout(() => {
                window.audioSystem.unmuteUser(userId);
            }, duration);
        }

        this.showNotification(`User temporarily muted for ${duration / 60000} minutes.`);
    }

    teleportUserAway(userId) {
        const avatar = window.datingChat?.sceneManager?.avatars.find(av => av.userId === userId);
        if (avatar) {
            // Move avatar to a random location away from other users
            const newPosition = this.getSafeLocation();
            avatar.moveTo(newPosition);
        }
    }

    getSafeLocation() {
        // Find a location away from other users
        const angle = Math.random() * Math.PI * 2;
        const distance = 5 + Math.random() * 5;
        return [
            Math.cos(angle) * distance,
            0,
            Math.sin(angle) * distance
        ];
    }

    notifyModerators(activity) {
        // Send notification to online moderators
        this.moderators.forEach(moderatorId => {
            window.datingChat?.chatConnection?.sendMessage('moderator_alert', {
                moderatorId,
                activity,
                timestamp: Date.now()
            });
        });
    }

    getRecentMessages() {
        // Get recent chat messages for monitoring
        // This would interface with the chat system
        return [];
    }

    hideSecurityPanel() {
        this.securityPanel.style.display = 'none';
    }

    showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 20px;
            z-index: 1000;
        `;
        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }
}

// Automated content filter
class AutomatedContentFilter {
    constructor() {
        this.badWords = new Set([
            // Inappropriate words list (simplified)
            'badword1', 'badword2', 'badword3'
        ]);

        this.patterns = [
            { pattern: /(\d{10,})/g, reason: 'phone_number' }, // Phone numbers
            { pattern: /(\S+@\S+\.\S+)/g, reason: 'email' }, // Email addresses
            { pattern: /(http|https):\/\/[^\s]+/g, reason: 'url' } // URLs
        ];
    }

    isInappropriate(text) {
        const lowerText = text.toLowerCase();

        // Check for bad words
        for (const word of this.badWords) {
            if (lowerText.includes(word)) {
                return true;
            }
        }

        // Check for patterns
        for (const { pattern } of this.patterns) {
            if (pattern.test(text)) {
                return true;
            }
        }

        return false;
    }

    getSeverity(text) {
        // Determine severity of violation
        const lowerText = text.toLowerCase();

        // High severity words
        const highSeverityWords = ['verybadword1', 'verybadword2'];
        if (highSeverityWords.some(word => lowerText.includes(word))) {
            return 'high';
        }

        // Medium severity
        const mediumSeverityWords = ['mediumbadword1', 'mediumbadword2'];
        if (mediumSeverityWords.some(word => lowerText.includes(word))) {
            return 'medium';
        }

        return 'low';
    }

    filterMessage(text) {
        let filtered = text;

        // Replace bad words
        for (const word of this.badWords) {
            const regex = new RegExp(word, 'gi');
            filtered = filtered.replace(regex, '*'.repeat(word.length));
        }

        // Filter patterns
        for (const { pattern } of this.patterns) {
            filtered = filtered.replace(pattern, '[FILTERED]');
        }

        return filtered;
    }
}

5. Analytics and User Insights

Let's implement analytics to understand user behavior and improve the platform:

// Analytics and user insights system
class AnalyticsSystem {
    constructor() {
        this.userMetrics = new Map();
        this.sessionData = {
            startTime: Date.now(),
            interactions: [],
            movements: [],
            chatActivity: []
        };

        this.insightEngine = new InsightEngine();
        this.setupAnalytics();
    }

    setupAnalytics() {
        // Track user interactions
        this.trackInteractions();

        // Periodic data flush
        setInterval(() => {
            this.flushAnalyticsData();
        }, 60000); // Every minute
    }

    trackInteractions() {
        // Track avatar movements
        const originalMoveTo = AnimatedAvatar.prototype.moveTo;
        AnimatedAvatar.prototype.moveTo = function(position) {
            const result = originalMoveTo.call(this, position);

            // Log movement
            window.analyticsSystem.recordMovement(this.userId, position);

            return result;
        };

        // Track chat messages
        if (window.datingChat?.realTimeChat) {
            const originalAddMessage = window.datingChat.realTimeChat.addMessage;
            window.datingChat.realTimeChat.addMessage = function(sender, content, timestamp, isOwn) {
                const result = originalAddMessage.call(this, sender, content, timestamp, isOwn);

                // Log chat activity
                window.analyticsSystem.recordChatActivity(sender.id, content, timestamp);

                return result;
            };
        }
    }

    recordMovement(userId, position) {
        this.sessionData.movements.push({
            userId,
            position: [...position],
            timestamp: Date.now()
        });
    }

    recordChatActivity(userId, content, timestamp) {
        this.sessionData.chatActivity.push({
            userId,
            content,
            timestamp,
            length: content.length
        });

        // Generate real-time insights
        this.insightEngine.analyzeMessage(userId, content);
    }

    recordInteraction(type, targetUserId, metadata = {}) {
        const interaction = {
            type,
            sourceUserId: window.datingChat?.chatConnection?.getCurrentUser().id,
            targetUserId,
            timestamp: Date.now(),
            ...metadata
        };

        this.sessionData.interactions.push(interaction);

        // Update user metrics
        this.updateUserMetrics(interaction);
    }

    updateUserMetrics(interaction) {
        const userId = interaction.sourceUserId;

        if (!this.userMetrics.has(userId)) {
            this.userMetrics.set(userId, {
                totalInteractions: 0,
                interactionTypes: new Map(),
                activeTime: 0,
                lastActivity: Date.now()
            });
        }

        const metrics = this.userMetrics.get(userId);
        metrics.totalInteractions++;
        metrics.lastActivity = Date.now();

        // Track interaction types
        const typeCount = metrics.interactionTypes.get(interaction.type) || 0;
        metrics.interactionTypes.set(interaction.type, typeCount + 1);
    }

    flushAnalyticsData() {
        // Send analytics data to server
        const data = {
            sessionId: this.getSessionId(),
            userId: window.datingChat?.chatConnection?.getCurrentUser().id,
            ...this.sessionData,
            userMetrics: Object.fromEntries(this.userMetrics),
            insights: this.insightEngine.getInsights()
        };

        // Reset session data (keep user metrics)
        this.sessionData.interactions = [];
        this.sessionData.movements = [];
        this.sessionData.chatActivity = [];

        // Send to server (in real app)
        console.log('Flushing analytics data:', data);

        // Generate periodic insights
        this.generatePeriodicInsights();
    }

    generatePeriodicInsights() {
        const insights = this.insightEngine.generateInsights();

        // Show relevant insights to user
        if (insights.length > 0) {
            this.showInsightNotification(insights[0]);
        }
    }

    showInsightNotification(insight) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: absolute;
            bottom: 200px;
            right: 20px;
            background: rgba(0, 0, 0, 0.9);
            border-left: 4px solid #2196F3;
            border-radius: 8px;
            padding: 15px;
            color: white;
            max-width: 300px;
            z-index: 1000;
        `;

        notification.innerHTML = `
            <div style="font-size: 12px; color: #2196F3; margin-bottom: 5px;">๐Ÿ’ก Insight</div>
            <div style="font-size: 14px;">${insight.message}</div>
            ${insight.suggestion ? `<div style="font-size: 12px; color: #aaa; margin-top: 5px;">${insight.suggestion}</div>` : ''}
        `;

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

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 8000);
    }

    getSessionId() {
        let sessionId = localStorage.getItem('analytics_session_id');
        if (!sessionId) {
            sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
            localStorage.setItem('analytics_session_id', sessionId);
        }
        return sessionId;
    }

    // User behavior analysis
    analyzeUserBehavior(userId) {
        const metrics = this.userMetrics.get(userId);
        if (!metrics) return null;

        const behavior = {
            activityLevel: this.calculateActivityLevel(metrics),
            socialTendency: this.calculateSocialTendency(metrics),
            preferredInteractions: this.getPreferredInteractions(metrics),
            engagementScore: this.calculateEngagementScore(metrics)
        };

        return behavior;
    }

    calculateActivityLevel(metrics) {
        const timeSinceLastActivity = Date.now() - metrics.lastActivity;
        const activityScore = metrics.totalInteractions / (timeSinceLastActivity / 60000); // interactions per minute

        if (activityScore > 2) return 'high';
        if (activityScore > 0.5) return 'medium';
        return 'low';
    }

    calculateSocialTendency(metrics) {
        const socialInteractions = metrics.interactionTypes.get('chat') || 0;
        const totalInteractions = metrics.totalInteractions;

        const socialRatio = totalInteractions > 0 ? socialInteractions / totalInteractions : 0;

        if (socialRatio > 0.7) return 'social';
        if (socialRatio > 0.3) return 'balanced';
        return 'reserved';
    }

    getPreferredInteractions(metrics) {
        const types = Array.from(metrics.interactionTypes.entries());
        return types.sort((a, b) => b[1] - a[1]).slice(0, 3).map(([type]) => type);
    }

    calculateEngagementScore(metrics) {
        // Calculate overall engagement score (0-100)
        const sessionDuration = (Date.now() - this.sessionData.startTime) / 60000; // minutes
        const interactionsPerMinute = metrics.totalInteractions / sessionDuration;

        return Math.min(100, interactionsPerMinute * 10);
    }
}

// Insight generation engine
class InsightEngine {
    constructor() {
        this.userPatterns = new Map();
        this.conversationMetrics = new Map();
        this.insightHistory = [];
    }

    analyzeMessage(userId, message) {
        if (!this.conversationMetrics.has(userId)) {
            this.conversationMetrics.set(userId, {
                totalMessages: 0,
                averageLength: 0,
                responseTimes: [],
                topics: new Set(),
                sentimentScores: []
            });
        }

        const metrics = this.conversationMetrics.get(userId);
        metrics.totalMessages++;

        // Update average message length
        metrics.averageLength = (metrics.averageLength * (metrics.totalMessages - 1) + message.length) / metrics.totalMessages;

        // Extract topics
        const topics = this.extractTopics(message);
        topics.forEach(topic => metrics.topics.add(topic));

        // Analyze sentiment
        const sentiment = this.analyzeSentiment(message);
        metrics.sentimentScores.push(sentiment);

        // Detect patterns
        this.detectPatterns(userId, message, metrics);
    }

    extractTopics(message) {
        const topics = [];
        const topicKeywords = {
            'sports': ['game', 'sports', 'team', 'play', 'win', 'lose'],
            'music': ['music', 'song', 'band', 'concert', 'listen', 'album'],
            'travel': ['travel', 'vacation', 'trip', 'beach', 'mountains', 'hotel'],
            'food': ['food', 'restaurant', 'cook', 'recipe', 'dinner', 'lunch'],
            'movies': ['movie', 'film', 'watch', 'netflix', 'cinema', 'actor']
        };

        const lowerMessage = message.toLowerCase();
        for (const [topic, keywords] of Object.entries(topicKeywords)) {
            if (keywords.some(keyword => lowerMessage.includes(keyword))) {
                topics.push(topic);
            }
        }

        return topics;
    }

    analyzeSentiment(message) {
        // Simplified sentiment analysis
        const positiveWords = ['love', 'great', 'awesome', 'amazing', 'happy', 'good', 'nice', 'wonderful', 'excited'];
        const negativeWords = ['hate', 'bad', 'terrible', 'awful', 'sad', 'angry', 'horrible', 'boring', 'disappointed'];

        let score = 0;
        const words = message.toLowerCase().split(/\s+/);

        words.forEach(word => {
            if (positiveWords.includes(word)) score += 1;
            if (negativeWords.includes(word)) score -= 1;
        });

        return Math.max(-1, Math.min(1, score / Math.max(1, words.length)));
    }

    detectPatterns(userId, message, metrics) {
        // Detect conversation patterns
        const patterns = this.userPatterns.get(userId) || {
            questionFrequency: 0,
            emojiUsage: 0,
            responseStyle: 'neutral'
        };

        // Question frequency
        if (message.includes('?')) {
            patterns.questionFrequency++;
        }

        // Emoji usage
        const emojiCount = (message.match(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]/gu) || []).length;
        patterns.emojiUsage = (patterns.emojiUsage * (metrics.totalMessages - 1) + emojiCount) / metrics.totalMessages;

        // Response style based on message length and content
        if (message.length > 100) {
            patterns.responseStyle = 'detailed';
        } else if (message.length < 30) {
            patterns.responseStyle = 'concise';
        }

        this.userPatterns.set(userId, patterns);
    }

    generateInsights() {
        const insights = [];
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();

        if (!currentUser) return insights;

        const userMetrics = this.conversationMetrics.get(currentUser.id);
        const patterns = this.userPatterns.get(currentUser.id);

        if (!userMetrics || !patterns) return insights;

        // Generate insights based on user behavior
        if (userMetrics.totalMessages > 10) {
            // Conversation style insight
            if (patterns.responseStyle === 'detailed' && userMetrics.averageLength > 150) {
                insights.push({
                    type: 'conversation_style',
                    message: "You tend to write detailed messages. This shows thoughtfulness!",
                    suggestion: "Try mixing in some shorter, casual messages to keep conversations dynamic.",
                    priority: 'medium'
                });
            }

            // Question asking insight
            const questionRatio = patterns.questionFrequency / userMetrics.totalMessages;
            if (questionRatio < 0.1) {
                insights.push({
                    type: 'engagement',
                    message: "Asking more questions can help keep conversations flowing.",
                    suggestion: "Try asking open-ended questions about their interests or experiences.",
                    priority: 'high'
                });
            }

            // Sentiment insight
            const avgSentiment = userMetrics.sentimentScores.reduce((a, b) => a + b, 0) / userMetrics.sentimentScores.length;
            if (avgSentiment < -0.3) {
                insights.push({
                    type: 'sentiment',
                    message: "Your messages have been leaning negative recently.",
                    suggestion: "Sharing positive experiences can create a more uplifting conversation atmosphere.",
                    priority: 'medium'
                });
            }
        }

        // Social activity insight
        const nearbyUsers = window.datingChat?.sceneManager?.avatars.length - 1 || 0;
        if (nearbyUsers > 3 && userMetrics.totalMessages < 5) {
            insights.push({
                type: 'social_opportunity',
                message: `There are ${nearbyUsers} people nearby. Great opportunity to meet someone new!`,
                suggestion: "Try starting a conversation or joining a group chat.",
                priority: 'high'
            });
        }

        // Sort by priority and limit to top 3
        const priorityOrder = { high: 3, medium: 2, low: 1 };
        return insights
            .sort((a, b) => priorityOrder[b.priority] - priorityOrder[a.priority])
            .slice(0, 3);
    }

    getInsights() {
        return {
            userPatterns: Object.fromEntries(this.userPatterns),
            conversationMetrics: Object.fromEntries(this.conversationMetrics),
            generatedInsights: this.generateInsights()
        };
    }
}

Updated Main Application Integration

Finally, let's update our main application to integrate all these new advanced features:

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

        this.aiMatchmaker = null;
        this.miniGameSystem = null;
        this.avatarCustomizer = null;
        this.securitySystem = null;
        this.analyticsSystem = null;

        this.init();
    }

    async init() {
        this.setupWebGL();
        this.setupShaders();
        this.setupCamera();
        this.setupLighting();
        this.setupTextures();
        this.setupScene();
        this.setupNetwork();
        this.setupSocialFeatures();
        this.setupAdvancedFeatures();
        this.setupAIFeatures(); // New
        this.setupInteraction();
        this.render();
    }

    setupAIFeatures() {
        // Initialize AI systems
        this.aiMatchmaker = new AIMatchmaker();
        this.miniGameSystem = new MiniGameSystem();
        this.avatarCustomizer = new AvatarCustomizer(this.sceneManager);
        this.securitySystem = new SecuritySystem();
        this.analyticsSystem = new AnalyticsSystem();

        // Set global references
        window.aiMatchmaker = this.aiMatchmaker;
        window.miniGameSystem = this.miniGameSystem;
        window.avatarCustomizer = this.avatarCustomizer;
        window.securitySystem = this.securitySystem;
        window.analyticsSystem = this.analyticsSystem;

        // Setup matchmaking for current user
        const currentUser = this.chatConnection.getCurrentUser();
        const userProfile = window.profileSystem?.currentUserProfile;
        if (userProfile) {
            this.aiMatchmaker.addUserProfile(currentUser.id, userProfile);
        }

        console.log('AI features initialized');
    }

    // ... rest of the class
}

// Make all systems globally accessible
window.addEventListener('load', async () => {
    window.datingChat = new DatingChat3D();

    // Set global references after initialization
    setTimeout(() => {
        window.aiMatchmaker = window.datingChat.aiMatchmaker;
        window.miniGameSystem = window.datingChat.miniGameSystem;
        window.avatarCustomizer = window.datingChat.avatarCustomizer;
        window.securitySystem = window.datingChat.securitySystem;
        window.analyticsSystem = window.datingChat.analyticsSystem;
    }, 1000);
});

What We've Accomplished in Part 6

In this sixth part, we've transformed our 3D dating platform into an intelligent, engaging, and secure environment with:

  1. AI-Powered Matchmaking with intelligent compatibility scoring and behavioral analysis
  2. Interactive Mini-Games including ice breakers, trivia, and collaborative puzzles
  3. Advanced Avatar Customization with extensive appearance options and asset store
  4. Comprehensive Security System with reporting, blocking, and automated moderation
  5. Advanced Analytics with user behavior tracking and personalized insights

Key Features Added:

Next Steps

In Part 7, we'll focus on:

Our platform now has the sophisticated features needed to compete with commercial dating applications, providing a safe, engaging, and intelligent environment for meaningful connections!

Part 7: Virtual Economy, Environment Customization, and Advanced AI

Welcome to Part 7 of our 10-part tutorial series! In this installment, we'll implement a virtual economy, user-customizable environments, advanced AI assistants, and cross-platform features to create a truly comprehensive dating platform.

Table of Contents for Part 7

  1. Virtual Economy System
  2. Environment Customization
  3. AI Conversation Assistant
  4. Cross-Platform Features
  5. Advanced Performance Optimization

1. Virtual Economy System

Let's create a comprehensive virtual economy with coins, rewards, and premium features:

// Virtual economy and reward system
class VirtualEconomy {
    constructor() {
        this.userBalances = new Map();
        this.transactionHistory = new Map();
        this.rewardSystem = new RewardSystem();
        this.premiumFeatures = new PremiumFeatures();
        this.dailyQuests = new DailyQuestSystem();

        this.setupEconomyUI();
        this.startPeriodicRewards();
    }

    // Initialize user wallet
    initializeUser(userId, initialBalance = 100) {
        this.userBalances.set(userId, {
            coins: initialBalance,
            gems: 0,
            premium: false,
            joinDate: new Date(),
            totalEarned: initialBalance
        });

        this.transactionHistory.set(userId, []);

        // Add welcome bonus transaction
        this.addTransaction(userId, {
            type: 'welcome_bonus',
            amount: initialBalance,
            description: 'Welcome bonus',
            timestamp: new Date()
        });

        console.log(`Economy initialized for user ${userId} with ${initialBalance} coins`);
    }

    // Get user balance
    getUserBalance(userId) {
        return this.userBalances.get(userId) || { coins: 0, gems: 0, premium: false };
    }

    // Add coins to user account
    addCoins(userId, amount, source = 'system', description = '') {
        const balance = this.userBalances.get(userId);
        if (!balance) {
            console.warn(`User ${userId} not found in economy system`);
            return false;
        }

        balance.coins += amount;
        balance.totalEarned += amount;

        this.addTransaction(userId, {
            type: 'credit',
            amount,
            source,
            description,
            timestamp: new Date()
        });

        this.updateBalanceUI(userId);
        this.checkAchievements(userId);

        return true;
    }

    // Spend coins (with validation)
    spendCoins(userId, amount, item = 'purchase', description = '') {
        const balance = this.userBalances.get(userId);
        if (!balance || balance.coins < amount) {
            return false;
        }

        balance.coins -= amount;

        this.addTransaction(userId, {
            type: 'debit',
            amount: -amount,
            item,
            description,
            timestamp: new Date()
        });

        this.updateBalanceUI(userId);
        return true;
    }

    // Add transaction to history
    addTransaction(userId, transaction) {
        if (!this.transactionHistory.has(userId)) {
            this.transactionHistory.set(userId, []);
        }

        const history = this.transactionHistory.get(userId);
        history.unshift(transaction);

        // Keep only last 100 transactions
        if (history.length > 100) {
            history.pop();
        }
    }

    // Get transaction history
    getTransactionHistory(userId, limit = 10) {
        const history = this.transactionHistory.get(userId) || [];
        return history.slice(0, limit);
    }

    // Setup economy UI
    setupEconomyUI() {
        this.createWalletDisplay();
        this.createCoinStore();
        this.createEarningOpportunitiesPanel();
    }

    createWalletDisplay() {
        this.walletDisplay = document.createElement('div');
        this.walletDisplay.id = 'wallet-display';
        this.walletDisplay.style.cssText = `
            position: absolute;
            top: 20px;
            right: 210px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 20px;
            padding: 8px 15px;
            color: white;
            font-family: Arial, sans-serif;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 10px;
            z-index: 1001;
            cursor: pointer;
            transition: background 0.3s;
        `;

        this.walletDisplay.innerHTML = `
            <div style="display: flex; align-items: center; gap: 5px;">
                <span style="color: gold;">๐Ÿช™</span>
                <span id="coin-balance">100</span>
            </div>
            <div style="display: flex; align-items: center; gap: 5px;">
                <span style="color: #00ffd5;">๐Ÿ’Ž</span>
                <span id="gem-balance">0</span>
            </div>
        `;

        this.walletDisplay.addEventListener('click', () => {
            this.showWalletDetails();
        });

        this.walletDisplay.addEventListener('mouseenter', () => {
            this.walletDisplay.style.background = 'rgba(50, 50, 50, 0.9)';
        });

        this.walletDisplay.addEventListener('mouseleave', () => {
            this.walletDisplay.style.background = 'rgba(0, 0, 0, 0.8)';
        });

        document.getElementById('container').appendChild(this.walletDisplay);
    }

    updateBalanceUI(userId) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        if (!currentUser || currentUser.id !== userId) return;

        const balance = this.getUserBalance(userId);

        const coinElement = document.getElementById('coin-balance');
        const gemElement = document.getElementById('gem-balance');

        if (coinElement) coinElement.textContent = balance.coins;
        if (gemElement) gemElement.textContent = balance.gems;

        // Add animation for balance changes
        this.animateBalanceChange();
    }

    animateBalanceChange() {
        this.walletDisplay.style.transform = 'scale(1.1)';
        setTimeout(() => {
            this.walletDisplay.style.transform = 'scale(1)';
        }, 200);
    }

    showWalletDetails() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const balance = this.getUserBalance(currentUser.id);
        const transactions = this.getTransactionHistory(currentUser.id, 5);

        const modal = document.createElement('div');
        modal.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 25px;
            color: white;
            width: 400px;
            max-width: 90vw;
            z-index: 2000;
            border: 2px solid gold;
        `;

        modal.innerHTML = `
            <div style="text-align: center; margin-bottom: 20px;">
                <h2 style="margin: 0; color: gold;">๐Ÿ’ฐ Your Wallet</h2>
                <div style="font-size: 12px; color: #aaa;">Balance Overview</div>
            </div>

            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 25px;">
                <div style="background: rgba(255, 215, 0, 0.2); padding: 15px; border-radius: 10px; text-align: center;">
                    <div style="font-size: 24px; margin-bottom: 5px;">๐Ÿช™</div>
                    <div style="font-size: 18px; font-weight: bold;">${balance.coins}</div>
                    <div style="font-size: 12px; color: #aaa;">Coins</div>
                </div>
                <div style="background: rgba(0, 255, 213, 0.2); padding: 15px; border-radius: 10px; text-align: center;">
                    <div style="font-size: 24px; margin-bottom: 5px;">๐Ÿ’Ž</div>
                    <div style="font-size: 18px; font-weight: bold;">${balance.gems}</div>
                    <div style="font-size: 12px; color: #aaa;">Gems</div>
                </div>
            </div>

            <div style="margin-bottom: 20px;">
                <h3 style="margin-bottom: 10px;">Recent Transactions</h3>
                <div style="max-height: 200px; overflow-y: auto;">
                    ${transactions.length > 0 ? transactions.map(tx => `
                        <div style="display: flex; justify-content: space-between; padding: 8px; border-bottom: 1px solid #333;">
                            <div>
                                <div style="font-size: 12px; font-weight: bold;">${tx.description || tx.item || tx.source}</div>
                                <div style="font-size: 10px; color: #aaa;">${new Date(tx.timestamp).toLocaleDateString()}</div>
                            </div>
                            <div style="color: ${tx.amount > 0 ? '#4CAF50' : '#f44336'}; font-weight: bold;">
                                ${tx.amount > 0 ? '+' : ''}${tx.amount}
                            </div>
                        </div>
                    `).join('') : '<div style="text-align: center; color: #aaa; padding: 20px;">No transactions yet</div>'}
                </div>
            </div>

            <div style="display: flex; gap: 10px;">
                <button onclick="virtualEconomy.showCoinStore()" 
                        style="flex: 1; padding: 10px; background: linear-gradient(45deg, #FFD700, #FFA000); color: black; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">
                    Buy Coins
                </button>
                <button onclick="virtualEconomy.showEarningOpportunities()" 
                        style="flex: 1; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer;">
                    Earn Coins
                </button>
                <button onclick="this.parentElement.parentElement.remove()" 
                        style="padding: 10px; background: #666; color: white; border: none; border-radius: 8px; cursor: pointer;">
                    Close
                </button>
            </div>
        `;

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

        // Close modal when clicking outside
        const closeModal = (e) => {
            if (e.target === modal) {
                modal.remove();
                document.removeEventListener('click', closeModal);
            }
        };
        setTimeout(() => document.addEventListener('click', closeModal), 100);
    }

    createCoinStore() {
        this.coinStore = document.createElement('div');
        this.coinStore.id = 'coin-store';
        this.coinStore.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 25px;
            color: white;
            width: 500px;
            max-width: 90vw;
            max-height: 80vh;
            overflow-y: auto;
            display: none;
            z-index: 2000;
            border: 2px solid #FFD700;
        `;

        document.getElementById('container').appendChild(this.coinStore);
    }

    showCoinStore() {
        const coinPackages = [
            { coins: 100, price: 0.99, bonus: 0, popular: false },
            { coins: 300, price: 2.99, bonus: 30, popular: false },
            { coins: 600, price: 4.99, bonus: 100, popular: true },
            { coins: 1200, price: 8.99, bonus: 300, popular: false },
            { coins: 2500, price: 14.99, bonus: 750, popular: false },
            { coins: 5000, price: 24.99, bonus: 2000, popular: false }
        ];

        this.coinStore.innerHTML = `
            <div style="text-align: center; margin-bottom: 25px;">
                <h2 style="margin: 0; color: gold;">๐Ÿช™ Coin Store</h2>
                <div style="font-size: 12px; color: #aaa;">Boost your dating experience</div>
            </div>

            <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-bottom: 25px;">
                ${coinPackages.map(pkg => `
                    <div style="background: ${pkg.popular ? 'rgba(255, 215, 0, 0.1)' : 'rgba(255, 255, 255, 0.05)'}; 
                         border: 2px solid ${pkg.popular ? 'gold' : '#333'}; 
                         border-radius: 10px; padding: 20px; text-align: center; cursor: pointer; transition: transform 0.2s;"
                         onclick="virtualEconomy.purchaseCoins(${pkg.coins}, ${pkg.price})">
                        ${pkg.popular ? '<div style="background: gold; color: black; padding: 2px 8px; border-radius: 10px; font-size: 10px; margin-bottom: 10px;">MOST POPULAR</div>' : ''}
                        <div style="font-size: 24px; font-weight: bold; color: gold; margin-bottom: 5px;">${pkg.coins + pkg.bonus}</div>
                        <div style="font-size: 12px; color: #aaa; margin-bottom: 10px;">${pkg.coins} + ${pkg.bonus} bonus</div>
                        <div style="font-size: 18px; font-weight: bold; margin-bottom: 5px;">$${pkg.price}</div>
                        <div style="font-size: 10px; color: #4CAF50;">$${(pkg.price / (pkg.coins + pkg.bonus)).toFixed(4)} per coin</div>
                    </div>
                `).join('')}
            </div>

            <div style="background: rgba(255, 215, 0, 0.1); padding: 15px; border-radius: 10px; margin-bottom: 20px;">
                <h4 style="margin: 0 0 10px 0; color: gold;">๐Ÿ’Ž Premium Subscription</h4>
                <div style="font-size: 14px; margin-bottom: 10px;">Get unlimited features and 1000 coins monthly!</div>
                <button onclick="virtualEconomy.purchasePremium()" 
                        style="width: 100%; padding: 12px; background: linear-gradient(45deg, #FFD700, #FFA000); color: black; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">
                    Subscribe Premium - $9.99/month
                </button>
            </div>

            <button onclick="virtualEconomy.hideCoinStore()" 
                    style="width: 100%; padding: 10px; background: #666; color: white; border: none; border-radius: 8px; cursor: pointer;">
                Close Store
            </button>
        `;

        this.coinStore.style.display = 'block';
    }

    hideCoinStore() {
        this.coinStore.style.display = 'none';
    }

    purchaseCoins(coinAmount, price) {
        // In a real application, this would integrate with a payment processor
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();

        // Simulate payment processing
        this.showPaymentProcessing(coinAmount, price);

        setTimeout(() => {
            // Simulate successful payment
            this.addCoins(currentUser.id, coinAmount, 'purchase', `Purchased ${coinAmount} coins`);
            this.showPurchaseSuccess(coinAmount);
        }, 2000);
    }

    purchasePremium() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const balance = this.getUserBalance(currentUser.id);

        // Simulate premium subscription
        balance.premium = true;
        this.addCoins(currentUser.id, 1000, 'premium', 'Premium subscription bonus');

        this.showNotification('๐ŸŽ‰ Welcome to Premium! Enjoy exclusive features and monthly coin rewards!');
        this.hideCoinStore();
    }

    showPaymentProcessing(coins, price) {
        const processing = document.createElement('div');
        processing.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 30px;
            color: white;
            text-align: center;
            z-index: 3000;
            border: 2px solid gold;
        `;

        processing.innerHTML = `
            <div style="font-size: 48px; margin-bottom: 20px;">โณ</div>
            <h3 style="margin: 0 0 10px 0;">Processing Payment</h3>
            <p style="margin: 0; color: #aaa;">Purchasing ${coins} coins for $${price}</p>
            <div style="margin-top: 20px; font-size: 12px; color: #aaa;">
                Please wait...
            </div>
        `;

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

        return processing;
    }

    showPurchaseSuccess(coins) {
        document.querySelectorAll('div').forEach(div => {
            if (div.innerHTML.includes('Processing Payment')) {
                div.remove();
            }
        });

        const success = document.createElement('div');
        success.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 30px;
            color: white;
            text-align: center;
            z-index: 3000;
            border: 2px solid #4CAF50;
        `;

        success.innerHTML = `
            <div style="font-size: 48px; margin-bottom: 20px;">๐ŸŽ‰</div>
            <h3 style="margin: 0 0 10px 0; color: #4CAF50;">Purchase Successful!</h3>
            <p style="margin: 0; font-size: 18px; color: gold;">+${coins} coins</p>
            <p style="margin: 10px 0 0 0; color: #aaa;">Your balance has been updated</p>
            <button onclick="this.parentElement.remove()" 
                    style="margin-top: 20px; padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer;">
                Awesome!
            </button>
        `;

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

    createEarningOpportunitiesPanel() {
        this.earningPanel = document.createElement('div');
        this.earningPanel.id = 'earning-panel';
        this.earningPanel.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 25px;
            color: white;
            width: 500px;
            max-width: 90vw;
            max-height: 80vh;
            overflow-y: auto;
            display: none;
            z-index: 2000;
            border: 2px solid #4CAF50;
        `;

        document.getElementById('container').appendChild(this.earningPanel);
    }

    showEarningOpportunities() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const dailyQuests = this.dailyQuests.getQuests(currentUser.id);
        const achievements = this.rewardSystem.getAvailableAchievements(currentUser.id);

        this.earningPanel.innerHTML = `
            <div style="text-align: center; margin-bottom: 25px;">
                <h2 style="margin: 0; color: #4CAF50;">๐Ÿ’Ž Earn Free Coins</h2>
                <div style="font-size: 12px; color: #aaa;">Complete tasks and earn rewards</div>
            </div>

            <div style="margin-bottom: 25px;">
                <h3 style="margin-bottom: 15px;">๐Ÿ“… Daily Quests</h3>
                <div style="display: flex; flex-direction: column; gap: 10px;">
                    ${dailyQuests.map(quest => `
                        <div style="background: rgba(255, 255, 255, 0.05); padding: 15px; border-radius: 8px; display: flex; justify-content: between; align-items: center;">
                            <div style="flex: 1;">
                                <div style="font-weight: bold; margin-bottom: 5px;">${quest.title}</div>
                                <div style="font-size: 12px; color: #aaa;">${quest.description}</div>
                                <div style="margin-top: 8px;">
                                    <div style="background: #333; border-radius: 10px; height: 6px;">
                                        <div style="background: #4CAF50; height: 100%; border-radius: 10px; width: ${(quest.progress / quest.required) * 100}%;"></div>
                                    </div>
                                    <div style="font-size: 10px; text-align: right; margin-top: 2px;">${quest.progress}/${quest.required}</div>
                                </div>
                            </div>
                            <div style="text-align: center; min-width: 80px;">
                                <div style="color: gold; font-weight: bold; font-size: 14px;">+${quest.reward}</div>
                                <div style="font-size: 10px; color: #aaa;">coins</div>
                                <button onclick="virtualEconomy.claimQuest('${quest.id}')" 
                                        ${quest.completed ? 'disabled' : ''}
                                        style="margin-top: 5px; padding: 5px 10px; background: ${quest.completed ? '#666' : '#4CAF50'}; color: white; border: none; border-radius: 5px; cursor: ${quest.completed ? 'default' : 'pointer'}; font-size: 10px;">
                                    ${quest.completed ? 'Claimed' : 'Claim'}
                                </button>
                            </div>
                        </div>
                    `).join('')}
                </div>
            </div>

            <div style="margin-bottom: 25px;">
                <h3 style="margin-bottom: 15px;">๐Ÿ† Achievements</h3>
                <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;">
                    ${achievements.slice(0, 4).map(achievement => `
                        <div style="background: rgba(255, 215, 0, 0.1); padding: 15px; border-radius: 8px; text-align: center;">
                            <div style="font-size: 24px; margin-bottom: 8px;">${achievement.icon}</div>
                            <div style="font-weight: bold; font-size: 12px; margin-bottom: 5px;">${achievement.title}</div>
                            <div style="font-size: 10px; color: #aaa; margin-bottom: 8px;">${achievement.description}</div>
                            <div style="color: gold; font-size: 11px; font-weight: bold;">+${achievement.reward} coins</div>
                            ${achievement.completed ? 
                                '<div style="color: #4CAF50; font-size: 10px; margin-top: 5px;">โœ… Completed</div>' :
                                '<div style="font-size: 10px; color: #aaa; margin-top: 5px;">Progress: ' + achievement.progress + '/' + achievement.required + '</div>'
                            }
                        </div>
                    `).join('')}
                </div>
            </div>

            <div style="background: rgba(0, 150, 255, 0.1); padding: 15px; border-radius: 10px;">
                <h4 style="margin: 0 0 10px 0; color: #0096FF;">๐Ÿ‘ฅ Refer Friends</h4>
                <div style="font-size: 14px; margin-bottom: 10px;">Invite friends and earn 100 coins for each friend who joins!</div>
                <div style="display: flex; gap: 10px;">
                    <input type="text" id="referral-link" value="https://datingapp.com/invite/${currentUser.id}" 
                           style="flex: 1; padding: 8px; background: #222; color: white; border: 1px solid #444; border-radius: 5px; font-size: 12px;" readonly>
                    <button onclick="virtualEconomy.copyReferralLink()" 
                            style="padding: 8px 15px; background: #0096FF; color: white; border: none; border-radius: 5px; cursor: pointer;">
                        Copy
                    </button>
                </div>
            </div>

            <button onclick="virtualEconomy.hideEarningPanel()" 
                    style="width: 100%; margin-top: 20px; padding: 10px; background: #666; color: white; border: none; border-radius: 8px; cursor: pointer;">
                Close
            </button>
        `;

        this.earningPanel.style.display = 'block';
    }

    hideEarningPanel() {
        this.earningPanel.style.display = 'none';
    }

    copyReferralLink() {
        const linkInput = document.getElementById('referral-link');
        linkInput.select();
        document.execCommand('copy');

        this.showNotification('Referral link copied to clipboard!');
    }

    claimQuest(questId) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const reward = this.dailyQuests.claimQuest(currentUser.id, questId);

        if (reward) {
            this.addCoins(currentUser.id, reward, 'quest', 'Daily quest reward');
            this.showNotification(`๐ŸŽ‰ Quest completed! +${reward} coins`);
            this.showEarningOpportunities(); // Refresh the panel
        }
    }

    startPeriodicRewards() {
        // Give small rewards for active usage
        setInterval(() => {
            this.giveActiveUsageReward();
        }, 300000); // Every 5 minutes

        // Reset daily quests
        setInterval(() => {
            this.dailyQuests.resetDailyQuests();
        }, 24 * 60 * 60 * 1000); // Every 24 hours
    }

    giveActiveUsageReward() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        if (!currentUser) return;

        // Check if user is active (has recent interactions)
        const isActive = this.checkUserActivity(currentUser.id);

        if (isActive) {
            this.addCoins(currentUser.id, 5, 'activity', 'Active usage reward');
        }
    }

    checkUserActivity(userId) {
        // Check if user has been active in the last 10 minutes
        // This would interface with the analytics system
        return true; // Simplified
    }

    showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 12px 24px;
            border-radius: 25px;
            z-index: 1000;
            font-size: 14px;
        `;
        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }
}

// Daily quest system
class DailyQuestSystem {
    constructor() {
        this.userQuests = new Map();
        this.availableQuests = this.generateDailyQuests();
    }

    generateDailyQuests() {
        return [
            {
                id: 'chat_5',
                title: 'Social Butterfly',
                description: 'Send 5 chat messages',
                required: 5,
                reward: 25,
                type: 'chat'
            },
            {
                id: 'meet_3',
                title: 'Making Connections',
                description: 'Meet 3 new people',
                required: 3,
                reward: 50,
                type: 'social'
            },
            {
                id: 'game_1',
                title: 'Fun and Games',
                description: 'Play 1 mini-game',
                required: 1,
                reward: 30,
                type: 'game'
            },
            {
                id: 'profile_1',
                title: 'Profile Perfection',
                description: 'Update your profile',
                required: 1,
                reward: 20,
                type: 'profile'
            },
            {
                id: 'avatar_1',
                title: 'Style Update',
                description: 'Customize your avatar',
                required: 1,
                reward: 15,
                type: 'avatar'
            }
        ];
    }

    getQuests(userId) {
        if (!this.userQuests.has(userId)) {
            this.initializeUserQuests(userId);
        }

        const userQuestData = this.userQuests.get(userId);
        return this.availableQuests.map(quest => ({
            ...quest,
            progress: userQuestData[quest.id] || 0,
            completed: (userQuestData[quest.id] || 0) >= quest.required
        }));
    }

    initializeUserQuests(userId) {
        const questProgress = {};
        this.availableQuests.forEach(quest => {
            questProgress[quest.id] = 0;
        });
        this.userQuests.set(userId, questProgress);
    }

    updateQuestProgress(userId, questType, amount = 1) {
        if (!this.userQuests.has(userId)) {
            this.initializeUserQuests(userId);
        }

        const userQuestData = this.userQuests.get(userId);

        // Find quests of this type
        this.availableQuests.forEach(quest => {
            if (quest.type === questType) {
                const currentProgress = userQuestData[quest.id] || 0;
                userQuestData[quest.id] = Math.min(quest.required, currentProgress + amount);
            }
        });
    }

    claimQuest(userId, questId) {
        const userQuestData = this.userQuests.get(userId);
        if (!userQuestData) return null;

        const quest = this.availableQuests.find(q => q.id === questId);
        if (!quest) return null;

        const progress = userQuestData[questId] || 0;
        if (progress >= quest.required) {
            // Mark as claimed by resetting progress (simplified)
            userQuestData[questId] = 0;
            return quest.reward;
        }

        return null;
    }

    resetDailyQuests() {
        // Reset all user quests (in a real app, this would be more sophisticated)
        this.userQuests.clear();
    }
}

// Reward and achievement system
class RewardSystem {
    constructor() {
        this.achievements = new Map();
        this.userAchievements = new Map();

        this.setupAchievements();
    }

    setupAchievements() {
        this.achievements.set('first_chat', {
            id: 'first_chat',
            title: 'First Conversation',
            description: 'Send your first chat message',
            icon: '๐Ÿ’ฌ',
            reward: 50,
            required: 1
        });

        this.achievements.set('social_butterfly', {
            id: 'social_butterfly',
            title: 'Social Butterfly',
            description: 'Chat with 10 different people',
            icon: '๐Ÿฆ‹',
            reward: 100,
            required: 10
        });

        this.achievements.set('game_master', {
            id: 'game_master',
            title: 'Game Master',
            description: 'Play 5 mini-games',
            icon: '๐ŸŽฎ',
            reward: 75,
            required: 5
        });

        this.achievements.set('fashion_icon', {
            id: 'fashion_icon',
            title: 'Fashion Icon',
            description: 'Unlock 5 avatar customization items',
            icon: '๐Ÿ‘—',
            reward: 80,
            required: 5
        });

        this.achievements.set('match_maker', {
            id: 'match_maker',
            title: 'Match Maker',
            description: 'Get 5 high-compatibility matches',
            icon: '๐Ÿ’•',
            reward: 150,
            required: 5
        });
    }

    getAvailableAchievements(userId) {
        const userProgress = this.userAchievements.get(userId) || {};

        return Array.from(this.achievements.values()).map(achievement => ({
            ...achievement,
            progress: userProgress[achievement.id] || 0,
            completed: (userProgress[achievement.id] || 0) >= achievement.required
        }));
    }

    updateAchievementProgress(userId, achievementId, amount = 1) {
        if (!this.userAchievements.has(userId)) {
            this.userAchievements.set(userId, {});
        }

        const userProgress = this.userAchievements.get(userId);
        const currentProgress = userProgress[achievementId] || 0;
        userProgress[achievementId] = currentProgress + amount;

        // Check if achievement is completed
        const achievement = this.achievements.get(achievementId);
        if (achievement && userProgress[achievementId] >= achievement.required) {
            this.giveAchievementReward(userId, achievement);
        }
    }

    giveAchievementReward(userId, achievement) {
        // Give coins reward
        if (window.virtualEconomy) {
            window.virtualEconomy.addCoins(userId, achievement.reward, 'achievement', achievement.title);
        }

        // Show achievement notification
        this.showAchievementNotification(achievement);
    }

    showAchievementNotification(achievement) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border: 3px solid gold;
            border-radius: 15px;
            padding: 25px;
            color: white;
            text-align: center;
            z-index: 3000;
            min-width: 300px;
        `;

        notification.innerHTML = `
            <div style="font-size: 48px; margin-bottom: 15px;">๐Ÿ†</div>
            <h3 style="margin: 0 0 10px 0; color: gold;">Achievement Unlocked!</h3>
            <div style="font-size: 18px; font-weight: bold; margin-bottom: 5px;">${achievement.title}</div>
            <div style="font-size: 14px; color: #aaa; margin-bottom: 15px;">${achievement.description}</div>
            <div style="color: gold; font-size: 16px; font-weight: bold;">+${achievement.reward} coins</div>
            <button onclick="this.parentElement.remove()" 
                    style="margin-top: 20px; padding: 8px 20px; background: gold; color: black; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">
                Awesome!
            </button>
        `;

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

        // Auto-remove after 5 seconds
        setTimeout(() => {
            if (notification.parentElement) {
                notification.remove();
            }
        }, 5000);
    }
}

// Premium features system
class PremiumFeatures {
    constructor() {
        this.premiumUsers = new Set();
        this.premiumBenefits = {
            unlimitedSwipes: true,
            advancedFilters: true,
            readReceipts: true,
            incognitoMode: true,
            boostProfile: true,
            monthlyCoins: 1000
        };
    }

    isPremium(userId) {
        return this.premiumUsers.has(userId);
    }

    activatePremium(userId) {
        this.premiumUsers.add(userId);

        // Apply premium benefits
        this.applyPremiumBenefits(userId);

        // Give monthly coin reward
        if (window.virtualEconomy) {
            window.virtualEconomy.addCoins(userId, this.premiumBenefits.monthlyCoins, 'premium', 'Monthly premium reward');
        }
    }

    applyPremiumBenefits(userId) {
        // Apply various premium features
        this.enableAdvancedFilters(userId);
        this.enableIncognitoMode(userId);
        this.boostUserProfile(userId);
    }

    enableAdvancedFilters(userId) {
        // Enable advanced matching filters
        console.log(`Advanced filters enabled for user ${userId}`);
    }

    enableIncognitoMode(userId) {
        // Enable incognito browsing
        console.log(`Incognito mode enabled for user ${userId}`);
    }

    boostUserProfile(userId) {
        // Boost user profile in matchmaking
        console.log(`Profile boosted for user ${userId}`);
    }

    getPremiumBenefits() {
        return this.premiumBenefits;
    }
}

2. Environment Customization

Let's create a system for users to customize their environments and create personal spaces:

// Environment customization and personal spaces
class EnvironmentCustomizer {
    constructor(sceneManager) {
        this.sceneManager = sceneManager;
        this.userEnvironments = new Map();
        this.availableThemes = new Map();
        this.furnitureCatalog = new Map();
        this.activeEnvironment = null;

        this.loadThemesAndAssets();
        this.setupEnvironmentUI();
    }

    loadThemesAndAssets() {
        // Load available environment themes
        this.availableThemes.set('romantic_garden', {
            id: 'romantic_garden',
            name: 'Romantic Garden',
            description: 'A beautiful garden with flowers and fountains',
            price: 200,
            unlocked: true,
            skybox: 'sky_garden',
            lighting: 'warm',
            ambientSound: 'garden_ambience',
            objects: this.generateGardenObjects()
        });

        this.availableThemes.set('modern_lounge', {
            id: 'modern_lounge',
            name: 'Modern Lounge',
            description: 'Contemporary space with comfortable seating',
            price: 300,
            unlocked: false,
            skybox: 'sky_night',
            lighting: 'modern',
            ambientSound: 'city_ambience',
            objects: this.generateLoungeObjects()
        });

        this.availableThemes.set('beach_sunset', {
            id: 'beach_sunset',
            name: 'Beach Sunset',
            description: 'Relaxing beach with sunset views',
            price: 250,
            unlocked: false,
            skybox: 'sky_sunset',
            lighting: 'golden',
            ambientSound: 'beach_waves',
            objects: this.generateBeachObjects()
        });

        this.availableThemes.set('cozy_cafe', {
            id: 'cozy_cafe',
            name: 'Cozy Cafe',
            description: 'Warm cafe atmosphere for intimate conversations',
            price: 180,
            unlocked: true,
            skybox: 'sky_evening',
            lighting: 'warm',
            ambientSound: 'cafe_ambience',
            objects: this.generateCafeObjects()
        });

        // Load furniture catalog
        this.loadFurnitureCatalog();
    }

    loadFurnitureCatalog() {
        const furnitureItems = [
            { id: 'comfy_chair', name: 'Comfy Chair', type: 'seating', price: 50, category: 'furniture' },
            { id: 'coffee_table', name: 'Coffee Table', type: 'table', price: 75, category: 'furniture' },
            { id: 'floor_lamp', name: 'Floor Lamp', type: 'lighting', price: 40, category: 'decor' },
            { id: 'plant_palm', name: 'Palm Plant', type: 'plant', price: 30, category: 'decor' },
            { id: 'rug_circular', name: 'Circular Rug', type: 'rug', price: 60, category: 'furniture' },
            { id: 'bookshelf', name: 'Bookshelf', type: 'storage', price: 80, category: 'furniture' },
            { id: 'fireplace', name: 'Fireplace', type: 'decor', price: 120, category: 'decor' },
            { id: 'fountain', name: 'Fountain', type: 'water', price: 150, category: 'decor' }
        ];

        furnitureItems.forEach(item => {
            this.furnitureCatalog.set(item.id, item);
        });
    }

    setupEnvironmentUI() {
        this.createEnvironmentPanel();
        this.createQuickThemeSelector();
    }

    createEnvironmentPanel() {
        this.environmentPanel = document.createElement('div');
        this.environmentPanel.id = 'environment-customization';
        this.environmentPanel.style.cssText = `
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.95);
            border-radius: 15px;
            padding: 25px;
            color: white;
            width: 900px;
            max-width: 95vw;
            max-height: 90vh;
            overflow-y: auto;
            display: none;
            z-index: 2000;
            border: 2px solid #8A2BE2;
        `;

        document.getElementById('container').appendChild(this.environmentPanel);

        // Create environment customization button
        this.createEnvironmentButton();
    }

    createEnvironmentButton() {
        const envButton = document.createElement('button');
        envButton.innerHTML = '๐Ÿ ';
        envButton.title = 'Customize Environment';
        envButton.style.cssText = `
            position: absolute;
            top: 20px;
            right: 210px;
            width: 40px;
            height: 40px;
            border: none;
            border-radius: 50%;
            background: rgba(138, 43, 226, 0.8);
            color: white;
            font-size: 18px;
            cursor: pointer;
            z-index: 1001;
        `;

        envButton.addEventListener('click', () => {
            this.showEnvironmentPanel();
        });

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

    createQuickThemeSelector() {
        this.quickThemeSelector = document.createElement('div');
        this.quickThemeSelector.id = 'quick-theme-selector';
        this.quickThemeSelector.style.cssText = `
            position: absolute;
            bottom: 200px;
            left: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 10px;
            padding: 10px;
            display: none;
            flex-direction: column;
            gap: 5px;
            z-index: 1001;
        `;

        document.getElementById('container').appendChild(this.quickThemeSelector);
    }

    showEnvironmentPanel() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const userEnvironment = this.userEnvironments.get(currentUser.id) || this.createDefaultEnvironment();

        this.environmentPanel.innerHTML = `
            <div style="display: flex; justify-content: between; align-items: center; margin-bottom: 25px;">
                <h2 style="margin: 0; color: #8A2BE2;">๐Ÿ  Environment Customization</h2>
                <button onclick="environmentCustomizer.hideEnvironmentPanel()" 
                        style="padding: 8px 16px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer;">
                    Close
                </button>
            </div>

            <div style="display: grid; grid-template-columns: 250px 1fr; gap: 25px;">
                <!-- Preview Section -->
                <div style="background: #222; border-radius: 10px; padding: 20px; text-align: center;">
                    <h3>Environment Preview</h3>
                    <div id="environment-preview" style="width: 200px; height: 200px; background: #333; margin: 0 auto 15px auto; border-radius: 10px; overflow: hidden;">
                        <!-- Environment preview will be rendered here -->
                    </div>
                    <div style="font-size: 14px; font-weight: bold; margin-bottom: 5px;">${userEnvironment.theme.name}</div>
                    <div style="font-size: 12px; color: #aaa; margin-bottom: 15px;">${userEnvironment.theme.description}</div>
                    <button onclick="environmentCustomizer.applyEnvironment()" 
                            style="width: 100%; padding: 10px; background: #8A2BE2; color: white; border: none; border-radius: 8px; cursor: pointer; margin-bottom: 10px;">
                        Apply Environment
                    </button>
                    <button onclick="environmentCustomizer.saveEnvironment()" 
                            style="width: 100%; padding: 8px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
                        Save as Default
                    </button>
                </div>

                <!-- Customization Options -->
                <div>
                    <div style="margin-bottom: 25px;">
                        <h3>๐ŸŽจ Themes</h3>
                        <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 15px;">
                            ${Array.from(this.availableThemes.values()).map(theme => `
                                <div style="background: ${userEnvironment.theme.id === theme.id ? 'rgba(138, 43, 226, 0.2)' : 'rgba(255, 255, 255, 0.05)'}; 
                                     border: 2px solid ${userEnvironment.theme.id === theme.id ? '#8A2BE2' : '#333'}; 
                                     border-radius: 10px; padding: 15px; text-align: center; cursor: pointer; transition: transform 0.2s;"
                                     onclick="environmentCustomizer.selectTheme('${theme.id}')">
                                    <div style="font-size: 32px; margin-bottom: 10px;">${this.getThemeIcon(theme.id)}</div>
                                    <div style="font-weight: bold; margin-bottom: 5px;">${theme.name}</div>
                                    <div style="font-size: 11px; color: #aaa; margin-bottom: 10px;">${theme.description}</div>
                                    ${theme.unlocked ? 
                                        '<div style="color: #4CAF50; font-size: 10px;">โœ“ Unlocked</div>' :
                                        `<div style="color: gold; font-size: 10px;">${theme.price} coins</div>`
                                    }
                                </div>
                            `).join('')}
                        </div>
                    </div>

                    <div style="margin-bottom: 25px;">
                        <h3>๐Ÿ›‹๏ธ Furniture & Decor</h3>
                        <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px;">
                            ${Array.from(this.furnitureCatalog.values()).map(item => `
                                <div style="background: rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 12px; text-align: center; cursor: pointer;"
                                     onclick="environmentCustomizer.addFurniture('${item.id}')">
                                    <div style="font-size: 24px; margin-bottom: 8px;">${this.getFurnitureIcon(item.type)}</div>
                                    <div style="font-size: 11px; font-weight: bold; margin-bottom: 5px;">${item.name}</div>
                                    <div style="font-size: 10px; color: gold;">${item.price} coins</div>
                                </div>
                            `).join('')}
                        </div>
                    </div>

                    <div>
                        <h3>โš™๏ธ Environment Settings</h3>
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
                            <div>
                                <label style="display: block; margin-bottom: 8px; font-weight: bold;">Lighting</label>
                                <select onchange="environmentCustomizer.updateLighting(this.value)" 
                                        style="width: 100%; padding: 8px; border-radius: 5px; background: #333; color: white; border: 1px solid #555;">
                                    <option value="warm">Warm</option>
                                    <option value="cool">Cool</option>
                                    <option value="natural">Natural</option>
                                    <option value="romantic">Romantic</option>
                                </select>
                            </div>
                            <div>
                                <label style="display: block; margin-bottom: 8px; font-weight: bold;">Ambient Sound</label>
                                <select onchange="environmentCustomizer.updateAmbientSound(this.value)" 
                                        style="width: 100%; padding: 8px; border-radius: 5px; background: #333; color: white; border: 1px solid #555;">
                                    <option value="none">None</option>
                                    <option value="garden">Garden</option>
                                    <option value="cafe">Cafe</option>
                                    <option value="beach">Beach</option>
                                    <option value="city">City</option>
                                </select>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Placed Objects -->
            <div style="margin-top: 25px; border-top: 1px solid #333; padding-top: 20px;">
                <h3>Placed Objects</h3>
                <div id="placed-objects" style="display: flex; flex-wrap: wrap; gap: 10px; min-height: 60px;">
                    ${userEnvironment.objects.map(obj => `
                        <div style="background: rgba(138, 43, 226, 0.2); padding: 8px 12px; border-radius: 20px; display: flex; align-items: center; gap: 8px;">
                            <span>${this.getFurnitureIcon(obj.type)}</span>
                            <span style="font-size: 12px;">${obj.name}</span>
                            <button onclick="environmentCustomizer.removeObject('${obj.id}')" 
                                    style="background: none; border: none; color: #ff4444; cursor: pointer; font-size: 12px;">
                                โœ•
                            </button>
                        </div>
                    `).join('')}
                </div>
            </div>
        `;

        this.renderEnvironmentPreview(userEnvironment);
        this.environmentPanel.style.display = 'block';
    }

    renderEnvironmentPreview(environment) {
        const preview = document.getElementById('environment-preview');
        if (!preview) return;

        // Create a simple 2D representation of the environment
        preview.innerHTML = `
            <div style="width: 100%; height: 100%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); position: relative; overflow: hidden;">
                <!-- Sky -->
                <div style="position: absolute; top: 0; left: 0; right: 0; height: 60%; background: ${this.getThemeSkyColor(environment.theme.id)};"></div>

                <!-- Ground -->
                <div style="position: absolute; bottom: 0; left: 0; right: 0; height: 40%; background: ${this.getThemeGroundColor(environment.theme.id)};"></div>

                <!-- Objects -->
                ${environment.objects.slice(0, 4).map((obj, index) => `
                    <div style="position: absolute; bottom: 40%; left: ${20 + index * 25}%; font-size: 20px;">
                        ${this.getFurnitureIcon(obj.type)}
                    </div>
                `).join('')}

                <!-- Lighting effect -->
                <div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(circle at 30% 70%, rgba(255,255,255,0.1) 0%, transparent 50%);"></div>
            </div>
        `;
    }

    selectTheme(themeId) {
        const theme = this.availableThemes.get(themeId);
        if (!theme) return;

        // Check if theme is unlocked
        if (!theme.unlocked) {
            if (window.virtualEconomy) {
                const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
                const balance = window.virtualEconomy.getUserBalance(currentUser.id);

                if (balance.coins >= theme.price) {
                    // Purchase theme
                    if (window.virtualEconomy.spendCoins(currentUser.id, theme.price, 'theme', theme.name)) {
                        theme.unlocked = true;
                        this.applyTheme(themeId);
                        this.showNotification(`๐ŸŽ‰ ${theme.name} theme purchased and applied!`);
                    }
                } else {
                    this.showNotification(`You need ${theme.price} coins to unlock this theme.`);
                    return;
                }
            }
        } else {
            this.applyTheme(themeId);
        }
    }

    applyTheme(themeId) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        let userEnvironment = this.userEnvironments.get(currentUser.id) || this.createDefaultEnvironment();

        const theme = this.availableThemes.get(themeId);
        userEnvironment.theme = theme;

        // Update scene with new theme
        this.updateSceneEnvironment(userEnvironment);

        // Update preview
        this.renderEnvironmentPreview(userEnvironment);

        this.userEnvironments.set(currentUser.id, userEnvironment);
    }

    updateSceneEnvironment(environment) {
        // Update WebGL scene with new environment settings
        const scene = window.datingChat?.sceneManager;
        if (!scene) return;

        // Update lighting
        this.updateSceneLighting(environment.theme.lighting);

        // Update background/skybox
        this.updateSceneBackground(environment.theme.skybox);

        // Update ambient sound
        if (window.audioSystem && environment.theme.ambientSound !== 'none') {
            window.audioSystem.updateAmbientSound(environment.theme.ambientSound);
        }

        // Update environment objects
        this.updateEnvironmentObjects(environment.objects);
    }

    updateSceneLighting(lightingType) {
        const lightingSystem = window.datingChat?.lightingSystem;
        if (!lightingSystem) return;

        switch(lightingType) {
            case 'warm':
                lightingSystem.ambientColor = [0.3, 0.2, 0.1];
                break;
            case 'cool':
                lightingSystem.ambientColor = [0.1, 0.2, 0.3];
                break;
            case 'natural':
                lightingSystem.ambientColor = [0.2, 0.2, 0.2];
                break;
            case 'romantic':
                lightingSystem.ambientColor = [0.4, 0.1, 0.2];
                break;
        }
    }

    updateSceneBackground(skyboxType) {
        // Update WebGL background/skybox
        // This would involve changing clear colors or rendering a skybox
        const gl = window.datingChat?.gl;
        if (!gl) return;

        switch(skyboxType) {
            case 'sky_garden':
                gl.clearColor(0.4, 0.6, 0.8, 1.0);
                break;
            case 'sky_night':
                gl.clearColor(0.1, 0.1, 0.2, 1.0);
                break;
            case 'sky_sunset':
                gl.clearColor(0.8, 0.4, 0.2, 1.0);
                break;
            case 'sky_evening':
                gl.clearColor(0.3, 0.3, 0.5, 1.0);
                break;
        }
    }

    updateEnvironmentObjects(objects) {
        // Add or update objects in the scene
        const scene = window.datingChat?.sceneManager;
        if (!scene) return;

        // Remove existing environment objects
        scene.objects = scene.objects.filter(obj => !obj.isEnvironmentObject);

        // Add new objects
        objects.forEach(obj => {
            const sceneObject = this.createSceneObjectFromFurniture(obj);
            if (sceneObject) {
                scene.addObject(sceneObject);
            }
        });
    }

    createSceneObjectFromFurniture(furniture) {
        // Create a WebGL scene object from furniture data
        // This is a simplified version
        const furnitureData = this.furnitureCatalog.get(furniture.id);
        if (!furnitureData) return null;

        // Generate geometry based on furniture type
        const vertices = this.generateFurnitureGeometry(furnitureData.type);
        const colors = this.generateFurnitureColors(furnitureData.type);
        const indices = this.generateFurnitureIndices(furnitureData.type);

        const sceneObject = new SceneObject(vertices, colors, indices, furniture.position || [0, 0, 0]);
        sceneObject.isEnvironmentObject = true;
        sceneObject.furnitureId = furniture.id;

        return sceneObject;
    }

    addFurniture(furnitureId) {
        const furniture = this.furnitureCatalog.get(furnitureId);
        if (!furniture) return;

        // Check if user can afford it
        if (window.virtualEconomy) {
            const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
            const balance = window.virtualEconomy.getUserBalance(currentUser.id);

            if (balance.coins >= furniture.price) {
                // Purchase furniture
                if (window.virtualEconomy.spendCoins(currentUser.id, furniture.price, 'furniture', furniture.name)) {
                    this.placeFurnitureInEnvironment(furniture);
                    this.showNotification(`๐Ÿ›‹๏ธ ${furniture.name} added to your environment!`);
                }
            } else {
                this.showNotification(`You need ${furniture.price} coins to purchase this item.`);
            }
        }
    }

    placeFurnitureInEnvironment(furniture) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        let userEnvironment = this.userEnvironments.get(currentUser.id) || this.createDefaultEnvironment();

        // Add furniture to environment
        userEnvironment.objects.push({
            id: furniture.id,
            name: furniture.name,
            type: furniture.type,
            position: this.calculatePlacementPosition(userEnvironment.objects.length)
        });

        this.userEnvironments.set(currentUser.id, userEnvironment);

        // Update UI
        this.updatePlacedObjectsUI(userEnvironment.objects);

        // Update scene
        this.updateEnvironmentObjects(userEnvironment.objects);
    }

    calculatePlacementPosition(index) {
        // Calculate position for new furniture item
        const angle = (index * 72) * Math.PI / 180; // Spread around in a circle
        const distance = 2 + (index * 0.5);
        return [
            Math.cos(angle) * distance,
            0,
            Math.sin(angle) * distance
        ];
    }

    removeObject(objectId) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const userEnvironment = this.userEnvironments.get(currentUser.id);
        if (!userEnvironment) return;

        // Remove object from environment
        userEnvironment.objects = userEnvironment.objects.filter(obj => obj.id !== objectId);

        // Update UI and scene
        this.updatePlacedObjectsUI(userEnvironment.objects);
        this.updateEnvironmentObjects(userEnvironment.objects);
    }

    updatePlacedObjectsUI(objects) {
        const placedObjects = document.getElementById('placed-objects');
        if (!placedObjects) return;

        placedObjects.innerHTML = objects.map(obj => `
            <div style="background: rgba(138, 43, 226, 0.2); padding: 8px 12px; border-radius: 20px; display: flex; align-items: center; gap: 8px;">
                <span>${this.getFurnitureIcon(obj.type)}</span>
                <span style="font-size: 12px;">${obj.name}</span>
                <button onclick="environmentCustomizer.removeObject('${obj.id}')" 
                        style="background: none; border: none; color: #ff4444; cursor: pointer; font-size: 12px;">
                    โœ•
                </button>
            </div>
        `).join('');
    }

    applyEnvironment() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const userEnvironment = this.userEnvironments.get(currentUser.id);
        if (userEnvironment) {
            this.activeEnvironment = userEnvironment;
            this.updateSceneEnvironment(userEnvironment);
            this.showNotification('Environment applied successfully!');
        }
    }

    saveEnvironment() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const userEnvironment = this.userEnvironments.get(currentUser.id);
        if (userEnvironment) {
            // Save to localStorage or server
            localStorage.setItem(`environment_${currentUser.id}`, JSON.stringify(userEnvironment));
            this.showNotification('Environment saved as default!');
        }
    }

    loadEnvironment(userId) {
        const saved = localStorage.getItem(`environment_${userId}`);
        if (saved) {
            return JSON.parse(saved);
        }
        return this.createDefaultEnvironment();
    }

    createDefaultEnvironment() {
        return {
            theme: this.availableThemes.get('romantic_garden'),
            objects: [],
            settings: {
                lighting: 'warm',
                ambientSound: 'garden'
            }
        };
    }

    // Utility methods
    getThemeIcon(themeId) {
        const icons = {
            'romantic_garden': '๐ŸŒน',
            'modern_lounge': '๐Ÿข',
            'beach_sunset': '๐Ÿ–๏ธ',
            'cozy_cafe': 'โ˜•'
        };
        return icons[themeId] || '๐Ÿ ';
    }

    getFurnitureIcon(type) {
        const icons = {
            'seating': '๐Ÿช‘',
            'table': '๐Ÿชต',
            'lighting': '๐Ÿ’ก',
            'plant': '๐ŸŒฟ',
            'rug': '๐Ÿงถ',
            'storage': '๐Ÿ“š',
            'decor': '๐Ÿ–ผ๏ธ',
            'water': 'โ›ฒ'
        };
        return icons[type] || '๐Ÿ“ฆ';
    }

    getThemeSkyColor(themeId) {
        const colors = {
            'romantic_garden': '#87CEEB',
            'modern_lounge': '#1a1a2e',
            'beach_sunset': '#ff7e5f',
            'cozy_cafe': '#4a4a6a'
        };
        return colors[themeId] || '#87CEEB';
    }

    getThemeGroundColor(themeId) {
        const colors = {
            'romantic_garden': '#2d5a27',
            'modern_lounge': '#333333',
            'beach_sunset': '#feb47b',
            'cozy_cafe': '#3a3a3a'
        };
        return colors[themeId] || '#2d5a27';
    }

    generateFurnitureGeometry(type) {
        // Generate vertices for different furniture types
        // Simplified - returns empty array
        return new Float32Array([]);
    }

    generateFurnitureColors(type) {
        // Generate colors for furniture
        return new Float32Array([]);
    }

    generateFurnitureIndices(type) {
        // Generate indices for furniture
        return new Uint16Array([]);
    }

    hideEnvironmentPanel() {
        this.environmentPanel.style.display = 'none';
    }

    showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 12px 24px;
            border-radius: 25px;
            z-index: 1000;
            font-size: 14px;
        `;
        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }
}

3. AI Conversation Assistant

Let's implement an AI assistant that helps with conversations and date planning:

// AI conversation assistant and date planner
class AIConversationAssistant {
    constructor() {
        this.conversationHistory = new Map();
        this.suggestions = new Map();
        this.dateIdeas = new Map();
        this.moodAnalyzer = new MoodAnalyzer();

        this.setupAIAssistant();
    }

    setupAIAssistant() {
        this.createAssistantUI();
        this.setupMessageMonitoring();
    }

    createAssistantUI() {
        this.assistantPanel = document.createElement('div');
        this.assistantPanel.id = 'ai-assistant';
        this.assistantPanel.style.cssText = `
            position: absolute;
            bottom: 80px;
            right: 20px;
            background: rgba(0, 0, 0, 0.9);
            border-radius: 15px;
            padding: 15px;
            color: white;
            width: 300px;
            max-height: 400px;
            overflow-y: auto;
            display: none;
            z-index: 1001;
            border: 2px solid #00CED1;
        `;

        document.getElementById('container').appendChild(this.assistantPanel);

        // Create assistant toggle button
        this.createAssistantButton();
    }

    createAssistantButton() {
        this.assistantButton = document.createElement('button');
        this.assistantButton.innerHTML = '๐Ÿค–';
        this.assistantButton.title = 'AI Assistant';
        this.assistantButton.style.cssText = `
            position: absolute;
            bottom: 80px;
            right: 20px;
            width: 50px;
            height: 50px;
            border: none;
            border-radius: 50%;
            background: linear-gradient(45deg, #00CED1, #008B8B);
            color: white;
            font-size: 20px;
            cursor: pointer;
            z-index: 1001;
            transition: transform 0.2s;
        `;

        this.assistantButton.addEventListener('click', () => {
            this.toggleAssistant();
        });

        this.assistantButton.addEventListener('mouseenter', () => {
            this.assistantButton.style.transform = 'scale(1.1)';
        });

        this.assistantButton.addEventListener('mouseleave', () => {
            this.assistantButton.style.transform = 'scale(1)';
        });

        document.getElementById('container').appendChild(this.assistantButton);
    }

    toggleAssistant() {
        if (this.assistantPanel.style.display === 'block') {
            this.hideAssistant();
        } else {
            this.showAssistant();
        }
    }

    showAssistant() {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        const currentConversation = this.conversationHistory.get(currentUser.id) || [];

        this.assistantPanel.innerHTML = `
            <div style="text-align: center; margin-bottom: 15px;">
                <h3 style="margin: 0; color: #00CED1;">๐Ÿค– AI Assistant</h3>
                <div style="font-size: 11px; color: #aaa;">Your conversation helper</div>
            </div>

            <div style="margin-bottom: 15px;">
                <div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #00CED1;">๐Ÿ’ก Conversation Tips</div>
                <div id="conversation-tips" style="font-size: 11px; line-height: 1.4;">
                    ${this.generateConversationTips(currentConversation)}
                </div>
            </div>

            <div style="margin-bottom: 15px;">
                <div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #00CED1;">๐Ÿ’ฌ Response Suggestions</div>
                <div id="response-suggestions" style="display: flex; flex-direction: column; gap: 5px;">
                    ${this.generateResponseSuggestions(currentConversation).slice(0, 3).map(suggestion => `
                        <div style="background: rgba(0, 206, 209, 0.2); padding: 8px; border-radius: 8px; font-size: 11px; cursor: pointer;"
                             onclick="aiAssistant.useSuggestion('${suggestion.replace(/'/g, "\\'")}')">
                            "${suggestion}"
                        </div>
                    `).join('')}
                </div>
            </div>

            <div>
                <div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #00CED1;">๐Ÿ“… Date Ideas</div>
                <div id="date-ideas" style="display: flex; flex-direction: column; gap: 5px;">
                    ${this.generateDateIdeas(currentConversation).slice(0, 2).map(idea => `
                        <div style="background: rgba(255, 105, 180, 0.2); padding: 8px; border-radius: 8px; font-size: 11px; cursor: pointer;"
                             onclick="aiAssistant.suggestDate('${idea.replace(/'/g, "\\'")}')">
                            ${idea}
                        </div>
                    `).join('')}
                </div>
            </div>

            <button onclick="aiAssistant.hideAssistant()" 
                    style="width: 100%; margin-top: 15px; padding: 8px; background: #666; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 11px;">
                Close Assistant
            </button>
        `;

        this.assistantPanel.style.display = 'block';
    }

    hideAssistant() {
        this.assistantPanel.style.display = 'none';
    }

    setupMessageMonitoring() {
        // Monitor chat messages for analysis
        if (window.datingChat?.realTimeChat) {
            const originalAddMessage = window.datingChat.realTimeChat.addMessage;

            window.datingChat.realTimeChat.addMessage = function(sender, content, timestamp, isOwn) {
                const result = originalAddMessage.call(this, sender, content, timestamp, isOwn);

                // Analyze message with AI assistant
                if (isOwn) {
                    window.aiAssistant.analyzeOutgoingMessage(content);
                } else {
                    window.aiAssistant.analyzeIncomingMessage(sender.id, content);
                }

                return result;
            };
        }
    }

    analyzeOutgoingMessage(content) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        this.addToConversationHistory(currentUser.id, content, 'outgoing');

        // Analyze message quality
        const analysis = this.analyzeMessageQuality(content);

        if (analysis.score < 0.3) {
            this.showQualityWarning(analysis.feedback);
        }
    }

    analyzeIncomingMessage(userId, content) {
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        this.addToConversationHistory(currentUser.id, content, 'incoming');

        // Analyze conversation mood and topics
        const mood = this.moodAnalyzer.analyzeMood(content);
        const topics = this.extractTopics(content);

        // Update conversation analysis
        this.updateConversationAnalysis(currentUser.id, mood, topics);

        // Generate response suggestions
        this.generateRealTimeSuggestions(currentUser.id, content, mood);
    }

    addToConversationHistory(userId, content, direction) {
        if (!this.conversationHistory.has(userId)) {
            this.conversationHistory.set(userId, []);
        }

        const history = this.conversationHistory.get(userId);
        history.push({
            content,
            direction,
            timestamp: Date.now(),
            mood: this.moodAnalyzer.analyzeMood(content),
            topics: this.extractTopics(content)
        });

        // Keep only last 50 messages
        if (history.length > 50) {
            history.shift();
        }
    }

    analyzeMessageQuality(message) {
        let score = 0.5; // Base score

        // Check message length
        if (message.length < 3) {
            score -= 0.3;
        } else if (message.length > 10 && message.length < 100) {
            score += 0.2;
        }

        // Check for questions (good for engagement)
        if (message.includes('?')) {
            score += 0.2;
        }

        // Check for positive language
        const positiveWords = ['great', 'awesome', 'amazing', 'wonderful', 'nice', 'good', 'happy', 'excited'];
        if (positiveWords.some(word => message.toLowerCase().includes(word))) {
            score += 0.1;
        }

        // Check for negative language
        const negativeWords = ['hate', 'boring', 'terrible', 'awful', 'bad', 'sad', 'angry'];
        if (negativeWords.some(word => message.toLowerCase().includes(word))) {
            score -= 0.2;
        }

        // Generate feedback
        let feedback = '';
        if (score < 0.3) {
            feedback = 'Try asking a question or sharing something positive!';
        } else if (score > 0.7) {
            feedback = 'Great message! Keep the conversation flowing.';
        }

        return { score, feedback };
    }

    showQualityWarning(feedback) {
        const warning = document.createElement('div');
        warning.style.cssText = `
            position: absolute;
            bottom: 150px;
            right: 20px;
            background: rgba(255, 100, 100, 0.9);
            color: white;
            padding: 10px 15px;
            border-radius: 10px;
            font-size: 12px;
            z-index: 1001;
            max-width: 250px;
        `;

        warning.innerHTML = `
            <div style="font-weight: bold; margin-bottom: 5px;">๐Ÿ’ก Suggestion</div>
            <div>${feedback}</div>
        `;

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

        setTimeout(() => {
            warning.style.opacity = '0';
            setTimeout(() => warning.remove(), 300);
        }, 5000);
    }

    extractTopics(message) {
        const topics = [];
        const topicKeywords = {
            'hobbies': ['hobby', 'interest', 'passion', 'activity', 'do for fun'],
            'travel': ['travel', 'vacation', 'trip', 'beach', 'mountains', 'city'],
            'food': ['food', 'restaurant', 'cook', 'recipe', 'dinner', 'cuisine'],
            'music': ['music', 'song', 'band', 'concert', 'listen', 'artist'],
            'movies': ['movie', 'film', 'watch', 'netflix', 'cinema', 'actor'],
            'sports': ['sports', 'game', 'team', 'play', 'exercise', 'fitness']
        };

        const lowerMessage = message.toLowerCase();
        for (const [topic, keywords] of Object.entries(topicKeywords)) {
            if (keywords.some(keyword => lowerMessage.includes(keyword))) {
                topics.push(topic);
            }
        }

        return topics;
    }

    updateConversationAnalysis(userId, mood, topics) {
        // Update conversation analysis for better suggestions
        if (!this.suggestions.has(userId)) {
            this.suggestions.set(userId, {
                commonTopics: new Set(),
                conversationStyle: 'neutral',
                engagementLevel: 0.5,
                lastActive: Date.now()
            });
        }

        const analysis = this.suggestions.get(userId);
        topics.forEach(topic => analysis.commonTopics.add(topic));
        analysis.lastActive = Date.now();

        // Update engagement level based on response time and message quality
        analysis.engagementLevel = this.calculateEngagementLevel(userId);
    }

    calculateEngagementLevel(userId) {
        const history = this.conversationHistory.get(userId) || [];
        if (history.length < 2) return 0.5;

        let engagement = 0.5;

        // Calculate based on response time
        const recentMessages = history.slice(-5);
        let responseTimes = [];

        for (let i = 1; i < recentMessages.length; i++) {
            if (recentMessages[i].direction !== recentMessages[i-1].direction) {
                const timeDiff = recentMessages[i].timestamp - recentMessages[i-1].timestamp;
                responseTimes.push(timeDiff);
            }
        }

        if (responseTimes.length > 0) {
            const avgResponseTime = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length;
            // Faster responses indicate higher engagement
            if (avgResponseTime < 30000) engagement += 0.3; // < 30 seconds
            else if (avgResponseTime > 120000) engagement -= 0.2; // > 2 minutes
        }

        return Math.max(0, Math.min(1, engagement));
    }

    generateConversationTips(conversation) {
        if (conversation.length === 0) {
            return "Start by asking about their interests or sharing something about yourself!";
        }

        const lastMessage = conversation[conversation.length - 1];
        const tips = [];

        // Check if conversation is one-sided
        const outgoingCount = conversation.filter(msg => msg.direction === 'outgoing').length;
        const incomingCount = conversation.filter(msg => msg.direction === 'incoming').length;

        if (outgoingCount > incomingCount + 2) {
            tips.push("Try asking more questions to keep them engaged");
        }

        // Check message variety
        const recentTopics = new Set();
        conversation.slice(-5).forEach(msg => {
            msg.topics.forEach(topic => recentTopics.add(topic));
        });

        if (recentTopics.size < 2) {
            tips.push("Consider introducing a new topic to keep things interesting");
        }

        // Check for positive language
        const positiveMessages = conversation.filter(msg => 
            msg.mood.sentiment > 0.3
        ).length;

        if (positiveMessages / conversation.length < 0.3) {
            tips.push("Adding some positive language can improve the conversation mood");
        }

        return tips.length > 0 ? tips.join('. ') + '.' : "Conversation is going well! Keep it up.";
    }

    generateResponseSuggestions(conversation) {
        if (conversation.length === 0) {
            return [
                "Hi! What brings you here today?",
                "Hello! I'd love to learn more about you",
                "Hey there! What are your interests?"
            ];
        }

        const lastMessage = conversation[conversation.length - 1];
        const suggestions = [];

        // Question-based responses
        if (lastMessage.content.includes('?')) {
            suggestions.push(
                "That's a great question! What about you?",
                "I'd love to hear your thoughts on that too",
                "Interesting question! Here's what I think..."
            );
        }

        // Topic-based responses
        if (lastMessage.topics.includes('hobbies')) {
            suggestions.push(
                "That sounds like a fun hobby! I enjoy similar activities",
                "I've always wanted to try that. How did you get started?",
                "That's awesome! What do you love most about it?"
            );
        }

        if (lastMessage.topics.includes('travel')) {
            suggestions.push(
                "I love traveling too! What's your favorite destination?",
                "That sounds amazing! I've always wanted to visit there",
                "Traveling is so enriching. Any upcoming trips planned?"
            );
        }

        // General engaging responses
        suggestions.push(
            "That's really interesting! Tell me more",
            "I completely agree with you on that",
            "That's a unique perspective! I never thought of it that way",
            "Wow, that's impressive! How did you manage that?"
        );

        return this.shuffleArray(suggestions).slice(0, 5);
    }

    generateDateIdeas(conversation) {
        const commonTopics = new Set();
        conversation.forEach(msg => {
            msg.topics.forEach(topic => commonTopics.add(topic));
        });

        const ideas = [];

        if (commonTopics.has('food')) {
            ideas.push(
                "Virtual cooking date - cook the same recipe together",
                "Food tasting tour of local restaurants",
                "Wine and cheese pairing evening"
            );
        }

        if (commonTopics.has('movies')) {
            ideas.push(
                "Netflix watch party with synchronized movie",
                "Virtual cinema date with popcorn and snacks",
                "Movie trivia night with fun prizes"
            );
        }

        if (commonTopics.has('music')) {
            ideas.push(
                "Create a shared playlist together",
                "Virtual concert experience",
                "Music trivia and karaoke night"
            );
        }

        if (commonTopics.has('travel')) {
            ideas.push(
                "Virtual tour of a museum or landmark",
                "Plan a dream vacation together",
                "Travel photo sharing and storytelling"
            );
        }

        // General date ideas
        ideas.push(
            "Online game night with fun multiplayer games",
            "Virtual escape room challenge",
            "Coffee chat in a cozy virtual cafe",
            "Sunset watching with relaxing music"
        );

        return this.shuffleArray(ideas).slice(0, 5);
    }

    useSuggestion(suggestion) {
        // Insert suggestion into chat input
        const chatInput = document.getElementById('message-input');
        if (chatInput) {
            chatInput.value = suggestion;
            chatInput.focus();
        }

        this.hideAssistant();
    }

    suggestDate(idea) {
        const chatInput = document.getElementById('message-input');
        if (chatInput) {
            const message = `I was thinking, would you like to try a ${idea.toLowerCase()} together sometime?`;
            chatInput.value = message;
            chatInput.focus();
        }

        this.hideAssistant();
        this.showNotification("Great date idea! Feel free to customize the message before sending.");
    }

    shuffleArray(array) {
        const shuffled = [...array];
        for (let i = shuffled.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
        }
        return shuffled;
    }

    generateRealTimeSuggestions(userId, message, mood) {
        // Generate suggestions based on the current conversation context
        const history = this.conversationHistory.get(userId) || [];

        // Update assistant panel if it's visible
        if (this.assistantPanel.style.display === 'block') {
            this.showAssistant();
        }

        // Show notification for important conversation events
        if (mood.sentiment < -0.5) {
            this.showNotification("The conversation seems negative. Consider changing the topic or offering support.");
        } else if (mood.sentiment > 0.7) {
            this.showNotification("Great conversation mood! Perfect time to suggest a date idea.");
        }
    }

    showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            bottom: 150px;
            right: 20px;
            background: rgba(0, 206, 209, 0.9);
            color: white;
            padding: 10px 15px;
            border-radius: 10px;
            font-size: 12px;
            z-index: 1001;
            max-width: 250px;
        `;

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

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 5000);
    }
}

// Mood analyzer for conversation analysis
class MoodAnalyzer {
    analyzeMood(message) {
        const words = message.toLowerCase().split(/\s+/);

        // Sentiment analysis
        const positiveWords = ['love', 'great', 'awesome', 'amazing', 'happy', 'good', 'nice', 'wonderful', 'excited', 'fantastic', 'perfect'];
        const negativeWords = ['hate', 'bad', 'terrible', 'awful', 'sad', 'angry', 'horrible', 'boring', 'disappointed', 'upset', 'annoying'];

        let positiveScore = 0;
        let negativeScore = 0;

        words.forEach(word => {
            if (positiveWords.includes(word)) positiveScore++;
            if (negativeWords.includes(word)) negativeScore++;
        });

        const sentiment = (positiveScore - negativeScore) / Math.max(1, words.length);

        // Energy level (based on punctuation and word choice)
        const exclamationCount = (message.match(/!/g) || []).length;
        const questionCount = (message.match(/\?/g) || []).length;
        const energy = Math.min(1, (exclamationCount * 0.3) + (questionCount * 0.2));

        // Engagement level (based on message length and content)
        const engagement = Math.min(1, message.length / 100);

        return {
            sentiment: Math.max(-1, Math.min(1, sentiment)),
            energy: Math.max(0, Math.min(1, energy)),
            engagement: Math.max(0, Math.min(1, engagement)),
            dominantEmotion: this.getDominantEmotion(sentiment, energy)
        };
    }

    getDominantEmotion(sentiment, energy) {
        if (sentiment > 0.3) {
            return energy > 0.3 ? 'excited' : 'happy';
        } else if (sentiment < -0.3) {
            return energy > 0.3 ? 'angry' : 'sad';
        } else {
            return energy > 0.3 ? 'curious' : 'neutral';
        }
    }
}

4. Cross-Platform Features

Let's implement progressive web app features and cross-platform compatibility:

// Cross-platform compatibility and PWA features
class CrossPlatformSupport {
    constructor() {
        this.isPWA = false;
        this.isMobile = this.detectMobile();
        this.isOffline = false;

        this.setupPWA();
        this.setupOfflineSupport();
        this.setupCrossPlatformUI();
    }

    detectMobile() {
        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
               window.innerWidth <= 768;
    }

    setupPWA() {
        // Check if app is running as PWA
        this.isPWA = window.matchMedia('(display-mode: standalone)').matches || 
                    window.navigator.standalone ||
                    document.referrer.includes('android-app://');

        if (this.isPWA) {
            console.log('Running as Progressive Web App');
            this.enablePWAFeatures();
        }

        // Add to home screen prompt
        this.setupAddToHomeScreen();
    }

    enablePWAFeatures() {
        // Enable PWA-specific features
        this.enableBackgroundSync();
        this.enablePushNotifications();
        this.optimizeForStandalone();
    }

    setupAddToHomeScreen() {
        let deferredPrompt;

        window.addEventListener('beforeinstallprompt', (e) => {
            // Prevent the mini-infobar from appearing on mobile
            e.preventDefault();
            // Stash the event so it can be triggered later
            deferredPrompt = e;
            // Show install promotion
            this.showInstallPromotion();
        });

        window.addEventListener('appinstalled', () => {
            // Hide the app-provided install promotion
            this.hideInstallPromotion();
            // Clear the deferredPrompt so it can be garbage collected
            deferredPrompt = null;
            console.log('PWA installed successfully');
        });
    }

    showInstallPromotion() {
        const installPromotion = document.createElement('div');
        installPromotion.id = 'install-promotion';
        installPromotion.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: linear-gradient(45deg, #667eea, #764ba2);
            color: white;
            padding: 15px 25px;
            border-radius: 25px;
            z-index: 10000;
            display: flex;
            align-items: center;
            gap: 15px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
        `;

        installPromotion.innerHTML = `
            <div style="flex: 1;">
                <div style="font-weight: bold; margin-bottom: 3px;">Install Dating App</div>
                <div style="font-size: 12px; opacity: 0.9;">Get the full experience with our app</div>
            </div>
            <button id="install-button" style="background: white; color: #667eea; border: none; padding: 8px 16px; border-radius: 15px; cursor: pointer; font-weight: bold;">
                Install
            </button>
            <button id="dismiss-install" style="background: none; border: none; color: white; cursor: pointer; font-size: 18px;">
                โœ•
            </button>
        `;

        document.body.appendChild(installPromotion);

        document.getElementById('install-button').addEventListener('click', async () => {
            // Show the install prompt
            if (deferredPrompt) {
                deferredPrompt.prompt();
                // Wait for the user to respond to the prompt
                const { outcome } = await deferredPrompt.userChoice;
                if (outcome === 'accepted') {
                    console.log('User accepted the install prompt');
                } else {
                    console.log('User dismissed the install prompt');
                }
                // Clear the deferredPrompt variable
                deferredPrompt = null;
            }
        });

        document.getElementById('dismiss-install').addEventListener('click', () => {
            this.hideInstallPromotion();
        });
    }

    hideInstallPromotion() {
        const promotion = document.getElementById('install-promotion');
        if (promotion) {
            promotion.remove();
        }
    }

    setupOfflineSupport() {
        // Monitor online/offline status
        window.addEventListener('online', () => {
            this.handleOnline();
        });

        window.addEventListener('offline', () => {
            this.handleOffline();
        });

        // Set up service worker for offline functionality
        this.setupServiceWorker();
    }

    setupServiceWorker() {
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('/sw.js')
                .then(registration => {
                    console.log('Service Worker registered:', registration);
                })
                .catch(error => {
                    console.log('Service Worker registration failed:', error);
                });
        }
    }

    handleOnline() {
        this.isOffline = false;
        this.hideOfflineIndicator();
        this.showNotification('Connection restored!');

        // Sync any pending data
        this.syncPendingData();
    }

    handleOffline() {
        this.isOffline = true;
        this.showOfflineIndicator();
        this.showNotification('You are currently offline. Some features may be limited.');
    }

    showOfflineIndicator() {
        const indicator = document.createElement('div');
        indicator.id = 'offline-indicator';
        indicator.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            background: #f44336;
            color: white;
            text-align: center;
            padding: 10px;
            z-index: 10000;
            font-size: 14px;
        `;
        indicator.textContent = '๐Ÿ”ด You are currently offline';
        document.body.appendChild(indicator);
    }

    hideOfflineIndicator() {
        const indicator = document.getElementById('offline-indicator');
        if (indicator) {
            indicator.remove();
        }
    }

    syncPendingData() {
        // Sync any data that was queued while offline
        console.log('Syncing pending data...');

        // This would sync messages, profile updates, etc.
        // For now, we'll just show a notification
        this.showNotification('Syncing your data...');
    }

    setupCrossPlatformUI() {
        this.adaptUIForPlatform();
        this.setupPlatformSpecificFeatures();
    }

    adaptUIForPlatform() {
        if (this.isMobile) {
            this.optimizeForMobile();
        } else {
            this.optimizeForDesktop();
        }

        if (this.isPWA) {
            this.optimizeForStandalone();
        }
    }

    optimizeForMobile() {
        // Mobile-specific optimizations
        const style = document.createElement('style');
        style.textContent = `
            /* Mobile-optimized styles */
            @media (max-width: 768px) {
                #webgl-canvas {
                    touch-action: pan-x pan-y;
                }

                .desktop-only {
                    display: none !important;
                }

                /* Larger touch targets */
                button, [role="button"] {
                    min-height: 44px;
                    min-width: 44px;
                }

                /* Optimize text size */
                body {
                    font-size: 16px; /* Prevent zoom on iOS */
                }
            }
        `;
        document.head.appendChild(style);
    }

    optimizeForDesktop() {
        // Desktop-specific enhancements
        const style = document.createElement('style');
        style.textContent = `
            .mobile-only {
                display: none !important;
            }

            /* Desktop hover effects */
            button:hover {
                transform: translateY(-1px);
                transition: transform 0.2s;
            }
        `;
        document.head.appendChild(style);
    }

    optimizeForStandalone() {
        // PWA standalone mode optimizations
        const style = document.createElement('style');
        style.textContent = `
            /* Standalone app styling */
            @media all and (display-mode: standalone) {
                body {
                    -webkit-app-region: drag;
                }

                /* Add padding for status bar on iOS */
                .pwa-safe-area {
                    padding-top: env(safe-area-inset-top);
                }
            }
        `;
        document.head.appendChild(style);
    }

    setupPlatformSpecificFeatures() {
        // Platform-specific feature detection
        this.detectPlatformCapabilities();

        // Setup platform-appropriate controls
        this.setupPlatformControls();
    }

    detectPlatformCapabilities() {
        // Detect device capabilities
        const capabilities = {
            touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
            vibration: 'vibrate' in navigator,
            geolocation: 'geolocation' in navigator,
            camera: 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices,
            notifications: 'Notification' in window,
            bluetooth: 'bluetooth' in navigator
        };

        console.log('Device capabilities:', capabilities);

        // Enable features based on capabilities
        if (capabilities.vibration) {
            this.enableHapticFeedback();
        }

        if (capabilities.geolocation) {
            this.enableLocationFeatures();
        }

        if (capabilities.notifications) {
            this.setupNotifications();
        }
    }

    enableHapticFeedback() {
        // Add haptic feedback to interactions
        const originalAddEventListener = EventTarget.prototype.addEventListener;

        EventTarget.prototype.addEventListener = function(type, listener, options) {
            // Add vibration to click/touch events on buttons
            if (type === 'click' && this.tagName === 'BUTTON') {
                const wrappedListener = function(e) {
                    // Short vibration on interaction
                    if (navigator.vibrate) {
                        navigator.vibrate(10);
                    }
                    return listener.call(this, e);
                };
                return originalAddEventListener.call(this, type, wrappedListener, options);
            }
            return originalAddEventListener.call(this, type, listener, options);
        };
    }

    enableLocationFeatures() {
        // Enable location-based features
        if (window.datingChat?.sceneManager) {
            this.setupProximityFeatures();
        }
    }

    setupProximityFeatures() {
        // Setup proximity-based user discovery
        if ('geolocation' in navigator) {
            navigator.geolocation.getCurrentPosition(
                (position) => {
                    const userLocation = {
                        latitude: position.coords.latitude,
                        longitude: position.coords.longitude,
                        accuracy: position.coords.accuracy
                    };

                    // Store user location for proximity matching
                    this.storeUserLocation(userLocation);

                    // Show nearby users
                    this.showNearbyUsers(userLocation);
                },
                (error) => {
                    console.log('Location access denied or unavailable:', error);
                },
                {
                    enableHighAccuracy: true,
                    timeout: 10000,
                    maximumAge: 60000
                }
            );
        }
    }

    storeUserLocation(location) {
        // Store location for matching (in a real app, this would go to server)
        const currentUser = window.datingChat?.chatConnection?.getCurrentUser();
        if (currentUser) {
            localStorage.setItem(`location_${currentUser.id}`, JSON.stringify(location));
        }
    }

    showNearbyUsers(userLocation) {
        // Show users who are physically nearby
        // This would integrate with the main user discovery system
        console.log('Showing nearby users based on location:', userLocation);
    }

    setupNotifications() {
        // Request notification permission
        if (Notification.permission === 'default') {
            // Show custom notification permission prompt
            this.showNotificationPermissionPrompt();
        }
    }

    showNotificationPermissionPrompt() {
        const prompt = document.createElement('div');
        prompt.id = 'notification-prompt';
        prompt.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 20px;
            border-radius: 15px;
            z-index: 10000;
            max-width: 400px;
            text-align: center;
            border: 2px solid #4CAF50;
        `;

        prompt.innerHTML = `
            <h3 style="margin: 0 0 10px 0;">๐Ÿ”” Enable Notifications</h3>
            <p style="margin: 0 0 15px 0; font-size: 14px;">Get notified about new messages, matches, and important updates</p>
            <div style="display: flex; gap: 10px; justify-content: center;">
                <button onclick="crossPlatform.enableNotifications()" 
                        style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer;">
                    Enable
                </button>
                <button onclick="crossPlatform.hideNotificationPrompt()" 
                        style="padding: 10px 20px; background: #666; color: white; border: none; border-radius: 8px; cursor: pointer;">
                    Later
                </button>
            </div>
        `;

        document.body.appendChild(prompt);
    }

    enableNotifications() {
        Notification.requestPermission().then(permission => {
            if (permission === 'granted') {
                this.showNotification('Notifications enabled!');
                this.setupPushNotifications();
            } else {
                this.showNotification('Notifications disabled. You can enable them in browser settings.');
            }
            this.hideNotificationPrompt();
        });
    }

    hideNotificationPrompt() {
        const prompt = document.getElementById('notification-prompt');
        if (prompt) {
            prompt.remove();
        }
    }

    setupPushNotifications() {
        // Setup push notifications for new messages, matches, etc.
        if ('serviceWorker' in navigator && 'PushManager' in window) {
            navigator.serviceWorker.ready.then(registration => {
                // Subscribe to push notifications
                // This would integrate with a push notification service
                console.log('Push notifications setup complete');
            });
        }
    }

    setupPlatformControls() {
        // Setup platform-appropriate control schemes
        if (this.isMobile) {
            this.setupTouchControls();
        } else {
            this.setupKeyboardControls();
        }
    }

    setupTouchControls() {
        // Enhanced touch controls for mobile
        const canvas = document.getElementById('webgl-canvas');
        if (!canvas) return;

        // Pinch to zoom
        let initialDistance = null;

        canvas.addEventListener('touchstart', (e) => {
            if (e.touches.length === 2) {
                initialDistance = this.getTouchDistance(e.touches[0], e.touches[1]);
            }
        });

        canvas.addEventListener('touchmove', (e) => {
            if (e.touches.length === 2 && initialDistance !== null) {
                e.preventDefault();
                const currentDistance = this.getTouchDistance(e.touches[0], e.touches[1]);
                const zoomDelta = (currentDistance - initialDistance) * 0.01;

                // Adjust camera zoom
                if (window.datingChat?.camera) {
                    window.datingChat.camera.distance = Math.max(2, Math.min(20, 
                        window.datingChat.camera.distance - zoomDelta));
                }

                initialDistance = currentDistance;
            }
        });

        canvas.addEventListener('touchend', () => {
            initialDistance = null;
        });
    }

    setupKeyboardControls() {
        // Enhanced keyboard controls for desktop
        document.addEventListener('keydown', (e) => {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
                return; // Don't interfere with text input
            }

            const camera = window.datingChat?.camera;
            if (!camera) return;

            const moveSpeed = 0.2;
            const zoomSpeed = 0.5;

            switch(e.key) {
                case 'w':
                case 'ArrowUp':
                    camera.moveForward(moveSpeed);
                    break;
                case 's':
                case 'ArrowDown':
                    camera.moveForward(-moveSpeed);
                    break;
                case 'a':
                case 'ArrowLeft':
                    camera.moveRight(-moveSpeed);
                    break;
                case 'd':
                case 'ArrowRight':
                    camera.moveRight(moveSpeed);
                    break;
                case 'q':
                    camera.distance = Math.min(20, camera.distance + zoomSpeed);
                    break;
                case 'e':
                    camera.distance = Math.max(2, camera.distance - zoomSpeed);
                    break;
                case ' ':
                    // Space bar for quick actions
                    this.quickAction();
                    break;
            }
        });
    }

    getTouchDistance(touch1, touch2) {
        const dx = touch1.clientX - touch2.clientX;
        const dy = touch1.clientY - touch2.clientY;
        return Math.sqrt(dx * dx + dy * dy);
    }

    quickAction() {
        // Quick action based on context
        const focusedInput = document.activeElement;
        if (focusedInput && (focusedInput.tagName === 'INPUT' || focusedInput.tagName === 'TEXTAREA')) {
            return; // Don't interfere with text input
        }

        // Toggle chat focus or perform other quick action
        const chatInput = document.getElementById('message-input');
        if (chatInput) {
            chatInput.focus();
        }
    }

    showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 12px 24px;
            border-radius: 25px;
            z-index: 1000;
            font-size: 14px;
        `;
        document.getElementById('container').appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }
}

5. Advanced Performance Optimization

Let's implement advanced performance optimizations for large-scale deployment:

// Advanced performance optimization system
class AdvancedPerformanceOptimizer {
    constructor(sceneManager, renderer) {
        this.sceneManager = sceneManager;
        this.renderer = renderer;
        this.performanceMetrics = new Map();
        this.optimizationStrategies = new Map();
        this.memoryMonitor = new MemoryMonitor();

        this.setupAdvancedMonitoring();
        this.applyAdvancedOptimizations();
        this.startPerformanceTuning();
    }

    setupAdvancedMonitoring() {
        // Advanced performance metrics collection
        this.setupFrameTimeMonitoring();
        this.setupMemoryMonitoring();
        this.setupNetworkMonitoring();
        this.setupUserInteractionMonitoring();

        // Performance overlay
        this.createPerformanceOverlay();
    }

    setupFrameTimeMonitoring() {
        let frameCount = 0;
        let lastTime = performance.now();
        const fpsUpdateInterval = 1000; // Update FPS every second

        const updateFrameTime = () => {
            frameCount++;
            const currentTime = performance.now();

            if (currentTime - lastTime >= fpsUpdateInterval) {
                const fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
                this.updatePerformanceMetric('fps', fps);

                frameCount = 0;
                lastTime = currentTime;
            }

            // Monitor frame time
            const frameTime = performance.now() - currentTime;
            this.updatePerformanceMetric('frameTime', frameTime);

            requestAnimationFrame(updateFrameTime);
        };

        updateFrameTime();
    }

    setupMemoryMonitoring() {
        if ('memory' in performance) {
            setInterval(() => {
                const memory = performance.memory;
                this.updatePerformanceMetric('usedJSHeapSize', memory.usedJSHeapSize);
                this.updatePerformanceMetric('totalJSHeapSize', memory.totalJSHeapSize);
                this.updatePerformanceMetric('jsHeapSizeLimit', memory.jsHeapSizeLimit);
            }, 5000);
        }
    }

    setupNetworkMonitoring() {
        if ('connection' in navigator) {
            const connection = navigator.connection;
            if (connection) {
                this.updatePerformanceMetric('effectiveType', connection.effectiveType);
                this.updatePerformanceMetric('downlink', connection.downlink);
                this.updatePerformanceMetric('rtt', connection.rtt);

                connection.addEventListener('change', () => {
                    this.handleNetworkChange();
                });
            }
        }
    }

    setupUserInteractionMonitoring() {
        // Monitor user interactions for performance insights
        let interactionStart = 0;

        document.addEventListener('mousedown', () => {
            interactionStart = performance.now();
        });

        document.addEventListener('mouseup', () => {
            const interactionTime = performance.now() - interactionStart;
            this.updatePerformanceMetric('interactionTime', interactionTime);
        });

        // Monitor input latency
        let inputQueue = [];
        const maxQueueSize = 60; // 1 second at 60fps

        const monitorInput = () => {
            const now = performance.now();
            inputQueue.push(now);

            // Keep only recent timestamps
            if (inputQueue.length > maxQueueSize) {
                inputQueue.shift();
            }

            // Calculate input latency
            if (inputQueue.length > 1) {
                const averageInterval = (now - inputQueue[0]) / inputQueue.length;
                this.updatePerformanceMetric('inputLatency', averageInterval);
            }

            requestAnimationFrame(monitorInput);
        };

        monitorInput();
    }

    handleNetworkChange() {
        const connection = navigator.connection;
        if (connection) {
            const networkQuality = this.calculateNetworkQuality(connection);
            this.adjustForNetworkConditions(networkQuality);
        }
    }

    calculateNetworkQuality(connection) {
        let quality = 1.0; // Default high quality

        if (connection.effectiveType) {
            switch(connection.effectiveType) {
                case 'slow-2g':
                    quality = 0.2;
                    break;
                case '2g':
                    quality = 0.4;
                    break;
                case '3g':
                    quality = 0.7;
                    break;
                case '4g':
                    quality = 0.9;
                    break;
            }
        }

        // Adjust based on downlink speed
        if (connection.downlink) {
            quality = Math.min(quality, connection.downlink / 10); // Normalize to 0-1
        }

        return quality;
    }

    adjustForNetworkConditions(networkQuality) {
        // Adjust streaming quality, update frequency, etc.
        if (networkQuality < 0.5) {
            this.enableLowBandwidthMode();
        } else {
            this.disableLowBandwidthMode();
        }
    }

    enableLowBandwidthMode() {
        // Reduce data usage
        console.log('Enabling low bandwidth mode');

        // Reduce avatar update frequency
        if (window.datingChat) {
            window.datingChat.avatarUpdateInterval = 2000; // Update every 2 seconds instead of every frame
        }

        // Use lower quality assets
        this.setQualityLevel('low');

        // Reduce chat history size
        if (window.datingChat?.realTimeChat) {
            window.datingChat.realTimeChat.maxMessages = 50;
        }
    }

    disableLowBandwidthMode() {
        // Restore normal data usage
        console.log('Disabling low bandwidth mode');

        if (window.datingChat) {
            window.datingChat.avatarUpdateInterval = 100; // Normal update frequency
        }

        this.setQualityLevel('high');

        if (window.datingChat?.realTimeChat) {
            window.datingChat.realTimeChat.maxMessages = 100;
        }
    }

    createPerformanceOverlay() {
        this.performanceOverlay = document.createElement('div');
        this.performanceOverlay.id = 'performance-overlay';
        this.performanceOverlay.style.cssText = `
            position: absolute;
            top: 10px;
            left: 10px;
            background: rgba(0, 0, 0, 0.8);
            color: #0f0;
            padding: 10px;
            border-radius: 5px;
            font-family: monospace;
            font-size: 12px;
            z-index: 10000;
            display: none;
            pointer-events: none;
        `;

        document.getElementById('container').appendChild(this.performanceOverlay);

        // Toggle with Ctrl+Shift+P
        document.addEventListener('keydown', (e) => {
            if (e.ctrlKey && e.shiftKey && e.key === 'P') {
                e.preventDefault();
                this.togglePerformanceOverlay();
            }
        });
    }

    togglePerformanceOverlay() {
        if (this.performanceOverlay.style.display === 'block') {
            this.performanceOverlay.style.display = 'none';
        } else {
            this.performanceOverlay.style.display = 'block';
            this.updatePerformanceOverlay();
        }
    }

    updatePerformanceOverlay() {
        if (this.performanceOverlay.style.display !== 'block') return;

        const fps = this.performanceMetrics.get('fps') || 0;
        const frameTime = this.performanceMetrics.get('frameTime') || 0;
        const usedMemory = this.performanceMetrics.get('usedJSHeapSize');
        const inputLatency = this.performanceMetrics.get('inputLatency') || 0;

        let memoryText = '';
        if (usedMemory) {
            const usedMB = Math.round(usedMemory / (1024 * 1024));
            memoryText = `Memory: ${usedMB}MB\n`;
        }

        this.performanceOverlay.innerHTML = `
            FPS: ${fps}\n
            Frame: ${frameTime.toFixed(2)}ms\n
            ${memoryText}Input: ${inputLatency.toFixed(2)}ms\n
            Objects: ${this.sceneManager.objects.length}\n
            Avatars: ${this.sceneManager.avatars.length}
        `;

        // Update color based on performance
        if (fps < 30) {
            this.performanceOverlay.style.color = '#f00';
        } else if (fps < 50) {
            this.performanceOverlay.style.color = '#ff0';
        } else {
            this.performanceOverlay.style.color = '#0f0';
        }

        requestAnimationFrame(() => this.updatePerformanceOverlay());
    }

    updatePerformanceMetric(key, value) {
        this.performanceMetrics.set(key, value);
    }

    applyAdvancedOptimizations() {
        // Apply advanced WebGL optimizations
        this.optimizeWebGL();
        this.optimizeGeometry();
        this.optimizeShaders();
        this.optimizeRendering();
    }

    optimizeWebGL() {
        const gl = this.renderer.gl;
        if (!gl) return;

        // Enable WebGL extensions for better performance
        const extensions = [
            'EXT_texture_filter_anisotropic',
            'OES_element_index_uint',
            'WEBGL_compressed_texture_s3tc',
            'WEBGL_compressed_texture_etc'
        ];

        extensions.forEach(extName => {
            const extension = gl.getExtension(extName);
            if (extension) {
                console.log(`Enabled WebGL extension: ${extName}`);
            }
        });

        // Optimize WebGL state changes
        this.minimizeGLStateChanges();
    }

    minimizeGLStateChanges() {
        // Batch similar rendering operations to minimize state changes
        const originalRender = this.sceneManager.render;

        this.sceneManager.render = function(gl, program, attribLocations, uniformLocations, viewMatrix, projectionMatrix) {
            // Sort objects by material/shader to minimize state changes
            const sortedObjects = this.objects.sort((a, b) => {
                // Sort by material type, then by distance from camera
                const materialA = a.material?.type || 'default';
                const materialB = b.material?.type || 'default';

                if (materialA !== materialB) {
                    return materialA.localeCompare(materialB);
                }

                // Could also sort by distance for better culling
                return 0;
            });

            // Render sorted objects
            sortedObjects.forEach(object => {
                // Original rendering logic here
                // This would use the optimized batching
            });
        };
    }

    optimizeGeometry() {
        // Advanced geometry optimization
        this.sceneManager.objects.forEach(object => {
            if (object.optimize) {
                object.optimize();
            }
        });

        // Implement geometry instancing for identical objects
        this.setupGeometryInstancing();
    }

    setupGeometryInstancing() {
        // Use instanced rendering for identical objects (like trees, furniture)
        console.log('Setting up geometry instancing for better performance');
    }

    optimizeShaders() {
        // Optimize shaders for target hardware
        this.detectHardwareCapabilities();
        this.compileOptimizedShaders();
    }

    detectHardwareCapabilities() {
        const gl = this.renderer.gl;
        if (!gl) return;

        const capabilities = {
            maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
            maxVertexUniforms: gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS),
            maxFragmentUniforms: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
            maxTextureUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
        };

        console.log('Hardware capabilities:', capabilities);

        // Adjust shader complexity based on capabilities
        if (capabilities.maxVertexUniforms < 256) {
            this.useSimplifiedShaders();
        }
    }

    useSimplifiedShaders() {
        // Use simpler shaders for low-end devices
        console.log('Using simplified shaders for better performance');
    }

    compileOptimizedShaders() {
        // Pre-compile optimized shader variations
        console.log('Pre-compiling optimized shader variations');
    }

    optimizeRendering() {
        // Advanced rendering optimizations
        this.implementOcclusionCulling();
        this.implementLODSystem();
        this.optimizeTextureLoading();
    }

    implementOcclusionCulling() {
        // Don't render objects that are behind other objects
        console.log('Implementing occlusion culling');
    }

    implementLODSystem() {
        // Level of Detail system - use simpler models for distant objects
        console.log('Implementing LOD system');

        this.sceneManager.objects.forEach(object => {
            if (object.setLOD) {
                object.setLOD('high'); // Start with high detail
            }
        });
    }

    optimizeTextureLoading() {
        // Implement texture streaming and compression
        console.log('Optimizing texture loading system');

        // Use texture atlases
        this.createTextureAtlases();

        // Implement progressive loading
        this.implementProgressiveLoading();
    }

    createTextureAtlases() {
        // Combine multiple small textures into larger texture atlases
        console.log('Creating texture atlases for better performance');
    }

    implementProgressiveLoading() {
        // Load low-res textures first, then high-res
        console.log('Implementing progressive texture loading');
    }

    startPerformanceTuning() {
        // Continuously monitor and adjust performance
        setInterval(() => {
            this.adaptivePerformanceTuning();
        }, 2000); // Tune every 2 seconds
    }

    adaptivePerformanceTuning() {
        const fps = this.performanceMetrics.get('fps') || 60;
        const frameTime = this.performanceMetrics.get('frameTime') || 0;

        // Adjust quality based on performance
        if (fps < 25) {
            this.increaseOptimizationLevel();
        } else if (fps > 55 && this.optimizationLevel > 0) {
            this.decreaseOptimizationLevel();
        }

        // Monitor memory usage
        this.memoryMonitor.checkMemoryUsage();
    }

    increaseOptimizationLevel() {
        // Apply more aggressive optimizations
        console.log('Increasing optimization level due to low FPS');

        // Reduce LOD quality
        this.sceneManager.objects.forEach(object => {
            if (object.setLOD) {
                object.setLOD('medium');
            }
        });

        // Reduce texture quality
        this.setTextureQuality('medium');

        // Reduce particle effects
        this.reduceVisualEffects();
    }

    decreaseOptimizationLevel() {
        // Reduce optimizations for better quality
        console.log('Decreasing optimization level - good performance');

        // Increase LOD quality
        this.sceneManager.objects.forEach(object => {
            if (object.setLOD) {
                object.setLOD('high');
            }
        });

        // Increase texture quality
        this.setTextureQuality('high');

        // Enable visual effects
        this.enableVisualEffects();
    }

    setTextureQuality(quality) {
        console.log(`Setting texture quality to: ${quality}`);
        // Implementation would adjust texture resolution and compression
    }

    reduceVisualEffects() {
        // Reduce or disable non-essential visual effects
        console.log('Reducing visual effects for better performance');
    }

    enableVisualEffects() {
        // Enable visual effects when performance allows
        console.log('Enabling visual effects');
    }

    setQualityLevel(level) {
        // Comprehensive quality level setting
        switch(level) {
            case 'low':
                this.setTextureQuality('low');
                this.setGeometryQuality('low');
                this.setShaderQuality('low');
                break;
            case 'medium':
                this.setTextureQuality('medium');
                this.setGeometryQuality('medium');
                this.setShaderQuality('medium');
                break;
            case 'high':
                this.setTextureQuality('high');
                this.setGeometryQuality('high');
                this.setShaderQuality('high');
                break;
        }
    }

    setGeometryQuality(quality) {
        console.log(`Setting geometry quality to: ${quality}`);
        // Adjust polygon counts and LOD levels
    }

    setShaderQuality(quality) {
        console.log(`Setting shader quality to: ${quality}`);
        // Use simpler or more complex shaders
    }
}

// Memory monitoring system
class MemoryMonitor {
    constructor() {
        this.memoryUsage = new Map();
        this.cleanupCallbacks = [];
        this.setupMemoryMonitoring();
    }

    setupMemoryMonitoring() {
        // Monitor memory usage
        setInterval(() => {
            this.checkMemoryUsage();
        }, 10000); // Check every 10 seconds
    }

    checkMemoryUsage() {
        if ('memory' in performance) {
            const memory = performance.memory;
            const usedMB = memory.usedJSHeapSize / (1024 * 1024);
            const totalMB = memory.totalJSHeapSize / (1024 * 1024);
            const limitMB = memory.jsHeapSizeLimit / (1024 * 1024);

            this.memoryUsage.set('used', usedMB);
            this.memoryUsage.set('total', totalMB);
            this.memoryUsage.set('limit', limitMB);

            const usagePercent = (usedMB / limitMB) * 100;

            if (usagePercent > 80) {
                this.triggerMemoryCleanup();
            }

            console.log(`Memory usage: ${usedMB.toFixed(1)}MB / ${limitMB.toFixed(1)}MB (${usagePercent.toFixed(1)}%)`);
        }
    }

    triggerMemoryCleanup() {
        console.log('High memory usage detected, triggering cleanup');

        // Call all registered cleanup callbacks
        this.cleanupCallbacks.forEach(callback => {
            try {
                callback();
            } catch (error) {
                console.error('Error in memory cleanup callback:', error);
            }
        });

        // Force garbage collection if available
        if (window.gc) {
            window.gc();
        }
    }

    registerCleanupCallback(callback) {
        this.cleanupCallbacks.push(callback);
    }

    unregisterCleanupCallback(callback) {
        const index = this.cleanupCallbacks.indexOf(callback);
        if (index > -1) {
            this.cleanupCallbacks.splice(index, 1);
        }
    }
}

Updated Main Application Integration

Finally, let's update our main application to integrate all these new advanced features:

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

        this.virtualEconomy = null;
        this.environmentCustomizer = null;
        this.aiAssistant = null;
        this.crossPlatform = null;
        this.advancedPerformance = null;

        this.init();
    }

    async init() {
        this.setupWebGL();
        this.setupShaders();
        this.setupCamera();
        this.setupLighting();
        this.setupTextures();
        this.setupScene();
        this.setupNetwork();
        this.setupSocialFeatures();
        this.setupAdvancedFeatures();
        this.setupAIFeatures();
        this.setupPlatformFeatures(); // New
        this.setupInteraction();
        this.render();
    }

    setupPlatformFeatures() {
        // Initialize cross-platform support
        this.crossPlatform = new CrossPlatformSupport();
        window.crossPlatform = this.crossPlatform;

        // Initialize virtual economy
        this.virtualEconomy = new VirtualEconomy();
        window.virtualEconomy = this.virtualEconomy;

        // Initialize environment customization
        this.environmentCustomizer = new EnvironmentCustomizer(this.sceneManager);
        window.environmentCustomizer = this.environmentCustomizer;

        // Initialize AI conversation assistant
        this.aiAssistant = new AIConversationAssistant();
        window.aiAssistant = this.aiAssistant;

        // Initialize advanced performance optimization
        this.advancedPerformance = new AdvancedPerformanceOptimizer(this.sceneManager, this);
        window.advancedPerformance = this.advancedPerformance;

        // Initialize user's economy account
        const currentUser = this.chatConnection.getCurrentUser();
        this.virtualEconomy.initializeUser(currentUser.id);

        console.log('Platform features initialized');
    }

    // ... rest of the class
}

// Make all systems globally accessible
window.addEventListener('load', async () => {
    window.datingChat = new DatingChat3D();

    // Set global references after initialization
    setTimeout(() => {
        window.virtualEconomy = window.datingChat.virtualEconomy;
        window.environmentCustomizer = window.datingChat.environmentCustomizer;
        window.aiAssistant = window.datingChat.aiAssistant;
        window.crossPlatform = window.datingChat.crossPlatform;
        window.advancedPerformance = window.datingChat.advancedPerformance;
    }, 1000);
});

What We've Accomplished in Part 7

In this seventh part, we've transformed our 3D dating platform into a sophisticated, feature-rich application with:

  1. Virtual Economy System with coins, rewards, daily quests, and premium features
  2. Environment Customization allowing users to create personalized spaces with themes and furniture
  3. AI Conversation Assistant providing real-time conversation tips and date ideas
  4. Cross-Platform Support with PWA features, offline capability, and platform optimization
  5. Advanced Performance Optimization with intelligent tuning and memory management

Key Features Added:

Next Steps

In Part 8, we'll focus on:

Our platform now has all the features needed for a commercial-grade dating application, with monetization, personalization, and cross-platform support!

Back to ChameleonSoftwareOnline.com