Add Recently Added notebooks section to homepage

Server-rendered section between Featured and Explore showing the 4
most recently modified notebooks, excluding featured and untitled ones.
Compact card layout with engine badge, cell count, and relative date.
This commit is contained in:
Ryan Malloy 2026-03-06 14:45:00 -07:00
parent 3e68a355c3
commit 365df157b4
2 changed files with 98 additions and 0 deletions

View File

@ -0,0 +1,92 @@
---
import { Icon } from 'astro-icon/components';
import type { NotebookSummary } from '../lib/types';
interface Props {
notebooks: NotebookSummary[];
}
const FEATURED_IDS = new Set([
'rc-lowpass-filter',
'555-astable-blinker',
'common-emitter-amplifier',
]);
const { notebooks } = Astro.props;
const recent = notebooks
.filter((nb) => !FEATURED_IDS.has(nb.id) && nb.title !== 'Untitled Notebook')
.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime())
.slice(0, 4);
function formatDate(iso: string): string {
try {
return new Date(iso).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
timeZone: 'UTC',
});
} catch {
return iso;
}
}
const engineColor: Record<string, { border: string; bg: string; text: string }> = {
ngspice: { border: 'border-blue-500/30', bg: 'bg-blue-500/10', text: 'text-blue-400' },
ltspice: { border: 'border-amber-500/30', bg: 'bg-amber-500/10', text: 'text-amber-400' },
};
---
{recent.length > 0 && (
<section class="max-w-6xl mx-auto px-6 py-12">
<div class="flex items-center gap-3 mb-6">
<Icon name="lucide:clock" class="w-4 h-4 text-slate-500" />
<h2 class="text-lg font-semibold text-slate-200">Recently Added</h2>
</div>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{recent.map((nb) => {
const colors = engineColor[nb.engine] ?? engineColor.ngspice;
return (
<a
href={`/notebook/${nb.id}`}
class:list={[
'group block rounded-lg border bg-slate-900/50 p-4',
'hover:border-blue-500/40 hover:bg-slate-800/70',
'hover:-translate-y-0.5 hover:shadow-md hover:shadow-black/20',
'transition-all duration-200',
'border-slate-800',
]}
>
<div class="flex items-start justify-between mb-2">
<h3 class="text-sm font-semibold text-slate-100 group-hover:text-blue-400 transition-colors line-clamp-1">
{nb.title}
</h3>
<span class:list={[
'shrink-0 ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium border',
colors.border, colors.bg, colors.text,
]}>
{nb.engine}
</span>
</div>
{nb.description && (
<p class="text-xs text-slate-400 leading-relaxed mb-2 line-clamp-2">
{nb.description}
</p>
)}
<div class="flex items-center gap-2 text-[11px] text-slate-500">
<span class="inline-flex items-center gap-1">
<Icon name="lucide:layers" class="w-3 h-3" />
{nb.cell_count} cell{nb.cell_count !== 1 ? 's' : ''}
</span>
<span>&middot;</span>
<span>{formatDate(nb.modified)}</span>
</div>
</a>
);
})}
</div>
</section>
)}

View File

@ -4,6 +4,7 @@ import NotebookLayout from '../layouts/NotebookLayout.astro';
import NotebookGallery from '../components/NotebookGallery';
import PipelineStrip from '../components/PipelineStrip.astro';
import FeaturedNotebooks from '../components/FeaturedNotebooks.astro';
import RecentNotebooks from '../components/RecentNotebooks.astro';
import OscilloscopeDisplay from '../components/OscilloscopeDisplay.astro';
import { fetchNotebookList } from '../lib/server-api';
import type { NotebookSummary } from '../lib/types';
@ -150,6 +151,11 @@ try {
<FeaturedNotebooks />
</section>
<!-- Recently Added -->
<section class="section-fade">
<RecentNotebooks notebooks={notebooks} />
</section>
<!-- Notebook Gallery -->
<section id="notebooks" class="max-w-6xl mx-auto px-6 py-20 scroll-mt-8">
<div class="mb-8">