Skip to content

Instantly share code, notes, and snippets.

@hideya
Last active August 13, 2025 20:32
Show Gist options
  • Save hideya/228c7e42b1a261f4c7bb868d9c0e66d8 to your computer and use it in GitHub Desktop.
Save hideya/228c7e42b1a261f4c7bb868d9c0e66d8 to your computer and use it in GitHub Desktop.

Revisions

  1. hideya revised this gist Aug 13, 2025. 1 changed file with 7 additions and 18 deletions.
    25 changes: 7 additions & 18 deletions SVG-filter-shiny-metal-demo.html
    Original file line number Diff line number Diff line change
    @@ -23,7 +23,7 @@
    overflow: hidden;
    }

    .metal-ball-container {
    .metal-panel-container {
    position: fixed;
    top: 0;
    left: 0;
    @@ -32,7 +32,7 @@
    z-index: 10;
    }

    .metal-ball {
    .metal-panel {
    width: 100%;
    height: 100%;
    }
    @@ -43,8 +43,8 @@
    </style>
    </head>
    <body>
    <div class="metal-ball-container">
    <svg class="metal-ball" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
    <div class="metal-panel-container">
    <svg class="metal-panel" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <defs>
    <!-- Bump map using turbulence -->
    <filter id="bumpMap" x="-50%" y="-50%" width="200%" height="200%">
    @@ -60,12 +60,6 @@
    -1 8 -1
    -1 -1 -1" result="normalMap"/>

    <!-- Create lighting effect -->
    <!-- feSpecularLighting in="normalMap" specularConstant="2" specularExponent="20"
    lighting-color="#ffffff" result="specularOut">
    <fePointLight id="lightSource" x="0" y="0" z="100"/>
    </feSpecularLighting -->

    <!-- Create diffuse lighting -->
    <feDiffuseLighting in="normalMap" diffuseConstant="1" lighting-color="#ffffff" result="diffuseOut">
    <fePointLight id="diffuseLight" x="0" y="0" z="100"/>
    @@ -106,15 +100,10 @@
    // if (lightSource && diffuseLight) {
    if (diffuseLight) {
    // Convert mouse position to light coordinates (scaled for full screen)
    const lightX = mouseX * 300 - 50;
    const lightY = mouseY * 300 - 50;
    const lightX = mouseX * 300 - 100;
    const lightY = mouseY * 300 - 100;
    const lightZ = 100 // - (mouseX * 50); // Vary Z based on X for more dynamic effect

    // Update both light sources
    //lightSource.setAttribute('x', lightX);
    //lightSource.setAttribute('y', lightY);
    //lightSource.setAttribute('z', lightZ);

    diffuseLight.setAttribute('x', lightX);
    diffuseLight.setAttribute('y', lightY);
    diffuseLight.setAttribute('z', lightZ * 0.5);
    @@ -133,4 +122,4 @@
    updateLighting();
    </script>
    </body>
    </html>
    </html>
  2. hideya revised this gist Aug 7, 2025. 1 changed file with 87 additions and 228 deletions.
    315 changes: 87 additions & 228 deletions SVG-filter-shiny-metal-demo.html
    Original file line number Diff line number Diff line change
    @@ -3,275 +3,134 @@
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Super Shiny Metal with Dynamic Lighting</title>
    <title>SVG Filter Demo -- Bumpy Metal Surface</title>
    <style>
    body {
    * {
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
    background: #1a1a1a;
    min-height: 100vh;
    overflow: hidden;
    cursor: none;
    box-sizing: border-box;
    }

    .container {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    position: relative;
    body, html {
    height: 100%;
    overflow: hidden;
    }

    .metal-surface {
    width: 100px;
    height: 100px;
    background: radial-gradient(ellipse at center, #e8e8e8, #c0c0c0, #a0a0a0);
    border-radius: 15px;
    body {
    background: #000000;
    font-family: 'Arial', sans-serif;
    position: relative;
    filter: url(#ultraShiny);
    box-shadow:
    0 0 20px rgba(255,255,255,0.3),
    inset 0 0 30px rgba(255,255,255,0.1);
    border: 2px solid #999;
    }

    .surface-label {
    position: absolute;
    bottom: -40px;
    left: 50%;
    transform: translateX(-50%);
    color: #ccc;
    font-size: 16px;
    font-weight: bold;
    text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
    text-align: center;
    white-space: nowrap;
    }

    .light-cursor {
    position: fixed;
    width: 40px;
    height: 40px;
    background: radial-gradient(circle,
    #ffffff 0%,
    #ffff99 20%,
    #ffcc00 50%,
    transparent 70%);
    border-radius: 50%;
    pointer-events: none;
    z-index: 1000;
    transform: translate(-50%, -50%);
    box-shadow:
    0 0 30px #ffff00,
    0 0 60px #ffff00,
    0 0 90px #ffaa00;
    animation: pulse 2s ease-in-out infinite alternate;
    }

    @keyframes pulse {
    0% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0.8;
    }
    100% {
    transform: translate(-50%, -50%) scale(1.2);
    opacity: 1;
    }
    overflow: hidden;
    }

    .info-panel {
    .metal-ball-container {
    position: fixed;
    top: 20px;
    left: 20px;
    background: rgba(0,0,0,0.8);
    color: white;
    padding: 20px;
    border-radius: 10px;
    font-size: 14px;
    border: 1px solid #444;
    max-width: 300px;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 10;
    }

    .info-panel h3 {
    margin: 0 0 10px 0;
    color: #ffdd00;
    .metal-ball {
    width: 100%;
    height: 100%;
    }

    .coord-display {
    position: fixed;
    bottom: 20px;
    right: 20px;
    background: rgba(0,0,0,0.8);
    color: #00ff00;
    padding: 15px;
    border-radius: 10px;
    font-family: 'Courier New', monospace;
    border: 1px solid #444;
    @keyframes gradientShift {
    /* Removed - no longer needed */
    }
    </style>
    </head>
    <body>
    <div class="container">
    <!-- Custom light cursor -->
    <div class="light-cursor" id="lightCursor"></div>

    <!-- SVG Filter for ultra-shiny metal -->
    <svg width="0" height="0" style="position: absolute;">
    <div class="metal-ball-container">
    <svg class="metal-ball" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
    <defs>
    <filter id="ultraShiny" x="-100%" y="-100%" width="300%" height="300%">
    <!-- Create fine surface texture for smooth metallic finish -->
    <feTurbulence type="fractalNoise" baseFrequency="0.3" numOctaves="2" result="fineBumps"/>

    <!-- Convert to height map -->
    <feColorMatrix type="saturate" values="0" in="fineBumps" result="heightMap"/>
    <!-- Bump map using turbulence -->
    <filter id="bumpMap" x="-50%" y="-50%" width="200%" height="200%">
    <!-- Generate noise for surface bumps -->
    <feTurbulence baseFrequency="0.5" numOctaves="4" seed="2" result="noise"/>

    <!-- Smooth out the surface for minimal texture -->
    <feGaussianBlur stdDeviation="1" in="heightMap" result="smoothHeight"/>
    <!-- Create height map from noise -->
    <feColorMatrix in="noise" type="saturate" values="0" result="heightMap"/>

    <!-- Create ultra-strong diffuse lighting -->
    <feDiffuseLighting in="enhancedHeight"
    lighting-color="white"
    surfaceScale="8"
    diffuseConstant="1.5"
    result="diffuse">
    <fePointLight id="mainLight" x="300" y="200" z="150"/>
    </feDiffuseLighting>
    <!-- Generate normal map from height map -->
    <feConvolveMatrix in="heightMap" order="3" kernelMatrix="
    -1 -1 -1
    -1 8 -1
    -1 -1 -1" result="normalMap"/>

    <!-- Create extremely strong specular highlights -->
    <feSpecularLighting in="enhancedHeight"
    lighting-color="#ffffff"
    surfaceScale="12"
    specularConstant="3.5"
    specularExponent="35"
    result="specular">
    <fePointLight x="300" y="200" z="150"/>
    </feSpecularLighting>
    <!-- Create lighting effect -->
    <!-- feSpecularLighting in="normalMap" specularConstant="2" specularExponent="20"
    lighting-color="#ffffff" result="specularOut">
    <fePointLight id="lightSource" x="0" y="0" z="100"/>
    </feSpecularLighting -->

    <!-- Create secondary specular for extra shine -->
    <feSpecularLighting in="enhancedHeight"
    lighting-color="#ffffcc"
    surfaceScale="15"
    specularConstant="2.8"
    specularExponent="50"
    result="specular2">
    <fePointLight x="300" y="200" z="120"/>
    </feSpecularLighting>
    <!-- Create diffuse lighting -->
    <feDiffuseLighting in="normalMap" diffuseConstant="1" lighting-color="#ffffff" result="diffuseOut">
    <fePointLight id="diffuseLight" x="0" y="0" z="100"/>
    </feDiffuseLighting>

    <!-- Combine specular highlights -->
    <feComposite in="specular" in2="specular2" operator="screen" result="combinedSpecular"/>
    <!-- Combine lighting effects -->
    <feComposite in="specularOut" in2="diffuseOut" operator="add" result="lighting"/>

    <!-- Blend everything with the original surface -->
    <feComposite in="combinedSpecular" in2="SourceGraphic" operator="arithmetic"
    k1="0" k2="1" k3="2.5" k4="0" result="shinyMetal"/>
    <feComposite in="shinyMetal" in2="diffuse" operator="multiply" result="litMetal"/>
    <!-- Apply to just the noise pattern -->
    <feComposite in="noise" in2="lighting" operator="multiply" result="lit"/>

    <!-- Final brightness and contrast boost -->
    <feComponentTransfer in="litMetal">
    <feFuncR type="gamma" amplitude="1.2" exponent="0.8" offset="0.1"/>
    <feFuncG type="gamma" amplitude="1.2" exponent="0.8" offset="0.1"/>
    <feFuncB type="gamma" amplitude="1.2" exponent="0.8" offset="0.1"/>
    </feComponentTransfer>
    <!-- Add specular highlights -->
    <feComposite in="lit" in2="specularOut" operator="screen"/>
    </filter>

    <!-- Shadow filter -->
    <filter id="shadow" x="-100%" y="-100%" width="300%" height="300%">
    <feDropShadow dx="0" dy="10" stdDeviation="15" flood-color="#000000" flood-opacity="0.3"/>
    </filter>
    </defs>

    <!-- Shadow -->
    <rect x="15" y="15" width="170" height="170" fill="#000000" opacity="0.2" filter="url(#shadow)"/>

    <!-- Pure bumpy texture rectangle -->
    <rect x="15" y="15" width="170" height="170" fill="#C0C0C0" filter="url(#bumpMap)"/>
    </svg>

    <div class="metal-surface">
    <div class="surface-label">Ultra-Shiny Chrome Metal</div>
    </div>

    <div class="info-panel">
    <h3>Super Shiny Metal Demo</h3>
    <p><strong>Surface Scale:</strong> 3-5 (high but controlled)</p>
    <p><strong>Specular Constant:</strong> 1.2-1.8 (strong shininess)</p>
    <p><strong>Specular Exponent:</strong> 40-60 (sharp highlights)</p>
    <p><strong>Surface Texture:</strong> Smooth metallic finish with fine detail</p>
    <p><strong>Lighting:</strong> Dual specular layers for ultra-bright reflections</p>
    <br>
    <p><em>Move your mouse to see the light reflections dance across the bumpy metal surface!</em></p>
    </div>

    <div class="coord-display" id="coordDisplay">
    Light Position: (0, 0)
    Azimuth: 0°
    Elevation: 45°
    </div>
    </div>

    <script>
    const lightCursor = document.getElementById('lightCursor');
    const coordDisplay = document.getElementById('coordDisplay');
    const mainLight = document.getElementById('mainLight');

    // Get all point lights in the filter
    const pointLights = document.querySelectorAll('fePointLight');
    let mouseX = 0.3;
    let mouseY = 0.3;

    let mouseX = window.innerWidth / 2;
    let mouseY = window.innerHeight / 2;
    function updateLighting() {
    // const lightSource = document.getElementById('lightSource');
    const diffuseLight = document.getElementById('diffuseLight');

    // if (lightSource && diffuseLight) {
    if (diffuseLight) {
    // Convert mouse position to light coordinates (scaled for full screen)
    const lightX = mouseX * 300 - 50;
    const lightY = mouseY * 300 - 50;
    const lightZ = 100 // - (mouseX * 50); // Vary Z based on X for more dynamic effect

    // Update both light sources
    //lightSource.setAttribute('x', lightX);
    //lightSource.setAttribute('y', lightY);
    //lightSource.setAttribute('z', lightZ);

    diffuseLight.setAttribute('x', lightX);
    diffuseLight.setAttribute('y', lightY);
    diffuseLight.setAttribute('z', lightZ * 0.5);
    }
    }

    // Update lighting based on mouse position
    document.addEventListener('mousemove', (e) => {
    mouseX = e.clientX;
    mouseY = e.clientY;
    // Calculate relative position from entire screen
    mouseX = e.clientX / window.innerWidth;
    mouseY = e.clientY / window.innerHeight;

    updateLighting();
    });

    function updateLighting() {
    // Update cursor position
    lightCursor.style.left = mouseX + 'px';
    lightCursor.style.top = mouseY + 'px';

    // Get metal surface bounds for relative positioning
    const metalSurface = document.querySelector('.metal-surface');
    const rect = metalSurface.getBoundingClientRect();

    // Calculate relative position within the metal surface (0-600 x 0-400)
    const relativeX = ((mouseX - rect.left) / rect.width) * 600;
    const relativeY = ((mouseY - rect.top) / rect.height) * 400;

    // Calculate Z position based on distance from center (closer to center = higher Z)
    const centerX = 300;
    const centerY = 200;
    const distanceFromCenter = Math.sqrt(
    Math.pow(relativeX - centerX, 2) + Math.pow(relativeY - centerY, 2)
    );
    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
    const zPosition = 100 + (1 - (distanceFromCenter / maxDistance)) * 100; // 100-200 range

    // Update all point lights
    pointLights.forEach((light, index) => {
    const offset = index * 20; // Slight offset for multiple lights
    light.setAttribute('x', Math.max(0, Math.min(600, relativeX + offset)));
    light.setAttribute('y', Math.max(0, Math.min(400, relativeY + offset)));
    light.setAttribute('z', zPosition + offset);
    });

    // Calculate angles for display
    const azimuth = Math.atan2(relativeY - centerY, relativeX - centerX) * 180 / Math.PI;
    const elevation = Math.atan2(zPosition, distanceFromCenter) * 180 / Math.PI;

    // Update coordinate display
    coordDisplay.innerHTML = `
    Light Position: (${Math.round(relativeX)}, ${Math.round(relativeY)})
    Z-Height: ${Math.round(zPosition)}
    Azimuth: ${Math.round(azimuth)}°
    Elevation: ${Math.round(elevation)}°
    `;
    }

    // Initialize lighting
    updateLighting();

    // Add some subtle animation to the light cursor
    let time = 0;
    function animateLight() {
    time += 0.02;
    const pulse = Math.sin(time) * 0.1 + 1;
    lightCursor.style.filter = `brightness(${pulse})`;
    requestAnimationFrame(animateLight);
    }
    animateLight();
    </script>
    </body>
    </html>
  3. hideya revised this gist Aug 7, 2025. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions SVG-filter-shiny-metal-demo.html
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,4 @@
    // Calculate Z position based on distance from center (closer to center = higher Z)
    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
    const zPosition = 100 + (1 - (distanceFromCenter / maxDistance)) * 100; // 100-200 range<!DOCTYPE html>
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
  4. hideya created this gist Aug 7, 2025.
    279 changes: 279 additions & 0 deletions SVG-filter-shiny-metal-demo.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,279 @@
    // Calculate Z position based on distance from center (closer to center = higher Z)
    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
    const zPosition = 100 + (1 - (distanceFromCenter / maxDistance)) * 100; // 100-200 range<!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Super Shiny Metal with Dynamic Lighting</title>
    <style>
    body {
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
    background: #1a1a1a;
    min-height: 100vh;
    overflow: hidden;
    cursor: none;
    }

    .container {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    position: relative;
    }

    .metal-surface {
    width: 100px;
    height: 100px;
    background: radial-gradient(ellipse at center, #e8e8e8, #c0c0c0, #a0a0a0);
    border-radius: 15px;
    position: relative;
    filter: url(#ultraShiny);
    box-shadow:
    0 0 20px rgba(255,255,255,0.3),
    inset 0 0 30px rgba(255,255,255,0.1);
    border: 2px solid #999;
    }

    .surface-label {
    position: absolute;
    bottom: -40px;
    left: 50%;
    transform: translateX(-50%);
    color: #ccc;
    font-size: 16px;
    font-weight: bold;
    text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
    text-align: center;
    white-space: nowrap;
    }

    .light-cursor {
    position: fixed;
    width: 40px;
    height: 40px;
    background: radial-gradient(circle,
    #ffffff 0%,
    #ffff99 20%,
    #ffcc00 50%,
    transparent 70%);
    border-radius: 50%;
    pointer-events: none;
    z-index: 1000;
    transform: translate(-50%, -50%);
    box-shadow:
    0 0 30px #ffff00,
    0 0 60px #ffff00,
    0 0 90px #ffaa00;
    animation: pulse 2s ease-in-out infinite alternate;
    }

    @keyframes pulse {
    0% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0.8;
    }
    100% {
    transform: translate(-50%, -50%) scale(1.2);
    opacity: 1;
    }
    }

    .info-panel {
    position: fixed;
    top: 20px;
    left: 20px;
    background: rgba(0,0,0,0.8);
    color: white;
    padding: 20px;
    border-radius: 10px;
    font-size: 14px;
    border: 1px solid #444;
    max-width: 300px;
    }

    .info-panel h3 {
    margin: 0 0 10px 0;
    color: #ffdd00;
    }

    .coord-display {
    position: fixed;
    bottom: 20px;
    right: 20px;
    background: rgba(0,0,0,0.8);
    color: #00ff00;
    padding: 15px;
    border-radius: 10px;
    font-family: 'Courier New', monospace;
    border: 1px solid #444;
    }
    </style>
    </head>
    <body>
    <div class="container">
    <!-- Custom light cursor -->
    <div class="light-cursor" id="lightCursor"></div>

    <!-- SVG Filter for ultra-shiny metal -->
    <svg width="0" height="0" style="position: absolute;">
    <defs>
    <filter id="ultraShiny" x="-100%" y="-100%" width="300%" height="300%">
    <!-- Create fine surface texture for smooth metallic finish -->
    <feTurbulence type="fractalNoise" baseFrequency="0.3" numOctaves="2" result="fineBumps"/>

    <!-- Convert to height map -->
    <feColorMatrix type="saturate" values="0" in="fineBumps" result="heightMap"/>

    <!-- Smooth out the surface for minimal texture -->
    <feGaussianBlur stdDeviation="1" in="heightMap" result="smoothHeight"/>

    <!-- Create ultra-strong diffuse lighting -->
    <feDiffuseLighting in="enhancedHeight"
    lighting-color="white"
    surfaceScale="8"
    diffuseConstant="1.5"
    result="diffuse">
    <fePointLight id="mainLight" x="300" y="200" z="150"/>
    </feDiffuseLighting>

    <!-- Create extremely strong specular highlights -->
    <feSpecularLighting in="enhancedHeight"
    lighting-color="#ffffff"
    surfaceScale="12"
    specularConstant="3.5"
    specularExponent="35"
    result="specular">
    <fePointLight x="300" y="200" z="150"/>
    </feSpecularLighting>

    <!-- Create secondary specular for extra shine -->
    <feSpecularLighting in="enhancedHeight"
    lighting-color="#ffffcc"
    surfaceScale="15"
    specularConstant="2.8"
    specularExponent="50"
    result="specular2">
    <fePointLight x="300" y="200" z="120"/>
    </feSpecularLighting>

    <!-- Combine specular highlights -->
    <feComposite in="specular" in2="specular2" operator="screen" result="combinedSpecular"/>

    <!-- Blend everything with the original surface -->
    <feComposite in="combinedSpecular" in2="SourceGraphic" operator="arithmetic"
    k1="0" k2="1" k3="2.5" k4="0" result="shinyMetal"/>
    <feComposite in="shinyMetal" in2="diffuse" operator="multiply" result="litMetal"/>

    <!-- Final brightness and contrast boost -->
    <feComponentTransfer in="litMetal">
    <feFuncR type="gamma" amplitude="1.2" exponent="0.8" offset="0.1"/>
    <feFuncG type="gamma" amplitude="1.2" exponent="0.8" offset="0.1"/>
    <feFuncB type="gamma" amplitude="1.2" exponent="0.8" offset="0.1"/>
    </feComponentTransfer>
    </filter>
    </defs>
    </svg>

    <div class="metal-surface">
    <div class="surface-label">Ultra-Shiny Chrome Metal</div>
    </div>

    <div class="info-panel">
    <h3>Super Shiny Metal Demo</h3>
    <p><strong>Surface Scale:</strong> 3-5 (high but controlled)</p>
    <p><strong>Specular Constant:</strong> 1.2-1.8 (strong shininess)</p>
    <p><strong>Specular Exponent:</strong> 40-60 (sharp highlights)</p>
    <p><strong>Surface Texture:</strong> Smooth metallic finish with fine detail</p>
    <p><strong>Lighting:</strong> Dual specular layers for ultra-bright reflections</p>
    <br>
    <p><em>Move your mouse to see the light reflections dance across the bumpy metal surface!</em></p>
    </div>

    <div class="coord-display" id="coordDisplay">
    Light Position: (0, 0)
    Azimuth: 0°
    Elevation: 45°
    </div>
    </div>

    <script>
    const lightCursor = document.getElementById('lightCursor');
    const coordDisplay = document.getElementById('coordDisplay');
    const mainLight = document.getElementById('mainLight');

    // Get all point lights in the filter
    const pointLights = document.querySelectorAll('fePointLight');

    let mouseX = window.innerWidth / 2;
    let mouseY = window.innerHeight / 2;

    // Update lighting based on mouse position
    document.addEventListener('mousemove', (e) => {
    mouseX = e.clientX;
    mouseY = e.clientY;

    updateLighting();
    });

    function updateLighting() {
    // Update cursor position
    lightCursor.style.left = mouseX + 'px';
    lightCursor.style.top = mouseY + 'px';

    // Get metal surface bounds for relative positioning
    const metalSurface = document.querySelector('.metal-surface');
    const rect = metalSurface.getBoundingClientRect();

    // Calculate relative position within the metal surface (0-600 x 0-400)
    const relativeX = ((mouseX - rect.left) / rect.width) * 600;
    const relativeY = ((mouseY - rect.top) / rect.height) * 400;

    // Calculate Z position based on distance from center (closer to center = higher Z)
    const centerX = 300;
    const centerY = 200;
    const distanceFromCenter = Math.sqrt(
    Math.pow(relativeX - centerX, 2) + Math.pow(relativeY - centerY, 2)
    );
    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
    const zPosition = 100 + (1 - (distanceFromCenter / maxDistance)) * 100; // 100-200 range

    // Update all point lights
    pointLights.forEach((light, index) => {
    const offset = index * 20; // Slight offset for multiple lights
    light.setAttribute('x', Math.max(0, Math.min(600, relativeX + offset)));
    light.setAttribute('y', Math.max(0, Math.min(400, relativeY + offset)));
    light.setAttribute('z', zPosition + offset);
    });

    // Calculate angles for display
    const azimuth = Math.atan2(relativeY - centerY, relativeX - centerX) * 180 / Math.PI;
    const elevation = Math.atan2(zPosition, distanceFromCenter) * 180 / Math.PI;

    // Update coordinate display
    coordDisplay.innerHTML = `
    Light Position: (${Math.round(relativeX)}, ${Math.round(relativeY)})
    Z-Height: ${Math.round(zPosition)}
    Azimuth: ${Math.round(azimuth)}°
    Elevation: ${Math.round(elevation)}°
    `;
    }

    // Initialize lighting
    updateLighting();

    // Add some subtle animation to the light cursor
    let time = 0;
    function animateLight() {
    time += 0.02;
    const pulse = Math.sin(time) * 0.1 + 1;
    lightCursor.style.filter = `brightness(${pulse})`;
    requestAnimationFrame(animateLight);
    }
    animateLight();
    </script>
    </body>
    </html>