@elyri/motion 0.0.0-stage → 0.0.1

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.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -2
  3. package/dist/chunk-A7JCTGZO.js +230 -0
  4. package/dist/chunk-A7JCTGZO.js.map +1 -0
  5. package/dist/core.cjs +238 -0
  6. package/dist/core.cjs.map +1 -0
  7. package/dist/core.d.cts +77 -0
  8. package/dist/core.d.ts +77 -0
  9. package/dist/core.js +3 -0
  10. package/dist/core.js.map +1 -0
  11. package/dist/index.cjs +4288 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.css +1118 -0
  14. package/dist/index.css.map +1 -0
  15. package/dist/index.d.cts +532 -0
  16. package/dist/index.d.ts +532 -0
  17. package/dist/index.js +4029 -0
  18. package/dist/index.js.map +1 -0
  19. package/package.json +60 -3
  20. package/src/components/animations/CountUp/CountUp.css +4 -0
  21. package/src/components/animations/CountUp/CountUp.tsx +100 -0
  22. package/src/components/animations/CountUp/index.ts +2 -0
  23. package/src/components/animations/FadeIn/FadeIn.css +43 -0
  24. package/src/components/animations/FadeIn/FadeIn.tsx +72 -0
  25. package/src/components/animations/FadeIn/index.ts +2 -0
  26. package/src/components/animations/Magnetic/Magnetic.css +11 -0
  27. package/src/components/animations/Magnetic/Magnetic.tsx +107 -0
  28. package/src/components/animations/Magnetic/index.ts +2 -0
  29. package/src/components/animations/ScaleIn/ScaleIn.css +24 -0
  30. package/src/components/animations/ScaleIn/ScaleIn.tsx +65 -0
  31. package/src/components/animations/ScaleIn/index.ts +2 -0
  32. package/src/components/animations/Spotlight/Spotlight.css +93 -0
  33. package/src/components/animations/Spotlight/Spotlight.tsx +100 -0
  34. package/src/components/animations/Spotlight/index.ts +2 -0
  35. package/src/components/animations/Tilt/Tilt.css +35 -0
  36. package/src/components/animations/Tilt/Tilt.tsx +123 -0
  37. package/src/components/animations/Tilt/index.ts +2 -0
  38. package/src/components/animations/index.ts +12 -0
  39. package/src/components/backgrounds/Aurora/Aurora.css +33 -0
  40. package/src/components/backgrounds/Aurora/Aurora.tsx +147 -0
  41. package/src/components/backgrounds/Aurora/index.ts +2 -0
  42. package/src/components/backgrounds/Caustics/Caustics.css +34 -0
  43. package/src/components/backgrounds/Caustics/Caustics.tsx +142 -0
  44. package/src/components/backgrounds/Caustics/index.ts +2 -0
  45. package/src/components/backgrounds/Contours/Contours.css +42 -0
  46. package/src/components/backgrounds/Contours/Contours.tsx +147 -0
  47. package/src/components/backgrounds/Contours/index.ts +2 -0
  48. package/src/components/backgrounds/DotField/DotField.css +44 -0
  49. package/src/components/backgrounds/DotField/DotField.tsx +135 -0
  50. package/src/components/backgrounds/DotField/index.ts +2 -0
  51. package/src/components/backgrounds/DragonScales/DragonScales.css +44 -0
  52. package/src/components/backgrounds/DragonScales/DragonScales.tsx +250 -0
  53. package/src/components/backgrounds/DragonScales/index.ts +2 -0
  54. package/src/components/backgrounds/Filaments/Filaments.css +38 -0
  55. package/src/components/backgrounds/Filaments/Filaments.tsx +138 -0
  56. package/src/components/backgrounds/Filaments/index.ts +2 -0
  57. package/src/components/backgrounds/FlipTiles/FlipTiles.css +44 -0
  58. package/src/components/backgrounds/FlipTiles/FlipTiles.tsx +368 -0
  59. package/src/components/backgrounds/FlipTiles/index.ts +2 -0
  60. package/src/components/backgrounds/Halo/Halo.css +43 -0
  61. package/src/components/backgrounds/Halo/Halo.tsx +122 -0
  62. package/src/components/backgrounds/Halo/index.ts +2 -0
  63. package/src/components/backgrounds/MeshGradient/MeshGradient.css +43 -0
  64. package/src/components/backgrounds/MeshGradient/MeshGradient.tsx +195 -0
  65. package/src/components/backgrounds/MeshGradient/index.ts +2 -0
  66. package/src/components/backgrounds/PixelVortex/PixelVortex.css +44 -0
  67. package/src/components/backgrounds/PixelVortex/PixelVortex.tsx +278 -0
  68. package/src/components/backgrounds/PixelVortex/index.ts +2 -0
  69. package/src/components/backgrounds/Planet/Planet.css +39 -0
  70. package/src/components/backgrounds/Planet/Planet.tsx +736 -0
  71. package/src/components/backgrounds/Planet/index.ts +2 -0
  72. package/src/components/backgrounds/SilkWaves/SilkWaves.css +44 -0
  73. package/src/components/backgrounds/SilkWaves/SilkWaves.tsx +144 -0
  74. package/src/components/backgrounds/SilkWaves/index.ts +2 -0
  75. package/src/components/backgrounds/Velvet/Velvet.css +35 -0
  76. package/src/components/backgrounds/Velvet/Velvet.tsx +246 -0
  77. package/src/components/backgrounds/Velvet/index.ts +2 -0
  78. package/src/components/backgrounds/index.ts +26 -0
  79. package/src/components/index.ts +4 -0
  80. package/src/components/media/AsciiImage/AsciiImage.css +16 -0
  81. package/src/components/media/AsciiImage/AsciiImage.tsx +366 -0
  82. package/src/components/media/AsciiImage/index.ts +2 -0
  83. package/src/components/media/index.ts +2 -0
  84. package/src/components/text/GradientText/GradientText.css +32 -0
  85. package/src/components/text/GradientText/GradientText.tsx +27 -0
  86. package/src/components/text/GradientText/index.ts +2 -0
  87. package/src/components/text/Marquee/Marquee.css +91 -0
  88. package/src/components/text/Marquee/Marquee.tsx +130 -0
  89. package/src/components/text/Marquee/index.ts +2 -0
  90. package/src/components/text/ParticleText/ParticleText.css +16 -0
  91. package/src/components/text/ParticleText/ParticleText.tsx +279 -0
  92. package/src/components/text/ParticleText/index.ts +2 -0
  93. package/src/components/text/RotatingText/RotatingText.css +133 -0
  94. package/src/components/text/RotatingText/RotatingText.tsx +112 -0
  95. package/src/components/text/RotatingText/index.ts +2 -0
  96. package/src/components/text/ScrambleText/ScrambleText.css +48 -0
  97. package/src/components/text/ScrambleText/ScrambleText.tsx +141 -0
  98. package/src/components/text/ScrambleText/index.ts +2 -0
  99. package/src/components/text/ScrollMarquee/ScrollMarquee.css +33 -0
  100. package/src/components/text/ScrollMarquee/ScrollMarquee.tsx +110 -0
  101. package/src/components/text/ScrollMarquee/index.ts +2 -0
  102. package/src/components/text/ScrollReveal/ScrollReveal.css +51 -0
  103. package/src/components/text/ScrollReveal/ScrollReveal.tsx +186 -0
  104. package/src/components/text/ScrollReveal/index.ts +2 -0
  105. package/src/components/text/SplitReveal/SplitReveal.css +52 -0
  106. package/src/components/text/SplitReveal/SplitReveal.tsx +127 -0
  107. package/src/components/text/SplitReveal/index.ts +2 -0
  108. package/src/components/text/Typewriter/Typewriter.css +37 -0
  109. package/src/components/text/Typewriter/Typewriter.tsx +80 -0
  110. package/src/components/text/Typewriter/index.ts +2 -0
  111. package/src/components/text/WaveText/WaveText.css +28 -0
  112. package/src/components/text/WaveText/WaveText.tsx +52 -0
  113. package/src/components/text/WaveText/index.ts +2 -0
  114. package/src/components/text/index.ts +20 -0
@@ -0,0 +1,2 @@
1
+ export { Planet } from './Planet';
2
+ export type { PlanetKind, PlanetProps } from './Planet';
@@ -0,0 +1,44 @@
1
+ .elyri-silk-waves {
2
+ --elyri-silk-waves-color: #9a6424;
3
+
4
+ position: relative;
5
+ isolation: isolate;
6
+ overflow: hidden;
7
+ /* WebGL 不可用或尚未绘制时的底色 */
8
+ background: radial-gradient(
9
+ 120% 90% at 40% 30%,
10
+ color-mix(in srgb, var(--elyri-silk-waves-color) 45%, #000),
11
+ #070503 75%
12
+ );
13
+ }
14
+
15
+ /* 浅色主题:兜底底色同步翻转为浅色,避免就绪前闪深色 */
16
+ .elyri-silk-waves[data-scheme='light'] {
17
+ background: radial-gradient(
18
+ 120% 90% at 40% 30%,
19
+ #ffffff,
20
+ color-mix(in srgb, var(--elyri-silk-waves-color) 18%, #ffffff) 75%
21
+ );
22
+ }
23
+
24
+ .elyri-silk-waves__canvas {
25
+ position: absolute;
26
+ inset: 0;
27
+ z-index: 0;
28
+ display: block;
29
+ width: 100%;
30
+ height: 100%;
31
+ opacity: 0;
32
+ pointer-events: none;
33
+ transition: opacity var(--elyri-duration-slow) var(--elyri-ease-out);
34
+ }
35
+
36
+ .elyri-silk-waves__canvas[data-ready] {
37
+ opacity: 1;
38
+ }
39
+
40
+ .elyri-silk-waves__content {
41
+ position: relative;
42
+ z-index: 1;
43
+ height: 100%;
44
+ }
@@ -0,0 +1,144 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+
3
+ import { cn, hexToLinearRgb, useColorScheme, useShaderCanvas } from '../../../core';
4
+
5
+ import './SilkWaves.css';
6
+
7
+ export interface SilkWavesProps {
8
+ children?: ReactNode;
9
+ className?: string;
10
+ /** 丝绸本体色(hex) */
11
+ color?: string;
12
+ /** 光泽高光色(hex) */
13
+ highlightColor?: string;
14
+ /** 褶皱密度倍率 */
15
+ folds?: number;
16
+ /** 动画速度倍率 */
17
+ speed?: number;
18
+ /** 光源是否跟随指针 */
19
+ interactive?: boolean;
20
+ }
21
+
22
+ // 丝绸:多组正弦波经过域扭曲叠成连续起伏的高度场,
23
+ // 用柔和漫反射 + 锐利高光 + 线形灯带反射模拟缎面光泽,褶皱谷底做遮蔽。
24
+ const FRAGMENT_SHADER = `
25
+ precision highp float;
26
+
27
+ uniform vec2 uResolution;
28
+ uniform float uTime;
29
+ uniform vec2 uPointer;
30
+ uniform vec3 uBase;
31
+ uniform vec3 uHighlight;
32
+ uniform float uFolds;
33
+ uniform float uScheme;
34
+
35
+ float hash(vec2 p) {
36
+ p = fract(p * vec2(123.34, 456.21));
37
+ p += dot(p, p + 45.32);
38
+ return fract(p.x * p.y);
39
+ }
40
+
41
+ float silk(vec2 p, float t) {
42
+ vec2 q = p;
43
+ q.x += 0.55 * sin(q.y * 1.3 + t * 0.3);
44
+ q.y += 0.45 * sin(q.x * 1.1 - t * 0.25);
45
+ q.x += 0.2 * sin(q.y * 2.3 - t * 0.2);
46
+ return sin(q.x * 1.6 + q.y * 0.9 + t * 0.2) * 0.5
47
+ + sin(q.x * 0.7 - q.y * 1.9 - t * 0.15) * 0.35
48
+ + sin((q.x + q.y) * 2.7 + t * 0.35) * 0.12;
49
+ }
50
+
51
+ vec3 aces(vec3 x) {
52
+ return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);
53
+ }
54
+
55
+ void main() {
56
+ vec2 frag = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y);
57
+ float minRes = min(uResolution.x, uResolution.y);
58
+ vec2 p = frag / minRes * 2.6 * uFolds;
59
+ float t = uTime;
60
+
61
+ float e = 0.01;
62
+ float h = silk(p, t);
63
+ vec2 grad = vec2(silk(p + vec2(e, 0.0), t) - silk(p - vec2(e, 0.0), t), silk(p + vec2(0.0, e), t) - silk(p - vec2(0.0, e), t)) / (2.0 * e);
64
+ vec3 n = normalize(vec3(-grad * 0.55, 1.0));
65
+
66
+ vec3 V = vec3(0.0, 0.0, 1.0);
67
+ vec3 toLight = vec3(uPointer - frag, minRes * 0.7);
68
+ float dist = length(toLight);
69
+ vec3 L = toLight / dist;
70
+ vec3 H = normalize(L + V);
71
+ float atten = 1.0 / (1.0 + 1.2 * pow(dist / minRes, 2.0));
72
+ float NdotL = max(dot(n, L), 0.0);
73
+ float NdotH = max(dot(n, H), 0.0);
74
+ float NdotV = max(n.z, 0.0);
75
+ float fres = pow(1.0 - NdotV, 3.0);
76
+
77
+ // 褶皱谷底更暗
78
+ float cavity = mix(0.35, 1.0, smoothstep(-1.0, 0.8, h));
79
+
80
+ vec3 color = uBase * (0.015 + 0.45 * NdotL * NdotL * atten) * cavity;
81
+ color += mix(uBase, uHighlight, 0.5) * pow(NdotH, 14.0) * 0.45 * atten * cavity;
82
+ color += uHighlight * pow(NdotH, 90.0) * 1.1 * atten;
83
+
84
+ // 线形灯带在褶皱上形成的缎面亮线
85
+ vec3 Rf = reflect(-V, n);
86
+ vec2 toL = (uPointer - frag) / minRes;
87
+ float a = -0.5 + 0.3 * sin(t * 0.13);
88
+ vec2 dir = vec2(cos(a), sin(a));
89
+ float s = dot(Rf.xy, dir) - dot(toL, dir) * 1.1;
90
+ color += uHighlight * (exp(-s * s / 0.004) * 0.9 + exp(-s * s / 0.08) * 0.12) * (0.4 + atten) * cavity;
91
+ color += mix(uBase, uHighlight, 0.3) * fres * 0.25 * cavity;
92
+
93
+ vec2 vc = frag / uResolution - 0.5;
94
+ float vig = dot(vc, vc);
95
+
96
+ if (uScheme > 0.5) {
97
+ // 浅色:先色调映射,再做亮暗镜像,保留色相 —— 深底亮纹翻转为浅底深纹
98
+ vec3 c = pow(aces(color * 1.1), vec3(1.0 / 2.2));
99
+ float lum = dot(c, vec3(0.2126, 0.7152, 0.0722));
100
+ color = clamp(c + (1.0 - 2.0 * lum), 0.0, 1.0) * (1.0 - vig * 0.35);
101
+ } else {
102
+ color *= 1.0 - vig * 1.2;
103
+ color = pow(aces(color * 1.1), vec3(1.0 / 2.2));
104
+ }
105
+
106
+ color += (hash(frag + fract(t)) - 0.5) / 255.0;
107
+
108
+ gl_FragColor = vec4(color, 1.0);
109
+ }
110
+ `;
111
+
112
+ export function SilkWaves({
113
+ children,
114
+ className,
115
+ color = '#9a6424',
116
+ highlightColor = '#ffe3b0',
117
+ folds = 1,
118
+ speed = 1,
119
+ interactive = true,
120
+ }: SilkWavesProps) {
121
+ const scheme = useColorScheme();
122
+ const canvasRef = useShaderCanvas({
123
+ fragmentShader: FRAGMENT_SHADER,
124
+ speed,
125
+ interactive,
126
+ onDraw: ({ gl, uniform }) => {
127
+ gl.uniform3fv(uniform('uBase'), hexToLinearRgb(color));
128
+ gl.uniform3fv(uniform('uHighlight'), hexToLinearRgb(highlightColor));
129
+ gl.uniform1f(uniform('uFolds'), Math.max(0.2, folds));
130
+ gl.uniform1f(uniform('uScheme'), scheme === 'light' ? 1 : 0);
131
+ },
132
+ });
133
+
134
+ return (
135
+ <div
136
+ className={cn('elyri-silk-waves', className)}
137
+ data-scheme={scheme}
138
+ style={{ '--elyri-silk-waves-color': color } as CSSProperties}
139
+ >
140
+ <canvas ref={canvasRef} className="elyri-silk-waves__canvas" aria-hidden="true" />
141
+ {children != null && <div className="elyri-silk-waves__content">{children}</div>}
142
+ </div>
143
+ );
144
+ }
@@ -0,0 +1,2 @@
1
+ export { SilkWaves } from './SilkWaves';
2
+ export type { SilkWavesProps } from './SilkWaves';
@@ -0,0 +1,35 @@
1
+ .elyri-velvet {
2
+ --elyri-velvet-color: #6a1230;
3
+
4
+ position: relative;
5
+ isolation: isolate;
6
+ overflow: hidden;
7
+ /* WebGL 不可用或尚未绘制时的底色 */
8
+ background: radial-gradient(
9
+ ellipse at 60% 30%,
10
+ color-mix(in srgb, var(--elyri-velvet-color) 70%, #000),
11
+ color-mix(in srgb, var(--elyri-velvet-color) 15%, #000)
12
+ );
13
+ }
14
+
15
+ .elyri-velvet__canvas {
16
+ position: absolute;
17
+ inset: 0;
18
+ z-index: 0;
19
+ display: block;
20
+ width: 100%;
21
+ height: 100%;
22
+ opacity: 0;
23
+ pointer-events: none;
24
+ transition: opacity var(--elyri-duration-slow) var(--elyri-ease-out);
25
+ }
26
+
27
+ .elyri-velvet__canvas[data-ready] {
28
+ opacity: 1;
29
+ }
30
+
31
+ .elyri-velvet__content {
32
+ position: relative;
33
+ z-index: 1;
34
+ height: 100%;
35
+ }
@@ -0,0 +1,246 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import type { CSSProperties, ReactNode } from 'react';
3
+
4
+ import { cn, hexToLinearRgb, useShaderCanvas } from '../../../core';
5
+
6
+ import './Velvet.css';
7
+
8
+ export interface VelvetProps {
9
+ children?: ReactNode;
10
+ className?: string;
11
+ /** 丝绒本体色(hex) */
12
+ color?: string;
13
+ /** 绒面光晕色(hex) */
14
+ sheenColor?: string;
15
+ /** 褶皱密度倍率 */
16
+ folds?: number;
17
+ /** 绒毛质感强度,0 为光洁、1 为明显的毛绒颗粒 */
18
+ fuzz?: number;
19
+ /** 四周暗角强度,0 为无暗角 */
20
+ vignette?: number;
21
+ /** 动画速度倍率 */
22
+ speed?: number;
23
+ /** 光源是否跟随指针,指针划过是否留下渐消的压痕 */
24
+ interactive?: boolean;
25
+ }
26
+
27
+ /** 压痕轨迹点数与消退时长 */
28
+ const TRAIL = 24;
29
+ const TRAIL_STEP_MS = 40;
30
+ const TRAIL_FADE_S = 1.6;
31
+ const trailData = new Float32Array(TRAIL * 3);
32
+
33
+ // 丝绒:圆背窄谷的斜向垂坠褶皱缓慢摆动。用 Charlie 绒面分布着色——正对视线处吸光发暗,
34
+ // 褶皱侧面因绒毛掠射散射而泛起柔光;绒光上叠加可调强度的像素级纤维亮点与碎绒明暗。
35
+ // 指针轨迹以线段形式传入,被压倒的绒毛光晕消失、略显光亮,随时间回弹。
36
+ const FRAGMENT_SHADER = `
37
+ precision highp float;
38
+
39
+ #define TRAIL ${TRAIL}
40
+ #define PI 3.14159265
41
+
42
+ uniform vec2 uResolution;
43
+ uniform float uTime;
44
+ uniform vec2 uPointer;
45
+ uniform vec3 uBase;
46
+ uniform vec3 uSheen;
47
+ uniform float uFolds;
48
+ uniform float uFuzz;
49
+ uniform float uVignette;
50
+ uniform vec3 uTrail[TRAIL];
51
+
52
+ float hash(vec2 p) {
53
+ p = fract(p * vec2(123.34, 456.21));
54
+ p += dot(p, p + 45.32);
55
+ return fract(p.x * p.y);
56
+ }
57
+
58
+ float noise(vec2 p) {
59
+ vec2 i = floor(p);
60
+ vec2 f = fract(p);
61
+ vec2 u = f * f * (3.0 - 2.0 * f);
62
+ return mix(
63
+ mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),
64
+ mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
65
+ u.y
66
+ );
67
+ }
68
+
69
+ // 圆润的褶背 + 收紧的折痕:|sin| 在零点处形成 V 形谷,k 控制谷底锐度
70
+ float fold(float s, float k) {
71
+ return sqrt(s * s + k);
72
+ }
73
+
74
+ float drape(vec2 p, float t) {
75
+ // 褶皱沿 x 分布、顺 y 垂下,并随高度缓慢摆动
76
+ float sway = 0.28 * sin(p.y * 0.5 + t * 0.3) + 0.12 * sin(p.y * 1.15 - t * 0.23 + p.x * 0.3);
77
+ float x = p.x + sway;
78
+ float s1 = sin(x * 1.35 + 0.6 * sin(x * 0.37 + t * 0.08));
79
+ float s2 = sin(x * 2.7 + p.y * 0.28 - t * 0.18 + 1.3);
80
+ float h = 0.62 * fold(s1, 0.012) + 0.26 * fold(s2, 0.03);
81
+ // 褶背上的斜向细皱
82
+ h += 0.05 * sin(p.x * 4.0 - p.y * 5.5 + t * 0.25) * s1 * s1;
83
+ h += 0.25 * noise(p * 0.5 + vec2(t * 0.04, -t * 0.03));
84
+ return h;
85
+ }
86
+
87
+ float pressAt(vec2 frag, float minRes) {
88
+ float s = 0.0;
89
+ for (int i = 0; i < TRAIL - 1; i++) {
90
+ vec3 a = uTrail[i];
91
+ vec3 b = uTrail[i + 1];
92
+ float w = min(a.z, b.z);
93
+ if (w <= 0.0) continue;
94
+ vec2 pa = frag - a.xy;
95
+ vec2 ba = b.xy - a.xy;
96
+ float k = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-3), 0.0, 1.0);
97
+ vec2 d = (pa - ba * k) / minRes;
98
+ s = max(s, w * exp(-dot(d, d) * 600.0));
99
+ }
100
+ return s;
101
+ }
102
+
103
+ vec3 aces(vec3 x) {
104
+ return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);
105
+ }
106
+
107
+ void main() {
108
+ vec2 frag = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y);
109
+ float minRes = min(uResolution.x, uResolution.y);
110
+ vec2 uv = frag / minRes;
111
+ float t = uTime;
112
+
113
+ // 斜向约 16° 的垂坠
114
+ mat2 rot = mat2(0.96, 0.28, -0.28, 0.96);
115
+ vec2 p = rot * uv * 2.2 * uFolds;
116
+
117
+ // 中心差分求法线,步长足够小以保留折痕的锐利边缘
118
+ float e = 0.004;
119
+ vec2 ex = vec2(e, 0.0);
120
+ vec2 ey = vec2(0.0, e);
121
+ float h = drape(p, t);
122
+ vec2 grad = vec2(drape(p + ex, t) - drape(p - ex, t), drape(p + ey, t) - drape(p - ey, t)) / (2.0 * e);
123
+ // 梯度转回屏幕坐标系
124
+ grad = grad * rot;
125
+ vec3 n = normalize(vec3(-grad * 0.75, 1.0));
126
+ float NdotV = max(n.z, 1e-3);
127
+
128
+ float pressed = pressAt(frag, minRes);
129
+ // 绒毛:像素级的纤维亮点,沿垂坠方向略拉长;大块碎绒明暗与之同步受 uFuzz 控制
130
+ vec2 fp = rot * frag;
131
+ float fiber = 0.55 * hash(floor(fp)) + 0.45 * hash(floor(vec2(fp.x * 0.5, fp.y * 0.2)) + 31.0);
132
+ float nap = 1.0 + (noise(p * 0.9 + 3.1) - 0.5) * 0.7 * uFuzz;
133
+ float fuzz = mix(1.0, 0.45 + 1.1 * fiber, uFuzz);
134
+ float ao = mix(0.4, 1.0, smoothstep(0.1, 0.8, h));
135
+
136
+ // 主光:左上方固定柔光,保证形体稳定可读;辅光:跟随指针的点光
137
+ vec2 ptr = uPointer / minRes;
138
+ vec2 toPtr = ptr - uv;
139
+ vec3 lights[2];
140
+ lights[0] = normalize(vec3(-0.45, -0.55, 0.7));
141
+ lights[1] = normalize(vec3(toPtr, 0.55));
142
+ float power[2];
143
+ power[0] = 1.5;
144
+ power[1] = 1.6 / (1.0 + 3.0 * dot(toPtr, toPtr));
145
+
146
+ // Charlie 绒面分布:正对光处吸光,掠射角散射出柔光
147
+ const float INV_A = 1.0 / 0.38;
148
+ vec3 color = uBase * 0.03 * ao;
149
+ for (int i = 0; i < 2; i++) {
150
+ vec3 L = lights[i];
151
+ vec3 H = normalize(L + vec3(0.0, 0.0, 1.0));
152
+ float NdotL = max(dot(n, L), 0.0);
153
+ float NdotH = max(dot(n, H), 0.0);
154
+ float sin2 = max(1.0 - NdotH * NdotH, 0.0078125);
155
+ float D = (2.0 + INV_A) * pow(sin2, INV_A * 0.5) / (2.0 * PI);
156
+ float vis = 1.0 / (4.0 * (NdotL + NdotV - NdotL * NdotV) + 1e-3);
157
+ vec3 diffuse = uBase * (0.08 + 0.55 * NdotL) * ao;
158
+ vec3 sheen = uSheen * D * vis * NdotL * nap * mix(fuzz, 1.0, pressed) * (1.0 - 0.85 * pressed);
159
+ // 压倒的绒毛:顺毛方向出现一点缎面般的光泽
160
+ vec3 gloss = mix(uBase, uSheen, 0.6) * pow(NdotH, 40.0) * 0.35 * pressed;
161
+ color += (diffuse * (1.0 - 0.3 * pressed) + sheen + gloss) * power[i];
162
+ }
163
+
164
+ vec2 vc = frag / uResolution - 0.5;
165
+ color *= max(1.0 - dot(vc, vc) * 1.2 * uVignette, 0.0);
166
+ color = pow(aces(color * 1.2), vec3(1.0 / 2.2));
167
+ color += (hash(frag + fract(t)) - 0.5) / 255.0;
168
+
169
+ gl_FragColor = vec4(color, 1.0);
170
+ }
171
+ `;
172
+
173
+ interface TrailPoint {
174
+ x: number;
175
+ y: number;
176
+ t: number;
177
+ }
178
+
179
+ export function Velvet({
180
+ children,
181
+ className,
182
+ color = '#6a1230',
183
+ sheenColor = '#ffb3c8',
184
+ folds = 1,
185
+ fuzz = 0.4,
186
+ vignette = 0.5,
187
+ speed = 1,
188
+ interactive = true,
189
+ }: VelvetProps) {
190
+ const trailRef = useRef<TrailPoint[]>([]);
191
+
192
+ const canvasRef = useShaderCanvas({
193
+ fragmentShader: FRAGMENT_SHADER,
194
+ speed,
195
+ interactive,
196
+ onDraw: ({ gl, uniform, dpr }) => {
197
+ const now = performance.now();
198
+ trailData.fill(0);
199
+ trailRef.current.forEach((point, i) => {
200
+ const life = Math.max(0, 1 - (now - point.t) / 1000 / TRAIL_FADE_S);
201
+ trailData[i * 3] = point.x * dpr;
202
+ trailData[i * 3 + 1] = point.y * dpr;
203
+ trailData[i * 3 + 2] = life * life;
204
+ });
205
+ gl.uniform3fv(uniform('uBase'), hexToLinearRgb(color));
206
+ gl.uniform3fv(uniform('uSheen'), hexToLinearRgb(sheenColor));
207
+ gl.uniform1f(uniform('uFolds'), Math.max(0.2, folds));
208
+ gl.uniform1f(uniform('uFuzz'), Math.min(1, Math.max(0, fuzz)));
209
+ gl.uniform1f(uniform('uVignette'), Math.min(1, Math.max(0, vignette)));
210
+ gl.uniform3fv(uniform('uTrail'), trailData);
211
+ },
212
+ });
213
+
214
+ useEffect(() => {
215
+ trailRef.current = [];
216
+ if (!interactive) return;
217
+
218
+ // 记录相对画布的轨迹:每 40ms 落一个点,期间只更新末端位置,保证笔触连贯
219
+ const handlePointerMove = (event: PointerEvent) => {
220
+ const rect = canvasRef.current?.getBoundingClientRect();
221
+ if (!rect) return;
222
+ const x = event.clientX - rect.left;
223
+ const y = event.clientY - rect.top;
224
+ const trail = trailRef.current;
225
+ const now = performance.now();
226
+ const last = trail[trail.length - 1];
227
+ if (last && now - last.t < TRAIL_STEP_MS) {
228
+ last.x = x;
229
+ last.y = y;
230
+ return;
231
+ }
232
+ trail.push({ x, y, t: now });
233
+ if (trail.length > TRAIL) trail.shift();
234
+ };
235
+
236
+ window.addEventListener('pointermove', handlePointerMove, { passive: true });
237
+ return () => window.removeEventListener('pointermove', handlePointerMove);
238
+ }, [interactive, canvasRef]);
239
+
240
+ return (
241
+ <div className={cn('elyri-velvet', className)} style={{ '--elyri-velvet-color': color } as CSSProperties}>
242
+ <canvas ref={canvasRef} className="elyri-velvet__canvas" aria-hidden="true" />
243
+ {children != null && <div className="elyri-velvet__content">{children}</div>}
244
+ </div>
245
+ );
246
+ }
@@ -0,0 +1,2 @@
1
+ export { Velvet } from './Velvet';
2
+ export type { VelvetProps } from './Velvet';
@@ -0,0 +1,26 @@
1
+ export { Aurora } from './Aurora';
2
+ export type { AuroraProps } from './Aurora';
3
+ export { Caustics } from './Caustics';
4
+ export type { CausticsProps } from './Caustics';
5
+ export { Contours } from './Contours';
6
+ export type { ContoursProps } from './Contours';
7
+ export { DotField } from './DotField';
8
+ export type { DotFieldProps } from './DotField';
9
+ export { DragonScales } from './DragonScales';
10
+ export type { DragonScalesProps } from './DragonScales';
11
+ export { Filaments } from './Filaments';
12
+ export type { FilamentsProps } from './Filaments';
13
+ export { FlipTiles } from './FlipTiles';
14
+ export type { FlipTilesPattern, FlipTilesProps } from './FlipTiles';
15
+ export { Halo } from './Halo';
16
+ export type { HaloProps } from './Halo';
17
+ export { MeshGradient } from './MeshGradient';
18
+ export type { MeshGradientProps } from './MeshGradient';
19
+ export { PixelVortex } from './PixelVortex';
20
+ export type { PixelVortexProps } from './PixelVortex';
21
+ export { Planet } from './Planet';
22
+ export type { PlanetKind, PlanetProps } from './Planet';
23
+ export { SilkWaves } from './SilkWaves';
24
+ export type { SilkWavesProps } from './SilkWaves';
25
+ export { Velvet } from './Velvet';
26
+ export type { VelvetProps } from './Velvet';
@@ -0,0 +1,4 @@
1
+ export * from './animations';
2
+ export * from './backgrounds';
3
+ export * from './media';
4
+ export * from './text';
@@ -0,0 +1,16 @@
1
+ .elyri-ascii-image {
2
+ display: block;
3
+ width: 100%;
4
+ font-family: var(--elyri-ascii-font, ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace);
5
+ font-weight: 600;
6
+ }
7
+
8
+ .elyri-ascii-image__canvas,
9
+ .elyri-ascii-image__fallback {
10
+ display: block;
11
+ width: 100%;
12
+ }
13
+
14
+ .elyri-ascii-image__fallback {
15
+ height: auto;
16
+ }