├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── README.md ├── app.vue ├── assets ├── shader │ ├── baseFragment.glsl │ ├── baseVertex.glsl │ ├── distortion │ │ ├── 001-fragment.glsl │ │ ├── 001-vertex.glsl │ │ ├── 002-fragment.glsl │ │ ├── 002-vertex.glsl │ │ ├── 003-fragment.glsl │ │ ├── 003-vertex.glsl │ │ ├── 004-fragment.glsl │ │ └── 004-vertex.glsl │ ├── hover │ │ ├── 001-fragment.glsl │ │ ├── 001-vertex.glsl │ │ ├── 002-fragment.glsl │ │ ├── 002-vertex.glsl │ │ ├── 003-fragment.glsl │ │ ├── 003-vertex.glsl │ │ ├── 004-fragment.glsl │ │ └── 004-vertex.glsl │ ├── resources │ │ ├── color.glsl │ │ ├── easings.glsl │ │ ├── noise.glsl │ │ └── utils.glsl │ └── scroll │ │ ├── 001-fragment.glsl │ │ ├── 001-vertex.glsl │ │ ├── 002-fragment.glsl │ │ └── 002-vertex.glsl └── styles │ ├── _base.scss │ ├── _fonts.scss │ ├── _functions.scss │ ├── _mixins.scss │ ├── _typography.scss │ ├── _vars.scss │ └── global.scss ├── components ├── Canvas.vue ├── Cursor.vue ├── EffectTile.vue ├── Footer.vue ├── PageTransition.vue └── ShaderLinks.vue ├── composables ├── useContent.ts ├── useCursor.ts └── useScroll.ts ├── declarations.d.ts ├── error.vue ├── eslint.config.mjs ├── layouts └── default.vue ├── nuxt.config.ts ├── package.json ├── pages ├── [uid].vue ├── index.vue ├── legal.vue └── privacy.vue ├── plugins └── lenis.client.ts ├── public ├── 404.mp4 ├── apple-touch-icon.png ├── bg-texture.webp ├── favicon-192.png ├── favicon-512.png ├── favicon.ico ├── favicon.svg ├── fonts │ └── Erstling-Regular.woff2 ├── manifest.webmanifest ├── og-image.jpg ├── something.webp └── texture.jpg ├── tsconfig.json ├── utils ├── debounce.ts ├── lerp.ts ├── mapRange.ts ├── shader.ts └── three.ts ├── vercel.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[javascript]": { 4 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 5 | }, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 8 | }, 9 | "[vue]": { 10 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 11 | }, 12 | "[scss]": { 13 | "editor.formatOnSave": false, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3 Minimal Starter 2 | 3 | Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. 4 | 5 | ## Setup 6 | 7 | Make sure to install the dependencies: 8 | 9 | ```bash 10 | # yarn 11 | yarn install 12 | 13 | # npm 14 | npm install 15 | 16 | # pnpm 17 | pnpm install 18 | ``` 19 | 20 | ## Development Server 21 | 22 | Start the development server on http://localhost:3000 23 | 24 | ```bash 25 | npm run dev 26 | ``` 27 | 28 | ## Production 29 | 30 | Build the application for production: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | Locally preview production build: 37 | 38 | ```bash 39 | npm run preview 40 | ``` 41 | 42 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. 43 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /assets/shader/baseFragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | float calcDistance(vec2 uv) { 20 | vec2 positionInQuadrant = abs(uv * 2.0 - 1.0); 21 | vec2 extend = vec2(uQuadSize) / 2.0; 22 | vec2 coords = positionInQuadrant * (extend + uBorderRadius); 23 | vec2 delta = max(coords - extend, 0.0); 24 | 25 | return length(delta); 26 | } 27 | 28 | 29 | void main() { 30 | // texture 31 | vec3 texture = vec3(texture(uTexture, vUvCover)); 32 | 33 | // border-radius 34 | float alpha = 1.0; 35 | 36 | if (uBorderRadius > 0.0) { 37 | float dist = calcDistance(vUv); 38 | alpha = 1.0 - (dist - (uBorderRadius - 1.0)); 39 | } 40 | 41 | // output 42 | outColor = vec4(texture, alpha); 43 | } 44 | -------------------------------------------------------------------------------- /assets/shader/baseVertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include './resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/shader/distortion/001-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | void main() { 21 | // texture 22 | vec3 texture = vec3(texture(uTexture, vUvCover)); 23 | 24 | // output 25 | outColor = vec4(texture, 1.0); 26 | } 27 | -------------------------------------------------------------------------------- /assets/shader/distortion/001-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | // wave based on x 23 | vec3 newPosition = vec3(position.x, position.y, 10.0 * sin(uv.x * 10.0 + uTime)); 24 | 25 | // output 26 | gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); 27 | } 28 | -------------------------------------------------------------------------------- /assets/shader/distortion/002-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | void main() { 21 | // texture 22 | vec3 texture = vec3(texture(uTexture, vUvCover)); 23 | 24 | // output 25 | outColor = vec4(texture, 1.0); 26 | } 27 | -------------------------------------------------------------------------------- /assets/shader/distortion/002-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | // wave based on y 23 | vec3 newPosition = vec3(position.x, position.y, 10.0 * sin(uv.y * 10.0 + uTime)); 24 | 25 | // output 26 | gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); 27 | } 28 | -------------------------------------------------------------------------------- /assets/shader/distortion/003-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | vec2 bulge(vec2 uv) { 21 | // centering 22 | vec2 tempUv = uv - vec2(0.5); 23 | 24 | // distort in a circle, make it stronger, invert it 25 | float dist = (length(tempUv) / 1.0); 26 | float distStrength = min(dist, 2.0) * 0.2; 27 | float distRevert = 1.0 / (1.0 + distStrength); 28 | 29 | tempUv *= distRevert; 30 | 31 | // centering 32 | tempUv += vec2(0.5); 33 | 34 | return tempUv; 35 | } 36 | 37 | void main() { 38 | vec2 bulgeUv = bulge(vUvCover); 39 | 40 | // texture 41 | vec3 texture = vec3(texture(uTexture, bulgeUv + 0.05 * sin(bulgeUv.x + uTime * 2.0))); 42 | 43 | // output 44 | outColor = vec4(texture, 1.0); 45 | } 46 | -------------------------------------------------------------------------------- /assets/shader/distortion/003-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/shader/distortion/004-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | float PI = 3.141592653589793; 4 | 5 | uniform vec2 uResolution; // in pixel 6 | uniform float uTime; // in s 7 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 8 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 9 | uniform sampler2D uTexture; // texture 10 | uniform vec2 uTextureSize; // size of texture 11 | uniform vec2 uQuadSize; // size of texture element 12 | uniform float uBorderRadius; // pixel value 13 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 14 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 15 | 16 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 17 | in vec2 vUvCover; 18 | 19 | out vec4 outColor; 20 | 21 | 22 | void main() { 23 | float effectRadius = 0.5; 24 | float effectAngle = 0.5 * PI * sin(uTime); 25 | 26 | // to create the effect in the center 27 | vec2 centeredUv = vUvCover - 0.5; 28 | 29 | float len = length(centeredUv * vec2(uQuadSize.x / uQuadSize.y, 1.0)); 30 | float angle = atan(centeredUv.y, centeredUv.x) + effectAngle * smoothstep(effectRadius, 0.0, len); 31 | float radius = length(centeredUv); 32 | 33 | // texture 34 | vec3 texture = vec3(texture(uTexture, vec2(radius * cos(angle), radius * sin(angle)) + vec2(0.5, 0.5))); 35 | 36 | // output 37 | outColor = vec4(texture, 1.0); 38 | } 39 | -------------------------------------------------------------------------------- /assets/shader/distortion/004-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/shader/hover/001-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | #include '../resources/noise.glsl'; 18 | 19 | out vec4 outColor; 20 | 21 | 22 | void main() { 23 | vec2 texCoords = vUvCover; 24 | 25 | // aspect ratio needed to create a real circle when quadSize is not 1:1 ratio 26 | float aspectRatio = uQuadSize.y / uQuadSize.x; 27 | 28 | // create a circle following the mouse with size 15 29 | float circle = 1.0 - distance( 30 | vec2(uMouseOverPos.x, (1.0 - uMouseOverPos.y) * aspectRatio), 31 | vec2(vUv.x, vUv.y * aspectRatio) 32 | ) * 15.0; 33 | 34 | // create noise 35 | float noise = snoise(gl_FragCoord.xy); 36 | 37 | // modify texture coordinates 38 | texCoords.x += mix(0.0, circle * noise * 0.01, uMouseEnter); 39 | texCoords.y += mix(0.0, circle * noise * 0.01, uMouseEnter); 40 | 41 | // texture 42 | vec3 texture = vec3(texture(uTexture, texCoords)); 43 | 44 | // output 45 | outColor = vec4(texture, 1.0); 46 | } 47 | -------------------------------------------------------------------------------- /assets/shader/hover/001-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/shader/hover/002-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | vec2 bulge(vec2 uv) { 21 | // centering 22 | vec2 tempUv = uv - vec2(uMouseOverPos.x, 1.0 - uMouseOverPos.y); 23 | 24 | // distort in a circle, make it stronger, invert it 25 | float dist = (length(tempUv) / 1.0); 26 | float distStrength = min(dist, 2.0) * 0.2; 27 | float distRevert = 1.0 / (1.0 + distStrength); 28 | 29 | tempUv *= distRevert; 30 | 31 | // centering 32 | tempUv += vec2(uMouseOverPos.x, 1.0 - uMouseOverPos.y); 33 | 34 | return tempUv; 35 | } 36 | 37 | void main() { 38 | vec2 bulgeUv = bulge(vUvCover); 39 | 40 | // texture 41 | vec3 texture = vec3(texture(uTexture, mix(vUvCover, bulgeUv, uMouseEnter))); 42 | 43 | // output 44 | outColor = vec4(texture, 1.0); 45 | } 46 | -------------------------------------------------------------------------------- /assets/shader/hover/002-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/shader/hover/003-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | float PI = 3.141592653589793; 4 | 5 | uniform vec2 uResolution; // in pixel 6 | uniform float uTime; // in s 7 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 8 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 9 | uniform sampler2D uTexture; // texture 10 | uniform vec2 uTextureSize; // size of texture 11 | uniform vec2 uQuadSize; // size of texture element 12 | uniform float uBorderRadius; // pixel value 13 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 14 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 15 | 16 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 17 | in vec2 vUvCover; 18 | 19 | out vec4 outColor; 20 | 21 | 22 | void main() { 23 | vec2 texCoords = vUvCover; 24 | 25 | // aspect ratio needed to create a real circle when quadSize is not 1:1 ratio 26 | float aspectRatio = uQuadSize.y / uQuadSize.x; 27 | 28 | // create a circle following the mouse 29 | float circle = distance( 30 | vec2(uMouseOverPos.x, (1.0 - uMouseOverPos.y) * aspectRatio), 31 | vec2(vUv.x, vUv.y * aspectRatio) 32 | ); 33 | 34 | // distort rgb channels separately 35 | float r = texture(uTexture, texCoords += mix(0.0, (cos(uMouseOverPos.x * PI) * cos(uMouseOverPos.y * PI) * 0.04), uMouseEnter)).x; 36 | float g = texture(uTexture, texCoords += mix(0.0, (cos(uMouseOverPos.x * PI) * cos(uMouseOverPos.y * PI) * 0.05), uMouseEnter)).y; 37 | float b = texture(uTexture, texCoords += mix(0.0, (cos(uMouseOverPos.x * PI) * cos(uMouseOverPos.y * PI) * 0.06), uMouseEnter)).z; 38 | 39 | // output 40 | outColor = vec4(r, g, b, 1.0); 41 | } 42 | -------------------------------------------------------------------------------- /assets/shader/hover/003-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/shader/hover/004-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | void main() { 21 | vec2 texCoords = vUvCover; 22 | 23 | // aspect ratio needed to create a real circle when quadSize is not 1:1 ratio 24 | float aspectRatio = uQuadSize.y / uQuadSize.x; 25 | 26 | // create a circle following the mouse 27 | float circle = distance( 28 | vec2(uMouseOverPos.x, (1.0 - uMouseOverPos.y) * aspectRatio), 29 | vec2(vUv.x, vUv.y * aspectRatio) 30 | ); 31 | 32 | // distort rgb channels separately 33 | float r = texture(uTexture, texCoords += mix(0.0, (circle * 0.02), uMouseEnter)).x; 34 | float g = texture(uTexture, texCoords += mix(0.0, (circle * 0.025), uMouseEnter)).y; 35 | float b = texture(uTexture, texCoords += mix(0.0, (circle * 0.03), uMouseEnter)).z; 36 | 37 | // output 38 | outColor = vec4(r, g, b, 1.0); 39 | } 40 | -------------------------------------------------------------------------------- /assets/shader/hover/004-vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 uResolution; // in pixel 2 | uniform float uTime; // in s 3 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 4 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 5 | uniform sampler2D uTexture; // texture 6 | uniform vec2 uTextureSize; // size of texture 7 | uniform vec2 uQuadSize; // size of texture element 8 | uniform float uBorderRadius; // pixel value 9 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 10 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 11 | 12 | #include '../resources/utils.glsl'; 13 | 14 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 15 | out vec2 vUvCover; 16 | 17 | 18 | void main() { 19 | vUv = uv; 20 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 21 | 22 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/shader/resources/color.glsl: -------------------------------------------------------------------------------- 1 | vec3 rgb2hsb(in vec3 c) { 2 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 3 | vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); 4 | vec4 q = mix(vec4(p.xyw, c.r), ec4(c.r, p.yzx), step(p.x, c.r)); 5 | float d = q.x - min(q.w, q.y); 6 | float e = 1.0e-10; 7 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), 8 | d / (q.x + e), 9 | q.x); 10 | } 11 | 12 | // Function from Iñigo Quiles 13 | // https://www.shadertoy.com/view/MsS3Wc 14 | vec3 hsb2rgb(in vec3 c) { 15 | vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 16 | 0.0, 17 | 1.0); 18 | rgb = rgb * rgb * (3.0 - 2.0 * rgb); 19 | return c.z * mix(vec3(1.0), rgb, c.y); 20 | } 21 | 22 | // https://iquilezles.org/articles/palettes/ 23 | vec3 palette(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) { 24 | return a + b * cos(6.28318 * (c * t + d)); 25 | } 26 | -------------------------------------------------------------------------------- /assets/shader/resources/easings.glsl: -------------------------------------------------------------------------------- 1 | // Robert Penner's easing functions in GLSL 2 | // https://github.com/stackgl/glsl-easings 3 | // https://glslify.github.io/glsl-easings/ 4 | float linear(float t) { 5 | return t; 6 | } 7 | 8 | float exponentialIn(float t) { 9 | return t == 0.0 ? t : pow(2.0, 10.0 * (t - 1.0)); 10 | } 11 | 12 | float exponentialOut(float t) { 13 | return t == 1.0 ? t : 1.0 - pow(2.0, -10.0 * t); 14 | } 15 | 16 | float exponentialInOut(float t) { 17 | return t == 0.0 || t == 1.0 18 | ? t 19 | : t < 0.5 20 | ? +0.5 * pow(2.0, (20.0 * t) - 10.0) 21 | : -0.5 * pow(2.0, 10.0 - (t * 20.0)) + 1.0; 22 | } 23 | 24 | float sineIn(float t) { 25 | return sin((t - 1.0) * HALF_PI) + 1.0; 26 | } 27 | 28 | float sineOut(float t) { 29 | return sin(t * HALF_PI); 30 | } 31 | 32 | float sineInOut(float t) { 33 | return -0.5 * (cos(PI * t) - 1.0); 34 | } 35 | 36 | float qinticIn(float t) { 37 | return pow(t, 5.0); 38 | } 39 | 40 | float qinticOut(float t) { 41 | return 1.0 - (pow(t - 1.0, 5.0)); 42 | } 43 | 44 | float qinticInOut(float t) { 45 | return t < 0.5 46 | ? +16.0 * pow(t, 5.0) 47 | : -0.5 * pow(2.0 * t - 2.0, 5.0) + 1.0; 48 | } 49 | 50 | float quarticIn(float t) { 51 | return pow(t, 4.0); 52 | } 53 | 54 | float quarticOut(float t) { 55 | return pow(t - 1.0, 3.0) * (1.0 - t) + 1.0; 56 | } 57 | 58 | float quarticInOut(float t) { 59 | return t < 0.5 60 | ? +8.0 * pow(t, 4.0) 61 | : -8.0 * pow(t - 1.0, 4.0) + 1.0; 62 | } 63 | 64 | float quadraticInOut(float t) { 65 | float p = 2.0 * t * t; 66 | return t < 0.5 ? p : -p + (4.0 * t) - 1.0; 67 | } 68 | 69 | float quadraticIn(float t) { 70 | return t * t; 71 | } 72 | 73 | float quadraticOut(float t) { 74 | return -t * (t - 2.0); 75 | } 76 | 77 | float cubicIn(float t) { 78 | return t * t * t; 79 | } 80 | 81 | float cubicOut(float t) { 82 | float f = t - 1.0; 83 | return f * f * f + 1.0; 84 | } 85 | 86 | float cubicInOut(float t) { 87 | return t < 0.5 88 | ? 4.0 * t * t * t 89 | : 0.5 * pow(2.0 * t - 2.0, 3.0) + 1.0; 90 | } 91 | 92 | float elasticIn(float t) { 93 | return sin(13.0 * t * HALF_PI) * pow(2.0, 10.0 * (t - 1.0)); 94 | } 95 | 96 | float elasticOut(float t) { 97 | return sin(-13.0 * (t + 1.0) * HALF_PI) * pow(2.0, -10.0 * t) + 1.0; 98 | } 99 | 100 | float elasticInOut(float t) { 101 | return t < 0.5 102 | ? 0.5 * sin(+13.0 * HALF_PI * 2.0 * t) * pow(2.0, 10.0 * (2.0 * t - 1.0)) 103 | : 0.5 * sin(-13.0 * HALF_PI * ((2.0 * t - 1.0) + 1.0)) * pow(2.0, -10.0 * (2.0 * t - 1.0)) + 1.0; 104 | } 105 | 106 | float circularIn(float t) { 107 | return 1.0 - sqrt(1.0 - t * t); 108 | } 109 | 110 | float circularOut(float t) { 111 | return sqrt((2.0 - t) * t); 112 | } 113 | 114 | float circularInOut(float t) { 115 | return t < 0.5 116 | ? 0.5 * (1.0 - sqrt(1.0 - 4.0 * t * t)) 117 | : 0.5 * (sqrt((3.0 - 2.0 * t) * (2.0 * t - 1.0)) + 1.0); 118 | } 119 | 120 | float bounceOut(float t) { 121 | const float a = 4.0 / 11.0; 122 | const float b = 8.0 / 11.0; 123 | const float c = 9.0 / 10.0; 124 | 125 | const float ca = 4356.0 / 361.0; 126 | const float cb = 35442.0 / 1805.0; 127 | const float cc = 16061.0 / 1805.0; 128 | 129 | float t2 = t * t; 130 | 131 | return t < a 132 | ? 7.5625 * t2 133 | : t < b 134 | ? 9.075 * t2 - 9.9 * t + 3.4 135 | : t < c 136 | ? ca * t2 - cb * t + cc 137 | : 10.8 * t * t - 20.52 * t + 10.72; 138 | } 139 | 140 | float bounceIn(float t) { 141 | return 1.0 - bounceOut(1.0 - t); 142 | } 143 | 144 | float bounceInOut(float t) { 145 | return t < 0.5 146 | ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0)) 147 | : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5; 148 | } 149 | 150 | float backIn(float t) { 151 | return pow(t, 3.0) - t * sin(t * PI); 152 | } 153 | 154 | float backOut(float t) { 155 | float f = 1.0 - t; 156 | return 1.0 - (pow(f, 3.0) - f * sin(f * PI)); 157 | } 158 | 159 | float backInOut(float t) { 160 | float f = t < 0.5 161 | ? 2.0 * t 162 | : 1.0 - (2.0 * t - 1.0); 163 | 164 | float g = pow(f, 3.0) - f * sin(f * PI); 165 | 166 | return t < 0.5 167 | ? 0.5 * g 168 | : 0.5 * (1.0 - g) + 0.5; 169 | } 170 | -------------------------------------------------------------------------------- /assets/shader/resources/noise.glsl: -------------------------------------------------------------------------------- 1 | // 2 | // Description : Array and textureless GLSL 2D simplex noise function. 3 | // Author : Ian McEwan, Ashima Arts. 4 | // Maintainer : stegu 5 | // Lastmod : 20110822 (ijm) 6 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 7 | // Distributed under the MIT License. See LICENSE file. 8 | // https://github.com/ashima/webgl-noise 9 | // https://github.com/stegu/webgl-noise 10 | // 11 | 12 | vec3 mod289(vec3 x) { 13 | return x - floor(x * (1.0 / 289.0)) * 289.0; 14 | } 15 | 16 | vec2 mod289(vec2 x) { 17 | return x - floor(x * (1.0 / 289.0)) * 289.0; 18 | } 19 | 20 | vec3 permute(vec3 x) { 21 | return mod289(((x*34.0)+10.0)*x); 22 | } 23 | 24 | float snoise(vec2 v) 25 | { 26 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 27 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 28 | -0.577350269189626, // -1.0 + 2.0 * C.x 29 | 0.024390243902439); // 1.0 / 41.0 30 | // First corner 31 | vec2 i = floor(v + dot(v, C.yy) ); 32 | vec2 x0 = v - i + dot(i, C.xx); 33 | 34 | // Other corners 35 | vec2 i1; 36 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 37 | //i1.y = 1.0 - i1.x; 38 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 39 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 40 | // x1 = x0 - i1 + 1.0 * C.xx ; 41 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 42 | vec4 x12 = x0.xyxy + C.xxzz; 43 | x12.xy -= i1; 44 | 45 | // Permutations 46 | i = mod289(i); // Avoid truncation effects in permutation 47 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) 48 | + i.x + vec3(0.0, i1.x, 1.0 )); 49 | 50 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 51 | m = m*m ; 52 | m = m*m ; 53 | 54 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 55 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 56 | 57 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 58 | vec3 h = abs(x) - 0.5; 59 | vec3 ox = floor(x + 0.5); 60 | vec3 a0 = x - ox; 61 | 62 | // Normalise gradients implicitly by scaling m 63 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 64 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 65 | 66 | // Compute final noise value at P 67 | vec3 g; 68 | g.x = a0.x * x0.x + h.x * x0.y; 69 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 70 | return 130.0 * dot(m, g); 71 | } 72 | -------------------------------------------------------------------------------- /assets/shader/resources/utils.glsl: -------------------------------------------------------------------------------- 1 | // random 2 | float random(vec2 st) { 3 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 4 | } 5 | 6 | // contain 7 | vec2 getContainUvFrag(vec2 uv, vec2 textureSize, vec2 quadSize) { 8 | vec2 tempUv = uv - vec2(0.5); 9 | 10 | float quadAspect = quadSize.x / quadSize.y; 11 | float textureAspect = textureSize.x / textureSize.y; 12 | 13 | if (quadAspect > textureAspect) { 14 | tempUv *= vec2(quadAspect / textureAspect, 1.0); 15 | } else { 16 | tempUv *= vec2(1.0, textureAspect / quadAspect); 17 | } 18 | 19 | tempUv += vec2(0.5); 20 | 21 | return tempUv; 22 | } 23 | 24 | // cover 25 | vec2 getCoverUvVert(vec2 uv, vec2 textureSize, vec2 quadSize) { 26 | vec2 ratio = vec2( 27 | min((quadSize.x / quadSize.y) / (textureSize.x / textureSize.y), 1.0), 28 | min((quadSize.y / quadSize.x) / (textureSize.y / textureSize.x), 1.0) 29 | ); 30 | 31 | return vec2( 32 | uv.x * ratio.x + (1.0 - ratio.x) * 0.5, 33 | uv.y * ratio.y + (1.0 - ratio.y) * 0.5 34 | ); 35 | } 36 | 37 | vec2 getCoverUvFrag(vec2 uv, vec2 textureSize, vec2 quadSize) { 38 | vec2 tempUv = uv - vec2(0.5); 39 | 40 | float quadAspect = quadSize.x / quadSize.y; 41 | float textureAspect = textureSize.x / textureSize.y; 42 | 43 | if (quadAspect < textureAspect) { 44 | tempUv *= vec2(quadAspect / textureAspect, 1.0); 45 | } else { 46 | tempUv *= vec2(1.0, textureAspect / quadAspect); 47 | } 48 | 49 | tempUv += vec2(0.5); 50 | 51 | return tempUv; 52 | } 53 | 54 | // uv, rotation (in radians), mid (point to rotate around) 55 | vec2 rotate(vec2 uv, float rotation, vec2 mid) { 56 | return vec2( 57 | cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x, 58 | cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y 59 | ); 60 | } 61 | 62 | 63 | // float circle(vec2 st, float radius) { 64 | // vec2 dist = st - vec2(0.5); 65 | // return 1.0 - smoothstep(radius - (radius * 0.01), 66 | // radius + (radius * 0.01), 67 | // dot(dist, dist) * 4.0); 68 | // } 69 | 70 | // float circle(vec2 uv, vec2 resolution, vec2 disc_center, float disc_radius, float border_size) { 71 | // uv -= disc_center; 72 | // uv *= resolution; 73 | // float dist = sqrt(dot(uv, uv)); 74 | // return smoothstep(disc_radius + border_size, disc_radius - border_size, dist); 75 | // } 76 | -------------------------------------------------------------------------------- /assets/shader/scroll/001-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | void main() { 21 | // texture 22 | vec3 texture = vec3(texture(uTexture, vUvCover)); 23 | 24 | // output 25 | outColor = vec4(texture, 1.0); 26 | } 27 | -------------------------------------------------------------------------------- /assets/shader/scroll/001-vertex.glsl: -------------------------------------------------------------------------------- 1 | float PI = 3.141592653589793; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | #include '../resources/utils.glsl'; 15 | 16 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 17 | out vec2 vUvCover; 18 | 19 | vec3 deformationCurve(vec3 position, vec2 uv) { 20 | position.y = position.y - (sin(uv.x * PI) * uScrollVelocity * -0.01); 21 | 22 | return position; 23 | } 24 | 25 | 26 | void main() { 27 | vUv = uv; 28 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 29 | 30 | vec3 deformedPosition = deformationCurve(position, vUvCover); 31 | 32 | gl_Position = projectionMatrix * modelViewMatrix * vec4(deformedPosition, 1.0); 33 | } 34 | -------------------------------------------------------------------------------- /assets/shader/scroll/002-fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | in vec2 vUv; // 0 (left) 0 (bottom) - 1 (right) 1 (top) 15 | in vec2 vUvCover; 16 | 17 | out vec4 outColor; 18 | 19 | 20 | void main() { 21 | vec4 originalColor = texture(uTexture, vUvCover); 22 | 23 | if (abs(uScrollVelocity) <= 0.001) { 24 | outColor = vec4(vec3(originalColor), 1.0); 25 | return; 26 | } 27 | 28 | vec4 col = vec4(0.0); 29 | 30 | float blurStrength = min(abs(uScrollVelocity), 3.0); 31 | float blurSize = (1.0 / 333.0) * blurStrength; 32 | 33 | // gaussian weights (approximated) 34 | float weight[7]; 35 | weight[0] = 0.06; 36 | weight[1] = 0.11; 37 | weight[2] = 0.17; 38 | weight[3] = 0.32; 39 | weight[4] = 0.17; 40 | weight[5] = 0.11; 41 | weight[6] = 0.06; 42 | 43 | // center sample 44 | col = texture(uTexture, vUvCover) * weight[3]; 45 | 46 | // horizontal samples 47 | for (int i = 1; i <= 3; i++) { 48 | float offset = blurSize * float(i); 49 | col += texture(uTexture, vUvCover + vec2(offset, 0.0)) * weight[i + 3]; 50 | col += texture(uTexture, vUvCover - vec2(offset, 0.0)) * weight[3 - i]; 51 | } 52 | 53 | // vertical samples 54 | for (int i = 1; i <= 3; i++) { 55 | float offset = blurSize * float(i); 56 | col += texture(uTexture, vUvCover + vec2(0.0, offset)) * weight[i + 3]; 57 | col += texture(uTexture, vUvCover - vec2(0.0, offset)) * weight[3 - i]; 58 | } 59 | 60 | // normalize (weights sum should be ~2.0 due to our two-pass approach) 61 | col /= 2.0; 62 | 63 | // blend blurred color with original color based on scroll velocity 64 | float blendFactor = smoothstep(0.0, 0.5, abs(uScrollVelocity)); 65 | vec4 finalColor = mix(originalColor, col, blendFactor); 66 | 67 | outColor = finalColor; 68 | } 69 | -------------------------------------------------------------------------------- /assets/shader/scroll/002-vertex.glsl: -------------------------------------------------------------------------------- 1 | float PI = 3.141592653589793; 2 | 3 | uniform vec2 uResolution; // in pixel 4 | uniform float uTime; // in s 5 | uniform vec2 uCursor; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 6 | uniform float uScrollVelocity; // - (scroll up) / + (scroll down) 7 | uniform sampler2D uTexture; // texture 8 | uniform vec2 uTextureSize; // size of texture 9 | uniform vec2 uQuadSize; // size of texture element 10 | uniform float uBorderRadius; // pixel value 11 | uniform float uMouseEnter; // 0 - 1 (enter) / 1 - 0 (leave) 12 | uniform vec2 uMouseOverPos; // 0 (left) 0 (top) / 1 (right) 1 (bottom) 13 | 14 | #include '../resources/utils.glsl'; 15 | 16 | out vec2 vUv; // 0 (left) 0 (bottom) - 1 (top) 1 (right) 17 | out vec2 vUvCover; 18 | 19 | 20 | void main() { 21 | vUv = uv; 22 | vUvCover = getCoverUvVert(uv, uTextureSize, uQuadSize); 23 | 24 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 25 | } 26 | -------------------------------------------------------------------------------- /assets/styles/_base.scss: -------------------------------------------------------------------------------- 1 | @use 'mixins' as *; 2 | 3 | // reset 4 | *, 5 | *::before, 6 | *::after { 7 | box-sizing: border-box; 8 | } 9 | 10 | * { 11 | padding: 0; 12 | margin: 0; 13 | } 14 | 15 | html, body { 16 | min-height: 100%; 17 | height: auto; 18 | -webkit-text-size-adjust: none; 19 | text-size-adjust: none; 20 | } 21 | 22 | body { 23 | width: 100%; 24 | overflow-x: hidden; 25 | hyphens: auto; 26 | -webkit-hyphenate-character: '-'; 27 | hyphenate-character: '-'; 28 | line-height: 1.5; 29 | -webkit-font-smoothing: antialiased; 30 | 31 | @include has-hover { 32 | width: 100vw; 33 | padding-right: 10px; 34 | } 35 | } 36 | 37 | img, picture, video, svg { 38 | display: block; 39 | max-width: 100%; 40 | } 41 | 42 | canvas { 43 | display: block; 44 | } 45 | 46 | input, button, textarea, select { 47 | font: inherit; 48 | } 49 | 50 | button { 51 | appearance: none; 52 | background: transparent; 53 | color: inherit; 54 | border: none; 55 | cursor: pointer; 56 | } 57 | 58 | h1, h2, h3, h4, h5, h6 { 59 | overflow-wrap: break-word; 60 | text-wrap: balance; 61 | } 62 | 63 | p { 64 | overflow-wrap: break-word; 65 | text-wrap: pretty; 66 | } 67 | 68 | a { 69 | color: inherit; 70 | } 71 | 72 | // custom 73 | ::selection { 74 | color: var(--c-dark); 75 | background-color: var(--c-light); 76 | } 77 | 78 | body { 79 | font-family: var(--ff-primary); 80 | font-weight: var(--fw-regular); 81 | font-size: var(--fs-base); 82 | color: var(--c-light); 83 | background-color: var(--c-background); 84 | } 85 | 86 | .visually-hidden { 87 | position: absolute; 88 | width: 1px; 89 | height: 1px; 90 | clip: rect(0 0 0 0); 91 | clip-path: inset(50%); 92 | overflow: hidden; 93 | white-space: nowrap; 94 | } 95 | 96 | // scroll 97 | .lenis.lenis-smooth { 98 | scroll-behavior: auto; 99 | } 100 | 101 | .lenis.lenis-smooth [data-lenis-prevent] { 102 | overscroll-behavior: contain; 103 | } 104 | 105 | .lenis.lenis-stopped { 106 | overflow: hidden; 107 | } 108 | 109 | // grid 110 | .container { 111 | max-width: var(--container-max-width); 112 | margin-inline: auto; 113 | 114 | &.padding { 115 | padding-inline: var(--grid-margin); 116 | } 117 | } 118 | 119 | .grid { 120 | display: grid; 121 | gap: var(--grid-gutter); 122 | grid-template-columns: repeat(var(--grid-columns), 1fr); 123 | } 124 | 125 | // webgl 126 | [data-webgl-media] { 127 | opacity: 0; 128 | } 129 | 130 | // tweakpane 131 | .tp-dfwv { 132 | position: fixed !important; 133 | } 134 | -------------------------------------------------------------------------------- /assets/styles/_fonts.scss: -------------------------------------------------------------------------------- 1 | // erstling 2 | @font-face { 3 | font-display: swap; 4 | font-family: 'Erstling'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: url('/fonts/Erstling-Regular.woff2') format('woff2'); 8 | } 9 | -------------------------------------------------------------------------------- /assets/styles/_functions.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | @function strip-unit($number) { 4 | @if type-of($number)=='number' and not unitless($number) { 5 | @return math.div($number, ($number * 0 + 1)); 6 | } 7 | 8 | @return $number; 9 | } 10 | 11 | @function to-rem($size-in-px) { 12 | @return math.div(strip-unit($size-in-px), strip-unit(16px)) * 1rem; 13 | } 14 | 15 | @function clamp-fluid($min, $max) { 16 | @return clamp(to-rem($min), calc(((100 * ($max - $min)) / (var(--fluid-max) - var(--fluid-min))) * 1vw) + calc((((var(--fluid-min) * $max) - (var(--fluid-max) * $min)) / (var(--fluid-min) - var(--fluid-max))) / 16 * 1rem), to-rem($max)); 17 | } 18 | 19 | @function str-replace($string, $search, $replace: "") { 20 | $index: str-index($string, $search); 21 | 22 | @if $index { 23 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 24 | } 25 | 26 | @return $string; 27 | } 28 | 29 | @function url-encode($string) { 30 | $map: (('<', '%3C'), 31 | ('>', '%3E'), 32 | ('"', '\''), 33 | ('#', '%23'), 34 | ('&', '%26'), 35 | ); 36 | 37 | $new: $string; 38 | 39 | @each $search, 40 | $replace in $map { 41 | $new: str-replace($new, $search, $replace); 42 | } 43 | 44 | @return $new; 45 | } 46 | 47 | @function inline-svg($string) { 48 | @return url("data:image/svg+xml,#{url-encode($string)}"); 49 | } 50 | -------------------------------------------------------------------------------- /assets/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin has-hover() { 2 | @media (hover: hover) and (min-width: 768px) { 3 | @content; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /assets/styles/_typography.scss: -------------------------------------------------------------------------------- 1 | h1, 2 | .h1, 3 | h2, 4 | .h2, 5 | h3, 6 | .h3, 7 | h4, 8 | .h4, 9 | h5, 10 | .h5, 11 | h6, 12 | .h6 { 13 | font-weight: var(--fw-regular); 14 | line-height: 1.2; 15 | text-transform: uppercase; 16 | } 17 | 18 | h1, 19 | .h1 { 20 | font-size: var(--fs-1); 21 | } 22 | 23 | h2, 24 | .h2 { 25 | font-size: var(--fs-2); 26 | } 27 | 28 | h3, 29 | .h3 { 30 | font-size: var(--fs-3); 31 | } 32 | 33 | h4, 34 | .h4 { 35 | font-size: var(--fs-4); 36 | } 37 | 38 | h5, 39 | .h5 { 40 | font-size: var(--fs-5); 41 | } 42 | 43 | h6, 44 | .h6 { 45 | font-size: var(--fs-6); 46 | } 47 | 48 | .richtext { 49 | * { 50 | &:not(:last-child) { 51 | margin-bottom: 1em; 52 | } 53 | } 54 | 55 | h1, 56 | h2, 57 | h3, 58 | h4, 59 | h5, 60 | h6 { 61 | &:not(:first-child) { 62 | margin-top: 1.2em; 63 | } 64 | } 65 | 66 | ul, 67 | ol { 68 | padding-left: 1em; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /assets/styles/_vars.scss: -------------------------------------------------------------------------------- 1 | @use 'functions' as *; 2 | 3 | :root { 4 | // spacings 5 | --s-m: #{clamp-fluid(25, 50)}; 6 | --s-l: #{clamp-fluid(50, 100)}; 7 | 8 | // grid 9 | --fluid-min: 375; 10 | --fluid-max: 1720; 11 | --container-max-width: #{to-rem(1920)}; 12 | --grid-margin: #{to-rem(20)}; 13 | --grid-gutter: #{clamp-fluid(10, 60)}; 14 | --grid-max-width: calc(var(--container-max-width) - var(--grid-margin) * 2); 15 | --grid-columns: 12; 16 | 17 | // fonts 18 | --ff-primary: 'Erstling', 'Helvetica', 'Arial', sans-serif; 19 | 20 | // font-sizes 21 | --fs-tiny: #{clamp-fluid(14, 16)}; 22 | --fs-base: #{clamp-fluid(16, 18)}; 23 | --fs-1: #{clamp-fluid(40, 56)}; 24 | --fs-2: #{clamp-fluid(24, 28)}; 25 | --fs-3: #{clamp-fluid(18, 20)}; 26 | --fs-4: #{clamp-fluid(16, 18)}; 27 | --fs-5: #{clamp-fluid(16, 18)}; 28 | --fs-6: #{clamp-fluid(16, 18)}; 29 | 30 | // font-weights 31 | --fw-thin: 100; 32 | --fw-hairline: var(--fw-thin); 33 | --fw-xlight: 200; 34 | --fw-ultralight: var(--fw-xlight); 35 | --fw-light: 300; 36 | --fw-normal: 400; 37 | --fw-regular: var(--fw-normal); 38 | --fw-medium: 500; 39 | --fw-semibold: 600; 40 | --fw-demibold: var(--fw-semibold); 41 | --fw-bold: 700; 42 | --fw-xbold: 800; 43 | --fw-ultrabold: var(--fw-xbold); 44 | --fw-black: 900; 45 | --fw-heavy: var(--fw-black); 46 | --fw-xblack: 950; 47 | --fw-ultrablack: var(--fw-xblack); 48 | 49 | // colors 50 | --c-light: #eeeeee; 51 | --c-dark: #151515; 52 | --c-background: #232a2a; 53 | --c-accent: #21EBB9; 54 | 55 | // animations 56 | --ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1); 57 | --ease-in-out-custom: cubic-bezier(0.4, 0, 0.2, 1); 58 | 59 | // z-index 60 | --zi-canvas: 5000; 61 | --zi-cursor: 10000; 62 | --zi-page-transition: 50000 63 | } 64 | -------------------------------------------------------------------------------- /assets/styles/global.scss: -------------------------------------------------------------------------------- 1 | @use 'vars'; 2 | @use 'functions'; 3 | @use 'mixins'; 4 | @use 'fonts'; 5 | @use 'base'; 6 | @use 'typography'; 7 | -------------------------------------------------------------------------------- /components/Canvas.vue: -------------------------------------------------------------------------------- 1 | 294 | 295 | 298 | 299 | 310 | -------------------------------------------------------------------------------- /components/Cursor.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 48 | 49 | 78 | -------------------------------------------------------------------------------- /components/EffectTile.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /components/Footer.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 103 | -------------------------------------------------------------------------------- /components/PageTransition.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | -------------------------------------------------------------------------------- /components/ShaderLinks.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /composables/useContent.ts: -------------------------------------------------------------------------------- 1 | export const useContent = () => { 2 | const content = useState('content', () => ([ 3 | { 4 | groupTitle: 'Distortion Effects', 5 | effects: [ 6 | { 7 | title: 'Wave 1', 8 | description: 'A simple sinus wave applied to the x coordinate in the vertex shader.', 9 | path: 'distortion-001', 10 | image: '/something.webp', 11 | }, 12 | { 13 | title: 'Wave 2', 14 | description: 'A simple sinus wave applied to the y coordinate in the vertex shader.', 15 | path: 'distortion-002', 16 | image: '/something.webp', 17 | }, 18 | { 19 | title: 'Diagonal', 20 | description: 'A bulge effect, circular distortion applied to the texture in the fragment shader, animated over time.', 21 | path: 'distortion-003', 22 | image: '/something.webp', 23 | }, 24 | { 25 | title: 'Swirl', 26 | description: 'A swirl effect, circular distortion, animated over time.', 27 | path: 'distortion-004', 28 | image: '/something.webp', 29 | }, 30 | ], 31 | }, 32 | { 33 | groupTitle: 'Hover Effects', 34 | effects: [ 35 | { 36 | title: 'Noise / Grain', 37 | description: 'A noise effect with a clearer circle around the mouse position.', 38 | path: 'hover-001', 39 | image: '/something.webp', 40 | }, 41 | { 42 | title: 'Bulge / Distort', 43 | description: 'A bulge effect, circular distortion applied to the texture in the fragment shader, based on mouse position.', 44 | path: 'hover-002', 45 | image: '/something.webp', 46 | }, 47 | { 48 | title: 'RGB-Shift', 49 | description: 'A rgb-shift effect, based on mouse position.', 50 | path: 'hover-003', 51 | image: '/something.webp', 52 | }, 53 | { 54 | title: 'Bulge / Distort + RGB-Shift', 55 | description: 'A bulge effect, circular distortion applied to the texture in the fragment shader plus a rgb-shift, based on mouse position.', 56 | path: 'hover-004', 57 | image: '/something.webp', 58 | }, 59 | ], 60 | }, 61 | { 62 | groupTitle: 'On-Scroll Effects', 63 | effects: [ 64 | { 65 | title: 'Bulge', 66 | description: 'A bulge effect, a curve applied based on scroll and scroll speed.', 67 | path: 'scroll-001', 68 | image: '/something.webp' 69 | }, 70 | { 71 | title: 'Blur', 72 | description: 'A blur applied based on scroll and scroll speed.', 73 | path: 'scroll-002', 74 | image: '/something.webp' 75 | } 76 | ] 77 | } 78 | ])) 79 | 80 | const getEffectByPath = (path: string | string[]) => { 81 | return content.value.flatMap(group => group.effects).find(effect => effect.path === path) 82 | } 83 | 84 | return { 85 | content, 86 | getEffectByPath, 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /composables/useCursor.ts: -------------------------------------------------------------------------------- 1 | export const useCursor = () => { 2 | const cursorPos = useState('cursorPos', () => ({ 3 | current: { x: 0.5, y: 0.5 }, 4 | target: { x: 0.5, y: 0.5 }, 5 | })) 6 | 7 | const setCursorPosCurrent = (x: number, y: number) => { 8 | cursorPos.value.current.x = x 9 | cursorPos.value.current.y = y 10 | } 11 | 12 | const setCursorPosTarget = (mouseX: number, mouseY: number) => { 13 | cursorPos.value.target.x = (mouseX / window.innerWidth) 14 | cursorPos.value.target.y = (mouseY / window.innerHeight) 15 | } 16 | 17 | return { 18 | cursorPos, 19 | setCursorPosCurrent, 20 | setCursorPosTarget, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /composables/useScroll.ts: -------------------------------------------------------------------------------- 1 | export const useScroll = () => { 2 | return useState('scroll', () => ({ 3 | scrollY: 0, 4 | scrollVelocity: 0, 5 | })) 6 | } 7 | -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.glsl'; 2 | -------------------------------------------------------------------------------- /error.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 47 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import withNuxt from './.nuxt/eslint.config.mjs' 2 | 3 | export default withNuxt( 4 | // your custom flat configs go here, for example: 5 | // { 6 | // files: ['**/*.ts', '**/*.tsx'], 7 | // rules: { 8 | // 'no-console': 'off' // allow console.log in TypeScript files 9 | // } 10 | // }, 11 | // { 12 | // ... 13 | // } 14 | { 15 | rules: { 16 | 'vue/multi-word-component-names': ['off', { 17 | ignores: [], 18 | }], 19 | '@stylistic/brace-style': ['error', '1tbs'], 20 | }, 21 | }, 22 | ) 23 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 156 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import glsl from 'vite-plugin-glsl' 2 | 3 | // https://nuxt.com/docs/api/configuration/nuxt-config 4 | export default defineNuxtConfig({ 5 | modules: ['@nuxt/eslint', '@nuxtjs/robots', '@nuxtjs/sitemap'], 6 | app: { 7 | head: { 8 | htmlAttrs: { 9 | lang: 'en', 10 | }, 11 | title: 'real world shader', 12 | link: [ 13 | { rel: 'icon', href: '/favicon.ico', sizes: '32x32' }, 14 | { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' }, 15 | { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }, 16 | { rel: 'manifest', href: '/manifest.webmanifest' }, 17 | ], 18 | script: [ 19 | { 'async': true, 'src': 'https://tracking.jnkl.dev/script.js', 'data-do-not-track': 'true', 'data-website-id': 'cb5b5e20-08da-4ab4-9207-6e3bd7a7d7df', 'data-domains': 'real-world-shader.jankohlbach.com' }, 20 | ], 21 | meta: [ 22 | { name: 'theme-color', content: '#000000' }, 23 | { name: 'description', content: 'a collection of shader effects that are actually useful in real world client projects' }, 24 | { property: 'og:title', content: 'real world shader' }, 25 | { property: 'og:description', content: 'a collection of shader effects that are actually useful in real world client projects' }, 26 | { property: 'og:image', content: 'https://real-world-shader.jankohlbach.com/og-image.jpg' }, 27 | { property: 'og:type', content: 'website' }, 28 | { property: 'og:locale', content: 'en' }, 29 | ], 30 | }, 31 | pageTransition: { name: 'page', mode: 'out-in' }, 32 | }, 33 | css: [ 34 | '@/assets/styles/global.scss', 35 | ], 36 | site: { 37 | url: 'https://real-world-shader.jankohlbach.com', 38 | // indexable: false, 39 | }, 40 | nitro: { 41 | prerender: { 42 | crawlLinks: true, 43 | routes: ['/', '/sitemap.xml'], 44 | }, 45 | }, 46 | vite: { 47 | css: { 48 | preprocessorOptions: { 49 | scss: { 50 | additionalData: '@use "~/assets/styles/functions" as *; @use "~/assets/styles/mixins" as *;', 51 | }, 52 | }, 53 | }, 54 | plugins: [glsl()], 55 | }, 56 | typescript: { 57 | typeCheck: true, 58 | }, 59 | eslint: { 60 | config: { 61 | stylistic: { 62 | braceStyle: '1tbs', 63 | }, 64 | }, 65 | }, 66 | }) 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare" 10 | }, 11 | "devDependencies": { 12 | "@nuxt/eslint": "^0.6.0", 13 | "@nuxtjs/robots": "^4.1.8", 14 | "@nuxtjs/sitemap": "^6.1.2", 15 | "@tweakpane/core": "^2.0.4", 16 | "@types/three": "^0.169.0", 17 | "eslint": "^9.12.0", 18 | "nuxt": "^3.13.2", 19 | "sass": "^1.79.5", 20 | "tweakpane": "^4.0.4", 21 | "typescript": "^5.6.3", 22 | "vite-plugin-glsl": "^1.3.0", 23 | "vue-tsc": "^2.1.6" 24 | }, 25 | "dependencies": { 26 | "gsap": "^3.12.5", 27 | "lenis": "^1.1.14", 28 | "split-type": "^0.3.4", 29 | "three": "^0.166.1" 30 | }, 31 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" 32 | } 33 | -------------------------------------------------------------------------------- /pages/[uid].vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 30 | 31 | 54 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 45 | 46 | 89 | -------------------------------------------------------------------------------- /pages/legal.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 70 | -------------------------------------------------------------------------------- /pages/privacy.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 123 | -------------------------------------------------------------------------------- /plugins/lenis.client.ts: -------------------------------------------------------------------------------- 1 | import Lenis from 'lenis' 2 | 3 | export default defineNuxtPlugin(() => { 4 | const scroll = useScroll() 5 | 6 | const lenis = new Lenis() 7 | 8 | const raf = (time: number) => { 9 | lenis.raf(time) 10 | requestAnimationFrame(raf) 11 | } 12 | 13 | requestAnimationFrame(raf) 14 | 15 | const scrollStop = () => { 16 | document.body.style.overflowY = 'hidden' 17 | lenis.stop() 18 | } 19 | 20 | const scrollStart = () => { 21 | document.body.style.overflowY = '' 22 | lenis.start() 23 | } 24 | 25 | scroll.value.scrollY = window.scrollY 26 | 27 | lenis.on('scroll', (e) => { 28 | scroll.value.scrollY = window.scrollY 29 | scroll.value.scrollVelocity = e.velocity 30 | }) 31 | 32 | return { 33 | provide: { 34 | lenis, 35 | scrollStop, 36 | scrollStart, 37 | }, 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /public/404.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/404.mp4 -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/bg-texture.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/bg-texture.webp -------------------------------------------------------------------------------- /public/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/favicon-192.png -------------------------------------------------------------------------------- /public/favicon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/favicon-512.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/fonts/Erstling-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/fonts/Erstling-Regular.woff2 -------------------------------------------------------------------------------- /public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "icons": [ 3 | { 4 | "src": "/favicon-192.png", 5 | "type": "image/png", 6 | "sizes": "192x192" 7 | }, 8 | { 9 | "src": "/favicon-512.png", 10 | "type": "image/png", 11 | "sizes": "512x512" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /public/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/og-image.jpg -------------------------------------------------------------------------------- /public/something.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/something.webp -------------------------------------------------------------------------------- /public/texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankohlbach/real-world-shader/6c8e7346cce551df361a708824cc4014523642e2/public/texture.jpg -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /utils/debounce.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export const debounce = (func: any, timeout = 300) => { 3 | let timer: NodeJS.Timeout 4 | 5 | return (...args: any) => { 6 | clearTimeout(timer) 7 | timer = setTimeout(() => { 8 | func.apply(this, args) 9 | }, timeout) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /utils/lerp.ts: -------------------------------------------------------------------------------- 1 | export const lerp = (start: number, end: number, damping: number) => start * (1 - damping) + end * damping 2 | -------------------------------------------------------------------------------- /utils/mapRange.ts: -------------------------------------------------------------------------------- 1 | export const mapRange = (number: number, inMin: number, inMax: number, outMin: number, outMax: number) => (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin 2 | -------------------------------------------------------------------------------- /utils/shader.ts: -------------------------------------------------------------------------------- 1 | export const resizeShaderCanvas = (canvas: HTMLCanvasElement, gl: WebGL2RenderingContext, fixedRatio: number | null = null) => { 2 | if (canvas) { 3 | canvas.width = window.innerWidth * (fixedRatio || Math.min(window.devicePixelRatio, 2)) 4 | canvas.height = window.innerHeight * (fixedRatio || Math.min(window.devicePixelRatio, 2)) 5 | canvas.style.width = `${window.innerWidth}px` 6 | canvas.style.height = `${window.innerHeight}px` 7 | 8 | gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) 9 | } 10 | } 11 | 12 | export const createShader = (gl: WebGL2RenderingContext, type: number, code: string) => { 13 | const shader = gl.createShader(type) 14 | 15 | if (!shader) { 16 | throw new Error('Failed to create shader') 17 | } 18 | 19 | gl.shaderSource(shader, code) 20 | gl.compileShader(shader) 21 | 22 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 23 | throw new Error('Failed to compile shader: ' + gl.getShaderInfoLog(shader)) 24 | } 25 | 26 | return shader 27 | } 28 | 29 | export const createShaderProgram = (gl: WebGL2RenderingContext, vertexShaderCode: string, fragmentShaderCode: string) => { 30 | const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderCode) 31 | const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderCode) 32 | 33 | const program = gl.createProgram() 34 | 35 | if (!program) { 36 | throw new Error('Failed to create program') 37 | } 38 | 39 | gl.attachShader(program, vertexShader) 40 | gl.attachShader(program, fragmentShader) 41 | gl.linkProgram(program) 42 | gl.useProgram(program) 43 | 44 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 45 | throw new Error('Failed to link shader program: ' + gl.getProgramInfoLog(program)) 46 | } 47 | 48 | return program 49 | } 50 | -------------------------------------------------------------------------------- /utils/three.ts: -------------------------------------------------------------------------------- 1 | import type { OrthographicCamera, WebGLRenderer } from 'three' 2 | import type { EffectComposer } from 'three/examples/jsm/Addons.js' 3 | 4 | import { PerspectiveCamera } from 'three' 5 | 6 | export const resizeThreeCanvas = ({ 7 | camera, 8 | fov = null, 9 | renderer, 10 | effectComposer = null, 11 | }: { 12 | camera: PerspectiveCamera | OrthographicCamera 13 | fov?: number | null 14 | renderer: WebGLRenderer 15 | effectComposer?: EffectComposer | null 16 | }) => { 17 | if (camera instanceof PerspectiveCamera) { 18 | camera.aspect = window.innerWidth / window.innerHeight 19 | 20 | if (fov) { 21 | camera.fov = fov 22 | } 23 | } 24 | 25 | camera.updateProjectionMatrix() 26 | 27 | renderer.setSize(window.innerWidth, window.innerHeight) 28 | renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) 29 | 30 | if (effectComposer) { 31 | effectComposer.setSize(window.innerWidth, window.innerHeight) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | } 5 | } 6 | --------------------------------------------------------------------------------