Files
triqura-ecd/components/ui/dot-shader-background.tsx
2025-11-15 22:06:48 +01:00

191 lines
5.3 KiB
TypeScript

'use client'
import { useMemo, useEffect } from 'react'
import { Canvas, ThreeEvent, useFrame, useThree } from '@react-three/fiber'
import { shaderMaterial, useTrailTexture } from '@react-three/drei'
import { useTheme } from 'next-themes' // lub twój provider
import * as THREE from 'three'
const DotMaterial = shaderMaterial(
{
time: 0,
resolution: new THREE.Vector2(),
dotColor: new THREE.Color('#FFFFFF'),
bgColor: new THREE.Color('#121212'),
mouseTrail: null,
render: 0,
rotation: 0,
gridSize: 50,
dotOpacity: 0.05
},
/* glsl */ `
void main() {
gl_Position = vec4(position.xy, 0.0, 1.0);
}
`,
/* glsl */ `
uniform float time;
uniform int render;
uniform vec2 resolution;
uniform vec3 dotColor;
uniform vec3 bgColor;
uniform sampler2D mouseTrail;
uniform float rotation;
uniform float gridSize;
uniform float dotOpacity;
vec2 rotate(vec2 uv, float angle) {
float s = sin(angle);
float c = cos(angle);
mat2 rotationMatrix = mat2(c, -s, s, c);
return rotationMatrix * (uv - 0.5) + 0.5;
}
vec2 coverUv(vec2 uv) {
vec2 s = resolution.xy / max(resolution.x, resolution.y);
vec2 newUv = (uv - 0.5) * s + 0.5;
return clamp(newUv, 0.0, 1.0);
}
float sdfCircle(vec2 p, float r) {
return length(p - 0.5) - r;
}
void main() {
vec2 screenUv = gl_FragCoord.xy / resolution;
vec2 uv = coverUv(screenUv);
vec2 rotatedUv = rotate(uv, rotation);
// Create a grid
vec2 gridUv = fract(rotatedUv * gridSize);
vec2 gridUvCenterInScreenCoords = rotate((floor(rotatedUv * gridSize) + 0.5) / gridSize, -rotation);
// Calculate distance from the center of each cell
float baseDot = sdfCircle(gridUv, 0.25);
// Screen mask
float screenMask = smoothstep(0.0, 1.0, 1.0 - uv.y); // 0 at the top, 1 at the bottom
vec2 centerDisplace = vec2(0.7, 1.1);
float circleMaskCenter = length(uv - centerDisplace);
float circleMaskFromCenter = smoothstep(0.5, 1.0, circleMaskCenter);
float combinedMask = screenMask * circleMaskFromCenter;
float circleAnimatedMask = sin(time * 2.0 + circleMaskCenter * 10.0);
// Mouse trail effect
float mouseInfluence = texture2D(mouseTrail, gridUvCenterInScreenCoords).r;
float scaleInfluence = max(mouseInfluence * 0.5, circleAnimatedMask * 0.3);
// Create dots with animated scale, influenced by mouse
float dotSize = min(pow(circleMaskCenter, 2.0) * 0.3, 0.3);
float sdfDot = sdfCircle(gridUv, dotSize * (1.0 + scaleInfluence * 0.5));
float smoothDot = smoothstep(0.05, 0.0, sdfDot);
float opacityInfluence = max(mouseInfluence * 50.0, circleAnimatedMask * 0.5);
// Mix background color with dot color, using animated opacity to increase visibility
vec3 composition = mix(bgColor, dotColor, smoothDot * combinedMask * dotOpacity * (1.0 + opacityInfluence));
gl_FragColor = vec4(composition, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`
)
function Scene() {
const size = useThree((s) => s.size)
const viewport = useThree((s) => s.viewport)
const { theme } = useTheme()
const rotation = 0
const gridSize = 100
const getThemeColors = () => {
switch (theme) {
case 'dark':
return {
dotColor: '#FFFFFF',
bgColor: '#121212',
dotOpacity: 0.025
}
case 'light':
return {
dotColor: '#e1e1e1',
bgColor: '#F4F5F5',
dotOpacity: 0.15
}
default:
return {
dotColor: '#FFFFFF',
bgColor: '#121212',
dotOpacity: 0.05
}
}
}
const themeColors = getThemeColors()
const [trail, onMove] = useTrailTexture({
size: 512,
radius: 0.1,
maxAge: 400,
interpolate: 1,
ease: function easeInOutCirc(x) {
return x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2
}
})
const dotMaterial = useMemo(() => {
return new DotMaterial()
}, [])
useEffect(() => {
dotMaterial.uniforms.dotColor.value.setHex(themeColors.dotColor.replace('#', '0x'))
dotMaterial.uniforms.bgColor.value.setHex(themeColors.bgColor.replace('#', '0x'))
dotMaterial.uniforms.dotOpacity.value = themeColors.dotOpacity
}, [theme, dotMaterial, themeColors])
useFrame((state) => {
dotMaterial.uniforms.time.value = state.clock.elapsedTime
})
const handlePointerMove = (e: ThreeEvent<PointerEvent>) => {
onMove(e)
}
const scale = Math.max(viewport.width, viewport.height) / 2
return (
<mesh scale={[scale, scale, 1]} onPointerMove={handlePointerMove}>
<planeGeometry args={[2, 2]} />
<primitive
object={dotMaterial}
resolution={[size.width * viewport.dpr, size.height * viewport.dpr]}
rotation={rotation}
gridSize={gridSize}
mouseTrail={trail}
render={0}
/>
</mesh>
)
}
export const DotScreenShader = () => {
return (
<Canvas
gl={{
antialias: true,
powerPreference: 'high-performance',
outputColorSpace: THREE.SRGBColorSpace,
toneMapping: THREE.NoToneMapping
}}>
<Scene />
</Canvas>
)
}