"use client" import { useEffect, useRef, useState } from "react" import type { LayoutNode } from "@/app/page" interface TerminalVisualizerProps { layout: LayoutNode activePaneId: number } export default function TerminalVisualizer({ layout, activePaneId }: TerminalVisualizerProps) { const canvasRef = useRef(null) const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }) const animationRef = useRef(0) const lastTimeRef = useRef(0) const cursorBlinkRef = useRef(true) // Colors const BG_COLOR = "#0a0a0a" const BORDER_COLOR = "#333333" const ACTIVE_BORDER_COLOR = "#00ff00" const TEXT_COLOR = "#00ff00" const MUTED_TEXT = "#444444" // Handle resize useEffect(() => { const handleResize = () => { if (canvasRef.current && canvasRef.current.parentElement) { const parent = canvasRef.current.parentElement const rect = parent.getBoundingClientRect() const dpr = window.devicePixelRatio || 1 canvasRef.current.width = rect.width * dpr canvasRef.current.height = rect.height * dpr setContainerSize({ width: rect.width, height: rect.height }) } } window.addEventListener("resize", handleResize) handleResize() return () => window.removeEventListener("resize", handleResize) }, []) // Animation Loop useEffect(() => { const animate = (time: number) => { if (time - lastTimeRef.current > 500) { cursorBlinkRef.current = !cursorBlinkRef.current lastTimeRef.current = time } // Trigger a redraw draw() animationRef.current = requestAnimationFrame(animate) } animationRef.current = requestAnimationFrame(animate) return () => cancelAnimationFrame(animationRef.current) }) // No dependency array to ensure it always has access to latest props via closure if needed, // but actually we should probably use refs for props if we want to avoid re-binding the loop. // However, since we call draw() which uses the props, we need to make sure draw() sees the latest props. // The best way in React for a canvas loop is often to use refs for the mutable state or just let the effect re-run. // Let's optimize: // Drawing Logic const draw = () => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext("2d") if (!ctx) return const dpr = window.devicePixelRatio || 1 const width = containerSize.width const height = containerSize.height // Reset transform to handle DPR correctly on every frame ctx.setTransform(dpr, 0, 0, dpr, 0, 0) // Clear ctx.fillStyle = BG_COLOR ctx.fillRect(0, 0, width, height) // Draw Layout drawNode(ctx, layout, 0, 0, width, height) } function drawNode(ctx: CanvasRenderingContext2D, node: LayoutNode, x: number, y: number, w: number, h: number) { // 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 // Use a pseudo-random based on ID and index to keep it stable 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 && cursorBlinkRef.current) { 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(ctx, node.children[0], x, y, w, h1) drawNode(ctx, 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(ctx, node.children[0], x, y, w1, h) drawNode(ctx, node.children[1], x + w1, y, w2, h) } } return (
{/* Scanline overlay for the canvas specifically */}
RENDERER: CANVAS_2D // {Math.round(containerSize.width)}x{Math.round(containerSize.height)}
) }