diff --git a/Skills.md b/Skills.md
new file mode 100644
index 0000000..c7c2b11
--- /dev/null
+++ b/Skills.md
@@ -0,0 +1,171 @@
+# SKILLS.md — Project Orbit / DevNation Frontend
+
+> **Project context:** This is the frontend of Project Orbit, a developer club platform for DevNation.
+> Built with: **Next.js 16 (App Router) · Tailwind CSS · Framer Motion · Lucide React**
+> Minimum Node.js version: **18.18.0+** (required by Next.js 16)
+
+---
+
+## ⚠️ CRITICAL: DO NOT TOUCH
+
+The following are **strictly off-limits** for all contributors:
+
+### Backend & API
+- **Do not modify, refactor, or delete any backend files, routes, or server-side logic.**
+- **Do not alter any API contracts** — endpoint paths, request shapes, response shapes, HTTP methods, and status codes must remain exactly as defined.
+- **Do not add, remove, or rename API parameters** without explicit approval from the backend owner.
+- **Do not introduce new API calls** to endpoints that haven't been defined by the backend team.
+- If you believe a backend change is necessary, open a discussion/issue and wait for backend owner sign-off before touching anything.
+
+> Violating any of the above will break the integration between frontend and backend and will require a rollback.
+
+---
+
+## ✅ Frontend Contribution Guidelines
+
+### Write Highly Customizable Code
+
+All frontend code contributed to this project must be **modular, prop-driven, and easy to extend**. Follow these rules:
+
+#### 1. Components must be prop-driven
+Every component should accept props for any value that could reasonably vary — text, colors, sizes, icons, callbacks, visibility toggles.
+
+```tsx
+// ❌ Bad — hardcoded values
+export function StatCard() {
+ return
+ );
+}
+```
+
+#### 2. Use TypeScript interfaces for all props
+Define a clear `interface` or `type` for every component's props. No implicit `any` types.
+
+#### 3. Provide sensible defaults
+Use default parameter values so components work out of the box but remain overridable.
+
+#### 4. Avoid magic numbers and hardcoded strings
+Use constants, config objects, or Tailwind theme tokens instead:
+
+```tsx
+// ❌ Bad
+
+
+// ✅ Good — defined in a shared config or Tailwind class
+
+```
+
+#### 5. Keep components single-responsibility
+One component = one job. Split large components into smaller composable pieces.
+
+#### 6. Export everything that could be reused
+If it might be useful to another page or contributor, export it from the component file.
+
+---
+
+## 🎨 Aesthetic Rules (CS_ARCHIVE_V1.0)
+
+All UI must conform to the established brutalist-terminal aesthetic. Do not deviate.
+
+| Token | Value |
+|---|---|
+| Background | Near-black (`#0a0a0a` or equivalent) |
+| Primary font | `Bebas Neue` (display headers) |
+| UI font | `IBM Plex Mono` (labels, body, data) |
+| Accent | Status green only (`#22c55e` or Tailwind `green-500`) |
+| Palette | Strictly monochrome + single accent |
+| Borders | Dashed or dotted, zero border-radius |
+| Cards | Polaroid-style with physical stamp overlays |
+
+> Do not introduce new colors, fonts, rounded corners, or gradients without a design review.
+
+---
+
+## 📁 File & Folder Conventions
+
+```
+/components → Reusable UI components (must follow prop-driven rules above)
+/app → Next.js 16 App Router pages, layouts, and Route Handlers
+/lib → Utility functions and shared helpers
+/types → Shared TypeScript types and interfaces
+/constants → App-wide constants (no magic numbers elsewhere)
+/hooks → Custom React hooks
+/proxy.ts → Next.js 16 network boundary config (replaces middleware for proxy rules)
+```
+
+- Name component files in `PascalCase`: `StatCard.tsx`, `MemberRow.tsx`
+- Name utility/hook files in `camelCase`: `useActivityFeed.ts`, `formatDate.ts`
+- Co-locate component-specific styles/logic with the component file
+
+---
+
+## ⚡ Next.js 16 — Key Rules for Contributors
+
+Next.js 16 introduces breaking changes and new patterns. All contributors **must** follow these:
+
+### Caching — opt-in only
+Next.js 16 removed implicit caching. **All routes are dynamic by default.** To cache, you must explicitly opt in using the new `Cache Components` API and `use cache` directive.
+
+```tsx
+// ✅ Next.js 16 — explicit opt-in caching
+"use cache";
+
+export default async function MemberStats() {
+ const data = await fetchStats();
+ return ;
+}
+```
+
+Do **not** rely on the old `fetch` auto-caching behavior from Next.js 14 — it no longer works.
+
+### Middleware → proxy.ts
+Next.js 16 replaces Middleware with `proxy.ts` for defining network boundary rules. Do not create or edit `middleware.ts` — use `proxy.ts` at the project root instead.
+
+### React Compiler is stable
+The React Compiler (automatic memoization) is stable in Next.js 16. **Do not manually add `useMemo` or `useCallback`** unless you have a specific reason — the compiler handles it. Unnecessary manual memoization will conflict.
+
+### React 19.2 features available
+The App Router now ships with React 19.2. You can use:
+- `useEffectEvent` — extract non-reactive logic from Effects
+- `Activity` — hide UI with `display: none` while preserving state
+- View Transitions API — animate between navigations
+
+### Turbopack is the default bundler
+Turbopack is now the default for both `next dev` and `next build`. Do not add Webpack plugins or custom Webpack config — raise it as a discussion first.
+
+### Upgrading (if not already on v16)
+```bash
+npx @next/codemod@latest upgrade latest
+# or manually:
+npm install next@latest react@latest react-dom@latest eslint-config-next@latest
+```
+
+---
+
+## 🔄 Workflow
+
+1. **Pull latest** before starting any work.
+2. **Work on a feature branch** — never commit directly to `main`.
+3. **Do not merge your own PRs** — at least one other contributor must review.
+4. **Test your component in isolation** before integrating it into a page.
+5. If you're unsure whether something touches the backend boundary → **ask first**.
+
+---
+
+*Last updated by: Aimaan — Project Orbit frontend lead · Updated for Next.js 16*
diff --git a/app/globals.css b/app/globals.css
index 99eb7a9..90a37aa 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,99 +1,130 @@
@import "tailwindcss";
-@theme {
- --font-mono: "Geist Mono", "Courier New", monospace;
- --color-tactical-black: #000000;
- --color-tactical-white: #ffffff;
+/* ── ORBIT Design System: CS_ARCHIVE_V1.0 ── */
+
+@theme inline {
+ /* Colors */
+ --color-bg: #0a0a0a;
+ --color-surface: #111111;
+ --color-surface-2: #1a1a1a;
+ --color-surface-3: #2a2a2a;
+ --color-accent: #22c55e;
+ --color-text: #ededed;
+ --color-text-muted: #888888;
+ --color-border: #333333;
+ --color-white: #ffffff;
+
+ /* Fonts */
+ --font-heading: var(--font-bebas-neue);
+ --font-mono: var(--font-ibm-plex-mono);
}
-:root {
- --background: #000000;
- --foreground: #ffffff;
+/* ── Base Reset ── */
+
+* {
+ border-radius: 0 !important;
}
body {
- background: var(--background);
- color: var(--foreground);
- font-family: var(--font-mono);
- overflow-x: hidden;
-}
-
-/* Hide number input spin buttons */
-input::-webkit-outer-spin-button,
-input::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-input[type=number] {
- -moz-appearance: textfield;
-}
-
-/* Tactical Archive Effects */
-
-.scanlines {
- position: relative;
- overflow: hidden;
-}
-
-.scanlines::after {
- content: " ";
- display: block;
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- background: linear-gradient(
- rgba(18, 16, 16, 0) 50%,
- rgba(0, 0, 0, 0.25) 50%
- ),
- linear-gradient(
- 90deg,
- rgba(255, 0, 0, 0.06),
- rgba(0, 255, 0, 0.02),
- rgba(0, 255, 0, 0.06)
- );
- z-index: 2;
- background-size: 100% 2px, 3px 100%;
- pointer-events: none;
-}
-
-.noise {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-image: url("https://grainy-gradients.vercel.app/noise.svg");
- opacity: 0.05;
- pointer-events: none;
- z-index: 100;
-}
-
-.tactical-border {
- border: 1px solid rgba(255, 255, 255, 0.2);
- position: relative;
-}
-
-.tactical-border::before {
- content: "";
- position: absolute;
- top: -1px;
- left: -1px;
- width: 10px;
- height: 10px;
- border-top: 2px solid white;
- border-left: 2px solid white;
-}
-
-.tactical-border::after {
- content: "";
- position: absolute;
- bottom: -1px;
- right: -1px;
- width: 10px;
- height: 10px;
- border-bottom: 2px solid white;
- border-right: 2px solid white;
+ background-color: var(--color-bg);
+ color: var(--color-text);
+ font-family: var(--font-mono), ui-monospace, monospace;
+}
+
+/* ── Dot Grid Background Pattern ── */
+
+.dot-grid-bg {
+ background-image: radial-gradient(circle, #333333 1px, transparent 1px);
+ background-size: 24px 24px;
+}
+
+/* ── Stamp Variants ── */
+
+.stamp-verified {
+ color: #22c55e;
+ border-color: #22c55e;
+}
+
+.stamp-confidential {
+ color: #f97316;
+ border-color: #f97316;
+}
+
+.stamp-urgent {
+ color: #ef4444;
+ border-color: #ef4444;
+}
+
+.stamp-restricted {
+ color: #eab308;
+ border-color: #eab308;
+}
+
+.stamp-archived {
+ color: #888888;
+ border-color: #888888;
+}
+
+/* ── Badge Variants ── */
+
+.badge-stable {
+ background-color: #22c55e;
+ color: #0a0a0a;
+}
+
+.badge-experimental {
+ background-color: #f97316;
+ color: #0a0a0a;
+}
+
+.badge-archived {
+ background-color: #888888;
+ color: #0a0a0a;
+}
+
+.badge-restricted {
+ background-color: #eab308;
+ color: #0a0a0a;
+}
+
+.badge-urgent {
+ background-color: #ef4444;
+ color: #ffffff;
+}
+
+/* ── Scrollbar Styling ── */
+
+::-webkit-scrollbar {
+ width: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background: #0a0a0a;
+}
+
+::-webkit-scrollbar-thumb {
+ background: #333333;
}
+
+::-webkit-scrollbar-thumb:hover {
+ background: #555555;
+}
+
+/* ── Selection ── */
+
+::selection {
+ background-color: #22c55e;
+ color: #0a0a0a;
+}
+
+/* ── Terminal Cursor Blink ── */
+
+@keyframes blink {
+ 0%, 50% { opacity: 1; }
+ 51%, 100% { opacity: 0; }
+}
+
+.cursor-blink {
+ animation: blink 1s step-end infinite;
+}
+
diff --git a/app/layout.tsx b/app/layout.tsx
index 5068e0a..e25a11a 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,21 +1,27 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Bebas_Neue, IBM_Plex_Mono } from "next/font/google";
+import { DotGridBackground } from "@/components/ui/DotGridBackground";
import "./globals.css";
import { Providers } from "./providers";
-const geistSans = Geist({
- variable: "--font-geist-sans",
+const bebasNeue = Bebas_Neue({
+ weight: "400",
+ variable: "--font-bebas-neue",
subsets: ["latin"],
+ display: "swap",
});
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
+const ibmPlexMono = IBM_Plex_Mono({
+ weight: ["400", "500", "600", "700"],
+ variable: "--font-ibm-plex-mono",
subsets: ["latin"],
+ display: "swap",
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "ORBIT — DevNation",
+ description:
+ "Terminal interface for the DevNation collective. Access encrypted project logs, member databases, and upcoming tactical events.",
};
export default function RootLayout({
@@ -26,9 +32,10 @@ export default function RootLayout({
return (
-
+
+ {children}
diff --git a/app/page.tsx b/app/page.tsx
index c8695bb..7af63eb 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,30 +1,262 @@
"use client";
-import { signOut, useSession } from "next-auth/react";
+import { motion } from "framer-motion";
+import { PageShell } from "@/components/layout/PageShell";
+import { ArchiveTag } from "@/components/ui/ArchiveTag";
+import { TerminalButton } from "@/components/ui/TerminalButton";
+import { SectionHeader } from "@/components/ui/SectionHeader";
+import { PolaroidCard } from "@/components/ui/PolaroidCard";
+import { StampLabel } from "@/components/ui/StampLabel";
+import { ArchiveBadge } from "@/components/ui/ArchiveBadge";
+import { RepoRow } from "@/components/ui/RepoRow";
-export default function Home() {
- const { data: session } = useSession();
+import { TerminalParallax } from "@/components/sections/TerminalParallax";
+
+const fadeUp = {
+ hidden: { opacity: 0, y: 20 },
+ visible: (i: number) => ({
+ opacity: 1,
+ y: 0,
+ transition: { delay: i * 0.1, duration: 0.5, ease: "easeOut" as const },
+ }),
+};
+
+const sampleRepos = [
+ {
+ name: "TACTICAL-GRID-OS",
+ description: "A lightweight dashboard for managing archive datasets",
+ stars: 194,
+ forks: 17,
+ status: "stable" as const,
+ },
+ {
+ name: "MEMBER-REGISTRY-V3",
+ description: "New member onboarding system with encrypted log-ins",
+ stars: 63,
+ forks: 5,
+ status: "experimental" as const,
+ },
+ {
+ name: "CLI-TOOLKIT-CORE",
+ description: "Custom terminal commands for common archival tasks",
+ stars: 87,
+ forks: 15,
+ status: "stable" as const,
+ },
+];
+export default function Home() {
return (
-
-
Dashboard
-
-
-
Welcome, {session?.user?.name || "User"}!
-
Your USN is: {session?.user?.usn || "Not set"}
-
Role: {session?.user?.role || "Unknown"}
-
-
-
- If you manually deleted your user record in Neon, your browser still holds the JWT session cookie! Click below to clear it and start fresh.
-
+
+ );
+}
diff --git a/components/ui/ArchiveBadge.tsx b/components/ui/ArchiveBadge.tsx
new file mode 100644
index 0000000..520aead
--- /dev/null
+++ b/components/ui/ArchiveBadge.tsx
@@ -0,0 +1,26 @@
+import type { BadgeVariant } from "@/types";
+
+interface ArchiveBadgeProps {
+ label: string;
+ variant?: BadgeVariant;
+ className?: string;
+}
+
+/**
+ * ArchiveBadge — Small inline status badge.
+ * Uses CSS classes from globals.css for variant-specific backgrounds.
+ * Always uppercase IBM Plex Mono.
+ */
+export function ArchiveBadge({
+ label,
+ variant = "stable",
+ className = "",
+}: ArchiveBadgeProps) {
+ return (
+
+ {label}
+
+ );
+}
diff --git a/components/ui/ArchiveTag.tsx b/components/ui/ArchiveTag.tsx
new file mode 100644
index 0000000..1de792b
--- /dev/null
+++ b/components/ui/ArchiveTag.tsx
@@ -0,0 +1,19 @@
+interface ArchiveTagProps {
+ label: string;
+ className?: string;
+}
+
+/**
+ * ArchiveTag — Small entry tag displayed above hero titles.
+ * Think of it like a classified-document stamp ID at the top of a page.
+ * Always uppercase, dashed border, IBM Plex Mono.
+ */
+export function ArchiveTag({ label, className = "" }: ArchiveTagProps) {
+ return (
+
+ {label}
+
+ );
+}
diff --git a/components/ui/DotGridBackground.tsx b/components/ui/DotGridBackground.tsx
new file mode 100644
index 0000000..aed8eb8
--- /dev/null
+++ b/components/ui/DotGridBackground.tsx
@@ -0,0 +1,13 @@
+/**
+ * DotGridBackground — Fixed full-viewport dot-grid texture overlay.
+ * Placed once in the root layout. pointer-events-none so it never
+ * blocks interaction with actual content underneath.
+ */
+export function DotGridBackground() {
+ return (
+
+ );
+}
diff --git a/components/ui/PolaroidCard.tsx b/components/ui/PolaroidCard.tsx
new file mode 100644
index 0000000..705a4d3
--- /dev/null
+++ b/components/ui/PolaroidCard.tsx
@@ -0,0 +1,48 @@
+import type { ReactNode } from "react";
+
+interface PolaroidCardProps {
+ title: string;
+ author: string;
+ date: string;
+ image: string;
+ children?: ReactNode;
+}
+
+/**
+ * PolaroidCard — White-bordered Polaroid-style project card.
+ * The thick white border creates the physical photo effect.
+ * Children slot is for StampLabel overlays.
+ */
+export function PolaroidCard({
+ title,
+ author,
+ date,
+ image,
+ children,
+}: PolaroidCardProps) {
+ return (
+
+ {/* Image region */}
+
+
+ {/* Stamp overlays */}
+ {children}
+
+
+ {/* Metadata strip — thicker bottom like a real Polaroid */}
+
+
+ {title}
+
+
+ AUTH: {author}
+ {date}
+
+
+
+ );
+}
diff --git a/components/ui/RepoRow.tsx b/components/ui/RepoRow.tsx
new file mode 100644
index 0000000..48fcf8c
--- /dev/null
+++ b/components/ui/RepoRow.tsx
@@ -0,0 +1,53 @@
+import { Play, Star, GitFork, ExternalLink } from "lucide-react";
+import { ArchiveBadge } from "@/components/ui/ArchiveBadge";
+import type { RepoData } from "@/types";
+
+interface RepoRowProps {
+ repo: RepoData;
+}
+
+/**
+ * RepoRow — Single repository list row.
+ * Horizontal layout: play icon → name + description → status badge → stats → link icon.
+ * Dashed bottom border for separation. Zero radius everywhere.
+ */
+export function RepoRow({ repo }: RepoRowProps) {
+ return (
+
+ {/* Play icon */}
+
+
+
+
+ {/* Repo info */}
+
+
+
+ {repo.name}
+
+
+
+ {repo.description && (
+
+ {repo.description}
+
+ )}
+
+
+ {/* Stats */}
+
+
+
+ {repo.stars}
+
+
+
+ {repo.forks}
+
+
+
+ {/* External link */}
+
+
+ );
+}
diff --git a/components/ui/SectionHeader.tsx b/components/ui/SectionHeader.tsx
new file mode 100644
index 0000000..a0cdce1
--- /dev/null
+++ b/components/ui/SectionHeader.tsx
@@ -0,0 +1,27 @@
+interface SectionHeaderProps {
+ label: string;
+ path?: string;
+}
+
+/**
+ * SectionHeader — Section title with optional breadcrumb-style path.
+ * Bebas Neue heading with a left vertical bar accent.
+ * Path is displayed in IBM Plex Mono as muted text on the right.
+ */
+export function SectionHeader({ label, path }: SectionHeaderProps) {
+ return (
+
+
+
+
+ {label}
+
+
+ {path && (
+
+ {path}
+
+ )}
+
+ );
+}
diff --git a/components/ui/StampLabel.tsx b/components/ui/StampLabel.tsx
new file mode 100644
index 0000000..9ce5e92
--- /dev/null
+++ b/components/ui/StampLabel.tsx
@@ -0,0 +1,29 @@
+import type { StampVariant } from "@/types";
+
+interface StampLabelProps {
+ label: string;
+ variant?: StampVariant;
+ rotate?: number;
+ className?: string;
+}
+
+/**
+ * StampLabel — Absolutely positioned rotated stamp overlay.
+ * Used on cards and sections to add a classified-document feel.
+ * Variant controls the color (matches CSS classes in globals.css).
+ */
+export function StampLabel({
+ label,
+ variant = "verified",
+ rotate = -6,
+ className = "",
+}: StampLabelProps) {
+ return (
+
+ {label}
+
+ );
+}
diff --git a/components/ui/TerminalButton.tsx b/components/ui/TerminalButton.tsx
new file mode 100644
index 0000000..8b9d3d2
--- /dev/null
+++ b/components/ui/TerminalButton.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { motion } from "framer-motion";
+import type { ButtonVariant } from "@/types";
+
+interface TerminalButtonProps {
+ label: string;
+ href?: string;
+ variant?: ButtonVariant;
+ onClick?: () => void;
+ className?: string;
+}
+
+/**
+ * TerminalButton — Sharp rectangular CTA button with dashed border.
+ * Renders as when `href` is provided, otherwise