replace brand logo with pixel shader animation

This commit is contained in:
2025-06-13 21:08:12 +02:00
parent 5e8b15f928
commit 4e99bb16bc

View File

@@ -11,6 +11,348 @@ header.bg-white.text-nls-black(class="dark:text-white dark:bg-nls-black")
|
| & #{landingpage.jobTitle[1]}
img.absolute.z-0.left-0.right-0.mx-auto.transition.invisible(class="dark:visible h-[54vh]", src=landingpage.logoSvg)
canvas#aurora-canvas.absolute.z-0.left-0.right-0.mx-auto.transition(class="h-[54vh] w-full")
img.absolute.z-0.left-0.right-0.mx-auto.transition.visible(class="dark:invisible h-[54vh]", src=landingpage.logoSvgInverted)
script.
class QuantumAurora {
constructor(canvas) {
this.canvas = canvas;
this.gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
this.time = 0;
this.animationId = null;
this.mouseX = 0.5;
this.mouseY = 0.5;
if (!this.gl) {
console.warn('WebGL not supported');
return;
}
this.initShaders();
this.initBuffers();
this.setupInteraction();
this.resize();
this.render();
}
initShaders() {
const vertexShaderSource = `
attribute vec2 a_position;
varying vec2 v_uv;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_uv = a_position * 0.5 + 0.5;
}
`;
const fragmentShaderSource = `
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
varying vec2 v_uv;
// Noise functions for organic aurora movement
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for(int i = 0; i < 6; i++) {
value += amplitude * noise(p * frequency);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
// Magnetic field simulation
vec2 magneticField(vec2 pos, float time) {
vec2 field = vec2(0.0);
// Earth's magnetic field approximation
float lat = (pos.y - 0.5) * 3.14159;
float lon = (pos.x - 0.5) * 6.28318;
// Magnetic dipole field with solar wind interaction
float solarWind = sin(time * 0.1 + pos.x * 2.0) * 0.3 + 0.7;
float magneticStrength = cos(lat) * solarWind;
field.x = sin(lat * 2.0 + time * 0.2) * magneticStrength;
field.y = cos(lat * 3.0 + time * 0.15) * magneticStrength;
return field * 0.5;
}
// Particle trajectory simulation
float particleFlow(vec2 uv, float time) {
vec2 pos = uv;
float intensity = 0.0;
// Simulate charged particle streams
for(int i = 0; i < 8; i++) {
float fi = float(i);
vec2 offset = vec2(sin(fi * 0.5), cos(fi * 0.7)) * 0.1;
vec2 particlePos = pos + offset;
// Particle follows magnetic field lines
vec2 field = magneticField(particlePos, time + fi);
particlePos += field * 0.3;
// Altitude-dependent intensity (atmosphere interaction)
float altitude = 1.0 - abs(particlePos.y - 0.5) * 2.0;
altitude = smoothstep(0.2, 0.8, altitude);
// Particle stream noise
float streamNoise = fbm(particlePos * 8.0 + vec2(time * 0.3, 0.0));
intensity += altitude * streamNoise * 0.125;
}
return intensity;
}
// Guilloche pattern generation
float guilloche(vec2 uv, float time) {
vec2 pos = uv * 20.0;
// Multiple rotating circles creating interference patterns
float g = 0.0;
// Primary guilloche rings
for(int i = 0; i < 4; i++) {
float fi = float(i);
float radius = 3.0 + fi * 2.0;
float speed = 0.5 + fi * 0.2;
float phase = fi * 1.5708; // π/2 phase offset
vec2 center = vec2(
sin(time * speed + phase) * 2.0,
cos(time * speed * 0.7 + phase) * 1.5
);
float dist = length(pos - center);
g += sin(dist * radius - time * (speed * 10.0 + 5.0)) / (radius * 0.5);
}
// Secondary interference pattern
float interference = sin(pos.x * 3.0 + time) * sin(pos.y * 2.0 + time * 1.3);
g += interference * 0.3;
return g;
}
// Aurora curtain effect
vec3 auroraColor(vec2 uv, float time) {
// Particle flow intensity
float particles = particleFlow(uv, time);
// Guilloche modulation
float guillochePattern = guilloche(uv, time * 0.5);
float guillocheModulation = sin(guillochePattern * 0.5) * 0.5 + 0.5;
// Combine particle physics with guilloche aesthetics
float intensity = particles * (0.7 + guillocheModulation * 0.6);
// Atmospheric layers - different altitudes emit different colors
float altitude = uv.y;
// Oxygen emissions (green/red) - 557.7nm, 630.0nm
vec3 oxygen_green = vec3(0.3, 0.9, 0.2);
vec3 oxygen_red = vec3(0.8, 0.2, 0.1);
// Nitrogen emissions (blue/purple) - 427.8nm, 391.4nm
vec3 nitrogen_blue = vec3(0.2, 0.4, 0.9);
vec3 nitrogen_purple = vec3(0.6, 0.2, 0.8);
// Helium (rare, yellow) - 587.6nm
vec3 helium_yellow = vec3(0.9, 0.8, 0.2);
// Altitude-based color mixing (realistic atmospheric chemistry)
vec3 color = vec3(0.0);
// High altitude (200-400km) - mostly oxygen red
float highAlt = smoothstep(0.7, 0.9, altitude);
color += oxygen_red * highAlt;
// Mid altitude (100-200km) - oxygen green dominates
float midAlt = smoothstep(0.3, 0.7, altitude) * smoothstep(0.7, 0.5, altitude);
color += oxygen_green * midAlt * 2.0;
// Lower altitude (80-100km) - nitrogen blue/purple
float lowAlt = smoothstep(0.0, 0.4, altitude) * smoothstep(0.6, 0.3, altitude);
color += mix(nitrogen_blue, nitrogen_purple, sin(time * 0.3 + uv.x * 5.0) * 0.5 + 0.5) * lowAlt;
// Rare helium emissions (very sparse)
float heliumChance = noise(uv * 100.0 + time * 0.1);
if(heliumChance > 0.95) {
color += helium_yellow * 0.3;
}
// Solar wind interaction - color temperature shift
float solarActivity = sin(time * 0.05) * 0.3 + 0.7;
color *= solarActivity;
// Magnetic field strength affects brightness
vec2 magneticF = magneticField(uv, time);
float magneticIntensity = length(magneticF);
return color * intensity * (0.5 + magneticIntensity);
}
// Atmospheric scattering simulation
vec3 atmosphericScattering(vec3 auroraColor, vec2 uv) {
float atmosphereDensity = exp(-abs(uv.y - 0.5) * 8.0);
// Rayleigh scattering (blue light scattered more)
vec3 rayleigh = vec3(0.1, 0.2, 0.6) * atmosphereDensity * 0.1;
// Mie scattering (dust/ice crystals)
vec3 mie = vec3(0.8, 0.8, 0.9) * atmosphereDensity * 0.05;
return auroraColor + rayleigh + mie;
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
// Mouse interaction - simulates solar wind direction
vec2 solarWind = (u_mouse - 0.5) * 0.5;
uv += solarWind * 0.1;
// Generate aurora
vec3 aurora = auroraColor(uv, u_time);
// Apply atmospheric effects
aurora = atmosphericScattering(aurora, uv);
// Subtle animation shimmer
float shimmer = sin(u_time * 2.0 + uv.x * 10.0 + uv.y * 15.0) * 0.05 + 0.95;
aurora *= shimmer;
// Transparency based on intensity
float alpha = length(aurora) * 0.8;
alpha = smoothstep(0.0, 0.3, alpha);
gl_FragColor = vec4(aurora, alpha);
}
`;
this.program = this.createProgram(vertexShaderSource, fragmentShaderSource);
this.gl.useProgram(this.program);
this.u_time = this.gl.getUniformLocation(this.program, 'u_time');
this.u_resolution = this.gl.getUniformLocation(this.program, 'u_resolution');
this.u_mouse = this.gl.getUniformLocation(this.program, 'u_mouse');
}
createProgram(vertexSource, fragmentSource) {
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
const program = this.gl.createProgram();
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
console.error('Program link error:', this.gl.getProgramInfoLog(program));
return null;
}
return program;
}
createShader(type, source) {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
console.error('Shader compile error:', this.gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
initBuffers() {
const vertices = new Float32Array([-1,-1, 1,-1, -1,1, 1,1]);
this.buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
const a_position = this.gl.getAttribLocation(this.program, 'a_position');
this.gl.enableVertexAttribArray(a_position);
this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 0, 0);
}
setupInteraction() {
this.canvas.addEventListener('mousemove', (e) => {
const rect = this.canvas.getBoundingClientRect();
this.mouseX = e.clientX / rect.width;
this.mouseY = 1.0 - (e.clientY / rect.height);
});
}
resize() {
const rect = this.canvas.getBoundingClientRect();
//this.canvas.width = rect.width * window.devicePixelRatio;
//this.canvas.height = rect.height * window.devicePixelRatio;
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.gl.uniform2f(this.u_resolution, this.canvas.width, this.canvas.height);
}
render() {
if (!this.gl) return;
this.time += 0.016;
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
this.gl.clearColor(0, 0, 0, 0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.uniform1f(this.u_time, this.time);
this.gl.uniform2f(this.u_mouse, this.mouseX, this.mouseY);
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
this.animationId = requestAnimationFrame(() => this.render());
}
destroy() {
if (this.animationId) cancelAnimationFrame(this.animationId);
}
}
document.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById('aurora-canvas');
if (canvas) {
const aurora = new QuantumAurora(canvas);
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => aurora.resize(), 100);
});
window.addEventListener('beforeunload', () => aurora.destroy());
}
});