Skip to content
Prev Previous commit
Next Next commit
improvement: blog
  • Loading branch information
emir-karabeg committed Apr 5, 2026
commit de831c89b8e6e2e1037df15f20447691ec25af96
65 changes: 44 additions & 21 deletions apps/sim/app/(landing)/blog/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,55 @@
import { Skeleton } from '@/components/emcn'

const SKELETON_CARD_COUNT = 6

export default function BlogLoading() {
return (
<main className='mx-auto max-w-[1200px] px-6 py-12 sm:px-8 md:px-12'>
<Skeleton className='h-[48px] w-[100px] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='mt-3 h-[18px] w-[420px] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<div className='mt-10 grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3'>
{Array.from({ length: SKELETON_CARD_COUNT }).map((_, i) => (
<div
key={i}
className='flex flex-col overflow-hidden rounded-xl border border-[var(--landing-border)]'
>
<Skeleton className='aspect-video w-full rounded-none bg-[var(--landing-bg-elevated)]' />
<div className='flex flex-1 flex-col p-4'>
<Skeleton className='mb-2 h-[12px] w-[80px] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='mb-1 h-[20px] w-[85%] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='mb-3 h-[14px] w-full rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='h-[14px] w-[70%] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<div className='mt-3 flex items-center gap-2'>
<Skeleton className='h-[16px] w-[16px] rounded-full bg-[var(--landing-bg-elevated)]' />
<Skeleton className='h-[12px] w-[80px] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<section className='bg-[var(--landing-bg)]'>
{/* Header skeleton */}
<div className='px-5 pt-[60px] lg:px-16 lg:pt-[100px]'>
<Skeleton className='mb-5 h-[20px] w-[60px] rounded-md bg-[var(--landing-bg-elevated)]' />
<div className='flex flex-col gap-4 md:flex-row md:items-end md:justify-between'>
<Skeleton className='h-[40px] w-[240px] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='h-[18px] w-[320px] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
</div>
</div>

{/* Content area with vertical border rails */}
<div className='mx-5 mt-8 border-[var(--landing-bg-elevated)] border-x lg:mx-16'>
<div className='h-px w-full bg-[var(--landing-bg-elevated)]' />

{/* Featured skeleton */}
<div className='flex'>
{Array.from({ length: 3 }).map((_, i) => (
<div
key={i}
className='flex flex-1 flex-col gap-4 border-[var(--landing-bg-elevated)] p-6 md:border-l md:first:border-l-0'
>
<Skeleton className='aspect-video w-full rounded-[5px] bg-[var(--landing-bg-elevated)]' />
<div className='flex flex-col gap-2'>
<Skeleton className='h-[12px] w-[60px] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='h-[20px] w-[80%] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='h-[14px] w-full rounded-[4px] bg-[var(--landing-bg-elevated)]' />
</div>
</div>
))}
</div>

<div className='h-px w-full bg-[var(--landing-bg-elevated)]' />

{/* List skeleton */}
{Array.from({ length: 5 }).map((_, i) => (
<div key={i}>
<div className='flex items-center gap-6 px-6 py-6'>
<Skeleton className='hidden h-[14px] w-[120px] rounded-[4px] bg-[var(--landing-bg-elevated)] md:block' />
<div className='flex min-w-0 flex-1 flex-col gap-1'>
<Skeleton className='h-[18px] w-[70%] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
<Skeleton className='h-[14px] w-[90%] rounded-[4px] bg-[var(--landing-bg-elevated)]' />
</div>
<Skeleton className='hidden h-[80px] w-[140px] rounded-[5px] bg-[var(--landing-bg-elevated)] sm:block' />
</div>
<div className='h-px w-full bg-[var(--landing-bg-elevated)]' />
</div>
))}
</div>
</main>
</section>
)
}
187 changes: 144 additions & 43 deletions apps/sim/app/(landing)/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata } from 'next'
import Link from 'next/link'
import { Badge } from '@/components/emcn'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused PostGrid component file after import removal

Low Severity

The PostGrid import was removed from page.tsx and the blog page now renders its own inline layout, but the post-grid.tsx file still exists with an exported PostGrid component that is no longer imported anywhere in the codebase. This is dead code that can be cleaned up.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2d0e30b. Configure here.

import { getAllPostMeta } from '@/lib/blog/registry'
import { PostGrid } from '@/app/(landing)/blog/post-grid'

export const metadata: Metadata = {
title: 'Blog',
Expand Down Expand Up @@ -34,8 +34,9 @@ export default async function BlogIndex({
const totalPages = Math.max(1, Math.ceil(sorted.length / perPage))
const start = (pageNum - 1) * perPage
const posts = sorted.slice(start, start + perPage)
// Tag filter chips are intentionally disabled for now.
// const tags = await getAllTags()
const featured = pageNum === 1 ? posts.slice(0, 3) : []
const remaining = pageNum === 1 ? posts.slice(3) : posts

const blogJsonLd = {
'@context': 'https://schema.org',
'@type': 'Blog',
Expand All @@ -45,54 +46,154 @@ export default async function BlogIndex({
}

return (
<main className='mx-auto max-w-[1200px] px-6 py-12 sm:px-8 md:px-12'>
<section className='bg-[var(--landing-bg)]'>
<script
type='application/ld+json'
dangerouslySetInnerHTML={{ __html: JSON.stringify(blogJsonLd) }}
/>
<h1 className='mb-3 text-balance font-[500] text-[40px] text-[var(--landing-text)] leading-tight sm:text-[56px]'>
Blog
</h1>
<p className='mb-10 text-[var(--landing-text-muted)] text-lg'>
Announcements, insights, and guides for building AI agent workflows.
</p>

{/* Tag filter chips hidden until we have more posts */}
{/* <div className='mb-10 flex flex-wrap gap-3'>
<Link href='/blog' className={`rounded-full border px-3 py-1 text-sm ${!tag ? 'border-black bg-black text-white' : 'border-gray-300'}`}>All</Link>
{tags.map((t) => (
<Link key={t.tag} href={`/blog?tag=${encodeURIComponent(t.tag)}`} className={`rounded-full border px-3 py-1 text-sm ${tag === t.tag ? 'border-black bg-black text-white' : 'border-gray-300'}`}>
{t.tag} ({t.count})
</Link>
))}
</div> */}

{/* Grid layout for consistent rows */}
<PostGrid posts={posts} />
{/* Section header */}
<div className='px-5 pt-[60px] lg:px-16 lg:pt-[100px]'>
<Badge
variant='blue'
size='md'
dot
className='mb-5 bg-white/10 font-season text-white uppercase tracking-[0.02em]'
>
Blog
</Badge>

{totalPages > 1 && (
<div className='mt-10 flex items-center justify-center gap-3'>
{pageNum > 1 && (
<Link
href={`/blog?page=${pageNum - 1}${tag ? `&tag=${encodeURIComponent(tag)}` : ''}`}
className='rounded-[5px] border border-[var(--landing-border-strong)] px-3 py-1 text-[var(--landing-text)] text-sm transition-colors hover:bg-[var(--landing-bg-elevated)]'
>
Previous
</Link>
)}
<span className='text-[var(--landing-text-muted)] text-sm'>
Page {pageNum} of {totalPages}
</span>
{pageNum < totalPages && (
<div className='flex flex-col gap-4 md:flex-row md:items-end md:justify-between'>
<h1 className='text-balance font-[430] font-season text-[28px] text-white leading-[100%] tracking-[-0.02em] lg:text-[40px]'>
Latest from Sim
</h1>
<p className='max-w-[360px] font-[430] font-season text-[#F6F6F0]/50 text-sm leading-[150%] tracking-[0.02em] lg:text-base'>
Announcements, insights, and guides for building AI agent workflows.
</p>
</div>
</div>

{/* Full-width top line */}
<div className='mt-8 h-px w-full bg-[var(--landing-bg-elevated)]' />

{/* Content area with vertical border rails */}
<div className='mx-5 border-[var(--landing-bg-elevated)] border-x lg:mx-16'>
{/* Featured posts */}
{featured.length > 0 && (
<>
<div className='flex'>
{featured.map((p, index) => (
<Link
key={p.slug}
href={`/blog/${p.slug}`}
className='group flex flex-1 flex-col gap-4 border-[var(--landing-bg-elevated)] p-6 transition-colors hover:bg-[var(--landing-bg-elevated)] md:border-l md:first:border-l-0'
>
<div className='relative aspect-video w-full overflow-hidden rounded-[5px]'>
<img
src={p.ogImage}
alt={p.title}
className='h-full w-full object-cover'
loading={index < 3 ? 'eager' : 'lazy'}
/>
</div>
Comment on lines +91 to +98
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Use next/image instead of bare <img> for post thumbnails

Both the featured-card images (lines 91–98) and the list-row thumbnail (lines 154–160) use a plain HTML <img> element. Since the project uses next/image throughout and these images are locally-served assets, replacing them would enable automatic WebP conversion, responsive srcset, and proper LCP/lazy-load management.

The featured card's parent already has relative aspect-video overflow-hidden, so the fill prop works directly:

Suggested change
<div className='relative aspect-video w-full overflow-hidden rounded-[5px]'>
<img
src={p.ogImage}
alt={p.title}
className='h-full w-full object-cover'
loading={index < 3 ? 'eager' : 'lazy'}
/>
</div>
<div className='relative aspect-video w-full overflow-hidden rounded-[5px]'>
<Image
src={p.ogImage}
alt={p.title}
fill
className='object-cover'
priority={index === 0}
/>
</div>

The same swap applies to the thumbnail at lines 154–160.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

<div className='flex flex-col gap-2'>
<span className='font-martian-mono text-[var(--landing-text-subtle)] text-xs uppercase tracking-[0.1em]'>
{new Date(p.date).toLocaleDateString('en-US', {
month: 'short',
year: '2-digit',
})}
</span>
<h3 className='font-[430] font-season text-lg text-white leading-tight tracking-[-0.01em]'>
{p.title}
</h3>
<p className='line-clamp-2 text-[#F6F6F0]/50 text-sm leading-[150%]'>
{p.description}
</p>
</div>
</Link>
))}
</div>

<div className='h-px w-full bg-[var(--landing-bg-elevated)]' />
</>
)}

{remaining.map((p) => (
<div key={p.slug}>
<Link
href={`/blog?page=${pageNum + 1}${tag ? `&tag=${encodeURIComponent(tag)}` : ''}`}
className='rounded-[5px] border border-[var(--landing-border-strong)] px-3 py-1 text-[var(--landing-text)] text-sm transition-colors hover:bg-[var(--landing-bg-elevated)]'
href={`/blog/${p.slug}`}
className='group flex items-start gap-6 px-6 py-6 transition-colors hover:bg-[var(--landing-bg-elevated)] md:items-center'
>
Next
{/* Date */}
<span className='hidden w-[120px] shrink-0 pt-1 font-martian-mono text-[var(--landing-text-subtle)] text-xs uppercase tracking-[0.1em] md:block'>
{new Date(p.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</span>

{/* Title + description */}
<div className='flex min-w-0 flex-1 flex-col gap-1'>
<span className='font-martian-mono text-[var(--landing-text-subtle)] text-xs uppercase tracking-[0.1em] md:hidden'>
{new Date(p.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</span>
<h3 className='font-[430] font-season text-base text-white leading-tight tracking-[-0.01em] lg:text-lg'>
{p.title}
</h3>
<p className='line-clamp-2 text-[#F6F6F0]/40 text-sm leading-[150%]'>
{p.description}
</p>
</div>

{/* Image */}
<div className='hidden h-[80px] w-[140px] shrink-0 overflow-hidden rounded-[5px] sm:block'>
<img
src={p.ogImage}
alt={p.title}
className='h-full w-full object-cover'
loading='lazy'
/>
</div>
</Link>
)}
</div>
)}
</main>
<div className='h-px w-full bg-[var(--landing-bg-elevated)]' />
</div>
))}

{/* Pagination */}
{totalPages > 1 && (
<div className='px-6 py-8'>
<div className='flex items-center justify-center gap-3'>
{pageNum > 1 && (
<Link
href={`/blog?page=${pageNum - 1}${tag ? `&tag=${encodeURIComponent(tag)}` : ''}`}
className='rounded-[5px] border border-[var(--landing-border-strong)] px-3 py-1 text-[var(--landing-text)] text-sm transition-colors hover:bg-[var(--landing-bg-elevated)]'
>
Previous
</Link>
)}
<span className='text-[var(--landing-text-muted)] text-sm'>
Page {pageNum} of {totalPages}
</span>
{pageNum < totalPages && (
<Link
href={`/blog?page=${pageNum + 1}${tag ? `&tag=${encodeURIComponent(tag)}` : ''}`}
className='rounded-[5px] border border-[var(--landing-border-strong)] px-3 py-1 text-[var(--landing-text)] text-sm transition-colors hover:bg-[var(--landing-bg-elevated)]'
>
Next
</Link>
)}
</div>
</div>
)}
</div>

{/* Full-width bottom line — overlaps last inner divider to avoid double border */}
<div className='-mt-px h-px w-full bg-[var(--landing-bg-elevated)]' />
</section>
)
}
54 changes: 8 additions & 46 deletions apps/sim/app/(landing)/components/features/features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,36 +117,6 @@ function ScrollLetter({ scrollYProgress, charIndex, children }: ScrollLetterProp
return <motion.span style={{ opacity }}>{children}</motion.span>
}

function DotGrid({
cols,
rows,
width,
borderLeft,
}: {
cols: number
rows: number
width?: number
borderLeft?: boolean
}) {
return (
<div
aria-hidden='true'
className={`h-full shrink-0 bg-[var(--landing-bg-section)] p-1.5 ${borderLeft ? 'border-[var(--divider)] border-l' : ''}`}
style={{
width: width ? `${width}px` : undefined,
display: 'grid',
gridTemplateColumns: `repeat(${cols}, 1fr)`,
gap: 4,
placeItems: 'center',
}}
>
{Array.from({ length: cols * rows }, (_, i) => (
<div key={i} className='h-[1.5px] w-[1.5px] rounded-full bg-[#DEDEDE]' />
))}
</div>
)
}

export default function Features() {
const sectionRef = useRef<HTMLDivElement>(null)
const [activeTab, setActiveTab] = useState(0)
Expand Down Expand Up @@ -224,14 +194,10 @@ export default function Features() {
/>

<div className='flex h-[68px] border border-[var(--divider)] lg:overflow-hidden'>
<div className='h-full shrink-0'>
<div className='h-full lg:hidden'>
<DotGrid cols={3} rows={8} width={24} />
</div>
<div className='hidden h-full lg:block'>
<DotGrid cols={8} rows={8} width={64} />
</div>
</div>
<div
aria-hidden='true'
className='h-full w-[24px] shrink-0 bg-[var(--landing-bg-section)] lg:w-16'
/>

<div role='tablist' aria-label='Feature categories' className='flex flex-1'>
{FEATURE_TABS.map((tab, index) => (
Expand Down Expand Up @@ -275,14 +241,10 @@ export default function Features() {
))}
</div>

<div className='h-full shrink-0'>
<div className='h-full lg:hidden'>
<DotGrid cols={3} rows={8} width={24} />
</div>
<div className='hidden h-full lg:block'>
<DotGrid cols={8} rows={8} width={64} />
</div>
</div>
<div
aria-hidden='true'
className='h-full w-[24px] shrink-0 border-[var(--divider)] border-l bg-[var(--landing-bg-section)] lg:w-16'
/>
</div>

<div
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/components/hero/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function Hero() {
</h1>
<p
itemProp='description'
className='max-w-[280px] text-center font-[430] font-season text-[color-mix(in_srgb,var(--landing-text-subtle)_60%,transparent)] text-lg leading-[125%] tracking-[0.02em] sm:max-w-none lg:text-xl'
className='whitespace-nowrap text-center font-[430] font-season text-[4.4vw] text-[color-mix(in_srgb,var(--landing-text-subtle)_60%,transparent)] leading-[125%] tracking-[0.02em] sm:whitespace-normal sm:text-lg lg:text-xl'
>
Sim is the AI Workspace for Agent Builders
</p>
Expand Down
Loading
Loading