spicebook/frontend/src/components/FeaturedNotebooks.astro
Ryan Malloy 09fd59d570 Visual polish: card hover lift, gradient section dividers, editor spacing
- Cards (featured + gallery): hover adds -translate-y-1 + shadow-lg for
  subtle lift-off-surface effect
- Homepage sections: replace hard border-b with gradient-fade dividers
  that taper to transparent at edges
- Resistor button section: tighten vertical padding (py-10 → py-6)
- Notebook editor: cell spacing increased (space-y-0 → space-y-3),
  add-cell button slides up on hover instead of abrupt opacity flash,
  status bar gets more breathing room and lighter text
2026-02-21 15:28:08 -07:00

90 lines
3.3 KiB
Plaintext

---
import { rcLowPassSvg, astable555Svg, commonEmitterSvg } from '../lib/featured-waveforms';
const featured = [
{
id: 'rc-lowpass-filter',
title: 'RC Low-Pass Filter',
engine: 'ngspice' as const,
description: 'Classic transient analysis — square wave input through an RC network produces the characteristic exponential charge/discharge response.',
tags: ['transient', 'passive', 'beginner'],
svg: rcLowPassSvg,
},
{
id: '555-astable-blinker',
title: '555 Astable LED Blinker',
engine: 'ngspice' as const,
description: 'Timer IC in free-running mode — the capacitor ramp between comparator thresholds generates a square wave output.',
tags: ['timer', 'oscillator', 'digital'],
svg: astable555Svg,
},
{
id: 'common-emitter-amplifier',
title: 'Common Emitter Amplifier',
engine: 'ngspice' as const,
description: 'Single-stage BJT amplifier with AC analysis — input sine wave is amplified and phase-inverted at the collector.',
tags: ['amplifier', 'ac-analysis', 'bjt'],
svg: commonEmitterSvg,
},
];
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' },
};
---
<section class="max-w-6xl mx-auto px-6 py-16">
<p class="text-sm font-semibold tracking-widest text-blue-400 uppercase mb-2">Featured</p>
<h2 class="text-2xl font-bold text-slate-100 mb-8">Start with a classic circuit</h2>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{featured.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 overflow-hidden',
'hover:border-blue-500/40 hover:bg-slate-800/70',
'hover:-translate-y-1 hover:shadow-lg hover:shadow-black/25',
'transition-all duration-200',
'border-slate-800',
]}
>
{/* Waveform thumbnail */}
<div class="w-full h-[180px] bg-[#0a0a0a] border-b border-slate-800/60 overflow-hidden">
<Fragment set:html={nb.svg} />
</div>
<div class="p-5">
<div class="flex items-start justify-between mb-2">
<h3 class="text-base 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-2 py-0.5 rounded text-xs font-medium border',
colors.border, colors.bg, colors.text,
]}>
{nb.engine}
</span>
</div>
<p class="text-sm text-slate-400 leading-relaxed mb-3 line-clamp-2">
{nb.description}
</p>
<div class="flex flex-wrap gap-1.5">
{nb.tags.map((tag) => (
<span class="text-xs bg-slate-700/50 text-slate-400 px-2 py-0.5 rounded">
{tag}
</span>
))}
</div>
</div>
</a>
);
})}
</div>
</section>