fixed docs v1
This commit is contained in:
@@ -4,7 +4,8 @@ import { Button } from "@/components/ui/button";
|
||||
|
||||
const SSH_COMMAND = "ssh portfolio@keshavanand.net";
|
||||
|
||||
const CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()_+-=[]{}|;:,.<>?/\\~`";
|
||||
const CHARACTERS =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()_+-=[]{}|;:,.<>?/\\~`";
|
||||
|
||||
interface Particle {
|
||||
x: number;
|
||||
@@ -24,7 +25,10 @@ interface Particle {
|
||||
function ParticleBackground() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const particlesRef = useRef<Particle[]>([]);
|
||||
const mouseRef = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||
const mouseRef = useRef({
|
||||
x: window.innerWidth / 2,
|
||||
y: window.innerHeight / 2,
|
||||
});
|
||||
const animationRef = useRef<number>();
|
||||
const frameCountRef = useRef(0);
|
||||
|
||||
@@ -52,16 +56,24 @@ function ParticleBackground() {
|
||||
"#cba6f7",
|
||||
];
|
||||
|
||||
const createParticle = (x: number, y: number, isAmbient = false): Particle => {
|
||||
const createParticle = (
|
||||
x: number,
|
||||
y: number,
|
||||
isAmbient = false
|
||||
): Particle => {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const speed = isAmbient ? Math.random() * 0.5 + 0.2 : Math.random() * 2 + 1;
|
||||
const speed = isAmbient
|
||||
? Math.random() * 0.5 + 0.2
|
||||
: Math.random() * 2 + 1;
|
||||
const color = colors[Math.floor(Math.random() * colors.length)];
|
||||
const spread = isAmbient ? 200 : 120;
|
||||
return {
|
||||
x: x + (Math.random() - 0.5) * spread,
|
||||
y: y + (Math.random() - 0.5) * spread,
|
||||
char: CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)],
|
||||
opacity: isAmbient ? Math.random() * 0.4 + 0.1 : Math.random() * 0.8 + 0.2,
|
||||
opacity: isAmbient
|
||||
? Math.random() * 0.4 + 0.1
|
||||
: Math.random() * 0.8 + 0.2,
|
||||
targetX: x,
|
||||
targetY: y,
|
||||
vx: Math.cos(angle) * speed,
|
||||
@@ -75,7 +87,7 @@ function ParticleBackground() {
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
mouseRef.current = { x: e.clientX, y: e.clientY };
|
||||
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (particlesRef.current.length < 400) {
|
||||
particlesRef.current.push(createParticle(e.clientX, e.clientY));
|
||||
@@ -86,16 +98,22 @@ function ParticleBackground() {
|
||||
const animate = () => {
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
|
||||
frameCountRef.current++;
|
||||
|
||||
if (frameCountRef.current % 3 === 0 && particlesRef.current.length < 400) {
|
||||
|
||||
if (
|
||||
frameCountRef.current % 3 === 0 &&
|
||||
particlesRef.current.length < 400
|
||||
) {
|
||||
const mx = mouseRef.current.x;
|
||||
const my = mouseRef.current.y;
|
||||
particlesRef.current.push(createParticle(mx, my, true));
|
||||
}
|
||||
|
||||
if (frameCountRef.current % 8 === 0 && particlesRef.current.length < 400) {
|
||||
|
||||
if (
|
||||
frameCountRef.current % 8 === 0 &&
|
||||
particlesRef.current.length < 400
|
||||
) {
|
||||
const rx = Math.random() * canvas.width;
|
||||
const ry = Math.random() * canvas.height;
|
||||
particlesRef.current.push(createParticle(rx, ry, true));
|
||||
@@ -103,33 +121,34 @@ function ParticleBackground() {
|
||||
|
||||
particlesRef.current = particlesRef.current.filter((p) => {
|
||||
p.life++;
|
||||
|
||||
|
||||
const dx = mouseRef.current.x - p.x;
|
||||
const dy = mouseRef.current.y - p.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
|
||||
|
||||
if (distance < 200) {
|
||||
const force = (200 - distance) / 200;
|
||||
p.vx += (dx / distance) * force * 0.4;
|
||||
p.vy += (dy / distance) * force * 0.4;
|
||||
}
|
||||
|
||||
|
||||
p.vx *= 0.97;
|
||||
p.vy *= 0.97;
|
||||
|
||||
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
|
||||
|
||||
const lifeRatio = p.life / p.maxLife;
|
||||
const fadeOpacity = lifeRatio < 0.15
|
||||
? lifeRatio * 6.67
|
||||
: lifeRatio > 0.7
|
||||
? (1 - lifeRatio) * 3.33
|
||||
const fadeOpacity =
|
||||
lifeRatio < 0.15
|
||||
? lifeRatio * 6.67
|
||||
: lifeRatio > 0.7
|
||||
? (1 - lifeRatio) * 3.33
|
||||
: 1;
|
||||
|
||||
|
||||
const distanceOpacity = Math.max(0.15, 1 - distance / 400);
|
||||
const finalOpacity = p.opacity * fadeOpacity * distanceOpacity;
|
||||
|
||||
|
||||
if (finalOpacity > 0.01) {
|
||||
ctx.font = `${p.size}px 'JetBrains Mono', monospace`;
|
||||
ctx.fillStyle = p.color;
|
||||
@@ -137,7 +156,7 @@ function ParticleBackground() {
|
||||
ctx.fillText(p.char, p.x, p.y);
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
|
||||
return p.life < p.maxLife;
|
||||
});
|
||||
|
||||
@@ -169,6 +188,14 @@ function ParticleBackground() {
|
||||
function CommandBox() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const [showHowItWorks, setShowHowItWorks] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setShowHowItWorks(true);
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const handleCopy = useCallback(async () => {
|
||||
try {
|
||||
@@ -183,40 +210,71 @@ function CommandBox() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative z-10 flex items-center gap-4 px-6 py-4 rounded-xl border border-border/30 backdrop-blur-sm"
|
||||
style={{ backgroundColor: "rgba(49, 50, 68, 0.8)" }}
|
||||
data-testid="command-box"
|
||||
>
|
||||
<span className="text-muted-foreground mr-1" data-testid="text-prompt">$</span>
|
||||
<code
|
||||
className="text-lg md:text-xl font-mono"
|
||||
style={{ color: "#a6e3a1" }}
|
||||
data-testid="text-ssh-command"
|
||||
>
|
||||
{SSH_COMMAND}
|
||||
</code>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={handleCopy}
|
||||
className={`ml-2 text-muted-foreground transition-transform duration-150 ${isAnimating ? "scale-90" : "scale-100"}`}
|
||||
aria-label={copied ? "Copied to clipboard" : "Copy SSH command"}
|
||||
data-testid="button-copy"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-5 w-5" style={{ color: "#a6e3a1" }} />
|
||||
) : (
|
||||
<Copy className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
<div className="flex flex-col items-center justify-center min-h-screen p-4 sm:p-6">
|
||||
<div className="flex-1 flex items-center justify-center w-full max-w-[90vw] md:max-w-none">
|
||||
<div
|
||||
className="relative z-10 flex flex-wrap items-center justify-center md:justify-start gap-3 md:gap-4 px-4 py-3 md:px-6 md:py-4 rounded-xl border border-border/30 backdrop-blur-sm shadow-2xl w-full md:w-auto overflow-hidden"
|
||||
style={{ backgroundColor: "rgba(49, 50, 68, 0.8)" }}
|
||||
data-testid="command-box"
|
||||
>
|
||||
<div className="flex items-center gap-2 md:gap-4 flex-1 md:flex-none justify-center md:justify-start">
|
||||
<span
|
||||
className="text-muted-foreground shrink-0"
|
||||
data-testid="text-prompt"
|
||||
>
|
||||
$
|
||||
</span>
|
||||
<code
|
||||
className="text-sm sm:text-base md:text-lg lg:text-xl font-mono truncate"
|
||||
style={{ color: "#a6e3a1" }}
|
||||
data-testid="text-ssh-command"
|
||||
>
|
||||
{SSH_COMMAND}
|
||||
</code>
|
||||
</div>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={handleCopy}
|
||||
className={`shrink-0 text-muted-foreground transition-transform duration-150 ${
|
||||
isAnimating ? "scale-90" : "scale-100"
|
||||
}`}
|
||||
aria-label={copied ? "Copied to clipboard" : "Copy SSH command"}
|
||||
data-testid="button-copy"
|
||||
>
|
||||
{copied ? (
|
||||
<Check
|
||||
className="h-4 w-4 md:h-5 md:w-5"
|
||||
style={{ color: "#a6e3a1" }}
|
||||
/>
|
||||
) : (
|
||||
<Copy className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pb-12 md:pb-16">
|
||||
<a
|
||||
href="/docs"
|
||||
className={`relative z-10 px-6 py-3 rounded-lg border border-border/20 backdrop-blur-md text-sm font-mono text-muted-foreground transition-all duration-1000 hover:border-primary/40 hover:text-primary hover-elevate ${
|
||||
showHowItWorks
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 translate-y-8"
|
||||
}`}
|
||||
style={{ backgroundColor: "rgba(30, 30, 46, 0.4)" }}
|
||||
data-testid="link-how-it-works"
|
||||
>
|
||||
How it Works
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="min-h-screen w-full flex items-center justify-center overflow-hidden">
|
||||
<div className="min-h-screen w-full relative overflow-hidden bg-black">
|
||||
<ParticleBackground />
|
||||
<CommandBox />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user