Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 32 additions & 17 deletions app/community/_content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
import Image from "next/image";
import { useEffect, useMemo, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { Loader2, LogIn, RefreshCw, Send, TrendingUp } from "lucide-react";
import { Loader2, LogIn, RefreshCw, Send, TrendingUp, Clock, Heart, Zap } from "lucide-react";
import { LocalizedLink } from "@/components/i18n/localized-link";
import { useCommunityFeed } from "@/lib/swr";
import { useUser } from "@/lib/auth/use-user";
import { useI18n } from "@/lib/i18n/context";
import { SORT_OPTIONS, type SortMethod } from "@/lib/community/leaderboard";
import { SORT_OPTIONS_DEF, type SortMethod, type SortOptionDef } from "@/lib/community/leaderboard";

Check warning on line 11 in app/community/_content.tsx

View workflow job for this annotation

GitHub Actions / Lint

'SortOptionDef' is defined but never used
import { CommunityStatsCard } from "@/components/community/community-stats-card";

// Icon mapping for sort options
const SORT_ICONS = {
TrendingUp: TrendingUp,
Clock: Clock,
Heart: Heart,
Zap: Zap,
} as const;

const PAGE_SIZE = 12;

function formatDate(iso: string, locale: "zh" | "en"): string {
Expand Down Expand Up @@ -163,21 +171,27 @@

{/* Sort Controls */}
<div className="mb-6 flex flex-wrap gap-2">
{SORT_OPTIONS.map((option) => (
<button
key={option.value}
onClick={() => setSortMethod(option.value)}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm transition-colors ${
sortMethod === option.value
? "bg-foreground text-background"
: "border border-border hover:border-foreground"
}`}
title={option.description}
>
{option.icon}
{option.label}
</button>
))}
{SORT_OPTIONS_DEF.map((option) => {
const Icon = SORT_ICONS[option.iconName];
const label = locale === "zh" ? option.labelZh : option.labelEn;
const description = locale === "zh" ? option.descriptionZh : option.descriptionEn;

return (
<button
key={option.value}
onClick={() => setSortMethod(option.value)}
className={`flex items-center gap-2 px-3 py-1.5 text-sm transition-colors ${
sortMethod === option.value
? "bg-foreground text-background"
: "border border-border hover:border-foreground"
}`}
title={description}
>
<Icon className="w-4 h-4" />
{label}
</button>
);
})}
</div>

{error && (
Expand Down Expand Up @@ -358,6 +372,7 @@
</div>
</div>
)}
</div>
</div>
</section>
);
Expand Down
117 changes: 72 additions & 45 deletions app/guides/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ import { Header } from "@/components/layout/header";
import { Footer } from "@/components/layout/footer";
import { LocalizedLink } from "@/components/i18n/localized-link";
import { styleGuides } from "@/lib/seo/style-guides";
import { BookOpen, ArrowRight } from "lucide-react";
import { BookOpen, ArrowRight, Sparkles, History } from "lucide-react";

// Color scheme for each guide
const guideColors: Record<string, string> = {
neumorphism: "from-rose-500/20 to-orange-500/20",
"minimalist-flat": "from-zinc-400/20 to-slate-500/20",
glassmorphism: "from-blue-500/20 to-purple-500/20",
"neo-brutalism": "from-yellow-400/20 to-pink-500/20",
editorial: "from-stone-400/20 to-amber-500/20",
"cyber-wafuu": "from-red-500/20 to-cyan-500/20",
};

export const metadata: Metadata = {
title: "Design Style Guides - StyleKit",
Expand Down Expand Up @@ -55,54 +65,71 @@ export default function GuidesPage() {
<section className="border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{guides.map((guide) => (
<LocalizedLink
key={guide.slug}
href={`/guides/${guide.slug}`}
className="group border border-border rounded-lg p-6 hover:border-foreground hover:shadow-sm hover:bg-muted/2 transition-all"
>
<div className="flex items-start justify-between mb-3">
<div>
<h3 className="text-xl font-bold mb-1 group-hover:underline">
{guide.nameEn}
</h3>
{guide.name !== guide.nameEn && (
<p className="text-sm text-muted">{guide.name}</p>
)}
{guides.map((guide) => {
const colorScheme = guideColors[guide.slug] || "from-gray-400/20 to-gray-500/20";

return (
<LocalizedLink
key={guide.slug}
href={`/guides/${guide.slug}`}
className="group relative border border-border rounded-xl overflow-hidden hover:border-foreground hover:shadow-lg transition-all"
>
{/* Gradient header */}
<div className={`h-24 bg-gradient-to-br ${colorScheme} relative`}>
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/50 to-transparent" />
<div className="absolute bottom-3 left-5 right-5 flex items-end justify-between">
<div>
<h3 className="text-xl font-bold mb-0.5 group-hover:text-accent transition-colors">
{guide.nameEn}
</h3>
{guide.name !== guide.nameEn && (
<p className="text-sm text-muted">{guide.name}</p>
)}
</div>
<div className="p-2 bg-background/80 backdrop-blur-sm rounded-full opacity-0 group-hover:opacity-100 transition-opacity">
<ArrowRight className="w-4 h-4" />
</div>
</div>
</div>
<ArrowRight className="w-5 h-5 text-muted group-hover:translate-x-1 transition-transform" />
</div>

<div className="p-5">
<p className="text-sm text-muted leading-relaxed mb-4 line-clamp-2">
{guide.descriptionEn}
</p>

<p className="text-sm text-muted leading-relaxed mb-4 line-clamp-2">
{guide.descriptionEn}
</p>
{/* Influenced by tags */}
{guide.influencedBy && guide.influencedBy.length > 0 && (
<div className="flex items-center gap-2 mb-4">
<History className="w-3.5 h-3.5 text-muted shrink-0" />
<div className="flex flex-wrap gap-1.5">
{guide.influencedBy.map((style) => (
<span
key={style}
className="text-[10px] px-2 py-0.5 bg-zinc-100 dark:bg-zinc-800 text-muted rounded-full"
>
{style}
</span>
))}
</div>
</div>
)}

{guide.influencedBy && guide.influencedBy.length > 0 && (
<div className="flex flex-wrap gap-1 mb-3">
{guide.influencedBy.map((style) => (
<span
key={style}
className="text-xs px-2 py-0.5 bg-muted/20 text-muted rounded"
>
{style}
</span>
))}
<div className="flex items-center justify-between pt-3 border-t border-border/50">
<div className="flex items-center gap-3 text-xs text-muted">
<span className="inline-flex items-center gap-1">
<Sparkles className="w-3 h-3" />
{guide.useCases.length} use case{guide.useCases.length !== 1 ? "s" : ""}
</span>
<span>•</span>
<span>
{guide.references.length} reference{guide.references.length !== 1 ? "s" : ""}
</span>
</div>
</div>
</div>
)}

<div className="flex items-center gap-2 text-xs text-muted">
<span>
{guide.useCases.length} use case
{guide.useCases.length !== 1 ? "s" : ""}
</span>
<span>•</span>
<span>
{guide.references.length} reference
{guide.references.length !== 1 ? "s" : ""}
</span>
</div>
</LocalizedLink>
))}
</LocalizedLink>
);
})}
</div>
</div>
</section>
Expand Down
31 changes: 22 additions & 9 deletions app/recipes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,35 @@ export default function RecipesPage() {
</div>

{/* Hero */}
<section className="py-12 md:py-20 border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-medium mb-4">
<section className="relative py-16 md:py-24 border-b border-border overflow-hidden">
{/* Decorative gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 via-transparent to-blue-500/5 pointer-events-none" />

<div className="relative max-w-7xl mx-auto px-4 sm:px-6 md:px-12">
<div className="inline-flex items-center gap-2 px-3 py-1 mb-6 text-xs font-medium uppercase tracking-wider border border-border rounded-full bg-background/80">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
Style + Layout + Animation
</div>

<h1 className="text-4xl md:text-5xl lg:text-6xl font-semibold tracking-tight mb-5">
Design Recipes
</h1>
<p className="text-xl text-muted max-w-2xl mb-8">
<p className="text-lg md:text-xl text-muted max-w-2xl mb-8 leading-relaxed">
Curated combinations of visual styles, layouts, and animations.
Each recipe is optimized for a specific use case and includes
reasoning for why it works.
</p>
<div className="flex items-center gap-4 text-sm text-muted">
<span className="px-3 py-1 border border-border">
{allRecipes.length} Recipes

<div className="flex flex-wrap items-center gap-3 text-sm">
<span className="inline-flex items-center gap-2 px-4 py-2 border border-border bg-background/80 rounded-lg">
<span className="w-5 h-5 rounded bg-gradient-to-br from-purple-500 to-pink-500" />
<span className="font-medium">{allRecipes.length}</span>
<span className="text-muted">Recipes</span>
</span>
<span className="px-3 py-1 border border-border">
{allRecipes.filter((r) => r.featured).length} Featured
<span className="inline-flex items-center gap-2 px-4 py-2 border border-amber-500/30 bg-amber-500/5 rounded-lg">
<span className="w-5 h-5 rounded bg-gradient-to-br from-amber-400 to-orange-500" />
<span className="font-medium">{allRecipes.filter((r) => r.featured).length}</span>
<span className="text-muted">Featured</span>
</span>
</div>
</div>
Expand Down
109 changes: 71 additions & 38 deletions components/community/community-stats-card.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Heart, Eye, Share2, TrendingUp, Clock, Award } from "lucide-react";
"use client";

import { Heart, Eye, TrendingUp, Clock, Award, Users } from "lucide-react";
import { useI18n } from "@/lib/i18n/context";

export interface CommunityStats {
totalSubmissions: number;
Expand All @@ -17,62 +20,92 @@ interface CommunityStatsCardProps {
}

export function CommunityStatsCard({ stats }: CommunityStatsCardProps) {
const { locale } = useI18n();
const isZh = locale === "zh";

const statItems = [
{
icon: <Award className="w-5 h-5" />,
label: "Total Styles",
icon: Award,
label: isZh ? "社区风格" : "Community Styles",
value: stats.totalSubmissions.toLocaleString(),
color: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
gradient: "from-blue-500/20 to-indigo-500/20",
iconColor: "text-blue-500",
},
{
icon: <TrendingUp className="w-5 h-5" />,
label: "Contributors",
icon: Users,
label: isZh ? "贡献者" : "Contributors",
value: stats.totalCollaborators.toLocaleString(),
color: "bg-purple-500/10 text-purple-600 dark:text-purple-400",
gradient: "from-purple-500/20 to-pink-500/20",
iconColor: "text-purple-500",
},
{
icon: <Clock className="w-5 h-5" />,
label: "This Month",
icon: Clock,
label: isZh ? "本月新增" : "This Month",
value: stats.recentSubmissions.toLocaleString(),
color: "bg-green-500/10 text-green-600 dark:text-green-400",
gradient: "from-emerald-500/20 to-teal-500/20",
iconColor: "text-emerald-500",
},
];

return (
<div className="border-b border-border">
<div className="border-b border-border bg-gradient-to-b from-zinc-50/50 to-transparent dark:from-zinc-900/50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12 py-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
{statItems.map((item, idx) => (
<div
key={idx}
className={`${item.color} border border-current/20 rounded-lg p-4 flex items-center gap-3`}
>
{item.icon}
<div>
<div className="text-xs font-semibold opacity-70">{item.label}</div>
<div className="text-2xl font-bold">{item.value}</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
{statItems.map((item, idx) => {
const Icon = item.icon;
return (
<div
key={idx}
className={`relative overflow-hidden rounded-xl bg-gradient-to-br ${item.gradient}
border border-border/50 p-5 transition-all hover:scale-[1.02] hover:shadow-lg`}
>
<div className="flex items-center gap-4">
<div className={`p-2.5 rounded-lg bg-background/80 ${item.iconColor}`}>
<Icon className="w-5 h-5" />
</div>
<div>
<div className="text-xs font-medium text-muted uppercase tracking-wider">
{item.label}
</div>
<div className="text-2xl font-bold tracking-tight mt-0.5">
{item.value}
</div>
</div>
</div>
</div>
</div>
))}
);
})}
</div>

{/* Featured Style */}
{stats.topStyle && (
<div className="border border-border rounded-lg p-4 bg-background/50 backdrop-blur-sm">
<div className="flex items-center gap-2 mb-2 text-xs font-semibold uppercase tracking-wider text-muted">
<TrendingUp className="w-4 h-4" />
Featured This Month
</div>
<h3 className="text-lg font-semibold mb-3">{stats.topStyle.title}</h3>
<div className="flex items-center justify-between">
<div className="text-sm text-muted">by {stats.topStyle.author}</div>
<div className="flex items-center gap-4 text-sm text-muted">
<div className="flex items-center gap-1">
<Heart className="w-4 h-4" />
{stats.topStyle.likes}
<div className="relative overflow-hidden rounded-xl border border-border bg-background/80 backdrop-blur-sm p-5">
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-amber-500/10 to-transparent rounded-bl-full" />

<div className="relative">
<div className="flex items-center gap-2 mb-3">
<TrendingUp className="w-4 h-4 text-amber-500" />
<span className="text-xs font-semibold uppercase tracking-wider text-amber-600 dark:text-amber-400">
{isZh ? "本月精选" : "Featured This Month"}
</span>
</div>

<h3 className="text-lg font-semibold mb-3">{stats.topStyle.title}</h3>

<div className="flex items-center justify-between">
<div className="text-sm text-muted">
{isZh ? "作者:" : "by "}{stats.topStyle.author}
</div>
<div className="flex items-center gap-1">
<Eye className="w-4 h-4" />
{stats.topStyle.views}
<div className="flex items-center gap-4 text-sm">
<div className="flex items-center gap-1.5 text-rose-500">
<Heart className="w-4 h-4" />
<span className="font-medium">{stats.topStyle.likes}</span>
</div>
<div className="flex items-center gap-1.5 text-muted">
<Eye className="w-4 h-4" />
<span>{stats.topStyle.views}</span>
</div>
</div>
</div>
</div>
Expand Down
Loading
Loading