Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions public/r/hacker-background.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "hacker-background",
"type": "registry:component",
"dependencies": [
""
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "./src/components/nurui/hacker-background-demo.tsx",
"content": "import HackerBackground from \"./hacker-background\";\n\nexport function HackerBackgroundDemo() {\n return (\n <div className=\"relative z-10 h-[500px] w-full overflow-hidden rounded-lg\">\n <HackerBackground />\n </div>\n );\n}\n",
"type": "registry:component"
},
{
"path": "./src/components/nurui/hacker-background.tsx",
"content": "\"use client\";\nimport React, { useEffect, useRef } from \"react\";\n\ninterface HackerBackgroundProps {\n color?: string;\n fontSize?: number;\n className?: string;\n speed?: number;\n}\n\nconst HackerBackground: React.FC<HackerBackgroundProps> = ({\n color = \"#3ca2fa\",\n fontSize = 15,\n className = \"\",\n speed = 1,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n const resizeCanvas = () => {\n canvas.width = window.innerWidth;\n canvas.height = window.innerHeight;\n };\n\n resizeCanvas();\n window.addEventListener(\"resize\", resizeCanvas);\n\n let animationFrameId: number;\n\n const columns = Math.floor(canvas.width / fontSize);\n const drops: number[] = new Array(columns).fill(1);\n\n const chars =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()_+\";\n\n let lastTime = 0;\n const interval = 33; // ~30 fps\n\n const draw = (currentTime: number) => {\n animationFrameId = requestAnimationFrame(draw);\n\n if (currentTime - lastTime < interval) return;\n lastTime = currentTime;\n\n ctx.fillStyle = \"rgba(0, 0, 0, 0.05)\";\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n ctx.fillStyle = color;\n ctx.font = `${fontSize}px monospace`;\n\n for (let i = 0; i < drops.length; i++) {\n const text = chars[Math.floor(Math.random() * chars.length)];\n ctx.fillText(text, i * fontSize, drops[i] * fontSize);\n\n if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {\n drops[i] = 0;\n }\n drops[i] += speed; // Use the speed prop to control fall rate\n }\n };\n\n animationFrameId = requestAnimationFrame(draw);\n\n return () => {\n window.removeEventListener(\"resize\", resizeCanvas);\n cancelAnimationFrame(animationFrameId);\n };\n }, [color, fontSize, speed]);\n\n return (\n <canvas\n ref={canvasRef}\n className={`pointer-events-none ${className}`}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n }}\n />\n );\n};\n\nexport default HackerBackground;\n",
"type": "registry:component"
}
]
}
2 changes: 1 addition & 1 deletion public/r/tech-cursor.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
{
"path": "./src/components/nurui/tech-cursor.tsx",
"content": "\"use client\";\nimport React, { useEffect, useRef } from \"react\";\n\ninterface TechImage {\n name: string;\n src: string;\n image: HTMLImageElement;\n}\n\ninterface Particle {\n x: number;\n y: number;\n alpha: number;\n image: HTMLImageElement;\n size: number;\n update: () => void;\n draw: (ctx: CanvasRenderingContext2D) => void;\n}\n\nconst icons: { name: string; src: string }[] = [\n // replace with your image path\n { name: \"JavaScript\", src: \"/js.png\" },\n { name: \"TypeScript\", src: \"/ts.png\" },\n { name: \"React\", src: \"/react.svg\" },\n { name: \"Next.js\", src: \"/js.png\" },\n { name: \"HTML\", src: \"/html.png\" },\n { name: \"CSS\", src: \"/css.png\" },\n];\n\nconst TechCursor = () => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const particlesRef = useRef<Particle[]>([]);\n const techImagesRef = useRef<TechImage[]>([]);\n\n useEffect(() => {\n // Preload images\n const loadImages = async () => {\n techImagesRef.current = await Promise.all(\n icons.map(({ name, src }) => {\n return new Promise<TechImage>((resolve) => {\n const img = new Image();\n img.src = src;\n img.onload = () => resolve({ name, src, image: img });\n });\n }),\n );\n };\n\n loadImages().then(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n canvas.width = window.innerWidth;\n canvas.height = window.innerHeight;\n\n const particles = particlesRef.current;\n\n const animate = () => {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n for (let i = particles.length - 1; i >= 0; i--) {\n const p = particles[i];\n p.update();\n p.draw(ctx);\n if (p.alpha <= 0) {\n particles.splice(i, 1);\n }\n }\n requestAnimationFrame(animate);\n };\n\n animate();\n\n const onMove = (e: MouseEvent) => {\n const randomIcon =\n techImagesRef.current[\n Math.floor(Math.random() * techImagesRef.current.length)\n ];\n\n const size = 22 + Math.random() * 8;\n\n const particle: Particle = {\n x: e.clientX,\n y: e.clientY,\n alpha: 1,\n image: randomIcon.image,\n size,\n update() {\n this.y -= 0.4;\n this.alpha -= 0.02;\n },\n draw(ctx: CanvasRenderingContext2D) {\n ctx.globalAlpha = this.alpha;\n ctx.drawImage(\n this.image,\n this.x - this.size / 2,\n this.y - this.size / 2,\n this.size,\n this.size,\n );\n ctx.globalAlpha = 1;\n },\n };\n\n particles.push(particle);\n };\n\n window.addEventListener(\"mousemove\", onMove);\n return () => {\n window.removeEventListener(\"mousemove\", onMove);\n };\n });\n }, []);\n\n return (\n <canvas\n ref={canvasRef}\n className=\"fixed top-0 left-0 w-full h-full pointer-events-none z-50\"\n />\n );\n};\n\nexport default TechCursor;\n",
"content": "\"use client\";\nimport React, { useEffect, useRef } from \"react\";\n\ninterface TechImage {\n name: string;\n src: string;\n image: HTMLImageElement;\n}\n\ninterface Particle {\n x: number;\n y: number;\n alpha: number;\n image: HTMLImageElement;\n size: number;\n update: () => void;\n draw: (ctx: CanvasRenderingContext2D) => void;\n}\n\nconst icons: { name: string; src: string }[] = [\n // replace with your image path\n {\n name: \"JavaScript\",\n src: \"https://res.cloudinary.com/dz1fy2tof/image/upload/v1755012752/js_nocitj.png\",\n },\n {\n name: \"TypeScript\",\n src: \"https://res.cloudinary.com/dz1fy2tof/image/upload/v1755012632/ts_elsqw8.png\",\n },\n {\n name: \"React\",\n src: \"https://res.cloudinary.com/dz1fy2tof/image/upload/v1755012941/react_ogt6ny.svg\",\n },\n {\n name: \"Next.js\",\n src: \"https://res.cloudinary.com/dz1fy2tof/image/upload/v1755012973/next_hrodnb.svg\",\n },\n {\n name: \"HTML\",\n src: \"https://res.cloudinary.com/dz1fy2tof/image/upload/v1755012812/html_xbcdkj.png\",\n },\n {\n name: \"CSS\",\n src: \"https://res.cloudinary.com/dz1fy2tof/image/upload/v1755012862/css_1_irojyc.png\",\n },\n];\n\nconst TechCursor = () => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const particlesRef = useRef<Particle[]>([]);\n const techImagesRef = useRef<TechImage[]>([]);\n\n useEffect(() => {\n // Preload images\n const loadImages = async () => {\n techImagesRef.current = await Promise.all(\n icons.map(({ name, src }) => {\n return new Promise<TechImage>((resolve) => {\n const img = new Image();\n img.src = src;\n img.onload = () => resolve({ name, src, image: img });\n });\n }),\n );\n };\n\n loadImages().then(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n canvas.width = window.innerWidth;\n canvas.height = window.innerHeight;\n\n const particles = particlesRef.current;\n\n const animate = () => {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n for (let i = particles.length - 1; i >= 0; i--) {\n const p = particles[i];\n p.update();\n p.draw(ctx);\n if (p.alpha <= 0) {\n particles.splice(i, 1);\n }\n }\n requestAnimationFrame(animate);\n };\n\n animate();\n\n const onMove = (e: MouseEvent) => {\n const randomIcon =\n techImagesRef.current[\n Math.floor(Math.random() * techImagesRef.current.length)\n ];\n\n const size = 22 + Math.random() * 8;\n\n const particle: Particle = {\n x: e.clientX,\n y: e.clientY,\n alpha: 1,\n image: randomIcon.image,\n size,\n update() {\n this.y -= 0.4;\n this.alpha -= 0.02;\n },\n draw(ctx: CanvasRenderingContext2D) {\n ctx.globalAlpha = this.alpha;\n ctx.drawImage(\n this.image,\n this.x - this.size / 2,\n this.y - this.size / 2,\n this.size,\n this.size,\n );\n ctx.globalAlpha = 1;\n },\n };\n\n particles.push(particle);\n };\n\n window.addEventListener(\"mousemove\", onMove);\n return () => {\n window.removeEventListener(\"mousemove\", onMove);\n };\n });\n }, []);\n\n return (\n <canvas\n ref={canvasRef}\n className=\"fixed top-0 left-0 w-full h-full pointer-events-none z-50\"\n />\n );\n};\n\nexport default TechCursor;\n",
"type": "registry:component"
}
]
Expand Down
13 changes: 13 additions & 0 deletions registry-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,19 @@
}
]
},
{
"name": "hacker-background",
"type": "registry:component",
"devDependencies": [],
"dependencies": [],
"registryDependencies": [],
"files": [
{
"path": "./src/components/nurui/backer-background.tsx",
"type": "registry:component"
}
]
},

{
"name": "magnet-button",
Expand Down
17 changes: 17 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,23 @@
}
]
},
{
"name": "hacker-background",
"type": "registry:component",
"devDependencies": [],
"dependencies": [""],
"registryDependencies": [],
"files": [
{
"path": "./src/components/nurui/hacker-background-demo.tsx",
"type": "registry:component"
},
{
"path": "./src/components/nurui/hacker-background.tsx",
"type": "registry:component"
}
]
},

{
"name": "magnet-button",
Expand Down
3 changes: 3 additions & 0 deletions src/app/preview/[component]/components-preview-registry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export const componentsPreviewRegistry: Record<
"virus-background": {
component: dynamic(() => import("@/components/nurui/virus-background")),
},
"hacker-background": {
component: dynamic(() => import("@/components/nurui/hacker-background")),
},
// texts
"gradient-text": {
component: dynamic(() => import("@/components/nurui/gradient-text-demo")),
Expand Down
9 changes: 9 additions & 0 deletions src/components/nurui/hacker-background-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import HackerBackground from "./hacker-background";

export function HackerBackgroundDemo() {
return (
<div className="relative z-10 h-[500px] w-full overflow-hidden rounded-lg">
<HackerBackground />
</div>
);
}
91 changes: 91 additions & 0 deletions src/components/nurui/hacker-background.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"use client";
import React, { useEffect, useRef } from "react";

interface HackerBackgroundProps {
color?: string;
fontSize?: number;
className?: string;
speed?: number;
}

const HackerBackground: React.FC<HackerBackgroundProps> = ({
color = "#3ca2fa",
fontSize = 15,
className = "",
speed = 1,
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext("2d");
if (!ctx) return;

const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};

resizeCanvas();
window.addEventListener("resize", resizeCanvas);

let animationFrameId: number;

const columns = Math.floor(canvas.width / fontSize);
const drops: number[] = new Array(columns).fill(1);

const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()_+";

let lastTime = 0;
const interval = 33; // ~30 fps

const draw = (currentTime: number) => {
animationFrameId = requestAnimationFrame(draw);

if (currentTime - lastTime < interval) return;
lastTime = currentTime;

ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.fillStyle = color;
ctx.font = `${fontSize}px monospace`;

for (let i = 0; i < drops.length; i++) {
const text = chars[Math.floor(Math.random() * chars.length)];
ctx.fillText(text, i * fontSize, drops[i] * fontSize);

if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i] += speed; // Use the speed prop to control fall rate
}
};

animationFrameId = requestAnimationFrame(draw);

return () => {
window.removeEventListener("resize", resizeCanvas);
cancelAnimationFrame(animationFrameId);
};
}, [color, fontSize, speed]);

return (
<canvas
ref={canvasRef}
className={`pointer-events-none ${className}`}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
}}
/>
);
};

export default HackerBackground;
48 changes: 48 additions & 0 deletions src/content/docs/hacker-background.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: "hacker background"
description: "A stylish hacker background component for modern UIs, built with accessibility and animation in mind."
---

<ComponentPreview componentName="hackerBackground" />

## Installation

<Tabs defaultValue="cli">

<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>

<TabsContent value="cli">
<Cli />
</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Install dependencies</Step>

<Step>Copy and paste the following code into your project.</Step>

`components/nurui/hacker-background.tsx`

<CodeBlock componentName="hackerBackground" fileName="hacker-background" />

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</Tabs>

### Props

| Prop | Type | Default | Description |
| ----------- | -------- | ----------- | ---------------------------------------------------------------------------- |
| `color` | `string` | `"#3ca2fa"` | Color of the falling hacker characters. |
| `fontSize` | `number` | `14` | Font size (in pixels) for the hacker characters. |
| `className` | `string` | `""` | Additional class names for the canvas element. |
| `speed` | `number` | `1` | Speed multiplier controlling how fast the characters fall (higher = faster). |
Loading