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 地球",