Restructure the homepage to lead with visuals instead of text: - Hero: split layout with animated Tektronix 465 oscilloscope showing RC step response (CSS+SVG, zero JS) that links to the notebook - Pipeline strip: 3-step Write → Simulate → Visualize with code/terminal previews and inline waveform SVG - Featured notebooks: 3 curated circuits (RC, 555, common emitter) with pre-rendered waveform thumbnails - Gallery cards: decorative graticule header strip, color-coded by engine - Footer: updated copy with clearer call to action All new sections are server-rendered Astro components. Total new client JavaScript: zero bytes.
69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
import { LayoutGrid } from 'lucide-react';
|
|
import { Badge } from './ui/Badge';
|
|
import type { NotebookSummary } from '../lib/types';
|
|
|
|
const engineVariant: Record<string, 'blue' | 'amber'> = {
|
|
ngspice: 'blue',
|
|
ltspice: 'amber',
|
|
};
|
|
|
|
function formatDate(iso: string): string {
|
|
try {
|
|
return new Date(iso).toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
});
|
|
} catch {
|
|
return iso;
|
|
}
|
|
}
|
|
|
|
interface NotebookCardProps {
|
|
notebook: NotebookSummary;
|
|
}
|
|
|
|
export function NotebookCard({ notebook }: NotebookCardProps) {
|
|
const { id, title, engine, tags, cell_count, modified } = notebook;
|
|
|
|
return (
|
|
<a
|
|
href={`/notebook/${encodeURIComponent(id)}`}
|
|
className="group block rounded-lg border border-slate-800 bg-slate-900/50 overflow-hidden hover:border-blue-500/40 hover:bg-slate-800/70 transition-all"
|
|
>
|
|
<div className="notebook-card-strip" data-engine={engine} />
|
|
<div className="p-5">
|
|
<div className="flex items-start justify-between mb-2">
|
|
<h3 className="text-base font-semibold text-slate-100 group-hover:text-blue-400 transition-colors line-clamp-1">
|
|
{title}
|
|
</h3>
|
|
<Badge variant={engineVariant[engine] ?? 'default'} className="shrink-0 ml-2">
|
|
{engine}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3 text-xs text-slate-500 mb-3">
|
|
<span className="inline-flex items-center gap-1">
|
|
<LayoutGrid className="w-3 h-3" />
|
|
{cell_count} cell{cell_count !== 1 ? 's' : ''}
|
|
</span>
|
|
<span>{formatDate(modified)}</span>
|
|
</div>
|
|
|
|
{tags.length > 0 && (
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{tags.map((tag) => (
|
|
<span
|
|
key={tag}
|
|
className="text-xs bg-slate-700/50 text-slate-400 px-2 py-0.5 rounded"
|
|
>
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</a>
|
|
);
|
|
}
|