Merge homepage-redesign-show-dont-tell: animated scope hero, pipeline strip, featured notebooks
This commit is contained in:
commit
6350f15991
87
frontend/src/components/FeaturedNotebooks.astro
Normal file
87
frontend/src/components/FeaturedNotebooks.astro
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
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 transition-all',
|
||||||
|
'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>
|
||||||
@ -29,37 +29,40 @@ export function NotebookCard({ notebook }: NotebookCardProps) {
|
|||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href={`/notebook/${encodeURIComponent(id)}`}
|
href={`/notebook/${encodeURIComponent(id)}`}
|
||||||
className="group block rounded-lg border border-slate-800 bg-slate-900/50 p-5 hover:border-blue-500/40 hover:bg-slate-800/70 transition-all"
|
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="flex items-start justify-between mb-2">
|
<div className="notebook-card-strip" data-engine={engine} />
|
||||||
<h3 className="text-base font-semibold text-slate-100 group-hover:text-blue-400 transition-colors line-clamp-1">
|
<div className="p-5">
|
||||||
{title}
|
<div className="flex items-start justify-between mb-2">
|
||||||
</h3>
|
<h3 className="text-base font-semibold text-slate-100 group-hover:text-blue-400 transition-colors line-clamp-1">
|
||||||
<Badge variant={engineVariant[engine] ?? 'default'} className="shrink-0 ml-2">
|
{title}
|
||||||
{engine}
|
</h3>
|
||||||
</Badge>
|
<Badge variant={engineVariant[engine] ?? 'default'} className="shrink-0 ml-2">
|
||||||
</div>
|
{engine}
|
||||||
|
</Badge>
|
||||||
<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>
|
||||||
)}
|
|
||||||
|
<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>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
163
frontend/src/components/PipelineStrip.astro
Normal file
163
frontend/src/components/PipelineStrip.astro
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="max-w-6xl mx-auto px-6 py-16">
|
||||||
|
<div class="grid gap-6 md:grid-cols-[1fr_auto_1fr_auto_1fr] items-start">
|
||||||
|
|
||||||
|
<!-- Step 1: Write -->
|
||||||
|
<div class="pipeline-step">
|
||||||
|
<span class="pipeline-number">01</span>
|
||||||
|
<h3 class="pipeline-title">Write</h3>
|
||||||
|
<div class="pipeline-preview">
|
||||||
|
<pre class="pipeline-code"><span class="text-slate-500">* RC Low-Pass Filter</span>
|
||||||
|
<span class="text-blue-400">V1</span> in 0 PULSE(0 5 0 1n 1n 0.5m 1m)
|
||||||
|
<span class="text-blue-400">R1</span> in out 10k
|
||||||
|
<span class="text-blue-400">C1</span> out 0 100n
|
||||||
|
<span class="text-emerald-400">.tran</span> 10u 5m</pre>
|
||||||
|
</div>
|
||||||
|
<p class="pipeline-desc">
|
||||||
|
SPICE netlists with syntax highlighting and autocomplete
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Arrow 1→2 -->
|
||||||
|
<div class="pipeline-arrow">
|
||||||
|
<Icon name="lucide:arrow-right" class="w-5 h-5 text-slate-600 hidden md:block" />
|
||||||
|
<Icon name="lucide:chevron-down" class="w-5 h-5 text-slate-600 md:hidden" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2: Simulate -->
|
||||||
|
<div class="pipeline-step">
|
||||||
|
<span class="pipeline-number">02</span>
|
||||||
|
<h3 class="pipeline-title">Simulate</h3>
|
||||||
|
<div class="pipeline-preview">
|
||||||
|
<div class="pipeline-terminal">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
||||||
|
<span class="text-emerald-400 text-xs font-mono font-semibold">ngspice 43</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs font-mono text-slate-500 space-y-0.5">
|
||||||
|
<p>Circuit: RC Low-Pass Filter</p>
|
||||||
|
<p>Doing transient analysis...</p>
|
||||||
|
<p>501 data points, 23ms</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1.5 mt-2">
|
||||||
|
<Icon name="lucide:check-circle-2" class="w-3.5 h-3.5 text-emerald-400" />
|
||||||
|
<span class="text-emerald-400 text-xs font-mono font-semibold">complete</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="pipeline-desc">
|
||||||
|
Run ngspice simulations with one click
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Arrow 2→3 -->
|
||||||
|
<div class="pipeline-arrow">
|
||||||
|
<Icon name="lucide:arrow-right" class="w-5 h-5 text-slate-600 hidden md:block" />
|
||||||
|
<Icon name="lucide:chevron-down" class="w-5 h-5 text-slate-600 md:hidden" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 3: Visualize -->
|
||||||
|
<div class="pipeline-step">
|
||||||
|
<span class="pipeline-number">03</span>
|
||||||
|
<h3 class="pipeline-title">Visualize</h3>
|
||||||
|
<div class="pipeline-preview pipeline-waveform">
|
||||||
|
<svg viewBox="0 0 300 140" xmlns="http://www.w3.org/2000/svg" class="w-full h-full" preserveAspectRatio="none">
|
||||||
|
<!-- Faint grid -->
|
||||||
|
<defs>
|
||||||
|
<pattern id="ps-grid" width="30" height="17.5" patternUnits="userSpaceOnUse">
|
||||||
|
<line x1="30" y1="0" x2="30" y2="17.5" stroke="rgba(45,212,191,0.06)" stroke-width="0.5"/>
|
||||||
|
<line x1="0" y1="17.5" x2="30" y2="17.5" stroke="rgba(45,212,191,0.06)" stroke-width="0.5"/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect width="300" height="140" fill="#0a0a0a"/>
|
||||||
|
<rect width="300" height="140" fill="url(#ps-grid)"/>
|
||||||
|
<!-- Input square wave (dim) -->
|
||||||
|
<polyline fill="none" stroke="rgba(96,165,250,0.3)" stroke-width="1.2"
|
||||||
|
points="0,115 0,25 75,25 75,115 150,115 150,25 225,25 225,115 300,115"/>
|
||||||
|
<!-- RC response (bright teal) -->
|
||||||
|
<path fill="none" stroke="#2dd4bf" stroke-width="1.8" stroke-linecap="round"
|
||||||
|
d="M0,115 C19,50 38,30 75,27 C94,95 112,112 150,114 C169,50 188,30 225,27 C244,95 262,112 300,114"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="pipeline-desc">
|
||||||
|
Interactive waveforms + oscilloscope mode
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pipeline-step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-number {
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: var(--color-sb-accent, #2563eb);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-title {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #f1f5f9;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-preview {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid #1e293b;
|
||||||
|
background: #0f172a;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-code {
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 0.875rem 1rem;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
color: #e2e8f0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-terminal {
|
||||||
|
padding: 0.875rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-waveform {
|
||||||
|
padding: 0;
|
||||||
|
aspect-ratio: 300 / 140;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-desc {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-arrow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.pipeline-arrow {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
59
frontend/src/lib/featured-waveforms.ts
Normal file
59
frontend/src/lib/featured-waveforms.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Pre-rendered SVG waveform thumbnails for the 3 featured notebooks.
|
||||||
|
// Each is a self-contained SVG string with dark background, faint grid, and trace paths.
|
||||||
|
|
||||||
|
const GRID = `<defs>
|
||||||
|
<pattern id="ft-grid" width="10%" height="12.5%" patternUnits="objectBoundingBox">
|
||||||
|
<line x1="100%" y1="0" x2="100%" y2="100%" stroke="rgba(45,212,191,0.06)" stroke-width="0.5"/>
|
||||||
|
<line x1="0" y1="100%" x2="100%" y2="100%" stroke="rgba(45,212,191,0.06)" stroke-width="0.5"/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect width="100%" height="100%" fill="#0a0a0a"/>
|
||||||
|
<rect width="100%" height="100%" fill="url(#ft-grid)"/>`;
|
||||||
|
|
||||||
|
// 1. RC Low-Pass Filter — square wave input (dim) + exponential charge/discharge output (bright)
|
||||||
|
// Time axis: 0→5ms, two full cycles of 1kHz square wave through R=10k, C=100nF (τ=1ms)
|
||||||
|
export const rcLowPassSvg = `<svg viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
|
||||||
|
${GRID}
|
||||||
|
<!-- Input: square wave (dimmer) -->
|
||||||
|
<polyline fill="none" stroke="rgba(96,165,250,0.3)" stroke-width="1.5"
|
||||||
|
points="0,160 0,40 100,40 100,160 200,160 200,40 300,40 300,160 400,160"/>
|
||||||
|
<!-- Output: RC exponential response (bright teal) -->
|
||||||
|
<path fill="none" stroke="#2dd4bf" stroke-width="2" stroke-linecap="round"
|
||||||
|
d="M0,160
|
||||||
|
C25,70 50,45 100,42
|
||||||
|
C125,130 150,155 200,158
|
||||||
|
C225,70 250,45 300,42
|
||||||
|
C325,130 350,155 400,158"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
// 2. 555 Astable LED Blinker — sharp square wave output with slight ramp on edges
|
||||||
|
// Output toggles between ~0V and ~Vcc with RC timing visible
|
||||||
|
export const astable555Svg = `<svg viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
|
||||||
|
${GRID}
|
||||||
|
<!-- Capacitor voltage: sawtooth ramp between thresholds (dimmer) -->
|
||||||
|
<polyline fill="none" stroke="rgba(250,204,21,0.3)" stroke-width="1.5"
|
||||||
|
points="0,130 70,80 70,130 140,80 140,130 210,80 210,130 280,80 280,130 350,80 350,130 400,95"/>
|
||||||
|
<!-- Output: clean square wave (bright amber) -->
|
||||||
|
<polyline fill="none" stroke="#f59e0b" stroke-width="2"
|
||||||
|
points="0,160 0,40 70,40 70,160 140,160 140,40 210,40 210,160 280,160 280,40 350,40 350,160 400,160"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
// 3. Common Emitter Amplifier — input sine (dim) + amplified inverted sine (bright)
|
||||||
|
// AC analysis showing voltage gain with phase inversion
|
||||||
|
export const commonEmitterSvg = `<svg viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
|
||||||
|
${GRID}
|
||||||
|
<!-- Input: small sine wave centered (dimmer) -->
|
||||||
|
<path fill="none" stroke="rgba(167,139,250,0.35)" stroke-width="1.5"
|
||||||
|
d="M0,100
|
||||||
|
C17,85 33,85 50,100 C67,115 83,115 100,100
|
||||||
|
C117,85 133,85 150,100 C167,115 183,115 200,100
|
||||||
|
C217,85 233,85 250,100 C267,115 283,115 300,100
|
||||||
|
C317,85 333,85 350,100 C367,115 383,115 400,100"/>
|
||||||
|
<!-- Output: amplified, inverted sine (bright green) -->
|
||||||
|
<path fill="none" stroke="#34d399" stroke-width="2" stroke-linecap="round"
|
||||||
|
d="M0,100
|
||||||
|
C17,145 33,145 50,100 C67,55 83,55 100,100
|
||||||
|
C117,145 133,145 150,100 C167,55 183,55 200,100
|
||||||
|
C217,145 233,145 250,100 C267,55 283,55 300,100
|
||||||
|
C317,145 333,145 350,100 C367,55 383,55 400,100"/>
|
||||||
|
</svg>`;
|
||||||
@ -2,6 +2,8 @@
|
|||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import NotebookLayout from '../layouts/NotebookLayout.astro';
|
import NotebookLayout from '../layouts/NotebookLayout.astro';
|
||||||
import NotebookGallery from '../components/NotebookGallery';
|
import NotebookGallery from '../components/NotebookGallery';
|
||||||
|
import PipelineStrip from '../components/PipelineStrip.astro';
|
||||||
|
import FeaturedNotebooks from '../components/FeaturedNotebooks.astro';
|
||||||
import { fetchNotebookList } from '../lib/server-api';
|
import { fetchNotebookList } from '../lib/server-api';
|
||||||
import type { NotebookSummary } from '../lib/types';
|
import type { NotebookSummary } from '../lib/types';
|
||||||
|
|
||||||
@ -23,72 +25,139 @@ try {
|
|||||||
@import '../styles/homepage.css';
|
@import '../styles/homepage.css';
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Hero -->
|
<!-- Hero: Split layout — text left, animated scope visual right -->
|
||||||
<section class="hero-graticule border-b border-slate-800/60">
|
<section class="hero-graticule border-b border-slate-800/60">
|
||||||
<div class="relative max-w-6xl mx-auto px-6 pt-16 pb-20">
|
<div class="relative max-w-6xl mx-auto px-6 pt-16 pb-20">
|
||||||
<p class="text-sm font-semibold tracking-widest text-blue-400 uppercase mb-4">SpiceBook</p>
|
<div class="grid md:grid-cols-2 gap-12 items-center">
|
||||||
<h1 class="text-4xl md:text-5xl font-bold text-slate-100 tracking-tight max-w-2xl">
|
|
||||||
Circuit Simulation Notebooks
|
<!-- Left: Copy + CTAs -->
|
||||||
</h1>
|
<div>
|
||||||
<p class="mt-4 text-lg text-slate-400 max-w-xl leading-relaxed">
|
<p class="text-sm font-semibold tracking-widest text-blue-400 uppercase mb-4">SpiceBook</p>
|
||||||
Write SPICE netlists, run ngspice simulations, and visualize waveforms in a single document.
|
<h1 class="text-4xl md:text-5xl font-bold text-slate-100 tracking-tight">
|
||||||
</p>
|
Circuit Simulation Notebooks
|
||||||
<div class="flex items-center gap-3 mt-8">
|
</h1>
|
||||||
<a
|
<p class="mt-4 text-lg text-slate-400 max-w-xl leading-relaxed">
|
||||||
href="/notebook/new"
|
Write SPICE netlists, run ngspice simulations, and visualize waveforms in a single document.
|
||||||
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-blue-600 hover:bg-blue-500 text-white font-medium transition-colors text-sm"
|
</p>
|
||||||
>
|
<div class="flex items-center gap-3 mt-8">
|
||||||
<Icon name="lucide:plus" class="w-4 h-4" />
|
<a
|
||||||
New Notebook
|
href="/notebook/new"
|
||||||
</a>
|
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-blue-600 hover:bg-blue-500 text-white font-medium transition-colors text-sm"
|
||||||
<a
|
>
|
||||||
href="#notebooks"
|
<Icon name="lucide:plus" class="w-4 h-4" />
|
||||||
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg border border-slate-700 text-slate-300 hover:border-slate-500 hover:text-slate-100 font-medium transition-colors text-sm"
|
New Notebook
|
||||||
>
|
</a>
|
||||||
Browse Notebooks
|
<a
|
||||||
<Icon name="lucide:chevron-down" class="w-4 h-4" />
|
href="#notebooks"
|
||||||
</a>
|
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg border border-slate-700 text-slate-300 hover:border-slate-500 hover:text-slate-100 font-medium transition-colors text-sm"
|
||||||
|
>
|
||||||
|
Browse Notebooks
|
||||||
|
<Icon name="lucide:chevron-down" class="w-4 h-4" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right: Animated oscilloscope visual -->
|
||||||
|
<div class="flex justify-center md:justify-end">
|
||||||
|
<a href="/notebook/rc-lowpass-filter" class="hero-scope" aria-label="Open RC Low-Pass Filter notebook">
|
||||||
|
<div class="hero-scope-frame">
|
||||||
|
|
||||||
|
<!-- Brand bar -->
|
||||||
|
<div class="hero-scope-brand">
|
||||||
|
<span class="hero-scope-brand-name">Tektronix</span>
|
||||||
|
<span class="hero-scope-brand-model">465</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CRT bay -->
|
||||||
|
<div class="hero-scope-crt-bay">
|
||||||
|
<div class="hero-scope-screen">
|
||||||
|
<!-- Waveform SVG: RC step response -->
|
||||||
|
<svg viewBox="0 0 500 300" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" aria-hidden="true">
|
||||||
|
<!-- Input: square wave (dim blue) -->
|
||||||
|
<polyline
|
||||||
|
class="hero-scope-trace-input"
|
||||||
|
fill="none"
|
||||||
|
stroke="rgba(96,165,250,0.25)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
points="0,240 0,60 125,60 125,240 250,240 250,60 375,60 375,240 500,240"
|
||||||
|
/>
|
||||||
|
<!-- Output: RC exponential response (bright teal) -->
|
||||||
|
<path
|
||||||
|
class="hero-scope-trace-output"
|
||||||
|
fill="none"
|
||||||
|
stroke="#2dd4bf"
|
||||||
|
stroke-width="2.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M0,240
|
||||||
|
C31,105 62,68 125,62
|
||||||
|
C156,195 188,232 250,238
|
||||||
|
C281,105 312,68 375,62
|
||||||
|
C406,195 438,232 500,238"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<!-- Graticule + scanlines -->
|
||||||
|
<div class="hero-scope-graticule"></div>
|
||||||
|
<div class="hero-scope-scanlines"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Digital readout -->
|
||||||
|
<div class="hero-scope-readout">
|
||||||
|
<span>V(out)</span>
|
||||||
|
<span class="hero-scope-readout-divider">|</span>
|
||||||
|
<span>200μs/div</span>
|
||||||
|
<span class="hero-scope-readout-divider">|</span>
|
||||||
|
<span>500mV/div</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Control panel -->
|
||||||
|
<div class="hero-scope-panel">
|
||||||
|
<div class="hero-scope-controls">
|
||||||
|
<div class="hero-scope-section">
|
||||||
|
<span class="hero-scope-section-label">Volts</span>
|
||||||
|
<div class="hero-scope-knob" data-pos="2"></div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-scope-section">
|
||||||
|
<span class="hero-scope-section-label">Time</span>
|
||||||
|
<div class="hero-scope-knob" data-pos="3"></div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-scope-section">
|
||||||
|
<span class="hero-scope-section-label">Trigger</span>
|
||||||
|
<div class="hero-scope-knob" data-pos="1"></div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-scope-section">
|
||||||
|
<div class="hero-scope-mode">TRAN</div>
|
||||||
|
<div class="hero-scope-led"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Feature Highlights -->
|
<!-- Pipeline: Write → Simulate → Visualize -->
|
||||||
<section class="max-w-6xl mx-auto px-6 py-16">
|
<section class="border-b border-slate-800/60">
|
||||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<PipelineStrip />
|
||||||
<div class="p-5 rounded-lg border border-slate-800 bg-slate-900/50">
|
</section>
|
||||||
<Icon name="lucide:zap" class="w-6 h-6 text-blue-400 mb-3" />
|
|
||||||
<h3 class="font-semibold text-slate-100 mb-1">SPICE Simulation</h3>
|
<!-- Featured Notebooks -->
|
||||||
<p class="text-sm text-slate-400 leading-relaxed">
|
<section class="border-b border-slate-800/60">
|
||||||
Write netlists and run ngspice. Transient, AC, DC sweep, operating point.
|
<FeaturedNotebooks />
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-5 rounded-lg border border-slate-800 bg-slate-900/50">
|
|
||||||
<Icon name="lucide:activity" class="w-6 h-6 text-blue-400 mb-3" />
|
|
||||||
<h3 class="font-semibold text-slate-100 mb-1">Waveform Plots</h3>
|
|
||||||
<p class="text-sm text-slate-400 leading-relaxed">
|
|
||||||
Results render as interactive plots below each netlist.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-5 rounded-lg border border-slate-800 bg-slate-900/50">
|
|
||||||
<Icon name="lucide:circuit-board" class="w-6 h-6 text-blue-400 mb-3" />
|
|
||||||
<h3 class="font-semibold text-slate-100 mb-1">Schematic Generation</h3>
|
|
||||||
<p class="text-sm text-slate-400 leading-relaxed">
|
|
||||||
Netlists produce SVG circuit diagrams automatically.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-5 rounded-lg border border-slate-800 bg-slate-900/50">
|
|
||||||
<Icon name="lucide:file-text" class="w-6 h-6 text-blue-400 mb-3" />
|
|
||||||
<h3 class="font-semibold text-slate-100 mb-1">Notebook Format</h3>
|
|
||||||
<p class="text-sm text-slate-400 leading-relaxed">
|
|
||||||
Markdown, SPICE, and Python cells in a single document.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Notebook Gallery -->
|
<!-- Notebook Gallery -->
|
||||||
<section id="notebooks" class="max-w-6xl mx-auto px-6 pb-20 scroll-mt-8">
|
<section id="notebooks" class="max-w-6xl mx-auto px-6 py-20 scroll-mt-8">
|
||||||
<h2 class="text-2xl font-bold text-slate-100 mb-8">Notebooks</h2>
|
<div class="mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-slate-100">Explore Notebooks</h2>
|
||||||
|
{notebooks.length > 0 && (
|
||||||
|
<p class="text-sm text-slate-500 mt-1">{notebooks.length} notebooks available</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<NotebookGallery
|
<NotebookGallery
|
||||||
client:load
|
client:load
|
||||||
initialNotebooks={notebooks}
|
initialNotebooks={notebooks}
|
||||||
@ -99,10 +168,11 @@ try {
|
|||||||
<!-- Footer CTA -->
|
<!-- Footer CTA -->
|
||||||
<footer class="border-t border-slate-800/60">
|
<footer class="border-t border-slate-800/60">
|
||||||
<div class="max-w-6xl mx-auto px-6 py-16 text-center">
|
<div class="max-w-6xl mx-auto px-6 py-16 text-center">
|
||||||
<h2 class="text-2xl font-bold text-slate-100 mb-3">Ready to simulate?</h2>
|
<h2 class="text-2xl font-bold text-slate-100 mb-2">Start with a blank notebook</h2>
|
||||||
|
<p class="text-sm text-slate-400 mb-6">Write a SPICE netlist, run it, see results in seconds.</p>
|
||||||
<a
|
<a
|
||||||
href="/notebook/new"
|
href="/notebook/new"
|
||||||
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-blue-600 hover:bg-blue-500 text-white font-medium transition-colors text-sm mt-4"
|
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-blue-600 hover:bg-blue-500 text-white font-medium transition-colors text-sm"
|
||||||
>
|
>
|
||||||
<Icon name="lucide:plus" class="w-4 h-4" />
|
<Icon name="lucide:plus" class="w-4 h-4" />
|
||||||
New Notebook
|
New Notebook
|
||||||
|
|||||||
@ -29,3 +29,418 @@
|
|||||||
.filter-pills::-webkit-scrollbar {
|
.filter-pills::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════
|
||||||
|
Hero Oscilloscope Visual — Pure CSS + SVG
|
||||||
|
Adapted from scope-skin.css, fully independent (hero-scope- prefix)
|
||||||
|
═══════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* Color palette */
|
||||||
|
.hero-scope {
|
||||||
|
--hs-teal: #2dd4bf;
|
||||||
|
--hs-teal-dim: rgba(45, 212, 191, 0.12);
|
||||||
|
--hs-teal-glow: rgba(45, 212, 191, 0.18);
|
||||||
|
--hs-panel: #b5a48a;
|
||||||
|
--hs-panel-light: #c7b89e;
|
||||||
|
--hs-panel-dark: #9e8f78;
|
||||||
|
--hs-crt-bg: #0a0a0a;
|
||||||
|
--hs-label: #3b3428;
|
||||||
|
--hs-knob: #2a2a2d;
|
||||||
|
--hs-knob-ring: #1e1e20;
|
||||||
|
--hs-section-line: rgba(59, 52, 40, 0.25);
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
max-width: 420px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Outer frame ──────────────────────────────────────────── */
|
||||||
|
.hero-scope-frame {
|
||||||
|
position: relative;
|
||||||
|
background:
|
||||||
|
url("data:image/svg+xml,%3Csvg width='4' height='4' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='1' height='1' x='0' y='0' fill='rgba(0,0,0,0.03)'/%3E%3Crect width='1' height='1' x='2' y='2' fill='rgba(255,255,255,0.02)'/%3E%3C/svg%3E"),
|
||||||
|
linear-gradient(175deg, var(--hs-panel-light), var(--hs-panel), var(--hs-panel-dark));
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow:
|
||||||
|
0 8px 30px rgba(0, 0, 0, 0.45),
|
||||||
|
0 2px 6px rgba(0, 0, 0, 0.25),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15),
|
||||||
|
inset 0 -1px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Brand bar ────────────────────────────────────────────── */
|
||||||
|
.hero-scope-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 14px 6px;
|
||||||
|
border-bottom: 1px solid var(--hs-section-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-brand-name {
|
||||||
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--hs-label);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-brand-model {
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 0.55rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--hs-label);
|
||||||
|
opacity: 0.78;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CRT bay ──────────────────────────────────────────────── */
|
||||||
|
.hero-scope-crt-bay {
|
||||||
|
margin: 10px 12px 0;
|
||||||
|
background: #1a1816;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 2px 6px rgba(0, 0, 0, 0.5),
|
||||||
|
inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CRT screen ───────────────────────────────────────────── */
|
||||||
|
.hero-scope-screen {
|
||||||
|
position: relative;
|
||||||
|
background: var(--hs-crt-bg);
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
aspect-ratio: 5 / 3;
|
||||||
|
box-shadow:
|
||||||
|
0 0 15px var(--hs-teal-glow),
|
||||||
|
inset 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SVG fills the CRT */
|
||||||
|
.hero-scope-screen svg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Graticule overlay (10×8 grid) ────────────────────────── */
|
||||||
|
.hero-scope-graticule {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
background-image:
|
||||||
|
repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
transparent calc(10% - 0.5px),
|
||||||
|
rgba(45, 212, 191, 0.07) calc(10% - 0.5px),
|
||||||
|
rgba(45, 212, 191, 0.07) calc(10% + 0.5px),
|
||||||
|
transparent calc(10% + 0.5px)
|
||||||
|
),
|
||||||
|
repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent calc(12.5% - 0.5px),
|
||||||
|
rgba(45, 212, 191, 0.07) calc(12.5% - 0.5px),
|
||||||
|
rgba(45, 212, 191, 0.07) calc(12.5% + 0.5px),
|
||||||
|
transparent calc(12.5% + 0.5px)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center crosshair ticks */
|
||||||
|
.hero-scope-graticule::before,
|
||||||
|
.hero-scope-graticule::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-graticule::before {
|
||||||
|
top: 50%;
|
||||||
|
left: calc(50% - 8px);
|
||||||
|
width: 16px;
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(45, 212, 191, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-graticule::after {
|
||||||
|
left: 50%;
|
||||||
|
top: calc(50% - 8px);
|
||||||
|
width: 1px;
|
||||||
|
height: 16px;
|
||||||
|
background: rgba(45, 212, 191, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Scanlines ────────────────────────────────────────────── */
|
||||||
|
.hero-scope-scanlines {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 3;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent 2px,
|
||||||
|
rgba(0, 0, 0, 0.04) 2px,
|
||||||
|
rgba(0, 0, 0, 0.04) 4px
|
||||||
|
);
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── SVG trace animation ──────────────────────────────────── */
|
||||||
|
.hero-scope-trace-input {
|
||||||
|
stroke-dasharray: 1200;
|
||||||
|
stroke-dashoffset: 1200;
|
||||||
|
animation: hero-trace-draw 1.8s ease-out 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-trace-output {
|
||||||
|
stroke-dasharray: 1200;
|
||||||
|
stroke-dashoffset: 1200;
|
||||||
|
animation: hero-trace-draw 1.8s ease-out 0.6s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hero-trace-draw {
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Phosphor glow pulse after draw completes */
|
||||||
|
.hero-scope-trace-output {
|
||||||
|
animation:
|
||||||
|
hero-trace-draw 1.8s ease-out 0.6s forwards,
|
||||||
|
hero-phosphor-pulse 3s ease-in-out 2.4s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hero-phosphor-pulse {
|
||||||
|
0%, 100% {
|
||||||
|
filter: drop-shadow(0 0 2px var(--hs-teal-glow));
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
filter: drop-shadow(0 0 6px rgba(45, 212, 191, 0.35));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Digital readout bar ──────────────────────────────────── */
|
||||||
|
.hero-scope-readout {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
background: #0a0a0a;
|
||||||
|
margin: 0 12px;
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--hs-teal);
|
||||||
|
text-shadow: 0 0 4px var(--hs-teal-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-readout-divider {
|
||||||
|
color: rgba(45, 212, 191, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Control panel ────────────────────────────────────────── */
|
||||||
|
.hero-scope-panel {
|
||||||
|
padding: 8px 12px 8px;
|
||||||
|
border-top: 1px solid var(--hs-section-line);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 2px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section dividers */
|
||||||
|
.hero-scope-section + .hero-scope-section::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -1px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background: var(--hs-section-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-section-label {
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 0.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--hs-label);
|
||||||
|
opacity: 0.88;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Decorative knobs ─────────────────────────────────────── */
|
||||||
|
.hero-scope-knob {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle at 40% 35%, #3a3a3e, var(--hs-knob));
|
||||||
|
border: 2px solid var(--hs-knob-ring);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 4px rgba(0, 0, 0, 0.35),
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.06);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Knob indicator line */
|
||||||
|
.hero-scope-knob::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 50%;
|
||||||
|
width: 1.5px;
|
||||||
|
height: 7px;
|
||||||
|
background: #d4d0c8;
|
||||||
|
border-radius: 1px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Knob rotation variants */
|
||||||
|
.hero-scope-knob[data-pos="1"] { transform: rotate(-60deg); }
|
||||||
|
.hero-scope-knob[data-pos="2"] { transform: rotate(-20deg); }
|
||||||
|
.hero-scope-knob[data-pos="3"] { transform: rotate(30deg); }
|
||||||
|
.hero-scope-knob[data-pos="4"] { transform: rotate(70deg); }
|
||||||
|
|
||||||
|
/* ── Mode badge ───────────────────────────────────────────── */
|
||||||
|
.hero-scope-mode {
|
||||||
|
background: var(--hs-knob);
|
||||||
|
border: 2px solid var(--hs-knob-ring);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--hs-teal);
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 0.55rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
padding: 5px 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 1;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 4px rgba(0, 0, 0, 0.3),
|
||||||
|
0 0 6px var(--hs-teal-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Power LED ────────────────────────────────────────────── */
|
||||||
|
.hero-scope-led {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--hs-teal);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||||
|
box-shadow: 0 0 6px var(--hs-teal-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Accessibility: reduced motion ────────────────────────── */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.hero-scope-trace-input,
|
||||||
|
.hero-scope-trace-output {
|
||||||
|
stroke-dasharray: none;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
animation: none;
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-scanlines {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsive ───────────────────────────────────────────── */
|
||||||
|
@media (max-width: 50rem) {
|
||||||
|
.hero-scope-knob {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-knob::after {
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-scope-readout {
|
||||||
|
font-size: 0.55rem;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Gallery card decorative header strip ─────────────────── */
|
||||||
|
.notebook-card-strip {
|
||||||
|
height: 3rem;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-card-strip::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image:
|
||||||
|
repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
transparent calc(10% - 0.5px),
|
||||||
|
var(--strip-accent, rgba(37, 99, 235, 0.08)) calc(10% - 0.5px),
|
||||||
|
var(--strip-accent, rgba(37, 99, 235, 0.08)) calc(10% + 0.5px),
|
||||||
|
transparent calc(10% + 0.5px)
|
||||||
|
),
|
||||||
|
repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent calc(25% - 0.5px),
|
||||||
|
var(--strip-accent, rgba(37, 99, 235, 0.08)) calc(25% - 0.5px),
|
||||||
|
var(--strip-accent, rgba(37, 99, 235, 0.08)) calc(25% + 0.5px),
|
||||||
|
transparent calc(25% + 0.5px)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom accent line */
|
||||||
|
.notebook-card-strip::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
var(--strip-line, rgba(37, 99, 235, 0.25)) 20%,
|
||||||
|
var(--strip-line, rgba(37, 99, 235, 0.25)) 80%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Engine color variants */
|
||||||
|
.notebook-card-strip[data-engine="ngspice"] {
|
||||||
|
--strip-accent: rgba(37, 99, 235, 0.08);
|
||||||
|
--strip-line: rgba(37, 99, 235, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-card-strip[data-engine="ltspice"] {
|
||||||
|
--strip-accent: rgba(245, 158, 11, 0.08);
|
||||||
|
--strip-line: rgba(245, 158, 11, 0.25);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user