Add three.js dependency in package.json and bun.lock, implement particle background in main.tsx, and update SASS and HTML templates for background integration.

This commit is contained in:
Hananoshika Yomaru 2026-05-19 02:05:58 -07:00
parent 3f4a7a9e73
commit ee1ecbfb27
20 changed files with 38477 additions and 2 deletions

View file

@ -7,6 +7,7 @@
"dependencies": {
"katex": "^0.16.47",
"solid-js": "^1.9.5",
"three": "^0.184.0",
},
"devDependencies": {
"typescript": "^5.8.3",
@ -250,6 +251,8 @@
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"three": ["three@0.184.0", "", {}, "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg=="],
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

View file

@ -0,0 +1,489 @@
import {
AdditiveBlending,
BufferAttribute,
BufferGeometry,
Clock,
Color,
NormalBlending,
PerspectiveCamera,
Plane,
Points,
Raycaster,
Scene,
ShaderMaterial,
Vector2,
Vector3,
WebGLRenderer,
} from "three";
import { resolveTheme, type ThemeChoice } from "../theme";
const DESKTOP_COUNT = 60000;
const MOBILE_COUNT = 20000;
const SHAPE_COUNT = 4;
const MORPH_MAX = 4;
const MORPH_LOOP_EPS = 0.001;
const SHAPE_SCALE = 0.8;
type ShapeColors = [number, number, number];
type ThemePalette = {
layers: ShapeColors;
sphere: ShapeColors;
cube: ShapeColors;
helix: ShapeColors;
clear: string;
glow: number;
particleAlpha: number;
colorBoost: number;
pointScale: number;
colorJitter: number;
};
const LIGHT_PALETTE: ThemePalette = {
layers: [0.44, 0.28, 0.05],
sphere: [0.58, 0.34, 0.02],
cube: [0.03, 0.34, 0.16],
helix: [0.34, 0.44, 0.06],
clear: "#f5f4f1",
glow: 0.72,
particleAlpha: 1,
colorBoost: 1.45,
pointScale: 1.35,
colorJitter: 0.04,
};
const DARK_PALETTE: ThemePalette = {
layers: [0.82, 0.7, 0.48],
sphere: [0.85, 0.58, 0.22],
cube: [0.28, 0.78, 0.46],
helix: [0.62, 0.82, 0.34],
clear: "#1a1917",
glow: 0.82,
particleAlpha: 0.78,
colorBoost: 1,
pointScale: 1,
colorJitter: 0.12,
};
function prefersReducedMotion() {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function isMobileTier() {
return window.matchMedia("(max-width: 768px)").matches;
}
function hasFinePointer() {
return window.matchMedia("(pointer: fine)").matches;
}
function getPalette(theme: ThemeChoice): ThemePalette {
return theme === "dark" ? DARK_PALETTE : LIGHT_PALETTE;
}
function getPaperColor(): Color {
const value = getComputedStyle(document.documentElement).getPropertyValue("--paper").trim();
return new Color(value || LIGHT_PALETTE.clear);
}
function setColor(arr: Float32Array, index: number, rgb: ShapeColors, jitter = 0.12) {
arr[index * 3] = rgb[0] + (Math.random() - 0.5) * jitter;
arr[index * 3 + 1] = rgb[1] + (Math.random() - 0.5) * jitter;
arr[index * 3 + 2] = rgb[2] + (Math.random() - 0.5) * jitter;
}
function scalePositions(positions: Float32Array, scale: number) {
for (let i = 0; i < positions.length; i += 1) {
positions[i] *= scale;
}
}
function generateLayers(count: number, positions: Float32Array, colors: Float32Array, rgb: ShapeColors) {
const layerCount = 14;
for (let i = 0; i < count; i += 1) {
const layerIndex = Math.floor(Math.random() * layerCount);
const layerY = (layerIndex / (layerCount - 1)) * 12 - 6;
positions[i * 3] = (Math.random() - 0.5) * 9;
positions[i * 3 + 1] = layerY + (Math.random() - 0.5) * 0.18;
positions[i * 3 + 2] = (Math.random() - 0.5) * 6;
setColor(colors, i, rgb, 0.06);
}
scalePositions(positions, SHAPE_SCALE);
}
function generateSphere(count: number, positions: Float32Array, colors: Float32Array, rgb: ShapeColors) {
for (let i = 0; i < count; i += 1) {
const phi = Math.acos(-1 + (2 * i) / count);
const theta = Math.sqrt(count * Math.PI) * phi;
const radius = 5 + (Math.random() - 0.5) * 0.5;
positions[i * 3] = radius * Math.cos(theta) * Math.sin(phi);
positions[i * 3 + 1] = radius * Math.sin(theta) * Math.sin(phi);
positions[i * 3 + 2] = radius * Math.cos(phi);
setColor(colors, i, rgb, 0.1);
}
scalePositions(positions, SHAPE_SCALE);
}
function generateCube(count: number, positions: Float32Array, colors: Float32Array, rgb: ShapeColors) {
for (let i = 0; i < count; i += 1) {
let x = (Math.random() - 0.5) * 10;
let y = (Math.random() - 0.5) * 10;
let z = (Math.random() - 0.5) * 10;
const axis = Math.floor(Math.random() * 3);
const sign = Math.random() > 0.5 ? 1 : -1;
if (axis === 0) x = 5 * sign;
else if (axis === 1) y = 5 * sign;
else z = 5 * sign;
positions[i * 3] = x;
positions[i * 3 + 1] = y;
positions[i * 3 + 2] = z;
setColor(colors, i, rgb, 0.1);
}
scalePositions(positions, SHAPE_SCALE);
}
function generateHelix(count: number, positions: Float32Array, colors: Float32Array, rgb: ShapeColors) {
for (let i = 0; i < count; i += 1) {
const t = (i / count) * Math.PI * 20;
const radius = 3;
const offset = (Math.random() - 0.5) * 1.5;
const arm = i % 2 === 0 ? 0 : Math.PI;
positions[i * 3] = (radius + offset) * Math.cos(t + arm);
positions[i * 3 + 1] = (t / (Math.PI * 20)) * 14 - 7;
positions[i * 3 + 2] = (radius + offset) * Math.sin(t + arm);
setColor(colors, i, rgb, 0.1);
}
scalePositions(positions, SHAPE_SCALE);
}
function fillShapeColors(
geometry: BufferGeometry,
palette: ThemePalette,
count: number
) {
const col0 = geometry.getAttribute("col0") as BufferAttribute;
const col1 = geometry.getAttribute("col1") as BufferAttribute;
const col2 = geometry.getAttribute("col2") as BufferAttribute;
const col3 = geometry.getAttribute("col3") as BufferAttribute;
for (let i = 0; i < count; i += 1) {
setColor(col0.array as Float32Array, i, palette.layers, palette.colorJitter);
setColor(col1.array as Float32Array, i, palette.sphere, palette.colorJitter);
setColor(col2.array as Float32Array, i, palette.cube, palette.colorJitter);
setColor(col3.array as Float32Array, i, palette.helix, palette.colorJitter);
}
col0.needsUpdate = true;
col1.needsUpdate = true;
col2.needsUpdate = true;
col3.needsUpdate = true;
}
const vertexShader = `
attribute vec3 pos0;
attribute vec3 col0;
attribute vec3 pos1;
attribute vec3 col1;
attribute vec3 pos2;
attribute vec3 col2;
attribute vec3 pos3;
attribute vec3 col3;
uniform float uMorph;
uniform float uTime;
uniform vec3 uMouse;
uniform float uPixelRatio;
uniform float uMouseEnabled;
uniform float uGlow;
uniform float uPointScale;
varying vec3 vColor;
varying float vAlpha;
void main() {
float m = mod(uMorph, ${SHAPE_COUNT}.0);
float t = fract(m);
float ease = t < 0.5 ? 4.0 * t * t * t : 1.0 - pow(-2.0 * t + 2.0, 3.0) / 2.0;
vec3 finalPos;
vec3 finalCol;
if (m < 1.0) {
finalPos = mix(pos0, pos1, ease);
finalCol = mix(col0, col1, ease);
} else if (m < 2.0) {
finalPos = mix(pos1, pos2, ease);
finalCol = mix(col1, col2, ease);
} else if (m < 3.0) {
finalPos = mix(pos2, pos3, ease);
finalCol = mix(col2, col3, ease);
} else {
finalPos = mix(pos3, pos0, ease);
finalCol = mix(col3, col0, ease);
}
float sphereInfluence = max(0.0, 1.0 - abs(m - 1.0));
if (sphereInfluence > 0.0) {
float breathing = sin(uTime * 1.5) * 0.5;
float shockTime = fract(uTime * 0.15);
float shockRadius = shockTime * 20.0;
float distFromCenter = length(finalPos);
float shock = smoothstep(2.0, 0.0, abs(distFromCenter - shockRadius));
vec3 dir = normalize(finalPos + vec3(0.0001));
finalPos += dir * (breathing + shock * 4.0) * sphereInfluence;
finalCol += vec3(0.4, 0.7, 1.0) * shock * sphereInfluence * uGlow;
}
if (uMouseEnabled > 0.5) {
float mouseDist = distance(finalPos, uMouse);
float radius = 3.5;
if (mouseDist < radius) {
vec3 pushDir = normalize(finalPos - uMouse);
float force = pow(1.0 - (mouseDist / radius), 2.0);
finalPos += pushDir * force * 1.5;
}
}
finalPos.y += sin(finalPos.x * 2.0 + uTime * 2.0) * 0.1;
finalPos.x += cos(finalPos.y * 2.0 + uTime * 2.0) * 0.1;
finalPos.z += sin(finalPos.x * 1.5 + finalPos.y * 1.5 + uTime * 1.6) * 0.06;
vec4 mvPosition = modelViewMatrix * vec4(finalPos, 1.0);
gl_PointSize = (13.0 * uPixelRatio * uPointScale) * (1.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
vColor = finalCol;
vAlpha = 1.0;
}
`;
const fragmentShader = `
uniform float uParticleAlpha;
uniform float uColorBoost;
varying vec3 vColor;
varying float vAlpha;
void main() {
vec2 center = vec2(0.5, 0.5);
float dist = distance(gl_PointCoord, center);
if (dist > 0.5) discard;
float alpha = smoothstep(0.5, 0.08, dist);
vec3 color = clamp(vColor * uColorBoost, 0.0, 1.0);
gl_FragColor = vec4(color, alpha * uParticleAlpha * vAlpha);
}
`;
function clampMorph(morph: number) {
if (morph <= 0) return 0;
if (morph >= MORPH_MAX - MORPH_LOOP_EPS) return MORPH_MAX - MORPH_LOOP_EPS;
return morph;
}
function getLayersHold(morph: number) {
const clamped = clampMorph(morph);
if (clamped <= MORPH_LOOP_EPS || clamped >= MORPH_MAX - MORPH_LOOP_EPS) {
return 1;
}
const m = clamped % SHAPE_COUNT;
if (m < 0.5) return 1 - m / 0.5;
if (m > 3.5) return (SHAPE_COUNT - m) / 0.5;
return 0;
}
export function initParticleBackground(container: HTMLElement): () => void {
if (prefersReducedMotion()) {
return () => {};
}
document.body.classList.add("has-particle-background");
const particleCount = isMobileTier() ? MOBILE_COUNT : DESKTOP_COUNT;
const mouseEnabled = hasFinePointer();
const palette = getPalette(resolveTheme());
const scene = new Scene();
const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 10;
const renderer = new WebGLRenderer({
alpha: false,
antialias: false,
powerPreference: "high-performance",
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
container.appendChild(renderer.domElement);
const geometry = new BufferGeometry();
const pos0 = new Float32Array(particleCount * 3);
const col0 = new Float32Array(particleCount * 3);
const pos1 = new Float32Array(particleCount * 3);
const col1 = new Float32Array(particleCount * 3);
const pos2 = new Float32Array(particleCount * 3);
const col2 = new Float32Array(particleCount * 3);
const pos3 = new Float32Array(particleCount * 3);
const col3 = new Float32Array(particleCount * 3);
generateLayers(particleCount, pos0, col0, palette.layers);
generateSphere(particleCount, pos1, col1, palette.sphere);
generateCube(particleCount, pos2, col2, palette.cube);
generateHelix(particleCount, pos3, col3, palette.helix);
geometry.setAttribute("pos0", new BufferAttribute(pos0, 3));
geometry.setAttribute("col0", new BufferAttribute(col0, 3));
geometry.setAttribute("pos1", new BufferAttribute(pos1, 3));
geometry.setAttribute("col1", new BufferAttribute(col1, 3));
geometry.setAttribute("pos2", new BufferAttribute(pos2, 3));
geometry.setAttribute("col2", new BufferAttribute(col2, 3));
geometry.setAttribute("pos3", new BufferAttribute(pos3, 3));
geometry.setAttribute("col3", new BufferAttribute(col3, 3));
geometry.setAttribute("position", new BufferAttribute(pos0, 3));
const material = new ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uMorph: { value: 0 },
uTime: { value: 0 },
uMouse: { value: new Vector3(999, 999, 999) },
uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) },
uMouseEnabled: { value: mouseEnabled ? 1 : 0 },
uGlow: { value: palette.glow },
uParticleAlpha: { value: palette.particleAlpha },
uColorBoost: { value: palette.colorBoost },
uPointScale: { value: palette.pointScale },
},
transparent: true,
depthWrite: false,
blending: resolveTheme() === "dark" ? AdditiveBlending : NormalBlending,
});
const points = new Points(geometry, material);
scene.add(points);
let targetMorph = 0;
let currentMorph = 0;
let spinY = 0;
let spinX = 0;
const clock = new Clock();
let frameId = 0;
let disposed = false;
const plane = new Plane(new Vector3(0, 0, 1), 0);
const raycaster = new Raycaster();
const mouseNdc = new Vector2(-999, -999);
const targetMouseWorld = new Vector3();
function applyThemePalette(theme: ThemeChoice) {
const nextPalette = getPalette(theme);
fillShapeColors(geometry, nextPalette, particleCount);
material.uniforms.uGlow.value = nextPalette.glow;
material.uniforms.uParticleAlpha.value = nextPalette.particleAlpha;
material.uniforms.uColorBoost.value = nextPalette.colorBoost;
material.uniforms.uPointScale.value = nextPalette.pointScale;
material.blending = theme === "dark" ? AdditiveBlending : NormalBlending;
material.needsUpdate = true;
renderer.setClearColor(getPaperColor(), 1);
}
applyThemePalette(resolveTheme());
function onScroll() {
const maxScroll = document.body.scrollHeight - window.innerHeight;
targetMorph = maxScroll > 0 ? clampMorph((window.scrollY / maxScroll) * MORPH_MAX) : 0;
}
function onMouseMove(event: MouseEvent) {
if (!mouseEnabled) return;
mouseNdc.x = (event.clientX / window.innerWidth) * 2 - 1;
mouseNdc.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
material.uniforms.uPixelRatio.value = Math.min(window.devicePixelRatio, 2);
renderer.setClearColor(getPaperColor(), 1);
}
const themeObserver = new MutationObserver(() => {
applyThemePalette(resolveTheme());
});
const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
const onSystemThemeChange = () => {
applyThemePalette(resolveTheme());
};
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
darkModeQuery.addEventListener("change", onSystemThemeChange);
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("mousemove", onMouseMove, { passive: true });
window.addEventListener("resize", onResize);
onScroll();
function smoothstep(value: number) {
return value * value * (3 - 2 * value);
}
function animate() {
if (disposed) return;
frameId = window.requestAnimationFrame(animate);
const delta = clock.getDelta();
const time = clock.getElapsedTime();
material.uniforms.uTime.value = time;
currentMorph += (targetMorph - currentMorph) * 0.05;
currentMorph = clampMorph(currentMorph);
material.uniforms.uMorph.value = currentMorph;
const layersHold = getLayersHold(currentMorph);
const spinWeight = 1 - layersHold;
if (layersHold < 0.99) {
spinY += delta * 0.1;
spinX += delta * 0.05;
} else {
spinY = 0;
spinX = 0;
}
const easedSpin = smoothstep(spinWeight);
points.rotation.y = spinY * easedSpin;
points.rotation.x = spinX * easedSpin;
if (mouseEnabled) {
raycaster.setFromCamera(mouseNdc, camera);
raycaster.ray.intersectPlane(plane, targetMouseWorld);
const localMouse = targetMouseWorld.clone();
points.worldToLocal(localMouse);
material.uniforms.uMouse.value.lerp(localMouse, 0.1);
}
renderer.setClearColor(getPaperColor(), 1);
renderer.render(scene, camera);
}
animate();
return () => {
disposed = true;
window.cancelAnimationFrame(frameId);
window.removeEventListener("scroll", onScroll);
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("resize", onResize);
darkModeQuery.removeEventListener("change", onSystemThemeChange);
themeObserver.disconnect();
document.body.classList.remove("has-particle-background");
geometry.dispose();
material.dispose();
renderer.dispose();
renderer.domElement.remove();
};
}

View file

@ -59,6 +59,13 @@ if (marqueeRoot) {
render(() => <ScrollRevealBootstrap />, document.body);
render(() => <ContributorsDialogBootstrap />, document.body);
const particleRoot = document.getElementById("webgl-background");
if (particleRoot && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
void import("./lib/particle-background").then(({ initParticleBackground }) => {
initParticleBackground(particleRoot);
});
}
const quadraticRoot = document.getElementById("quadratic-explorer");
if (quadraticRoot) {
void Promise.all([

View file

@ -11,7 +11,8 @@
},
"dependencies": {
"katex": "^0.16.47",
"solid-js": "^1.9.5"
"solid-js": "^1.9.5",
"three": "^0.184.0"
},
"devDependencies": {
"typescript": "^5.8.3",

View file

@ -0,0 +1,61 @@
#webgl-background {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
}
#webgl-background canvas {
display: block;
width: 100%;
height: 100%;
}
body.has-particle-background {
background: transparent;
}
body.has-particle-background main {
position: relative;
z-index: 1;
}
body.has-particle-background main::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--paper) 34%, transparent),
color-mix(in srgb, var(--paper) 56%, transparent) 42%,
color-mix(in srgb, var(--paper) 82%, transparent) 100%
);
}
body.has-particle-background .footer {
position: relative;
z-index: 1;
background: color-mix(in srgb, var(--paper) 94%, transparent);
}
[data-theme="dark"] body.has-particle-background main::before {
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--paper) 48%, transparent),
color-mix(in srgb, var(--paper) 68%, transparent) 42%,
color-mix(in srgb, var(--paper) 88%, transparent) 100%
);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) body.has-particle-background main::before {
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--paper) 48%, transparent),
color-mix(in srgb, var(--paper) 68%, transparent) 42%,
color-mix(in srgb, var(--paper) 88%, transparent) 100%
);
}
}

View file

@ -1,4 +1,5 @@
@import "tokens";
@import "particle-background";
@import "grain";
@import "layout";
@import "components";

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{j as J,t as K,u as N,o as O}from"./bundle.js";var Q=K("<canvas class=hero-ascii-canvas aria-hidden=true>");const W=" .·:;-=+*#%@";function U(d){const e=Math.max(0,Math.min(1,d)),n=Math.floor(e*(W.length-1));return W[n]??" "}function tt(){let d;return J(()=>{const e=d;if(!e)return;const n=e.closest(".hero");if(!n)return;const o=e.getContext("2d",{alpha:!0});if(!o)return;const R=window.matchMedia("(prefers-reduced-motion: reduce)"),k=window.matchMedia("(pointer: coarse)"),C=window.matchMedia("(max-width: 768px)"),S=()=>R.matches||k.matches||C.matches;let u=S()?"static":"animated",p=0,g=0,$=-9999,B=-9999,r=0,c=0,v=0,w=0,x=10,y=6,L=12;const H=()=>{const t=getComputedStyle(n);return{ink:t.getPropertyValue("--ink").trim()||"#1a1a1a",soft:t.getPropertyValue("--soft-ink").trim()||"#555",mono:t.getPropertyValue("--mono").trim()||'"IBM Plex Mono", Menlo, monospace'}};let E=H();const X=()=>{E=H();const t=n.getBoundingClientRect(),a=Math.min(window.devicePixelRatio||1,2);r=t.width,c=t.height,e.width=Math.max(1,Math.floor(r*a)),e.height=Math.max(1,Math.floor(c*a)),e.style.width=`${r}px`,e.style.height=`${c}px`,o.setTransform(a,0,0,a,0,0);const i=S();x=i?12:10,L=x*1.18,y=x*.6,v=Math.max(8,Math.floor(r/y)),w=Math.max(6,Math.floor(c/L)),i&&(v=Math.max(8,Math.floor(v*.7)),w=Math.max(6,Math.floor(w*.7))),o.font=`${x}px ${E.mono}`,o.textBaseline="top"},j=(t,a,i)=>{const l=r*.72,f=c*.48,M=t-l,s=a-f,V=Math.hypot(M,s),q=Math.atan2(s,M)+i*.45,P=Math.sin(V*.09-q*2.8)*.5+.5,A=Math.sin(t*.018+i*.9)*Math.cos(a*.014-i*.65)*.22,m=t/Math.max(r,1),b=m*m*(3-2*m);return(P*.82+A)*(b*.92+.06)},Y=()=>{o.clearRect(0,0,r,c);const t=Math.min(r,c)*.22,a=t*t;for(let i=0;i<w;i+=1)for(let l=0;l<v;l+=1){const f=(l+.5)*y,M=(i+.5)*L;let s=j(f,M,g);if(u==="animated"){const m=f-$,b=M-B,D=Math.exp(-(m*m+b*b)/a);s=Math.min(1,s+D*.42)}if(s<.07)continue;const V=f/Math.max(r,1),q=U(s),P=Math.min(1,V*1.15+s*.25),A=(l*17+i*31+Math.floor(g*5))%113;P>.55&&s>.62&&A<2?(o.fillStyle=A===0?"rgba(180, 60, 70, 0.55)":"rgba(60, 90, 180, 0.5)",o.globalAlpha=1):P>.5?(o.fillStyle=E.ink,o.globalAlpha=.18+s*.72):(o.fillStyle=E.soft,o.globalAlpha=.08+s*.35),o.fillText(q,l*y,i*L),o.globalAlpha=1}},_=()=>{g+=.016,Y(),p=window.requestAnimationFrame(_)},z=()=>{u=S()?"static":"animated",X(),u==="animated"?p=window.requestAnimationFrame(_):(g=0,Y())},F=()=>{p&&(window.cancelAnimationFrame(p),p=0)},G=t=>{if(u!=="animated")return;const a=n.getBoundingClientRect();$=t.clientX-a.left,B=t.clientY-a.top},I=()=>{$=-9999,B=-9999},T=()=>{F(),z()},h=()=>{F(),z()};z(),n.addEventListener("pointermove",G),n.addEventListener("pointerleave",I),window.addEventListener("resize",T),R.addEventListener("change",h),k.addEventListener("change",h),C.addEventListener("change",h),O(()=>{F(),n.removeEventListener("pointermove",G),n.removeEventListener("pointerleave",I),window.removeEventListener("resize",T),R.removeEventListener("change",h),k.removeEventListener("change",h),C.removeEventListener("change",h)})}),(()=>{var e=Q(),n=d;return typeof n=="function"?N(n,e):d=e,e})()}export{tt as HeroAsciiBackground};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -35,6 +35,8 @@
</script>
<div id="site-header"></div>
{% block page_background %}{% endblock page_background %}
<main>
{% block content %}{% endblock content %}
</main>

View file

@ -6,6 +6,10 @@
<meta name="description" content="{{ section.description | default(value=config.description) }}" />
{% endblock meta_description %}
{% block page_background %}
<div id="webgl-background" aria-hidden="true"></div>
{% endblock page_background %}
{% block content %}
<section class="hero">
<div class="container">