Skip to content

Commit 9ccebb3

Browse files
authored
feat: revamp landing page with interactive components and unified nav (#251) (#315)
* feat: revamp landing page for modern UI/UX (#251) - Split monolithic landing page into section components - Add smooth scroll animations and micro-interactions - Implement orbit animations, BYOM pipeline, services showcase - Improve typography hierarchy and spacing - Fix auth forms with proper titles and branding - Remove redundant 'Back to Home' button from signup - Fully responsive across mobile, tablet, and desktop - Mobile hamburger menu support * feat: enhance landing page with interactive components and unified nav (#251) - Add BorderGlow, Hyperspeed, and MagicBento components - Refactor hero section: centered layout with Hyperspeed background - Replace services grid with MagicBento card component - Add unified glassmorphism nav with active states - Update Pricing page to use shared nav and mobile overlay - Add gsap, three, postprocessing dependencies - Polish animations and visual effects across landing page * fix: resolve CodeRabbit review comments - Fix Stylelint declaration-empty-line-before in BorderGlow.css - Add animation cancellation cleanup to BorderGlow.jsx - Scope canvas selector to #lights in Hyperspeed.css - Guard async asset-init against unmount race in Hyperspeed.jsx - Scope :root tokens to .bento-section in MagicBento.css - Wire ParticleCard props in MagicBento.jsx - Add ARIA attributes and extract inline styles in Pricing.jsx/css
1 parent a8dde05 commit 9ccebb3

16 files changed

Lines changed: 6137 additions & 1268 deletions

File tree

apps/web-dashboard/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@
1919
"@tanstack/react-table": "^8.21.3",
2020
"axios": "^1.13.2",
2121
"framer-motion": "^12.35.0",
22+
"gsap": "^3.15.0",
2223
"lucide-react": "^0.554.0",
24+
"postprocessing": "^6.39.1",
2325
"react": "^19.2.0",
2426
"react-dom": "^19.2.0",
2527
"react-hot-toast": "^2.6.0",
2628
"react-markdown": "^10.1.0",
2729
"react-router-dom": "^7.9.6",
2830
"recharts": "^2.13.0",
2931
"remark-gfm": "^4.0.1",
30-
"tailwindcss": "^4.1.18"
32+
"tailwindcss": "^4.1.18",
33+
"three": "^0.184.0"
3134
},
3235
"devDependencies": {
3336
"@eslint/js": "^9.39.1",

apps/web-dashboard/src/components/AuthShell.jsx

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,25 @@ function AuthShell({
1717

1818
<div className="auth-shell__content">
1919
<div className="auth-form-card">
20+
<div className="auth-form-card__brand">
21+
<div className="auth-form-card__brand-mark">U</div>
22+
<span className="auth-form-card__brand-text">urBackend</span>
23+
</div>
24+
2025
<div className="auth-form-card__header">
21-
{onModeClick ? (
22-
<button
23-
type="button"
24-
className="auth-form-card__mode auth-form-card__mode--clickable"
25-
onClick={onModeClick}
26-
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
27-
>
28-
{modeLabel}
29-
</button>
30-
) : (
31-
<span className="auth-form-card__mode">{modeLabel}</span>
26+
{modeLabel && (
27+
onModeClick ? (
28+
<button
29+
type="button"
30+
className="auth-form-card__mode auth-form-card__mode--clickable"
31+
onClick={onModeClick}
32+
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
33+
>
34+
{modeLabel}
35+
</button>
36+
) : (
37+
<span className="auth-form-card__mode">{modeLabel}</span>
38+
)
3239
)}
3340
{title ? <h1>{title}</h1> : null}
3441
{subtitle ? <p>{subtitle}</p> : null}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
.border-glow-card {
2+
--edge-proximity: 0;
3+
--cursor-angle: 45deg;
4+
--edge-sensitivity: 30;
5+
--color-sensitivity: calc(var(--edge-sensitivity) + 20);
6+
--border-radius: 28px;
7+
--glow-padding: 40px;
8+
--cone-spread: 25;
9+
10+
position: relative;
11+
border-radius: var(--border-radius);
12+
isolation: isolate;
13+
transform: translate3d(0, 0, 0.01px);
14+
display: flex;
15+
flex-direction: column;
16+
border: 1px solid rgba(255, 255, 255, 0.08);
17+
background: var(--card-bg, rgba(255, 255, 255, 0.02));
18+
backdrop-filter: blur(12px);
19+
-webkit-backdrop-filter: blur(12px);
20+
overflow: visible;
21+
box-shadow:
22+
rgba(0, 0, 0, 0.1) 0px 1px 2px,
23+
rgba(0, 0, 0, 0.1) 0px 2px 4px,
24+
rgba(0, 0, 0, 0.1) 0px 4px 8px,
25+
rgba(0, 0, 0, 0.1) 0px 8px 16px,
26+
rgba(0, 0, 0, 0.1) 0px 16px 32px,
27+
rgba(0, 0, 0, 0.1) 0px 32px 64px;
28+
}
29+
30+
.border-glow-card::before,
31+
.border-glow-card > .edge-light {
32+
content: "";
33+
position: absolute;
34+
inset: 0;
35+
border-radius: inherit;
36+
transition: opacity 0.25s ease-out;
37+
z-index: -1;
38+
}
39+
40+
.border-glow-card:not(:hover):not(.sweep-active)::before,
41+
.border-glow-card:not(:hover):not(.sweep-active) > .edge-light {
42+
opacity: 0;
43+
transition: opacity 0.75s ease-in-out;
44+
}
45+
46+
/* colored mesh-gradient border */
47+
.border-glow-card::before {
48+
border: 1px solid transparent;
49+
background:
50+
linear-gradient(#0c0a0f 0 100%) padding-box,
51+
linear-gradient(rgba(255, 255, 255, 0) 0% 100%) border-box,
52+
var(--gradient-one, radial-gradient(at 80% 55%, hsla(268, 100%, 76%, 1) 0px, transparent 50%)) border-box,
53+
var(--gradient-two, radial-gradient(at 69% 34%, hsla(349, 100%, 74%, 1) 0px, transparent 50%)) border-box,
54+
var(--gradient-three, radial-gradient(at 8% 6%, hsla(136, 100%, 78%, 1) 0px, transparent 50%)) border-box,
55+
var(--gradient-four, radial-gradient(at 41% 38%, hsla(192, 100%, 64%, 1) 0px, transparent 50%)) border-box,
56+
var(--gradient-five, radial-gradient(at 86% 85%, hsla(186, 100%, 74%, 1) 0px, transparent 50%)) border-box,
57+
var(--gradient-six, radial-gradient(at 82% 18%, hsla(52, 100%, 65%, 1) 0px, transparent 50%)) border-box,
58+
var(--gradient-seven, radial-gradient(at 51% 4%, hsla(12, 100%, 72%, 1) 0px, transparent 50%)) border-box,
59+
var(--gradient-base, linear-gradient(#c299ff 0 100%)) border-box;
60+
opacity: calc((var(--edge-proximity) - var(--color-sensitivity)) / (100 - var(--color-sensitivity)));
61+
mask-image:
62+
conic-gradient(
63+
from var(--cursor-angle) at center,
64+
black calc(var(--cone-spread) * 1%),
65+
transparent calc((var(--cone-spread) + 15) * 1%),
66+
transparent calc((100 - var(--cone-spread) - 15) * 1%),
67+
black calc((100 - var(--cone-spread)) * 1%)
68+
);
69+
}
70+
71+
/* outer glow layer */
72+
.border-glow-card > .edge-light {
73+
inset: calc(var(--glow-padding) * -1);
74+
pointer-events: none;
75+
z-index: 1;
76+
mask-image:
77+
conic-gradient(
78+
from var(--cursor-angle) at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%
79+
);
80+
opacity: calc((var(--edge-proximity) - var(--edge-sensitivity)) / (100 - var(--edge-sensitivity)));
81+
mix-blend-mode: plus-lighter;
82+
}
83+
84+
.border-glow-card > .edge-light::before {
85+
content: "";
86+
position: absolute;
87+
inset: var(--glow-padding);
88+
border-radius: inherit;
89+
box-shadow:
90+
0 0 1px 0 var(--glow-color-60, hsl(40deg 80% 80% / 60%)),
91+
0 0 3px 0 var(--glow-color-50, hsl(40deg 80% 80% / 50%)),
92+
0 0 6px 0 var(--glow-color-40, hsl(40deg 80% 80% / 40%)),
93+
0 0 15px 0 var(--glow-color-30, hsl(40deg 80% 80% / 30%)),
94+
0 0 25px 2px var(--glow-color-20, hsl(40deg 80% 80% / 20%)),
95+
0 0 50px 2px var(--glow-color-10, hsl(40deg 80% 80% / 10%));
96+
}
97+
98+
.border-glow-inner {
99+
display: flex;
100+
flex-direction: column;
101+
justify-content: space-between;
102+
height: 100%;
103+
width: 100%;
104+
position: relative;
105+
z-index: 1;
106+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { useRef, useCallback, useEffect } from 'react';
2+
import './BorderGlow.css';
3+
4+
function parseHSL(hslStr) {
5+
const match = hslStr.match(/([\d.]+)\s*([\d.]+)%?\s*([\d.]+)%?/);
6+
if (!match) return { h: 40, s: 80, l: 80 };
7+
return { h: parseFloat(match[1]), s: parseFloat(match[2]), l: parseFloat(match[3]) };
8+
}
9+
10+
function buildGlowVars(glowColor, intensity) {
11+
const { h, s, l } = parseHSL(glowColor);
12+
const base = `${h}deg ${s}% ${l}%`;
13+
const opacities = [100, 60, 50, 40, 30, 20, 10];
14+
const keys = ['', '-60', '-50', '-40', '-30', '-20', '-10'];
15+
const vars = {};
16+
for (let i = 0; i < opacities.length; i++) {
17+
vars[`--glow-color${keys[i]}`] = `hsl(${base} / ${Math.min(opacities[i] * intensity, 100)}%)`;
18+
}
19+
return vars;
20+
}
21+
22+
const GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'];
23+
const GRADIENT_KEYS = ['--gradient-one', '--gradient-two', '--gradient-three', '--gradient-four', '--gradient-five', '--gradient-six', '--gradient-seven'];
24+
const COLOR_MAP = [0, 1, 2, 0, 1, 2, 1];
25+
26+
function buildGradientVars(colors) {
27+
const vars = {};
28+
for (let i = 0; i < 7; i++) {
29+
const c = colors[Math.min(COLOR_MAP[i], colors.length - 1)];
30+
vars[GRADIENT_KEYS[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${c} 0px, transparent 50%)`;
31+
}
32+
vars['--gradient-base'] = `linear-gradient(${colors[0]} 0 100%)`;
33+
return vars;
34+
}
35+
36+
function easeOutCubic(x) { return 1 - Math.pow(1 - x, 3); }
37+
function easeInCubic(x) { return x * x * x; }
38+
39+
function animateValue({ start = 0, end = 100, duration = 1000, delay = 0, ease = easeOutCubic, onUpdate, onEnd }) {
40+
const t0 = performance.now() + delay;
41+
let rafId = null;
42+
function tick() {
43+
const elapsed = performance.now() - t0;
44+
const t = Math.min(elapsed / duration, 1);
45+
onUpdate(start + (end - start) * ease(t));
46+
if (t < 1) rafId = requestAnimationFrame(tick);
47+
else if (onEnd) onEnd();
48+
}
49+
const timeoutId = setTimeout(() => {
50+
rafId = requestAnimationFrame(tick);
51+
}, delay);
52+
53+
return () => {
54+
clearTimeout(timeoutId);
55+
if (rafId != null) cancelAnimationFrame(rafId);
56+
};
57+
}
58+
59+
const BorderGlow = ({
60+
children,
61+
className = '',
62+
edgeSensitivity = 30,
63+
glowColor = '40 80 80',
64+
backgroundColor = 'rgba(255, 255, 255, 0.02)',
65+
borderRadius = 16,
66+
glowRadius = 40,
67+
glowIntensity = 1.0,
68+
coneSpread = 25,
69+
animated = false,
70+
colors = ['#00f5d4', '#00b4ab', '#00ffd8'],
71+
fillOpacity = 0.5,
72+
}) => {
73+
const cardRef = useRef(null);
74+
75+
const getCenterOfElement = useCallback((el) => {
76+
const { width, height } = el.getBoundingClientRect();
77+
return [width / 2, height / 2];
78+
}, []);
79+
80+
const getEdgeProximity = useCallback((el, x, y) => {
81+
const [cx, cy] = getCenterOfElement(el);
82+
const dx = x - cx;
83+
const dy = y - cy;
84+
let kx = Infinity;
85+
let ky = Infinity;
86+
if (dx !== 0) kx = cx / Math.abs(dx);
87+
if (dy !== 0) ky = cy / Math.abs(dy);
88+
return Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);
89+
}, [getCenterOfElement]);
90+
91+
const getCursorAngle = useCallback((el, x, y) => {
92+
const [cx, cy] = getCenterOfElement(el);
93+
const dx = x - cx;
94+
const dy = y - cy;
95+
if (dx === 0 && dy === 0) return 0;
96+
const radians = Math.atan2(dy, dx);
97+
let degrees = radians * (180 / Math.PI) + 90;
98+
if (degrees < 0) degrees += 360;
99+
return degrees;
100+
}, [getCenterOfElement]);
101+
102+
const handlePointerMove = useCallback((e) => {
103+
const card = cardRef.current;
104+
if (!card) return;
105+
106+
const rect = card.getBoundingClientRect();
107+
const x = e.clientX - rect.left;
108+
const y = e.clientY - rect.top;
109+
110+
const edge = getEdgeProximity(card, x, y);
111+
const angle = getCursorAngle(card, x, y);
112+
113+
card.style.setProperty('--edge-proximity', `${(edge * 100).toFixed(3)}`);
114+
card.style.setProperty('--cursor-angle', `${angle.toFixed(3)}deg`);
115+
}, [getEdgeProximity, getCursorAngle]);
116+
117+
useEffect(() => {
118+
if (!animated || !cardRef.current) return;
119+
const card = cardRef.current;
120+
const angleStart = 110;
121+
const angleEnd = 465;
122+
card.classList.add('sweep-active');
123+
card.style.setProperty('--cursor-angle', `${angleStart}deg`);
124+
125+
const stops = [];
126+
stops.push(animateValue({ duration: 500, onUpdate: v => card.style.setProperty('--edge-proximity', v) }));
127+
stops.push(animateValue({ ease: easeInCubic, duration: 1500, end: 50, onUpdate: v => {
128+
card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`);
129+
}}));
130+
stops.push(animateValue({ ease: easeOutCubic, delay: 1500, duration: 2250, start: 50, end: 100, onUpdate: v => {
131+
card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`);
132+
}}));
133+
stops.push(animateValue({ ease: easeInCubic, delay: 2500, duration: 1500, start: 100, end: 0,
134+
onUpdate: v => card.style.setProperty('--edge-proximity', v),
135+
onEnd: () => card.classList.remove('sweep-active'),
136+
}));
137+
138+
return () => {
139+
stops.forEach(stop => stop?.());
140+
card.classList.remove('sweep-active');
141+
};
142+
}, [animated]);
143+
144+
const glowVars = buildGlowVars(glowColor, glowIntensity);
145+
146+
return (
147+
<div
148+
ref={cardRef}
149+
onPointerMove={handlePointerMove}
150+
className={`border-glow-card ${className}`}
151+
style={{
152+
'--card-bg': backgroundColor,
153+
'--edge-sensitivity': edgeSensitivity,
154+
'--border-radius': `${borderRadius}px`,
155+
'--glow-padding': `${glowRadius}px`,
156+
'--cone-spread': coneSpread,
157+
'--fill-opacity': fillOpacity,
158+
...glowVars,
159+
...buildGradientVars(colors),
160+
}}
161+
>
162+
<span className="edge-light" />
163+
<div className="border-glow-inner">
164+
{children}
165+
</div>
166+
</div>
167+
);
168+
};
169+
170+
export default BorderGlow;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#lights {
2+
width: 100%;
3+
height: 100%;
4+
overflow: hidden;
5+
position: absolute;
6+
top: 0;
7+
left: 0;
8+
z-index: 0; /* Behind all content in the hero section! */
9+
pointer-events: none; /* Make sure users can click elements over it */
10+
opacity: 0.45; /* Soften the light beams for better foreground text readability */
11+
}
12+
13+
#lights canvas {
14+
width: 100%;
15+
height: 100%;
16+
display: block;
17+
}

0 commit comments

Comments
 (0)