feat: use webgl shaders instead of wasm

This commit is contained in:
Jet 2026-03-27 15:08:34 -07:00
parent 38efffa9b9
commit 51dc6c9ee6
No known key found for this signature in database
24 changed files with 1712 additions and 1607 deletions

View file

@ -1,13 +1,9 @@
export function frostedBox(content: string, extraClasses?: string): string {
return `
<div class="site-shell relative my-[2ch] overflow-hidden px-[2ch] py-[2ch] ${extraClasses ?? ""}">
<div
class="pointer-events-none absolute inset-0 h-[200%]"
style="background-color: rgba(0, 0, 0, 0.75); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%); -webkit-mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%);"
aria-hidden="true"
></div>
<div class="absolute inset-0 border-2 border-white" aria-hidden="true"></div>
<div class="relative z-10 h-full min-h-0">
<div class="site-shell site-panel-frame relative my-[2ch] overflow-hidden px-[2ch] py-[2ch] ${extraClasses ?? ""}">
<div class="site-panel-frost pointer-events-none" aria-hidden="true"></div>
<div class="site-panel-border" aria-hidden="true"></div>
<div class="site-panel-content h-full min-h-0">
${content}
</div>
</div>`;

View file

@ -1,78 +0,0 @@
const MOTION_QUERY = "(prefers-reduced-motion: reduce)";
const STILL_STEPS = 5;
type BackgroundMode = "animated" | "still" | "failed";
interface BackgroundActions {
start: () => void;
stop: () => void;
renderStill: (steps: number) => void;
}
function getMode(reducedMotion: boolean): BackgroundMode {
return reducedMotion ? "still" : "animated";
}
function applyCanvasState(mode: BackgroundMode) {
const canvas = document.getElementById("canvas");
document.body.dataset.backgroundMode = mode;
if (canvas) {
canvas.toggleAttribute("hidden", mode === "failed");
}
}
export function initBackgroundControls(actions: BackgroundActions) {
const media = window.matchMedia(MOTION_QUERY);
let mode: BackgroundMode = getMode(media.matches);
let failed = false;
const applyMode = (restartAnimation = false) => {
if (failed) return;
mode = getMode(media.matches);
applyCanvasState(mode);
if (mode === "animated") {
if (restartAnimation) {
actions.stop();
}
actions.start();
return;
}
actions.stop();
actions.renderStill(STILL_STEPS);
};
const restartAnimation = () => {
if (document.visibilityState === "hidden" || media.matches) {
return;
}
applyMode(true);
};
media.addEventListener("change", () => {
applyMode();
});
document.addEventListener("visibilitychange", () => {
restartAnimation();
});
window.addEventListener("pageshow", () => {
restartAnimation();
});
return {
applyInitialMode() {
applyMode();
},
setFailed() {
failed = true;
mode = "failed";
applyCanvasState(mode);
},
};
}

View file

@ -28,8 +28,10 @@ export function renderFooter() {
const mirror = getMirrorLink();
footer.innerHTML = `
<div class="site-panel px-[2ch] py-[1ch]">
<div class="site-footer-inner">
<div class="site-panel-frame px-[2ch] py-[1ch]">
<div class="site-panel-frost" aria-hidden="true"></div>
<div class="site-panel-border" aria-hidden="true"></div>
<div class="site-panel-content site-footer-inner">
<a href="${REPO_URL}">src</a>
<span aria-hidden="true">|</span>
<a href="/qa/rss.xml" data-native-link>rss</a>

1515
src/lib/webgl-background.ts Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
import "~/styles/globals.css";
import init, { render_still, start, stop } from "cgol";
import { route, initRouter } from "~/router";
import { initBackgroundControls } from "~/lib/background";
import { initWebGLBackground } from "~/lib/webgl-background";
import { renderFooter } from "~/lib/site";
import { homePage } from "~/pages/home";
import { qaPage } from "~/pages/qa";
@ -12,24 +11,5 @@ route("/qa", "Jet Pham - Q+A", qaPage);
route("*", "404 - Jet Pham", notFoundPage);
renderFooter();
const background = initBackgroundControls({
start() {
start();
},
stop() {
stop();
},
renderStill(steps) {
render_still(steps);
},
});
try {
await init();
background.applyInitialMode();
} catch (e) {
background.setFailed();
console.error("WASM init failed:", e);
}
initWebGLBackground();
initRouter();

View file

@ -6,30 +6,31 @@ export function homePage(outlet: HTMLElement) {
outlet.innerHTML = `
<div class="flex flex-col items-center justify-start">
${frostedBox(`
<div class="flex flex-col items-center justify-center gap-[2ch] md:flex-row">
<div class="flex flex-col items-center justify-center gap-[1.25ch] md:gap-[2ch] md:flex-row">
<div class="order-1 flex flex-col items-center md:order-2">
<h1 class="sr-only">Jet Pham</h1>
<div aria-hidden="true">${Jet}</div>
<div aria-hidden="true" data-emitter-ansi>${Jet}</div>
<p class="mt-[2ch]">Software Extremist</p>
</div>
<div class="order-2 shrink-0 md:order-1">
<img
data-emitter-image
src="/jet.svg"
alt="A picture of Jet wearing a beanie in purple and blue lighting"
width="250"
height="250"
class="aspect-square w-full max-w-[250px] object-cover md:h-[263px] md:w-[175px] md:max-w-none"
class="aspect-square w-full max-w-[220px] object-cover md:h-[263px] md:w-[175px] md:max-w-none"
style="background-color: #a80055; color: transparent"
/>
</div>
</div>
<fieldset class="section-block mt-[2ch] border-2 border-white px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0">
<legend class="-mx-[0.5ch] px-[0.5ch] text-white">Contact</legend>
<fieldset class="section-block mt-[2ch] border-2 px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0" style="border-color: var(--white)">
<legend class="-mx-[0.5ch] px-[0.5ch]" style="color: var(--white)">Contact</legend>
<button type="button" id="copy-email" class="qa-inline-action">jet@extremist.software</button>
<span id="copy-email-status" class="qa-meta ml-[1ch]" aria-live="polite"></span>
</fieldset>
<fieldset class="section-block mt-[2ch] border-2 border-white px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0">
<legend class="-mx-[0.5ch] px-[0.5ch] text-white">Links</legend>
<fieldset class="section-block mt-[2ch] border-2 px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0" style="border-color: var(--white)">
<legend class="-mx-[0.5ch] px-[0.5ch]" style="color: var(--white)">Links</legend>
<ol>
<li><a href="https://git.extremist.software" class="inline-flex items-center">Forgejo</a></li>
<li><a href="https://github.com/jetpham" class="inline-flex items-center">GitHub</a></li>

View file

@ -61,6 +61,7 @@ async function render() {
outlet.innerHTML = "";
await r.handler(outlet, params);
document.title = r.title;
document.dispatchEvent(new Event("site:render"));
return;
}
}
@ -70,6 +71,7 @@ async function render() {
await notFoundHandler(outlet, {});
}
document.title = notFoundTitle;
document.dispatchEvent(new Event("site:render"));
}
export function initRouter() {

View file

@ -33,6 +33,14 @@
--light-magenta: #ff55ff;
--yellow: #ffff55;
--white: #ffffff;
--panel-bg-alpha: 0.2;
--panel-gloss-alpha: 0.05;
--panel-border-inset: 0.6rem;
--panel-bg: rgba(0, 0, 0, var(--panel-bg-alpha));
--panel-blur: 10px;
--fallback-glow-a: rgba(85, 255, 255, 0.18);
--fallback-glow-b: rgba(85, 85, 255, 0.16);
--fallback-glow-c: rgba(255, 85, 85, 0.14);
}
html {
@ -43,6 +51,8 @@ html {
body {
min-height: 100vh;
min-height: 100dvh;
position: relative;
isolation: isolate;
margin: 0;
overflow: hidden;
background-color: var(--black);
@ -53,6 +63,38 @@ body {
line-height: 1;
}
body::before {
content: "";
position: fixed;
inset: -20vmax;
z-index: -30;
background: var(--black);
opacity: 0;
transform: translate3d(0, 0, 0) scale(1);
pointer-events: none;
}
body[data-background-mode="failed"]::before {
opacity: 1;
animation: fallback-drift 18s linear infinite alternate;
}
@media (prefers-reduced-motion: reduce) {
body[data-background-mode="failed"]::before {
animation: none;
}
}
@keyframes fallback-drift {
from {
transform: translate3d(-3%, -2%, 0) scale(1);
}
to {
transform: translate3d(3%, 2%, 0) scale(1.12);
}
}
::selection {
background-color: var(--light-blue);
color: var(--black);
@ -98,20 +140,49 @@ body {
}
.site-shell {
width: min(100%, 60%);
width: 100%;
box-sizing: border-box;
margin: 0 auto;
user-select: text;
}
.site-panel {
border: 2px solid var(--white);
background-color: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
@media (min-width: 768px) {
.site-shell {
width: min(100%, 60%);
}
}
.site-panel-frame {
position: relative;
overflow: hidden;
}
.site-panel-frost {
position: absolute;
inset: var(--panel-border-inset);
pointer-events: none;
overflow: hidden;
background:
linear-gradient(
to bottom,
rgba(255, 255, 255, var(--panel-gloss-alpha)),
transparent 24%
),
var(--panel-bg);
}
.site-panel-border {
position: absolute;
inset: var(--panel-border-inset);
border: 2px solid var(--white);
pointer-events: none;
}
.site-panel-content {
position: relative;
z-index: 1;
}
.site-region {
width: 100%;
}