Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import streamlit.components.v1 as components | |
| st.title("Mobile-Friendly Shader Demo") | |
| # HTML + JS shader component | |
| html_code = """ | |
| <canvas id="glcanvas"></canvas> | |
| <script type="text/javascript"> | |
| // Setup canvas | |
| const canvas = document.getElementById('glcanvas'); | |
| const gl = canvas.getContext('webgl'); | |
| if (!gl) { alert('WebGL not supported'); } | |
| // Resize canvas to fit screen | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth * window.devicePixelRatio; | |
| canvas.height = window.innerHeight * 0.6 * window.devicePixelRatio; // ~60% height | |
| } | |
| window.addEventListener('resize', resizeCanvas); | |
| resizeCanvas(); | |
| // Vertex shader | |
| const vertCode = ` | |
| attribute vec4 position; | |
| void main() { gl_Position = position; } | |
| `; | |
| // Fragment shader | |
| const fragCode = ` | |
| precision mediump float; | |
| uniform float iTime; | |
| uniform vec2 iMouse; | |
| uniform vec2 iResolution; | |
| void main() { | |
| vec2 uv = gl_FragCoord.xy / iResolution.xy; | |
| vec3 color = vec3( | |
| uv.x + iMouse.x*0.5 + 0.5*sin(iTime), | |
| uv.y + iMouse.y*0.5 + 0.5*cos(iTime), | |
| 0.5 + 0.5*sin(iTime) | |
| ); | |
| gl_FragColor = vec4(color, 1.0); | |
| } | |
| `; | |
| // Shader compile helper | |
| function compileShader(gl, source, type) { | |
| const shader = gl.createShader(type); | |
| gl.shaderSource(shader, source); | |
| gl.compileShader(shader); | |
| if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)){ | |
| console.error(gl.getShaderInfoLog(shader)); | |
| return null; | |
| } | |
| return shader; | |
| } | |
| // Compile shaders | |
| const vertShader = compileShader(gl, vertCode, gl.VERTEX_SHADER); | |
| const fragShader = compileShader(gl, fragCode, gl.FRAGMENT_SHADER); | |
| // Link program | |
| const program = gl.createProgram(); | |
| gl.attachShader(program, vertShader); | |
| gl.attachShader(program, fragShader); | |
| gl.linkProgram(program); | |
| gl.useProgram(program); | |
| // Fullscreen quad | |
| const vertices = new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]); | |
| const buffer = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
| gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); | |
| const position = gl.getAttribLocation(program, "position"); | |
| gl.enableVertexAttribArray(position); | |
| gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0); | |
| // Uniform locations | |
| const iTime = gl.getUniformLocation(program, "iTime"); | |
| const iResolution = gl.getUniformLocation(program, "iResolution"); | |
| const iMouse = gl.getUniformLocation(program, "iMouse"); | |
| // Initial mouse | |
| let mouseX = 0.5; | |
| let mouseY = 0.5; | |
| // Mouse / touch events | |
| canvas.addEventListener('mousemove', e => { | |
| const rect = canvas.getBoundingClientRect(); | |
| mouseX = (e.clientX - rect.left) / rect.width; | |
| mouseY = 1.0 - (e.clientY - rect.top) / rect.height; | |
| }); | |
| canvas.addEventListener('touchmove', e => { | |
| e.preventDefault(); | |
| const rect = canvas.getBoundingClientRect(); | |
| const touch = e.touches[0]; | |
| mouseX = (touch.clientX - rect.left) / rect.width; | |
| mouseY = 1.0 - (touch.clientY - rect.top) / rect.height; | |
| }, {passive:false}); | |
| // Animation loop | |
| function render() { | |
| gl.viewport(0, 0, canvas.width, canvas.height); | |
| gl.uniform2f(iResolution, canvas.width, canvas.height); | |
| gl.uniform2f(iMouse, mouseX, mouseY); | |
| gl.uniform1f(iTime, performance.now() / 1000.0); | |
| gl.drawArrays(gl.TRIANGLES, 0, 6); | |
| requestAnimationFrame(render); | |
| } | |
| render(); | |
| </script> | |
| """ | |
| components.html(html_code, height=400) | |
| st.markdown("**Instructions:** Move your mouse (desktop) or touch (mobile) the canvas to change colors in real time.") | |