WebSim Creation Manual: High-Quality Visualizations

This guide provides detailed instructions and examples for creating engaging interactive web content using SVG, CSS, JavaScript, AI tools, and 3D graphics within the WebSim environment.

1. Graphics with SVG

Scalable Vector Graphics (SVG) are ideal for WebSim projects. They are resolution-independent (always sharp), easily manipulated with code, and often have small file sizes.

Creating Simple SVG Graphics (Step-by-Step)

  1. Add the SVG Container: Start with an empty <svg> tag in your HTML.
  2. Define Dimensions: Set the display size using width and height attributes (e.g., width="100" height="100").
  3. Set the ViewBox: Define the internal coordinate system with the viewBox="min-x min-y width height" attribute (e.g., viewBox="0 0 100 100"). This allows scaling.
  4. Draw Shapes: Add basic shape elements inside the <svg> tag:
  5. Style the Shapes: Use presentation attributes directly on the shape tags (e.g., fill="#ff0000", stroke="black", stroke-width="2") or use CSS classes and external/internal stylesheets.

Example: Simple Inline SVG

<!-- 1. SVG Container with dimensions and viewBox -->
<svg width="100" height="100" viewBox="0 0 100 100">
  <!-- 2. Circle shape with position, radius, and style attributes -->
  <circle id="svg-example-circle" cx="50" cy="50" r="40"
          /* @tweakable Border color of the SVG example circle */
          stroke="#2c3e50"
          /* @tweakable Border width of the SVG example circle */
          stroke-width="3"
          /* @tweakable Fill color of the SVG example circle */
          fill="#e74c3c" />
  <!-- 3. Rectangle shape with position, dimensions, and style attributes -->
  <rect id="svg-example-rect" x="30" y="30" width="40" height="40"
        /* @tweakable Fill color of the SVG example rectangle */
        fill="#ecf0f1"
        /* @tweakable Opacity of the SVG example rectangle (0-1) */
        opacity="0.8"/>
</svg>

2. CSS Animations

CSS provides performant ways to add motion and transitions without complex JavaScript.

Creating Keyframe Animations (Step-by-Step)

  1. Define Keyframes: Create a @keyframes rule in your CSS. Give it a unique name (e.g., @keyframes pulse { ... }).
  2. Specify Stages: Inside the @keyframes rule, define the styles for different points in the animation timeline using percentages (0%, 50%, 100%) or keywords (from for 0%, to for 100%).
  3. Set Styles per Stage: For each stage (percentage or keyword), specify the CSS properties you want to animate (e.g., transform: scale(1.1);, opacity: 0;).
  4. Apply the Animation: Select the HTML/SVG element you want to animate using a CSS selector. Apply the animation shorthand property or individual properties: Shorthand: animation: pulse 2s ease-in-out infinite alternate;
  5. Optimize: Prefer animating transform (translate, scale, rotate) and opacity for smoother performance, as browsers can often optimize these.

Creating Transitions (Step-by-Step)

  1. Define Base Style: Set the initial style of the element in its default state (e.g., background-color: blue;).
  2. Define Target State Style: Define the style the element should have in its target state (e.g., on hover: .my-element:hover { background-color: red; }).
  3. Apply Transition Property: On the element's base style, add the transition property to specify which CSS properties should animate, the duration, and the timing function. Shorthand: transition: background-color 0.3s ease, transform 0.5s ease-out; or transition: all 0.3s ease;
  4. Trigger the Change: The transition will automatically occur when the specified property changes (e.g., due to a class being added/removed via JS, or a pseudo-class like :hover or :focus becoming active).

Example: Animated SVG Element (using Keyframes and Transition)

This circle uses a CSS @keyframes animation (pulse) for a background throb and a transition for a smooth color change on hover.

<!-- HTML -->
<svg width="100" height="100" viewBox="0 0 100 100">
    <circle class="animated-circle" cx="50" cy="50" r="40" />
</svg>

<!-- CSS -->
<style>
.animated-circle {
    /* @tweakable Initial fill color of the CSS animated circle */
    fill: #3498db;
    /* @tweakable Transition duration for hover color change */
    transition: fill 0.3s ease;
    cursor: pointer;
    /* Apply the keyframe animation */
    /* @tweakable Keyframe animation name */
    animation-name: pulse;
    /* @tweakable Keyframe animation duration */
    animation-duration: 2s;
    /* @tweakable Keyframe animation timing function */
    animation-timing-function: ease-in-out;
    /* @tweakable Keyframe animation iteration count */
    animation-iteration-count: infinite;
    /* @tweakable Keyframe animation direction */
    animation-direction: alternate;
    /* Ensure transform happens from the center */
    transform-origin: center;
}

.animated-circle:hover {
    /* @tweakable Hover fill color of the CSS animated circle */
    fill: #2980b9;
}

@keyframes pulse {
    from {
        /* @tweakable Initial scale in pulse animation */
        transform: scale(1);
    }
    to {
        /* @tweakable Max scale in pulse animation */
        transform: scale(1.1);
    }
}
</style>

3. JavaScript Interactivity

JavaScript brings your creations to life, allowing dynamic changes and responses to user actions.

Adding Event Listeners (Step-by-Step)

  1. Identify the Trigger Element: Decide which HTML or SVG element the user will interact with (e.g., a button, an image, an SVG shape). Make sure it has an ID (id="myButton") or a class (class="clickable") for easy selection.
  2. Select the Element in JS: Use JavaScript to get a reference to the element. Place your script in a <script type="module"> tag, usually before the closing </body> tag, or ensure it runs after the DOM is loaded.
  3. Attach the Event Listener: Use the .addEventListener() method on the selected element(s). Example: myElement.addEventListener('click', () => { console.log('Element clicked!'); });
  4. Write the Callback Function: Inside the callback function, write the code that should run when the event happens. This could involve:
  5. Handle Multiple Elements (if needed): If you selected multiple elements with querySelectorAll, loop through the resulting NodeList to attach the listener to each one. Example: clickableItems.forEach(item => { item.addEventListener('click', handleItemClick); });

Example: Interactive SVG Square (Click to Rotate/Recolor)

<!-- HTML -->
<svg width="100" height="100" viewBox="0 0 100 100">
     <rect id="interactive-square" class="interactive-square" x="25" y="25" width="50" height="50" />
</svg>

<!-- CSS -->
<style>
.interactive-square {
    /* @tweakable Initial fill color of the interactive square */
    fill: #e74c3c;
    cursor: pointer;
    /* @tweakable Transition settings for interactive square */
    transition: transform 0.2s ease, fill 0.2s ease;
    /* Ensure transform-origin is center for rotation/scale */
    transform-origin: center;
}

.interactive-square.clicked {
    /* @tweakable Rotation and scale when interactive square is clicked */
    transform: rotate(45deg) scale(0.8);
    /* @tweakable Fill color when interactive square is clicked */
    fill: #c0392b;
}
</style>

4. Timing and Synchronization

Coordinating the timing of different visual elements, animations, and potentially audio cues is crucial for creating polished and professional-looking interactive experiences. WebSim provides several ways to manage timing.

Understanding Timing Mechanisms

Synchronization Strategies

Example: Synchronizing Two SVG Elements with requestAnimationFrame

This example moves a rectangle across and fades out a circle using requestAnimationFrame.

// HTML
<svg id="raf-scene" width="300" height="100" viewBox="0 0 300 100">
    <rect id="raf-rect" x="10" y="40" width="20" height="20"></rect>
    <circle id="raf-circle" cx="150" cy="50" r="15"></circle>
</svg>

// JavaScript
/* @tweakable Total animation duration in milliseconds */
const animationDuration = 3000;
/* @tweakable Final x position of the rectangle */
const finalRectX = 270;
/* @tweakable Delay before circle starts fading (in milliseconds) */
const circleFadeDelay = 1000;

let animationId = null;
let startTime = null;

function animateElements(timestamp) {
    if (!startTime) startTime = timestamp;
    const elapsed = timestamp - startTime;
    const progress = Math.min(1, elapsed / animationDuration);
    
    // Move rectangle from left to right
    const rectX = 10 + (finalRectX - 10) * progress;
    rafRect.setAttribute('x', rectX);
    
    // Fade out circle after delay
    if (elapsed > circleFadeDelay) {
        const circleProgress = Math.min(1, (elapsed - circleFadeDelay) / 
                                        (animationDuration - circleFadeDelay));
        rafCircle.style.opacity = 1 - circleProgress;
    }
    
    if (progress < 1) {
        animationId = requestAnimationFrame(animateElements);
    }
}

startBtn.addEventListener('click', () => {
    if (animationId) cancelAnimationFrame(animationId);
    startTime = null;
    animationId = requestAnimationFrame(animateElements);
});

5. Using AI Tools (LLM & Image/Video Generation)

WebSim provides built-in access to powerful AI tools, including a Large Language Model (LLM) for text generation and analysis, and an Image/Video Generation model.

These tools are accessed via the globally available websim object (e.g., websim.chat.completions.create and websim.imageGen). Ensure the "Enable LLM" or equivalent setting is active for your project.

5.1. AI Chat (LLM)

Interact with the LLM to generate text, answer questions, analyze content, or even get structured JSON data.

Example: Basic AI Chat Interaction

AI response will appear here...
// Basic AI Chat Example
let conversationHistory = [];

async function sendChatMessage() {
    const userMessage = document.getElementById("chat-prompt-1").value.trim();
    if (!userMessage) return;
    
    const responseElement = document.getElementById("chat-response-1");
    responseElement.textContent = "Thinking...";
    
    // Add user message to history
    const newMessage = { role: "user", content: userMessage };
    conversationHistory.push(newMessage);
    
    try {
        /* @tweakable System message for the AI assistant */
        const systemMessage = "You are a helpful assistant who provides concise answers.";
        
        /* @tweakable Maximum conversation history length */
        const maxHistoryLength = 10;
        // Keep only the most recent messages
        conversationHistory = conversationHistory.slice(-maxHistoryLength);
        
        const completion = await websim.chat.completions.create({
            messages: [
                { role: "system", content: systemMessage },
                ...conversationHistory
            ]
        });
        
        // Display the response
        responseElement.textContent = completion.content;
        
        // Add the AI's response to history
        conversationHistory.push(completion);
    } catch (error) {
        responseElement.textContent = "Error: " + error.message;
    }
}

Example: AI Chat with JSON Output

JSON response will appear here...
// AI Chat with JSON Output Example
async function getJsonFromAI() {
    const prompt = document.getElementById("chat-prompt-2").value.trim();
    if (!prompt) return;
    
    const responseElement = document.getElementById("chat-response-2");
    responseElement.textContent = "Thinking...";
    
    try {
        const completion = await websim.chat.completions.create({
            messages: [
                {
                    role: "system",
                    content: `Provide information in JSON format following this schema:
                    {
                        "summary": string,  // A brief summary
                        "details": string,  // More detailed explanation
                        "confidence": number  // 0-1 rating of your confidence
                    }`
                },
                { role: "user", content: prompt }
            ],
            json: true // Request JSON output
        });
        
        // Parse and display the response
        const result = JSON.parse(completion.content);
        responseElement.innerHTML = `Summary: ${result.summary}
Details: ${result.details}
Confidence: ${result.confidence}`; } catch (error) { responseElement.textContent = "Error: " + error.message; } }

5.2. AI Image Generation

Generate images from text prompts. You can specify aspect ratios, dimensions, or use seeds for variations.

Example: AI Image Generation

Image will appear here...

// AI Image Generation Example
async function generateImage() {
    const prompt = document.getElementById("image-prompt").value.trim();
    if (!prompt) return;
    
    const resultElement = document.getElementById("image-result");
    resultElement.innerHTML = "Generating image...";
    
    try {
        /* @tweakable Aspect ratio for generated images */
        const aspectRatio = "16:9";
        
        const result = await websim.imageGen({
            prompt: prompt,
            aspect_ratio: aspectRatio
        });
        
        if (result && result.url) {
            // Create and display the image
            const img = document.createElement("img");
            img.src = result.url;
            img.alt = prompt;
            img.className = "ai-image-preview";
            
            resultElement.innerHTML = "";
            resultElement.appendChild(img);
        } else {
            throw new Error("Invalid response from image generator");
        }
    } catch (error) {
        resultElement.textContent = "Error: " + error.message;
    }
}

5.3. File Upload for Image Input

To use images as input for the LLM (e.g., asking questions about an image), you first need to upload the image file and get its URL using websim.upload(file). This function returns a promise that resolves with the URL of the uploaded file.

Example: Uploading a File

No file selected
Upload status will appear here...
// File Upload Example
document.getElementById("file-input").addEventListener("change", (event) => {
    const fileInfo = document.getElementById("file-name");
    if (event.target.files.length > 0) {
        fileInfo.textContent = event.target.files[0].name;
    } else {
        fileInfo.textContent = "No file selected";
    }
});

async function uploadFile() {
    const fileInput = document.getElementById("file-input");
    const resultElement = document.getElementById("upload-result");
    
    if (!fileInput.files || fileInput.files.length === 0) {
        resultElement.textContent = "Please select a file first";
        return;
    }
    
    const file = fileInput.files[0];
    resultElement.textContent = "Uploading...";
    
    try {
        // Upload to S3
        const url = await websim.upload(file);
        resultElement.textContent = "File uploaded successfully. URL: " + url;
        
        // Optionally display the image
        if (file.type.startsWith("image/")) {
            const img = document.createElement("img");
            img.src = url;
            img.alt = "Uploaded image";
            img.className = "ai-image-preview";
            resultElement.appendChild(img);
        }
    } catch (error) {
        resultElement.textContent = "Error uploading file: " + error.message;
    }
}

5.4. AI Video Generation (Hypothetical)

WebSim aims to provide tools for various creative tasks. While not yet implemented, this section describes how a potential AI video generation feature might work.

Imagine a function like websim.videoGen that takes a text prompt and parameters like duration or style to generate short video clips.

Example: AI Video Generation (Simulated)

Video preview or status will appear here...

(Note: Video Generation API (websim.videoGen) is hypothetical and not yet implemented.)

// Future AI Video Generation Example (hypothetical)
async function generateVideo() {
    const prompt = document.getElementById("video-prompt").value.trim();
    if (!prompt) return;
    
    const resultElement = document.getElementById("video-result");
    resultElement.innerHTML = "Generating video...";
    
    try {
        /* @tweakable Duration of generated video in seconds */
        const duration = 3;
        /* @tweakable Motion intensity (low, medium, high) */
        const motionLevel = "medium";
        
        // This is hypothetical - not yet implemented
        const result = await websim.videoGen({
            prompt: prompt,
            duration_seconds: duration,
            motion_level: motionLevel
        });
        
        if (result && result.url) {
            // Create and display the video
            const video = document.createElement("video");
            video.src = result.url;
            video.controls = true;
            video.autoplay = false;
            video.loop = true;
            video.muted = false;
            video.className = "ai-video-preview";
            
            resultElement.innerHTML = "";
            resultElement.appendChild(video);
        }
    } catch (error) {
        resultElement.textContent = "Error: " + error.message;
    }
}

6. 3D Graphics with Three.js

Integrate high-quality 3D graphics into your WebSim projects using libraries like Three.js.

Introduction to 3D in the Browser

Modern web browsers support WebGL (Web Graphics Library), a JavaScript API for rendering interactive 2D and 3D graphics without plug-ins. However, using WebGL directly can be complex. Libraries like Three.js provide higher-level abstractions, making it much easier to create and manage 3D scenes.

Using Three.js

Three.js is the de facto standard for web-based 3D graphics.

Example: Embedded Simple 3D Scene

This demonstrates embedding a basic Three.js scene directly within this page. You can drag to orbit the Torus Knot.

// Basic Three.js setup for a Torus Knot
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);

// Camera setup
/* @tweakable Camera field of view (degrees) */
const cameraFov = 75;
const camera = new THREE.PerspectiveCamera(
    cameraFov,
    canvas.clientWidth / canvas.clientHeight,
    0.1,
    1000
);
camera.position.z = 5;

// Renderer setup
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(canvas.clientWidth, canvas.clientHeight);

// Torus Knot geometry
/* @tweakable Radius of the Torus Knot */
const knotRadius = 0.7;
/* @tweakable Radius of the Torus Knot's tube */
const knotTubeRadius = 0.2;
/* @tweakable Segments in the Torus Knot's main radius */
const knotTubularSegments = 100;
/* @tweakable Segments around the Torus Knot's tube */
const knotRadialSegments = 16;
const geometry = new THREE.TorusKnotGeometry(
    knotRadius, 
    knotTubeRadius, 
    knotTubularSegments, 
    knotRadialSegments
);

// Material
/* @tweakable Color of the mesh material */
const materialColor = 0x1E90FF;
const material = new THREE.MeshStandardMaterial({
    color: materialColor,
    metalness: 0.5,
    roughness: 0.4
});

// Create mesh and add to scene
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// Add lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(2, 3, 4);
scene.add(directionalLight);

// Add OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;

// Animation loop
function animate() {
    requestAnimationFrame(animate);
    mesh.rotation.x += 0.005;
    mesh.rotation.y += 0.008;
    controls.update();
    renderer.render(scene, camera);
}

animate();

7. Available Templates

WebSim provides several templates to help you get started quickly with common project types. These templates come with the necessary structure and code organization to implement specific features.

8. Multiplayer & Data Persistence

WebSim provides built-in capabilities for creating multiplayer experiences and persisting data between sessions. This allows you to build collaborative projects, real-time games, and applications that remember user data.

Setting Up Multiplayer

The WebSim Socket provides easy-to-use APIs for real-time communication between users:

// Initialize the WebSim Socket
const room = new WebsimSocket();

// Wait for initialization
await room.initialize();

// Subscribe to presence updates (other users' states)
room.subscribePresence((currentPresence) => {
    // Update UI when any user's presence changes
    updatePlayerList(room.presence);
});

// Update your own presence (position, state, etc.)
/* @tweakable Player starting X position */
const startX = 100;
/* @tweakable Player starting Y position */
const startY = 150;

room.updatePresence({
    x: startX,
    y: startY,
    health: 100,
    username: "Player1"  // Will be automatically provided
});

Example: Simple Player Presence Display

A
Alice
X: 120, Y: 85
B
Bob
X: 240, Y: 150
// Player Presence Display Example
function updatePlayerList(presence) {
    const playerList = document.getElementById("player-list");
    playerList.innerHTML = "";
    
    // Convert presence object into an array of players
    const players = Object.entries(presence).map(([clientId, data]) => {
        return {
            id: clientId,
            username: room.peers[clientId]?.username || "Unknown",
            avatarUrl: room.peers[clientId]?.avatarUrl,
            x: data.x || 0,
            y: data.y || 0
        };
    });
    
    // Create a player element for each connected user
    players.forEach(player => {
        const playerElement = document.createElement("div");
        playerElement.className = "player";
        
        const avatar = document.createElement("div");
        avatar.className = "player-avatar";
        avatar.textContent = player.username.charAt(0).toUpperCase();
        
        const name = document.createElement("div");
        name.className = "player-name";
        name.textContent = player.username;
        
        const position = document.createElement("div");
        position.className = "player-position";
        position.textContent = `X: ${Math.round(player.x)}, Y: ${Math.round(player.y)}`;
        
        playerElement.appendChild(avatar);
        playerElement.appendChild(name);
        playerElement.appendChild(position);
        
        playerList.appendChild(playerElement);
    });
}

Persistent Records

WebSim also provides a database-like system for persisting records between sessions:

// Creating a new record
const post = await room.collection('post').create({
    message: "Hello world!",
    // id, username, and created_at are automatically added
});

// Getting all records of a specific type
const posts = room.collection('post').getList();

// Filtering records
const replies = room.collection('post')
    .filter({ parent_id: post.id })
    .getList();

// Updating a record (only works for records you created)
await room.collection('post').update(post.id, {
    message: "Updated message"
});

// Subscribing to changes
room.collection('post').subscribe(function(posts) {
    // This runs whenever the posts collection changes
    displayPosts(posts);
});

Example: Simple Chat System

System: Welcome to the chat! This is a demonstration, not a live chat.
Alice: Hello everyone!
Bob: Hi Alice! How are you today?
// Simple Chat System Example
const room = new WebsimSocket();

// Initialize the chat system
async function initChat() {
    await room.initialize();
    
    // Subscribe to messages
    room.collection('message').subscribe(function(messages) {
        displayMessages(messages);
    });
    
    // Send message handler
    document.getElementById('send-button').addEventListener('click', async () => {
        const input = document.getElementById('message-input');
        const text = input.value.trim();
        
        if (text) {
            await room.collection('message').create({
                text: text,
                /* @tweakable Message type (optional tag) */
                type: 'chat'
            });
            input.value = '';
        }
    });
    
    // Allow Enter key to send
    document.getElementById('message-input').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('send-button').click();
        }
    });
}

// Display messages in the UI
function displayMessages(messages) {
    const container = document.getElementById('messages');
    container.innerHTML = '';
    
    // Sort messages by time (oldest first)
    const sortedMessages = [...messages].sort((a, b) => 
        new Date(a.created_at) - new Date(b.created_at)
    );
    
    sortedMessages.forEach(msg => {
        const messageEl = document.createElement('div');
        messageEl.style = 'padding: 5px; margin-bottom: 5px; background-color: #f1f1f1; border-radius: 4px;';
        
        /* @tweakable Different color for user's own messages */
        if (msg.username === room.peers[room.clientId]?.username) {
            messageEl.style.backgroundColor = '#e1f5fe';
        }
        
        messageEl.innerHTML = `${msg.username}: ${msg.text}`;
        container.appendChild(messageEl);
    });
    
    // Scroll to bottom
    container.scrollTop = container.scrollHeight;
}

Room State for Shared Game Data

For game-specific shared state, use room.roomState to sync data across all clients:

// Update shared room state (e.g., world objects)
room.updateRoomState({
    objectPositions: {
        "rock-1": { x: 100, y: 200 },
        "tree-1": { x: 150, y: 300 }
    }
});

// Subscribe to room state changes
room.subscribeRoomState((currentRoomState) => {
    // Update world objects in the UI
    updateWorldObjects(currentRoomState.objectPositions);
});

9. Performance Optimization

Creating smooth, responsive web experiences requires attention to performance. This section covers essential techniques to optimize your WebSim projects.

Rendering Performance

Key Concept: The browser's rendering pipeline involves several steps: JavaScript → Style calculations → Layout → Paint → Composite. Optimizing each step leads to better performance.

JavaScript Optimization

Asset Optimization

WebSim Tip: For multiplayer applications, be cautious about the frequency and size of data updates sent through WebsimSocket. Batch updates when possible and only send what's changed rather than complete state objects.

Performance Testing

Regularly test your WebSim projects for performance using browser developer tools:

10. Best Practices

This section provides guidelines and best practices for creating high-quality, maintainable WebSim projects.

Code Organization

User Experience

Principle: Always provide visual feedback for user interactions, including hover states, loading indicators, and confirmation of actions.

WebSim-Specific Best Practices

Tip: WebSim projects often benefit from a combination of techniques. Don't hesitate to mix SVG, CSS animations, JavaScript, and AI tools in creative ways.

Comparison of Visualization Techniques

This table summarizes when to use different techniques based on your project requirements:

Technique Best For Limitations
SVG Vector graphics, interactive diagrams, data visualizations Can become slow with many elements (100s+)
CSS Animations Simple UI animations, transitions, hover effects Limited control flow, difficult to coordinate multiple animations
Canvas 2D Pixel-based graphics, complex animations with many objects No built-in object model, requires manual redrawing
WebGL/Three.js 3D visualizations, games, complex simulations Steeper learning curve, higher performance requirements
AI Generation Dynamic content creation, natural language interfaces Less predictable outputs, API call overhead