diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index b78e3d2..c8386c0 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -7,6 +7,7 @@ import { Globe, Map } from "lucide-react"; import { Navbar } from "@/components/layout/navbar"; import { InstallBanner } from "@/components/layout/install-banner"; import { ParticleBg } from "@/components/layout/particle-bg"; +import { Hero } from "@/components/layout/hero"; import { GlobeView } from "@/components/globe/globe-view"; import { StatsPanel } from "@/components/dashboard/stats-panel"; import { ActivityTimeline } from "@/components/dashboard/activity-timeline"; @@ -45,6 +46,9 @@ export default function HomePage() {
+ {/* Hero Section */} + + {/* Section 1: Main Map View + Dashboard */}
diff --git a/app/api/v1/heartbeat/route.ts b/app/api/v1/heartbeat/route.ts index 4d48b49..d58f32b 100644 --- a/app/api/v1/heartbeat/route.ts +++ b/app/api/v1/heartbeat/route.ts @@ -8,7 +8,7 @@ import { incrementHourlyActivity, publishEvent, } from "@/lib/redis"; -import { validateApiKey } from "@/lib/auth/api-key"; +import { authenticateRequest } from "@/lib/auth/request"; import { getGeoLocation } from "@/lib/geo/ip-location"; import { heartbeatSchema } from "@/lib/validators/schemas"; @@ -22,19 +22,11 @@ function getClientIp(req: NextRequest): string { export async function POST(req: NextRequest) { try { - const authHeader = req.headers.get("authorization"); - if (!authHeader?.startsWith("Bearer ")) { - return NextResponse.json( - { error: "Missing or invalid authorization header" }, - { status: 401 } - ); - } - - const apiKey = authHeader.slice(7); - const claw = await validateApiKey(apiKey); - if (!claw) { - return NextResponse.json({ error: "Invalid API key" }, { status: 401 }); + const auth = await authenticateRequest(req); + if (auth instanceof NextResponse) { + return auth; } + const { claw } = auth; let bodyData = {}; try { diff --git a/app/api/v1/task/route.ts b/app/api/v1/task/route.ts index 9b9de89..292b12f 100644 --- a/app/api/v1/task/route.ts +++ b/app/api/v1/task/route.ts @@ -7,24 +7,16 @@ import { incrementHourlyActivity, publishEvent, } from "@/lib/redis"; -import { validateApiKey } from "@/lib/auth/api-key"; +import { authenticateRequest } from "@/lib/auth/request"; import { taskSchema } from "@/lib/validators/schemas"; export async function POST(req: NextRequest) { try { - const authHeader = req.headers.get("authorization"); - if (!authHeader?.startsWith("Bearer ")) { - return NextResponse.json( - { error: "Missing or invalid authorization header" }, - { status: 401 } - ); - } - - const apiKey = authHeader.slice(7); - const claw = await validateApiKey(apiKey); - if (!claw) { - return NextResponse.json({ error: "Invalid API key" }, { status: 401 }); + const auth = await authenticateRequest(req); + if (auth instanceof NextResponse) { + return auth; } + const { claw } = auth; const body = await req.json(); const parsed = taskSchema.safeParse(body); diff --git a/app/api/v1/token/route.ts b/app/api/v1/token/route.ts index 1b8d429..c346742 100644 --- a/app/api/v1/token/route.ts +++ b/app/api/v1/token/route.ts @@ -2,7 +2,8 @@ import { NextRequest, NextResponse } from "next/server"; import { sql } from "drizzle-orm"; import { db } from "@/lib/db"; import { invalidateTokenLeaderboardCache } from "@/lib/redis"; -import { authenticateRequest, getTodayDateString } from "@/lib/utils"; +import { authenticateRequest } from "@/lib/auth/request"; +import { getTodayDateString } from "@/lib/utils"; import { tokenSchema } from "@/lib/validators/schemas"; export async function POST(req: NextRequest) { diff --git a/app/api/v1/token/stats/route.ts b/app/api/v1/token/stats/route.ts index 10200c8..cbb5f49 100644 --- a/app/api/v1/token/stats/route.ts +++ b/app/api/v1/token/stats/route.ts @@ -2,7 +2,8 @@ import { NextRequest, NextResponse } from "next/server"; import { eq, and, gte, sql } from "drizzle-orm"; import { db } from "@/lib/db"; import { tokenUsage } from "@/lib/db/schema"; -import { authenticateRequest, getTodayDateString } from "@/lib/utils"; +import { authenticateRequest } from "@/lib/auth/request"; +import { getTodayDateString } from "@/lib/utils"; export async function GET(req: NextRequest) { try { diff --git a/app/globals.css b/app/globals.css index b2de9f5..9fe867c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -171,3 +171,80 @@ body { .maplibre-dark-popup .maplibregl-popup-close-button { display: none; } + +/* === Hero Animations === */ + +/* Fade in animation */ +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Fade in and slide up animation */ +@keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Glowing text pulse animation */ +@keyframes glow-pulse { + 0%, 100% { + text-shadow: + 0 0 10px rgba(0, 240, 255, 0.5), + 0 0 20px rgba(0, 240, 255, 0.3), + 0 0 40px rgba(0, 240, 255, 0.1); + } + 50% { + text-shadow: + 0 0 15px rgba(0, 240, 255, 0.6), + 0 0 30px rgba(0, 240, 255, 0.4), + 0 0 60px rgba(0, 240, 255, 0.2); + } +} + +/* Animation utility classes */ +.animate-fade-in { + animation: fade-in 0.6s ease-out forwards; +} + +.animate-fade-in-up { + animation: fade-in-up 0.6s ease-out forwards; +} + +/* Glowing text with pulse effect */ +.glow-text-pulse { + color: var(--text-primary); + animation: glow-pulse 3s ease-in-out infinite; +} + +/* Animation delays */ +.animation-delay-200 { + animation-delay: 0.2s; + opacity: 0; +} + +.animation-delay-400 { + animation-delay: 0.4s; + opacity: 0; +} + +/* Accessibility: Respect reduced motion preferences */ +@media (prefers-reduced-motion: reduce) { + .animate-fade-in, + .animate-fade-in-up, + .glow-text-pulse { + animation: none; + opacity: 1; + transform: none; + } +} diff --git a/components/layout/hero.tsx b/components/layout/hero.tsx new file mode 100644 index 0000000..6438e91 --- /dev/null +++ b/components/layout/hero.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { ArrowRight, Sparkles } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; + +export function Hero() { + const t = useTranslations("hero"); + + return ( +
+
+ {/* Badge */} + + {t("badge")} + + + {/* Main Title */} +

+ {t("title")} +

+ + {/* Subtitle */} +

+ {t("subtitle")} +

+ + {/* CTA Buttons */} + +
+
+ ); +} diff --git a/lib/auth/request.ts b/lib/auth/request.ts new file mode 100644 index 0000000..5272875 --- /dev/null +++ b/lib/auth/request.ts @@ -0,0 +1,24 @@ +import { NextRequest, NextResponse } from "next/server"; +import { validateApiKey } from "@/lib/auth/api-key"; + +/** + * Authenticate an API request with Bearer token + * Returns the claw object if authenticated, or a 401 NextResponse if not + */ +export async function authenticateRequest(req: NextRequest) { + const authHeader = req.headers.get("authorization"); + if (!authHeader?.startsWith("Bearer ")) { + return NextResponse.json( + { error: "Missing or invalid authorization header" }, + { status: 401 } + ); + } + + const apiKey = authHeader.slice(7); + const claw = await validateApiKey(apiKey); + if (!claw) { + return NextResponse.json({ error: "Invalid API key" }, { status: 401 }); + } + + return { claw }; +} diff --git a/lib/utils.ts b/lib/utils.ts index 82599ca..d4f417f 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,7 +1,5 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -import { NextRequest, NextResponse } from "next/server"; -import { validateApiKey } from "@/lib/auth/api-key"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -13,25 +11,3 @@ export function cn(...inputs: ClassValue[]) { export function getTodayDateString(): string { return new Date().toISOString().split("T")[0]; } - -/** - * Authenticate an API request with Bearer token - * Returns the claw object if authenticated, or a 401 NextResponse if not - */ -export async function authenticateRequest(req: NextRequest) { - const authHeader = req.headers.get("authorization"); - if (!authHeader?.startsWith("Bearer ")) { - return NextResponse.json( - { error: "Missing or invalid authorization header" }, - { status: 401 } - ); - } - - const apiKey = authHeader.slice(7); - const claw = await validateApiKey(apiKey); - if (!claw) { - return NextResponse.json({ error: "Invalid API key" }, { status: 401 }); - } - - return { claw }; -} diff --git a/messages/en.json b/messages/en.json index f95256a..a936283 100644 --- a/messages/en.json +++ b/messages/en.json @@ -3,6 +3,13 @@ "title": "OpenClaw Market - Global Claw Activity", "description": "Real-time visualization of AI agent activity worldwide" }, + "hero": { + "badge": "🦞 OpenClaw Market", + "title": "A Community Built for Claw Agents", + "subtitle": "Share your Agent prompts, skills, and tuning tips. Learn from the community and level up your AI workflow.", + "cta": "Get Started", + "secondary": "Learn More" + }, "navbar": { "brand": "OpenClaw Market", "globe": "3D Globe", diff --git a/messages/zh.json b/messages/zh.json index 2100e05..b2ceb82 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -3,6 +3,13 @@ "title": "OpenClaw Market - 全球龙虾活动", "description": "全球 AI 代理活动实时可视化" }, + "hero": { + "badge": "🦞 OpenClaw Market", + "title": "专为 Claw Agent 打造的社区", + "subtitle": "分享你的 Agent 提示词、技能和调教技巧。从社区学习,提升你的 AI 工作流。", + "cta": "立即开始", + "secondary": "了解更多" + }, "navbar": { "brand": "OpenClaw Market", "globe": "3D 地球",