This commit is contained in:
2025-12-09 21:46:46 +01:00
parent 19ec048d72
commit edb1aadf04
8 changed files with 315 additions and 0 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -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"
)

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -0,0 +1,64 @@
{% extends "base.html" %}
{% block title %}Noise Letter{% endblock %}
{% block css %}
<style>
.noise-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 80vh;
background-color: white;
color: black;
font-family: monospace;
padding: 20px;
margin-top: 20px;
}
#controls {
margin-bottom: 20px;
display: flex;
gap: 10px;
width: 100%;
max-width: 600px;
justify-content: center;
}
#container {
width: 100%;
max-width: 1200px;
height: 400px;
border: 1px solid #ccc;
background-color: white;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
</style>
{% endblock %}
{% block content %}
<div class="noise-wrapper rounded-lg">
<div id="controls">
<input type="text" id="textInput" maxlength="12" placeholder="ENTER TEXT (MAX 12)"
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 placeholder-gray-400">
<button id="renderBtn"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
RENDER
</button>
</div>
<div id="container">
<canvas id="glcanvas"></canvas>
</div>
<p class="mt-4 text-gray-600 text-sm">Visualizing text as noise signal</p>
</div>
{% endblock %}
{% block js %}
<script src="{{ url_for('static', path='js/noise_letter.js') }}"></script>
{% endblock %}