@nous-research/ui 0.15.0 → 0.17.0
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 +266 -0
- package/README.md +24 -4
- package/dist/fonts.js +1 -0
- package/dist/hooks/use-below-breakpoint.d.ts +2 -0
- package/dist/hooks/use-below-breakpoint.js +17 -0
- package/dist/hooks/use-capped-frame.js +1 -0
- package/dist/hooks/use-confirm-delete.d.ts +10 -0
- package/dist/hooks/use-confirm-delete.js +35 -0
- package/dist/hooks/use-css-var-dims.js +1 -0
- package/dist/hooks/use-gpu-tier.js +1 -0
- package/dist/hooks/use-render-loop.js +1 -0
- package/dist/hooks/use-smooth-controls.js +1 -0
- package/dist/hooks/use-toast.d.ts +7 -0
- package/dist/hooks/use-toast.js +21 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +23 -1
- package/dist/ui/basic-page.js +1 -0
- package/dist/ui/components/animated-count.js +1 -0
- package/dist/ui/components/ascii.js +1 -0
- package/dist/ui/components/badge.js +2 -1
- package/dist/ui/components/badges/nous-girl.js +1 -0
- package/dist/ui/components/blend-mode.js +1 -0
- package/dist/ui/components/blink.js +1 -0
- package/dist/ui/components/bottom-sheet.d.ts +15 -0
- package/dist/ui/components/bottom-sheet.js +192 -0
- package/dist/ui/components/button.js +2 -1
- package/dist/ui/components/card.d.ts +5 -0
- package/dist/ui/components/card.js +74 -0
- package/dist/ui/components/checkbox.d.ts +1 -1
- package/dist/ui/components/checkbox.js +2 -1
- package/dist/ui/components/command-block.js +4 -3
- package/dist/ui/components/confirm-dialog.d.ts +13 -0
- package/dist/ui/components/confirm-dialog.js +113 -0
- package/dist/ui/components/cursor.js +1 -0
- package/dist/ui/components/dialog.d.ts +15 -0
- package/dist/ui/components/dialog.js +171 -0
- package/dist/ui/components/dropdown-menu.js +1 -0
- package/dist/ui/components/fit-text/index.js +1 -0
- package/dist/ui/components/graphs/bar-chart.js +1 -0
- package/dist/ui/components/graphs/index.js +1 -0
- package/dist/ui/components/graphs/line-chart.js +1 -0
- package/dist/ui/components/graphs/utils.js +1 -0
- package/dist/ui/components/grid/index.js +1 -0
- package/dist/ui/components/hover-bg.js +1 -0
- package/dist/ui/components/icons/arrow.js +1 -0
- package/dist/ui/components/icons/check.js +1 -0
- package/dist/ui/components/icons/chevron.js +1 -0
- package/dist/ui/components/icons/discord.js +1 -0
- package/dist/ui/components/icons/eye.js +1 -0
- package/dist/ui/components/icons/gear.js +1 -0
- package/dist/ui/components/icons/github.js +1 -0
- package/dist/ui/components/icons/hamburger.js +1 -0
- package/dist/ui/components/icons/heart.js +1 -0
- package/dist/ui/components/icons/index.js +1 -0
- package/dist/ui/components/icons/link.js +1 -0
- package/dist/ui/components/icons/minus.js +1 -0
- package/dist/ui/components/icons/search.js +1 -0
- package/dist/ui/components/image-distortion.js +1 -0
- package/dist/ui/components/input.d.ts +1 -0
- package/dist/ui/components/input.js +21 -0
- package/dist/ui/components/label.d.ts +1 -0
- package/dist/ui/components/label.js +18 -0
- package/dist/ui/components/leva-client.js +1 -0
- package/dist/ui/components/list-item.js +3 -2
- package/dist/ui/components/overlays/blend-modes.js +1 -0
- package/dist/ui/components/overlays/glitch.js +1 -0
- package/dist/ui/components/overlays/greys.js +1 -0
- package/dist/ui/components/overlays/index.js +1 -0
- package/dist/ui/components/overlays/lens-layers.js +1 -0
- package/dist/ui/components/overlays/lens.js +1 -0
- package/dist/ui/components/overlays/noise.js +1 -0
- package/dist/ui/components/overlays/vignette.js +1 -0
- package/dist/ui/components/poster.js +1 -0
- package/dist/ui/components/progress.js +1 -0
- package/dist/ui/components/scene-canvas.js +1 -0
- package/dist/ui/components/scramble.js +1 -0
- package/dist/ui/components/segmented.js +5 -4
- package/dist/ui/components/select.js +1 -0
- package/dist/ui/components/selection-switcher.js +1 -0
- package/dist/ui/components/separator.d.ts +5 -0
- package/dist/ui/components/separator.js +22 -0
- package/dist/ui/components/shader.js +1 -0
- package/dist/ui/components/socials.js +1 -0
- package/dist/ui/components/spinner.js +1 -0
- package/dist/ui/components/stats.js +2 -1
- package/dist/ui/components/switch.js +1 -0
- package/dist/ui/components/tabs.js +4 -3
- package/dist/ui/components/terminal-demo.js +2 -1
- package/dist/ui/components/theme-toggle.js +1 -0
- package/dist/ui/components/tier-card.js +2 -1
- package/dist/ui/components/toast.d.ts +8 -0
- package/dist/ui/components/toast.js +39 -0
- package/dist/ui/components/tv.js +1 -0
- package/dist/ui/components/typography/h1.js +1 -0
- package/dist/ui/components/typography/h2.js +1 -0
- package/dist/ui/components/typography/index.js +1 -0
- package/dist/ui/components/typography/legend.js +1 -0
- package/dist/ui/components/typography/small.js +1 -0
- package/dist/ui/components/watchlist.js +2 -1
- package/dist/ui/footer.js +1 -0
- package/dist/ui/globals.css +47 -3
- package/dist/ui/header.js +1 -0
- package/dist/ui/layout-wrapper.js +2 -1
- package/dist/utils/color.js +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/poly.js +1 -0
- package/package.json +5 -3
- package/src/assets/filler-bg0.webp +0 -0
- package/src/assets.d.ts +38 -0
- package/src/fonts/Collapse-Bold.woff2 +0 -0
- package/src/fonts/Collapse-BoldItalic.woff2 +0 -0
- package/src/fonts/Collapse-Italic.woff2 +0 -0
- package/src/fonts/Collapse-Light.woff2 +0 -0
- package/src/fonts/Collapse-LightItalic.woff2 +0 -0
- package/src/fonts/Collapse-Regular.woff2 +0 -0
- package/src/fonts/Collapse-Thin.woff2 +0 -0
- package/src/fonts/Collapse-ThinItalic.woff2 +0 -0
- package/src/fonts/Mondwest-Regular.woff2 +0 -0
- package/src/fonts/Neuebit-Bold.woff2 +0 -0
- package/src/fonts/RulesCompressed-Medium.woff2 +0 -0
- package/src/fonts/RulesCompressed-Regular.woff2 +0 -0
- package/src/fonts/RulesExpanded-Bold.woff2 +0 -0
- package/src/fonts/RulesExpanded-Regular.woff2 +0 -0
- package/src/fonts.ts +6 -0
- package/src/hooks/use-below-breakpoint.ts +21 -0
- package/src/hooks/use-capped-frame.ts +18 -0
- package/src/hooks/use-confirm-delete.ts +43 -0
- package/src/hooks/use-css-var-dims.ts +39 -0
- package/src/hooks/use-gpu-tier.ts +165 -0
- package/src/hooks/use-render-loop.ts +121 -0
- package/src/hooks/use-smooth-controls.ts +318 -0
- package/src/hooks/use-toast.ts +29 -0
- package/src/index.ts +130 -0
- package/src/ui/basic-page.tsx +34 -0
- package/src/ui/build.css +4 -0
- package/src/ui/components/animated-count.stories.tsx +67 -0
- package/src/ui/components/animated-count.tsx +168 -0
- package/src/ui/components/ascii.stories.tsx +30 -0
- package/src/ui/components/ascii.tsx +110 -0
- package/src/ui/components/badge.stories.tsx +31 -0
- package/src/ui/components/badge.tsx +60 -0
- package/src/ui/components/badges/nous-girl.tsx +52 -0
- package/src/ui/components/blend-mode.stories.tsx +33 -0
- package/src/ui/components/blend-mode.tsx +129 -0
- package/src/ui/components/blink.stories.tsx +32 -0
- package/src/ui/components/blink.tsx +21 -0
- package/src/ui/components/bottom-sheet.stories.tsx +43 -0
- package/src/ui/components/bottom-sheet.tsx +227 -0
- package/src/ui/components/button.stories.tsx +68 -0
- package/src/ui/components/button.tsx +170 -0
- package/src/ui/components/card.stories.tsx +63 -0
- package/src/ui/components/card.tsx +85 -0
- package/src/ui/components/checkbox.stories.tsx +113 -0
- package/src/ui/components/checkbox.tsx +36 -0
- package/src/ui/components/command-block.stories.tsx +52 -0
- package/src/ui/components/command-block.tsx +86 -0
- package/src/ui/components/confirm-dialog.stories.tsx +91 -0
- package/src/ui/components/confirm-dialog.tsx +130 -0
- package/src/ui/components/cursor.tsx +115 -0
- package/src/ui/components/dialog.stories.tsx +169 -0
- package/src/ui/components/dialog.tsx +177 -0
- package/src/ui/components/dropdown-menu.stories.tsx +52 -0
- package/src/ui/components/dropdown-menu.tsx +117 -0
- package/src/ui/components/fit-text/fit-text.css +42 -0
- package/src/ui/components/fit-text/index.stories.tsx +33 -0
- package/src/ui/components/fit-text/index.tsx +45 -0
- package/src/ui/components/forms.stories.tsx +173 -0
- package/src/ui/components/graphs/bar-chart.tsx +153 -0
- package/src/ui/components/graphs/index.stories.tsx +64 -0
- package/src/ui/components/graphs/index.tsx +4 -0
- package/src/ui/components/graphs/line-chart.tsx +213 -0
- package/src/ui/components/graphs/utils.tsx +265 -0
- package/src/ui/components/grid/grid.css +79 -0
- package/src/ui/components/grid/index.tsx +19 -0
- package/src/ui/components/hover-bg.stories.tsx +29 -0
- package/src/ui/components/hover-bg.tsx +15 -0
- package/src/ui/components/icons/arrow.tsx +42 -0
- package/src/ui/components/icons/check.tsx +14 -0
- package/src/ui/components/icons/chevron.tsx +45 -0
- package/src/ui/components/icons/discord.tsx +16 -0
- package/src/ui/components/icons/eye.tsx +12 -0
- package/src/ui/components/icons/gear.tsx +51 -0
- package/src/ui/components/icons/github.tsx +16 -0
- package/src/ui/components/icons/hamburger.tsx +52 -0
- package/src/ui/components/icons/heart.tsx +12 -0
- package/src/ui/components/icons/index.ts +12 -0
- package/src/ui/components/icons/link.tsx +14 -0
- package/src/ui/components/icons/minus.tsx +14 -0
- package/src/ui/components/icons/search.tsx +28 -0
- package/src/ui/components/image-distortion.stories.tsx +120 -0
- package/src/ui/components/image-distortion.tsx +498 -0
- package/src/ui/components/input.stories.tsx +39 -0
- package/src/ui/components/input.tsx +20 -0
- package/src/ui/components/label.stories.tsx +26 -0
- package/src/ui/components/label.tsx +16 -0
- package/src/ui/components/leva-client.tsx +14 -0
- package/src/ui/components/list-item.stories.tsx +83 -0
- package/src/ui/components/list-item.tsx +37 -0
- package/src/ui/components/overlays/blend-modes.ts +13 -0
- package/src/ui/components/overlays/glitch.tsx +243 -0
- package/src/ui/components/overlays/greys.tsx +386 -0
- package/src/ui/components/overlays/index.tsx +47 -0
- package/src/ui/components/overlays/lens-layers.tsx +119 -0
- package/src/ui/components/overlays/lens.ts +91 -0
- package/src/ui/components/overlays/noise.tsx +174 -0
- package/src/ui/components/overlays/vignette.tsx +60 -0
- package/src/ui/components/poster.stories.tsx +513 -0
- package/src/ui/components/poster.tsx +411 -0
- package/src/ui/components/progress.stories.tsx +48 -0
- package/src/ui/components/progress.tsx +56 -0
- package/src/ui/components/scene-canvas.tsx +254 -0
- package/src/ui/components/scramble.stories.tsx +49 -0
- package/src/ui/components/scramble.tsx +95 -0
- package/src/ui/components/segmented.stories.tsx +101 -0
- package/src/ui/components/segmented.tsx +81 -0
- package/src/ui/components/select.stories.tsx +88 -0
- package/src/ui/components/select.tsx +267 -0
- package/src/ui/components/selection-switcher.tsx +44 -0
- package/src/ui/components/separator.stories.tsx +33 -0
- package/src/ui/components/separator.tsx +24 -0
- package/src/ui/components/shader.tsx +83 -0
- package/src/ui/components/socials.tsx +42 -0
- package/src/ui/components/spinner.stories.tsx +101 -0
- package/src/ui/components/spinner.tsx +60 -0
- package/src/ui/components/stats.stories.tsx +24 -0
- package/src/ui/components/stats.tsx +53 -0
- package/src/ui/components/switch.stories.tsx +77 -0
- package/src/ui/components/switch.tsx +48 -0
- package/src/ui/components/tabs.stories.tsx +101 -0
- package/src/ui/components/tabs.tsx +66 -0
- package/src/ui/components/terminal-demo.stories.tsx +67 -0
- package/src/ui/components/terminal-demo.tsx +189 -0
- package/src/ui/components/theme-toggle.stories.tsx +47 -0
- package/src/ui/components/theme-toggle.tsx +66 -0
- package/src/ui/components/tier-card.stories.tsx +217 -0
- package/src/ui/components/tier-card.tsx +190 -0
- package/src/ui/components/toast.stories.tsx +55 -0
- package/src/ui/components/toast.tsx +49 -0
- package/src/ui/components/tv.stories.tsx +37 -0
- package/src/ui/components/tv.tsx +257 -0
- package/src/ui/components/typography/h1.tsx +18 -0
- package/src/ui/components/typography/h2.tsx +18 -0
- package/src/ui/components/typography/index.tsx +54 -0
- package/src/ui/components/typography/legend.tsx +24 -0
- package/src/ui/components/typography/small.tsx +11 -0
- package/src/ui/components/watchlist.stories.tsx +33 -0
- package/src/ui/components/watchlist.tsx +105 -0
- package/src/ui/fonts.css +63 -0
- package/src/ui/footer.tsx +111 -0
- package/src/ui/globals.css +395 -0
- package/src/ui/header.tsx +398 -0
- package/src/ui/layout-wrapper.tsx +11 -0
- package/src/utils/color.ts +21 -0
- package/src/utils/index.ts +62 -0
- package/src/utils/poly.ts +26 -0
- package/dist/ui/components/modal/index.d.ts +0 -8
- package/dist/ui/components/modal/index.js +0 -34
- package/dist/ui/components/modal/modal.css +0 -36
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import * as THREE from 'three'
|
|
5
|
+
|
|
6
|
+
import { $gpuTier, useGpuTier } from '../../../hooks/use-gpu-tier'
|
|
7
|
+
import { runRenderLoop } from '../../../hooks/use-render-loop'
|
|
8
|
+
import { useSmoothControls } from '../../../hooks/use-smooth-controls'
|
|
9
|
+
import { cn } from '../../../utils'
|
|
10
|
+
|
|
11
|
+
import { BLEND_MODES } from './blend-modes'
|
|
12
|
+
|
|
13
|
+
const vert = /*glsl*/ `
|
|
14
|
+
varying vec2 vUv;
|
|
15
|
+
void main() {
|
|
16
|
+
vUv = uv;
|
|
17
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
18
|
+
}
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
const sourceFrag = /*glsl*/ `
|
|
22
|
+
uniform sampler2D uTex0, uTex1, uTex2, uTex3;
|
|
23
|
+
uniform float uTime, uZoom, uSpeed, uRotate, uFolds, uDrift;
|
|
24
|
+
varying vec2 vUv;
|
|
25
|
+
|
|
26
|
+
vec3 gray(vec3 c) { return vec3(dot(c, vec3(.299, .587, .114))); }
|
|
27
|
+
vec2 rot(vec2 p, float a) { return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); }
|
|
28
|
+
|
|
29
|
+
vec2 kaleid(vec2 p, float n) {
|
|
30
|
+
float a = mod(atan(p.y, p.x), 6.28318 / n) - 3.14159 / n;
|
|
31
|
+
return length(p) * vec2(cos(a), sin(a));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
vec4 tex(int i, vec2 uv) {
|
|
35
|
+
if (i == 0) return texture2D(uTex0, uv);
|
|
36
|
+
if (i == 1) return texture2D(uTex1, uv);
|
|
37
|
+
if (i == 2) return texture2D(uTex2, uv);
|
|
38
|
+
return texture2D(uTex3, uv);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void main() {
|
|
42
|
+
vec2 uv = rot(vUv - .5, uTime * uRotate * .05);
|
|
43
|
+
if (uFolds > 1.) uv = kaleid(uv, uFolds);
|
|
44
|
+
|
|
45
|
+
float dt = uTime * uDrift * .1;
|
|
46
|
+
uv = uv / uZoom + .5 + vec2(sin(dt * .7) * cos(dt * .3), cos(dt * .5) * sin(dt * .9)) * .15 * uDrift;
|
|
47
|
+
|
|
48
|
+
float cycle = mod(uTime * uSpeed * .01, 4.);
|
|
49
|
+
int i0 = int(floor(cycle)), i1 = int(mod(float(i0) + 1., 4.));
|
|
50
|
+
float t = smoothstep(0., 1., fract(cycle));
|
|
51
|
+
|
|
52
|
+
vec3 base = mix(gray(vec3(1.) - tex(i0, uv).rgb), gray(vec3(1.) - tex(i1, uv).rgb), t);
|
|
53
|
+
vec2 uvF = vec2(1. - uv.x, uv.y);
|
|
54
|
+
vec3 flip = mix(gray(vec3(1.) - tex(i0, uvF).rgb), gray(vec3(1.) - tex(i1, uvF).rgb), t);
|
|
55
|
+
|
|
56
|
+
gl_FragColor = vec4(mix(base, flip, .3 + sin(uTime * .2) * .2), 1.);
|
|
57
|
+
}
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
const moshFrag = /*glsl*/ `
|
|
61
|
+
uniform sampler2D uCurrent, uPrev, uTex0, uTex1, uTex2, uTex3;
|
|
62
|
+
uniform float uTime, uIntensity, uMotion, uZoom, uSpeed;
|
|
63
|
+
uniform vec2 uRes;
|
|
64
|
+
varying vec2 vUv;
|
|
65
|
+
|
|
66
|
+
float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
|
|
67
|
+
vec2 hash2(vec2 p) { return fract(sin(vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)))) * 43758.5453); }
|
|
68
|
+
|
|
69
|
+
float noise(vec2 p) {
|
|
70
|
+
vec2 i = floor(p), f = fract(p) * fract(p) * (3. - 2. * fract(p));
|
|
71
|
+
return mix(mix(hash(i), hash(i + vec2(1., 0.)), f.x), mix(hash(i + vec2(0., 1.)), hash(i + vec2(1., 1.)), f.x), f.y);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
vec3 gray(vec3 c) { return vec3(dot(c, vec3(.299, .587, .114))); }
|
|
75
|
+
|
|
76
|
+
vec2 distort(vec2 uv, float k, float t) {
|
|
77
|
+
float n1 = noise(uv * 8. + t * .5), n2 = noise(uv * 12. + t * .7), flow = noise(uv * 4. + t * .3);
|
|
78
|
+
return uv + vec2(cos(n1 * 6.28 + t * 1.2), sin(n2 * 6.28 + t * .9)) * .02 * k
|
|
79
|
+
+ vec2(cos(flow * 6.28 + uv.y * 10.), sin(flow * 6.28 + uv.x * 10.)) * .015 * k;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
vec3 tex(int i, vec2 uv) {
|
|
83
|
+
vec2 zuv = (uv - .5) / uZoom + .5;
|
|
84
|
+
if (i == 0) return gray(vec3(1.) - texture2D(uTex0, zuv).rgb);
|
|
85
|
+
if (i == 1) return gray(vec3(1.) - texture2D(uTex1, zuv).rgb);
|
|
86
|
+
if (i == 2) return gray(vec3(1.) - texture2D(uTex2, zuv).rgb);
|
|
87
|
+
return gray(vec3(1.) - texture2D(uTex3, zuv).rgb);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
void main() {
|
|
91
|
+
vec2 uv = vUv;
|
|
92
|
+
float t = uTime * uSpeed, tS = floor(t * .1), pS = 80.;
|
|
93
|
+
float amt = uIntensity * uMotion * .8 * (.7 + (sin(t * .5) * .5 + .5) * .3);
|
|
94
|
+
|
|
95
|
+
vec2 mUV = distort(uv, uIntensity * .4, t);
|
|
96
|
+
|
|
97
|
+
float hS = floor(uv.y * pS), hA = smoothstep(0., .8, hash(vec2(hS, tS)));
|
|
98
|
+
float hO = (hash(vec2(hS, tS + 50.)) - .5) * .25 * hA * amt;
|
|
99
|
+
float vS = floor(uv.x * pS), vA = smoothstep(0., .8, hash(vec2(vS, tS + 100.)));
|
|
100
|
+
float vO = (hash(vec2(vS, tS + 150.)) - .5) * .25 * vA * amt;
|
|
101
|
+
mUV += vec2(hO, vO);
|
|
102
|
+
|
|
103
|
+
float bS = pS * .25;
|
|
104
|
+
float hBA = step(.5, hash(vec2(floor(uv.y * bS), tS + 200.)));
|
|
105
|
+
float hBO = (hash(vec2(floor(uv.y * bS), 200.)) - .5) * .35 * hBA * amt;
|
|
106
|
+
float vBA = step(.5, hash(vec2(floor(uv.x * bS), tS + 300.)));
|
|
107
|
+
float vBO = (hash(vec2(floor(uv.x * bS), 250.)) - .5) * .35 * vBA * amt;
|
|
108
|
+
mUV += vec2(hBO, vBO);
|
|
109
|
+
|
|
110
|
+
vec2 blk = floor(uv * pS * .15);
|
|
111
|
+
mUV += (hash2(vec2(blk.x, blk.y + 500.)) - .5) * .4 * step(.7, hash(vec2(blk.x, blk.y + tS))) * amt;
|
|
112
|
+
mUV = clamp(mUV, 0., 1.);
|
|
113
|
+
|
|
114
|
+
vec3 prev = texture2D(uPrev, mUV).rgb;
|
|
115
|
+
prev = mix(prev, texture2D(uPrev, clamp(uv + vec2(hBO, vBO), 0., 1.)).rgb, max(hBA, vBA) * .9);
|
|
116
|
+
|
|
117
|
+
float tY = floor(uv.y * pS * .4);
|
|
118
|
+
if (hash(vec2(tY, tS + 400.)) > .75) {
|
|
119
|
+
prev = mix(prev, texture2D(uPrev, clamp(vec2(uv.x + (hash(vec2(tY, 400.)) - .5) * .5 * amt, uv.y), 0., 1.)).rgb, .85);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (hA > 0. && amt > .01) {
|
|
123
|
+
prev = mix(prev, gray(texture2D(uPrev, clamp(vec2(uv.x + (gray(prev).r - uv.x) * amt + hO, uv.y), 0., 1.)).rgb), hA);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
float d = mix(mix(.97, .99, noise(uv * 8. + t * .2)), 0., step(.994, hash(vec2(tS, 0.))));
|
|
127
|
+
gl_FragColor = vec4(mix(texture2D(uCurrent, uv).rgb, prev, d), 1.);
|
|
128
|
+
}
|
|
129
|
+
`
|
|
130
|
+
|
|
131
|
+
const outputFrag = /*glsl*/ `
|
|
132
|
+
uniform sampler2D uInput;
|
|
133
|
+
uniform float uTime, uAlpha, uHue;
|
|
134
|
+
uniform vec3 uColor;
|
|
135
|
+
varying vec2 vUv;
|
|
136
|
+
|
|
137
|
+
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
138
|
+
|
|
139
|
+
vec3 hueShift(vec3 c, float h) {
|
|
140
|
+
float a = h * 6.28318, s = sin(a), co = cos(a);
|
|
141
|
+
vec3 w = vec3(.299, .587, .114);
|
|
142
|
+
return clamp(vec3(
|
|
143
|
+
dot(c, w) + dot(c, vec3(.701, -.587, -.114) * co + vec3(.168, .330, -.497) * s),
|
|
144
|
+
dot(c, w) + dot(c, vec3(-.299, .413, -.114) * co + vec3(.328, .035, -.363) * s),
|
|
145
|
+
dot(c, w) + dot(c, vec3(-.299, -.587, .886) * co + vec3(-.497, .330, .168) * s)
|
|
146
|
+
), 0., 1.);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void main() {
|
|
150
|
+
vec3 m = texture2D(uInput, vUv).rgb;
|
|
151
|
+
m *= 1. - step(.5, fract(vUv.y * 200.)) * .06 * step(.97, hash(vec2(floor(vUv.y * 30.), floor(uTime * .5))));
|
|
152
|
+
|
|
153
|
+
float lum = dot(m, vec3(.299, .587, .114));
|
|
154
|
+
gl_FragColor = vec4(hueShift(mix(vec3(lum), uColor * lum * 2., length(uColor)), uHue) * uAlpha, smoothstep(.08, .18, lum * uAlpha));
|
|
155
|
+
}
|
|
156
|
+
`
|
|
157
|
+
|
|
158
|
+
const TEXTURES = [
|
|
159
|
+
'/anatomy/grays-0.jpg',
|
|
160
|
+
'/anatomy/grays-3.jpg',
|
|
161
|
+
'/anatomy/grays-6.jpg',
|
|
162
|
+
'/anatomy/grays-9.jpg'
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
export function Greys({ className, style }: GreysProps) {
|
|
166
|
+
const gpuTier = useGpuTier()
|
|
167
|
+
const [blendOverride, setBlendOverride] = useState<string | null>(null)
|
|
168
|
+
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
169
|
+
|
|
170
|
+
const c = useSmoothControls(
|
|
171
|
+
'Effects/Greys',
|
|
172
|
+
{
|
|
173
|
+
alpha: { max: 1, min: 0, step: 0.01, value: 0.19 },
|
|
174
|
+
blend: { options: BLEND_MODES, value: 'color-burn' },
|
|
175
|
+
color: { value: '#ffac02' },
|
|
176
|
+
drift: { max: 2, min: 0, step: 0.1, value: 0.5 },
|
|
177
|
+
enabled: { value: false },
|
|
178
|
+
folds: { max: 12, min: 1, step: 1, value: 1 },
|
|
179
|
+
hue: { max: 1, min: 0, step: 0.01, value: 0.37 },
|
|
180
|
+
intensity: { max: 3, min: 0, step: 0.1, value: 0.1 },
|
|
181
|
+
motion: { max: 2, min: 0, step: 0.1, value: 0.1 },
|
|
182
|
+
rotate: { max: 2, min: -2, step: 0.1, value: 0.3 },
|
|
183
|
+
speed: { max: 1, min: 0.01, step: 0.01, value: 0.21 },
|
|
184
|
+
zoom: { max: 4, min: 0.5, step: 0.1, value: 0.7 }
|
|
185
|
+
},
|
|
186
|
+
{ collapsed: true }
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const cRef = useRef(c)
|
|
190
|
+
cRef.current = c
|
|
191
|
+
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
const onKey = (e: KeyboardEvent) =>
|
|
194
|
+
e.key.toLowerCase() === 'x' &&
|
|
195
|
+
setBlendOverride(p => (p === 'screen' ? null : 'screen'))
|
|
196
|
+
|
|
197
|
+
window.addEventListener('keydown', onKey)
|
|
198
|
+
|
|
199
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
200
|
+
}, [])
|
|
201
|
+
|
|
202
|
+
const enabled = c.enabled && gpuTier === 2
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!canvasRef.current || !enabled) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let renderer: THREE.WebGLRenderer
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
renderer = new THREE.WebGLRenderer({
|
|
213
|
+
alpha: true,
|
|
214
|
+
canvas: canvasRef.current
|
|
215
|
+
})
|
|
216
|
+
} catch {
|
|
217
|
+
// See note in noise.tsx — eager gpu-tier detection should keep us
|
|
218
|
+
// out of here, but if the driver fails the renderer constructor
|
|
219
|
+
// anyway, downgrade so other overlays stop trying too.
|
|
220
|
+
$gpuTier.set(0)
|
|
221
|
+
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
|
|
226
|
+
const geo = new THREE.PlaneGeometry(2, 2)
|
|
227
|
+
|
|
228
|
+
const [rtSource, rtA, rtB] = [0, 1, 2].map(
|
|
229
|
+
() =>
|
|
230
|
+
new THREE.WebGLRenderTarget(innerWidth, innerHeight, {
|
|
231
|
+
magFilter: THREE.NearestFilter,
|
|
232
|
+
minFilter: THREE.NearestFilter
|
|
233
|
+
})
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
const textures = TEXTURES.map(p => {
|
|
237
|
+
const t = new THREE.TextureLoader().load(p)
|
|
238
|
+
t.wrapS = t.wrapT = THREE.ClampToEdgeWrapping
|
|
239
|
+
t.minFilter = t.magFilter = THREE.LinearFilter
|
|
240
|
+
|
|
241
|
+
return t
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
const texU = Object.fromEntries(
|
|
245
|
+
textures.map((t, i) => [`uTex${i}`, { value: t }])
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
const srcU = {
|
|
249
|
+
...texU,
|
|
250
|
+
uDrift: { value: 0 },
|
|
251
|
+
uFolds: { value: 0 },
|
|
252
|
+
uRotate: { value: 0 },
|
|
253
|
+
uSpeed: { value: 0 },
|
|
254
|
+
uTime: { value: 0 },
|
|
255
|
+
uZoom: { value: 0 }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const moshU = {
|
|
259
|
+
...texU,
|
|
260
|
+
uCurrent: { value: rtSource.texture },
|
|
261
|
+
uIntensity: { value: 0 },
|
|
262
|
+
uMotion: { value: 0 },
|
|
263
|
+
uPrev: { value: rtA.texture },
|
|
264
|
+
uRes: { value: new THREE.Vector2(innerWidth, innerHeight) },
|
|
265
|
+
uSpeed: { value: 0 },
|
|
266
|
+
uTime: { value: 0 },
|
|
267
|
+
uZoom: { value: 0 }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const outU = {
|
|
271
|
+
uAlpha: { value: 0 },
|
|
272
|
+
uColor: { value: new THREE.Color() },
|
|
273
|
+
uHue: { value: 0 },
|
|
274
|
+
uInput: { value: rtB.texture },
|
|
275
|
+
uTime: { value: 0 }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const mkScene = (frag: string, uniforms: object, transparent = false) => {
|
|
279
|
+
const s = new THREE.Scene()
|
|
280
|
+
s.add(
|
|
281
|
+
new THREE.Mesh(
|
|
282
|
+
geo.clone(),
|
|
283
|
+
new THREE.ShaderMaterial({
|
|
284
|
+
fragmentShader: frag,
|
|
285
|
+
transparent,
|
|
286
|
+
uniforms: uniforms as Record<string, THREE.IUniform<any>>,
|
|
287
|
+
vertexShader: vert
|
|
288
|
+
})
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return s
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const srcScene = mkScene(sourceFrag, srcU)
|
|
296
|
+
const moshScene = mkScene(moshFrag, moshU)
|
|
297
|
+
const outScene = mkScene(outputFrag, outU, true)
|
|
298
|
+
|
|
299
|
+
const resize = () => {
|
|
300
|
+
renderer.setSize(innerWidth, innerHeight)
|
|
301
|
+
// Cap at 1.5x — Greys does triple-buffered ping-pong rendering at
|
|
302
|
+
// every frame, so retina x2 is brutal on fillrate.
|
|
303
|
+
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.5))
|
|
304
|
+
;[rtSource, rtA, rtB].forEach(rt => rt.setSize(innerWidth, innerHeight))
|
|
305
|
+
moshU.uRes.value.set(innerWidth, innerHeight)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
resize()
|
|
309
|
+
window.addEventListener('resize', resize)
|
|
310
|
+
|
|
311
|
+
let ping = true,
|
|
312
|
+
time = 0
|
|
313
|
+
|
|
314
|
+
// 30fps cap — feedback effect, no perceptual loss vs 60fps but
|
|
315
|
+
// halves the cost of the heaviest overlay we ship.
|
|
316
|
+
const dispose = runRenderLoop({
|
|
317
|
+
el: canvasRef.current,
|
|
318
|
+
minIntervalMs: 33,
|
|
319
|
+
onFrame: deltaSeconds => {
|
|
320
|
+
time += deltaSeconds
|
|
321
|
+
|
|
322
|
+
const v = cRef.current
|
|
323
|
+
|
|
324
|
+
srcU.uTime.value = time
|
|
325
|
+
srcU.uSpeed.value = v.speed
|
|
326
|
+
srcU.uZoom.value = v.zoom
|
|
327
|
+
srcU.uRotate.value = v.rotate
|
|
328
|
+
srcU.uFolds.value = v.folds
|
|
329
|
+
srcU.uDrift.value = v.drift
|
|
330
|
+
|
|
331
|
+
moshU.uTime.value = time
|
|
332
|
+
moshU.uIntensity.value = v.intensity
|
|
333
|
+
moshU.uMotion.value = v.motion
|
|
334
|
+
moshU.uSpeed.value = v.speed
|
|
335
|
+
moshU.uZoom.value = v.zoom
|
|
336
|
+
|
|
337
|
+
outU.uTime.value = time
|
|
338
|
+
outU.uAlpha.value = v.alpha
|
|
339
|
+
outU.uHue.value = v.hue
|
|
340
|
+
outU.uColor.value.set(typeof v.color === 'string' ? v.color : '#fff')
|
|
341
|
+
|
|
342
|
+
renderer.setRenderTarget(rtSource)
|
|
343
|
+
renderer.render(srcScene, camera)
|
|
344
|
+
|
|
345
|
+
const [read, write] = ping ? [rtA, rtB] : [rtB, rtA]
|
|
346
|
+
moshU.uPrev.value = read.texture
|
|
347
|
+
renderer.setRenderTarget(write)
|
|
348
|
+
renderer.render(moshScene, camera)
|
|
349
|
+
|
|
350
|
+
outU.uInput.value = write.texture
|
|
351
|
+
renderer.setRenderTarget(null)
|
|
352
|
+
renderer.render(outScene, camera)
|
|
353
|
+
|
|
354
|
+
ping = !ping
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
return () => {
|
|
359
|
+
window.removeEventListener('resize', resize)
|
|
360
|
+
dispose()
|
|
361
|
+
textures.forEach(t => t.dispose())
|
|
362
|
+
;[geo, rtSource, rtA, rtB, renderer].forEach(x => x.dispose())
|
|
363
|
+
}
|
|
364
|
+
}, [enabled])
|
|
365
|
+
|
|
366
|
+
if (!enabled) {
|
|
367
|
+
return null
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<canvas
|
|
372
|
+
className={cn('h-full w-full', className)}
|
|
373
|
+
ref={canvasRef}
|
|
374
|
+
style={{
|
|
375
|
+
mixBlendMode: (blendOverride ??
|
|
376
|
+
c.blend) as React.CSSProperties['mixBlendMode'],
|
|
377
|
+
...style
|
|
378
|
+
}}
|
|
379
|
+
/>
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
interface GreysProps {
|
|
384
|
+
className?: string
|
|
385
|
+
style?: React.CSSProperties
|
|
386
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Glitch } from './glitch'
|
|
4
|
+
import { Greys } from './greys'
|
|
5
|
+
import { Lens } from './lens-layers'
|
|
6
|
+
import { Noise } from './noise'
|
|
7
|
+
import { Vignette } from './vignette'
|
|
8
|
+
|
|
9
|
+
import type { LensPreset } from './lens'
|
|
10
|
+
|
|
11
|
+
export { BLEND_MODES } from './blend-modes'
|
|
12
|
+
export { Glitch } from './glitch'
|
|
13
|
+
export { Greys } from './greys'
|
|
14
|
+
export { Lens } from './lens-layers'
|
|
15
|
+
export { Noise } from './noise'
|
|
16
|
+
export { Vignette } from './vignette'
|
|
17
|
+
export {
|
|
18
|
+
$lightMode,
|
|
19
|
+
applyLens,
|
|
20
|
+
lens0,
|
|
21
|
+
lens5i,
|
|
22
|
+
LENS_0,
|
|
23
|
+
LENS_5I,
|
|
24
|
+
LENSES,
|
|
25
|
+
toggleLens
|
|
26
|
+
} from './lens'
|
|
27
|
+
export type { LensPreset } from './lens'
|
|
28
|
+
|
|
29
|
+
const LAYER = 'pointer-events-none fixed inset-0'
|
|
30
|
+
|
|
31
|
+
export function Overlays({ dark, initial }: OverlaysProps) {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<Lens dark={dark} initial={initial} />
|
|
35
|
+
|
|
36
|
+
<Noise className={LAYER} style={{ zIndex: 101 }} />
|
|
37
|
+
<Vignette className={LAYER} style={{ zIndex: 99 }} />
|
|
38
|
+
<Greys className={LAYER} style={{ zIndex: 200 }} />
|
|
39
|
+
<Glitch className={LAYER} style={{ zIndex: 201 }} />
|
|
40
|
+
</>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface OverlaysProps {
|
|
45
|
+
dark?: boolean
|
|
46
|
+
initial?: LensPreset
|
|
47
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
|
|
5
|
+
import { useSmoothControls } from '../../../hooks/use-smooth-controls'
|
|
6
|
+
import { colorMix } from '../../../utils/color'
|
|
7
|
+
|
|
8
|
+
import fillerBg from '../../../assets/filler-bg0.webp'
|
|
9
|
+
|
|
10
|
+
import { BLEND_MODES } from './blend-modes'
|
|
11
|
+
import { $lightMode, LENS_0, LENS_5I, type LensPreset, toggleLens } from './lens'
|
|
12
|
+
|
|
13
|
+
const LAYER = 'pointer-events-none fixed inset-0'
|
|
14
|
+
|
|
15
|
+
export function Lens({ dark, initial }: LensProps) {
|
|
16
|
+
// `initial` lets the host (e.g. Storybook) seed the Leva/atom state with
|
|
17
|
+
// the *exact* lens preset the user selected, avoiding a one-cycle lag
|
|
18
|
+
// where useSmoothControls emits old colors for the first paint (and, on
|
|
19
|
+
// Storybook's fast iframe reload, sometimes never catches up because
|
|
20
|
+
// useControls' ready-gate swallows the instant color writes).
|
|
21
|
+
const base = initial?.Lens ?? (dark ? LENS_0.Lens : LENS_5I.Lens)
|
|
22
|
+
|
|
23
|
+
const lens = useSmoothControls(
|
|
24
|
+
'Lens',
|
|
25
|
+
{
|
|
26
|
+
bgBlend: { options: BLEND_MODES, value: base.bgBlend as 'multiply' },
|
|
27
|
+
bgColor: { value: base.bgColor },
|
|
28
|
+
bgOpacity: { max: 1, min: 0, step: 0.01, value: base.bgOpacity },
|
|
29
|
+
fgBlend: { options: BLEND_MODES, value: 'difference' as const },
|
|
30
|
+
fgColor: { value: base.fgColor },
|
|
31
|
+
fgOpacity: { max: 1, min: 0, step: 0.01, value: base.fgOpacity },
|
|
32
|
+
fillerBlend: { options: BLEND_MODES, value: 'difference' as const },
|
|
33
|
+
fillerOpacity: { max: 1, min: 0, step: 0.01, value: base.fillerOpacity },
|
|
34
|
+
mgColor: { value: base.mgColor },
|
|
35
|
+
mgOpacity: { max: 1, min: 0, step: 0.01, value: base.mgOpacity }
|
|
36
|
+
},
|
|
37
|
+
{ collapsed: false }
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
$lightMode.set(!dark)
|
|
42
|
+
}, [dark])
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const s = document.documentElement.style
|
|
46
|
+
|
|
47
|
+
for (const [name, color, alpha] of [
|
|
48
|
+
['foreground', lens.fgColor, lens.fgOpacity],
|
|
49
|
+
['midground', lens.mgColor, lens.mgOpacity],
|
|
50
|
+
['background', lens.bgColor, lens.bgOpacity]
|
|
51
|
+
] as [string, string, number][]) {
|
|
52
|
+
s.setProperty(`--${name}`, colorMix(color, alpha))
|
|
53
|
+
s.setProperty(`--${name}-base`, color)
|
|
54
|
+
s.setProperty(`--${name}-alpha`, `${alpha}`)
|
|
55
|
+
}
|
|
56
|
+
}, [lens])
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const handle = (e: KeyboardEvent) => e.key === 'x' && toggleLens()
|
|
60
|
+
window.addEventListener('keydown', handle)
|
|
61
|
+
return () => window.removeEventListener('keydown', handle)
|
|
62
|
+
}, [])
|
|
63
|
+
|
|
64
|
+
// NOTE: z-index is inlined because Tailwind's JIT sometimes doesn't emit
|
|
65
|
+
// these non-default utilities (e.g. in Storybook's isolated content
|
|
66
|
+
// scan), which silently collapses the overlay stack to DOM order and
|
|
67
|
+
// breaks the mix-blend-mode inversion — producing a muddy warm wash
|
|
68
|
+
// instead of the intended clean black/white inversion.
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<div
|
|
72
|
+
className={LAYER}
|
|
73
|
+
style={{
|
|
74
|
+
backgroundColor: colorMix(lens.fgColor, lens.fgOpacity),
|
|
75
|
+
mixBlendMode: lens.fgBlend,
|
|
76
|
+
zIndex: 100
|
|
77
|
+
}}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
<div
|
|
81
|
+
className={LAYER}
|
|
82
|
+
style={{
|
|
83
|
+
mixBlendMode: lens.fillerBlend,
|
|
84
|
+
opacity: lens.fillerOpacity,
|
|
85
|
+
zIndex: 2
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
89
|
+
<img
|
|
90
|
+
alt=""
|
|
91
|
+
className="h-[150dvh] w-auto min-w-dvw object-cover object-top-left invert"
|
|
92
|
+
fetchPriority="low"
|
|
93
|
+
src={fillerBg.src}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div
|
|
98
|
+
className={LAYER}
|
|
99
|
+
style={{
|
|
100
|
+
backgroundColor: colorMix(lens.bgColor, lens.bgOpacity),
|
|
101
|
+
mixBlendMode: lens.bgBlend,
|
|
102
|
+
zIndex: 1
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
</>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface LensProps {
|
|
110
|
+
dark?: boolean
|
|
111
|
+
/**
|
|
112
|
+
* Exact preset to seed the internal Leva controls with. When omitted the
|
|
113
|
+
* component falls back to `LENS_0` / `LENS_5I` based on `dark`. Pass the
|
|
114
|
+
* actual preset from a host (e.g. Storybook toolbar) to guarantee the
|
|
115
|
+
* first-paint colors match the selected lens without needing a followup
|
|
116
|
+
* `applyLens` that can be lost in useSmoothControls' startup window.
|
|
117
|
+
*/
|
|
118
|
+
initial?: LensPreset
|
|
119
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { atom } from 'nanostores'
|
|
4
|
+
|
|
5
|
+
import { setControlValue } from '../../../hooks/use-smooth-controls'
|
|
6
|
+
|
|
7
|
+
export const LENS_0 = {
|
|
8
|
+
Globe: { innerColor: '#170d02', innerOpacity: 0.1, outerColor: '#FFAC02' },
|
|
9
|
+
Lens: {
|
|
10
|
+
bgBlend: 'difference',
|
|
11
|
+
bgColor: '#041C1C',
|
|
12
|
+
bgOpacity: 1,
|
|
13
|
+
fgColor: '#FFFFFF',
|
|
14
|
+
fgOpacity: 0,
|
|
15
|
+
fillerOpacity: 0.033,
|
|
16
|
+
mgColor: '#ffe6cb',
|
|
17
|
+
mgOpacity: 1
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const LENS_5I = {
|
|
22
|
+
Globe: { innerColor: '#170d02', innerOpacity: 0.3, outerColor: '#FFAC02' },
|
|
23
|
+
Lens: {
|
|
24
|
+
bgBlend: 'multiply',
|
|
25
|
+
bgColor: '#170d02',
|
|
26
|
+
bgOpacity: 1,
|
|
27
|
+
fgColor: '#FFFFFF',
|
|
28
|
+
fgOpacity: 1,
|
|
29
|
+
fillerOpacity: 0.06,
|
|
30
|
+
mgColor: '#FFAC02',
|
|
31
|
+
mgOpacity: 1
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const lens0 = (
|
|
36
|
+
l?: Partial<typeof LENS_0.Lens>,
|
|
37
|
+
g?: Partial<typeof LENS_0.Globe>
|
|
38
|
+
): LensPreset => ({
|
|
39
|
+
Globe: { ...LENS_0.Globe, ...g },
|
|
40
|
+
Lens: { ...LENS_0.Lens, ...l }
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// The Hermes light-mode look is produced by a fullscreen opaque-white
|
|
44
|
+
// `mix-blend-mode: difference` foreground layer that inverts everything.
|
|
45
|
+
// Colored lenses that want a "white + accent" look MUST be built from
|
|
46
|
+
// LENS_5I, not LENS_0 — otherwise `bgBlend: 'difference'` + an opaque
|
|
47
|
+
// colored bg + active fg inversion land halfway between dark and light
|
|
48
|
+
// mode and produce a muddy warm wash instead of a clean inversion.
|
|
49
|
+
export const lens5i = (
|
|
50
|
+
l?: Partial<typeof LENS_5I.Lens>,
|
|
51
|
+
g?: Partial<typeof LENS_5I.Globe>
|
|
52
|
+
): LensPreset => ({
|
|
53
|
+
Globe: { ...LENS_5I.Globe, ...g },
|
|
54
|
+
Lens: { ...LENS_5I.Lens, ...l }
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Accent colors are the *pre-inversion* source; after the difference FG
|
|
58
|
+
// layer they read as their visual complement. e.g. `#FFAC02` (orange)
|
|
59
|
+
// renders as blue #0053FD on screen — that's the default LENS_5I accent.
|
|
60
|
+
export const LENSES: [string, LensPreset][] = [
|
|
61
|
+
['0', LENS_0],
|
|
62
|
+
['1', lens0({ bgColor: '#0A1F1F' })],
|
|
63
|
+
['2', lens0({ bgColor: '#0E0313', mgColor: '#e6cbff' })],
|
|
64
|
+
['3', lens5i({ mgColor: '#FFAC02' })],
|
|
65
|
+
['4', lens5i({ bgColor: '#0E0313', mgColor: '#FF5500' })],
|
|
66
|
+
['5', lens0({ bgColor: '#1540B1', bgOpacity: 0.7 })],
|
|
67
|
+
['5i', LENS_5I],
|
|
68
|
+
['6', lens5i({ bgColor: '#170D02', mgColor: '#00E5FF' })]
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
export const applyLens = (preset: LensPreset, animate = false) =>
|
|
72
|
+
Object.entries(preset).forEach(([g, v]) =>
|
|
73
|
+
Object.entries(v).forEach(([k, val]) =>
|
|
74
|
+
setControlValue(g, k, val, { animate })
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
export const $lightMode = atom(true)
|
|
79
|
+
|
|
80
|
+
export const toggleLens = () => {
|
|
81
|
+
const isLight = $lightMode.get()
|
|
82
|
+
const next = isLight ? LENS_0 : LENS_5I
|
|
83
|
+
|
|
84
|
+
$lightMode.set(!isLight)
|
|
85
|
+
applyLens(next, true)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface LensPreset {
|
|
89
|
+
Globe: typeof LENS_0.Globe
|
|
90
|
+
Lens: typeof LENS_0.Lens
|
|
91
|
+
}
|