Skip to content

Commit e6285d1

Browse files
committed
feat: Initialize Next.js frontend with a chat interface, UI components, and project dependencies.
1 parent 6fa7290 commit e6285d1

8 files changed

Lines changed: 190 additions & 14 deletions

File tree

frontend/app/chat/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { motion, AnimatePresence } from "framer-motion";
1717
import { Send, ShieldCheck, Terminal, AlertTriangle, Lock, ArrowLeft, ShieldAlert, ChevronDown } from "lucide-react";
1818
import CursorSpotlight from "@/components/ui/CursorSpotlight";
1919
import SystemStatusBadge from "@/components/ui/SystemStatusBadge";
20+
import BackToTop from "@/components/ui/BackToTop";
2021
import { useSystemStatus } from "@/hooks/useSystemStatus";
2122
import { useCouncil } from "@/hooks/useCouncil";
2223
import { useRateLimit } from "@/hooks/useRateLimit";
@@ -311,6 +312,9 @@ export default function ChatPage() {
311312
</motion.div>
312313
)}
313314
</AnimatePresence>
315+
316+
{/* Back To Top for Chat Container */}
317+
<BackToTop containerRef={chatContainerRef} />
314318
</div>
315319
);
316320
}

frontend/app/globals.css

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
@import "tailwindcss";
22

33
html {
4-
scroll-behavior: smooth;
4+
/* scroll-behavior: smooth; - Removed for Lenis fluid scroll */
5+
}
6+
7+
html.lenis,
8+
html.lenis body {
9+
height: auto;
10+
}
11+
12+
.lenis.lenis-smooth {
13+
scroll-behavior: auto !important;
14+
}
15+
16+
.lenis.lenis-smooth [data-lenis-prevent] {
17+
overscroll-behavior: contain;
18+
}
19+
20+
.lenis.lenis-stopped {
21+
overflow: hidden;
22+
}
23+
24+
.lenis.lenis-scrolling iframe {
25+
pointer-events: none;
526
}
627

728
:root {
@@ -27,11 +48,11 @@ body {
2748
.bg-grid-white {
2849
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke='rgb(255 255 255 / 0.04)'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e");
2950
}
30-
51+
3152
.glass-panel {
32-
@apply bg-white/5 backdrop-blur-2xl border-t border-white/10 shadow-[0_8px_32px_0_rgba(0,0,0,0.36)];
53+
@apply bg-white/5 backdrop-blur-2xl border-t border-white/10 shadow-[0_8px_32px_0_rgba(0, 0, 0, 0.36)];
3354
}
34-
55+
3556
.glass-card {
3657
@apply bg-gradient-to-br from-white/5 to-transparent backdrop-blur-xl border border-white/10 shadow-2xl transition-all duration-300;
3758
}
@@ -59,12 +80,14 @@ body {
5980
}
6081

6182
.custom-scrollbar::-webkit-scrollbar-thumb {
62-
background: #27272a; /* zinc-800 */
83+
background: #27272a;
84+
/* zinc-800 */
6385
border-radius: 9999px;
6486
}
6587

6688
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
67-
background: #3f3f46; /* zinc-700 */
89+
background: #3f3f46;
90+
/* zinc-700 */
6891
}
6992
}
7093

@@ -73,10 +96,12 @@ body {
7396
transform: scale(0.65);
7497
opacity: 0.35;
7598
}
99+
76100
50% {
77101
transform: scale(1);
78102
opacity: 0.6;
79103
}
104+
80105
100% {
81106
transform: scale(0.65);
82107
opacity: 0.35;
@@ -88,13 +113,14 @@ body {
88113
transform: scale(0.8);
89114
opacity: 0.2;
90115
}
116+
91117
50% {
92118
transform: scale(1.35);
93119
opacity: 0.45;
94120
}
121+
95122
100% {
96123
transform: scale(0.8);
97124
opacity: 0.2;
98125
}
99-
}
100-
126+
}

frontend/app/layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export const metadata: Metadata = {
1111
description: "A Production-Grade Multi-Layered Security System for LLMs.",
1212
};
1313

14+
import SmoothScroll from "@/components/ui/SmoothScroll";
15+
1416
export default function RootLayout({
1517
children,
1618
}: Readonly<{
@@ -21,6 +23,7 @@ export default function RootLayout({
2123
<body
2224
className={`${inter.variable} ${jetbrainsMono.variable} bg-[#020617] text-slate-200 antialiased selection:bg-cyan-500/30`}
2325
>
26+
<SmoothScroll />
2427
{children}
2528
<Analytics />
2629
</body>

frontend/app/page.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import CursorSpotlight from "@/components/ui/CursorSpotlight";
2121
import BreathingText from "@/components/landing/BreathingText";
2222
import SystemStatusBadge from "@/components/ui/SystemStatusBadge";
2323
import HeroBackground from "@/components/landing/HeroBackground";
24+
import BackToTop from "@/components/ui/BackToTop";
2425

2526
export default function Home() {
2627
return (
@@ -73,12 +74,13 @@ export default function Home() {
7374
</button>
7475
</Link>
7576

76-
<Link href="#pipeline">
77-
<button className="px-8 py-4 rounded-full border border-white/20 text-zinc-400 hover:text-white hover:border-white/50 transition-all flex items-center gap-2">
78-
<span>READ THE DOCS</span>
79-
<ArrowRight className="w-4 h-4" />
80-
</button>
81-
</Link>
77+
<button
78+
onClick={() => document.getElementById('pipeline')?.scrollIntoView({ behavior: 'smooth' })}
79+
className="px-8 py-4 rounded-full border border-white/20 text-zinc-400 hover:text-white hover:border-white/50 transition-all flex items-center gap-2"
80+
>
81+
<span>READ THE DOCS</span>
82+
<ArrowRight className="w-4 h-4" />
83+
</button>
8284
</div>
8385
</div>
8486

@@ -119,6 +121,7 @@ export default function Home() {
119121
</div>
120122
</footer>
121123

124+
<BackToTop />
122125
</main>
123126
);
124127
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client";
2+
3+
import { useState, useEffect, useCallback } from "react";
4+
import { motion, AnimatePresence } from "framer-motion";
5+
import { ArrowUp } from "lucide-react";
6+
7+
interface BackToTopProps {
8+
containerRef?: React.RefObject<HTMLElement | null>;
9+
threshold?: number;
10+
}
11+
12+
export default function BackToTop({ containerRef, threshold = 300 }: BackToTopProps) {
13+
const [isVisible, setIsVisible] = useState(false);
14+
15+
// Throttled scroll handler to prevent layout thrashing and stutter
16+
useEffect(() => {
17+
let ticking = false;
18+
19+
const handleScroll = () => {
20+
if (!ticking) {
21+
window.requestAnimationFrame(() => {
22+
let scrollTop = 0;
23+
if (containerRef?.current) {
24+
scrollTop = containerRef.current.scrollTop;
25+
} else {
26+
scrollTop = window.scrollY;
27+
}
28+
29+
setIsVisible(scrollTop > threshold);
30+
ticking = false;
31+
});
32+
ticking = true;
33+
}
34+
};
35+
36+
if (containerRef?.current) {
37+
const container = containerRef.current;
38+
container.addEventListener("scroll", handleScroll, { passive: true });
39+
return () => container.removeEventListener("scroll", handleScroll);
40+
} else {
41+
window.addEventListener("scroll", handleScroll, { passive: true });
42+
return () => window.removeEventListener("scroll", handleScroll);
43+
}
44+
}, [containerRef, threshold]);
45+
46+
const scrollToTop = () => {
47+
if (containerRef?.current) {
48+
containerRef.current.scrollTo({ top: 0, behavior: "smooth" });
49+
} else {
50+
// Use Lenis for window scroll if available, otherwise fallback
51+
// Since we can't easily access the Lenis instance here without context,
52+
// we'll rely on native smooth scroll which Lenis intercepts or handles.
53+
window.scrollTo({ top: 0, behavior: "smooth" });
54+
}
55+
};
56+
57+
return (
58+
<AnimatePresence>
59+
{isVisible && (
60+
<motion.button
61+
initial={{ opacity: 0, scale: 0.8, y: 20 }}
62+
animate={{ opacity: 1, scale: 1, y: 0 }}
63+
exit={{ opacity: 0, scale: 0.8, y: 20 }}
64+
whileHover={{ scale: 1.1 }}
65+
whileTap={{ scale: 0.9 }}
66+
onClick={scrollToTop}
67+
className="fixed bottom-6 right-6 z-50 p-3 rounded-full bg-white/10 backdrop-blur-md border border-white/20 text-white shadow-lg hover:bg-white/20 transition-colors"
68+
aria-label="Back to top"
69+
>
70+
<ArrowUp className="w-5 h-5" />
71+
</motion.button>
72+
)}
73+
</AnimatePresence>
74+
);
75+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
3+
import { useEffect, useLayoutEffect } from "react";
4+
import Lenis from "lenis";
5+
6+
export default function SmoothScroll() {
7+
// Use useLayoutEffect to ensure Lenis initializes before paint if possible,
8+
// but useEffect is generally safer for SSR. We'll stick to useEffect for Next.js
9+
// but ensure clean setup.
10+
useEffect(() => {
11+
const lenis = new Lenis({
12+
duration: 1.2,
13+
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // Exponential easing
14+
orientation: "vertical",
15+
gestureOrientation: "vertical",
16+
smoothWheel: true,
17+
wheelMultiplier: 1,
18+
touchMultiplier: 2,
19+
infinite: false,
20+
});
21+
22+
// Sync Lenis scroll with RAF
23+
function raf(time: number) {
24+
lenis.raf(time);
25+
requestAnimationFrame(raf);
26+
}
27+
28+
const rafId = requestAnimationFrame(raf);
29+
30+
return () => {
31+
cancelAnimationFrame(rafId);
32+
lenis.destroy();
33+
};
34+
}, []);
35+
36+
return null;
37+
}

frontend/package-lock.json

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"axios": "^1.13.2",
1616
"clsx": "^2.1.1",
1717
"framer-motion": "^12.23.24",
18+
"lenis": "^1.3.15",
1819
"lucide-react": "^0.554.0",
1920
"next": "16.0.3",
2021
"react": "19.2.0",

0 commit comments

Comments
 (0)