diff --git a/website/src/components/layout/site/site-layout.tsx b/website/src/components/layout/site/site-layout.tsx index 910851eb5d5..192c4cc917b 100644 --- a/website/src/components/layout/site/site-layout.tsx +++ b/website/src/components/layout/site/site-layout.tsx @@ -29,27 +29,36 @@ function Stars(): ReactElement { return; } - const canvas = canvasRef.current; - const ctx = canvas.getContext("2d"); + const glCanvas = canvasRef.current; + const gl = glCanvas.getContext("webgl", { + alpha: true, + premultipliedAlpha: false, + }); + + if (!gl) { + return; + } + + const starCanvas = document.createElement("canvas"); + const ctx = starCanvas.getContext("2d"); + + if (!ctx) { + return; + } + const numStars = 800; const speed = 0.25; let stars: Star[] = []; - let pointerX = 0; - let pointerY = 0; - let pointerTargetX = 0; - let pointerTargetY = 0; - let scrollTiltY = 0; - let scrollTargetY = 0; - let scrollAccum = 0; - let lastScrollTop = 0; - let scrollIdleTimer: number | null = null; - const pointerMax = 15; - const scrollMax = 60; - const scrollRampDistance = 400; function setCanvasSize() { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; + const w = window.innerWidth; + const h = window.innerHeight; + + glCanvas.width = w; + glCanvas.height = h; + starCanvas.width = w; + starCanvas.height = h; + gl!.viewport(0, 0, w, h); } class Star { @@ -68,26 +77,22 @@ function Stars(): ReactElement { } reset() { - this.z = canvas.width; - this.x = Math.random() * (canvas.width * 2) - canvas.width; - this.y = Math.random() * (canvas.height * 2) - canvas.height; + this.z = starCanvas.width; + this.x = Math.random() * (starCanvas.width * 2) - starCanvas.width; + this.y = Math.random() * (starCanvas.height * 2) - starCanvas.height; this.size = Math.random() * 2 + 1; } draw() { const x = - (((this.x + pointerX) / this.z) * canvas.width) / 2 + - canvas.width / 2; + ((this.x / this.z) * starCanvas.width) / 2 + starCanvas.width / 2; const y = - (((this.y + pointerY + scrollTiltY) / this.z) * canvas.height) / 2 + - canvas.height / 2; - const radius = (1 - this.z / canvas.width) * this.size; - - if (ctx) { - ctx.beginPath(); - ctx.arc(x, y, radius, 0, Math.PI * 2); - ctx.fill(); - } + ((this.y / this.z) * starCanvas.height) / 2 + starCanvas.height / 2; + const radius = (1 - this.z / starCanvas.width) * this.size; + + ctx!.beginPath(); + ctx!.arc(x, y, radius, 0, Math.PI * 2); + ctx!.fill(); } } @@ -96,68 +101,189 @@ function Stars(): ReactElement { { length: numStars }, () => new Star( - Math.random() * (canvas.width * 2) - canvas.width, - Math.random() * (canvas.height * 2) - canvas.height, - Math.random() * canvas.width, + Math.random() * (starCanvas.width * 2) - starCanvas.width, + Math.random() * (starCanvas.height * 2) - starCanvas.height, + Math.random() * starCanvas.width, Math.random() * 2 + 1 ) ); } - function updateStars() { - stars.forEach((star) => star.update()); - pointerX += (pointerTargetX - pointerX) * 0.08; - pointerY += (pointerTargetY - pointerY) * 0.08; - const scrollEase = scrollTargetY === 0 ? 0.009 : 0.08; - scrollTiltY += (scrollTargetY - scrollTiltY) * scrollEase; - } + const vertSrc = ` + attribute vec2 aPos; + varying vec2 vUv; + void main() { + vUv = (aPos + 1.0) * 0.5; + gl_Position = vec4(aPos, 0.0, 1.0); + } + `; - function handlePointerMove(e: PointerEvent) { - const nx = (e.clientX / window.innerWidth) * 2 - 1; - const ny = (e.clientY / window.innerHeight) * 2 - 1; - pointerTargetX = nx * pointerMax; - pointerTargetY = ny * pointerMax; - } + const noiseHelpers = ` + float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); + } - function handleScroll(e: Event) { - const target = e.target; - const scrollTop = - target instanceof HTMLElement ? target.scrollTop : window.scrollY; - const delta = scrollTop - lastScrollTop; - lastScrollTop = scrollTop; + float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + vec2 u = f * f * (3.0 - 2.0 * f); + return mix( + mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x), + mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), + u.y + ); + } - if (delta !== 0 && Math.sign(delta) !== Math.sign(scrollAccum)) { - scrollAccum = 0; + float fbm(vec2 p) { + float v = 0.0; + float a = 0.5; + for (int i = 0; i < 5; i++) { + v += a * noise(p); + p *= 2.0; + a *= 0.5; + } + return v; } - scrollAccum = Math.max( - -scrollRampDistance, - Math.min(scrollRampDistance, scrollAccum + delta) - ); - const t = Math.abs(scrollAccum) / scrollRampDistance; - const eased = t * t * (3 - 2 * t); - scrollTargetY = Math.sign(scrollAccum) * eased * scrollMax; - if (scrollIdleTimer) { - window.clearTimeout(scrollIdleTimer); + float fbm3(vec2 p) { + float v = 0.0; + float a = 0.5; + for (int i = 0; i < 3; i++) { + v += a * noise(p); + p *= 2.0; + a *= 0.5; + } + return v; } + `; - scrollIdleTimer = window.setTimeout(() => { - scrollTargetY = 0; - scrollAccum = 0; - }, 150); - } + const fragSrc = ` + precision mediump float; + uniform vec2 u_resolution; + uniform sampler2D u_texture; + uniform float u_time; + varying vec2 vUv; + + ${noiseHelpers} - function drawStars() { - if (ctx) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = "#f4ebcb"; - stars.forEach((star) => star.draw()); + void main() { + vec2 st = vUv; + float aspect = u_resolution.x / u_resolution.y; + + vec4 stars = texture2D(u_texture, st); + + vec2 nebUv = st * vec2(aspect, 1.0); + float t = u_time; + float n1 = fbm(nebUv * 2.2 + vec2(t * 0.012, t * 0.008)); + float n2 = fbm3(nebUv * 5.5 + vec2(31.7 - t * 0.018, 11.3 + t * 0.010)); + float n3 = fbm3(nebUv * 3.8 + vec2(-7.9 + t * 0.014, 22.5 - t * 0.009)); + float n4 = fbm3(nebUv * 4.3 + vec2(54.1 + t * 0.007, -18.7 + t * 0.016)); + float n5 = fbm3(nebUv * 6.1 + vec2(-23.9 - t * 0.013, 47.2 - t * 0.006)); + float n6 = fbm3(nebUv * 2.8 + vec2(67.4 + t * 0.005, 3.6 + t * 0.011)); + float n7 = fbm3(nebUv * 4.8 + vec2(-41.2 + t * 0.009, -62.5 - t * 0.013)); + float n8 = fbm3(nebUv * 5.0 + vec2(88.6 - t * 0.011, 29.4 + t * 0.015)); + + vec3 deep = vec3(0.10, 0.07, 0.22); + vec3 violet = vec3(0.35, 0.22, 0.60); + vec3 blueGrey = vec3(0.30, 0.45, 0.80); + vec3 coral = vec3(0.95, 0.65, 0.55); + vec3 magenta = vec3(0.85, 0.32, 0.65); + vec3 teal = vec3(0.15, 0.68, 0.78); + vec3 amber = vec3(0.95, 0.75, 0.35); + vec3 red = vec3(0.92, 0.26, 0.30); + vec3 yellow = vec3(0.98, 0.88, 0.35); + + vec3 nebulaColor = deep; + nebulaColor = mix(nebulaColor, violet, smoothstep(0.30, 0.80, n1)); + nebulaColor = mix(nebulaColor, blueGrey, smoothstep(0.55, 0.90, n2) * 0.65); + nebulaColor = mix(nebulaColor, teal, smoothstep(0.62, 0.92, n5) * 0.55); + nebulaColor = mix(nebulaColor, magenta, smoothstep(0.68, 0.92, n4) * 0.45); + nebulaColor = mix(nebulaColor, coral, smoothstep(0.72, 0.95, n3) * 0.40); + nebulaColor = mix(nebulaColor, red, smoothstep(0.78, 0.95, n7) * 0.40); + nebulaColor = mix(nebulaColor, amber, smoothstep(0.78, 0.96, n6) * 0.30); + nebulaColor = mix(nebulaColor, yellow, smoothstep(0.82, 0.97, n8) * 0.35); + + float nebulaAlpha = 0.20 + n1 * 0.38; + + float starsA = smoothstep(0.02, 0.5, stars.a); + vec3 preRgb = + stars.rgb * starsA + + nebulaColor * nebulaAlpha * (1.0 - starsA); + float outAlpha = starsA + nebulaAlpha * (1.0 - starsA); + gl_FragColor = vec4(preRgb, outAlpha); } + `; + + function compile(type: number, src: string): WebGLShader { + const sh = gl!.createShader(type)!; + gl!.shaderSource(sh, src); + gl!.compileShader(sh); + return sh; } + const vs = compile(gl.VERTEX_SHADER, vertSrc); + const fs = compile(gl.FRAGMENT_SHADER, fragSrc); + const program = gl.createProgram()!; + + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + gl.useProgram(program); + + const vbo = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vbo); + + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), + gl.STATIC_DRAW + ); + + const aPosLoc = gl.getAttribLocation(program, "aPos"); + + gl.enableVertexAttribArray(aPosLoc); + gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, 0, 0); + + const uResLoc = gl.getUniformLocation(program, "u_resolution"); + const uTexLoc = gl.getUniformLocation(program, "u_texture"); + const uTimeLoc = gl.getUniformLocation(program, "u_time"); + const startTime = performance.now(); + const tex = gl.createTexture(); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, tex); + 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); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.uniform1i(uTexLoc, 0); + + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.clearColor(0, 0, 0, 0); + function animate() { - updateStars(); - drawStars(); + ctx!.clearRect(0, 0, starCanvas.width, starCanvas.height); + ctx!.fillStyle = "#f4ebcb"; + stars.forEach((s) => s.update()); + stars.forEach((s) => s.draw()); + + gl!.bindTexture(gl!.TEXTURE_2D, tex); + gl!.texImage2D( + gl!.TEXTURE_2D, + 0, + gl!.RGBA, + gl!.RGBA, + gl!.UNSIGNED_BYTE, + starCanvas + ); + + gl!.clear(gl!.COLOR_BUFFER_BIT); + gl!.uniform2f(uResLoc, glCanvas.width, glCanvas.height); + gl!.uniform1f(uTimeLoc, (performance.now() - startTime) / 1000); + gl!.drawArrays(gl!.TRIANGLES, 0, 6); + requestAnimationFrame(animate); } @@ -165,13 +291,6 @@ function Stars(): ReactElement { setCanvasSize(); initStars(); }); - window.addEventListener("pointermove", handlePointerMove, { - passive: true, - }); - window.addEventListener("scroll", handleScroll, { - capture: true, - passive: true, - }); setCanvasSize(); initStars(); diff --git a/website/src/components/widgets/companies-section.tsx b/website/src/components/widgets/companies-section.tsx index 0fea5dbc902..b43dbc1b8a2 100644 --- a/website/src/components/widgets/companies-section.tsx +++ b/website/src/components/widgets/companies-section.tsx @@ -47,8 +47,6 @@ import ZioskLogoSvg from "@/images/companies/ziosk.svg"; export const CompaniesSection: FC = () => ( - - {[ { @@ -278,6 +276,19 @@ const VisibleArea = styled.div` width: 100%; height: 100%; max-width: ${MAX_CONTENT_WIDTH}px; + mask-image: linear-gradient( + 90deg, + rgba(0, 0, 0, 0) 0%, + rgb(0, 0, 0) 15%, + rgb(0, 0, 0) 85%, + rgba(0, 0, 0, 0) 100% + ); + mask-clip: border-box; + mask-composite: add; + mask-mode: match-source; + mask-origin: border-box; + mask-repeat: repeat; + mask-size: auto; `; interface TickerLogo { @@ -354,33 +365,3 @@ const GenericLogo = styled.div<{ width?: number }>` } } `; - -const FadeOut = styled.div` - position: absolute; - top: 0; - bottom: 0; - left: 0; - z-index: 1; - width: 120px; - background: linear-gradient( - 270deg, - #ffffff00 0%, - ${THEME_COLORS.background} 100% - ); - pointer-events: none; -`; - -const FadeIn = styled.div` - position: absolute; - top: 0; - bottom: 0; - right: 0; - z-index: 1; - width: 120px; - background: linear-gradient( - 90deg, - #ffffff00 0%, - ${THEME_COLORS.background} 100% - ); - pointer-events: none; -`; diff --git a/website/src/images/startpage/fusion.svg b/website/src/images/startpage/fusion.svg index b26ff15451a..f773291640b 100644 --- a/website/src/images/startpage/fusion.svg +++ b/website/src/images/startpage/fusion.svg @@ -48,7 +48,27 @@ + + + + + + + + + + + + + + + + + + + + @@ -196,38 +216,38 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + +