@nastechai-research/ui 0.13.2
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 +5 -0
- package/README.md +21 -0
- package/dist/assets/filler-bg0.webp +0 -0
- package/dist/assets.d.ts +38 -0
- package/dist/fonts/Collapse-Bold.woff2 +0 -0
- package/dist/fonts/Collapse-BoldItalic.woff2 +0 -0
- package/dist/fonts/Collapse-Italic.woff2 +0 -0
- package/dist/fonts/Collapse-Light.woff2 +0 -0
- package/dist/fonts/Collapse-LightItalic.woff2 +0 -0
- package/dist/fonts/Collapse-Regular.woff2 +0 -0
- package/dist/fonts/Collapse-Thin.woff2 +0 -0
- package/dist/fonts/Collapse-ThinItalic.woff2 +0 -0
- package/dist/fonts/Mondwest-Regular.woff2 +0 -0
- package/dist/fonts/Neuebit-Bold.woff2 +0 -0
- package/dist/fonts/RulesCompressed-Medium.woff2 +0 -0
- package/dist/fonts/RulesCompressed-Regular.woff2 +0 -0
- package/dist/fonts/RulesExpanded-Bold.woff2 +0 -0
- package/dist/fonts/RulesExpanded-Regular.woff2 +0 -0
- package/dist/fonts.d.ts +6 -0
- package/dist/fonts.js +6 -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.d.ts +2 -0
- package/dist/hooks/use-capped-frame.js +15 -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.d.ts +1 -0
- package/dist/hooks/use-css-var-dims.js +29 -0
- package/dist/hooks/use-gpu-tier.d.ts +34 -0
- package/dist/hooks/use-gpu-tier.js +111 -0
- package/dist/hooks/use-render-loop.d.ts +41 -0
- package/dist/hooks/use-render-loop.js +63 -0
- package/dist/hooks/use-smooth-controls.d.ts +16 -0
- package/dist/hooks/use-smooth-controls.js +217 -0
- package/dist/hooks/use-toast.d.ts +7 -0
- package/dist/hooks/use-toast.js +21 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +107 -0
- package/dist/ui/basic-page.d.ts +7 -0
- package/dist/ui/basic-page.js +18 -0
- package/dist/ui/build.css +4 -0
- package/dist/ui/components/animated-count.d.ts +10 -0
- package/dist/ui/components/animated-count.js +113 -0
- package/dist/ui/components/ascii.d.ts +10 -0
- package/dist/ui/components/ascii.js +79 -0
- package/dist/ui/components/badge.d.ts +6 -0
- package/dist/ui/components/badge.js +40 -0
- package/dist/ui/components/badges/nous-girl.d.ts +2 -0
- package/dist/ui/components/badges/nous-girl.js +83 -0
- package/dist/ui/components/blend-mode.d.ts +28 -0
- package/dist/ui/components/blend-mode.js +69 -0
- package/dist/ui/components/blink.d.ts +6 -0
- package/dist/ui/components/blink.js +17 -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.d.ts +14 -0
- package/dist/ui/components/button.js +147 -0
- package/dist/ui/components/card.d.ts +5 -0
- package/dist/ui/components/card.js +74 -0
- package/dist/ui/components/checkbox.d.ts +2 -0
- package/dist/ui/components/checkbox.js +27 -0
- package/dist/ui/components/command-block.d.ts +24 -0
- package/dist/ui/components/command-block.js +56 -0
- package/dist/ui/components/confirm-dialog.d.ts +13 -0
- package/dist/ui/components/confirm-dialog.js +113 -0
- package/dist/ui/components/cursor.d.ts +3 -0
- package/dist/ui/components/cursor.js +97 -0
- package/dist/ui/components/dialog.d.ts +15 -0
- package/dist/ui/components/dialog.js +171 -0
- package/dist/ui/components/dropdown-menu.d.ts +12 -0
- package/dist/ui/components/dropdown-menu.js +102 -0
- package/dist/ui/components/fit-text/fit-text.css +42 -0
- package/dist/ui/components/fit-text/index.d.ts +9 -0
- package/dist/ui/components/fit-text/index.js +25 -0
- package/dist/ui/components/graphs/bar-chart.d.ts +12 -0
- package/dist/ui/components/graphs/bar-chart.js +129 -0
- package/dist/ui/components/graphs/index.d.ts +3 -0
- package/dist/ui/components/graphs/index.js +4 -0
- package/dist/ui/components/graphs/line-chart.d.ts +14 -0
- package/dist/ui/components/graphs/line-chart.js +175 -0
- package/dist/ui/components/graphs/utils.d.ts +52 -0
- package/dist/ui/components/graphs/utils.js +162 -0
- package/dist/ui/components/grid/grid.css +79 -0
- package/dist/ui/components/grid/index.d.ts +2 -0
- package/dist/ui/components/grid/index.js +17 -0
- package/dist/ui/components/hover-bg.d.ts +1 -0
- package/dist/ui/components/hover-bg.js +14 -0
- package/dist/ui/components/icons/arrow.d.ts +6 -0
- package/dist/ui/components/icons/arrow.js +44 -0
- package/dist/ui/components/icons/check.d.ts +2 -0
- package/dist/ui/components/icons/check.js +13 -0
- package/dist/ui/components/icons/chevron.d.ts +6 -0
- package/dist/ui/components/icons/chevron.js +51 -0
- package/dist/ui/components/icons/discord.d.ts +2 -0
- package/dist/ui/components/icons/discord.js +15 -0
- package/dist/ui/components/icons/eye.d.ts +2 -0
- package/dist/ui/components/icons/eye.js +8 -0
- package/dist/ui/components/icons/gear.d.ts +6 -0
- package/dist/ui/components/icons/gear.js +30 -0
- package/dist/ui/components/icons/github.d.ts +2 -0
- package/dist/ui/components/icons/github.js +15 -0
- package/dist/ui/components/icons/hamburger.d.ts +6 -0
- package/dist/ui/components/icons/hamburger.js +56 -0
- package/dist/ui/components/icons/heart.d.ts +2 -0
- package/dist/ui/components/icons/heart.js +11 -0
- package/dist/ui/components/icons/index.d.ts +12 -0
- package/dist/ui/components/icons/index.js +13 -0
- package/dist/ui/components/icons/link.d.ts +2 -0
- package/dist/ui/components/icons/link.js +13 -0
- package/dist/ui/components/icons/minus.d.ts +2 -0
- package/dist/ui/components/icons/minus.js +13 -0
- package/dist/ui/components/icons/search.d.ts +2 -0
- package/dist/ui/components/icons/search.js +33 -0
- package/dist/ui/components/image-distortion.d.ts +21 -0
- package/dist/ui/components/image-distortion.js +398 -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.d.ts +1 -0
- package/dist/ui/components/leva-client.js +12 -0
- package/dist/ui/components/list-item.d.ts +6 -0
- package/dist/ui/components/list-item.js +27 -0
- package/dist/ui/components/overlays/blend-modes.d.ts +1 -0
- package/dist/ui/components/overlays/blend-modes.js +14 -0
- package/dist/ui/components/overlays/glitch.d.ts +6 -0
- package/dist/ui/components/overlays/glitch.js +209 -0
- package/dist/ui/components/overlays/greys.d.ts +6 -0
- package/dist/ui/components/overlays/greys.js +339 -0
- package/dist/ui/components/overlays/index.d.ts +14 -0
- package/dist/ui/components/overlays/index.js +34 -0
- package/dist/ui/components/overlays/lens-layers.d.ts +14 -0
- package/dist/ui/components/overlays/lens-layers.js +95 -0
- package/dist/ui/components/overlays/lens.d.ts +44 -0
- package/dist/ui/components/overlays/lens.js +60 -0
- package/dist/ui/components/overlays/noise.d.ts +6 -0
- package/dist/ui/components/overlays/noise.js +136 -0
- package/dist/ui/components/overlays/vignette.d.ts +6 -0
- package/dist/ui/components/overlays/vignette.js +47 -0
- package/dist/ui/components/poster.d.ts +62 -0
- package/dist/ui/components/poster.js +256 -0
- package/dist/ui/components/progress.d.ts +9 -0
- package/dist/ui/components/progress.js +53 -0
- package/dist/ui/components/scene-canvas.d.ts +23 -0
- package/dist/ui/components/scene-canvas.js +179 -0
- package/dist/ui/components/scramble.d.ts +9 -0
- package/dist/ui/components/scramble.js +63 -0
- package/dist/ui/components/segmented.d.ts +20 -0
- package/dist/ui/components/segmented.js +51 -0
- package/dist/ui/components/select.d.ts +18 -0
- package/dist/ui/components/select.js +215 -0
- package/dist/ui/components/selection-switcher.d.ts +1 -0
- package/dist/ui/components/selection-switcher.js +34 -0
- package/dist/ui/components/separator.d.ts +5 -0
- package/dist/ui/components/separator.js +22 -0
- package/dist/ui/components/shader.d.ts +7 -0
- package/dist/ui/components/shader.js +60 -0
- package/dist/ui/components/socials.d.ts +20 -0
- package/dist/ui/components/socials.js +21 -0
- package/dist/ui/components/spinner.d.ts +20 -0
- package/dist/ui/components/spinner.js +38 -0
- package/dist/ui/components/stats.d.ts +16 -0
- package/dist/ui/components/stats.js +36 -0
- package/dist/ui/components/switch.d.ts +7 -0
- package/dist/ui/components/switch.js +37 -0
- package/dist/ui/components/tabs.d.ts +14 -0
- package/dist/ui/components/tabs.js +44 -0
- package/dist/ui/components/terminal-demo.d.ts +32 -0
- package/dist/ui/components/terminal-demo.js +125 -0
- package/dist/ui/components/theme-toggle.d.ts +6 -0
- package/dist/ui/components/theme-toggle.js +66 -0
- package/dist/ui/components/tier-card.d.ts +53 -0
- package/dist/ui/components/tier-card.js +146 -0
- package/dist/ui/components/toast.d.ts +8 -0
- package/dist/ui/components/toast.js +39 -0
- package/dist/ui/components/tv.d.ts +3 -0
- package/dist/ui/components/tv.js +239 -0
- package/dist/ui/components/typography/h1.d.ts +11 -0
- package/dist/ui/components/typography/h1.js +18 -0
- package/dist/ui/components/typography/h2.d.ts +11 -0
- package/dist/ui/components/typography/h2.js +18 -0
- package/dist/ui/components/typography/index.d.ts +15 -0
- package/dist/ui/components/typography/index.js +41 -0
- package/dist/ui/components/typography/legend.d.ts +6 -0
- package/dist/ui/components/typography/legend.js +20 -0
- package/dist/ui/components/typography/small.d.ts +2 -0
- package/dist/ui/components/typography/small.js +9 -0
- package/dist/ui/components/watchlist.d.ts +11 -0
- package/dist/ui/components/watchlist.js +80 -0
- package/dist/ui/fonts.css +63 -0
- package/dist/ui/footer.d.ts +20 -0
- package/dist/ui/footer.js +65 -0
- package/dist/ui/globals.css +395 -0
- package/dist/ui/header.d.ts +41 -0
- package/dist/ui/header.js +270 -0
- package/dist/ui/layout-wrapper.d.ts +1 -0
- package/dist/ui/layout-wrapper.js +7 -0
- package/dist/utils/color.d.ts +4 -0
- package/dist/utils/color.js +14 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +48 -0
- package/dist/utils/poly.d.ts +8 -0
- package/dist/utils/poly.js +3 -0
- package/package.json +120 -0
- 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 +190 -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 +499 -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 +121 -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
|
@@ -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,121 @@
|
|
|
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
|
+
height={1024}
|
|
94
|
+
src={fillerBg.src}
|
|
95
|
+
width={1024}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div
|
|
100
|
+
className={LAYER}
|
|
101
|
+
style={{
|
|
102
|
+
backgroundColor: colorMix(lens.bgColor, lens.bgOpacity),
|
|
103
|
+
mixBlendMode: lens.bgBlend,
|
|
104
|
+
zIndex: 1
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
</>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface LensProps {
|
|
112
|
+
dark?: boolean
|
|
113
|
+
/**
|
|
114
|
+
* Exact preset to seed the internal Leva controls with. When omitted the
|
|
115
|
+
* component falls back to `LENS_0` / `LENS_5I` based on `dark`. Pass the
|
|
116
|
+
* actual preset from a host (e.g. Storybook toolbar) to guarantee the
|
|
117
|
+
* first-paint colors match the selected lens without needing a followup
|
|
118
|
+
* `applyLens` that can be lost in useSmoothControls' startup window.
|
|
119
|
+
*/
|
|
120
|
+
initial?: LensPreset
|
|
121
|
+
}
|
|
@@ -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
|
+
}
|