# Terminal Portfolio

An Arch Linux terminal-themed portfolio website with Hyprland-style TUI components, built with SvelteKit, Tailwind CSS, and Three.js.

![Terminal Portfolio](./static/og-image.png)

## Features

- 🖥️ **Hyprland-style TUI** - Terminal interface inspired by Textual Python TUI
- 🎨 **Theme Support** - Arch Linux and Catppuccin (Mocha/Latte) themes
- 🌓 **Dark/Light Mode** - Toggle between dark and light modes
- ⌨️ **Keyboard Navigation** - Navigate with arrow keys or vim-style j/k
- 🎮 **3D Model Viewer** - Interactive Three.js viewer for .glb models
- ⚡ **Configurable Speed** - Per-page typing animation speed
- 📱 **Responsive** - Works on desktop and mobile
- 🎨 **Rich Text Formatting** - Colors, backgrounds, and text decorations

## Pages

- **Home** (`/`) - Neofetch-style intro with navigation
- **Portfolio** (`/portfolio`) - Skills, projects, and contact info
- **Models** (`/models`) - 3D model gallery with interactive viewer
- **Hackathons** (`/hackathons`) - Hackathon projects and achievements
- **Components** (`/components`) - Showcase of all TUI components

## Configuration

The site configuration is now **modular** — split into focused files in `src/lib/config/` for easier maintenance. You can still import everything from `$lib/config` for backward compatibility.

### Config File Structure

```
src/lib/config/
├── index.ts       # Barrel export (re-exports all modules)
├── user.ts        # User profile, socials, skills
├── layout.ts      # Layout dimensions, breakpoints, fonts, navbar, scrollbar
├── theme.ts       # Colors, animations, effects, loading screen
├── content.ts     # Projects, 3D models, hackathon cards
├── terminal.ts    # Terminal settings, TUI styling, speed presets, shortcuts
└── navigation.ts  # Navigation links, site metadata, page meta
```

### Import Examples

```typescript
// Barrel import (backward compatible)
import { user, colorPalette, projects } from '$lib/config';

// Direct imports (smaller bundles, faster builds)
import { user, skills } from '$lib/config/user';
import { colorPalette, animations } from '$lib/config/theme';
import { projects, models, cards } from '$lib/config/content';
import { terminalSettings, keyboardShortcuts } from '$lib/config/terminal';
import { navigation, site, pageMeta } from '$lib/config/navigation';
```

### Config Modules

| File | Contents |
|------|----------|
| `user.ts` | `user`, `skills` |
| `layout.ts` | `layout`, `breakpoints`, `fonts`, `navbar`, `scrollbar` |
| `theme.ts` | `colorPalette`, `terminalButtons`, `loadingScreen`, `effects`, `animations` |
| `content.ts` | `projects`, `models`, `cards`, `sortedCards` + types |
| `terminal.ts` | `terminalSettings`, `tuiStyle`, `tuiText`, `pageSpeedSettings`, `pageAutoscrollSettings`, `speedPresets`, `modelViewer`, `particles`, `keyboardShortcuts` |
| `navigation.ts` | `navigation`, `site`, `pageMeta` |

### Example: Key config snippets
```typescript
// Toggle theme keys and other shortcuts
export const keyboardShortcuts = {
  skip: ['y', 'Y'],           // Skip typing animation
  toggleTheme: ['t', 'T'],    // Toggle dark/light mode
  navigateUp: ['ArrowUp', 'k'],
  navigateDown: ['ArrowDown', 'j'],
  select: ['Enter'],
};

// Terminal / typing
export const terminalSettings = {
  baseTypeSpeed: 20,
  minTypeSpeed: 5,
  maxTypeSpeed: 50,
  startDelay: 300,
  lineDelay: 100,
  showCursor: true,
  promptStyle: 'full',
  icon: '🐧',
  scrollMargin: 80,
};

// Color palette (Catppuccin Mocha by default)
export const colorPalette = {
  red: '#f38ba8',
  green: '#a6e3a1',
  yellow: '#f9e2af',
  blue: '#89b4fa',
  magenta: '#cba6f7',
  cyan: '#94e2d5',
  white: '#cdd6f4',
  gray: '#6c7086',
  error: '#f38ba8',
  success: '#a6e3a1',
};

// TUI styling example
export const tuiStyle = {
  borderRadius: 8,
  borderWidth: 2,
  width: '95%',
  bodyPadding: '1rem 1.25rem 2rem 1.25rem',
  buttonPadding: '0.5rem 0.75rem',
};
```

### How to Customize

- Edit `config/user.ts` to update your profile, socials, and skills
- Edit `config/content.ts` to add projects, models, or hackathon entries
- Edit `config/theme.ts` to change colors, animations, or loading screen
- Edit `config/terminal.ts` to adjust typing speed, TUI styling, or shortcuts
- Edit `config/layout.ts` to change dimensions, breakpoints, or navbar settings
- Edit `config/navigation.ts` to add/remove nav links or update page metadata

Changes take effect on next reload. Some values (fonts, CSS variables) may also require adjusting CSS or Tailwind config.

### Where to Look for Types & Utilities

- `src/lib/config/` — All configuration modules
- `src/lib/components/tui/types.ts` — TerminalLine types
- `src/lib/components/tui/utils.ts` — Parsing utilities and style helpers
- `src/lib/stores/theme.ts` — Theme store & `toggleMode()`
- `src/lib/index.ts` — Helper functions (barrel export)

## Speed Presets

| Preset | Effect |
|--------|--------|
| `instant` | No animation, appears immediately |
| `fast` | 3x faster than normal |
| `normal` | Default typing speed |
| `slow` | 2x slower than normal |
| `typewriter` | 3x slower, classic feel |

## Text Formatting

Use inline formatting with the `(&specs)text(&)` syntax:

### Colors

```typescript
// Basic colors
'(&red)Red text(&)'
'(&green)Green text(&)'
'(&blue)Blue text(&)'
'(&yellow)Yellow text(&)'
'(&magenta)Magenta text(&)'
'(&cyan)Cyan text(&)'
'(&orange)Orange text(&)'
'(&pink)Pink text(&)'
'(&gray)Gray text(&)'
'(&white)White text(&)'

// Semantic colors (theme-aware)
'(&primary)Primary color(&)'
'(&accent)Accent color(&)'
'(&muted)Muted text(&)'
'(&error)Error text(&)'
'(&success)Success text(&)'
'(&warning)Warning text(&)'
'(&info)Info text(&)'

// Custom hex colors
'(&#ff6b6b)Custom color(&)'
```

### Background Colors

Add `bg-` prefix to any color:

```typescript
'(&bg-red)Red background(&)'
'(&bg-blue,white)Blue bg with white text(&)'
'(&bg-surface)Surface background(&)'
'(&bg-#333333)Custom bg color(&)'
```

### Text Styles

```typescript
'(&bold)Bold text(&)'
'(&italic)Italic text(&)'
'(&dim)Dimmed text (60% opacity)(&)'
'(&underline)Underlined text(&)'
'(&strikethrough)Strikethrough text(&)'
'(&strike)Strikethrough shorthand(&)'
'(&overline)Overlined text(&)'
```

### Combining Styles

Combine multiple styles with commas:

```typescript
'(&bold,red)Bold red text(&)'
'(&italic,cyan,underline)Italic cyan underlined(&)'
'(&bg-blue,white,bold)Bold white on blue(&)'
'(&dim,strikethrough,gray)Dim gray strikethrough(&)'
```

## Line Types

### Basic Lines

```typescript
const lines: TerminalLine[] = [
  { type: 'command', content: 'ls -la' },           // With prompt prefix
  { type: 'output', content: 'File listing...' },   // Muted text
  { type: 'error', content: 'Error message' },      // Red with ✗ prefix
  { type: 'success', content: 'Success!' },         // Green with ✓ prefix
  { type: 'info', content: 'Information' },         // Primary with › prefix
  { type: 'header', content: 'Section Title' },     // Bold with # icon
  { type: 'blank', content: '' },                   // Empty line
  { type: 'divider', content: 'SECTION', id: 'section' },  // Horizontal divider with anchor ID
];
```

### Line Properties

All line types support these optional properties:

```typescript
{
  type: 'output',
  content: 'Hello world',
  id: 'my-section',      // Anchor ID for URL hash scrolling (e.g., /page#my-section)
  inline: true,          // Render inline with adjacent inline elements
  delay: 500,            // Delay before this line appears (ms)
}
```

### Button (Full-width interactive)

```typescript
{
  type: 'button',
  content: 'Click me',
  icon: 'mdi:github',           // Iconify icon
  style: 'primary',             // primary | accent | warning | error
  href: 'https://github.com',   // URL to navigate to
  external: true,               // Open in new tab (auto-detected for http/https)
  inline: true,                 // Render as compact inline button
  // OR
  action: () => doSomething(),  // Custom action
}
```

### Inline Elements

Multiple elements can be rendered on the same line using `inline: true`:

```typescript
// These will appear on the same line
{ type: 'output', content: 'Status:', inline: true },
{ type: 'success', content: 'Online', inline: true },
{ type: 'button', content: 'Refresh', icon: 'mdi:refresh', inline: true },

// Next line without inline breaks the group
{ type: 'blank', content: '' },
```

Supported inline types: `button`, `link`, `tooltip`, `progress`, `output`, `info`, `success`, `error`, `warning`

### Link (Inline clickable text)

```typescript
{
  type: 'link',
  content: 'Visit GitHub',
  icon: 'mdi:github',           // Optional icon
  style: 'accent',              // Styling
  href: 'https://github.com',
  external: true,               // Open in new tab (auto-detected for http/https)
}
```

### Image

```typescript
{
  type: 'image',
  content: 'Caption text',      // Optional caption
  image: '/path/to/image.png',
  imageAlt: 'Alt text',
  imageWidth: 300,              // Max width in pixels
}
```

### Card

```typescript
{
  type: 'card',
  content: 'Card body text with (&bold)formatting(&) support',
  cardTitle: 'Card Title',      // Optional header
  cardFooter: 'Footer text',    // Optional footer
  icon: 'mdi:star',             // Optional header icon
  image: '/path/to/image.png',  // Optional card image
  style: 'primary',             // Border accent color
  display: 'flex',              // flex | grid | block (controls children layout)
  children: [                   // Optional nested elements
    { type: 'button', content: 'Action', style: 'primary' }
  ]
}
```

### Progress Bar

```typescript
{
  type: 'progress',
  content: 'Loading assets...',  // Label above bar
  progress: 75,                  // 0-100 percentage
  progressLabel: '75%',          // Custom label (defaults to percentage)
  style: 'accent',               // Bar color
}
```

### Accordion

```typescript
{
  type: 'accordion',
  content: '',
  accordionItems: [
    { title: 'Section 1', content: 'Content for section 1 with (&cyan)colors(&)' },
    { title: 'Section 2', content: 'Content for section 2' },
  ],
  accordionOpen: true,           // First item open by default
  style: 'primary',              // Accent color
}
```

### Table

```typescript
{
  type: 'table',
  content: 'Table Title',        // Optional title
  tableHeaders: ['Name', 'Role', 'Status'],
  tableRows: [
    ['Alice', 'Developer', '(&success)Active(&)'],
    ['Bob', 'Designer', '(&warning)Away(&)'],
  ],
  style: 'accent',               // Header color
}
```

### Tooltip

```typescript
{
  type: 'tooltip',
  content: 'Hover me',           // Trigger text
  tooltipText: 'This is helpful information!',
  tooltipPosition: 'top',        // top | bottom | left | right
  style: 'info',                 // Tooltip border color
}
```

### Group

Groups allow you to arrange multiple elements together with custom layout:

```typescript
{
  type: 'group',
  content: '',
  groupDirection: 'row',         // row | column (default: row)
  groupAlign: 'start',           // start | center | end
  groupGap: '1rem',              // CSS gap value
  inline: true,                  // Render inline with other elements
  children: [
    { type: 'output', content: 'Label:', inline: true },
    { type: 'button', content: 'Action', style: 'primary', inline: true },
    { type: 'link', content: 'More info', href: '/help', inline: true }
  ]
}
```

Groups support nested groups and all element types as children. Children are rendered using the same `TuiLine` component, ensuring consistent behavior.

## TUI Components

### TerminalTUI

Main terminal component:

```svelte
<TerminalTUI 
  lines={lines}
  title="~/directory"
  interactive={true}
  speed="normal"
  autoscroll={true}
  onComplete={() => console.log('Done!')}
/>
```

Props:
- `lines` - Array of TerminalLine objects
- `title` - Terminal window title
- `interactive` - Enable keyboard navigation
- `speed` - Typing speed preset or multiplier
- `autoscroll` - Auto-scroll as content types (default: true)
- `onComplete` - Callback when typing animation finishes

### Anchor Scrolling

Add `id` to any line to create an anchor that can be linked to:

```typescript
{ type: 'divider', content: 'SKILLS', id: 'skills' },
```

Then link to it with `/portfolio#skills` - the page will scroll to that section after typing completes.

### Component Structure

```
src/lib/components/
├── TerminalTUI.svelte      # Main terminal container
└── tui/
    ├── types.ts            # TypeScript types (TerminalLine, TerminalAPI, etc.)
    ├── utils.ts            # Parsing & styling utilities
    ├── terminal-api.ts     # Terminal API factory for reactive control
    ├── terminal-typing.ts  # Typing animation engine
    ├── terminal-keyboard.ts# Keyboard navigation handler
    ├── TuiHeader.svelte    # Top status bar
    ├── TuiBody.svelte      # Scrollable content area (uses TuiLine)
    ├── TuiFooter.svelte    # Bottom status bar
    ├── TuiLine.svelte      # Unified line renderer for all types
    ├── TuiGroup.svelte     # Container for grouped elements
    ├── TuiButton.svelte    # Full-width button
    ├── TuiLink.svelte      # Inline clickable link
    ├── TuiCard.svelte      # Card with header/body/footer
    ├── TuiCardGrid.svelte  # Grid layout for cards
    ├── TuiProgress.svelte  # Animated progress bar
    ├── TuiAccordion.svelte # Collapsible sections
    ├── TuiTable.svelte     # Data table with headers
    ├── TuiTooltip.svelte   # Hover tooltip
    ├── TuiInput.svelte     # Text input field
    ├── TuiTextarea.svelte  # Multi-line text input
    ├── TuiCheckbox.svelte  # Checkbox input
    ├── TuiRadio.svelte     # Radio button group
    ├── TuiSelect.svelte    # Dropdown select
    └── TuiToggle.svelte    # Toggle switch
```

## Terminal API

The `TerminalTUI` component exposes a reactive API for programmatic control via the `terminal` bindable prop.

### Setup

```svelte
<script lang="ts">
  import TerminalTUI from '$lib/components/TerminalTUI.svelte';
  import type { TerminalAPI, TerminalLine } from '$lib';
  
  let terminal = $state<TerminalAPI>();
  let lines = $state<TerminalLine[]>([
    { type: 'output', content: 'Hello world!' }
  ]);
</script>

<TerminalTUI bind:terminal bind:lines title="~/demo" />
```

### API Methods

#### Writing Lines
| Method | Description |
|--------|-------------|
| `terminal.write(line)` | Append a single line |
| `terminal.writeLines(lines)` | Append multiple lines |
| `terminal.clear()` | Remove all lines |
| `terminal.setLines(lines)` | Replace all lines |

#### Updating Lines
| Method | Description |
|--------|-------------|
| `terminal.update(index, updates)` | Update line by index with partial changes |
| `terminal.updateContent(index, content)` | Update just the content of a line |
| `terminal.updateById(id, updates)` | Update line by its `id` property |

#### Removing Lines
| Method | Description |
|--------|-------------|
| `terminal.remove(index)` | Remove line at index |
| `terminal.removeRange(start, count)` | Remove a range of lines |
| `terminal.removeById(id)` | Remove line by its `id` property |

#### Inserting Lines
| Method | Description |
|--------|-------------|
| `terminal.insert(index, line)` | Insert line at a specific index |

#### Reading State
| Method | Description |
|--------|-------------|
| `terminal.getLine(index)` | Get line at index |
| `terminal.getLines()` | Get all lines (copy) |
| `terminal.getLineCount()` | Get number of lines |
| `terminal.findById(id)` | Find index of line by id |
| `terminal.isAnimating()` | Check if typing animation is active |

#### Navigation & Control
| Method | Description |
|--------|-------------|
| `terminal.scrollToBottom()` | Scroll to bottom of terminal |
| `terminal.scrollToLine(index)` | Scroll to specific line |
| `terminal.skip()` | Skip current typing animation |
| `terminal.restart()` | Restart typing animation from beginning |

### Example: Dynamic Updates

```svelte
<script lang="ts">
  import TerminalTUI from '$lib/components/TerminalTUI.svelte';
  import type { TerminalAPI } from '$lib';
  
  let terminal = $state<TerminalAPI>();
  let lines = $state([
    { type: 'output', content: 'Initializing...', id: 'status' }
  ]);
  
  async function runProcess() {
    // Update existing line
    terminal?.updateById('status', { 
      type: 'info', 
      content: 'Processing...' 
    });
    
    await delay(1000);
    
    // Add progress
    terminal?.write({ 
      type: 'progress', 
      content: '', 
      progress: 50, 
      progressLabel: '50%' 
    });
    
    await delay(1000);
    
    // Complete
    terminal?.updateById('status', { 
      type: 'success', 
      content: 'Complete!' 
    });
  }
</script>

<TerminalTUI bind:terminal bind:lines />
<button onclick={runProcess}>Start</button>
```

## 3D Model Viewer

The `ModelViewer` component provides an interactive Three.js viewer for `.glb` models.

### Features
- **Mouse Controls**: Drag to rotate, scroll to zoom
- **Arrow Key Controls**: Use arrow keys to orbit the camera (click viewer to focus first)
- **Auto-rotate**: Toggle automatic rotation
- **Wireframe Mode**: View model wireframe
- **Adjustable Lighting**: Increase/decrease scene brightness
- **Fullscreen Mode**: Expand to full viewport (press `Escape` to exit)
- **Ground Plane**: Optional shadow-receiving ground

### Usage

```svelte
<ModelViewer 
  modelPath="/models/my-model.glb"
  modelName="My Model"
/>
```

Place `.glb` files in `/static/models/` and they'll be accessible at `/models/filename.glb`.

## Tech Stack

- **Framework**: SvelteKit 2.x with Svelte 5 runes
- **Styling**: Tailwind CSS 4.x
- **3D**: Three.js with GLTFLoader
- **Icons**: @iconify/svelte
- **Font**: JetBrains Mono

## Development

```bash
# Install dependencies
bun install

# Start dev server
bun run dev

# Build for production
bun run build

# Preview production build
bun run preview
```

## Keyboard Shortcuts

| Key | Action |
|-----|--------|
| `↑` / `k` | Navigate up |
| `↓` / `j` | Navigate down |
| `Enter` | Activate button |
| `Y` | Skip typing animation |
| `T` | Toggle dark/light mode |

### 3D Model Viewer

| Key | Action |
|-----|--------|
| `←` | Rotate camera left |
| `→` | Rotate camera right |
| `↑` | Rotate camera up |
| `↓` | Rotate camera down |
| `Escape` | Exit fullscreen |

## Theme System

Themes are defined as JSON files in `src/lib/assets/themes/`. Each theme contains colors for both dark and light modes.

### Theme File Structure

```json
{
  "name": "Theme Name",
  "icon": "🎨",
  "dark": {
    "colors": {
      "primary": "#89b4fa",
      "secondary": "#313244",
      "accent": "#a6e3a1",
      "background": "#1e1e2e",
      "backgroundLight": "#313244",
      "text": "#cdd6f4",
      "textMuted": "#a6adc8",
      "border": "#45475a",
      "terminal": "#1e1e2e",
      "terminalPrompt": "#cba6f7",
      "terminalUser": "#a6e3a1",
      "terminalPath": "#89b4fa"
    },
    "colorMap": {
      "red": "#f38ba8",
      "green": "#a6e3a1",
      "blue": "#89b4fa",
      "primary": "var(--terminal-primary)",
      "accent": "var(--terminal-accent)",
      "muted": "var(--terminal-muted)"
    }
  },
  "light": {
    "colors": { /* light mode colors */ },
    "colorMap": { /* light mode color map */ }
  }
}
```

### Adding a New Theme

1. Create a new file: `src/lib/assets/themes/mytheme.theme.json`
2. Import it in `src/lib/stores/theme.ts`:
   ```typescript
   import myTheme from '$lib/assets/themes/mytheme.theme.json';
   ```
3. Add it to the themes object:
   ```typescript
   const themes: Record<ColorTheme, ThemeJson> = {
     arch: archTheme,
     catppuccin: catppuccinTheme,
     mytheme: myTheme as ThemeJson
   };
   ```
4. Update the `ColorTheme` type to include your theme name

### Available Themes
- **Arch Linux** (`arch`) - Classic terminal colors with Arch blue
- **Catppuccin** (`catppuccin`) - Soft, pastel Mocha/Latte colors

### Theme-Specific Colors

Beyond the basic colors, themes include:
- `teal`, `sky`, `sapphire`, `lavender` 
- `peach`, `maroon`, `mauve`
- `flamingo`, `rosewater`

```typescript
// These colors adapt to the current theme
'(&teal)Teal text(&)'
'(&lavender)Lavender text(&)'
'(&peach)Peach text(&)'
```

## Mobile Considerations

- **Viewport height**: Uses `100dvh` (dynamic viewport height) to properly handle mobile browser chrome
- **Background color**: A fallback dark background (`#1e1e2e`) is set on `html` and `body` to prevent white bars when the page content doesn't fill the viewport
- **Overflow handling**: Hidden horizontal scrollbar to prevent accidental horizontal scroll on mobile

