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 { GameList } from "./GameList";
|
||||||
import { GameFilter } from "./GameFilter";
|
import { GameFilter } from "./GameFilter";
|
||||||
import { GameDetails } from "./GameDetails";
|
import { GameDetails } from "./GameDetails";
|
||||||
|
import { ShaderBackground } from "./ShaderBackground";
|
||||||
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
|
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { apiFetch } from "./api";
|
import { apiFetch } from "./api";
|
||||||
@ -15,6 +16,15 @@ function App() {
|
|||||||
const [token, setToken] = useState<string>(
|
const [token, setToken] = useState<string>(
|
||||||
localStorage.getItem("token") || ""
|
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 = () => {
|
const fetchPeople = () => {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
@ -66,7 +76,15 @@ function App() {
|
|||||||
<button onClick={handleLogout} className="btn-secondary">
|
<button onClick={handleLogout} className="btn-secondary">
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsShaderTheme(!isShaderTheme)}
|
||||||
|
className="btn-secondary"
|
||||||
|
style={{ marginLeft: "1rem" }}
|
||||||
|
>
|
||||||
|
{isShaderTheme ? "Normal Theme" : "Shader Theme"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{isShaderTheme && <ShaderBackground />}
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<PersonList people={people} />} />
|
<Route path="/" element={<PersonList people={people} />} />
|
||||||
<Route path="/games" element={<GameList />} />
|
<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;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
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