zeroed-some/bashamole / 4f93540

Browse files

prettier terminal emulator component

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
4f935408d41412df15acf7179513adfaaa29ba71
Parents
0a7ec06
Tree
59b7d08

3 changed files

StatusFile+-
M frontend/src/app/globals.css 6 0
M frontend/src/components/Game.tsx 68 45
M frontend/src/components/TreeVisualizer.tsx 1 1
frontend/src/app/globals.cssmodified
@@ -45,4 +45,10 @@ body {
4545
 
4646
 .scrollbar-thumb-gray-700::-webkit-scrollbar-thumb:hover {
4747
   background-color: #4b5563;
48
+}
49
+
50
+/* Terminal cursor blink animation */
51
+@keyframes blink {
52
+  0%, 50% { opacity: 1; }
53
+  51%, 100% { opacity: 0; }
4854
 }
frontend/src/components/Game.tsxmodified
@@ -246,12 +246,19 @@ const Game: React.FC = () => {
246246
       </div>
247247
 
248248
       {/* Floating Terminal - Top Left */}
249
-      <div className={`absolute top-4 left-4 bg-gray-800/95 backdrop-blur-sm rounded-lg shadow-2xl border border-gray-700 transition-all duration-300 z-30 ${
250
-        terminalMinimized ? 'w-80' : 'w-[500px]'
249
+      <div className={`absolute top-4 left-4 bg-gray-900 rounded-lg shadow-2xl border border-gray-800 transition-all duration-300 z-30 ${
250
+        terminalMinimized ? 'w-80' : 'w-[700px]'
251251
       }`}>
252252
         {/* Terminal Header */}
253
-        <div className="flex items-center justify-between bg-gray-700 px-4 py-2 rounded-t-lg">
254
-          <h3 className="text-sm font-semibold text-gray-300">Terminal</h3>
253
+        <div className="flex items-center justify-between bg-gray-800 px-4 py-2 rounded-t-lg border-b border-gray-700">
254
+          <div className="flex items-center gap-2">
255
+            <div className="flex gap-1.5">
256
+              <div className="w-3 h-3 bg-red-500 rounded-full"></div>
257
+              <div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
258
+              <div className="w-3 h-3 bg-green-500 rounded-full"></div>
259
+            </div>
260
+            <h3 className="text-sm font-medium text-gray-300 ml-2">bash</h3>
261
+          </div>
255262
           <button
256263
             onClick={() => setTerminalMinimized(!terminalMinimized)}
257264
             className="text-gray-400 hover:text-white transition"
@@ -262,52 +269,68 @@ const Game: React.FC = () => {
262269
 
263270
         {/* Terminal Content */}
264271
         {!terminalMinimized && (
265
-          <>
266
-            <div 
267
-              ref={terminalRef}
268
-              className="bg-black text-green-400 p-3 font-mono text-xs h-[300px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-700"
269
-            >
270
-              {commandHistory.map((entry, index) => (
271
-                <div key={index} className="mb-2">
272
-                  <div className="text-gray-400">
273
-                    {entry.command.startsWith('Game started!') ? (
274
-                      <span className="text-yellow-400">{entry.command}</span>
275
-                    ) : (
276
-                      <>$ {entry.command}</>
277
-                    )}
278
-                  </div>
279
-                  <div className={entry.success ? 'text-green-400' : 'text-red-400'}>
272
+          <div 
273
+            ref={terminalRef}
274
+            className="bg-black p-4 font-mono text-base h-[350px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-700"
275
+            onClick={() => inputRef.current?.focus()}
276
+          >
277
+            {commandHistory.map((entry, index) => (
278
+              <div key={index} className="mb-1">
279
+                <div className="flex items-start">
280
+                  <span className="text-green-400">groundskeeper@molehill</span>
281
+                  <span className="text-gray-400 mx-1">::</span>
282
+                  <span className="text-blue-400">{entry.command.startsWith('Hunt started!') ? '~' : gameState.tree?.player_location || '~'}</span>
283
+                  <span className="text-gray-400 ml-1">$</span>
284
+                  <span className={`ml-2 ${entry.command.startsWith('Hunt started!') ? 'text-yellow-400' : 'text-gray-300'}`}>
285
+                    {entry.command.startsWith('Hunt started!') ? '' : entry.command}
286
+                  </span>
287
+                </div>
288
+                {entry.output && (
289
+                  <div className={`${entry.success ? 'text-gray-300' : 'text-red-400'} ml-0 mt-1`}>
280290
                     {entry.output.split('\n').map((line, i) => (
281
-                      <div key={i} className="ml-2">{line}</div>
291
+                      <div key={i}>{line}</div>
282292
                     ))}
283293
                   </div>
294
+                )}
295
+              </div>
296
+            ))}
297
+            
298
+            {/* Current input line */}
299
+            <div className="flex items-start">
300
+              <span className="text-green-400">groundskeeper@molehill</span>
301
+              <span className="text-gray-400 mx-1">::</span>
302
+              <span className="text-blue-400">{gameState.tree?.player_location || '~'}</span>
303
+              <span className="text-gray-400 ml-1">$</span>
304
+              <form onSubmit={handleSubmit} className="flex-1 ml-2">
305
+                <div className="relative">
306
+                  <input
307
+                    ref={inputRef}
308
+                    type="text"
309
+                    value={command}
310
+                    onChange={(e) => setCommand(e.target.value)}
311
+                    disabled={executing || gameState.tree?.is_completed}
312
+                    className="w-full bg-transparent text-gray-300 outline-none caret-transparent"
313
+                    placeholder=""
314
+                    autoFocus
315
+                    spellCheck={false}
316
+                    autoComplete="off"
317
+                    autoCorrect="off"
318
+                    autoCapitalize="off"
319
+                  />
320
+                  {/* Blinking cursor */}
321
+                  <span 
322
+                    className="absolute text-gray-300 pointer-events-none"
323
+                    style={{ 
324
+                      left: `${command.length * 0.6}em`,
325
+                      animation: 'blink 1s step-end infinite'
326
+                    }}
327
+                  >
328
+                    _
329
+                  </span>
284330
                 </div>
285
-              ))}
331
+              </form>
286332
             </div>
287
-            
288
-            <form onSubmit={handleSubmit} className="border-t border-gray-700">
289
-              <div className="flex bg-gray-900">
290
-                <span className="bg-gray-800 px-3 py-2 text-green-400 font-mono text-sm">$</span>
291
-                <input
292
-                  ref={inputRef}
293
-                  type="text"
294
-                  value={command}
295
-                  onChange={(e) => setCommand(e.target.value)}
296
-                  disabled={executing || gameState.tree.is_completed}
297
-                  className="flex-1 px-3 py-2 bg-gray-900 text-green-400 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 font-mono placeholder-gray-600"
298
-                  placeholder="cd, ls, pwd, help, killall moles"
299
-                  autoFocus
300
-                />
301
-                <button
302
-                  type="submit"
303
-                  disabled={executing || gameState.tree.is_completed}
304
-                  className="px-4 py-2 bg-blue-600 text-white text-sm hover:bg-blue-700 disabled:bg-gray-700 disabled:text-gray-500 transition"
305
-                >
306
-                  {executing ? '...' : 'Run'}
307
-                </button>
308
-              </div>
309
-            </form>
310
-          </>
333
+          </div>
311334
         )}
312335
       </div>
313336
 
frontend/src/components/TreeVisualizer.tsxmodified
@@ -92,7 +92,7 @@ const TreeVisualizer: React.FC<TreeVisualizerProps> = ({
9292
   const LABEL_CONFIG = {
9393
     fontSize: 14,
9494
     fontWeight: { base: '500', player: '700' },
95
-    offset: { parent: -32, leaf: 38 },
95
+    offset: { parent: -32, leaf: 40 },
9696
     colors: { player: '#93C5FD', regular: '#E5E7EB' },
9797
     textShadow: '0 0 4px rgba(0,0,0,0.8)'
9898
   };