mytmux.life-website/app/page.tsx

492 lines
21 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import TerminalVisualizer from "@/components/terminal-visualizer"
// Types for the layout tree
type PaneNode = {
type: "pane"
id: number
}
type SplitNode = {
type: "split-h" | "split-v"
ratio: number
children: [LayoutNode, LayoutNode]
}
export type LayoutNode = PaneNode | SplitNode
const ASCII_ART = `
_________________________________________
/ \\
| 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... | |
| |_____________________________________| |
\\_________________________________________/
`
export default function Home() {
const [layout, setLayout] = useState<LayoutNode>({ type: "pane", id: 0 })
const [nextId, setNextId] = useState(1)
const [activePaneId, setActivePaneId] = useState(0)
const [generatedConfig, setGeneratedConfig] = useState("")
// Helper to find and split the active node
function findAndSplitNode(
node: LayoutNode,
activeId: number,
direction: "h" | "v",
nextIdVal: number,
): { found: boolean; nextId: number; newNode?: LayoutNode } {
if (node.type === "pane") {
if (node.id === activeId) {
const oldId = node.id
const newId = nextIdVal
// Create new split node
const newNode: SplitNode = {
type: direction === "h" ? "split-h" : "split-v",
ratio: 0.5,
children: [
{ type: "pane", id: oldId },
{ type: "pane", id: newId },
],
}
return { found: true, nextId: nextIdVal + 1, newNode }
}
return { found: false, nextId: nextIdVal }
} else {
// Recursively check children
// We need to clone the node to avoid mutating state directly if we were modifying in place,
// but here we are rebuilding the tree structure where needed.
// Check left/top child
const leftResult = findAndSplitNode(node.children[0], activeId, direction, nextIdVal)
if (leftResult.found) {
return {
found: true,
nextId: leftResult.nextId,
newNode: {
...node,
children: [leftResult.newNode as LayoutNode, node.children[1]],
},
}
}
// Check right/bottom child
const rightResult = findAndSplitNode(node.children[1], activeId, direction, nextIdVal)
if (rightResult.found) {
return {
found: true,
nextId: rightResult.nextId,
newNode: {
...node,
children: [node.children[0], rightResult.newNode as LayoutNode],
},
}
}
return { found: false, nextId: nextIdVal }
}
}
function splitPane(direction: "h" | "v") {
const result = findAndSplitNode(layout, activePaneId, direction, nextId)
if (result.found && result.newNode) {
setNextId(result.nextId)
setLayout(result.newNode)
}
}
function resetLayout() {
setLayout({ type: "pane", id: 0 })
setNextId(1)
setActivePaneId(0)
}
// Generate config whenever layout changes
useEffect(() => {
let config = `# ~/.tmux.conf setup\n`
config += `# Configured by Shawn Anderson (Long Tail Financial)\n`
config += `# Generated by mytmux.life\n\n`
config += `new-session -s development -n editor\n`
function traverse(node: LayoutNode) {
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`
setGeneratedConfig(config)
}, [layout, activePaneId])
return (
<div className="flex flex-col gap-16 pb-20">
{/* Hero Section */}
<section className="container px-4 pt-20 md:pt-32">
<div className="grid gap-8 md:grid-cols-2 items-center">
<div className="space-y-6">
<div className="inline-flex items-center rounded-full border border-primary/50 bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
<span className="mr-2 h-2 w-2 rounded-full bg-primary animate-pulse"></span>
SYSTEM ONLINE
</div>
<h1 className="text-4xl font-extrabold tracking-tight lg:text-6xl glow-text">
Master Your <br />
<span className="text-primary">&lt;Terminal /&gt;</span>
</h1>
<p className="text-xl text-muted-foreground max-w-[600px]">
Stop wasting time switching windows. <span className="text-foreground font-bold">mytmux.life</span> helps
you architect the perfect terminal development environment.
</p>
<div className="flex flex-col sm:flex-row gap-4 pt-4">
<a
href="#configurator"
className="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"
className="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"
>
TMUX-IFESTO
</a>
</div>
</div>
{/* ASCII Art / Decorative Element */}
<div className="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>{ASCII_ART}</pre>
</div>
</div>
</section>
{/* Configurator Section */}
<section id="configurator" className="container px-4 py-12">
<div className="flex flex-col space-y-4 mb-8">
<h2 className="text-3xl font-bold tracking-tight border-l-4 border-primary pl-4">ENVIRONMENT_CONFIGURATOR</h2>
<p className="text-muted-foreground">
Visually design your session layout. Click actions to split the active pane.
</p>
</div>
<div className="grid lg:grid-cols-3 gap-8 h-[600px]">
{/* Controls & Output */}
<div className="flex flex-col gap-6 h-full">
<div className="p-6 border border-border bg-card space-y-6">
<h3 className="text-lg font-bold flex items-center gap-2">
<span className="text-primary">&gt;</span> ACTIONS
</h3>
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => splitPane("h")}
className="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 cursor-pointer"
>
<div className="flex gap-1 h-6 w-8 border border-current p-0.5">
<div className="w-1/2 h-full bg-current/50"></div>
<div className="w-1/2 h-full border border-current"></div>
</div>
<span className="text-xs">SPLIT_VERTICAL (%)</span>
</button>
<button
onClick={() => splitPane("v")}
className="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 cursor-pointer"
>
<div className="flex flex-col gap-1 h-8 w-6 border border-current p-0.5">
<div className="h-1/2 w-full bg-current/50"></div>
<div className="h-1/2 w-full border border-current"></div>
</div>
<span className="text-xs">SPLIT_HORIZONTAL (&quot;)</span>
</button>
</div>
<div className="pt-4 border-t border-border">
<div className="flex items-center justify-between mb-2">
<span className="text-xs text-muted-foreground">ACTIVE_PANE_ID:</span>
<span className="font-mono text-primary">{activePaneId}</span>
</div>
<div className="flex gap-2">
<button
onClick={resetLayout}
className="flex-1 py-2 text-xs bg-destructive/10 text-destructive hover:bg-destructive/20 border border-destructive/20 cursor-pointer"
>
RESET_LAYOUT
</button>
</div>
</div>
</div>
<div className="flex-1 p-6 border border-border bg-card flex flex-col min-h-0">
<h3 className="text-lg font-bold flex items-center gap-2 mb-4">
<span className="text-primary">&gt;</span> GENERATED_CONFIG
</h3>
<div className="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 className="lg:col-span-2 h-full border border-border bg-card p-1 relative">
<div className="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={layout} activePaneId={activePaneId} />
</div>
</div>
</section>
{/* Documentation / Info Section */}
<section id="learn" className="container px-4 py-12">
<div className="flex flex-col space-y-4 mb-8">
<h2 className="text-3xl font-bold tracking-tight border-l-4 border-primary pl-4">THE_TMUX_IFESTO</h2>
<p className="text-muted-foreground">The philosophy of persistent, multiplexed terminal environments.</p>
</div>
<div className="grid md:grid-cols-3 gap-12">
<div className="md:col-span-1 space-y-2">
<h3 className="text-xl font-bold text-primary">01. PERSISTENCE</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
Your work should not die when your connection drops. Detach your session, go home, and reattach exactly
where you left off. The session lives on the server, independent of your client.
</p>
</div>
<div className="md:col-span-1 space-y-2">
<h3 className="text-xl font-bold text-primary">02. MULTIPLEXING</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
One terminal is never enough. Split your workspace into windows (tabs) and panes (tiled splits). Monitor
logs, edit code, and run git commands simultaneously in a single SSH connection.
</p>
</div>
<div className="md:col-span-1 space-y-2">
<h3 className="text-xl font-bold text-primary">03. CONFIGURATION</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
The terminal is your canvas. Script your layouts, bind custom keys, and automate your startup. A
well-configured tmux environment is a force multiplier for any developer.
</p>
</div>
</div>
</section>
{/* Mobile Setup Section */}
<section className="container px-4 py-12 border-t border-border">
<h2 className="text-2xl font-bold mb-8">MOBILE_DEPLOYMENT_PROTOCOLS</h2>
<div className="grid md:grid-cols-2 gap-8">
{/* Android Protocol */}
<div className="border border-border bg-card p-6 space-y-4">
<h3 className="text-xl font-bold text-primary flex items-center gap-2">
<span className="text-xs bg-primary text-black px-1">ANDROID</span>
<span>TERMUX_PROTOCOL</span>
</h3>
<div className="space-y-4 text-sm text-muted-foreground font-mono">
<div className="flex gap-4">
<span className="text-primary">01.</span>
<div>
<p className="mb-1 text-foreground">Acquire F-Droid</p>
<p>Download the F-Droid APK from f-droid.org to access the open-source repository.</p>
</div>
</div>
<div className="flex gap-4">
<span className="text-primary">02.</span>
<div>
<p className="mb-1 text-foreground">Install Termux</p>
<p>Search for "Termux" within F-Droid. Do not use the Play Store version (deprecated).</p>
</div>
</div>
<div className="flex gap-4">
<span className="text-primary">03.</span>
<div>
<p className="mb-1 text-foreground">Initialize Environment</p>
<div className="bg-black p-2 mt-1 border border-border text-xs">
<p>$ pkg update && pkg upgrade</p>
<p>$ pkg install tmux vim git</p>
</div>
</div>
</div>
</div>
</div>
{/* iOS Protocol */}
<div className="border border-border bg-card p-6 space-y-4">
<h3 className="text-xl font-bold text-primary flex items-center gap-2">
<span className="text-xs bg-primary text-black px-1">APPLE</span>
<span>ISH_SHELL_PROTOCOL</span>
</h3>
<div className="space-y-4 text-sm text-muted-foreground font-mono">
<div className="flex gap-4">
<span className="text-primary">01.</span>
<div>
<p className="mb-1 text-foreground">Acquire iSH Shell</p>
<p>Install "iSH Shell" from the App Store. This provides a localized Alpine Linux environment.</p>
</div>
</div>
<div className="flex gap-4">
<span className="text-primary">02.</span>
<div>
<p className="mb-1 text-foreground">Package Management</p>
<p>iSH uses Alpine's 'apk' package manager.</p>
</div>
</div>
<div className="flex gap-4">
<span className="text-primary">03.</span>
<div>
<p className="mb-1 text-foreground">Deploy Tmux</p>
<div className="bg-black p-2 mt-1 border border-border text-xs">
<p>$ apk update</p>
<p>$ apk add tmux vim openssh</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Cheat Sheet */}
<section className="container px-4 py-12 border-t border-border">
<h2 className="text-2xl font-bold mb-8">COMMAND_REFERENCE_MATRIX</h2>
<div className="grid md:grid-cols-2 gap-4">
{[
{ cmd: "tmux new -s <name>", desc: "Start new named session" },
{ cmd: "tmux a -t <name>", desc: "Attach to existing session" },
{ cmd: "tmux ls", desc: "List all active sessions" },
{ cmd: "Ctrl+b d", desc: "Detach from current session" },
{ cmd: "Ctrl+b %", desc: "Split pane vertically" },
{ cmd: 'Ctrl+b "', desc: "Split pane horizontally" },
{ cmd: "Ctrl+b <arrows>", desc: "Navigate between panes" },
{ cmd: "Ctrl+b z", desc: "Toggle pane zoom (maximize)" },
{ cmd: "Ctrl+b c", desc: "Create new window" },
{ cmd: "Ctrl+b n", desc: "Move to next window" },
{ cmd: "Ctrl+b ,", desc: "Rename current window" },
{ cmd: "Ctrl+b [", desc: "Enter copy/scroll mode" },
].map((item, i) => (
<div
key={i}
className="flex items-center justify-between p-4 border border-border bg-card/50 hover:bg-card transition-colors group"
>
<span className="text-muted-foreground group-hover:text-foreground transition-colors text-sm font-mono">
{item.desc}
</span>
<code className="bg-secondary px-2 py-1 text-primary text-xs border border-primary/20">{item.cmd}</code>
</div>
))}
</div>
</section>
{/* Learning Resources Section */}
<section className="container px-4 py-12 border-t border-border">
<h2 className="text-2xl font-bold mb-8">LEARNING_RESOURCES</h2>
{/* Video Tutorials */}
<div className="mb-12">
<h3 className="text-xl font-bold text-primary mb-4">VIDEO_TUTORIALS</h3>
<div className="grid md:grid-cols-3 gap-6">
<a
href="https://www.youtube.com/watch?v=niuOc02Rvrc"
target="_blank"
rel="noopener noreferrer"
className="border border-border bg-card p-6 hover:bg-card/80 hover:border-primary transition-all group"
>
<div className="flex items-center gap-2 mb-2">
<span className="text-primary text-xl"></span>
<span className="text-sm font-mono text-muted-foreground group-hover:text-foreground">YouTube</span>
</div>
<p className="text-sm">Intro to tmux</p>
</a>
<a
href="https://www.youtube.com/watch?v=jaI3Hcw-ZaA"
target="_blank"
rel="noopener noreferrer"
className="border border-border bg-card p-6 hover:bg-card/80 hover:border-primary transition-all group"
>
<div className="flex items-center gap-2 mb-2">
<span className="text-primary text-xl"></span>
<span className="text-sm font-mono text-muted-foreground group-hover:text-foreground">YouTube</span>
</div>
<p className="text-sm">Advanced tmux workflows</p>
</a>
<a
href="https://www.youtube.com/watch?v=nTqu6w2wc68"
target="_blank"
rel="noopener noreferrer"
className="border border-border bg-card p-6 hover:bg-card/80 hover:border-primary transition-all group"
>
<div className="flex items-center gap-2 mb-2">
<span className="text-primary text-xl"></span>
<span className="text-sm font-mono text-muted-foreground group-hover:text-foreground">YouTube</span>
</div>
<p className="text-sm">Deep dive into tmux</p>
</a>
</div>
</div>
{/* Credits and References */}
<div className="border border-primary/30 bg-primary/5 p-6">
<h3 className="text-xl font-bold text-primary mb-4">INSPIRATION_&_CREDITS</h3>
<div className="space-y-4 text-sm text-muted-foreground">
<p className="text-foreground">
Special thanks to{" "}
<a
href="https://github.com/dvbuntu"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline font-mono"
>
Danielle Van Boxel
</a>{" "}
for inspiring much of Shawn Anderson's early tmux.conf configuration.
</p>
<div className="flex flex-col sm:flex-row gap-4 pt-2">
<a
href="https://github.com/dvbuntu/.files/blob/master/.tmux.conf"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 border border-border bg-card hover:bg-card/80 hover:border-primary transition-all font-mono text-xs"
>
<span className="text-primary">&gt;</span>
View tmux.conf
</a>
<a
href="https://github.com/dvbuntu"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 border border-border bg-card hover:bg-card/80 hover:border-primary transition-all font-mono text-xs"
>
<span className="text-primary">&gt;</span>
GitHub Profile
</a>
</div>
</div>
</div>
</section>
</div>
)
}