diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index dceda60..fa7458a 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -10,6 +10,9 @@ export default defineConfig({
starlight({
title: 'mcltspice',
tagline: 'LTspice circuit simulation via MCP',
+ components: {
+ Hero: './src/components/Hero.astro',
+ },
social: [
{ icon: 'external', label: 'Gitea', href: 'https://git.supported.systems/MCP/mcltspice' },
{ icon: 'external', label: 'PyPI', href: 'https://pypi.org/project/mcltspice/' },
diff --git a/docs/public/spirals_shrt.mp3 b/docs/public/spirals_shrt.mp3
new file mode 100644
index 0000000..7f00e84
Binary files /dev/null and b/docs/public/spirals_shrt.mp3 differ
diff --git a/docs/src/components/Hero.astro b/docs/src/components/Hero.astro
new file mode 100644
index 0000000..4dea1c0
--- /dev/null
+++ b/docs/src/components/Hero.astro
@@ -0,0 +1,156 @@
+---
+/**
+ * Custom Hero override for Starlight.
+ * On splash pages: injects the oscilloscope display in the image area.
+ * On other pages: delegates to the default Starlight Hero.
+ */
+import { Image } from 'astro:assets';
+import { LinkButton } from '@astrojs/starlight/components';
+import OscilloscopeDisplay from './OscilloscopeDisplay.astro';
+
+const PAGE_TITLE_ID = '_top';
+
+const { data } = Astro.locals.starlightRoute.entry;
+const { title = data.title, tagline, image, actions = [] } = data.hero || {};
+const isSplash = data.template === 'splash';
+
+const imageAttrs = {
+ loading: 'eager' as const,
+ decoding: 'async' as const,
+ width: 400,
+ height: 400,
+ alt: image?.alt || '',
+};
+
+let darkImage: ImageMetadata | undefined;
+let lightImage: ImageMetadata | undefined;
+let rawHtml: string | undefined;
+
+if (image) {
+ if ('file' in image) {
+ darkImage = image.file;
+ } else if ('dark' in image) {
+ darkImage = image.dark;
+ lightImage = image.light;
+ } else {
+ rawHtml = image.html;
+ }
+}
+---
+
+
+ {isSplash ? (
+
+
+
+ ) : (
+ <>
+ {darkImage && (
+
+ )}
+ {lightImage &&
}
+ {rawHtml &&
}
+ >
+ )}
+
+
+ {actions.length > 0 && (
+
+ {actions.map(
+ ({ attrs: { class: className, ...attrs } = {}, icon, link: href, text, variant }) => (
+
+ {text}
+ {icon?.html && }
+
+ )
+ )}
+
+ )}
+
+
+
+
diff --git a/docs/src/components/OscilloscopeDisplay.astro b/docs/src/components/OscilloscopeDisplay.astro
new file mode 100644
index 0000000..34c8cb5
--- /dev/null
+++ b/docs/src/components/OscilloscopeDisplay.astro
@@ -0,0 +1,256 @@
+---
+/**
+ * XY-mode audio oscilloscope display.
+ * Visual style inspired by the Tektronix 465 (1972).
+ * Renders stereo audio as Lissajous patterns on a canvas.
+ *
+ * Audio: "Spirals" by Jerobeam Fenderson (oscilloscopemusic.com)
+ * License: CC BY-NC-SA 4.0
+ * Original visual: Nick Watton (codepen.io/2Mogs)
+ * Gist: rsp2k (gist.github.com/rsp2k/ac68b1bb290b8124e162987ed1df8d53)
+ * Modernized: ScriptProcessor → AnalyserNode + rAF
+ */
+---
+
+
+
+
+ Tektronix
+ mcltspice
+
+
+
+
+
+
+
+
+
+
+
Vertical
+
+
Volts/Div
+
+
+
+
+
Horizontal
+
+
Time/Div
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css
index 4370c28..91b7b76 100644
--- a/docs/src/styles/custom.css
+++ b/docs/src/styles/custom.css
@@ -3,6 +3,7 @@
@import "@astrojs/starlight-tailwind";
@import "tailwindcss";
+@import "./oscilloscope.css";
:root {
--sl-color-accent-low: #083344; /* teal-950 */
diff --git a/docs/src/styles/oscilloscope.css b/docs/src/styles/oscilloscope.css
new file mode 100644
index 0000000..bc443b9
--- /dev/null
+++ b/docs/src/styles/oscilloscope.css
@@ -0,0 +1,343 @@
+/* Oscilloscope display -- Tektronix 465 inspired
+ * Tan/champagne panel, recessed CRT, teal phosphor
+ * Designed for mcltspice docs hero section */
+
+/* ── Outer chassis ───────────────────────────────────────── */
+.scope-frame {
+ --scope-teal: #2dd4bf;
+ --scope-teal-dim: rgba(45, 212, 191, 0.12);
+ --scope-teal-glow: rgba(45, 212, 191, 0.18);
+ --scope-panel: #b5a48a;
+ --scope-panel-light: #c7b89e;
+ --scope-panel-dark: #9e8f78;
+ --scope-crt-bg: #0a0a0a;
+ --scope-label: #3b3428;
+ --scope-knob: #2a2a2d;
+ --scope-knob-ring: #1e1e20;
+ --scope-section-line: rgba(59, 52, 40, 0.25);
+
+ position: relative;
+ background:
+ /* subtle metallic grain */
+ 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(--scope-panel-light), var(--scope-panel), var(--scope-panel-dark));
+ border-radius: 6px;
+ padding: 0;
+ box-shadow:
+ 0 10px 30px rgba(0, 0, 0, 0.55),
+ 0 2px 6px rgba(0, 0, 0, 0.3),
+ inset 0 1px 0 rgba(255, 255, 255, 0.15),
+ inset 0 -1px 0 rgba(0, 0, 0, 0.1);
+ max-width: 340px;
+ width: 100%;
+ margin: 0 auto;
+ overflow: hidden;
+}
+
+/* ── Top brand bar ───────────────────────────────────────── */
+.scope-brand {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ padding: 8px 14px 6px;
+ border-bottom: 1px solid var(--scope-section-line);
+}
+
+.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(--scope-label);
+ opacity: 0.75;
+}
+
+.scope-brand-model {
+ font-family: var(--sl-font-mono, ui-monospace, monospace);
+ font-size: 0.55rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--scope-label);
+ opacity: 0.55;
+}
+
+/* ── CRT bay (recessed dark area) ────────────────────────── */
+.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 ──────────────────────────────────────────── */
+.scope-screen {
+ position: relative;
+ background: var(--scope-crt-bg);
+ border-radius: 3px;
+ overflow: hidden;
+ aspect-ratio: 1;
+ box-shadow:
+ 0 0 15px var(--scope-teal-glow),
+ inset 0 1px 4px rgba(0, 0, 0, 0.4);
+}
+
+/* ── Canvas ──────────────────────────────────────────────── */
+.scope-canvas {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+/* ── Graticule overlay (8x10 grid, like the 465) ─────────── */
+.scope-graticule {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ /* 10 horizontal, 8 vertical — classic Tek grid */
+ background-image:
+ repeating-linear-gradient(
+ 90deg,
+ 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)
+ ),
+ 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 tick marks */
+.scope-graticule::before,
+.scope-graticule::after {
+ content: '';
+ position: absolute;
+}
+
+.scope-graticule::before {
+ /* horizontal center tick */
+ top: 50%;
+ left: calc(50% - 8px);
+ width: 16px;
+ height: 1px;
+ background: rgba(45, 212, 191, 0.18);
+}
+
+.scope-graticule::after {
+ /* vertical center tick */
+ left: 50%;
+ top: calc(50% - 8px);
+ width: 1px;
+ height: 16px;
+ background: rgba(45, 212, 191, 0.18);
+}
+
+/* ── Scanline overlay ────────────────────────────────────── */
+.scope-scanlines {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ 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;
+}
+
+/* ── Control panel area ──────────────────────────────────── */
+.scope-panel {
+ padding: 8px 12px 6px;
+ border-top: 1px solid var(--scope-section-line);
+ margin-top: 10px;
+}
+
+.scope-controls-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 2px;
+}
+
+/* ── Control section (labeled group) ─────────────────────── */
+.scope-section {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 2px;
+ position: relative;
+}
+
+/* Section divider lines */
+.scope-section + .scope-section::before {
+ content: '';
+ position: absolute;
+ left: -1px;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background: var(--scope-section-line);
+}
+
+.scope-section-label {
+ font-family: var(--sl-font-mono, ui-monospace, monospace);
+ font-size: 0.45rem;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--scope-label);
+ opacity: 0.6;
+ line-height: 1;
+}
+
+/* ── Rotary knob ─────────────────────────────────────────── */
+.scope-knob {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 40% 35%, #3a3a3e, var(--scope-knob));
+ border: 2px solid var(--scope-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 */
+.scope-knob::after {
+ content: '';
+ position: absolute;
+ top: 3px;
+ left: 50%;
+ width: 1.5px;
+ height: 7px;
+ background: #d4d0c8;
+ border-radius: 1px;
+ transform: translateX(-50%);
+}
+
+.scope-knob-label {
+ font-family: var(--sl-font-mono, ui-monospace, monospace);
+ font-size: 0.4rem;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+ color: var(--scope-label);
+ opacity: 0.5;
+ line-height: 1;
+}
+
+/* ── Power toggle ────────────────────────────────────────── */
+.scope-toggle {
+ appearance: none;
+ background: var(--scope-knob);
+ border: 2px solid var(--scope-knob-ring);
+ border-radius: 6px;
+ color: #8a8880;
+ font-family: var(--sl-font-mono, ui-monospace, monospace);
+ font-size: 0.55rem;
+ font-weight: 700;
+ letter-spacing: 0.06em;
+ padding: 5px 8px;
+ cursor: pointer;
+ transition: color 0.2s, box-shadow 0.2s;
+ text-transform: uppercase;
+ line-height: 1;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
+}
+
+.scope-toggle:hover {
+ color: #c4c0b8;
+}
+
+.scope-toggle[data-active="true"] {
+ color: var(--scope-teal);
+ box-shadow:
+ 0 2px 4px rgba(0, 0, 0, 0.3),
+ 0 0 6px var(--scope-teal-glow);
+}
+
+.scope-toggle:focus-visible {
+ outline: 2px solid var(--scope-teal);
+ outline-offset: 2px;
+}
+
+/* ── Power LED ───────────────────────────────────────────── */
+.scope-led {
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+ background: #3a3632;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ transition: background 0.3s, box-shadow 0.3s;
+}
+
+.scope-led[data-on="true"] {
+ background: var(--scope-teal);
+ box-shadow: 0 0 6px var(--scope-teal-glow);
+}
+
+/* ── Attribution bar ─────────────────────────────────────── */
+.scope-attribution-bar {
+ padding: 5px 14px 7px;
+ border-top: 1px solid var(--scope-section-line);
+}
+
+.scope-attribution {
+ font-family: var(--sl-font-mono, ui-monospace, monospace);
+ font-size: 0.45rem;
+ color: var(--scope-panel-dark);
+ line-height: 1.4;
+ text-align: center;
+}
+
+.scope-attribution a {
+ color: var(--scope-label);
+ text-decoration: none;
+ transition: color 0.15s;
+}
+
+.scope-attribution a:hover {
+ color: #1a1610;
+}
+
+/* ── Idle state ──────────────────────────────────────────── */
+.scope-screen[data-idle="true"] .scope-canvas {
+ opacity: 0.6;
+}
+
+/* ── Reduced motion ──────────────────────────────────────── */
+@media (prefers-reduced-motion: reduce) {
+ .scope-scanlines {
+ display: none;
+ }
+}
+
+/* ── Responsive ──────────────────────────────────────────── */
+@media (max-width: 50rem) {
+ .scope-frame {
+ max-width: 280px;
+ }
+
+ .scope-knob {
+ width: 24px;
+ height: 24px;
+ }
+
+ .scope-knob::after {
+ height: 5px;
+ }
+}