diff --git a/website/README.mdx b/website/README.mdx deleted file mode 100644 index 7e7cded..0000000 --- a/website/README.mdx +++ /dev/null @@ -1,202 +0,0 @@ -{/* ─── Hero: Scroll-driven logo animation ─── */} - - - - {/* - - As user scrolls, feature words stack in one at a time: - - "Multitasking." - - "Terminal." - - "For mice." - - "supports (and teaches) tmux shortcuts" - - Subtitle fades in with inline links. - Stage 5: Continued scroll pushes the logo up and off-screen, - revealing the first content section beneath. - */} - - Inside VSCode and its forks - Microsoft VSCode Marketplace{" / "} - OpenVSX. - Or standalone - Mac{" / "} - Windows{" / "} - Linux. - - - -{/* ─── Section 1: The pitch ─── */} - -
- -## Stop watching terminals spin - -Run builds, agents, servers, and scripts side by side. MouseTerm watches -them all and tells you which ones finished — so you don't have to. - -Split with a click. Resize with a drag. Minimize the ones you're not -watching to a compact status indicator. Restore them with a click. -Every pane keeps running whether you can see it or not. - -
- -{/* ─── Section 2: Completion detection ─── */} - -
- -## You'll know when it's done. - -You know the feeling — alt-tabbing back to a terminal just to see -it's still running. MouseTerm watches for you. - -When a pane stops producing output for two seconds, MouseTerm marks it -as done. No plugins, no configuration, no per-tool setup. Works with -any CLI tool that prints to a terminal. - -Waiting on a build or an agent? Sleep the pane and keep working. The status indicator -updates whether the pane is visible or not. - -
- -{/* ─── Section 3: Mouse + keyboard ─── */} - -
- -## Click everything. Or keyboard everything. - -Already know tmux? Same shortcuts. Nothing new to learn. - -Never used tmux? Click to split, drag to resize, hover to learn the -shortcuts if you want. Every action works with the mouse, the keyboard, -or both. - - - - **Split anywhere** - Click to split horizontally or vertically. Drag borders to resize. - - - **Navigate spatially** - Arrow keys or click to move between panes. Swap positions with a drag. - - - **Sleep and wake** - Minimize panes to compact status indicators. Restore with a click. - Tasks keep running. - - - **Zoom in** - Maximize any pane to full screen. One key to toggle back. - - - **Your theme, everywhere** - Uses your VSCode theme. Looks native from the moment you open it. - - - -
- -{/* ─── Section 4: Use cases ─── */} - -
- -## Built for how developers actually work. - - - - Claude Code in one pane, Codex in another, your dev server in a third. - See which one finishes first. - - - Start a build. Sleep the pane. Come back when it's done — - the status indicator already told you. - - - Using Claude Code or another CLI tool for the first time? MouseTerm - makes the terminal approachable. Click everything, learn shortcuts later. - - - Same keybindings you already know, plus VSCode themes and completion detection you can't get in tmux. - - - -
- -{/* ─── Section 5: Keyboard reference ─── */} - -
- -## Two modes. That's it. - -**Command mode** for managing panes. **Passthrough mode** where every -keypress goes straight to the terminal. - -`Enter` to go in. Quick double-tap `Cmd` to come back out. - - - -| Key | Action | -|-----|--------| -| `"` | Split horizontally | -| `%` | Split vertically | -| Arrow keys | Navigate between panes | -| `Cmd+Arrow` | Swap pane positions | -| `Enter` | Enter passthrough mode | -| `z` | Zoom / unzoom pane | -| `d` | Sleep pane to status bar | -| `x` | Close pane | -| `,` | Rename pane | - - - -
- -{/* ─── Section 6: Get it ─── */} - -
- -## Download - - - - Runs inside your editor. Open the command palette and search MouseTerm. - - VSCode Marketplace{" / "} - OpenVSX - - - Runs on its own. Free forever. Hack the source and run your own fork. - - Mac{" / "} - Windows{" / "} - Linux - - - -
- -{/* ─── Footer ─── */} - - diff --git a/website/index.html b/website/index.html index e405260..636b250 100644 --- a/website/index.html +++ b/website/index.html @@ -7,7 +7,7 @@ - +
diff --git a/website/src/components/SiteHeader.tsx b/website/src/components/SiteHeader.tsx index 5ec8649..f663209 100644 --- a/website/src/components/SiteHeader.tsx +++ b/website/src/components/SiteHeader.tsx @@ -39,7 +39,7 @@ const SiteHeader = forwardRef( ? { color: "var(--vscode-editor-foreground, #cccccc)", fontFamily: - "var(--vscode-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif)", + "var(--vscode-font-family, var(--font-display))", backgroundColor: "color-mix(in oklab, var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background, #252526)) 92%, transparent)", borderColor: "var(--vscode-panel-border, #2b2b2b)", @@ -51,23 +51,9 @@ const SiteHeader = forwardRef( return ( <> -
- 🚧 Under construction — check back soon! 🚧 -
( href="/" className={ brandVisible - ? `text-xl font-semibold tracking-tight transition-opacity ${ + ? `text-xl transition-opacity ${ themeAware ? "opacity-80 hover:opacity-100" : "opacity-50 hover:opacity-100 text-[var(--color-caramel)]" }` - : `text-xl font-semibold tracking-tight ${ + : `text-xl ${ themeAware ? "" : "text-[var(--color-caramel)]" }` } diff --git a/website/src/index.css b/website/src/index.css index 214ae9c..5063504 100644 --- a/website/src/index.css +++ b/website/src/index.css @@ -1,7 +1,8 @@ @import "tailwindcss"; @theme { - --font-display: "Instrument Sans", ui-sans-serif, system-ui, sans-serif; + --font-display: "Ubuntu Sans Mono", ui-monospace, monospace; + --font-body: "Ubuntu Mono", ui-monospace, monospace; --color-bg: oklch(10% 0.01 60); --color-surface: oklch(18% 0.015 60); --color-text: oklch(92% 0.01 60); @@ -12,13 +13,17 @@ html { background: var(--color-bg); color: var(--color-text); + font-family: var(--font-body); font-kerning: normal; } -/* Override lib's terminal-app styles (body overflow:hidden, #root height:100vh) - which Vite loads globally. The Playground page re-applies them when it mounts. */ -body { +/* Override lib's terminal-app styles (body overflow:hidden, #root height:100vh, + body font-family system font) which Vite loads globally. The Playground page + re-applies them when it mounts. Higher specificity (html body) is required so + we beat the lib's `body` rule which loads after ours on /playground. */ +html body { overflow: auto; + font-family: var(--font-body); } #root { diff --git a/website/src/pages/Dependencies.tsx b/website/src/pages/Dependencies.tsx index 282b475..b3a22d7 100644 --- a/website/src/pages/Dependencies.tsx +++ b/website/src/pages/Dependencies.tsx @@ -11,7 +11,7 @@ export function Component() {
-

+

Dependencies

@@ -21,9 +21,9 @@ export function Component() { - - - + + + diff --git a/website/src/pages/Home.tsx b/website/src/pages/Home.tsx index e44aaae..d97f17e 100644 --- a/website/src/pages/Home.tsx +++ b/website/src/pages/Home.tsx @@ -10,15 +10,20 @@ export { Home as Component }; const RUNWAY_VH = 300; /** Scroll thresholds within the pinned runway (0–1) */ -const WORD_THRESHOLDS = [0.0, 0.28, 0.41] as const; -const ASTERISK_THRESHOLD = 0.50; +const ICON_INITIAL_HIDE_FRAC = 0.67; // Fraction of icon's rendered height hidden at load — leaves top third visible +const HOOK_FADE_REMAINING = 0.10; // Hook begins fading when bottom 10% of icon enters viewport +const WORD_THRESHOLDS = [0.25, 0.40, 0.55] as const; +const ASTERISK_THRESHOLD = 0.65; /** Fraction of runway where the hero text unpins and scrolls away (0–1). * The video keeps scrubbing underneath. */ const UNPIN_THRESHOLD = 0.8; +/** Clamp a value to 0–1. */ +const clamp01 = (v: number) => Math.min(1, Math.max(0, v)); + const PILL = - "inline-block px-4 py-1.5 rounded-md border border-[var(--color-caramel)]/30 text-[var(--color-caramel)] text-sm font-display font-medium hover:bg-[var(--color-caramel)]/10 hover:border-[var(--color-caramel)]/60 hover:-translate-y-0.5 active:translate-y-0 transition-all duration-150"; + "inline-block px-4 py-1.5 rounded-md border border-[var(--color-caramel)]/30 text-[var(--color-caramel)] text-sm font-display hover:bg-[var(--color-caramel)]/10 hover:border-[var(--color-caramel)]/60 hover:-translate-y-0.5 active:translate-y-0 transition-all duration-150"; const INSTALL_STEPS: Record = { "darwin-aarch64": { @@ -58,6 +63,7 @@ function Home() { const footnoteRef = useRef(null); const headerRef = useRef(null); const headerBrandRef = useRef(null); + const hookRef = useRef(null); const [installGuide, setInstallGuide] = useState(null); useEffect(() => { @@ -96,35 +102,57 @@ function Home() { const runwayScroll = -rect.top; const runwayHeight = runway.offsetHeight - window.innerHeight; const fraction = runwayHeight > 0 - ? Math.min(1, Math.max(0, runwayScroll / runwayHeight)) + ? clamp01(runwayScroll / runwayHeight) : 0; - // Scrub video + + // Rendered icon height (object-contain preserves aspect ratio within container). + const naturalAspect = video.videoWidth && video.videoHeight + ? video.videoWidth / video.videoHeight + : 1.22; // fallback before metadata loads + const containerAspect = video.offsetWidth / video.offsetHeight; + const iconHeight = naturalAspect > containerAspect + ? video.offsetWidth / naturalAspect // width-limited + : video.offsetHeight; // height-limited + const initialOffset = iconHeight * ICON_INITIAL_HIDE_FRAC; + + // Scrub video: hold frame 0 during icon rise, then scrub remaining range. + // Skip redundant seeks whose delta is less than one frame's duration — + // each seek forces a decode, and sub-frame seeks produce the same output. if (video.duration && isFinite(video.duration)) { - video.currentTime = fraction * video.duration; + let target = 0; + if (runwayScroll >= initialOffset) { + const videoProgress = (runwayHeight - initialOffset) > 0 + ? clamp01((runwayScroll - initialOffset) / (runwayHeight - initialOffset)) + : 0; + target = videoProgress * video.duration; + } + if (Math.abs(video.currentTime - target) > 1 / 24) { + video.currentTime = target; + } } // Reveal words for (let i = 0; i < WORD_THRESHOLDS.length; i++) { const el = wordRefs[i].current; if (!el) continue; - const progress = Math.min(1, Math.max(0, + const progress = clamp01( (fraction - WORD_THRESHOLDS[i]) / 0.08 - )); + ); el.style.opacity = String(progress); el.style.transform = `translateY(${(1 - progress) * 12}px)`; } // Asterisk + footnote - const astProgress = Math.min(1, Math.max(0, + const astProgress = clamp01( (fraction - ASTERISK_THRESHOLD) / 0.08 - )); + ); if (asteriskRef.current) asteriskRef.current.style.opacity = String(astProgress); - if (footnoteRef.current) footnoteRef.current.style.opacity = String(astProgress * 0.5); + if (footnoteRef.current) footnoteRef.current.style.opacity = String(astProgress * 0.7); // Header: reveal brand + background at unpin threshold - const headerProgress = Math.min(1, Math.max(0, + const headerProgress = clamp01( (fraction - UNPIN_THRESHOLD) / 0.08 - )); + ); if (headerBrandRef.current) { headerBrandRef.current.style.opacity = String(headerProgress); } @@ -139,10 +167,28 @@ function Home() { const contentEnterScroll = runway.offsetHeight * UNPIN_THRESHOLD - window.innerHeight; const slideAmount = Math.max(0, runwayScroll - contentEnterScroll); - video.style.transform = slideAmount > 0 - ? `translateY(-${Math.round(slideAmount)}px)` + // Video transform combines two behaviors: + // 1. Icon-rise (runwayScroll 0 → initialOffset): translate down so only + // the top third is visible; scroll lifts it 1:1 until fully in view. + // 2. Unpin slide (fraction > UNPIN_THRESHOLD): translate up with content. + const iconCurrentOffset = Math.max(0, initialOffset - runwayScroll); + const videoTranslateY = iconCurrentOffset > 0 + ? iconCurrentOffset + : slideAmount > 0 ? -Math.round(slideAmount) : 0; + video.style.transform = videoTranslateY !== 0 + ? `translateY(${Math.round(videoTranslateY)}px)` : ''; + // Hook text: visible until the icon nearly finishes rising, then fades out. + if (hookRef.current) { + const remainingHidden = iconHeight > 0 ? iconCurrentOffset / iconHeight : 0; + const fadeProgress = iconCurrentOffset === 0 + ? 1 + : clamp01(1 - remainingHidden / HOOK_FADE_REMAINING); + hookRef.current.style.opacity = String(1 - fadeProgress); + hookRef.current.style.transform = `translateY(${-fadeProgress * 24}px)`; + } + // Hero: cap so it stops at unstick (fraction = 1); natural scroll takes over. const maxHeroOffset = runway.offsetHeight * (1 - UNPIN_THRESHOLD); const heroOffset = Math.min(slideAmount, maxHeroOffset); @@ -176,96 +222,122 @@ function Home() { playsInline preload="auto" className="fixed bottom-0 left-0 w-full object-contain object-bottom z-0" - style={{ height: "500px" }} + style={{ height: "min(500px, calc(100vh - 420px))" }} /> {/* ── Pinned scroll runway: hero text overlay ── */}
- {/* Hero words — sits above the video */} -
-
- - Multitasking - - - Terminal - - - - for Mice* - + {/* Hook copy — visible on load, fades out on first scroll */} +
+ Too many terminals. + Not enough focus. +
+ {/* Hero words — crossfade in place with the hook, just below the header */} +
+ + Multitasking + + + Terminal + + + + for Mice* -

- * supports (and teaches) tmux shortcuts -

-
+
+

+ *supports (and teaches) tmux shortcuts +

{/* ── Content sections — pulled up to appear as video starts scrolling ── */}
-
-

Stop watching terminals spin

-

- Run builds, agents, servers, and scripts side by side. MouseTerm - watches them all and tells you which ones finished — so you don't - have to. +

+

Stop watching terminals spin

+

+ MouseTerm tracks activity the same way you do — visual motion. When a + pane stops changing for two seconds, it marks the task complete and + alerts you.

-

- Split with a click. Resize with a drag. Minimize the ones you're not - watching to a compact status indicator. Every pane keeps running whether - you can see it or not. +

+ Works with any CLI tool that prints to a terminal — no plugins, no + configuration.

-

TODO: Split, resize, and minimize panes

+

TODO: Completion detection in action

- {/* Section 2: text left, video right */} -
+ {/* Section 2: text left, image right */} +
-

You'll know when it's done.

-

- MouseTerm tracks activity the same way you do — visual motion. When a - pane stops producing output for two seconds, MouseTerm marks it as - complete. +

Copy paste like you meant

+

+ Click and drag in a "mouse conformant" terminal doesn't select text; + it sends escape code{" "} + {"\\e[<0;x;yM"}. + And Ctrl+C{" "} + doesn't copy; it asks your program to kill itself.

-

- Works with any CLI tool that prints to a terminal. No fragile plugins, - no configuration, no per-tool setup. +

+ MouseTerm lets you copy paste like a human, not a terminal.

-

TODO: Activity detection and completion marking

+

TODO: Copy/paste with line-break rewrap

-
-

Get MouseTerm

+ {/* Section 3: image left, text right */} +
+
+

TODO: Tiling layout and tmux keybinds

+
+
+

Soft as a mouse, sharp as tmux

+

+ Run builds, agents, servers, and scripts side by side. Minimize the + ones you're not watching to a compact status indicator. Every pane + keeps running and every alert still fires whether you can see it or + not. +

+

+ Do it all with the mouse, or keep your hands on the keyboard with + tmux keybinds. +

+
+
+ +
+

Get MouseTerm

Try it in the Playground -
-
diff --git a/website/src/pages/Playground.tsx b/website/src/pages/Playground.tsx index 410e2b2..9f92441 100644 --- a/website/src/pages/Playground.tsx +++ b/website/src/pages/Playground.tsx @@ -93,7 +93,7 @@ function Playground() { controls={} /> -
+
{PondModule ? (
PackageVersionLicensePackageVersionLicense