Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2db4aa3
feat: implement usage tracking API and display real-time platform sta…
Arjun544 Jun 5, 2026
df76c1c
Merge branch 'main' of https://github.com/Arjun544/flutter_init
Arjun544 Jun 13, 2026
2466911
Merge branch 'main' of https://github.com/Arjun544/flutter_init
Arjun544 Jun 13, 2026
eb30a5e
feat: add CLI
Arjun544 Jun 15, 2026
75a56d3
feat: implement animate-ui library and landing page components with m…
Arjun544 Jun 15, 2026
4c15564
feat: add landing page components including interactive navbar and no…
Arjun544 Jun 15, 2026
8b0c0c1
feat: enhance landing page statistics section with improved card stru…
Arjun544 Jun 16, 2026
7c776df
feat: implement FlutterInit CLI scaffolding tool and landing page com…
Arjun544 Jun 20, 2026
92b628a
chore: add border-beam, lucide-react, and motion dependencies to bun.…
Arjun544 Jun 20, 2026
9a9c494
feat: implement CLI core structure, project generator, and native con…
Arjun544 Jun 20, 2026
2e7a110
chore: exclude cli directory from TypeScript compilation
Arjun544 Jun 20, 2026
c6ee2ae
feat: add AvatarGroup and AvatarGroupTooltip components with motion a…
Arjun544 Jun 20, 2026
9427735
feat: implement landing page components including hero, feature showc…
Arjun544 Jun 20, 2026
2d25b35
feat: components cleanup
Arjun544 Jun 20, 2026
3946b62
feat: implement blog infrastructure, animate-ui primitives, and archi…
Arjun544 Jun 20, 2026
8b06f7b
bug: fixed FlutterInit CLI
Arjun544 Jun 20, 2026
8e08b2b
feat: implement blog infrastructure, including post routing, filterin…
Arjun544 Jun 20, 2026
5c1f200
feat: implement wizard shell UI and add NPM version badge to README
Arjun544 Jun 20, 2026
f2dfe2b
Merge branch 'main' into add_cli
Arjun544 Jun 20, 2026
578bf25
feat: add canonical URL to blog metadata for SEO optimization
Arjun544 Aug 10, 2026
734154c
Merge branch 'add_cli' of https://github.com/Arjun544/flutter_init in…
Arjun544 Aug 10, 2026
9f62d38
Added live code editor
Arjun544 Aug 10, 2026
49d6059
Added more blogs
Arjun544 Aug 10, 2026
6f8ecc2
Merge branch 'main' into add_cli
Arjun544 Aug 10, 2026
d75e627
Remove blog layout, page, and related components to streamline the bl…
Arjun544 Aug 10, 2026
3fef1c8
Merge branch 'main' into add_cli
Arjun544 Aug 10, 2026
8ea1434
Update README and related files to reflect the change from "Blog" to …
Arjun544 Aug 10, 2026
f85560a
Merge branch 'add_cli' of https://github.com/Arjun544/flutter_init in…
Arjun544 Aug 10, 2026
f6c7e53
Refactor WizardShell component: update button shadow style and clean …
Arjun544 Aug 10, 2026
02907d1
Fixed issues
Arjun544 Aug 10, 2026
32ccfad
Merge branch 'main' into add_cli
Arjun544 Aug 10, 2026
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
73 changes: 59 additions & 14 deletions app/blogs/components/FeaturedPost.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import type { Post } from '@/lib/blog/types'
import { BookOpen02Icon, Megaphone01Icon } from '@hugeicons/core-free-icons'
import { HugeiconsIcon } from '@hugeicons/react'
Expand All @@ -9,16 +10,41 @@ import { TagPill } from './TagPill'

interface FeaturedPostProps {
post: Post
/** Stretch to parent height (e.g. match adjacent stacked cards). */
fillHeight?: boolean
/** Hide tags, author, and featured chrome — title + description + CTA only. */
hideMeta?: boolean
}

export function FeaturedPost({ post }: FeaturedPostProps) {
export function FeaturedPost({
post,
fillHeight = false,
hideMeta = false,
}: FeaturedPostProps) {
const href = `/blogs/${post.slug.join('/')}`

return (
<article className="group relative overflow-hidden rounded-3xl border border-zinc-200/80 bg-white shadow-xs hover:border-zinc-300 transition-all duration-400">
<div className="grid grid-cols-1 lg:grid-cols-2">
{/* Image side */}
<div className="relative aspect-4/3 lg:aspect-auto overflow-hidden bg-linear-to-br from-zinc-100 to-zinc-50 border-b lg:border-b-0 lg:border-r border-zinc-100">
<article
className={cn(
'group relative overflow-hidden rounded-3xl border border-zinc-200/80 bg-white shadow-xs hover:border-zinc-300 transition-all duration-400',
fillHeight && 'h-full',
)}
>
<div
className={cn(
'grid grid-cols-1 lg:grid-cols-5',
fillHeight && 'h-full',
)}
>
{/* Image side — narrower than content */}
<div
className={cn(
'relative overflow-hidden bg-linear-to-br from-zinc-100 to-zinc-50 border-b lg:border-b-0 lg:border-r border-zinc-100 lg:col-span-2',
fillHeight
? 'min-h-48 h-full lg:min-h-0'
: 'aspect-4/3 lg:aspect-auto',
)}
>
{post.coverImage ? (
<Image
src={post.coverImage}
Expand All @@ -28,7 +54,12 @@ export function FeaturedPost({ post }: FeaturedPostProps) {
className="object-cover transition-transform duration-700 group-hover:scale-105"
/>
) : (
<div className="flex h-full min-h-70 items-center justify-center">
<div
className={cn(
'flex h-full items-center justify-center',
!fillHeight && 'min-h-70',
)}
>
<div className="text-center px-8">
<div
className="mx-auto mb-4 size-16 rounded-2xl flex items-center justify-center shadow-lg"
Expand All @@ -50,7 +81,7 @@ export function FeaturedPost({ post }: FeaturedPostProps) {
)}

{/* Featured badge */}
{post.featured && (
{!hideMeta && post.featured && (
<div className="absolute left-4 top-4 flex items-center gap-1.5 rounded-full bg-white/90 backdrop-blur-sm px-3 py-1 text-[11px] font-bold text-zinc-800 border border-zinc-200/80 shadow-sm">
<span className="size-1.5 rounded-full bg-primary" />
Featured
Expand All @@ -59,20 +90,34 @@ export function FeaturedPost({ post }: FeaturedPostProps) {
</div>

{/* Content side */}
<div className="flex flex-col justify-center p-8 lg:p-10">
<TagPill kind={post.kind} category={post.category} className="mb-4" />
<div className="flex flex-col justify-center p-8 lg:col-span-3 lg:p-10">
{!hideMeta && (
<TagPill kind={post.kind} category={post.category} className="mb-4" />
)}

<h2 className="mb-3 text-2xl lg:text-3xl font-bold text-zinc-900 leading-snug tracking-tight group-hover:text-primary transition-colors duration-200">
<h2
className={cn(
'mb-3 font-bold leading-snug tracking-tight text-zinc-900 transition-colors duration-200 group-hover:text-primary',
hideMeta ? 'text-xl lg:text-2xl' : 'text-2xl lg:text-3xl',
)}
>
{post.title}
</h2>

<p className="mb-6 text-[15px] text-zinc-500 leading-relaxed line-clamp-3">
<p
className={cn(
'text-[15px] text-zinc-500 leading-relaxed line-clamp-3',
hideMeta ? 'mb-8' : 'mb-6',
)}
>
{post.description}
</p>

<div className="mb-6">
<AuthorByline post={post} />
</div>
{!hideMeta && (
<div className="mb-6">
<AuthorByline post={post} />
</div>
)}

<Button
asChild
Expand Down
104 changes: 69 additions & 35 deletions app/blogs/components/PostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AUTHORS } from '@/lib/blog/authors'
import type { Post } from '@/lib/blog/types'
import { HugeiconsIcon } from '@hugeicons/react'
import { BookOpen02Icon, Megaphone01Icon } from '@hugeicons/core-free-icons'
import { cn } from '@/lib/utils'

function formatDate(iso: string) {
return new Date(iso).toLocaleDateString('en-US', {
Expand All @@ -19,20 +20,37 @@ function formatDate(iso: string) {

interface PostCardProps {
post: Post
/** Stretch to parent height (e.g. equal split in a stacked column). */
fillHeight?: boolean
/** Hide tags, author, and date — title + description only. */
hideMeta?: boolean
}

export function PostCard({ post }: PostCardProps) {
export function PostCard({
post,
fillHeight = false,
hideMeta = false,
}: PostCardProps) {
const author = AUTHORS[post.author] ?? { name: post.author, avatar: '' }
const href = `/blogs/${post.slug.join('/')}`

return (
<motion.article
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
className="group relative flex flex-col overflow-hidden rounded-2xl border border-zinc-200/80 bg-white shadow-xs hover:shadow-md hover:border-zinc-300 transition-all duration-400"
className={cn(
'group relative flex flex-col overflow-hidden rounded-2xl border border-zinc-200/80 bg-white shadow-xs hover:shadow-md hover:border-zinc-300 transition-all duration-400',
fillHeight && 'h-full',
)}
>
<Link href={href} className="block flex-1">
<Link href={href} className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
{/* Cover image placeholder */}
<div className="mb-4 h-42 w-full overflow-hidden bg-linear-to-br from-zinc-100 to-zinc-50 border border-zinc-100">
<div
className={cn(
'w-full overflow-hidden bg-linear-to-br from-zinc-100 to-zinc-50 border border-zinc-100',
hideMeta ? 'mb-5' : 'mb-4',
fillHeight ? 'min-h-28 flex-1' : 'h-42',
)}
>
{post.coverImage ? (
<Image
src={post.coverImage}
Expand Down Expand Up @@ -64,40 +82,56 @@ export function PostCard({ post }: PostCardProps) {
)}
</div>

<TagPill kind={post.kind} category={post.category} className="mb-3 px-5" />

<h3 className="mb-2 text-[15px] px-5 font-semibold text-zinc-900 leading-snug line-clamp-2 group-hover:text-primary transition-colors duration-200">
{post.title}
</h3>
{!hideMeta && (
<TagPill kind={post.kind} category={post.category} className="mb-3 px-5" />
)}

<p className="text-[13px] text-zinc-500 leading-relaxed line-clamp-2 px-5">
{post.description}
</p>
<div
className={cn(
'w-full min-w-0 shrink-0 px-5',
hideMeta ? 'pb-8' : 'pb-4',
)}
>
<h3 className="mb-2 line-clamp-2 text-[15px] font-semibold leading-snug text-zinc-900 transition-colors duration-200 group-hover:text-primary">
{post.title}
</h3>
<p
className="overflow-hidden text-[13px] leading-relaxed text-zinc-500"
style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
}}
>
{post.description}
</p>
</div>
</Link>

{/* Footer */}
<div className="flex items-center gap-2.5 border-t border-zinc-100 px-5 py-3">
{author.avatar && (
<div className="relative size-6 shrink-0 overflow-hidden rounded-full ring-1 ring-zinc-200">
<Image
src={author.avatar}
alt={author.name}
fill
className="object-cover"
sizes="24px"
/>
</div>
)}
<span className="flex-1 truncate text-[12px] font-medium text-zinc-600">
{author.name}
</span>
<time className="shrink-0 text-[11px] text-zinc-400" dateTime={post.publishedAt}>
{formatDate(post.publishedAt)}
</time>
<span className="shrink-0 text-[11px] text-zinc-400">
· {post.readingTime}m
</span>
</div>
{!hideMeta && (
<div className="mt-auto flex items-center gap-2.5 border-t border-zinc-100 px-5 py-3">
{author.avatar && (
<div className="relative size-6 shrink-0 overflow-hidden rounded-full ring-1 ring-zinc-200">
<Image
src={author.avatar}
alt={author.name}
fill
className="object-cover"
sizes="24px"
/>
</div>
)}
<span className="flex-1 truncate text-[12px] font-medium text-zinc-600">
{author.name}
</span>
<time className="shrink-0 text-[11px] text-zinc-400" dateTime={post.publishedAt}>
{formatDate(post.publishedAt)}
</time>
<span className="shrink-0 text-[11px] text-zinc-400">
· {post.readingTime}m
</span>
</div>
)}
</motion.article>
)
}
107 changes: 107 additions & 0 deletions app/components/landing/FeaturedGuides.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import Link from "next/link"
import { getAllPosts } from "@/lib/blog/getAllPosts"
import type { Post } from "@/lib/blog/types"
import { FeaturedPost } from "@/app/blogs/components/FeaturedPost"
import { PostCard } from "@/app/blogs/components/PostCard"
import { Badge } from "@/components/ui/badge"
import { Blur } from "@/components/animate-ui/primitives/effects/blur"
import { KineticText } from "@/components/ui/kinetic-text"
import { ArrowRight01Icon } from "@hugeicons/core-free-icons"
import { HugeiconsIcon } from "@hugeicons/react"

const SIDE_SLUGS = [
"guides/ai-context/why-claude-and-agents-md",
"guides/ai-context/cursor-with-structured-ai-context",
] as const

function bySlug(posts: Post[], slugPath: string): Post | undefined {
return posts.find((post) => post.slug.join("/") === slugPath)
}

export async function FeaturedGuides() {
const allPosts = await getAllPosts()

// Same rule as /blogs: latest post marked featured: true
const featured = allPosts
.filter((post) => post.featured)
.sort(
(a, b) =>
new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime(),
)[0]

const featuredKey = featured?.slug.join("/")

const sidePosts = SIDE_SLUGS.map((slug) => bySlug(allPosts, slug))
.filter((post): post is Post => Boolean(post))
.filter((post) => post.slug.join("/") !== featuredKey)
.slice(0, 2)

if (!featured && sidePosts.length === 0) {
return null
}

return (
<section id="blogs" className="relative w-full overflow-hidden bg-zinc-50/50 py-24">
<div className="pointer-events-none absolute top-0 left-1/2 h-full w-full -translate-x-1/2 bg-[radial-gradient(circle_at_50%_0%,var(--color-primary)_0.03,transparent_50%)] opacity-5" />

<div className="relative z-10 mx-auto flex max-w-7xl flex-col gap-12 px-6 md:px-12">
<div className="flex flex-col items-center gap-4 text-center">
<Badge
variant="outline"
className="rounded-full border-primary/10 bg-primary/5 px-3 py-3 text-[11px] font-bold uppercase tracking-wider text-primary"
>
<span
aria-hidden="true"
className="mr-2 inline-flex size-1.5 animate-pulse rounded-full bg-primary"
/>
From the Blog
</Badge>
<Blur inView={true}>
<h2 className="text-4xl leading-[1.1] font-bold tracking-tight text-zinc-400 md:text-5xl lg:text-6xl">
Guides from{" "}
<KineticText
as="span"
text="FlutterInit"
className="pointer-events-auto font-extrabold tracking-wider text-primary"
/>
</h2>
</Blur>
<p className="max-w-2xl text-lg font-medium leading-relaxed text-zinc-500">
Featured updates, AI context deep-dives and tips.
</p>
</div>

{/*
lg: featured spans 2 rows; each side card is one row.
Featured height = card1 + gap + card2.
*/}
<div className="grid grid-cols-1 gap-5 lg:grid-cols-5 lg:grid-rows-2">
{featured && (
<div className="min-h-0 h-full lg:col-span-3 lg:row-span-2">
<FeaturedPost post={featured} fillHeight hideMeta />
</div>
)}
{sidePosts.map((post) => (
<div key={post.slug.join("/")} className="min-h-0 lg:col-span-2">
<PostCard post={post} fillHeight hideMeta />
</div>
))}
</div>

<div className="flex justify-center">
<Link
href="/blogs"
className="group inline-flex h-12 items-center gap-2 rounded-2xl border border-zinc-200 bg-white px-5 text-sm font-semibold text-zinc-700 shadow-xs transition-all duration-300 hover:border-zinc-300 hover:bg-zinc-50 hover:text-zinc-900"
>
Browse all posts
<HugeiconsIcon
icon={ArrowRight01Icon}
size={16}
className="text-zinc-400 transition-transform group-hover:translate-x-0.5 group-hover:text-zinc-700"
/>
</Link>
</div>
</div>
</section>
)
}
2 changes: 1 addition & 1 deletion app/components/landing/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function Footer() {
};

return (
<footer id="guides" className="w-full bg-white border-t border-zinc-200 pt-12 pb-8 overflow-hidden relative">
<footer className="w-full bg-white border-t border-zinc-200 pt-12 pb-8 overflow-hidden relative">
{/* Background decoration */}
<div className="absolute bottom-0 left-0 w-full h-25 bg-linear-to-t from-zinc-50 to-transparent pointer-events-none" />

Expand Down
Loading
Loading