fancy-ui-svelte 0.7.0 → 0.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 +107 -81
- package/dist/fancy-ui/animated-testimonials/README.md +44 -0
- package/dist/fancy-ui/animated-tooltip/README.md +40 -0
- package/dist/fancy-ui/apple-card-carousel/README.md +46 -0
- package/dist/fancy-ui/bento-grid/BentoGrid.test.d.ts +1 -0
- package/dist/fancy-ui/bento-grid/BentoGrid.test.js +109 -0
- package/dist/fancy-ui/border-beam/README.md +37 -0
- package/dist/fancy-ui/card-3d/CardContainer.test.d.ts +1 -0
- package/dist/fancy-ui/card-3d/CardContainer.test.js +118 -0
- package/dist/fancy-ui/card-spotlight/CardSpotlight.test.d.ts +1 -0
- package/dist/fancy-ui/card-spotlight/CardSpotlight.test.js +58 -0
- package/dist/fancy-ui/displacement-text/README.md +39 -0
- package/dist/fancy-ui/dock/README.md +1 -1
- package/dist/fancy-ui/editorial-engine/EditorialEngine.test.d.ts +1 -0
- package/dist/fancy-ui/editorial-engine/EditorialEngine.test.js +54 -0
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte +1250 -1108
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte.d.ts +11 -5
- package/dist/fancy-ui/fluid-cursor/FluidCursor.test.js +48 -0
- package/dist/fancy-ui/fluid-cursor/README.md +57 -6
- package/dist/fancy-ui/fluid-cursor/fluid-shared.d.ts +72 -0
- package/dist/fancy-ui/fluid-cursor/fluid-shared.js +125 -0
- package/dist/fancy-ui/fluid-cursor/fluid-shared.test.d.ts +1 -0
- package/dist/fancy-ui/fluid-cursor/fluid-shared.test.js +39 -0
- package/dist/fancy-ui/fluid-cursor/index.d.ts +1 -0
- package/dist/fancy-ui/fluid-cursor/webgpu-engine.d.ts +36 -0
- package/dist/fancy-ui/fluid-cursor/webgpu-engine.js +926 -0
- package/dist/fancy-ui/frosted-glass/FrostedGlass.svelte +5 -3
- package/dist/fancy-ui/frosted-glass/README.md +4 -2
- package/dist/fancy-ui/index.d.ts +1 -0
- package/dist/fancy-ui/index.js +1 -0
- package/dist/fancy-ui/line-hover-link/README.md +37 -0
- package/dist/fancy-ui/line-reveal/LineReveal.test.d.ts +1 -0
- package/dist/fancy-ui/line-reveal/LineReveal.test.js +77 -0
- package/dist/fancy-ui/liquid-glass/LiquidGlass.svelte +19 -2
- package/dist/fancy-ui/liquid-glass/LiquidGlass.svelte.d.ts +4 -0
- package/dist/fancy-ui/liquid-glass/LiquidGlass.test.js +14 -0
- package/dist/fancy-ui/liquid-glass/README.md +7 -1
- package/dist/fancy-ui/liquid-text/LiquidText.svelte +1151 -0
- package/dist/fancy-ui/liquid-text/LiquidText.svelte.d.ts +37 -0
- package/dist/fancy-ui/liquid-text/LiquidText.test.d.ts +1 -0
- package/dist/fancy-ui/liquid-text/LiquidText.test.js +219 -0
- package/dist/fancy-ui/liquid-text/README.md +139 -0
- package/dist/fancy-ui/liquid-text/index.d.ts +2 -0
- package/dist/fancy-ui/liquid-text/index.js +1 -0
- package/dist/fancy-ui/marquee/README.md +57 -0
- package/dist/fancy-ui/matrix-rain/MatrixRain.test.d.ts +1 -0
- package/dist/fancy-ui/matrix-rain/MatrixRain.test.js +30 -0
- package/dist/fancy-ui/matrix-rain/README.md +42 -0
- package/dist/fancy-ui/noise-reveal/README.md +52 -0
- package/dist/fancy-ui/rainbow-button/README.md +36 -0
- package/dist/fancy-ui/registry.js +144 -20
- package/dist/fancy-ui/ripple-button/README.md +36 -0
- package/dist/fancy-ui/smooth-cursor/SmoothCursor.test.d.ts +1 -0
- package/dist/fancy-ui/smooth-cursor/SmoothCursor.test.js +45 -0
- package/dist/fancy-ui/terminal-text/README.md +48 -0
- package/dist/fancy-ui/terminal-text/TerminalText.test.d.ts +1 -0
- package/dist/fancy-ui/terminal-text/TerminalText.test.js +88 -0
- package/package.json +18 -4
|
@@ -68,7 +68,7 @@ export const registry = {
|
|
|
68
68
|
"apple-card-carousel": {
|
|
69
69
|
name: "AppleCardCarousel",
|
|
70
70
|
slug: "apple-card-carousel",
|
|
71
|
-
description: "Horizontal card carousel
|
|
71
|
+
description: "Horizontal card carousel where the focused card expands into a spring-animated full-screen view",
|
|
72
72
|
category: "cards",
|
|
73
73
|
status: "done",
|
|
74
74
|
credits: [
|
|
@@ -246,7 +246,7 @@ export const registry = {
|
|
|
246
246
|
"blur-reveal": {
|
|
247
247
|
name: "BlurReveal",
|
|
248
248
|
slug: "blur-reveal",
|
|
249
|
-
description: "Scroll-triggered
|
|
249
|
+
description: "Scroll-triggered reveal that unblurs and slides each direct child into place as the container enters the viewport, staggering up to 10 children via CSS nth-child delays and respecting reduced motion",
|
|
250
250
|
category: "text",
|
|
251
251
|
status: "done",
|
|
252
252
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/blur-fade" }],
|
|
@@ -284,7 +284,7 @@ export const registry = {
|
|
|
284
284
|
"border-beam": {
|
|
285
285
|
name: "BorderBeam",
|
|
286
286
|
slug: "border-beam",
|
|
287
|
-
description: "
|
|
287
|
+
description: "Gradient beam that races around a container's border using a CSS offset-path animation, masked so only the border ring is painted, with configurable size, speed, anchor position, and gradient colors",
|
|
288
288
|
category: "effects",
|
|
289
289
|
status: "done",
|
|
290
290
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/border-beam" }],
|
|
@@ -405,7 +405,7 @@ export const registry = {
|
|
|
405
405
|
"image-trail-cursor": {
|
|
406
406
|
name: "ImageTrailCursor",
|
|
407
407
|
slug: "image-trail-cursor",
|
|
408
|
-
description: "
|
|
408
|
+
description: "Trail of images that spawn and animate along the cursor's path, powered by GSAP timelines, with 8 selectable variants spanning simple fades, momentum drift, rotation flings, and 3D perspective tilt",
|
|
409
409
|
category: "effects",
|
|
410
410
|
status: "done",
|
|
411
411
|
tags: ["cursor", "animation", "images", "trail", "interactive"],
|
|
@@ -643,7 +643,7 @@ export const registry = {
|
|
|
643
643
|
"ripple-button": {
|
|
644
644
|
name: "RippleButton",
|
|
645
645
|
slug: "ripple-button",
|
|
646
|
-
description: "Button
|
|
646
|
+
description: "Button that spawns an expanding, fading ripple circle centered on the click point, with configurable color and duration",
|
|
647
647
|
category: "buttons",
|
|
648
648
|
status: "done",
|
|
649
649
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/ripple-button" }],
|
|
@@ -734,7 +734,7 @@ export const registry = {
|
|
|
734
734
|
"bg-stars": {
|
|
735
735
|
name: "StarsBackground",
|
|
736
736
|
slug: "bg-stars",
|
|
737
|
-
description: "
|
|
737
|
+
description: "Three-layer starfield background that scrolls continuously while parallax-shifting in response to cursor movement via spring physics, with stars rendered as box-shadow dots across near, mid, and far depth layers",
|
|
738
738
|
category: "backgrounds",
|
|
739
739
|
status: "done",
|
|
740
740
|
credits: [
|
|
@@ -773,11 +773,11 @@ export const registry = {
|
|
|
773
773
|
dock: {
|
|
774
774
|
name: "Dock",
|
|
775
775
|
slug: "dock",
|
|
776
|
-
description: "
|
|
776
|
+
description: "Icon dock where each item magnifies smoothly as the cursor approaches, sharing pointer position via Svelte context so every DockIcon scales by proximity within a configurable magnification and distance range",
|
|
777
777
|
category: "navigation",
|
|
778
778
|
status: "done",
|
|
779
779
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/dock" }],
|
|
780
|
-
tags: ["dock", "navigation", "
|
|
780
|
+
tags: ["dock", "navigation", "launcher", "magnification", "hover"],
|
|
781
781
|
props: [
|
|
782
782
|
{
|
|
783
783
|
name: "magnification",
|
|
@@ -809,7 +809,7 @@ export const registry = {
|
|
|
809
809
|
"fluid-cursor": {
|
|
810
810
|
name: "FluidCursor",
|
|
811
811
|
slug: "fluid-cursor",
|
|
812
|
-
description: "
|
|
812
|
+
description: "Full Navier-Stokes fluid simulation that swirls color in response to cursor and touch movement, running on WebGL2 with a WebGL1 fallback and an optional WebGPU HDR path for wide-gamut, brighter-than-white glow",
|
|
813
813
|
category: "effects",
|
|
814
814
|
status: "done",
|
|
815
815
|
credits: [
|
|
@@ -935,12 +935,24 @@ export const registry = {
|
|
|
935
935
|
default: "true",
|
|
936
936
|
description: "Confine simulation to parent container",
|
|
937
937
|
},
|
|
938
|
+
{
|
|
939
|
+
name: "hdr",
|
|
940
|
+
type: "boolean",
|
|
941
|
+
default: "false",
|
|
942
|
+
description: "Render via the WebGPU HDR engine — wide gamut + brighter-than-white glow on HDR displays. Needs Chrome/Edge 129+ or Safari 26+; falls back to wide-gamut WebGL (Chrome 104+, Safari 16.4+, Firefox 132+), then to standard rendering",
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
name: "hdrBoost",
|
|
946
|
+
type: "number",
|
|
947
|
+
default: "1.5",
|
|
948
|
+
description: "Display exposure multiplier in HDR mode, clamped to [1, 4]",
|
|
949
|
+
},
|
|
938
950
|
],
|
|
939
951
|
},
|
|
940
952
|
"glow-border": {
|
|
941
953
|
name: "GlowBorder",
|
|
942
954
|
slug: "glow-border",
|
|
943
|
-
description: "
|
|
955
|
+
description: "Glowing ring that animates around a container's edge by sweeping a radial gradient's position, masked with CSS mask-composite so only the border ring is painted, supporting single or multi-color gradients",
|
|
944
956
|
category: "effects",
|
|
945
957
|
status: "done",
|
|
946
958
|
credits: [
|
|
@@ -1088,7 +1100,7 @@ export const registry = {
|
|
|
1088
1100
|
"flickering-grid": {
|
|
1089
1101
|
name: "FlickeringGrid",
|
|
1090
1102
|
slug: "flickering-grid",
|
|
1091
|
-
description: "Canvas
|
|
1103
|
+
description: "Canvas grid of squares that flicker in and out of opacity at random each frame, pausing automatically via IntersectionObserver when scrolled off-screen and resizing to fit its container via ResizeObserver",
|
|
1092
1104
|
category: "backgrounds",
|
|
1093
1105
|
status: "done",
|
|
1094
1106
|
credits: [
|
|
@@ -1171,7 +1183,7 @@ export const registry = {
|
|
|
1171
1183
|
"colourful-text": {
|
|
1172
1184
|
name: "ColourfulText",
|
|
1173
1185
|
slug: "colourful-text",
|
|
1174
|
-
description: "Per-character
|
|
1186
|
+
description: "Per-character text animation where each glyph transitions to a new color from a shuffled palette, staggered across characters and reshuffling automatically every 5 seconds using pure CSS transitions",
|
|
1175
1187
|
category: "text",
|
|
1176
1188
|
status: "done",
|
|
1177
1189
|
credits: [
|
|
@@ -1226,7 +1238,7 @@ export const registry = {
|
|
|
1226
1238
|
"hyper-text": {
|
|
1227
1239
|
name: "HyperText",
|
|
1228
1240
|
slug: "hyper-text",
|
|
1229
|
-
description: "
|
|
1241
|
+
description: "Text that scrambles into random uppercase letters and resolves character-by-character back to the original on hover, with a staggered per-letter reveal and an optional animate-on-load mode",
|
|
1230
1242
|
category: "text",
|
|
1231
1243
|
status: "done",
|
|
1232
1244
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/hyper-text" }],
|
|
@@ -1309,7 +1321,7 @@ export const registry = {
|
|
|
1309
1321
|
"sparkles-text": {
|
|
1310
1322
|
name: "SparklesText",
|
|
1311
1323
|
slug: "sparkles-text",
|
|
1312
|
-
description: "Text with
|
|
1324
|
+
description: "Text overlaid with small SVG star sparkles that scale, rotate, and fade in a continuous loop, each one regenerating at a random position once its lifespan expires, cycling between two configurable colors",
|
|
1313
1325
|
category: "text",
|
|
1314
1326
|
status: "done",
|
|
1315
1327
|
credits: [{ source: "Aceternity UI", url: "https://ui.aceternity.com/components/sparkles" }],
|
|
@@ -1333,7 +1345,7 @@ export const registry = {
|
|
|
1333
1345
|
"box-reveal": {
|
|
1334
1346
|
name: "BoxReveal",
|
|
1335
1347
|
slug: "box-reveal",
|
|
1336
|
-
description: "
|
|
1348
|
+
description: "Reveal animation where a solid color box slides left to right across content on viewport entry, unveiling it as it fades up into place underneath, triggered once via IntersectionObserver",
|
|
1337
1349
|
category: "text",
|
|
1338
1350
|
status: "done",
|
|
1339
1351
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/box-reveal" }],
|
|
@@ -1383,7 +1395,7 @@ export const registry = {
|
|
|
1383
1395
|
"card-spotlight": {
|
|
1384
1396
|
name: "CardSpotlight",
|
|
1385
1397
|
slug: "card-spotlight",
|
|
1386
|
-
description: "Card
|
|
1398
|
+
description: "Card whose radial-gradient spotlight tracks the cursor as it moves inside, fading in on hover and parking itself off-canvas when the pointer leaves, with configurable gradient size, color, and opacity",
|
|
1387
1399
|
category: "cards",
|
|
1388
1400
|
status: "done",
|
|
1389
1401
|
credits: [
|
|
@@ -1779,6 +1791,18 @@ export const registry = {
|
|
|
1779
1791
|
},
|
|
1780
1792
|
{ name: "scale", type: "number", default: "-180", description: "Displacement scale" },
|
|
1781
1793
|
{ name: "frost", type: "number", default: "0.05", description: "Frosted glass opacity" },
|
|
1794
|
+
{
|
|
1795
|
+
name: "fallbackBlur",
|
|
1796
|
+
type: "number",
|
|
1797
|
+
default: "20",
|
|
1798
|
+
description: "Backdrop blur in pixels for the Safari fallback",
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
name: "fallbackSaturation",
|
|
1802
|
+
type: "number",
|
|
1803
|
+
default: "180",
|
|
1804
|
+
description: "Backdrop saturation percentage for the Safari fallback",
|
|
1805
|
+
},
|
|
1782
1806
|
{
|
|
1783
1807
|
name: "containerClass",
|
|
1784
1808
|
type: "string",
|
|
@@ -1943,7 +1967,7 @@ export const registry = {
|
|
|
1943
1967
|
ripple: {
|
|
1944
1968
|
name: "Ripple",
|
|
1945
1969
|
slug: "ripple",
|
|
1946
|
-
description: "
|
|
1970
|
+
description: "Set of concentric circles that pulse in a staggered wave, each ring larger, more transparent, and more delayed than the last, with the outermost ring rendered dashed to suggest a fading signal",
|
|
1947
1971
|
category: "effects",
|
|
1948
1972
|
status: "done",
|
|
1949
1973
|
credits: [{ source: "Magic UI", url: "https://magicui.design/docs/components/ripple" }],
|
|
@@ -2101,7 +2125,7 @@ export const registry = {
|
|
|
2101
2125
|
"matrix-rain": {
|
|
2102
2126
|
name: "MatrixRain",
|
|
2103
2127
|
slug: "matrix-rain",
|
|
2104
|
-
description: "Canvas-based
|
|
2128
|
+
description: "Canvas-based falling glyph rain with configurable color, speed, and density",
|
|
2105
2129
|
category: "backgrounds",
|
|
2106
2130
|
status: "done",
|
|
2107
2131
|
tags: ["background", "matrix", "canvas", "animation", "glyphs", "cyberpunk"],
|
|
@@ -2110,7 +2134,7 @@ export const registry = {
|
|
|
2110
2134
|
name: "color",
|
|
2111
2135
|
type: "string",
|
|
2112
2136
|
default: '"#00ff41"',
|
|
2113
|
-
description: "Glyph color (
|
|
2137
|
+
description: "Glyph color (classic terminal green)",
|
|
2114
2138
|
},
|
|
2115
2139
|
{
|
|
2116
2140
|
name: "speed",
|
|
@@ -2182,7 +2206,7 @@ export const registry = {
|
|
|
2182
2206
|
"noise-reveal": {
|
|
2183
2207
|
name: "NoiseReveal",
|
|
2184
2208
|
slug: "noise-reveal",
|
|
2185
|
-
description: "WebGL image reveal with a Perlin-noise dissolve mask, contracting radial gradient, and wave displacement
|
|
2209
|
+
description: "WebGL image reveal with a Perlin-noise dissolve mask, contracting radial gradient, and wave displacement",
|
|
2186
2210
|
category: "media",
|
|
2187
2211
|
status: "done",
|
|
2188
2212
|
credits: [
|
|
@@ -2225,6 +2249,106 @@ export const registry = {
|
|
|
2225
2249
|
},
|
|
2226
2250
|
],
|
|
2227
2251
|
},
|
|
2252
|
+
"liquid-text": {
|
|
2253
|
+
name: "LiquidText",
|
|
2254
|
+
slug: "liquid-text",
|
|
2255
|
+
description: "Big text that liquefies along the cursor's path via a raw-WebGL fluid solver, with chromatic-aberration fringing that relaxes back over time",
|
|
2256
|
+
category: "text",
|
|
2257
|
+
status: "done",
|
|
2258
|
+
credits: [
|
|
2259
|
+
{ source: "Inspired by ryanritzenthaler.com (behavioral study)" },
|
|
2260
|
+
{ source: "Fluid solver after Pavel Dobryakov's WebGL fluid simulation" },
|
|
2261
|
+
],
|
|
2262
|
+
tags: ["text", "webgl", "fluid", "displacement", "cursor", "shader", "chromatic-aberration"],
|
|
2263
|
+
props: [
|
|
2264
|
+
{ name: "text", type: "string", default: '"Liquid"', description: "Text to display" },
|
|
2265
|
+
{
|
|
2266
|
+
name: "font",
|
|
2267
|
+
type: "string",
|
|
2268
|
+
default: '""',
|
|
2269
|
+
description: "Font family; empty string resolves via getComputedStyle(host).fontFamily",
|
|
2270
|
+
},
|
|
2271
|
+
{
|
|
2272
|
+
name: "fontSize",
|
|
2273
|
+
type: "number",
|
|
2274
|
+
default: "0",
|
|
2275
|
+
description: "Font size in pixels; 0 auto-fits to the container width",
|
|
2276
|
+
},
|
|
2277
|
+
{
|
|
2278
|
+
name: "fontWeight",
|
|
2279
|
+
type: "number | string",
|
|
2280
|
+
default: "700",
|
|
2281
|
+
description: "Font weight",
|
|
2282
|
+
},
|
|
2283
|
+
{
|
|
2284
|
+
name: "lightColor",
|
|
2285
|
+
type: "string",
|
|
2286
|
+
default: '"#000000"',
|
|
2287
|
+
description: "Text color in light mode",
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
name: "darkColor",
|
|
2291
|
+
type: "string",
|
|
2292
|
+
default: '"#ffffff"',
|
|
2293
|
+
description: "Text color in dark mode",
|
|
2294
|
+
},
|
|
2295
|
+
{ name: "class", type: "string", description: "Additional CSS classes" },
|
|
2296
|
+
{
|
|
2297
|
+
name: "strength",
|
|
2298
|
+
type: "number",
|
|
2299
|
+
default: "0.5",
|
|
2300
|
+
description: "Geometric UV warp gain",
|
|
2301
|
+
},
|
|
2302
|
+
{
|
|
2303
|
+
name: "radius",
|
|
2304
|
+
type: "number",
|
|
2305
|
+
default: "160",
|
|
2306
|
+
description: "Splat radius in screen pixels",
|
|
2307
|
+
},
|
|
2308
|
+
{
|
|
2309
|
+
name: "forceGain",
|
|
2310
|
+
type: "number",
|
|
2311
|
+
default: "17",
|
|
2312
|
+
description: "Mouse-delta to force multiplier",
|
|
2313
|
+
},
|
|
2314
|
+
{
|
|
2315
|
+
name: "dissipation",
|
|
2316
|
+
type: "number",
|
|
2317
|
+
default: "0.98",
|
|
2318
|
+
description: "Per-frame velocity decay factor",
|
|
2319
|
+
},
|
|
2320
|
+
{
|
|
2321
|
+
name: "viscosity",
|
|
2322
|
+
type: "number",
|
|
2323
|
+
default: "4",
|
|
2324
|
+
description: "Jacobi viscous diffusion strength (8 iterations)",
|
|
2325
|
+
},
|
|
2326
|
+
{
|
|
2327
|
+
name: "chromaticRatio",
|
|
2328
|
+
type: "number",
|
|
2329
|
+
default: "0.2",
|
|
2330
|
+
description: "Chromatic offset as a ratio of the warp",
|
|
2331
|
+
},
|
|
2332
|
+
{
|
|
2333
|
+
name: "staticBelow",
|
|
2334
|
+
type: "number",
|
|
2335
|
+
default: "1024",
|
|
2336
|
+
description: "innerWidth at/below which the component renders as static DOM text (also used at mount)",
|
|
2337
|
+
},
|
|
2338
|
+
{
|
|
2339
|
+
name: "interactive",
|
|
2340
|
+
type: "boolean",
|
|
2341
|
+
default: "true",
|
|
2342
|
+
description: "Whether pointer movement drives the fluid simulation",
|
|
2343
|
+
},
|
|
2344
|
+
{
|
|
2345
|
+
name: "pauseWhenHidden",
|
|
2346
|
+
type: "boolean",
|
|
2347
|
+
default: "true",
|
|
2348
|
+
description: "Pause the render loop while the document is hidden",
|
|
2349
|
+
},
|
|
2350
|
+
],
|
|
2351
|
+
},
|
|
2228
2352
|
};
|
|
2229
2353
|
// =============================================================================
|
|
2230
2354
|
// Registry Helpers
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# RippleButton
|
|
2
|
+
|
|
3
|
+
A button that spawns a Material-style expanding ripple circle from the exact click position on every click, layered behind the button's content.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { RippleButton } from "fancy-ui-svelte";
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<RippleButton onclick={() => console.log("clicked")}>Click me</RippleButton>
|
|
13
|
+
|
|
14
|
+
<RippleButton rippleColor="#f472b6" duration={800} class="bg-primary text-primary-foreground">
|
|
15
|
+
Custom ripple
|
|
16
|
+
</RippleButton>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
| ------------- | --------- | ----------- | ---------------------------------- |
|
|
23
|
+
| `rippleColor` | `string` | `"#ADD8E6"` | Color of the ripple effect |
|
|
24
|
+
| `duration` | `number` | `600` | Animation duration in milliseconds |
|
|
25
|
+
| `class` | `string` | — | Additional CSS classes |
|
|
26
|
+
| `children` | `Snippet` | — | Button content |
|
|
27
|
+
|
|
28
|
+
`RippleButtonProps` also extends `HTMLButtonAttributes` (minus `class`), and any attributes not explicitly listed above (e.g. `type`, `aria-*`, `data-*`) are spread onto the rendered `<button>` via `...restProps`.
|
|
29
|
+
|
|
30
|
+
## Implementation notes
|
|
31
|
+
|
|
32
|
+
- `onclick` is intercepted, not overwritten: the component's own `handleClick` always creates the ripple first, then calls the caller's `onclick` (if provided) with the original event — passing `onclick` works exactly as it would on a plain `<button>`.
|
|
33
|
+
- Ripple size is `Math.max(width, height)` of the button so a single circle always covers the whole button regardless of click position; position is computed from `event.clientX/Y` relative to the button's `getBoundingClientRect()`, centered on the click point.
|
|
34
|
+
- Each ripple is tracked in a `ripples` array keyed by `Date.now()` and removed via `setTimeout(duration)` after the animation finishes — rapid repeated clicks stack multiple concurrent ripple elements rather than restarting a single one.
|
|
35
|
+
- `duration` is threaded into the CSS animation via the `--ripple-duration` custom property, so the visual animation and the removal timeout always stay in sync even when `duration` changes between renders.
|
|
36
|
+
- No `prefers-reduced-motion` handling — the ripple scale/opacity animation plays on every click regardless of user preference.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { render, cleanup, fireEvent } from "@testing-library/svelte";
|
|
2
|
+
import { createRawSnippet } from "svelte";
|
|
3
|
+
import { afterEach, describe, it, expect } from "vitest";
|
|
4
|
+
import SmoothCursor from "./SmoothCursor.svelte";
|
|
5
|
+
function textSnippet(html) {
|
|
6
|
+
return createRawSnippet(() => ({ render: () => html }));
|
|
7
|
+
}
|
|
8
|
+
describe("SmoothCursor", () => {
|
|
9
|
+
afterEach(cleanup);
|
|
10
|
+
it("renders hidden (opacity-0) before any pointer movement", () => {
|
|
11
|
+
const { container } = render(SmoothCursor);
|
|
12
|
+
const cursor = container.firstElementChild;
|
|
13
|
+
expect(cursor.className).toContain("opacity-0");
|
|
14
|
+
expect(cursor.className).not.toContain("opacity-100");
|
|
15
|
+
});
|
|
16
|
+
it("applies custom class names", () => {
|
|
17
|
+
const { container } = render(SmoothCursor, { props: { class: "my-cursor" } });
|
|
18
|
+
const cursor = container.firstElementChild;
|
|
19
|
+
expect(cursor.className).toContain("my-cursor");
|
|
20
|
+
});
|
|
21
|
+
it("renders the default arrow svg when no cursor snippet is provided", () => {
|
|
22
|
+
const { container } = render(SmoothCursor);
|
|
23
|
+
expect(container.querySelector("svg")).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
it("renders a custom cursor snippet instead of the default arrow", () => {
|
|
26
|
+
const { container } = render(SmoothCursor, {
|
|
27
|
+
props: { cursor: textSnippet("<div data-testid='custom-cursor'></div>") },
|
|
28
|
+
});
|
|
29
|
+
expect(container.querySelector("svg")).toBeFalsy();
|
|
30
|
+
expect(container.querySelector("[data-testid='custom-cursor']")).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
it("hides the native cursor on mount and restores it on unmount", () => {
|
|
33
|
+
const { unmount } = render(SmoothCursor);
|
|
34
|
+
expect(document.body.style.cursor).toBe("none");
|
|
35
|
+
unmount();
|
|
36
|
+
expect(document.body.style.cursor).toBe("");
|
|
37
|
+
});
|
|
38
|
+
it("becomes visible once the pointer moves, and unmount does not throw once the animation loop is running", async () => {
|
|
39
|
+
const { container, unmount } = render(SmoothCursor);
|
|
40
|
+
const cursor = container.firstElementChild;
|
|
41
|
+
await fireEvent.mouseMove(document, { clientX: 120, clientY: 80 });
|
|
42
|
+
expect(cursor.className).toContain("opacity-100");
|
|
43
|
+
expect(() => unmount()).not.toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# TerminalText
|
|
2
|
+
|
|
3
|
+
Streams an array of lines onto the screen character by character, terminal-style, with a blinking block cursor and an optional post-stream "glitch" effect that randomly swaps a visible character for a glyph for a moment.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```svelte
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { TerminalText } from "fancy-ui-svelte";
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div class="rounded-lg border bg-black p-5 text-green-400">
|
|
13
|
+
<TerminalText
|
|
14
|
+
lines={[
|
|
15
|
+
"[INFO] boot sequence complete",
|
|
16
|
+
"[WARN] memory fragmentation at sector 0x4A2F",
|
|
17
|
+
"[OK] all systems nominal",
|
|
18
|
+
]}
|
|
19
|
+
speed={25}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```svelte
|
|
25
|
+
<!-- Glitch effect after streaming finishes, and a completion callback -->
|
|
26
|
+
<TerminalText lines={["Connection established."]} glitch onComplete={() => console.log("done")} />
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
| Prop | Type | Default | Description |
|
|
32
|
+
| ------------ | ------------ | ------- | ---------------------------------------------------------- |
|
|
33
|
+
| `lines` | `string[]` | — | Array of lines to stream character by character (required) |
|
|
34
|
+
| `speed` | `number` | `40` | Delay between each character in ms |
|
|
35
|
+
| `delay` | `number` | `0` | Initial delay before streaming starts, in ms |
|
|
36
|
+
| `cursor` | `boolean` | `true` | Show the blinking cursor |
|
|
37
|
+
| `cursorChar` | `string` | `"█"` | Character used as the cursor |
|
|
38
|
+
| `glitch` | `boolean` | `false` | Enable random character glitching after streaming |
|
|
39
|
+
| `class` | `string` | — | Additional CSS classes |
|
|
40
|
+
| `onComplete` | `() => void` | — | Called once, after the last line finishes streaming |
|
|
41
|
+
|
|
42
|
+
## Implementation notes
|
|
43
|
+
|
|
44
|
+
- Streaming is scheduled with a flat list of `setTimeout` calls computed up front from a running `totalDelay` counter (one timeout per character, plus a `speed * 3` pause between lines), not a single interval — every timeout is tracked and cleared together (`clearAllTimeouts`) so restarting the stream never leaves stray callbacks running.
|
|
45
|
+
- Streaming and glitching are two independent `$effect`s. The streaming effect depends on `lines`, `speed`, and `delay` only (`void`-read to declare the dependency without using the value), so toggling `glitch` mid-stream does not restart the text. The glitch effect depends only on `glitch` and starts/stops its own self-rescheduling loop (`scheduleGlitch`, random 2–4s interval).
|
|
46
|
+
- The glitch loop picks a random visible character from a random non-empty line, swaps in a random glyph from a fixed set, and restores the original character after 100ms — it checks that the same `(lineIdx, charIdx)` is still the active glitch before restoring, so it can't clobber a different glitch that started in between.
|
|
47
|
+
- The blinking cursor is CSS-only (`animation: blink 1s step-end infinite`) and is rendered after whichever line is currently last — while streaming that's the in-progress line, once `done` it moves to its own trailing line.
|
|
48
|
+
- No `prefers-reduced-motion` handling — lines always stream character-by-character and the cursor always blinks, regardless of user preference.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { render, cleanup } from "@testing-library/svelte";
|
|
2
|
+
import { afterEach, describe, it, expect, vi } from "vitest";
|
|
3
|
+
import TerminalText from "./TerminalText.svelte";
|
|
4
|
+
// The template has static whitespace between its {#each} and {#if} blocks
|
|
5
|
+
// that shows up as stray spaces in textContent depending on which branch is
|
|
6
|
+
// active. Stripping all whitespace isolates the actual streamed characters.
|
|
7
|
+
function text(el) {
|
|
8
|
+
return (el.textContent ?? "").replace(/\s+/g, "");
|
|
9
|
+
}
|
|
10
|
+
describe("TerminalText", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
cleanup();
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
it("renders nothing until the first scheduled tick, then streams characters over time and completes", async () => {
|
|
16
|
+
vi.useFakeTimers();
|
|
17
|
+
const onComplete = vi.fn();
|
|
18
|
+
const { container } = render(TerminalText, {
|
|
19
|
+
props: { lines: ["hi"], speed: 10, onComplete },
|
|
20
|
+
});
|
|
21
|
+
const wrapper = container.firstElementChild;
|
|
22
|
+
// The effect only schedules timeouts synchronously; none have fired yet.
|
|
23
|
+
expect(text(wrapper)).toBe("");
|
|
24
|
+
// t=0: empty line placeholder + blinking cursor appear.
|
|
25
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
26
|
+
expect(text(wrapper)).toBe("█");
|
|
27
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
28
|
+
// t=10: first character streamed in.
|
|
29
|
+
await vi.advanceTimersByTimeAsync(9);
|
|
30
|
+
expect(text(wrapper)).toBe("h█");
|
|
31
|
+
// t=20: second character streamed in.
|
|
32
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
33
|
+
expect(text(wrapper)).toBe("hi█");
|
|
34
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
35
|
+
// t=50: stream marked done, onComplete fires exactly once.
|
|
36
|
+
await vi.advanceTimersByTimeAsync(30);
|
|
37
|
+
expect(text(wrapper)).toBe("hi█");
|
|
38
|
+
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
39
|
+
});
|
|
40
|
+
it("renders no cursor glyph anywhere when cursor is false", async () => {
|
|
41
|
+
vi.useFakeTimers();
|
|
42
|
+
const { container } = render(TerminalText, {
|
|
43
|
+
props: { lines: ["ok"], speed: 5, cursor: false },
|
|
44
|
+
});
|
|
45
|
+
await vi.advanceTimersByTimeAsync(25);
|
|
46
|
+
const wrapper = container.firstElementChild;
|
|
47
|
+
expect(text(wrapper)).toBe("ok");
|
|
48
|
+
expect(container.querySelector(".cursor-blink")).toBeFalsy();
|
|
49
|
+
});
|
|
50
|
+
it("streams multiple lines in order, one div per line", async () => {
|
|
51
|
+
vi.useFakeTimers();
|
|
52
|
+
const { container } = render(TerminalText, {
|
|
53
|
+
props: { lines: ["a", "bb"], speed: 10, cursor: false },
|
|
54
|
+
});
|
|
55
|
+
await vi.advanceTimersByTimeAsync(90);
|
|
56
|
+
const wrapper = container.firstElementChild;
|
|
57
|
+
const lineDivs = Array.from(wrapper.children);
|
|
58
|
+
expect(lineDivs).toHaveLength(2);
|
|
59
|
+
expect(lineDivs[0].textContent).toBe("a");
|
|
60
|
+
expect(lineDivs[1].textContent).toBe("bb");
|
|
61
|
+
});
|
|
62
|
+
it("applies custom class names alongside the base classes", () => {
|
|
63
|
+
const { container } = render(TerminalText, {
|
|
64
|
+
props: { lines: ["x"], class: "my-terminal" },
|
|
65
|
+
});
|
|
66
|
+
const wrapper = container.firstElementChild;
|
|
67
|
+
expect(wrapper.className).toContain("my-terminal");
|
|
68
|
+
expect(wrapper.className).toContain("font-mono");
|
|
69
|
+
});
|
|
70
|
+
it("uses a custom cursor character once the stream is done", async () => {
|
|
71
|
+
vi.useFakeTimers();
|
|
72
|
+
const { container } = render(TerminalText, {
|
|
73
|
+
props: { lines: ["a"], speed: 5, cursorChar: "_" },
|
|
74
|
+
});
|
|
75
|
+
// push@0, char@5, done@20 (5 + speed*3=15)
|
|
76
|
+
await vi.advanceTimersByTimeAsync(20);
|
|
77
|
+
const wrapper = container.firstElementChild;
|
|
78
|
+
expect(text(wrapper)).toBe("a_");
|
|
79
|
+
});
|
|
80
|
+
it("does not throw or leave dangling timers when glitch is enabled", async () => {
|
|
81
|
+
vi.useFakeTimers();
|
|
82
|
+
const { unmount } = render(TerminalText, {
|
|
83
|
+
props: { lines: ["glitchy line"], speed: 5, glitch: true },
|
|
84
|
+
});
|
|
85
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
86
|
+
expect(() => unmount()).not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fancy-ui-svelte",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
|
+
"description": "61 animated UI components for Svelte 5 — WebGL, GSAP and CSS motion, styled with Tailwind CSS v4.",
|
|
6
|
+
"author": "Rama Herbin",
|
|
7
|
+
"funding": "https://github.com/sponsors/RamaHerbin",
|
|
5
8
|
"type": "module",
|
|
6
9
|
"license": "MIT",
|
|
7
10
|
"repository": {
|
|
@@ -15,10 +18,18 @@
|
|
|
15
18
|
"keywords": [
|
|
16
19
|
"svelte",
|
|
17
20
|
"svelte5",
|
|
21
|
+
"sveltekit",
|
|
18
22
|
"components",
|
|
23
|
+
"component-library",
|
|
19
24
|
"animations",
|
|
25
|
+
"animated-components",
|
|
26
|
+
"motion",
|
|
20
27
|
"ui",
|
|
21
|
-
"
|
|
28
|
+
"ui-library",
|
|
29
|
+
"tailwind",
|
|
30
|
+
"tailwindcss",
|
|
31
|
+
"webgl",
|
|
32
|
+
"typescript"
|
|
22
33
|
],
|
|
23
34
|
"sideEffects": [
|
|
24
35
|
"**/*.css",
|
|
@@ -60,7 +71,6 @@
|
|
|
60
71
|
},
|
|
61
72
|
"devDependencies": {
|
|
62
73
|
"@changesets/cli": "^2.30.0",
|
|
63
|
-
"@vercel/analytics": "^2.0.1",
|
|
64
74
|
"@internationalized/date": "^3.10.1",
|
|
65
75
|
"@lucide/svelte": "^0.562.0",
|
|
66
76
|
"@playwright/test": "^1.58.2",
|
|
@@ -80,12 +90,13 @@
|
|
|
80
90
|
"@types/canvas-confetti": "^1.9.0",
|
|
81
91
|
"@types/node": "^25.2.3",
|
|
82
92
|
"@types/three": "^0.183.1",
|
|
93
|
+
"@vercel/analytics": "^2.0.1",
|
|
94
|
+
"@webgpu/types": "^0.1.71",
|
|
83
95
|
"arctic": "^3.7.0",
|
|
84
96
|
"bits-ui": "^2.15.4",
|
|
85
97
|
"isomorphic-dompurify": "^3.7.1",
|
|
86
98
|
"jsdom": "^28.0.0",
|
|
87
99
|
"marked": "^17.0.3",
|
|
88
|
-
"mdsvex": "^0.12.7",
|
|
89
100
|
"nanoid": "^5.1.6",
|
|
90
101
|
"prettier": "^3.8.1",
|
|
91
102
|
"prettier-plugin-svelte": "^3.5.0",
|
|
@@ -109,6 +120,9 @@
|
|
|
109
120
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
110
121
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
111
122
|
"check:registry": "node scripts/check-registry.mjs",
|
|
123
|
+
"check:i18n": "node scripts/check-i18n.mjs",
|
|
124
|
+
"build:assets": "node scripts/build-synthwave-assets.mjs",
|
|
125
|
+
"build:og": "node scripts/build-og-images.mjs",
|
|
112
126
|
"test": "vitest run",
|
|
113
127
|
"test:watch": "vitest",
|
|
114
128
|
"test:e2e": "playwright test",
|