Initialized repository for chat Terminal multiplexor website

Co-authored-by: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
This commit is contained in:
v0 2025-11-20 05:25:08 +00:00
commit 01f7de52c5
12 changed files with 2246 additions and 0 deletions

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "base-sveltekit-app",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check ."
},
"devDependencies": {
"src": "latest",
"svelte": "^5.0.0",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.0.0",
"@sveltejs/kit": "^2.16.0",
"vite": "^6.2.6",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "5.9.3"
},
"dependencies": {}
}

1572
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

87
src/app.css Normal file
View File

@ -0,0 +1,87 @@
@import "tailwindcss";
@theme inline {
--font-mono: "JetBrains Mono", "Fira Code", "Courier New", monospace;
--font-sans: var(--font-mono); /* Force mono everywhere for the nerdy vibe */
--color-background: #0a0a0a;
--color-foreground: #00ff00; /* Terminal Green */
--color-card: #111111;
--color-card-foreground: #e5e5e5;
--color-popover: #111111;
--color-popover-foreground: #e5e5e5;
--color-primary: #00ff00;
--color-primary-foreground: #000000;
--color-secondary: #333333;
--color-secondary-foreground: #ffffff;
--color-muted: #222222;
--color-muted-foreground: #888888;
--color-accent: #00ff00;
--color-accent-foreground: #000000;
--color-destructive: #ff0000;
--color-destructive-foreground: #ffffff;
--color-border: #333333;
--color-input: #222222;
--color-ring: #00ff00;
--radius-sm: 0px;
--radius-md: 0px;
--radius-lg: 0px;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground font-mono antialiased selection:bg-primary selection:text-primary-foreground;
}
/* Custom Scrollbar for that retro feel */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: #0a0a0a;
border-left: 1px solid #333;
}
::-webkit-scrollbar-thumb {
background: #333;
border: 1px solid #0a0a0a;
}
::-webkit-scrollbar-thumb:hover {
background: #00ff00;
}
}
.scanline {
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0) 50%,
rgba(0, 0, 0, 0.2) 50%,
rgba(0, 0, 0, 0.2)
);
background-size: 100% 4px;
position: fixed;
pointer-events: none;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
opacity: 0.15;
}
.glow-text {
text-shadow: 0 0 5px rgba(0, 255, 0, 0.5);
}

12
src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,158 @@
<script lang="ts">
import { onMount } from 'svelte';
let {
layout = { type: 'pane', id: 0 },
activePaneId = 0
} = $props();
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D | null = null;
let containerWidth = $state(0);
let containerHeight = $state(0);
let animationFrameId: number;
let lastTime = 0;
let cursorBlink = true;
// Colors
const BG_COLOR = '#0a0a0a';
const BORDER_COLOR = '#333333';
const ACTIVE_BORDER_COLOR = '#00ff00';
const TEXT_COLOR = '#00ff00';
const MUTED_TEXT = '#444444';
$effect(() => {
if (canvas && layout) {
draw();
}
});
function resize() {
if (!canvas) return;
const parent = canvas.parentElement;
if (parent) {
// Handle high DPI displays
const dpr = window.devicePixelRatio || 1;
const rect = parent.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
// Scale context to match
ctx?.scale(dpr, dpr);
containerWidth.set(rect.width);
containerHeight.set(rect.height);
draw();
}
}
function animate(time: number) {
if (time - lastTime > 500) { // Blink every 500ms
cursorBlink = !cursorBlink;
lastTime = time;
draw();
}
animationFrameId = requestAnimationFrame(animate);
}
onMount(() => {
ctx = canvas.getContext('2d');
window.addEventListener('resize', resize);
resize();
animationFrameId = requestAnimationFrame(animate);
return () => {
window.removeEventListener('resize', resize);
cancelAnimationFrame(animationFrameId);
};
});
function draw() {
if (!ctx) return;
// Clear
ctx.fillStyle = BG_COLOR;
ctx.fillRect(0, 0, containerWidth.get(), containerHeight.get());
// Draw Layout
drawNode(layout, 0, 0, containerWidth.get(), containerHeight.get());
}
function drawNode(node: any, x: number, y: number, w: number, h: number) {
if (!ctx) return;
// Add a small gap for the border
const gap = 2;
if (node.type === 'pane') {
// Draw Pane Background
ctx.fillStyle = '#050505';
ctx.fillRect(x + gap, y + gap, w - gap*2, h - gap*2);
// Draw Border
ctx.lineWidth = 1;
ctx.strokeStyle = node.id === activePaneId ? ACTIVE_BORDER_COLOR : BORDER_COLOR;
// If active, make border slightly thicker/glowy
if (node.id === activePaneId) {
ctx.shadowColor = ACTIVE_BORDER_COLOR;
ctx.shadowBlur = 4;
} else {
ctx.shadowBlur = 0;
}
ctx.strokeRect(x + gap, y + gap, w - gap*2, h - gap*2);
ctx.shadowBlur = 0; // Reset
// Draw Pane ID/Status
ctx.font = '12px "JetBrains Mono", monospace';
ctx.fillStyle = node.id === activePaneId ? TEXT_COLOR : MUTED_TEXT;
ctx.fillText(`[${node.id}] zsh`, x + 15, y + 25);
// Draw "Content" simulation
if (h > 60) {
ctx.fillStyle = MUTED_TEXT;
const lines = Math.floor((h - 50) / 16);
for (let i = 0; i < Math.min(lines, 8); i++) {
// Randomize line length to look like code
const width = (Math.sin(i * 132 + node.id) * 0.5 + 0.5) * (w - 60) + 20;
ctx.fillRect(x + 15, y + 45 + (i * 16), width, 6);
}
// Cursor
if (node.id === activePaneId && cursorBlink) {
ctx.fillStyle = TEXT_COLOR;
const lastLineY = y + 45 + (Math.min(lines, 8) * 16);
ctx.fillRect(x + 15, lastLineY, 8, 14);
}
}
} else if (node.type === 'split-v') {
// Vertical Split (Top/Bottom)
const h1 = h * (node.ratio || 0.5);
const h2 = h - h1;
drawNode(node.children[0], x, y, w, h1);
drawNode(node.children[1], x, y + h1, w, h2);
} else if (node.type === 'split-h') {
// Horizontal Split (Left/Right)
const w1 = w * (node.ratio || 0.5);
const w2 = w - w1;
drawNode(node.children[0], x, y, w1, h);
drawNode(node.children[1], x + w1, y, w2, h);
}
}
</script>
<div class="w-full h-full min-h-[400px] bg-black border border-border relative group overflow-hidden">
<canvas bind:this={canvas} class="block w-full h-full"></canvas>
<!-- Scanline overlay for the canvas specifically -->
<div class="absolute inset-0 pointer-events-none opacity-10 bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] z-10 bg-[length:100%_2px,3px_100%]"></div>
<div class="absolute bottom-2 right-2 text-[10px] text-muted-foreground opacity-50 group-hover:opacity-100 transition-opacity z-20 font-mono">
RENDERER: CANVAS_2D // {containerWidth.get()}x{containerHeight.get()}
</div>
</div>

45
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,45 @@
<script lang="ts">
import '../app.css';
let { children } = $props();
</script>
<div class="min-h-screen flex flex-col relative overflow-hidden">
<!-- Retro CRT Scanline Effect -->
<div class="scanline"></div>
<header class="border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50">
<div class="container flex h-14 max-w-screen-2xl items-center px-4">
<div class="mr-4 flex">
<a class="mr-6 flex items-center space-x-2" href="/">
<span class="font-bold text-primary text-xl tracking-tighter glow-text">~/mytmux.life</span>
<span class="animate-pulse inline-block w-2 h-4 bg-primary"></span>
</a>
<nav class="flex items-center space-x-6 text-sm font-medium text-muted-foreground">
<a class="transition-colors hover:text-primary hover:underline decoration-primary underline-offset-4" href="#learn">/docs</a>
<a class="transition-colors hover:text-primary hover:underline decoration-primary underline-offset-4" href="#configurator">/config</a>
<a class="transition-colors hover:text-primary hover:underline decoration-primary underline-offset-4" href="#about">/about</a>
</nav>
</div>
<div class="flex flex-1 items-center justify-end space-x-2">
<div class="text-xs text-muted-foreground border border-border px-2 py-1">
v1.0.0-alpha
</div>
</div>
</div>
</header>
<main class="flex-1 relative z-10">
{@render children()}
</main>
<footer class="border-t border-border py-6 md:px-8 md:py-0">
<div class="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
<p class="text-center text-sm leading-loose text-muted-foreground md:text-left">
Built by <span class="text-primary">v0</span>. The source code is available on <a href="https://github.com" class="font-medium underline underline-offset-4">GitHub</a>.
</p>
<div class="text-xs text-muted-foreground">
$ echo "Happy Hacking"
</div>
</div>
</footer>
</div>

258
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,258 @@
<script lang="ts">
import TerminalVisualizer from '$lib/components/terminal-visualizer.svelte';
import { onMount } from 'svelte';
// State for the layout configurator
let layout = $state({ type: 'pane', id: 0 });
let nextId = 1;
let activePaneId = $state(0);
let generatedConfig = $state('');
function splitPane(direction: 'h' | 'v') {
// Find the active pane in the tree and replace it with a split
const newLayout = JSON.parse(JSON.stringify(layout));
function findAndSplit(node: any) {
if (node.type === 'pane') {
if (node.id === activePaneId) {
const oldId = node.id;
const newId = nextId++;
// Create new split node
node.type = direction === 'h' ? 'split-h' : 'split-v';
node.ratio = 0.5;
delete node.id;
node.children = [
{ type: 'pane', id: oldId },
{ type: 'pane', id: newId }
];
return true;
}
return false;
} else {
return findAndSplit(node.children[0]) || findAndSplit(node.children[1]);
}
}
findAndSplit(newLayout);
layout = newLayout;
generateTmuxConfig();
}
function resetLayout() {
layout = { type: 'pane', id: 0 };
nextId = 1;
activePaneId = 0;
generateTmuxConfig();
}
function selectPane(id: number) {
activePaneId = id;
}
// Simple recursive function to generate tmux commands
function generateTmuxConfig() {
let config = `# ~/.tmux.conf setup\n`;
config += `# Generated by mytmux.life\n\n`;
config += `new-session -s development -n editor\n`;
function traverse(node: any) {
if (node.type === 'split-h') {
config += `split-window -h\n`;
traverse(node.children[1]); // Right child
config += `select-pane -L\n`; // Go back left
traverse(node.children[0]); // Left child
} else if (node.type === 'split-v') {
config += `split-window -v\n`;
traverse(node.children[1]); // Bottom child
config += `select-pane -U\n`; // Go back up
traverse(node.children[0]); // Top child
}
}
traverse(layout);
config += `\n# Select the initially active pane\n`;
config += `select-pane -t ${activePaneId}\n`;
generatedConfig = config;
}
onMount(() => {
generateTmuxConfig();
});
</script>
<div class="flex flex-col gap-16 pb-20">
<!-- Hero Section -->
<section class="container px-4 pt-20 md:pt-32">
<div class="grid gap-8 md:grid-cols-2 items-center">
<div class="space-y-6">
<div class="inline-flex items-center rounded-full border border-primary/50 bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
<span class="mr-2 h-2 w-2 rounded-full bg-primary animate-pulse"></span>
SYSTEM ONLINE
</div>
<h1 class="text-4xl font-extrabold tracking-tight lg:text-6xl glow-text">
Master Your <br/>
<span class="text-primary">&lt;Terminal /&gt;</span>
</h1>
<p class="text-xl text-muted-foreground max-w-[600px]">
Stop wasting time switching windows.
<span class="text-foreground font-bold">mytmux.life</span> helps you architect the perfect terminal development environment.
</p>
<div class="flex flex-col sm:flex-row gap-4 pt-4">
<a href="#configurator" class="inline-flex h-12 items-center justify-center bg-primary text-primary-foreground px-8 text-sm font-medium transition-colors hover:bg-primary/90 hover:shadow-[0_0_20px_rgba(0,255,0,0.5)]">
INITIALIZE_CONFIG
</a>
<a href="#learn" class="inline-flex h-12 items-center justify-center border border-input bg-background px-8 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">
READ_MAN_PAGE
</a>
</div>
</div>
<!-- ASCII Art / Decorative Element -->
<div class="hidden md:block p-6 border border-border bg-card/50 font-mono text-xs leading-none text-muted-foreground select-none overflow-hidden">
<pre>
_________________________________________
/ \
| root@mytmux:~ $ tmux new -s dev |
| [0] nvim ---------------- [1] server -- |
| | | | |
| | import { life } | npm run | |
| | from 'tmux'; | dev | |
| | | | |
| | // TODO: Sleep | | |
| |________________________|____________| |
| [2] logs ------------------------------ |
| | > ready in 200ms | |
| | > watching files... | |
| |_____________________________________| |
\_________________________________________/
</pre>
</div>
</div>
</section>
<!-- Configurator Section -->
<section id="configurator" class="container px-4 py-12">
<div class="flex flex-col space-y-4 mb-8">
<h2 class="text-3xl font-bold tracking-tight border-l-4 border-primary pl-4">
ENVIRONMENT_CONFIGURATOR
</h2>
<p class="text-muted-foreground">
Visually design your session layout. Click actions to split the active pane.
</p>
</div>
<div class="grid lg:grid-cols-3 gap-8 h-[600px]">
<!-- Controls & Output -->
<div class="flex flex-col gap-6 h-full">
<div class="p-6 border border-border bg-card space-y-6">
<h3 class="text-lg font-bold flex items-center gap-2">
<span class="text-primary">></span> ACTIONS
</h3>
<div class="grid grid-cols-2 gap-4">
<button
onclick={() => splitPane('h')}
class="h-20 border border-dashed border-muted-foreground hover:border-primary hover:text-primary hover:bg-primary/5 transition-all flex flex-col items-center justify-center gap-2"
>
<div class="flex gap-1 h-6 w-8 border border-current p-0.5">
<div class="w-1/2 h-full bg-current/50"></div>
<div class="w-1/2 h-full border border-current"></div>
</div>
<span class="text-xs">SPLIT_VERTICAL (%)</span>
</button>
<button
onclick={() => splitPane('v')}
class="h-20 border border-dashed border-muted-foreground hover:border-primary hover:text-primary hover:bg-primary/5 transition-all flex flex-col items-center justify-center gap-2"
>
<div class="flex flex-col gap-1 h-8 w-6 border border-current p-0.5">
<div class="h-1/2 w-full bg-current/50"></div>
<div class="h-1/2 w-full border border-current"></div>
</div>
<span class="text-xs">SPLIT_HORIZONTAL (")</span>
</button>
</div>
<div class="pt-4 border-t border-border">
<div class="flex items-center justify-between mb-2">
<span class="text-xs text-muted-foreground">ACTIVE_PANE_ID:</span>
<span class="font-mono text-primary">{activePaneId}</span>
</div>
<div class="flex gap-2">
<button
onclick={resetLayout}
class="flex-1 py-2 text-xs bg-destructive/10 text-destructive hover:bg-destructive/20 border border-destructive/20"
>
RESET_LAYOUT
</button>
</div>
</div>
</div>
<div class="flex-1 p-6 border border-border bg-card flex flex-col min-h-0">
<h3 class="text-lg font-bold flex items-center gap-2 mb-4">
<span class="text-primary">></span> GENERATED_CONFIG
</h3>
<div class="flex-1 bg-black p-4 font-mono text-xs text-muted-foreground overflow-auto border border-border">
<pre>{generatedConfig}</pre>
</div>
</div>
</div>
<!-- Visualizer Canvas -->
<div class="lg:col-span-2 h-full border border-border bg-card p-1 relative">
<div class="absolute top-0 left-0 bg-primary text-black text-[10px] font-bold px-2 py-0.5 z-10">
CANVAS_RENDER_TARGET
</div>
<TerminalVisualizer {layout} {activePaneId} />
</div>
</div>
</section>
<!-- Documentation / Info Section -->
<section id="learn" class="container px-4 py-12">
<div class="grid md:grid-cols-3 gap-12">
<div class="md:col-span-1 space-y-2">
<h3 class="text-xl font-bold text-primary">01. MULTIPLEXING</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Run multiple terminal sessions inside one single window. Detach them and leave them running in the background, then reattach later.
</p>
</div>
<div class="md:col-span-1 space-y-2">
<h3 class="text-xl font-bold text-primary">02. WINDOWS & PANES</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Organize your workspace into windows (tabs) and panes (splits). Keep your editor, server logs, and git commands visible at once.
</p>
</div>
<div class="md:col-span-1 space-y-2">
<h3 class="text-xl font-bold text-primary">03. CONFIGURATION</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Tmux is highly scriptable. Bind keys, change status bar colors, and create custom layouts to fit your specific workflow needs.
</p>
</div>
</div>
</section>
<!-- Cheat Sheet -->
<section class="container px-4 py-12 border-t border-border">
<h2 class="text-2xl font-bold mb-8">QUICK_REFERENCE_CARD</h2>
<div class="grid md:grid-cols-2 gap-4">
{#each [
{ cmd: 'Ctrl+b %', desc: 'Split pane vertically' },
{ cmd: 'Ctrl+b "', desc: 'Split pane horizontally' },
{ cmd: 'Ctrl+b o', desc: 'Swap to next pane' },
{ cmd: 'Ctrl+b c', desc: 'Create new window' },
{ cmd: 'Ctrl+b n', desc: 'Next window' },
{ cmd: 'Ctrl+b d', desc: 'Detach session' },
] as item}
<div class="flex items-center justify-between p-4 border border-border bg-card/50 hover:bg-card transition-colors group">
<span class="text-muted-foreground group-hover:text-foreground transition-colors">{item.desc}</span>
<code class="bg-secondary px-2 py-1 text-primary text-xs">{item.cmd}</code>
</div>
{/each}
</div>
</section>
</div>

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

18
svelte.config.js Normal file
View File

@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

7
vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()]
});