zeroed-some/bashamole / 5d19b36

Browse files

style mostly

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5d19b36fd30861387adb0c0008bfa71e6ac9099a
Parents
c2a21f5
Tree
f12ffaa

4 changed files

StatusFile+-
M frontend/src/app/globals.css 5 0
M frontend/src/app/layout.tsx 27 2
M frontend/src/components/Game.tsx 12 12
M frontend/src/components/TreeVisualizer.tsx 72 8
frontend/src/app/globals.cssmodified
@@ -25,6 +25,11 @@ body {
2525
   font-family: Arial, Helvetica, sans-serif;
2626
 }
2727
 
28
+/* Custom terminal font class */
29
+.font-terminal {
30
+  font-family: var(--font-terminal, 'Space Mono', 'Courier New', monospace);
31
+}
32
+
2833
 /* Custom scrollbar for terminal */
2934
 .scrollbar-thin {
3035
   scrollbar-width: thin;
frontend/src/app/layout.tsxmodified
@@ -1,13 +1,38 @@
11
 // src/app/layout.tsx
22
 import type { Metadata } from "next";
3
-import { Inter } from "next/font/google";
3
+import { Inter, Anonymous_Pro } from "next/font/google";
44
 import "./globals.css";
55
 
66
 const inter = Inter({ subsets: ["latin"] });
7
+const spaceMono = Anonymous_Pro({ 
8
+  subsets: ["latin"],
9
+  weight: ['400', '700'],
10
+  variable: '--font-terminal',
11
+});
712
 
813
 export const metadata: Metadata = {
914
   title: "Bashamole",
1015
   description: "A game to practice Unix navigation by hunting moles in the filesystem",
16
+  icons: {
17
+    icon: [
18
+      { url: '/favicon.ico' },
19
+      { url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
20
+      { url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
21
+    ],
22
+    apple: [
23
+      { url: '/apple-touch-icon.png' },
24
+    ],
25
+    other: [
26
+      {
27
+        rel: 'android-chrome-192x192',
28
+        url: '/android-chrome-192x192.png',
29
+      },
30
+      {
31
+        rel: 'android-chrome-512x512',
32
+        url: '/android-chrome-512x512.png',
33
+      },
34
+    ],
35
+  },
1136
 };
1237
 
1338
 export default function RootLayout({
@@ -16,7 +41,7 @@ export default function RootLayout({
1641
   children: React.ReactNode;
1742
 }>) {
1843
   return (
19
-    <html lang="en" className="h-full">
44
+    <html lang="en" className={`h-full ${spaceMono.variable}`}>
2045
       <body className={`${inter.className} h-full`}>{children}</body>
2146
     </html>
2247
   );
frontend/src/components/Game.tsxmodified
@@ -316,12 +316,12 @@ const Game: React.FC = () => {
316316
         {!terminalMinimized && (
317317
           <div 
318318
             ref={terminalRef}
319
-            className={`${terminalColors.content} p-4 font-mono text-base h-[350px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-700`}
319
+            className={`${terminalColors.content} p-4 font-terminal text-base h-[350px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-700`}
320320
             onClick={() => inputRef.current?.focus()}
321321
           >
322322
             {commandHistory.map((entry, index) => (
323323
               <div key={index} className="mb-1">
324
-                <div className="flex items-start font-mono">
324
+                <div className="flex items-start font-terminal">
325325
                   <span className="text-green-400">groundskeeper@molehill</span>
326326
                   <span className="text-gray-400 mx-1">::</span>
327327
                   <span className="text-blue-400">{entry.command.startsWith('Hunt started!') ? '~' : gameState.tree?.player_location || '~'}</span>
@@ -331,7 +331,7 @@ const Game: React.FC = () => {
331331
                   </span>
332332
                 </div>
333333
                 {entry.output && (
334
-                  <div className={`${entry.success ? 'text-gray-300' : 'text-red-400'} ml-0 mt-1 font-mono whitespace-pre-wrap`}>
334
+                  <div className={`${entry.success ? 'text-gray-300' : 'text-red-400'} ml-0 mt-1 font-terminal whitespace-pre-wrap`}>
335335
                     {entry.output.split('\n').map((line, i) => (
336336
                       <div key={i}>{line}</div>
337337
                     ))}
@@ -341,16 +341,16 @@ const Game: React.FC = () => {
341341
             ))}
342342
             
343343
             {/* Current input line */}
344
-            <div className="flex items-start font-mono">
344
+            <div className="flex items-start font-terminal">
345345
               <span className="text-green-400">groundskeeper@molehill</span>
346346
               <span className="text-gray-400 mx-1">::</span>
347347
               <span className="text-blue-400">{gameState.tree?.player_location || '~'}</span>
348348
               <span className="text-gray-400 ml-1">$</span>
349349
               <div className="flex-1 ml-2">
350350
                 <div className="relative inline-block">
351
-                  <span className="text-gray-300 font-mono">{command}</span>
351
+                  <span className="text-gray-300 font-terminal">{command}</span>
352352
                   <span 
353
-                    className="text-gray-300 font-mono"
353
+                    className="text-gray-300 font-terminal"
354354
                     style={{ 
355355
                       animation: 'blink 1s step-end infinite'
356356
                     }}
@@ -369,7 +369,7 @@ const Game: React.FC = () => {
369369
                       }
370370
                     }}
371371
                     disabled={executing || gameState.tree?.is_completed}
372
-                    className="absolute inset-0 w-full bg-transparent text-transparent outline-none caret-transparent font-mono"
372
+                    className="absolute inset-0 w-full bg-transparent text-transparent outline-none caret-transparent font-terminal"
373373
                     placeholder=""
374374
                     autoFocus
375375
                     spellCheck={false}
@@ -404,16 +404,16 @@ const Game: React.FC = () => {
404404
       <div className={`absolute bottom-0 left-0 right-0 ${isDarkMode ? 'bg-gray-900/90' : 'bg-white/90'} backdrop-blur-sm border-t ${isDarkMode ? 'border-gray-700' : 'border-gray-300'} p-4 z-20`}>
405405
         <div className="max-w-7xl mx-auto flex justify-between items-center">
406406
           <div className="flex items-center gap-4">
407
-            <h1 className={`text-2xl font-bold ${isDarkMode ? 'text-white' : 'text-gray-900'}`}>Bashamole</h1>
408
-            <div className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
409
-              Location: <span className="font-mono text-blue-600 dark:text-blue-400">{gameState.tree.player_location}</span>
410
-            </div>
407
+            <h1 className={`text-2xl font-bold ${isDarkMode ? 'text-white' : 'text-gray-900'}`}><span className='font-terminal bg-gray-200 dark:bg-gray-700 text-red-900 dark:text-red-400 px-1 py-0 rounded'>bash</span>amole</h1>
408
+            {/* <div className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
409
+              Location: <span className="font-terminal text-blue-600 dark:text-blue-400">{gameState.tree.player_location}</span>
410
+            </div> */}
411411
           </div>
412412
           
413413
           <div className="flex items-center gap-3">
414414
             {gameState.tree.is_completed ? (
415415
               <div className="text-green-600 dark:text-green-400 font-bold animate-pulse">
416
-                You found the mole!
416
+                You found a mole!
417417
               </div>
418418
             ) : (
419419
               <>
frontend/src/components/TreeVisualizer.tsxmodified
@@ -297,6 +297,43 @@ const TreeVisualizer: React.FC<TreeVisualizerProps> = ({
297297
     feMerge.append('feMergeNode').attr('in', 'coloredBlur');
298298
     feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
299299
 
300
+    // Add a subtle pulse animation for adjacent nodes
301
+    const pulseAnimation = defs.append('style')
302
+      .text(`
303
+        @keyframes subtlePulse {
304
+          0%, 100% { opacity: 1; }
305
+          50% { opacity: 0.8; }
306
+        }
307
+        .adjacent-node {
308
+          animation: subtlePulse 2s ease-in-out infinite;
309
+        }
310
+      `);
311
+
312
+    // Helper function to check if a node is adjacent to the current location
313
+    const isAdjacentNode = (nodePath: string, currentPath: string): boolean => {
314
+      // Check if it's the parent directory
315
+      if (currentPath !== '/') {
316
+        const parentPath = currentPath.substring(0, currentPath.lastIndexOf('/')) || '/';
317
+        if (nodePath === parentPath) return true;
318
+      }
319
+      
320
+      // Check if it's a direct child
321
+      if (currentPath === '/') {
322
+        // For root, children are paths with exactly one segment
323
+        const segments = nodePath.split('/').filter(s => s);
324
+        if (segments.length === 1) return true;
325
+      } else {
326
+        // For other directories, check if it's a direct child
327
+        if (nodePath.startsWith(currentPath + '/')) {
328
+          const relativePath = nodePath.substring(currentPath.length + 1);
329
+          // Make sure there are no additional slashes (not a grandchild)
330
+          if (!relativePath.includes('/')) return true;
331
+        }
332
+      }
333
+      
334
+      return false;
335
+    };
336
+
300337
     // Add circles for nodes
301338
     node
302339
       .append('circle')
@@ -317,15 +354,34 @@ const TreeVisualizer: React.FC<TreeVisualizerProps> = ({
317354
         return NODE_CONFIG.colors.regular.stroke;
318355
       })
319356
       .style('stroke-width', NODE_CONFIG.strokeWidth.base)
320
-      .style('cursor', 'pointer')
357
+      .style('cursor', d => {
358
+        // Only show pointer cursor for adjacent nodes
359
+        if (d.data.path === playerLocation) return 'default';
360
+        return isAdjacentNode(d.data.path, playerLocation) ? 'pointer' : 'not-allowed';
361
+      })
321362
       .style('filter', d => d.data.path === playerLocation ? NODE_CONFIG.glowFilter : 'none')
363
+      .style('opacity', d => {
364
+        // Slightly fade non-adjacent nodes
365
+        if (d.data.path === playerLocation) return 1;
366
+        return isAdjacentNode(d.data.path, playerLocation) ? 1 : 0.6;
367
+      })
322368
       .style('transition', 'all 0.3s ease')
369
+      .attr('class', d => {
370
+        // Add class for adjacent nodes to enable pulse animation
371
+        if (d.data.path !== playerLocation && isAdjacentNode(d.data.path, playerLocation)) {
372
+          return 'adjacent-node';
373
+        }
374
+        return '';
375
+      })
323376
       .on('mouseover', function(event, d) {
377
+        // Only apply hover effect to adjacent nodes
378
+        if (d.data.path !== playerLocation && isAdjacentNode(d.data.path, playerLocation)) {
324379
           d3.select(this)
325380
             .transition()
326381
             .duration(ANIMATION_CONFIG.nodeHover.duration)
327382
             .attr('r', d.data.path === '/' ? NODE_CONFIG.sizes.root.hover : NODE_CONFIG.sizes.regular.hover)
328383
             .style('stroke-width', NODE_CONFIG.strokeWidth.hover);
384
+        }
329385
       })
330386
       .on('mouseout', function(event, d) {
331387
         d3.select(this)
@@ -337,7 +393,8 @@ const TreeVisualizer: React.FC<TreeVisualizerProps> = ({
337393
           .style('stroke-width', NODE_CONFIG.strokeWidth.base);
338394
       })
339395
       .on('click', (event, d) => {
340
-        if (onNodeClick) {
396
+        // Only allow clicks on adjacent nodes
397
+        if (onNodeClick && d.data.path !== playerLocation && isAdjacentNode(d.data.path, playerLocation)) {
341398
           onNodeClick(d.data.path);
342399
         }
343400
       });
@@ -345,7 +402,14 @@ const TreeVisualizer: React.FC<TreeVisualizerProps> = ({
345402
     // Add tooltips
346403
     node
347404
       .append('title')
348
-      .text(d => `${d.data.path}\n${d.data.description}\n${d.data.has_mole ? '🐭 Mole is here!' : ''}`);
405
+      .text(d => {
406
+        const baseText = `${d.data.path}\n${d.data.description}`;
407
+        const moleText = d.data.has_mole ? '\n🐭 Mole is here!' : '';
408
+        const clickableText = (d.data.path !== playerLocation && isAdjacentNode(d.data.path, playerLocation)) 
409
+          ? '\n(Click to navigate here)' 
410
+          : '';
411
+        return baseText + moleText + clickableText;
412
+      });
349413
 
350414
     // Add labels
351415
     node