Skip to content
Closed
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
41 changes: 32 additions & 9 deletions src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
Comment on lines +1 to +3

:root {
color-scheme: light;
Expand Down Expand Up @@ -67,6 +67,12 @@ body {
transition: background-color 200ms ease, color 200ms ease;
}

/* Strip body background on landing page so the particle canvas (z-index: 0) is visible */
body:has(.lnd-root) {
Comment on lines +70 to +71
background: #050505 !important;
background-color: #050505 !important;
}

.surface-card {
border: 1px solid var(--border);
background: linear-gradient(180deg, color-mix(in srgb, var(--card) 92%, #ffffff 8%), var(--card));
Expand Down Expand Up @@ -174,6 +180,11 @@ body {
animation: lndTicker 40s linear infinite;
}

.lnd-root,
.lnd-root * {
cursor: none !important;
Comment on lines +183 to +185
}

.lnd-root ::selection {
background: rgba(129, 140, 248, 0.2);
color: #fff;
Expand Down Expand Up @@ -227,13 +238,25 @@ body {
.lnd-cta-secondary:hover { color: #e0e0e0; border-color: #444; }

.lnd-cell {
background: #0e0e0e;
border: 1px solid #1a1a1a;
border-radius: 8px;
padding: 14px 16px;
transition: background 0.2s, border-color 0.2s;
background: rgba(8, 8, 10, 0.55);
border: 1px solid rgba(255, 255, 255, 0.035);
border-radius: 12px;
padding: 18px 20px;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.05),
0 4px 20px rgba(0, 0, 0, 0.35);
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.lnd-cell:hover {
background: rgba(12, 12, 16, 0.75);
border-color: rgba(129, 140, 248, 0.35);
transform: translateY(-2px);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.08),
0 8px 30px rgba(129, 140, 248, 0.12);
}
.lnd-cell:hover { background: #141414; border-color: #2a2a2a; }

.lnd-heatmap-cell { cursor: crosshair; }
.lnd-heatmap-cell:hover {
Expand Down
159 changes: 159 additions & 0 deletions src/components/CustomCursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"use client";

import { useEffect, useState, useRef } from "react";

export default function CustomCursor() {
const [mounted, setMounted] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const [isHidden, setIsHidden] = useState(true);

// Target mouse coordinates
const mouseRef = useRef({ x: -100, y: -100 });

// Interpolated ring coordinates
const ringRef = useRef({ x: -100, y: -100 });

const dotDomRef = useRef<HTMLDivElement>(null);
const ringDomRef = useRef<HTMLDivElement>(null);

useEffect(() => {
setMounted(true);
Comment on lines +19 to +20

const onMouseMove = (e: MouseEvent) => {
mouseRef.current.x = e.clientX;
mouseRef.current.y = e.clientY;
setIsHidden(false);
};

const onMouseLeave = () => {
setIsHidden(true);
};

const onMouseEnter = () => {
setIsHidden(false);
};

// Check if user is hovering over interactive components
const onMouseOver = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (
target &&
(target.closest("a") ||
target.closest("button") ||
target.closest(".lnd-cell") ||
target.closest(".lnd-cta-primary") ||
target.closest(".lnd-cta-secondary") ||
target.closest(".lnd-nav-link") ||
target.style.cursor === "pointer")
) {
setIsHovered(true);
} else {
setIsHovered(false);
}
};
Comment on lines +36 to +53

window.addEventListener("mousemove", onMouseMove, { passive: true });
window.addEventListener("mouseleave", onMouseLeave, { passive: true });
window.addEventListener("mouseenter", onMouseEnter, { passive: true });
window.addEventListener("mouseover", onMouseOver, { passive: true });

let rafId: number;

const render = () => {
const targetX = mouseRef.current.x;
const targetY = mouseRef.current.y;

// Solid central core dot follows exactly
if (dotDomRef.current) {
dotDomRef.current.style.transform = `translate3d(${targetX - 4}px, ${targetY - 4}px, 0)`;
}

// Smooth lag-interpolation (lerp) for the outer concentric ring
const rx = ringRef.current.x;
const ry = ringRef.current.y;

const nextRx = rx + (targetX - rx) * 0.15;
const nextRy = ry + (targetY - ry) * 0.15;

ringRef.current.x = nextRx;
ringRef.current.y = nextRy;

if (ringDomRef.current) {
// Offset by -16px to align center (32px wide)
ringDomRef.current.style.transform = `translate3d(${nextRx - 16}px, ${nextRy - 16}px, 0)`;
}

rafId = requestAnimationFrame(render);
};

rafId = requestAnimationFrame(render);

return () => {
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseleave", onMouseLeave);
window.removeEventListener("mouseenter", onMouseEnter);
window.removeEventListener("mouseover", onMouseOver);
cancelAnimationFrame(rafId);
};
}, []);

if (!mounted) return null;

return (
<>
{/* 1. Glowing Cyan Dot Core */}
<div
ref={dotDomRef}
style={{
position: "fixed",
top: 0,
left: 0,
width: 8,
height: 8,
borderRadius: "50%",
backgroundColor: "#00f0ff",
pointerEvents: "none",
zIndex: 99999,
opacity: isHidden ? 0 : 1,
transform: "translate3d(-100px, -100px, 0)",
boxShadow: "0 0 10px #00f0ff, 0 0 20px #00f0ff",
transition: "opacity 0.25s ease-out, width 0.2s, height 0.2s, background-color 0.2s",
...(isHovered && {
width: 5,
height: 5,
backgroundColor: "#ffffff",
boxShadow: "0 0 8px #ffffff",
}),
}}
/>

{/* 2. Concentric spring outer ring */}
<div
ref={ringDomRef}
style={{
position: "fixed",
top: 0,
left: 0,
width: 32,
height: 32,
borderRadius: "50%",
border: "1.5px solid #00f0ff",
pointerEvents: "none",
zIndex: 99997, // just below the dot
opacity: isHidden ? 0 : 0.85,
transform: "translate3d(-100px, -100px, 0)",
boxShadow: "0 0 8px rgba(0, 240, 255, 0.4)",
transition: "opacity 0.25s ease-out, border-color 0.2s, width 0.25s, height 0.25s, box-shadow 0.25s, margin 0.25s",
...(isHovered && {
width: 48,
height: 48,
borderColor: "#ffffff",
boxShadow: "0 0 16px rgba(255, 255, 255, 0.55)",
marginLeft: -8, // offsets the larger width (48px vs 32px -> 16px diff -> 8px margin shift)
marginTop: -8,
}),
}}
/>
</>
);
}
8 changes: 7 additions & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

const year = new Date().getFullYear();

export default function Footer() {
const pathname = usePathname();
const isLanding = pathname === "/";

Comment on lines +1 to +11
Comment on lines +1 to +11
return (
<footer className="dark mt-auto border-t border-[var(--border)] bg-[var(--background)] relative">
<footer className={`dark mt-auto border-t relative ${isLanding ? 'bg-transparent border-slate-900/40' : 'border-[var(--border)] bg-[var(--background)]'}`}>
<div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(59,130,246,0.08),transparent)] pointer-events-none" />
<div className="relative mx-auto w-full max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
<div className="grid gap-10 lg:grid-cols-[1.2fr_0.8fr_0.8fr_0.8fr]">
Expand Down
Loading
Loading