@bbki.ng/site 5.5.6 → 5.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/package.json +2 -2
- package/src/blog/components/effect-layer/EffectContextProvider.tsx +26 -9
- package/src/blog/components/effect-layer/EffectLayer.tsx +0 -35
- package/src/blog/components/effect-layer/effects/grain.frag +0 -18
- package/src/blog/components/effect-layer/effects/paper.frag +0 -50
- package/src/blog/components/effect-layer/effects/spiral.frag +0 -99
- package/src/blog/components/effect-layer/effects/watermark.frag +0 -157
- package/src/blog/components/effect-layer/hooks/useFingerprintUniforms.ts +0 -49
- package/src/blog/components/effect-layer/hooks/useRender.ts +0 -80
- package/src/blog/components/effect-layer/hooks/useResolution.ts +0 -19
- package/src/blog/components/effect-layer/hooks/useWatermarkHover.ts +0 -63
- package/src/blog/components/effect-layer/main.frag +0 -27
- package/src/blog/components/effect-layer/shader.vert +0 -9
- package/src/blog/components/effect-layer/shapes/circle.frag +0 -32
- package/src/blog/components/effect-layer/shapes/heart.frag +0 -11
- package/src/blog/components/effect-layer/shapes/spiral.frag +0 -14
- package/src/blog/components/effect-layer/uniforms.ts +0 -62
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbki.ng/site",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.7",
|
|
4
4
|
"description": "code behind bbki.ng",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"react-dom": "^18.0.0",
|
|
15
15
|
"react-router-dom": "6",
|
|
16
16
|
"swr": "^2.2.5",
|
|
17
|
-
"@bbki.ng/ui": "0.2.
|
|
17
|
+
"@bbki.ng/ui": "0.2.7"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@eslint/compat": "^1.0.0",
|
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { ReactNode, useContext, useMemo } from 'react';
|
|
2
2
|
|
|
3
|
-
import { EffectLayer } from '
|
|
3
|
+
import { EffectLayer, grain, paper, spiral, watermark, Effect } from '@bbki.ng/ui';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { GlobalLoadingContext } from '@/context/global_loading_state_provider';
|
|
6
|
+
import { useFingerprint } from '@/hooks/use_fingerprint';
|
|
7
|
+
|
|
8
|
+
const hashStr: string = typeof GLOBAL_COMMIT_HASH === 'string' ? GLOBAL_COMMIT_HASH : '0000000';
|
|
6
9
|
|
|
7
10
|
export const EffectContextProvider = (props: { children: ReactNode }) => {
|
|
11
|
+
const { isLoading, isFontLoading } = useContext(GlobalLoadingContext);
|
|
12
|
+
const { deviceId } = useFingerprint();
|
|
13
|
+
|
|
14
|
+
const effects: Effect[] = useMemo(() => {
|
|
15
|
+
const wmLines = [hashStr];
|
|
16
|
+
if (deviceId) wmLines.push(deviceId);
|
|
17
|
+
wmLines.push('hello world');
|
|
18
|
+
|
|
19
|
+
return [
|
|
20
|
+
grain(),
|
|
21
|
+
paper(),
|
|
22
|
+
spiral({ active: isLoading || isFontLoading }),
|
|
23
|
+
watermark({ lines: wmLines }),
|
|
24
|
+
];
|
|
25
|
+
}, [isLoading, isFontLoading, deviceId]);
|
|
26
|
+
|
|
8
27
|
return (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</>
|
|
14
|
-
</EffectContext.Provider>
|
|
28
|
+
<>
|
|
29
|
+
<EffectLayer effects={effects} />
|
|
30
|
+
{props.children}
|
|
31
|
+
</>
|
|
15
32
|
);
|
|
16
33
|
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Canvas } from '@bbki.ng/ui';
|
|
3
|
-
import frag from './main.frag';
|
|
4
|
-
import vert from './shader.vert';
|
|
5
|
-
import cls from 'classnames';
|
|
6
|
-
import uniforms from './uniforms';
|
|
7
|
-
import { useRender } from '@/components/effect-layer/hooks/useRender';
|
|
8
|
-
|
|
9
|
-
export const EffectLayer = () => {
|
|
10
|
-
const canvasDefaultCls = cls(
|
|
11
|
-
'fixed',
|
|
12
|
-
'top-0',
|
|
13
|
-
'left-0',
|
|
14
|
-
'h-full',
|
|
15
|
-
'h-dvh',
|
|
16
|
-
'pointer-events-none',
|
|
17
|
-
'w-full',
|
|
18
|
-
'z-999'
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
const { onRender } = useRender();
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<Canvas
|
|
25
|
-
className={canvasDefaultCls}
|
|
26
|
-
uniforms={uniforms}
|
|
27
|
-
fragment={frag}
|
|
28
|
-
vertex={vert}
|
|
29
|
-
onRender={onRender}
|
|
30
|
-
style={{
|
|
31
|
-
height: '100lvh',
|
|
32
|
-
}}
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
35
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Integer-based hash, stable on mediump float and immune to precision loss
|
|
2
|
-
float rand(vec2 co) {
|
|
3
|
-
vec2 seed = co * uResolution + fract(uProgress * vec2(12.453, 78.379));
|
|
4
|
-
vec3 p = fract(vec3(seed.xyx) * vec3(443.897, 441.423, 437.195));
|
|
5
|
-
p += dot(p, p.yzx + 19.19);
|
|
6
|
-
return fract((p.x + p.y) * p.z);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
vec4 randGrain(vec2 uv) {
|
|
10
|
-
float n = rand(uv);
|
|
11
|
-
float intensity = (n - 0.5) * 0.05;
|
|
12
|
-
return vec4(vec3(0.0), abs(intensity) + 0.02);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
void drawGrain(vec2 uv) {
|
|
16
|
-
vec4 grain = randGrain(uv);
|
|
17
|
-
gl_FragColor = grain + gl_FragColor * (1.0 - grain.a);
|
|
18
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Fine book paper texture — static, subtle fiber pattern
|
|
2
|
-
// Simulates delicate diffuse micro-fiber scattering on white paper
|
|
3
|
-
|
|
4
|
-
float paperHash(vec2 p) {
|
|
5
|
-
vec3 p3 = fract(vec3(p.xyx) * vec3(443.897, 441.423, 437.195));
|
|
6
|
-
p3 += dot(p3, p3.yzx + 19.19);
|
|
7
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
float paperNoise(vec2 uv) {
|
|
11
|
-
vec2 i = floor(uv);
|
|
12
|
-
vec2 f = fract(uv);
|
|
13
|
-
// smooth interpolation
|
|
14
|
-
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
15
|
-
|
|
16
|
-
float a = paperHash(i);
|
|
17
|
-
float b = paperHash(i + vec2(1.0, 0.0));
|
|
18
|
-
float c = paperHash(i + vec2(0.0, 1.0));
|
|
19
|
-
float d = paperHash(i + vec2(1.0, 1.0));
|
|
20
|
-
|
|
21
|
-
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// fBm with 5 octaves for dense, fine fiber detail
|
|
25
|
-
float paperFBM(vec2 uv) {
|
|
26
|
-
float value = 0.0;
|
|
27
|
-
float amplitude = 0.5;
|
|
28
|
-
for (int i = 0; i < 3; i++) {
|
|
29
|
-
value += amplitude * paperNoise(uv);
|
|
30
|
-
uv *= 2.5;
|
|
31
|
-
amplitude *= 0.4;
|
|
32
|
-
}
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
void drawPaperTexture(vec2 uv) {
|
|
37
|
-
// Scale to physical pixels so texture density stays consistent across screens
|
|
38
|
-
vec2 texCoord = uv * uResolution / uDevicePixelRatio;
|
|
39
|
-
|
|
40
|
-
// Single high-frequency FBM — fine, uniform, no visible banding
|
|
41
|
-
float fiber = paperFBM(texCoord * 3.0);
|
|
42
|
-
|
|
43
|
-
// abs() for full symmetric coverage; low multiplier as quiet base layer
|
|
44
|
-
float alpha = abs(fiber - 0.5) * 0.05;
|
|
45
|
-
|
|
46
|
-
// Soft neutral gray (premultiplied) instead of pure black — avoids dirty look
|
|
47
|
-
float tone = 0.55;
|
|
48
|
-
vec4 paper = vec4(vec3(tone * alpha), alpha);
|
|
49
|
-
gl_FragColor = paper + gl_FragColor * (1.0 - alpha);
|
|
50
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
uniform float uLoading;
|
|
2
|
-
uniform float uSpiralProgress;
|
|
3
|
-
uniform float uSpiralOpacity;
|
|
4
|
-
|
|
5
|
-
const float PI2 = 3.141592653589793 * 2.0;
|
|
6
|
-
|
|
7
|
-
vec3 spiralCurve(float _percent) {
|
|
8
|
-
const float _length = 0.3;
|
|
9
|
-
const float radius = 0.056;
|
|
10
|
-
float t = mod(_percent, 0.25) / 0.25;
|
|
11
|
-
t = mod(_percent, 0.25) - (2.0 * (1.0 - t) * t * -0.0185 + t * t * 0.25);
|
|
12
|
-
float x = _length * sin(PI2 * _percent);
|
|
13
|
-
float y = radius * cos(PI2 * 3.0 * _percent);
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
floor(_percent / 0.25) == 0.0
|
|
17
|
-
|| floor(_percent / 0.25) == 2.0
|
|
18
|
-
) {
|
|
19
|
-
t = t * -1.0;
|
|
20
|
-
}
|
|
21
|
-
float z = radius * sin(PI2 * 2.0 * (_percent - t));
|
|
22
|
-
return vec3(x, y, z);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
vec3 rotateXVec3(vec3 v, float angle) {
|
|
26
|
-
float c = cos(angle);
|
|
27
|
-
float s = sin(angle);
|
|
28
|
-
return vec3(
|
|
29
|
-
v.x,
|
|
30
|
-
v.y * c - v.z * s,
|
|
31
|
-
v.y * s + v.z * c
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
void drawSpiral(vec2 uv) {
|
|
36
|
-
if (uLoading < 0.5) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
float aspect = uResolution.x / uResolution.y;
|
|
41
|
-
vec2 center = vec2(0.5, 0.6);
|
|
42
|
-
vec2 p = uv - center;
|
|
43
|
-
p.x *= aspect;
|
|
44
|
-
|
|
45
|
-
// Fixed visual size: spiral always appears ~120 CSS px wide, regardless of device.
|
|
46
|
-
// After aspect correction, 1 curve unit = uResolution.y / scale physical pixels,
|
|
47
|
-
// so spiral CSS width = 0.6 * cssHeight / scale. Solve for scale:
|
|
48
|
-
float cssHeight = uResolution.y / uDevicePixelRatio;
|
|
49
|
-
float targetCss = 80.0;
|
|
50
|
-
float scale = 0.6 * cssHeight / targetCss;
|
|
51
|
-
p *= scale;
|
|
52
|
-
|
|
53
|
-
// early exit: spiral fits within radius ~0.35 in curve space
|
|
54
|
-
if (length(p) > 0.5) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
float minDist = 1.0;
|
|
59
|
-
|
|
60
|
-
const int SAMPLES = 80;
|
|
61
|
-
vec2 prevProjected;
|
|
62
|
-
for (int i = 0; i < SAMPLES; i++) {
|
|
63
|
-
float pct = float(i) / float(SAMPLES);
|
|
64
|
-
vec3 pos3d = spiralCurve(pct);
|
|
65
|
-
vec3 rotated = rotateXVec3(pos3d, uSpiralProgress);
|
|
66
|
-
vec2 projected = rotated.xy;
|
|
67
|
-
|
|
68
|
-
if (i > 0) {
|
|
69
|
-
// distance from point to line segment
|
|
70
|
-
vec2 ab = projected - prevProjected;
|
|
71
|
-
vec2 ap = p - prevProjected;
|
|
72
|
-
float t = clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0);
|
|
73
|
-
vec2 closest = prevProjected + t * ab;
|
|
74
|
-
float d = distance(p, closest);
|
|
75
|
-
minDist = min(minDist, d);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
prevProjected = projected;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// close the loop: connect last sample back to first
|
|
82
|
-
vec3 firstPos = spiralCurve(0.0);
|
|
83
|
-
vec3 firstRotated = rotateXVec3(firstPos, uSpiralProgress);
|
|
84
|
-
vec2 firstProjected = firstRotated.xy;
|
|
85
|
-
vec2 ab = firstProjected - prevProjected;
|
|
86
|
-
vec2 ap = p - prevProjected;
|
|
87
|
-
float tc = clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0);
|
|
88
|
-
vec2 closest = prevProjected + tc * ab;
|
|
89
|
-
float dc = distance(p, closest);
|
|
90
|
-
minDist = min(minDist, dc);
|
|
91
|
-
|
|
92
|
-
// ~1.2 CSS px line width, device-independent
|
|
93
|
-
float lineWidth = 1.2 * scale / cssHeight;
|
|
94
|
-
float alpha = 1.0 - smoothstep(0.0, lineWidth, minDist);
|
|
95
|
-
|
|
96
|
-
float spiralAlpha = alpha * 0.8 * uSpiralOpacity;
|
|
97
|
-
gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0.0), spiralAlpha);
|
|
98
|
-
gl_FragColor.a = max(gl_FragColor.a, spiralAlpha);
|
|
99
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
// Bitmap-font watermark — 4×5 pixel hex glyphs (0-9, a-f)
|
|
2
|
-
// Rendered at the bottom-left corner; meant to be drawn BEFORE grain
|
|
3
|
-
// so that film-grain noise is composited on top of the text.
|
|
4
|
-
|
|
5
|
-
uniform vec4 uHashChars; // hex digit values for chars 0–3 (each 0.0–15.0)
|
|
6
|
-
uniform vec4 uHashChars2; // hex digit values for chars 4–6 (.w unused)
|
|
7
|
-
uniform vec4 uFpChars1; // fingerprint chars 0–3
|
|
8
|
-
uniform vec4 uFpChars2; // fingerprint chars 4–7
|
|
9
|
-
uniform vec4 uFpChars3; // fingerprint chars 8–11
|
|
10
|
-
uniform vec4 uFpChars4; // fingerprint chars 12–15
|
|
11
|
-
uniform float uWatermarkHover; // 0.0 = idle (#f1f1f1), 1.0 = hovered (black)
|
|
12
|
-
|
|
13
|
-
// Each glyph is a 4-wide × 5-tall bitmap packed into 20 bits of a float.
|
|
14
|
-
// Bit layout (MSB → LSB): row0-col0 … row0-col3, row1-col0 … row4-col3
|
|
15
|
-
// Row 0 = top of glyph. Requires highp for exact integer representation.
|
|
16
|
-
|
|
17
|
-
highp float charBitmap(float c) {
|
|
18
|
-
// .##. #..# #..# #..# .##.
|
|
19
|
-
if (c < 0.5) return 432534.0; // 0
|
|
20
|
-
// .#.. ##.. .#.. .#.. ###.
|
|
21
|
-
if (c < 1.5) return 312398.0; // 1
|
|
22
|
-
// .##. #..# ..#. .#.. ####
|
|
23
|
-
if (c < 2.5) return 430671.0; // 2
|
|
24
|
-
// ###. ...# .##. ...# ###.
|
|
25
|
-
if (c < 3.5) return 923166.0; // 3
|
|
26
|
-
// #..# #..# #### ...# ...#
|
|
27
|
-
if (c < 4.5) return 630545.0; // 4
|
|
28
|
-
// #### #... ###. ...# ###.
|
|
29
|
-
if (c < 5.5) return 1019422.0; // 5
|
|
30
|
-
// .##. #... ###. #..# .##.
|
|
31
|
-
if (c < 6.5) return 429718.0; // 6
|
|
32
|
-
// #### ...# ..#. .#.. .#..
|
|
33
|
-
if (c < 7.5) return 987716.0; // 7
|
|
34
|
-
// .##. #..# .##. #..# .##.
|
|
35
|
-
if (c < 8.5) return 431766.0; // 8
|
|
36
|
-
// .##. #..# .### ...# .##.
|
|
37
|
-
if (c < 9.5) return 431894.0; // 9
|
|
38
|
-
// .##. #..# #### #..# #..#
|
|
39
|
-
if (c < 10.5) return 434073.0; // a
|
|
40
|
-
// ###. #..# ###. #..# ###.
|
|
41
|
-
if (c < 11.5) return 958110.0; // b
|
|
42
|
-
// .### #... #... #... .###
|
|
43
|
-
if (c < 12.5) return 493703.0; // c
|
|
44
|
-
// ###. #..# #..# #..# ###.
|
|
45
|
-
if (c < 13.5) return 956830.0; // d
|
|
46
|
-
// #### #... ###. #... ####
|
|
47
|
-
if (c < 14.5) return 1019535.0; // e
|
|
48
|
-
// #### #... ###. #... #...
|
|
49
|
-
return 1019528.0; // f
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
float sampleChar(float c, vec2 p) {
|
|
53
|
-
// p in bitmap-pixel coords: x ∈ [0,4), y ∈ [0,5), origin = bottom-left
|
|
54
|
-
if (p.x < 0.0 || p.x >= 4.0 || p.y < 0.0 || p.y >= 5.0) return 0.0;
|
|
55
|
-
|
|
56
|
-
highp float bits = charBitmap(c);
|
|
57
|
-
float col = floor(p.x); // 0 = left
|
|
58
|
-
float row = floor(p.y); // 0 = bottom (screen coords)
|
|
59
|
-
// Map to bit position: row 0 (bottom) → bits 3-0, row 4 (top) → bits 19-16
|
|
60
|
-
highp float idx = row * 4.0 + (3.0 - col);
|
|
61
|
-
return mod(floor(bits / exp2(idx)), 2.0);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
float getHashChar(float i) {
|
|
65
|
-
if (i < 0.5) return uHashChars.x;
|
|
66
|
-
if (i < 1.5) return uHashChars.y;
|
|
67
|
-
if (i < 2.5) return uHashChars.z;
|
|
68
|
-
if (i < 3.5) return uHashChars.w;
|
|
69
|
-
if (i < 4.5) return uHashChars2.x;
|
|
70
|
-
if (i < 5.5) return uHashChars2.y;
|
|
71
|
-
return uHashChars2.z;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
float getFpChar(float i) {
|
|
75
|
-
if (i < 0.5) return uFpChars1.x;
|
|
76
|
-
if (i < 1.5) return uFpChars1.y;
|
|
77
|
-
if (i < 2.5) return uFpChars1.z;
|
|
78
|
-
if (i < 3.5) return uFpChars1.w;
|
|
79
|
-
if (i < 4.5) return uFpChars2.x;
|
|
80
|
-
if (i < 5.5) return uFpChars2.y;
|
|
81
|
-
if (i < 6.5) return uFpChars2.z;
|
|
82
|
-
if (i < 7.5) return uFpChars2.w;
|
|
83
|
-
if (i < 8.5) return uFpChars3.x;
|
|
84
|
-
if (i < 9.5) return uFpChars3.y;
|
|
85
|
-
if (i < 10.5) return uFpChars3.z;
|
|
86
|
-
if (i < 11.5) return uFpChars3.w;
|
|
87
|
-
if (i < 12.5) return uFpChars4.x;
|
|
88
|
-
if (i < 13.5) return uFpChars4.y;
|
|
89
|
-
if (i < 14.5) return uFpChars4.z;
|
|
90
|
-
return uFpChars4.w;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
void drawWatermark(vec2 uv) {
|
|
94
|
-
// Work in CSS (logical) pixels
|
|
95
|
-
vec2 pixel = gl_FragCoord.xy / uDevicePixelRatio;
|
|
96
|
-
|
|
97
|
-
// Layout constants
|
|
98
|
-
float scale = 2.0; // each bitmap pixel = 2 CSS px
|
|
99
|
-
float charW = 4.0 * scale; // 8 px per glyph
|
|
100
|
-
float charH = 5.0 * scale; // 10 px per glyph (≈ 12 px visual)
|
|
101
|
-
float gap = 3.0; // 3 px gap between glyphs
|
|
102
|
-
float cellW = charW + gap; // 11 px per cell
|
|
103
|
-
|
|
104
|
-
float padX = 16.0;
|
|
105
|
-
float padY = 16.0;
|
|
106
|
-
float lineGap = 6.0; // 6 px gap between lines
|
|
107
|
-
|
|
108
|
-
// ----- Line 1 (bottom): Git hash - 7 chars -----
|
|
109
|
-
{
|
|
110
|
-
float nChars = 7.0;
|
|
111
|
-
vec2 pos = pixel - vec2(padX, padY);
|
|
112
|
-
float totalW = nChars * cellW - gap;
|
|
113
|
-
|
|
114
|
-
if (pos.x >= 0.0 && pos.x < totalW && pos.y >= 0.0 && pos.y < charH) {
|
|
115
|
-
float ci = floor(pos.x / cellW);
|
|
116
|
-
if (ci < nChars) {
|
|
117
|
-
float localX = pos.x - ci * cellW;
|
|
118
|
-
if (localX < charW) {
|
|
119
|
-
vec2 bp = vec2(localX, pos.y) / scale;
|
|
120
|
-
float charCode = getHashChar(ci);
|
|
121
|
-
float on = sampleChar(charCode, bp);
|
|
122
|
-
if (on > 0.5) {
|
|
123
|
-
float a = 0.4;
|
|
124
|
-
vec3 col = mix(vec3(0.945), vec3(0.0), uWatermarkHover);
|
|
125
|
-
gl_FragColor = vec4(col * a, a) + gl_FragColor * (1.0 - a);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ----- Line 2 (above): Fingerprint - 16 chars -----
|
|
134
|
-
{
|
|
135
|
-
float nChars = 16.0;
|
|
136
|
-
float fpPadY = padY + charH + lineGap;
|
|
137
|
-
vec2 pos = pixel - vec2(padX, fpPadY);
|
|
138
|
-
float totalW = nChars * cellW - gap;
|
|
139
|
-
|
|
140
|
-
if (pos.x >= 0.0 && pos.x < totalW && pos.y >= 0.0 && pos.y < charH) {
|
|
141
|
-
float ci = floor(pos.x / cellW);
|
|
142
|
-
if (ci < nChars) {
|
|
143
|
-
float localX = pos.x - ci * cellW;
|
|
144
|
-
if (localX < charW) {
|
|
145
|
-
vec2 bp = vec2(localX, pos.y) / scale;
|
|
146
|
-
float charCode = getFpChar(ci);
|
|
147
|
-
float on = sampleChar(charCode, bp);
|
|
148
|
-
if (on > 0.5) {
|
|
149
|
-
float a = 0.4;
|
|
150
|
-
vec3 col = mix(vec3(0.945), vec3(0.0), uWatermarkHover);
|
|
151
|
-
gl_FragColor = vec4(col * a, a) + gl_FragColor * (1.0 - a);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useRef } from 'react';
|
|
2
|
-
import { useFingerprint } from '@/hooks/use_fingerprint';
|
|
3
|
-
|
|
4
|
-
export const useFingerprintUniforms = () => {
|
|
5
|
-
const { deviceId, loading } = useFingerprint();
|
|
6
|
-
|
|
7
|
-
const charsRef = useRef<number[] | null>(null);
|
|
8
|
-
const appliedRef = useRef(false);
|
|
9
|
-
|
|
10
|
-
if (!loading && deviceId && !charsRef.current) {
|
|
11
|
-
const hash = deviceId;
|
|
12
|
-
const chars = [...hash].map(c => parseInt(c, 16) || 0);
|
|
13
|
-
while (chars.length < 16) chars.push(0);
|
|
14
|
-
charsRef.current = chars;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const updateFingerprintUniforms = (inst: any) => {
|
|
18
|
-
if (!inst?.uniforms || appliedRef.current || !charsRef.current) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const chars = charsRef.current;
|
|
23
|
-
|
|
24
|
-
// Mutate the existing arrays instead of replacing them
|
|
25
|
-
inst.uniforms.uFpChars1.value[0] = chars[0];
|
|
26
|
-
inst.uniforms.uFpChars1.value[1] = chars[1];
|
|
27
|
-
inst.uniforms.uFpChars1.value[2] = chars[2];
|
|
28
|
-
inst.uniforms.uFpChars1.value[3] = chars[3];
|
|
29
|
-
|
|
30
|
-
inst.uniforms.uFpChars2.value[0] = chars[4];
|
|
31
|
-
inst.uniforms.uFpChars2.value[1] = chars[5];
|
|
32
|
-
inst.uniforms.uFpChars2.value[2] = chars[6];
|
|
33
|
-
inst.uniforms.uFpChars2.value[3] = chars[7];
|
|
34
|
-
|
|
35
|
-
inst.uniforms.uFpChars3.value[0] = chars[8];
|
|
36
|
-
inst.uniforms.uFpChars3.value[1] = chars[9];
|
|
37
|
-
inst.uniforms.uFpChars3.value[2] = chars[10];
|
|
38
|
-
inst.uniforms.uFpChars3.value[3] = chars[11];
|
|
39
|
-
|
|
40
|
-
inst.uniforms.uFpChars4.value[0] = chars[12];
|
|
41
|
-
inst.uniforms.uFpChars4.value[1] = chars[13];
|
|
42
|
-
inst.uniforms.uFpChars4.value[2] = chars[14];
|
|
43
|
-
inst.uniforms.uFpChars4.value[3] = chars[15];
|
|
44
|
-
|
|
45
|
-
appliedRef.current = true;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return { updateFingerprintUniforms };
|
|
49
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { useCallback, useContext, useRef } from 'react';
|
|
2
|
-
import { useMousePosition } from '@/hooks/use_mouse_position';
|
|
3
|
-
import { useResolution } from '@/components/effect-layer/hooks/useResolution';
|
|
4
|
-
import { useWatermarkHover } from '@/components/effect-layer/hooks/useWatermarkHover';
|
|
5
|
-
import { useFingerprintUniforms } from '@/components/effect-layer/hooks/useFingerprintUniforms';
|
|
6
|
-
import { GlobalLoadingContext } from '@/context/global_loading_state_provider';
|
|
7
|
-
|
|
8
|
-
const SPIRAL_ACCEL = 0.005;
|
|
9
|
-
const SPIRAL_MAX_SPEED = 0.2;
|
|
10
|
-
const FADE_SPEED = 0.04; // ~0→1 in 25 frames (~400ms at 60fps)
|
|
11
|
-
const MIN_DURATION = 800;
|
|
12
|
-
|
|
13
|
-
export const useRender = () => {
|
|
14
|
-
const pos = useMousePosition();
|
|
15
|
-
const resolution = useResolution();
|
|
16
|
-
const { isLoading, isFontLoading } = useContext(GlobalLoadingContext);
|
|
17
|
-
const loadingRef = useRef(false);
|
|
18
|
-
loadingRef.current = isLoading || isFontLoading;
|
|
19
|
-
|
|
20
|
-
const spiralSpeedRef = useRef(0);
|
|
21
|
-
const spiralOpacityRef = useRef(0);
|
|
22
|
-
const loadingStartRef = useRef(0);
|
|
23
|
-
const wasLoadingRef = useRef(false);
|
|
24
|
-
|
|
25
|
-
const { updateWatermarkHover } = useWatermarkHover();
|
|
26
|
-
const { updateFingerprintUniforms } = useFingerprintUniforms();
|
|
27
|
-
|
|
28
|
-
const onRender = useCallback((inst: any) => {
|
|
29
|
-
if (inst == null) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
inst.uniforms.uResolution.value[0] = inst.gl.canvas.width;
|
|
34
|
-
inst.uniforms.uResolution.value[1] = inst.gl.canvas.height;
|
|
35
|
-
|
|
36
|
-
inst.uniforms.uMouse.value[0] = pos.current.x;
|
|
37
|
-
inst.uniforms.uMouse.value[1] = pos.current.y;
|
|
38
|
-
|
|
39
|
-
const now = performance.now();
|
|
40
|
-
const loading = loadingRef.current;
|
|
41
|
-
|
|
42
|
-
// track when loading starts
|
|
43
|
-
if (loading && !wasLoadingRef.current) {
|
|
44
|
-
loadingStartRef.current = now;
|
|
45
|
-
}
|
|
46
|
-
wasLoadingRef.current = loading;
|
|
47
|
-
|
|
48
|
-
// determine if spiral should be visible (loading OR within min duration)
|
|
49
|
-
const elapsed = now - loadingStartRef.current;
|
|
50
|
-
const inMinDuration = !loading && loadingStartRef.current > 0 && elapsed < MIN_DURATION;
|
|
51
|
-
const shouldShow = loading || inMinDuration;
|
|
52
|
-
|
|
53
|
-
// fade in/out
|
|
54
|
-
if (shouldShow) {
|
|
55
|
-
spiralOpacityRef.current = Math.min(1, spiralOpacityRef.current + FADE_SPEED);
|
|
56
|
-
} else {
|
|
57
|
-
spiralOpacityRef.current = Math.max(0, spiralOpacityRef.current - FADE_SPEED);
|
|
58
|
-
if (spiralOpacityRef.current === 0) {
|
|
59
|
-
loadingStartRef.current = 0;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
inst.uniforms.uLoading.value[0] = spiralOpacityRef.current > 0 ? 1.0 : 0.0;
|
|
64
|
-
inst.uniforms.uSpiralOpacity.value[0] = spiralOpacityRef.current;
|
|
65
|
-
|
|
66
|
-
if (spiralOpacityRef.current > 0) {
|
|
67
|
-
// accelerate to max speed before fade out
|
|
68
|
-
const accel = inMinDuration ? SPIRAL_ACCEL * 4 : SPIRAL_ACCEL;
|
|
69
|
-
spiralSpeedRef.current = Math.min(SPIRAL_MAX_SPEED, spiralSpeedRef.current + accel);
|
|
70
|
-
} else {
|
|
71
|
-
spiralSpeedRef.current = 0;
|
|
72
|
-
}
|
|
73
|
-
inst.uniforms.uSpiralProgress.value[0] += spiralSpeedRef.current;
|
|
74
|
-
|
|
75
|
-
updateWatermarkHover(inst);
|
|
76
|
-
updateFingerprintUniforms(inst);
|
|
77
|
-
}, []);
|
|
78
|
-
|
|
79
|
-
return { onRender };
|
|
80
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
export const useResolution = () => {
|
|
4
|
-
const resolution = useRef<[number, number]>([window.innerWidth, window.innerHeight]);
|
|
5
|
-
|
|
6
|
-
const updateResolution = () => {
|
|
7
|
-
resolution.current = [window.innerWidth, window.innerHeight];
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
window.addEventListener('resize', updateResolution);
|
|
12
|
-
|
|
13
|
-
return () => {
|
|
14
|
-
window.removeEventListener('resize', updateResolution);
|
|
15
|
-
};
|
|
16
|
-
}, []);
|
|
17
|
-
|
|
18
|
-
return resolution;
|
|
19
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { useRef } from 'react';
|
|
2
|
-
import { useMousePosition } from '@/hooks/use_mouse_position';
|
|
3
|
-
|
|
4
|
-
// Watermark layout constants (must match watermark.frag)
|
|
5
|
-
const WM_PAD_X = 16;
|
|
6
|
-
const WM_PAD_Y = 16;
|
|
7
|
-
const WM_SCALE = 2;
|
|
8
|
-
const WM_CHAR_W = 4 * WM_SCALE; // 8
|
|
9
|
-
const WM_CHAR_H = 5 * WM_SCALE; // 10
|
|
10
|
-
const WM_GAP = 3;
|
|
11
|
-
const WM_CELL_W = WM_CHAR_W + WM_GAP; // 11
|
|
12
|
-
|
|
13
|
-
// Git hash line (bottom): 7 chars
|
|
14
|
-
const WM_N_CHARS = 7;
|
|
15
|
-
const WM_TOTAL_W = WM_N_CHARS * WM_CELL_W - WM_GAP; // 74
|
|
16
|
-
|
|
17
|
-
// Fingerprint line (above): 16 chars
|
|
18
|
-
const FP_N_CHARS = 16;
|
|
19
|
-
const FP_TOTAL_W = FP_N_CHARS * WM_CELL_W - WM_GAP; // 173
|
|
20
|
-
const LINE_GAP = 6; // vertical gap between lines
|
|
21
|
-
|
|
22
|
-
// Combined bounds
|
|
23
|
-
const MAX_W = Math.max(WM_TOTAL_W, FP_TOTAL_W);
|
|
24
|
-
const TOTAL_H = WM_CHAR_H * 2 + LINE_GAP; // 26
|
|
25
|
-
|
|
26
|
-
const WM_HOVER_PADDING = 4; // extra hit-area padding (px)
|
|
27
|
-
const WM_HOVER_SPEED = 0.06; // ~0→1 in 17 frames (~280ms at 60fps)
|
|
28
|
-
|
|
29
|
-
export const useWatermarkHover = () => {
|
|
30
|
-
const pos = useMousePosition();
|
|
31
|
-
const hoverRef = useRef(0);
|
|
32
|
-
|
|
33
|
-
const updateWatermarkHover = (inst: any) => {
|
|
34
|
-
// Detect mouse inside watermark bounding box (CSS pixels)
|
|
35
|
-
const viewportH = inst.gl.canvas.height / window.devicePixelRatio;
|
|
36
|
-
const mx = pos.current.x;
|
|
37
|
-
const my = pos.current.y;
|
|
38
|
-
// Convert clientY (top-down) to GL-style bottom-up for comparison
|
|
39
|
-
const myFromBottom = viewportH - my;
|
|
40
|
-
|
|
41
|
-
// The fingerprint line is above the git hash line
|
|
42
|
-
// Git hash starts at WM_PAD_Y from bottom
|
|
43
|
-
// Fingerprint starts at WM_PAD_Y + WM_CHAR_H + LINE_GAP from bottom
|
|
44
|
-
const fpPadY = WM_PAD_Y + WM_CHAR_H + LINE_GAP;
|
|
45
|
-
const maxPadY = Math.max(WM_PAD_Y, fpPadY);
|
|
46
|
-
|
|
47
|
-
const inWatermark =
|
|
48
|
-
mx >= WM_PAD_X - WM_HOVER_PADDING &&
|
|
49
|
-
mx <= WM_PAD_X + MAX_W + WM_HOVER_PADDING &&
|
|
50
|
-
myFromBottom >= WM_PAD_Y - WM_HOVER_PADDING &&
|
|
51
|
-
myFromBottom <= maxPadY + WM_CHAR_H + WM_HOVER_PADDING;
|
|
52
|
-
|
|
53
|
-
if (inWatermark) {
|
|
54
|
-
hoverRef.current = Math.min(1, hoverRef.current + WM_HOVER_SPEED);
|
|
55
|
-
} else {
|
|
56
|
-
hoverRef.current = Math.max(0, hoverRef.current - WM_HOVER_SPEED);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
inst.uniforms.uWatermarkHover.value[0] = hoverRef.current;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
return { updateWatermarkHover };
|
|
63
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
precision highp float;
|
|
2
|
-
|
|
3
|
-
uniform vec2 uResolution;
|
|
4
|
-
uniform float uProgress;
|
|
5
|
-
uniform float uDevicePixelRatio;
|
|
6
|
-
|
|
7
|
-
#define DefaultColor vec4(0.0, 0.0, 0.0, 0.0)
|
|
8
|
-
|
|
9
|
-
#include "effects/grain.frag"
|
|
10
|
-
#include "effects/paper.frag"
|
|
11
|
-
#include "effects/spiral.frag"
|
|
12
|
-
#include "effects/watermark.frag"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
void main() {
|
|
16
|
-
vec2 uv = gl_FragCoord.xy / uResolution;
|
|
17
|
-
|
|
18
|
-
gl_FragColor = DefaultColor;
|
|
19
|
-
|
|
20
|
-
drawWatermark(uv);
|
|
21
|
-
|
|
22
|
-
drawPaperTexture(uv);
|
|
23
|
-
|
|
24
|
-
drawGrain(uv);
|
|
25
|
-
|
|
26
|
-
drawSpiral(uv);
|
|
27
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
uniform vec2 uMouse;
|
|
2
|
-
|
|
3
|
-
vec4 sdfCircle(vec2 uv, float r, vec2 center) {
|
|
4
|
-
float x = uv.x;
|
|
5
|
-
float y = uv.y;
|
|
6
|
-
|
|
7
|
-
float d = distance(vec2(x, y), center) - r;
|
|
8
|
-
|
|
9
|
-
return d > 0. ? DefaultColor : vec4(1.0, 0.0, 1.0, 1.0);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
vec4 sdfSquare(vec2 uv, vec4 rect) {
|
|
13
|
-
float x = uv.x;
|
|
14
|
-
float y = uv.y;
|
|
15
|
-
|
|
16
|
-
float dx = max(abs(x - rect.x) - rect.z, 0.);
|
|
17
|
-
float dy = max(abs(y - rect.y) - rect.w, 0.);
|
|
18
|
-
|
|
19
|
-
float d = max(dx, dy);
|
|
20
|
-
|
|
21
|
-
return d > 0. ? DefaultColor : vec4(1.0, 0.0, 1.0, 1.0);
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
void drawCircleUnderMouse(vec2 uv, float r) {
|
|
26
|
-
// flip uMouse vertically
|
|
27
|
-
vec2 normalizedMouse = vec2(uMouse.x, uResolution.y - uMouse.y) * uDevicePixelRatio / uResolution;
|
|
28
|
-
|
|
29
|
-
// sdfCircle transition to sdfSquare
|
|
30
|
-
|
|
31
|
-
// gl_FragColor = sdfSquare(uv, vec4(normalizedMouse, 0.1, 0.1));
|
|
32
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
void cosCurve(vec2 uv) {
|
|
2
|
-
float curve = 0.1 * cos((19.25 * uv.x + 3.141592654 * 0.5) + (2.0 * uProgress));
|
|
3
|
-
|
|
4
|
-
vec2 p = uv - 0.5;
|
|
5
|
-
|
|
6
|
-
float d = length(p);
|
|
7
|
-
|
|
8
|
-
gl_FragColor = d > curve ? vec4(1.0) : vec4(0.0);
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
void spiral(vec2 uv) {
|
|
13
|
-
cosCurve(uv);
|
|
14
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
const hashStr: string = typeof GLOBAL_COMMIT_HASH === 'string' ? GLOBAL_COMMIT_HASH : '0000000';
|
|
2
|
-
const hc = [...hashStr].map(c => parseInt(c, 16) || 0);
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
uResolution: {
|
|
6
|
-
type: 'vec2',
|
|
7
|
-
value: [innerWidth, innerHeight],
|
|
8
|
-
},
|
|
9
|
-
uDevicePixelRatio: {
|
|
10
|
-
type: 'float',
|
|
11
|
-
value: [devicePixelRatio],
|
|
12
|
-
},
|
|
13
|
-
pi: {
|
|
14
|
-
type: 'float',
|
|
15
|
-
value: [Math.PI],
|
|
16
|
-
},
|
|
17
|
-
uMouse: {
|
|
18
|
-
type: 'vec2',
|
|
19
|
-
value: [0, 0],
|
|
20
|
-
},
|
|
21
|
-
uLoading: {
|
|
22
|
-
type: 'float',
|
|
23
|
-
value: [0.0],
|
|
24
|
-
},
|
|
25
|
-
uSpiralProgress: {
|
|
26
|
-
type: 'float',
|
|
27
|
-
value: [0.0],
|
|
28
|
-
},
|
|
29
|
-
uSpiralOpacity: {
|
|
30
|
-
type: 'float',
|
|
31
|
-
value: [0.0],
|
|
32
|
-
},
|
|
33
|
-
uHashChars: {
|
|
34
|
-
type: 'vec4',
|
|
35
|
-
value: [hc[0] ?? 0, hc[1] ?? 0, hc[2] ?? 0, hc[3] ?? 0],
|
|
36
|
-
},
|
|
37
|
-
uHashChars2: {
|
|
38
|
-
type: 'vec4',
|
|
39
|
-
value: [hc[4] ?? 0, hc[5] ?? 0, hc[6] ?? 0, 0],
|
|
40
|
-
},
|
|
41
|
-
// Fingerprint hash uniforms (16 characters, split into 4 vec4)
|
|
42
|
-
uFpChars1: {
|
|
43
|
-
type: 'vec4',
|
|
44
|
-
value: [0, 0, 0, 0],
|
|
45
|
-
},
|
|
46
|
-
uFpChars2: {
|
|
47
|
-
type: 'vec4',
|
|
48
|
-
value: [0, 0, 0, 0],
|
|
49
|
-
},
|
|
50
|
-
uFpChars3: {
|
|
51
|
-
type: 'vec4',
|
|
52
|
-
value: [0, 0, 0, 0],
|
|
53
|
-
},
|
|
54
|
-
uFpChars4: {
|
|
55
|
-
type: 'vec4',
|
|
56
|
-
value: [0, 0, 0, 0],
|
|
57
|
-
},
|
|
58
|
-
uWatermarkHover: {
|
|
59
|
-
type: 'float',
|
|
60
|
-
value: [0.0],
|
|
61
|
-
},
|
|
62
|
-
};
|