diff --git a/app/security/__pycache__/__init__.cpython-311.pyc b/app/security/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1d332e2 Binary files /dev/null and b/app/security/__pycache__/__init__.cpython-311.pyc differ diff --git a/app/security/__pycache__/security.cpython-311.pyc b/app/security/__pycache__/security.cpython-311.pyc new file mode 100644 index 0000000..c5c0e6c Binary files /dev/null and b/app/security/__pycache__/security.cpython-311.pyc differ diff --git a/app/security/__pycache__/security.cpython-312.pyc b/app/security/__pycache__/security.cpython-312.pyc index 478ebf9..e262439 100644 Binary files a/app/security/__pycache__/security.cpython-312.pyc and b/app/security/__pycache__/security.cpython-312.pyc differ diff --git a/app/security/security.py b/app/security/security.py index 932b287..e48dfa1 100644 --- a/app/security/security.py +++ b/app/security/security.py @@ -15,4 +15,10 @@ async def browser_local_cryptography_strength(request: Request): async def pigeonhole_principle_simulation(request: Request): return templates.TemplateResponse( request=request, name="pigeonhole.html" + ) + +@security_router.get("/noise-letter") +async def noise_letter_visualization(request: Request): + return templates.TemplateResponse( + request=request, name="noise_letter.html" ) \ No newline at end of file diff --git a/app/static/js/noise_letter.js b/app/static/js/noise_letter.js new file mode 100644 index 0000000..a97fe7d --- /dev/null +++ b/app/static/js/noise_letter.js @@ -0,0 +1,180 @@ + +async function loadFile(url) { + const res = await fetch(url); + return res.text(); +} + +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('Shader compile error:', gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); + return null; + } + return shader; +} + +function createTexture(gl) { + const tex = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, tex); + // Set parameters so we can handle non-power-of-2 images if needed + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + return tex; +} + + +function updateCharTexture(gl, texture, text) { + const height = 256; + // Dynamic width based on text length, min 1 + const chars = Math.max(1, text.length); + const width = 256 * chars; + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = 'white'; + ctx.font = 'bold 200px monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(text, width / 2, height / 2); + + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); + + return chars; // Return aspect ratio (width/height) +} + +async function main() { + const canvas = document.getElementById('glcanvas'); + const gl = canvas.getContext('webgl'); + + if (!gl) { + alert('WebGL not supported'); + return; + } + + // Load Shaders + const vsSource = await loadFile('/static/shaders/vertex.glsl'); + // Using simple Cache Busting for reloading shader during dev if needed, but not strictly necessary here + const fsSource = await loadFile('/static/shaders/fragment.glsl'); + + const vs = compileShader(gl, vsSource, gl.VERTEX_SHADER); + const fs = compileShader(gl, fsSource, gl.FRAGMENT_SHADER); + + const program = gl.createProgram(); + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + console.error('Program link error:', gl.getProgramInfoLog(program)); + return; + } + + gl.useProgram(program); + + // Quad Buffer + const verts = 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, verts, gl.STATIC_DRAW); + + const posLoc = gl.getAttribLocation(program, 'position'); + gl.enableVertexAttribArray(posLoc); + gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0); + + // Uniforms + const uTime = gl.getUniformLocation(program, 'u_time'); + const uRes = gl.getUniformLocation(program, 'u_resolution'); + const uCharPos = gl.getUniformLocation(program, 'u_charPos'); + const uCharSize = gl.getUniformLocation(program, 'u_charSize'); + // Texture uniform usually defaults to 0, which is fine + + // State + const charTexture = createTexture(gl); + + let charX = 0.5; + let charY = 0.5; + let charW = 0.5; + let charH = 0.5; + + let currentAspect = 1.0; + + // UI Handling + const input = document.getElementById('textInput'); + const btn = document.getElementById('renderBtn'); + + function updateText() { + // Use placeholder or "A" if empty to avoid 0 size + let text = input.value.toUpperCase().substring(0, 12); + if (text.length === 0) return; + + currentAspect = updateCharTexture(gl, charTexture, text); + resize(); // Recalculate size + } + + btn.addEventListener('click', updateText); + + // Initial Text + currentAspect = updateCharTexture(gl, charTexture, "HELLO"); + + function resize() { + // Look up container size + const container = canvas.parentElement; + canvas.width = container.clientWidth; + canvas.height = container.clientHeight; + gl.viewport(0, 0, canvas.width, canvas.height); + + // Adjust aspect ratio + const screenAspect = canvas.width / canvas.height; + + // Target: charW / charH = currentAspect / screenAspect + + // Try filling 60% of height + charH = 0.6; + charW = charH * currentAspect / screenAspect; + + // Constrain width to 95% + if (charW > 0.95) { + charW = 0.95; + charH = charW * screenAspect / currentAspect; + } + + charX = 0.5 - charW / 2; + charY = 0.5 - charH / 2; + } + window.addEventListener('resize', resize); + resize(); + + function render(time) { + time *= 0.001; // Seconds + + gl.uniform1f(uTime, time); + gl.uniform2f(uRes, canvas.width, canvas.height); + + gl.uniform2f(uCharPos, charX, charY); // uniform needs bottom-left + gl.uniform2f(uCharSize, charW, charH); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + requestAnimationFrame(render); + } + requestAnimationFrame(render); +} + +main(); diff --git a/app/static/shaders/fragment.glsl b/app/static/shaders/fragment.glsl new file mode 100644 index 0000000..c475fa6 --- /dev/null +++ b/app/static/shaders/fragment.glsl @@ -0,0 +1,58 @@ +precision mediump float; + +uniform float u_time; +uniform vec2 u_resolution; +uniform sampler2D u_charTexture; +uniform vec2 u_charPos; // Normalized 0-1 (Bottom-Left) +uniform vec2 u_charSize; // Normalized 0-1 (Width, Height) + +varying vec2 vUv; + +// reliable pseudo-random hash +float random(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * .1031); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +void main() { + // Correct UV for aspect ratio if needed, but here we work in screen space mostly + + // Background Noise + // Use floor to ensure we are sampling integer grid, making it purely pixel-noise + vec2 gridPos = floor(gl_FragCoord.xy); + float bgNoise = random(gridPos); + float val = step(0.5, bgNoise); + + // Character Composition + vec2 charEnd = u_charPos + u_charSize; + bool inBox = (vUv.x >= u_charPos.x && vUv.x <= charEnd.x && + vUv.y >= u_charPos.y && vUv.y <= charEnd.y); + + if (inBox) { + vec2 texUv = (vUv - u_charPos) / u_charSize; + texUv.y = 1.0 - texUv.y; + + vec4 texColor = texture2D(u_charTexture, texUv); + + // Hard threshold to avoid any smooth edges revealing the shape + if (texColor.a > 0.5) { + // Inner Character Noise - Scrolls Down + float scrollSpeed = 200.0; + // Calculate scrolled position + vec2 scrollCoords = gl_FragCoord.xy; + scrollCoords.y += u_time * scrollSpeed; + + // IMPORTANT: Floor the scrolled coordinates! + // This ensures the noise "pixels" look exactly like the background "pixels" in size and sharpness. + // If we don't floor, we get sub-pixel sampling which might look different (smooth/shimmering). + vec2 charGridPos = floor(scrollCoords); + + // Use different seed offset to distinguish content, but same distribution + float charNoise = random(charGridPos + vec2(42.0, 99.0)); + val = step(0.5, charNoise); + } + } + + gl_FragColor = vec4(vec3(1.0 - val), 1.0); +} diff --git a/app/static/shaders/vertex.glsl b/app/static/shaders/vertex.glsl new file mode 100644 index 0000000..48262e1 --- /dev/null +++ b/app/static/shaders/vertex.glsl @@ -0,0 +1,7 @@ +attribute vec2 position; +varying vec2 vUv; + +void main() { + vUv = position * 0.5 + 0.5; + gl_Position = vec4(position, 0.0, 1.0); +} diff --git a/app/templates/noise_letter.html b/app/templates/noise_letter.html new file mode 100644 index 0000000..ad80d66 --- /dev/null +++ b/app/templates/noise_letter.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} + +{% block title %}Noise Letter{% endblock %} + +{% block css %} + +{% endblock %} + +{% block content %} +
+
+ + +
+
+ +
+

Visualizing text as noise signal

+
+{% endblock %} + +{% block js %} + +{% endblock %} \ No newline at end of file