-
+
@@ -59,7 +64,7 @@ export default function MacBar({ fullscreenRef }: MacBarProps) {
- {window.location.hostname}
+ {hostname}
diff --git a/src/components/MainButton.tsx b/src/components/MainButton.tsx
index d5eb026..871a9dd 100644
--- a/src/components/MainButton.tsx
+++ b/src/components/MainButton.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Link } from 'react-router'
+import Link from 'next/link'
type MainButtonProps = {
children: React.ReactNode
@@ -16,7 +16,7 @@ export function MainButton({ children, link, variant = 'secondary' }: MainButton
}
return (
-
+
diff --git a/src/components/TerminalHandler.tsx b/src/components/TerminalHandler.tsx
index 5625832..5a55623 100644
--- a/src/components/TerminalHandler.tsx
+++ b/src/components/TerminalHandler.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, {
ReactNode,
useContext,
@@ -5,9 +7,9 @@ import React, {
useRef,
useState,
} from "react";
+import { useRouter } from "next/navigation";
import { KeyPressContext } from "../context/KeypressedContext";
import { commandMap } from "./commands/CommandMap";
-import { useNavigate } from "react-router";
import { extractText } from "../utils/textExtraction";
import { loadPersistedColor } from "../utils/colorPersistence";
import { fileSystem } from "../services/filesystem";
@@ -32,7 +34,7 @@ const PROMPT = "$ ";
const TerminalHandler = ({ onBufferChange, headless = false }: Props) => {
const context = useContext(KeyPressContext);
- const navigate = useNavigate();
+ const router = useRouter();
if (!context)
throw new Error("TerminalHandler must be used within KeyPressProvider");
@@ -436,7 +438,7 @@ const TerminalHandler = ({ onBufferChange, headless = false }: Props) => {
if (command) {
const result = await command.run(args, context);
- if (command.name === "exit") setTimeout(() => navigate("/"), 1000);
+ if (command.name === "exit") setTimeout(() => router.push("/"), 1000);
if (result) {
pushLine(
diff --git a/src/components/chat/tools/index.tsx b/src/components/chat/tools/index.tsx
index c0aa2c6..d74f3c3 100644
--- a/src/components/chat/tools/index.tsx
+++ b/src/components/chat/tools/index.tsx
@@ -1,3 +1,5 @@
+'use client'
+
/**
* Tool Renderer System
*
diff --git a/src/components/commands/CalcCommand.tsx b/src/components/commands/CalcCommand.tsx
index 8c9a8e3..5ea8194 100644
--- a/src/components/commands/CalcCommand.tsx
+++ b/src/components/commands/CalcCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { KeyPressContextType } from '../../context/KeypressedContext'
import { Calculator, Equal } from 'lucide-react'
diff --git a/src/components/commands/ColorCommand.tsx b/src/components/commands/ColorCommand.tsx
index e1d6c64..849d6b0 100644
--- a/src/components/commands/ColorCommand.tsx
+++ b/src/components/commands/ColorCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { KeyPressContextType } from '../../context/KeypressedContext'
import { Palette, Check, RotateCcw, Eye } from 'lucide-react'
diff --git a/src/components/commands/CvCommand.tsx b/src/components/commands/CvCommand.tsx
index 9c4e105..62ee0e5 100644
--- a/src/components/commands/CvCommand.tsx
+++ b/src/components/commands/CvCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { KeyPressContextType } from '../../context/KeypressedContext'
import { FileText, ExternalLink } from 'lucide-react'
diff --git a/src/components/commands/HelpCommand.tsx b/src/components/commands/HelpCommand.tsx
index fc30a85..08afa4c 100644
--- a/src/components/commands/HelpCommand.tsx
+++ b/src/components/commands/HelpCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { commandMap } from './CommandMap'
import { HelpCircle, Terminal, ChevronRight, AlertCircle } from 'lucide-react'
diff --git a/src/components/commands/IpCommand.tsx b/src/components/commands/IpCommand.tsx
index e911670..a8af000 100644
--- a/src/components/commands/IpCommand.tsx
+++ b/src/components/commands/IpCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { KeyPressContextType } from '../../context/KeypressedContext'
import { Globe, MapPin, Wifi, ExternalLink, AlertCircle } from 'lucide-react'
diff --git a/src/components/commands/NodeCommand.tsx b/src/components/commands/NodeCommand.tsx
index 8d6c0fa..ab82721 100644
--- a/src/components/commands/NodeCommand.tsx
+++ b/src/components/commands/NodeCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { KeyPressContextType } from '../../context/KeypressedContext'
import { fileSystem } from '../../services/filesystem'
import { getJavaScriptExecutor } from '../../services/executor/JavaScriptExecutor'
diff --git a/src/components/commands/PythonCommand.tsx b/src/components/commands/PythonCommand.tsx
index 24d280b..5750034 100644
--- a/src/components/commands/PythonCommand.tsx
+++ b/src/components/commands/PythonCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { KeyPressContextType } from '../../context/KeypressedContext'
import { fileSystem } from '../../services/filesystem'
import { getPythonExecutor } from '../../services/executor/PythonExecutor'
diff --git a/src/components/commands/WeatherCommand.tsx b/src/components/commands/WeatherCommand.tsx
index 628abc7..1ae108b 100644
--- a/src/components/commands/WeatherCommand.tsx
+++ b/src/components/commands/WeatherCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from './Command'
+import Command from './Command'
import { KeyPressContextType } from '../../context/KeypressedContext'
import { Cloud, Thermometer, Wind, Droplets, Sun, MapPin, AlertCircle } from 'lucide-react'
diff --git a/src/components/commands/fs/ClearFsCommand.tsx b/src/components/commands/fs/ClearFsCommand.tsx
index b6d9924..4c8d77e 100644
--- a/src/components/commands/fs/ClearFsCommand.tsx
+++ b/src/components/commands/fs/ClearFsCommand.tsx
@@ -66,7 +66,7 @@ export const ClearFsCommand: Command = {
// Perform reset
gitService.initialize()
fileSystem.reset()
- gitService.reset()
+ gitService.clearState()
envService.reset()
return (
diff --git a/src/components/commands/vim/VimCommand.tsx b/src/components/commands/vim/VimCommand.tsx
index e533a31..90adf3e 100644
--- a/src/components/commands/vim/VimCommand.tsx
+++ b/src/components/commands/vim/VimCommand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Command } from '../Command'
+import Command from '../Command'
import { KeyPressContextType } from '../../../context/KeypressedContext'
import { fileSystem } from '../../../services/filesystem'
import { FileText } from 'lucide-react'
diff --git a/src/components/commands/vim/VimEditor.tsx b/src/components/commands/vim/VimEditor.tsx
index 327812b..fe9a8b8 100644
--- a/src/components/commands/vim/VimEditor.tsx
+++ b/src/components/commands/vim/VimEditor.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react'
import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'
import { vim, Vim, getCM } from '@replit/codemirror-vim'
@@ -222,7 +224,8 @@ export function VimEditor({ filename, initialContent, onSave, onClose }: VimEdit
})
// :q - Quit (with :q! support via bang parameter)
- Vim.defineEx('quit', 'q', (_cm: any, params: { bang?: boolean }) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ Vim.defineEx('quit', 'q', (_cm: any, params: any) => {
// :q! - force quit without saving
if (params?.bang) {
onClose()
@@ -273,7 +276,8 @@ export function VimEditor({ filename, initialContent, onSave, onClose }: VimEdit
if (editorRef.current?.view) {
const cm = getCM(editorRef.current.view)
if (cm && cm.state && cm.state.vim) {
- const vimState = cm.state.vim
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const vimState = cm.state.vim as any
let newMode: VimMode = 'normal'
if (vimState.insertMode) {
newMode = 'insert'
diff --git a/src/components/terminal/AmbientEffects.tsx b/src/components/terminal/AmbientEffects.tsx
index b98bfe9..417bb7f 100644
--- a/src/components/terminal/AmbientEffects.tsx
+++ b/src/components/terminal/AmbientEffects.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, { useRef, useMemo } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
@@ -53,7 +55,7 @@ function DustParticles() {
const particlesRef = useRef
(null)
const particleCount = 50
- const { positions, velocities } = useMemo(() => {
+ const { geometry, velocities } = useMemo(() => {
const positions = new Float32Array(particleCount * 3)
const velocities = new Float32Array(particleCount * 3)
@@ -73,7 +75,10 @@ function DustParticles() {
velocities[i * 3 + 2] = (Math.random() - 0.5) * 0.001
}
- return { positions, velocities }
+ const geometry = new THREE.BufferGeometry()
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
+
+ return { geometry, velocities }
}, [])
useFrame(() => {
@@ -101,15 +106,7 @@ function DustParticles() {
})
return (
-
-
-
-
+
{/* CRT warm-up glow */}
{(phase === 'warming' || phase === 'booting') && (
diff --git a/src/components/terminal/CRTEffects.tsx b/src/components/terminal/CRTEffects.tsx
index 5f571a5..b148e2f 100644
--- a/src/components/terminal/CRTEffects.tsx
+++ b/src/components/terminal/CRTEffects.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React from 'react'
import { Bloom, ChromaticAberration, Scanline } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'
diff --git a/src/components/terminal/ReverseSearchPrompt.tsx b/src/components/terminal/ReverseSearchPrompt.tsx
index b1e8302..4b87fa3 100644
--- a/src/components/terminal/ReverseSearchPrompt.tsx
+++ b/src/components/terminal/ReverseSearchPrompt.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React from 'react'
import Cursor from '../Cursor'
import { Search } from 'lucide-react'
diff --git a/src/components/terminal/TerminalCanvas.tsx b/src/components/terminal/TerminalCanvas.tsx
index 0b4e262..52e3a37 100644
--- a/src/components/terminal/TerminalCanvas.tsx
+++ b/src/components/terminal/TerminalCanvas.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, { useState, useCallback, useEffect } from 'react'
import { isMobile } from 'react-device-detect'
import { Monitor, Zap, ZapOff, ExternalLink } from 'lucide-react'
diff --git a/src/components/terminal/TerminalOutput.tsx b/src/components/terminal/TerminalOutput.tsx
index 1917618..9c73872 100644
--- a/src/components/terminal/TerminalOutput.tsx
+++ b/src/components/terminal/TerminalOutput.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, { ReactNode } from 'react'
interface TerminalOutputProps {
diff --git a/src/components/terminal/TerminalPrompt.tsx b/src/components/terminal/TerminalPrompt.tsx
index 24840e0..7f42084 100644
--- a/src/components/terminal/TerminalPrompt.tsx
+++ b/src/components/terminal/TerminalPrompt.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import React, { useState, useEffect } from 'react'
import Cursor from '../Cursor'
import { GitBranch } from 'lucide-react'
diff --git a/src/components/terminal/TerminalScene.tsx b/src/components/terminal/TerminalScene.tsx
index 2aa6185..047912a 100644
--- a/src/components/terminal/TerminalScene.tsx
+++ b/src/components/terminal/TerminalScene.tsx
@@ -1,6 +1,8 @@
+'use client'
+
import { OrbitControls, useGLTF } from '@react-three/drei'
import { Canvas, useThree } from '@react-three/fiber'
-import React, { useMemo, useEffect, useRef, useCallback } from 'react'
+import React, { useMemo, useEffect, useRef, useCallback, useState } from 'react'
import * as THREE from 'three'
import { EffectComposer } from '@react-three/postprocessing'
import CRTEffects from './CRTEffects'
@@ -12,7 +14,13 @@ import type { TerminalSceneProps, CameraConfig } from '../../types/terminal3d'
* Hook that returns a CanvasTexture driven by terminal lines
*/
export function useTerminalTexture(lines: string[]) {
+ const [color, setColor] = useState('#0f0')
+
const { canvas, ctx, texture } = useMemo(() => {
+ // Guard for SSR - return dummy values that will be replaced on client
+ if (typeof document === 'undefined') {
+ return { canvas: null as HTMLCanvasElement | null, ctx: null as CanvasRenderingContext2D | null, texture: new THREE.CanvasTexture(new ImageData(1, 1)) }
+ }
const canvas = document.createElement('canvas')
canvas.width = 1024
canvas.height = 768
@@ -27,18 +35,22 @@ export function useTerminalTexture(lines: string[]) {
const visibleHeight = 768
const margin = 20
const lineHeight = 42
- const maxWidth = canvas.width - margin * 2
+ const maxWidth = canvas ? canvas.width - margin * 2 : 1024 - margin * 2
const cursorPosRef = useRef<{ x: number; y: number; visible: boolean }>({ x: 0, y: 0, visible: true })
const cursorVisibleRef = useRef(true)
const linesRef = useRef([])
- const color = useMemo(() => {
- const c = getComputedStyle(document.documentElement).getPropertyValue('--terminal').trim() || '#0f0'
- return c === '#22c55e' ? '#0f0' : c
+ // Get terminal color on client side
+ useEffect(() => {
+ if (typeof document !== 'undefined') {
+ const c = getComputedStyle(document.documentElement).getPropertyValue('--terminal').trim() || '#0f0'
+ setColor(c === '#22c55e' ? '#0f0' : c)
+ }
}, [])
useEffect(() => {
+ if (!canvas || !ctx) return
linesRef.current = lines
ctx.clearRect(0, 0, canvas.width, canvas.height)
@@ -107,6 +119,8 @@ export function useTerminalTexture(lines: string[]) {
}, [lines, color, canvas, ctx, texture, maxWidth])
useEffect(() => {
+ if (!canvas || !ctx) return
+
const cursorWidth = 15
const cursorHeight = lineHeight
@@ -230,9 +244,23 @@ export default function TerminalScene({
cameraConfig: customConfig,
onDoubleClick,
}: TerminalSceneProps) {
+ const [mounted, setMounted] = useState(false)
const screenTexture = useTerminalTexture(buffer)
const cameraConfig = { ...DEFAULT_CAMERA_CONFIG, ...customConfig }
+ // Wait for client-side mount before rendering Canvas
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ if (!mounted) {
+ return (
+
+ Initializing WebGL...
+
+ )
+ }
+
return (