replace brand logo with pixel shader animation
This commit is contained in:
@@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user