WIP
Try adding black hole background
This commit is contained in:
parent
59b23586bc
commit
4e91349a59
@ -6,6 +6,7 @@ import { PersonDetails } from "./PersonDetails";
|
||||
import { GameList } from "./GameList";
|
||||
import { GameFilter } from "./GameFilter";
|
||||
import { GameDetails } from "./GameDetails";
|
||||
import { ShaderBackground } from "./ShaderBackground";
|
||||
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
|
||||
import "./App.css";
|
||||
import { apiFetch } from "./api";
|
||||
@ -15,6 +16,15 @@ function App() {
|
||||
const [token, setToken] = useState<string>(
|
||||
localStorage.getItem("token") || ""
|
||||
);
|
||||
const [isShaderTheme, setIsShaderTheme] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isShaderTheme) {
|
||||
document.body.classList.add("shader-theme");
|
||||
} else {
|
||||
document.body.classList.remove("shader-theme");
|
||||
}
|
||||
}, [isShaderTheme]);
|
||||
|
||||
const fetchPeople = () => {
|
||||
if (!token) return;
|
||||
@ -66,7 +76,15 @@ function App() {
|
||||
<button onClick={handleLogout} className="btn-secondary">
|
||||
Logout
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsShaderTheme(!isShaderTheme)}
|
||||
className="btn-secondary"
|
||||
style={{ marginLeft: "1rem" }}
|
||||
>
|
||||
{isShaderTheme ? "Normal Theme" : "Shader Theme"}
|
||||
</button>
|
||||
</div>
|
||||
{isShaderTheme && <ShaderBackground />}
|
||||
<Routes>
|
||||
<Route path="/" element={<PersonList people={people} />} />
|
||||
<Route path="/games" element={<GameList />} />
|
||||
|
||||
1028
frontend/src/ShaderBackground.tsx
Normal file
1028
frontend/src/ShaderBackground.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -88,7 +88,21 @@ input:focus, select:focus {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.shader-theme {
|
||||
--primary-bg: transparent; /* Let the shader show through */
|
||||
--secondary-bg: rgba(0, 0, 0, 0.7); /* Translucent cards */
|
||||
--secondary-alt-bg: rgba(20, 20, 20, 0.6); /* Translucent inputs */
|
||||
--tertiary-bg: rgba(40, 40, 40, 0.8);
|
||||
--border-color: rgba(255, 255, 255, 0.15);
|
||||
--text-color: #ffffff;
|
||||
}
|
||||
|
||||
.shader-theme body {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
93
frontend/src/randomTextureForShader.ts
Normal file
93
frontend/src/randomTextureForShader.ts
Normal file
@ -0,0 +1,93 @@
|
||||
export function generateRandomRGBABytes(
|
||||
width: number,
|
||||
height: number
|
||||
): Uint8Array {
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new Error("width and height must be positive");
|
||||
}
|
||||
const len = width * height * 4;
|
||||
const out = new Uint8Array(len);
|
||||
|
||||
// Maximum bytes per getRandomValues call (per spec / browsers)
|
||||
const MAX_GETRANDOM_BYTES = 65536;
|
||||
|
||||
if (typeof crypto !== "undefined" && "getRandomValues" in crypto) {
|
||||
// Fill in chunks of up to MAX_GETRANDOM_BYTES
|
||||
let offset = 0;
|
||||
while (offset < len) {
|
||||
const chunkSize = Math.min(MAX_GETRANDOM_BYTES, len - offset);
|
||||
// Subarray view for the current chunk
|
||||
const chunkView = out.subarray(offset, offset + chunkSize);
|
||||
crypto.getRandomValues(chunkView);
|
||||
offset += chunkSize;
|
||||
}
|
||||
// Ensure alpha channel is fully opaque (255)
|
||||
for (let i = 3; i < len; i += 4) out[i] = 255;
|
||||
} else {
|
||||
// Fallback to Math.random for all bytes
|
||||
for (let i = 0; i < len; i++) {
|
||||
if ((i + 1) % 4 === 0) out[i] = 255;
|
||||
else out[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a WebGL texture from random data and bind it to texture unit 0,
|
||||
* then set the sampler2D uniform named `iChannel0` to use unit 0.
|
||||
*
|
||||
* gl: WebGLRenderingContext or WebGL2RenderingContext
|
||||
* program: compiled & linked shader program (must be in use or will be used)
|
||||
*/
|
||||
export function createAndBindRandomTextureToIChannel0(
|
||||
gl: WebGLRenderingContext | WebGL2RenderingContext,
|
||||
program: WebGLProgram,
|
||||
width: number,
|
||||
height: number
|
||||
): WebGLTexture {
|
||||
const bytes = generateRandomRGBABytes(width, height);
|
||||
|
||||
const tex = gl.createTexture();
|
||||
if (!tex) throw new Error("Failed to create texture");
|
||||
|
||||
// Activate texture unit 0 and bind the texture
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
|
||||
// Texture parameters suitable for data textures
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
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);
|
||||
|
||||
// Upload pixel data as RGBA UNSIGNED_BYTE
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.texImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.RGBA,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
bytes
|
||||
);
|
||||
|
||||
const location = gl.getUniformLocation(program, "iChannel0");
|
||||
if (location === null) {
|
||||
// Clean up binding and return but signal with thrown error if desired
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
throw new Error("Uniform 'iChannel0' not found in program");
|
||||
}
|
||||
|
||||
// Set sampler uniform to texture unit 0
|
||||
gl.uniform1i(location, 0);
|
||||
|
||||
// Unbind the texture if you prefer (not strictly necessary)
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
|
||||
return tex;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user