Monorepo for Aesthetic.Computer
aesthetic.computer
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Aesthetic Computer Shell</title>
7 <link rel="stylesheet" href="../node_modules/@xterm/xterm/css/xterm.css">
8 <style>
9 * { margin: 0; padding: 0; box-sizing: border-box; }
10 html, body {
11 width: 100%;
12 height: 100%;
13 overflow: hidden;
14 background: #0a0a12;
15 }
16 #terminal-container {
17 width: 100%;
18 height: calc(100% - 64px); /* Leave space at top */
19 margin-top: 64px; /* Push terminal down */
20 padding: 8px;
21 }
22 .xterm { height: 100%; }
23 .xterm-viewport { padding-top: 0 !important; }
24 </style>
25</head>
26<body>
27 <div id="terminal-container"></div>
28
29 <script>
30 // With nodeIntegration enabled, we can use require() directly
31 const { Terminal } = require('@xterm/xterm');
32 const { FitAddon } = require('@xterm/addon-fit');
33 const { WebglAddon } = require('@xterm/addon-webgl');
34 const { ipcRenderer } = require('electron');
35
36 const terminalContainer = document.getElementById('terminal-container');
37
38 // Initialize terminal with performance optimizations
39 const term = new Terminal({
40 cursorBlink: true,
41 cursorStyle: 'block',
42 fontSize: 14,
43 fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
44 theme: {
45 background: '#0a0a12',
46 foreground: '#eee',
47 cursor: '#f0f',
48 cursorAccent: '#0a0a12',
49 selectionBackground: 'rgba(255, 0, 255, 0.3)',
50 black: '#1a1a2e',
51 red: '#ff5555',
52 green: '#50fa7b',
53 yellow: '#f1fa8c',
54 blue: '#6272a4',
55 magenta: '#ff79c6',
56 cyan: '#8be9fd',
57 white: '#f8f8f2',
58 },
59 allowTransparency: false, // Disabled for WebGL performance
60 scrollback: 5000,
61 fastScrollModifier: 'alt',
62 fastScrollSensitivity: 5,
63 });
64
65 const fitAddon = new FitAddon();
66 term.loadAddon(fitAddon);
67 term.open(terminalContainer);
68
69 // Load WebGL addon for GPU-accelerated rendering
70 try {
71 const webglAddon = new WebglAddon();
72 webglAddon.onContextLoss(() => {
73 webglAddon.dispose();
74 });
75 term.loadAddon(webglAddon);
76 console.log('[shell] WebGL renderer enabled');
77 } catch (e) {
78 console.warn('[shell] WebGL not available, using canvas renderer');
79 }
80
81 // Fit after a short delay to ensure container is sized
82 setTimeout(() => fitAddon.fit(), 50);
83
84 // Resize handling
85 const resizeObserver = new ResizeObserver(() => {
86 fitAddon.fit();
87 ipcRenderer.send('pty-resize', term.cols, term.rows);
88 });
89 resizeObserver.observe(terminalContainer);
90
91 // Connect to PTY
92 async function initShell() {
93 try {
94 // Check Docker
95 const dockerOk = await ipcRenderer.invoke('check-docker');
96 if (!dockerOk) {
97 term.writeln('\x1b[31mError: Docker not running\x1b[0m');
98 return;
99 }
100
101 // Check if container is running
102 const containerRunning = await ipcRenderer.invoke('check-container');
103 if (!containerRunning) {
104 // Check if container exists but is stopped
105 const containerExists = await ipcRenderer.invoke('check-container-exists');
106 if (containerExists) {
107 term.writeln('\x1b[33mStarting existing container...\x1b[0m');
108 await ipcRenderer.invoke('start-existing-container');
109 } else {
110 term.writeln('\x1b[33mCreating devcontainer...\x1b[0m');
111 await ipcRenderer.invoke('start-container');
112 }
113 }
114
115 // Connect PTY
116 const connected = await ipcRenderer.invoke('connect-pty');
117 if (!connected) {
118 term.writeln('\x1b[31mFailed to connect to shell\x1b[0m');
119 return;
120 }
121
122 // PTY data → terminal
123 ipcRenderer.on('pty-data', (event, data) => {
124 term.write(data);
125 });
126
127 // Terminal input → PTY
128 term.onData((data) => {
129 ipcRenderer.send('pty-input', data);
130 });
131
132 // PTY exit
133 ipcRenderer.on('pty-exit', (event, code) => {
134 term.writeln(`\r\n\x1b[33mShell exited with code ${code}\x1b[0m`);
135 });
136
137 // Send initial resize
138 ipcRenderer.send('pty-resize', term.cols, term.rows);
139
140 // Auto-start emacs after a brief delay for shell to initialize
141 setTimeout(() => {
142 ipcRenderer.send('pty-input', 'ac-aesthetic\n');
143 }, 500);
144
145 } catch (err) {
146 term.writeln(`\x1b[31mError: ${err.message}\x1b[0m`);
147 }
148 }
149
150 // Expose reboot function globally for easy access
151 window.rebootApp = async () => {
152 console.log('Requesting app reboot...');
153 try {
154 const result = await ipcRenderer.invoke('app-reboot');
155 console.log('Reboot initiated:', result);
156 return result;
157 } catch (err) {
158 console.error('Reboot failed:', err);
159 throw err;
160 }
161 };
162
163 initShell();
164 </script>
165</body>
166</html>