apex-ui-kit 2.8.0 → 2.9.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/ReadMe.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/apex-ui-kit?color=a3e635)](https://www.npmjs.com/package/apex-ui-kit)
5
5
  [![license](https://img.shields.io/npm/l/apex-ui-kit?color=a3e635)](./Licence.md)
6
6
 
7
- **115 free, animated UI components for React.** Pick one, add it with a single command, and the source lands in your project so you can read it and change anything.
7
+ **116 free, animated UI components for React.** Pick one, add it with a single command, and the source lands in your project so you can read it and change anything.
8
8
 
9
9
  Built with React, Tailwind CSS and Framer Motion, plus GSAP, Canvas and WebGL where a component needs them.
10
10
 
@@ -15,7 +15,7 @@ Built with React, Tailwind CSS and Framer Motion, plus GSAP, Canvas and WebGL wh
15
15
 
16
16
  ## Why ApexUI
17
17
 
18
- - **115 components** across backgrounds, cards, text effects, navigation, buttons, interactive UI, AI interfaces, loaders and maps.
18
+ - **116 components** across backgrounds, cards, text effects, navigation, buttons, interactive UI, AI interfaces, loaders and maps.
19
19
  - **You own the code.** The CLI copies plain `.jsx` files into your project, no runtime package to keep in sync.
20
20
  - **Every docs page has** a live preview, a playground to tweak props, a Desktop / Tablet / Mobile preview, the full props table and a ready prompt you can paste into ChatGPT or Claude.
21
21
  - **Smooth on real devices.** Animations stick to transform and opacity, respect reduced motion, and work with mouse, touch and keyboard.
@@ -196,7 +196,7 @@ Every component's props, variants and copy-ready code are on its docs page.
196
196
  | [Magnetic Button](https://apex-ui.in/components/magnetic-button) | `magnetic-button` |
197
197
  | [Star Border](https://apex-ui.in/components/star-border) | `star-border` |
198
198
 
199
- ### Interactive (30)
199
+ ### Interactive (31)
200
200
 
201
201
  | Component | CLI name |
202
202
  | --- | --- |
@@ -219,6 +219,7 @@ Every component's props, variants and copy-ready code are on its docs page.
219
219
  | [Image Compare](https://apex-ui.in/components/image-compare) | `image-compare` |
220
220
  | [Infinite Canvas](https://apex-ui.in/components/infinite-canvas) | `infinite-canvas` |
221
221
  | [Liquid Blob 3D](https://apex-ui.in/components/liquid-blob-3d) | `liquid-blob-3d` |
222
+ | [Liquid Glass](https://apex-ui.in/components/liquid-glass) | `liquid-glass` |
222
223
  | [Magnet Lines](https://apex-ui.in/components/magnet-lines) | `magnet-lines` |
223
224
  | [Magnetic Cursor](https://apex-ui.in/components/magnetic-cursor) | `magnetic-cursor` |
224
225
  | [Marquee 3D](https://apex-ui.in/components/marquee-3d) | `marquee-3d` |
@@ -259,8 +260,9 @@ Every component's props, variants and copy-ready code are on its docs page.
259
260
  | [Dot Globe](https://apex-ui.in/components/dot-globe) | `dot-globe` |
260
261
  | [World Map](https://apex-ui.in/components/world-map) | `world-map` |
261
262
 
262
- ## What's new in 2.8
263
+ ## What's new in 2.9
263
264
 
265
+ - New in 2.9: Liquid Glass (iOS style WebGL glass), 10 live page templates on the site, Arc Gallery auto scroll, Scroll Stack on the page scroll, Sunrise trees and birds.
264
266
  - New in 2.8: Glass Lens (WebGL liquid glass), Dither Waves (WebGL 1-bit background), Infinite Canvas, Image Accordion, Orbit Icons, Command Palette, Crossed Marquee (tech-stack bands with simple-icons) and Theme Switcher (8 themes with a circle reveal).
265
267
  - Toast Stack and Radial Menu no longer blink in production builds; Radial Menu, 3D Text Ring, Animated Tabs and Gooey Nav now fit 320px phones.
266
268
  - 38 new components since 2.6, including WebGL and three.js (Sunrise Background, Horizon Glow, Circular Carousel, Arc Gallery, Galaxy 3D, Warp Tunnel, Wireframe Knot 3D, Marquee 3D, Distortion Image, Liquid Blob 3D, Particle Morph, Retro Grid 3D), Scroll Stack, Particle Text, Edge Blur, Magnetic Button, Radial Menu, the AI voice family (Wave, Bars, Ring, Blob), Gooey Nav (WebGL metaball pill), Coverflow 3D, Card Swap 3D, 3D Text Ring, Elastic Slider, Stepper and Pixel Transition.
@@ -3,9 +3,10 @@ import { Mesh, OrthographicCamera, PlaneGeometry, Scene, ShaderMaterial, SRGBCol
3
3
 
4
4
  /*
5
5
  * Arc Gallery: an endless row of image cards laid along a gentle arc and
6
- * drawn in WebGL. Drag, flick or scroll the wheel to move it; it eases to a
7
- * stop and settles on a card. While it moves the cards ripple slightly,
8
- * the edges fade out, and a title sits under each card. Arrow keys step.
6
+ * drawn in WebGL. It drifts on its own (pausing on hover); drag, flick or
7
+ * swipe sideways to move it and it eases to a stop on a card. While it
8
+ * moves the cards ripple and bow, the edges fade out, and a title sits under
9
+ * each card. Arrow keys step. Vertical wheel scrolling is left to the page.
9
10
  */
10
11
 
11
12
  const IK = "https://ik.imagekit.io/ny6yinyut/apexui/assets";
@@ -54,13 +55,13 @@ void main(){
54
55
  gl_FragColor = vec4(col, edge * f * uReady);
55
56
  }`;
56
57
 
57
- const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspectRatio = 0.78, gap = 0.22, cornerRadius = 0.05, wave = 1, pull = 1, fadeEdges = 0.14, ease = 0.08, snap = true, showTitles = true, height = "26rem", onChange, className = "" }) => {
58
+ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspectRatio = 0.78, gap = 0.22, cornerRadius = 0.05, wave = 1, pull = 1, fadeEdges = 0.14, ease = 0.08, snap = true, autoScroll = 40, pauseOnHover = true, showTitles = true, height = "26rem", onChange, className = "" }) => {
58
59
  const wrap = useRef(null);
59
60
  const labels = useRef([]);
60
61
  const api = useRef(null);
61
62
  const [active, setActive] = useState(0);
62
63
  const live = useRef({});
63
- live.current = { bend, wave, pull, fadeEdges, ease, snap, onChange };
64
+ live.current = { bend, wave, pull, fadeEdges, ease, snap, autoScroll, pauseOnHover, onChange };
64
65
  const list = items.length ? items : DEFAULT_ITEMS;
65
66
  const key = list.map((i) => i.image).join("|");
66
67
 
@@ -84,7 +85,6 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
84
85
  const textures = list.map((item) => {
85
86
  const entry = { tex: null, aspect: 1, at: 0 };
86
87
  loader.load(item.image, (t) => {
87
- t.colorSpace = SRGBColorSpace;
88
88
  entry.tex = t;
89
89
  entry.aspect = t.image.width / t.image.height;
90
90
  entry.at = performance.now();
@@ -116,7 +116,8 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
116
116
  return { mesh, m };
117
117
  };
118
118
 
119
- const s = { scroll: 0, target: 0, vel: 0, pull: 0, drag: null, time: 0 };
119
+ const s = { scroll: 0, target: 0, vel: 0, pull: 0, drag: null, time: 0, hover: false, hold: 0 };
120
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
120
121
  let cardW = 1;
121
122
  let cardH = 1;
122
123
  let spacing = 1;
@@ -172,6 +173,10 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
172
173
  const o = live.current;
173
174
  const dt = last ? Math.min(0.05, (now - last) / 1000) : 1 / 60;
174
175
  last = now;
176
+ // Auto scroll drifts the row along, pausing while hovered, dragged or
177
+ // just after someone moved it by hand.
178
+ const auto = o.autoScroll && !reduced && !s.drag && !(o.pauseOnHover && s.hover) && now > s.hold;
179
+ if (auto) s.target += o.autoScroll * dt;
175
180
  const before = s.scroll;
176
181
  const follow = 1 - Math.pow(1 - Math.min(1, Math.max(0.01, o.ease)), dt * 60);
177
182
  s.scroll += (s.target - s.scroll) * follow;
@@ -185,7 +190,7 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
185
190
  const pullGoal = Math.max(-1, Math.min(1, s.vel / (spacing * 7))) * 0.32 * o.pull;
186
191
  s.pull += (pullGoal - s.pull) * (1 - Math.exp(-dt / 0.12));
187
192
  if (Math.abs(s.pull) < 0.0005 && !pullGoal) s.pull = 0;
188
- let busy = s.pull !== 0 || s.scroll !== s.target || s.vel !== 0 || !!s.drag;
193
+ let busy = auto || s.pull !== 0 || s.scroll !== s.target || s.vel !== 0 || !!s.drag;
189
194
 
190
195
  const first = Math.floor((s.scroll - view.w / 2) / spacing) - 1;
191
196
  slots.forEach((slot, k) => {
@@ -251,6 +256,7 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
251
256
  const onDown = (e) => {
252
257
  if (e.button !== 0) return;
253
258
  s.drag = { x: e.clientX, start: s.target, moved: false, samples: [] };
259
+ wake();
254
260
  };
255
261
  const onMove = (e) => {
256
262
  const g = s.drag;
@@ -269,6 +275,7 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
269
275
  const onUp = (e) => {
270
276
  const g = s.drag;
271
277
  s.drag = null;
278
+ s.hold = performance.now() + 1800;
272
279
  if (!g) return;
273
280
  if (g.moved && g.samples.length > 1) {
274
281
  const a = g.samples[0];
@@ -286,10 +293,12 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
286
293
  settle();
287
294
  };
288
295
  const onWheel = (e) => {
289
- const d = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
290
- if (!d) return;
296
+ // Only sideways wheel / trackpad swipes move the row; vertical
297
+ // scrolling belongs to the page.
298
+ if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
291
299
  e.preventDefault();
292
- s.target += d * 1.2;
300
+ s.target += e.deltaX * 1.2;
301
+ s.hold = performance.now() + 1800;
293
302
  clearTimeout(snapTimer);
294
303
  snapTimer = setTimeout(settle, 140);
295
304
  wake();
@@ -299,6 +308,13 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
299
308
  canvas.addEventListener("pointerup", onUp);
300
309
  canvas.addEventListener("pointercancel", onUp);
301
310
  canvas.addEventListener("wheel", onWheel, { passive: false });
311
+ const onEnter = () => (s.hover = true);
312
+ const onLeave = () => {
313
+ s.hover = false;
314
+ wake();
315
+ };
316
+ canvas.addEventListener("pointerenter", onEnter);
317
+ canvas.addEventListener("pointerleave", onLeave);
302
318
  const io = new IntersectionObserver(([entry]) => {
303
319
  visible = entry.isIntersecting;
304
320
  wake();
@@ -316,6 +332,8 @@ const ArcGallery = ({ items = DEFAULT_ITEMS, bend = 1, cardHeight = 0.62, aspect
316
332
  canvas.removeEventListener("pointerup", onUp);
317
333
  canvas.removeEventListener("pointercancel", onUp);
318
334
  canvas.removeEventListener("wheel", onWheel);
335
+ canvas.removeEventListener("pointerenter", onEnter);
336
+ canvas.removeEventListener("pointerleave", onLeave);
319
337
  textures.forEach((t) => t.tex?.dispose());
320
338
  slots.forEach((slot) => slot.m.dispose());
321
339
  geometry.dispose();
@@ -124,7 +124,6 @@ const CircularCarousel = ({
124
124
  },
125
125
  });
126
126
  loader.load(item.src, (t) => {
127
- t.colorSpace = SRGBColorSpace;
128
127
  m.uniforms.uTex.value = t;
129
128
  const ia = t.image.width / t.image.height;
130
129
  // Cover crop: show the middle of the image at the card's shape.
@@ -92,8 +92,9 @@ const FlipClock = ({ mode = "seconds", target, seconds = 3 * 86400 + 4 * 3600 +
92
92
 
93
93
  return (
94
94
  // The clock is sized in em; on narrow screens the font shrinks with the
95
- // container (cqw) so every unit fits instead of being cut off.
96
- <div className="flex w-full justify-center" style={{ containerType: "inline-size" }}>
95
+ // container (cqw) so every unit fits instead of being cut off. The min
96
+ // width keeps it from collapsing inside a shrink-to-fit parent.
97
+ <div className="flex w-full justify-center" style={{ containerType: "inline-size", minWidth: `min(${(size * parts.length * 2.55).toFixed(2)}rem, 92vw)` }}>
97
98
  <div role="timer" aria-live="off" aria-label={parts.map(([l, v]) => `${v} ${l}`).join(", ")} className={`inline-flex items-start gap-[0.35em] font-mono font-bold ${className}`} style={{ fontSize: `min(${size}rem, ${(100 / (parts.length * 2.55)).toFixed(2)}cqw)` }}>
98
99
  {parts.map(([label, value], i) => (
99
100
  <React.Fragment key={label}>
@@ -6,24 +6,31 @@ function cn(...classes) {
6
6
  import React, { useRef } from "react";
7
7
  import { motion, useMotionValue, useSpring, useMotionTemplate } from "framer-motion";
8
8
  const bgHexa = 'https://ik.imagekit.io/ny6yinyut/apexui/assets/bg-hexa.jpg?tr=f-auto,q-80';
9
- export default function GlareCardDemo() {
10
- return (
11
- <GlareCard className="flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 via-slate-800 to-[#181825] p-8 border border-slate-800 shadow-2xl relative overflow-hidden group group-hover:scale-100 transition-transform duration-300 ease-in-out">
12
- {/* Background Image */}
13
- <img
14
- src={bgHexa}
15
- alt="bg"
16
- className="absolute inset-0 w-full h-full object-cover opacity-30 pointer-events-none select-none z-0"
17
- draggable="false"
18
- />
19
- <div className="flex items-center justify-center w-20 h-20 rounded-md bg-gradient-to-br from-[#23233a] to-[#181825] border-2 border-[#a855f7]/60 shadow-xl mb-4 z-10 group">
20
- <span className="text-5xl font-extrabold text-[#a855f7] drop-shadow-[0_2px_12px_#a855f7aa]">A</span>
21
- </div>
22
- <p className="text-slate-200 font-extrabold text-2xl mt-2 tracking-wide z-10">ApexUI</p>
23
- </GlareCard>
24
- );
25
- }
26
-
9
+ // Shown when no children are passed (the look from the docs preview).
10
+ const DEMO_CLASS = "flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 via-slate-800 to-[#181825] p-8 border border-slate-800 shadow-2xl relative overflow-hidden group group-hover:scale-100 transition-transform duration-300 ease-in-out";
11
+ const DemoContent = () => (
12
+ <>
13
+ <img src={bgHexa} alt="bg" className="absolute inset-0 w-full h-full object-cover opacity-30 pointer-events-none select-none z-0" draggable="false" />
14
+ <div className="flex items-center justify-center w-20 h-20 rounded-md bg-gradient-to-br from-[#23233a] to-[#181825] border-2 border-[#a855f7]/60 shadow-xl mb-4 z-10 group">
15
+ <span className="text-5xl font-extrabold text-[#a855f7] drop-shadow-[0_2px_12px_#a855f7aa]">A</span>
16
+ </div>
17
+ <p className="text-slate-200 font-extrabold text-2xl mt-2 tracking-wide z-10">ApexUI</p>
18
+ </>
19
+ );
20
+
21
+ // Default export: renders your children inside the card, or the demo when there are none.
22
+ export default function GlareCardDefault({ children, className = "", ...rest }) {
23
+ return children ? (
24
+ <GlareCard className={className} {...rest}>
25
+ {children}
26
+ </GlareCard>
27
+ ) : (
28
+ <GlareCard className={`${DEMO_CLASS} ${className}`} {...rest}>
29
+ <DemoContent />
30
+ </GlareCard>
31
+ );
32
+ }
33
+
27
34
  export function GlareCard({
28
35
  children,
29
36
  className = "",
@@ -126,7 +126,6 @@ const GlassLens = ({ image = DEFAULT_IMAGE, size = 9, refraction = 0.7, dispersi
126
126
  u.uCover.value.set(ia > a ? a / ia : 1, ia > a ? 1 : ia / a);
127
127
  };
128
128
  new TextureLoader().setCrossOrigin("anonymous").load(image, (t) => {
129
- t.colorSpace = SRGBColorSpace;
130
129
  u.uTex.value = t;
131
130
  view.imgAspect = t.image.width / t.image.height;
132
131
  cover();
@@ -0,0 +1,444 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import { Color, Mesh, OrthographicCamera, PlaneGeometry, Scene, ShaderMaterial, TextureLoader, Vector2, Vector4, WebGLRenderer } from "three";
3
+ import { CloudSun, Compass, Home, Music2, Plus, Search, User } from "lucide-react";
4
+
5
+ /*
6
+ * Liquid Glass: iOS style glass panels over a wallpaper, drawn in one WebGL
7
+ * shader. Each panel bends the picture at its rounded edges, frosts it a
8
+ * little inside, splits colours along the rim and catches a specular edge
9
+ * light. Panels are draggable, wobble like jelly while they move, and melt
10
+ * into each other when they get close. Content inside each panel is plain
11
+ * HTML that rides along with it.
12
+ *
13
+ * Props: items ({ id, x, y, offset?, width, maxWidth?, height, radius, content }[],
14
+ * up to 4; x / y place the centre as 0..1 of the box, offset nudges x in px,
15
+ * a width of 1 or less is a share of the box width), children (shown under
16
+ * the glass, e.g. hero text), image (wallpaper; a soft animated gradient when empty),
17
+ * refraction, frost, chroma, vibrancy, tint, tintAmount, merge (px of liquid
18
+ * blending),
19
+ * draggable, height, className.
20
+ */
21
+
22
+ const VERT = `void main(){ gl_Position = vec4(position.xy, 0.0, 1.0); }`;
23
+
24
+ const FRAG = `
25
+ precision highp float;
26
+ uniform vec2 uRes; uniform float uTime; uniform float uDpr;
27
+ uniform sampler2D uTex; uniform float uHasTex; uniform vec2 uCover;
28
+ uniform vec4 uBox[4]; uniform float uRad[4]; uniform float uCount;
29
+ uniform float uRefract; uniform float uFrost; uniform float uChroma; uniform vec3 uTint; uniform float uTintAmt; uniform float uMerge;
30
+ uniform vec2 uLight; uniform float uVibrancy;
31
+
32
+ float sdBox(vec2 p, vec2 b, float r){ vec2 q = abs(p) - b + r; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r; }
33
+ float smin(float a, float b, float k){ float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); return mix(b, a, h) - k * h * (1.0 - h); }
34
+
35
+ float scene(vec2 p){
36
+ float d = 1e5;
37
+ for (int i = 0; i < 4; i++) {
38
+ if (float(i) >= uCount) break;
39
+ float s = sdBox(p - uBox[i].xy, uBox[i].zw, uRad[i]);
40
+ d = smin(d, s, max(uMerge, 0.001));
41
+ }
42
+ return d;
43
+ }
44
+
45
+ // Half-size (shorter side) of the panel closest to p, for a bevel that suits it.
46
+ float nearestSize(vec2 p){
47
+ float best = 1e5; float size = 30.0;
48
+ for (int i = 0; i < 4; i++) {
49
+ if (float(i) >= uCount) break;
50
+ float s = sdBox(p - uBox[i].xy, uBox[i].zw, uRad[i]);
51
+ if (s < best) { best = s; size = min(uBox[i].z, uBox[i].w); }
52
+ }
53
+ return size;
54
+ }
55
+
56
+ // Default wallpaper: drifting colour blobs with fine stripes so the bending shows.
57
+ vec3 wallpaper(vec2 uv){
58
+ float t = uTime * 0.06;
59
+ vec2 a = vec2(0.25 + 0.15 * sin(t * 2.1), 0.3 + 0.2 * cos(t * 1.7));
60
+ vec2 b = vec2(0.75 + 0.12 * cos(t * 1.3), 0.65 + 0.15 * sin(t * 2.3));
61
+ vec2 c = vec2(0.5 + 0.25 * sin(t * 0.9 + 2.0), 0.15 + 0.1 * cos(t * 1.1));
62
+ vec2 asp = vec2(uRes.x / uRes.y, 1.0);
63
+ vec3 col = vec3(0.06, 0.07, 0.12);
64
+ col += vec3(1.0, 0.36, 0.42) * exp(-length((uv - a) * asp) * 3.2) * 0.95;
65
+ col += vec3(0.25, 0.45, 1.0) * exp(-length((uv - b) * asp) * 2.8) * 0.95;
66
+ col += vec3(1.0, 0.75, 0.25) * exp(-length((uv - c) * asp) * 3.6) * 0.8;
67
+ float stripes = smoothstep(0.35, 0.5, abs(fract((uv.x * asp.x + uv.y) * 14.0) - 0.5));
68
+ col *= 0.88 + 0.12 * stripes;
69
+ return col;
70
+ }
71
+
72
+ vec3 bg(vec2 px){
73
+ vec2 uv = px / uRes;
74
+ if (uHasTex > 0.5) return texture2D(uTex, (uv - 0.5) * uCover + 0.5).rgb;
75
+ return wallpaper(uv);
76
+ }
77
+
78
+ void main(){
79
+ vec2 p = gl_FragCoord.xy;
80
+ float d = scene(p);
81
+ vec3 base = bg(p);
82
+
83
+ // Two soft shadows under the glass: a tight contact one and a wide ambient one.
84
+ float sd = scene(p + vec2(0.0, 6.0 * uDpr));
85
+ float sd2 = scene(p + vec2(0.0, 18.0 * uDpr));
86
+ float outside = step(0.0, d);
87
+ vec3 col = base * (1.0 - outside * (0.22 * exp(-max(sd, 0.0) / (8.0 * uDpr)) + 0.18 * exp(-max(sd2, 0.0) / (34.0 * uDpr))));
88
+
89
+ if (d < 1.5 * uDpr) {
90
+ vec2 e = vec2(1.0, 0.0);
91
+ vec2 n = normalize(vec2(scene(p + e.xy) - scene(p - e.xy), scene(p + e.yx) - scene(p - e.yx)) + 1e-6);
92
+ // The rim curves over about half of the short side, like a real slab of glass.
93
+ float bevel = clamp(nearestSize(p) * 0.55, 10.0 * uDpr, 34.0 * uDpr);
94
+ float depth = clamp(-d / bevel, 0.0, 1.0); // 0 at the rim, 1 on the flat top
95
+ // A rounded edge profile: the steeper the surface, the more it bends light.
96
+ float x = 1.0 - depth;
97
+ float h = sqrt(max(1.0 - x * x, 0.0));
98
+ float slope = x / max(h, 0.08);
99
+ vec2 off = n * min(slope, 6.0) * uRefract * bevel * 0.32;
100
+ vec3 g = vec3(0.0);
101
+ float f = uFrost * 5.0 * uDpr;
102
+ for (int i = 0; i < 9; i++) {
103
+ float a = float(i) * 2.399;
104
+ vec2 o = f > 0.0 ? vec2(cos(a), sin(a)) * f * sqrt(float(i) / 9.0) : vec2(0.0);
105
+ vec2 q = p + off + o;
106
+ vec2 ch = n * x * x * uChroma * 2.5 * uDpr;
107
+ g += vec3(bg(q + ch).r, bg(q).g, bg(q - ch).b);
108
+ }
109
+ g /= 9.0;
110
+ // Vibrancy: what shows through gets a little richer and brighter.
111
+ float luma = dot(g, vec3(0.299, 0.587, 0.114));
112
+ g = mix(vec3(luma), g, 1.0 + 0.35 * uVibrancy) * (1.0 + 0.07 * uVibrancy) + 0.02 * uVibrancy;
113
+ g = mix(g, uTint, uTintAmt);
114
+
115
+ // Light follows the pointer, like tilting a phone.
116
+ vec2 L = normalize(uLight);
117
+ float facing = max(dot(n, L), 0.0);
118
+ float away = max(dot(n, -L), 0.0);
119
+ // Fresnel: the curved edge reflects more than the flat top.
120
+ g = mix(g, vec3(1.0), pow(x, 3.0) * 0.18);
121
+ // A crisp specular line on the rim, strongest facing the light, with a
122
+ // fainter bounce on the opposite edge.
123
+ float line = smoothstep(1.6 * uDpr, 0.0, abs(d + 0.9 * uDpr));
124
+ g += line * (0.75 * pow(facing, 1.5) + 0.32 * pow(away, 2.0) + 0.06);
125
+ // A broad glint just inside the lit edge.
126
+ g += smoothstep(0.55, 1.0, x) * pow(facing, 3.0) * 0.22;
127
+ // Inner shadow along the far edge gives the slab some thickness.
128
+ g *= 1.0 - smoothstep(0.6, 1.0, x) * pow(away, 1.5) * 0.18;
129
+ float mask = smoothstep(0.75 * uDpr, -0.75 * uDpr, d);
130
+ col = mix(col, g, mask);
131
+ }
132
+ gl_FragColor = vec4(col, 1.0);
133
+ }`;
134
+
135
+ const Tab = ({ icon: Icon, label, active }) => (
136
+ <span className={`flex flex-1 flex-col items-center gap-0.5 rounded-full py-1.5 text-[0.62rem] font-semibold ${active ? "bg-white/25 text-white" : "text-white/85"}`}>
137
+ <Icon className="h-[18px] w-[18px]" strokeWidth={2.2} />
138
+ {label}
139
+ </span>
140
+ );
141
+
142
+ const DEFAULT_ITEMS = [
143
+ {
144
+ id: "bar",
145
+ x: 0.5,
146
+ y: 0.84,
147
+ width: 300,
148
+ height: 62,
149
+ radius: 31,
150
+ offset: -36,
151
+ content: (
152
+ <div className="flex h-full items-center gap-1 px-2">
153
+ <Tab icon={Home} label="Home" active />
154
+ <Tab icon={Compass} label="Explore" />
155
+ <Tab icon={Music2} label="Music" />
156
+ <Tab icon={User} label="Profile" />
157
+ </div>
158
+ ),
159
+ },
160
+ {
161
+ id: "search",
162
+ x: 0.5,
163
+ y: 0.84,
164
+ width: 62,
165
+ height: 62,
166
+ radius: 31,
167
+ offset: 155,
168
+ content: (
169
+ <div className="grid h-full place-items-center text-white">
170
+ <Search className="h-5 w-5" strokeWidth={2.4} />
171
+ </div>
172
+ ),
173
+ },
174
+ {
175
+ id: "widget",
176
+ x: 0.3,
177
+ y: 0.3,
178
+ width: 170,
179
+ height: 150,
180
+ radius: 34,
181
+ content: (
182
+ <div className="flex h-full flex-col justify-between p-4 text-white">
183
+ <div className="flex items-center justify-between text-xs font-semibold text-white/85">
184
+ Cupertino <CloudSun className="h-5 w-5" />
185
+ </div>
186
+ <div>
187
+ <p className="text-4xl font-light leading-none">24°</p>
188
+ <p className="mt-1 text-[0.7rem] text-white/80">Partly cloudy · H 27° L 18°</p>
189
+ </div>
190
+ </div>
191
+ ),
192
+ },
193
+ {
194
+ id: "add",
195
+ x: 0.74,
196
+ y: 0.3,
197
+ width: 64,
198
+ height: 64,
199
+ radius: 22,
200
+ content: (
201
+ <div className="grid h-full place-items-center text-white">
202
+ <Plus className="h-6 w-6" strokeWidth={2.4} />
203
+ </div>
204
+ ),
205
+ },
206
+ ];
207
+
208
+ const LiquidGlass = ({ items = DEFAULT_ITEMS, children, image, refraction = 1, frost = 0.2, chroma = 1, vibrancy = 1, tint = "#ffffff", tintAmount = 0.06, merge = 18, draggable = true, height = "32rem", className = "" }) => {
209
+ const wrap = useRef(null);
210
+ const panels = useRef([]);
211
+ const live = useRef({});
212
+ live.current = { refraction, frost, chroma, vibrancy, tint, tintAmount, merge, draggable };
213
+ const list = items.slice(0, 4);
214
+
215
+ useEffect(() => {
216
+ const el = wrap.current;
217
+ const renderer = new WebGLRenderer({ antialias: false, alpha: false, powerPreference: "high-performance" });
218
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
219
+ renderer.setPixelRatio(dpr);
220
+ Object.assign(renderer.domElement.style, { position: "absolute", inset: "0", width: "100%", height: "100%", display: "block" });
221
+ el.prepend(renderer.domElement);
222
+ const scene = new Scene();
223
+ const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
224
+ const u = {
225
+ uRes: { value: new Vector2(1, 1) },
226
+ uTime: { value: 0 },
227
+ uDpr: { value: dpr },
228
+ uTex: { value: null },
229
+ uHasTex: { value: 0 },
230
+ uCover: { value: new Vector2(1, 1) },
231
+ uBox: { value: Array.from({ length: 4 }, () => new Vector4()) },
232
+ uRad: { value: [0, 0, 0, 0] },
233
+ uCount: { value: 0 },
234
+ uRefract: { value: 1 },
235
+ uFrost: { value: 0.2 },
236
+ uLight: { value: new Vector2(-0.6, 0.8) },
237
+ uVibrancy: { value: 1 },
238
+ uChroma: { value: 1 },
239
+ uTint: { value: new Color(1, 1, 1) },
240
+ uTintAmt: { value: 0.08 },
241
+ uMerge: { value: 18 },
242
+ };
243
+ const material = new ShaderMaterial({ vertexShader: VERT, fragmentShader: FRAG, uniforms: u, depthTest: false, depthWrite: false });
244
+ const geometry = new PlaneGeometry(2, 2);
245
+ scene.add(new Mesh(geometry, material));
246
+
247
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
248
+ const view = { w: 1, h: 1, imgAspect: 1 };
249
+ // Panel state in CSS px: target (tx, ty), spring position, velocity, press.
250
+ const st = list.map((it) => ({ it, tx: 0, ty: 0, x: 0, y: 0, vx: 0, vy: 0, press: 0, moved: false, placed: false }));
251
+ let raf = 0;
252
+ let last = 0;
253
+ let time = 0;
254
+ let visible = true;
255
+ let drag = null;
256
+ const light = { x: -0.6, y: 0.8, on: false };
257
+
258
+ const scaleFor = () => Math.min(1, (view.w - 24) / 360);
259
+ const place = () => {
260
+ const s = scaleFor();
261
+ st.forEach((p) => {
262
+ if (p.moved && p.placed) return;
263
+ p.tx = view.w * p.it.x + (p.it.offset ?? 0) * s;
264
+ p.ty = view.h * p.it.y;
265
+ if (!p.placed) {
266
+ p.x = p.tx;
267
+ p.y = p.ty;
268
+ p.placed = true;
269
+ }
270
+ });
271
+ };
272
+ const cover = () => {
273
+ const a = view.w / view.h;
274
+ const ia = view.imgAspect;
275
+ u.uCover.value.set(ia > a ? a / ia : 1, ia > a ? 1 : ia / a);
276
+ };
277
+ if (image) {
278
+ new TextureLoader().setCrossOrigin("anonymous").load(image, (t) => {
279
+ u.uTex.value = t;
280
+ u.uHasTex.value = 1;
281
+ view.imgAspect = t.image.width / t.image.height;
282
+ cover();
283
+ start();
284
+ });
285
+ }
286
+
287
+ const fit = () => {
288
+ const { width, height: hh } = el.getBoundingClientRect();
289
+ if (!width || !hh) return;
290
+ view.w = width;
291
+ view.h = hh;
292
+ renderer.setSize(width, hh, false);
293
+ u.uRes.value.set(width * dpr, hh * dpr);
294
+ cover();
295
+ place();
296
+ start();
297
+ };
298
+
299
+ const frame = (now) => {
300
+ raf = 0;
301
+ const o = live.current;
302
+ const dt = last ? Math.min(0.05, (now - last) / 1000) : 1 / 60;
303
+ last = now;
304
+ time += dt;
305
+ const s = scaleFor();
306
+ let busy = !u.uHasTex.value && !reduced; // the default wallpaper drifts
307
+ st.forEach((p, i) => {
308
+ // Jelly spring: a little underdamped so panels wobble into place.
309
+ const k = 260;
310
+ const c = 2 * Math.sqrt(k) * 0.62;
311
+ p.vx += (k * (p.tx - p.x) - c * p.vx) * dt;
312
+ p.vy += (k * (p.ty - p.y) - c * p.vy) * dt;
313
+ p.x += p.vx * dt;
314
+ p.y += p.vy * dt;
315
+ p.press += ((drag?.i === i ? 1 : 0) - p.press) * (1 - Math.exp(-dt / 0.08));
316
+ const speed = Math.hypot(p.vx, p.vy);
317
+ if (speed > 0.5 || Math.abs(p.tx - p.x) > 0.2 || Math.abs(p.ty - p.y) > 0.2 || Math.abs(p.press - (drag?.i === i ? 1 : 0)) > 0.01) busy = true;
318
+ // Stretch along the motion, squash across it; grow a touch while held.
319
+ const sx = 1 + Math.min(0.18, Math.abs(p.vx) / 4000) - Math.min(0.1, Math.abs(p.vy) / 6000);
320
+ const sy = 1 + Math.min(0.18, Math.abs(p.vy) / 4000) - Math.min(0.1, Math.abs(p.vx) / 6000);
321
+ const grow = 1 + p.press * 0.06;
322
+ // A width of 1 or less is a share of the box (handy for nav bars).
323
+ const baseW = Math.min(p.it.width <= 1 ? p.it.width * view.w : p.it.width * s, p.it.maxWidth ?? Infinity);
324
+ const hw = (baseW * sx * grow) / 2;
325
+ const hh = (p.it.height * s * sy * grow) / 2;
326
+ u.uBox.value[i].set(p.x * dpr, (view.h - p.y) * dpr, hw * dpr, hh * dpr);
327
+ u.uRad.value[i] = Math.min(p.it.radius * s * grow, hw, hh) * dpr;
328
+ const node = panels.current[i];
329
+ if (node) {
330
+ node.style.width = `${hw * 2}px`;
331
+ node.style.height = `${hh * 2}px`;
332
+ node.style.transform = `translate3d(${(p.x - hw).toFixed(2)}px, ${(p.y - hh).toFixed(2)}px, 0)`;
333
+ }
334
+ });
335
+ u.uCount.value = st.length;
336
+ // Ease the light toward the pointer direction from the centre.
337
+ const lv = u.uLight.value;
338
+ const lx = light.on ? light.x : -0.6;
339
+ const ly = light.on ? light.y : 0.8;
340
+ lv.set(lv.x + (lx - lv.x) * (1 - Math.exp(-dt / 0.25)), lv.y + (ly - lv.y) * (1 - Math.exp(-dt / 0.25)));
341
+ if (Math.abs(lx - lv.x) + Math.abs(ly - lv.y) > 0.002) busy = true;
342
+ u.uVibrancy.value = o.vibrancy;
343
+ u.uTime.value = time;
344
+ u.uRefract.value = o.refraction;
345
+ u.uFrost.value = o.frost;
346
+ u.uChroma.value = o.chroma;
347
+ u.uTint.value.set(o.tint).convertLinearToSRGB();
348
+ u.uTintAmt.value = o.tintAmount;
349
+ u.uMerge.value = o.merge * dpr;
350
+ renderer.render(scene, camera);
351
+ if (visible && busy) raf = requestAnimationFrame(frame);
352
+ else last = 0;
353
+ };
354
+ function start() {
355
+ if (!raf && visible) raf = requestAnimationFrame(frame);
356
+ }
357
+
358
+ const local = (e) => {
359
+ const r = el.getBoundingClientRect();
360
+ return [e.clientX - r.left, e.clientY - r.top];
361
+ };
362
+ const onDown = (e) => {
363
+ if (!live.current.draggable) return;
364
+ const node = e.target.closest("[data-glass]");
365
+ if (!node) return;
366
+ e.preventDefault();
367
+ const i = Number(node.dataset.glass);
368
+ const [x, y] = local(e);
369
+ drag = { i, dx: st[i].tx - x, dy: st[i].ty - y };
370
+ el.setPointerCapture(e.pointerId);
371
+ start();
372
+ };
373
+ const onMove = (e) => {
374
+ const [x, y] = local(e);
375
+ // The light comes from where the pointer is, relative to the centre.
376
+ const lx = x - view.w / 2;
377
+ const ly = view.h / 2 - y;
378
+ const len = Math.hypot(lx, ly) || 1;
379
+ light.x = lx / len;
380
+ light.y = ly / len;
381
+ light.on = true;
382
+ start();
383
+ if (!drag) return;
384
+ const p = st[drag.i];
385
+ p.tx = Math.max(0, Math.min(view.w, x + drag.dx));
386
+ p.ty = Math.max(0, Math.min(view.h, y + drag.dy));
387
+ p.moved = true;
388
+ start();
389
+ };
390
+ const onUp = () => {
391
+ drag = null;
392
+ start();
393
+ };
394
+ el.addEventListener("pointerdown", onDown);
395
+ el.addEventListener("pointermove", onMove);
396
+ el.addEventListener("pointerup", onUp);
397
+ el.addEventListener("pointercancel", onUp);
398
+ const ro = new ResizeObserver(fit);
399
+ ro.observe(el);
400
+ const io = new IntersectionObserver(([entry]) => {
401
+ visible = entry.isIntersecting;
402
+ if (visible) start();
403
+ });
404
+ io.observe(el);
405
+ fit();
406
+
407
+ return () => {
408
+ cancelAnimationFrame(raf);
409
+ ro.disconnect();
410
+ io.disconnect();
411
+ el.removeEventListener("pointerdown", onDown);
412
+ el.removeEventListener("pointermove", onMove);
413
+ el.removeEventListener("pointerup", onUp);
414
+ el.removeEventListener("pointercancel", onUp);
415
+ u.uTex.value?.dispose();
416
+ geometry.dispose();
417
+ material.dispose();
418
+ renderer.dispose();
419
+ renderer.forceContextLoss();
420
+ renderer.domElement.remove();
421
+ };
422
+ // Rebuild when the panels or the wallpaper change; the look is live.
423
+ // eslint-disable-next-line react-hooks/exhaustive-deps
424
+ }, [image, list.length]);
425
+
426
+ return (
427
+ <div ref={wrap} className={`relative w-full select-none overflow-hidden bg-[#0f1220] ${draggable ? "touch-none" : ""} ${className}`} style={{ height }}>
428
+ {children && <div className="pointer-events-none absolute inset-0 z-[5] [&_a]:pointer-events-auto [&_button]:pointer-events-auto">{children}</div>}
429
+ {list.map((it, i) => (
430
+ <div
431
+ key={it.id ?? i}
432
+ ref={(e) => (panels.current[i] = e)}
433
+ data-glass={i}
434
+ className={`absolute left-0 top-0 z-10 overflow-hidden [text-shadow:0_1px_2px_rgba(0,0,0,0.25)] ${draggable ? "cursor-grab active:cursor-grabbing" : ""}`}
435
+ style={{ borderRadius: it.radius }}
436
+ >
437
+ {it.content}
438
+ </div>
439
+ ))}
440
+ </div>
441
+ );
442
+ };
443
+
444
+ export default LiquidGlass;
@@ -5,8 +5,8 @@ import React, { Children, useLayoutEffect, useRef } from "react";
5
5
  * card that gets covered shrinks a little, darkens and (optionally) blurs or
6
6
  * leans back, so the pile reads as depth. It rides on native scrolling
7
7
  * (position: sticky + one rAF update per scroll), so it stays smooth with
8
- * mouse, trackpad, touch and keyboard. Scrolls inside its own box, or with
9
- * the page (useWindowScroll).
8
+ * mouse, trackpad, touch and keyboard. By default it stacks with the page
9
+ * scroll; set useWindowScroll={false} to stack inside its own scroll box.
10
10
  */
11
11
 
12
12
  export const ScrollStackItem = ({ children, className = "", style }) => (
@@ -16,7 +16,7 @@ export const ScrollStackItem = ({ children, className = "", style }) => (
16
16
  </div>
17
17
  );
18
18
 
19
- const ScrollStack = ({ children, gap = 90, offset = 22, top = 64, scaleStep = 0.05, minScale = 0.82, dim = 0.22, blur = 0, tilt = 0, endSpace = 180, useWindowScroll = false, height = "100%", className = "" }) => {
19
+ const ScrollStack = ({ children, gap = 90, offset = 22, top = 64, scaleStep = 0.05, minScale = 0.82, dim = 0.22, blur = 0, tilt = 0, endSpace = 180, useWindowScroll = true, height = "100%", className = "" }) => {
20
20
  const root = useRef(null);
21
21
  const slots = useRef([]);
22
22
  const items = Children.toArray(children);
@@ -23,6 +23,7 @@ precision highp float;
23
23
  uniform vec2 uRes; uniform float uTime; uniform float uRise; uniform vec2 uMouse;
24
24
  uniform vec3 uSun; uniform vec3 uSkyTop; uniform vec3 uSkyLow; uniform vec3 uMount;
25
25
  uniform float uRays; uniform float uStars; uniform float uHaze; uniform float uSunSize; uniform float uSunHeight;
26
+ uniform float uTrees; uniform float uBirds;
26
27
 
27
28
  float h1(float x){ return fract(sin(x * 127.1) * 43758.5453); }
28
29
  float h2(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
@@ -47,6 +48,23 @@ float range(float x, float c, float w, float peak, float base, float amp, float
47
48
  return base + s * peak + (crag(x * freq + seed) - 0.45) * amp * (0.35 + s);
48
49
  }
49
50
 
51
+ // A pine silhouette: three stacked tiers, each wider at its base, on a short trunk.
52
+ // dx / dy are measured from the foot of the tree, h is its height (all in screen units).
53
+ float pine(float dx, float dy, float h){
54
+ if (h <= 0.0) return 0.0;
55
+ float t = dy / h;
56
+ if (t < 0.0 || t > 1.0) return 0.0;
57
+ float aa = 0.0015;
58
+ float trunk = smoothstep(h * 0.035 + aa, h * 0.035 - aa, abs(dx)) * step(t, 0.14);
59
+ float tiers = 3.0;
60
+ float k = floor(min((t - 0.1) / 0.9, 0.999) * tiers);
61
+ float local = fract(min((t - 0.1) / 0.9, 0.999) * tiers);
62
+ float base = 0.36 * h * (1.0 - k / tiers * 0.62);
63
+ float w = base * (1.0 - local * 0.78);
64
+ float crown = smoothstep(w + aa, w - aa, abs(dx)) * step(0.1, t);
65
+ return max(trunk, crown);
66
+ }
67
+
50
68
  void main(){
51
69
  vec2 uv = gl_FragCoord.xy / uRes;
52
70
  float asp = uRes.x / uRes.y;
@@ -114,6 +132,46 @@ void main(){
114
132
  cn += uSun * exp(-max(hn - y, 0.0) * 120.0) * toSun * 0.9;
115
133
  col = mix(col, cn, mn);
116
134
 
135
+ // Pines along the near ridge, thinning out toward the valley so the sun stays clear.
136
+ // Sizes are in screen-height units so a tree keeps its shape on any screen.
137
+ float unit = 0.5 * clamp(asp, 0.7, 2.2); // one xn unit in height units
138
+ float tx = (xn + mx * 0.05) * 26.0;
139
+ float tcell = floor(tx);
140
+ float cellW = unit / 26.0;
141
+ float jitter = (h1(tcell + 1.9) - 0.5) * 0.3;
142
+ float dx = (fract(tx) - 0.5 - jitter) * cellW;
143
+ float pres = step(0.3, h1(tcell + 3.1)) * smoothstep(0.1, 0.42, abs(xn));
144
+ float ht = min(0.022 + 0.04 * h1(tcell + 7.7), cellW * 1.25) * pres * uTrees;
145
+ float trees = pine(dx, y - hn + 0.004, ht);
146
+ col = mix(col, uMount * 0.6 + uSun * toSun * 0.05, trees * (1.0 - mn));
147
+
148
+ // A few big pines framing the bottom corners.
149
+ float tx2 = (xn + mx * 0.08) * 6.0;
150
+ float cell2 = floor(tx2);
151
+ float cellW2 = unit / 6.0;
152
+ float dx2 = (fract(tx2) - 0.5) * cellW2;
153
+ float ht2 = min(0.16 + 0.1 * h1(cell2 + 1.3), cellW2 * 1.3) * step(0.35, h1(cell2 + 4.4)) * smoothstep(0.62, 0.78, abs(xn)) * uTrees;
154
+ col = mix(col, uMount * 0.38, pine(dx2, y + 0.015, ht2));
155
+
156
+ // A small flock crossing the sky, wings flapping out of step.
157
+ if (uBirds > 0.0) {
158
+ float lap = fract(uTime * 0.03 + 0.38);
159
+ vec2 flock = vec2(mix(-asp * 0.5 - 0.25, asp * 0.5 + 0.25, lap), 0.66 + 0.035 * sin(uTime * 0.31));
160
+ for (int i = 0; i < 7; i++) {
161
+ float fi = float(i);
162
+ if (fi >= uBirds * 7.0) break;
163
+ float side = mod(fi, 2.0) * 2.0 - 1.0;
164
+ float rank = floor((fi + 1.0) / 2.0);
165
+ vec2 bpos = flock + vec2(-rank * 0.045, -rank * 0.022 * side) + vec2(0.006 * sin(uTime * 1.3 + fi), 0.008 * sin(uTime * 0.9 + fi * 1.7));
166
+ float size = 0.016 * (1.0 - rank * 0.08);
167
+ vec2 r = q - bpos;
168
+ float flap = 0.15 + 0.75 * (0.5 + 0.5 * sin(uTime * 9.0 + fi * 1.9));
169
+ float dist = abs(r.y - abs(r.x) * flap + size * 0.15) - 0.0006;
170
+ float bird = smoothstep(0.0024, 0.0008, dist) * step(abs(r.x), size);
171
+ col = mix(col, uMount * 0.45, bird * smoothstep(0.2, 0.5, rise));
172
+ }
173
+ }
174
+
117
175
  // Low fog over everything and light bleeding over the ridges.
118
176
  col += haze * exp(-y * 7.0) * 0.12 * uHaze * rise;
119
177
  col += uSun * exp(-d * 3.0) * 0.18 * rise;
@@ -132,7 +190,9 @@ const SunriseBackground = ({
132
190
  mountainColor,
133
191
  duration = 6,
134
192
  autoRise = true,
135
- sunHeight = 0.36,
193
+ sunHeight = 0.28,
194
+ trees = true,
195
+ birds = 1,
136
196
  sunSize = 0.075,
137
197
  rays = 1,
138
198
  stars = 1,
@@ -158,6 +218,8 @@ const SunriseBackground = ({
158
218
  rays,
159
219
  stars,
160
220
  haze,
221
+ trees,
222
+ birds,
161
223
  speed,
162
224
  interactive,
163
225
  };
@@ -189,7 +251,9 @@ const SunriseBackground = ({
189
251
  uStars: { value: 1 },
190
252
  uHaze: { value: 1 },
191
253
  uSunSize: { value: 0.075 },
192
- uSunHeight: { value: 0.3 },
254
+ uSunHeight: { value: 0.28 },
255
+ uTrees: { value: 1 },
256
+ uBirds: { value: 1 },
193
257
  };
194
258
  const material = new ShaderMaterial({ vertexShader: VERT, fragmentShader: FRAG, uniforms: u, depthTest: false, depthWrite: false });
195
259
  const geometry = new PlaneGeometry(2, 2);
@@ -229,6 +293,8 @@ const SunriseBackground = ({
229
293
  u.uRays.value = o.rays;
230
294
  u.uStars.value = o.stars;
231
295
  u.uHaze.value = o.haze;
296
+ u.uTrees.value = o.trees ? 1 : 0;
297
+ u.uBirds.value = reduced ? 0 : Math.max(0, Math.min(1, o.birds));
232
298
  u.uSunSize.value = o.sunSize;
233
299
  u.uSunHeight.value = o.sunHeight;
234
300
  renderer.render(scene, camera);
package/dist/cli/index.js CHANGED
@@ -139,6 +139,7 @@ const componentMap = {
139
139
  "command-palette": "CommandPalette",
140
140
  "crossed-marquee": "CrossedMarquee",
141
141
  "theme-switcher": "ThemeSwitcher",
142
+ "liquid-glass": "LiquidGlass",
142
143
  "water-drop-reveal": "MaskView",
143
144
  };
144
145
  // Packages only some components need, installed on top of the shared ones.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "apex-ui-kit",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "license": "MIT",
5
- "description": "115 free, animated UI components for React, styled with Tailwind CSS. Add any of them with one CLI command.",
5
+ "description": "116 free, animated UI components for React, styled with Tailwind CSS. Add any of them with one CLI command.",
6
6
  "homepage": "https://apex-ui.in",
7
7
  "repository": {
8
8
  "type": "git",