550 lines
15 KiB
Markdown
550 lines
15 KiB
Markdown
# Terminal Portfolio
|
||
|
||
An Arch Linux terminal-themed portfolio website with Hyprland-style TUI components, built with SvelteKit, Tailwind CSS, and Three.js.
|
||
|
||

|
||
|
||
## 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
|
||
|
||
Everything in the site is configurable from `src/lib/config.ts`. This file is the single source of truth for UI, layout, TUI behavior, colors, and per-page settings. Edit it to personalize the website to your needs.
|
||
|
||
### Main Config Sections
|
||
- `user`: Profile info (name, username, bio, social links)
|
||
- `skills`, `projects`, `models`, `hackathons`: Content arrays shown in pages
|
||
- `layout`: Sizes and page margins (navbar height, container width)
|
||
- `breakpoints`: Responsive breakpoints (mobile/tablet/desktop)
|
||
- `fonts`: Font stacks and weights
|
||
- `colorPalette`: Terminal and UI colors (semantic and base colors)
|
||
- `terminalButtons`: Terminal header buttons colors (close/minimize/maximize)
|
||
- `terminalSettings`: Typing presets, delays, cursor visibility, prompt style
|
||
- `tuiStyle`: Styling options for border, spacing, font sizes, buttons, etc.
|
||
- `tuiText`: Labels, hints, prefixes, and text labels for interactive elements
|
||
- `animations`: Animation durations and easing for UI interactions
|
||
- `scrollbar`: Scrollbar appearance settings
|
||
- `navbar`: Navbar sizes and theme button settings
|
||
- `modelViewer`: 3D viewer camera, lighting, and text strings
|
||
- `particles`: 3D background particles (count, opacity, motion)
|
||
- `loadingScreen`: Loading text and colors
|
||
- `keyboardShortcuts`: Map keyboard actions to keys
|
||
- `effects`: Misc effects like selection background and backdrop blur
|
||
- `pageMeta`: Per-route metadata (title, description, icon, keywords)
|
||
|
||
### 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
|
||
- Change any color in `colorPalette` to affect both inline coloring and semantic tokens like `(&primary)`/`(&error)`.
|
||
- Update `terminalSettings` to control typing speed, delays, and the TUI icon.
|
||
- Modify `tuiStyle` to adjust spacing, rounded corners, and button sizes.
|
||
- Use `keyboardShortcuts` to remap keys (e.g., toggle theme, skip animation).
|
||
- Per-page speeds are controlled by `pageSpeedSettings` (preset or numeric multiplier).
|
||
|
||
### Where to look for types & utilities
|
||
- `src/lib/components/tui/types.ts` — main TerminalLine types
|
||
- `src/lib/components/tui/utils.ts` — parsing utilities and style helpers
|
||
- `src/lib/stores/theme.ts` — theme store & `toggleMode()`
|
||
|
||
If you change a value in `config.ts`, the UI should pick it up on next reload. Some values (fonts, CSS variables) may also require adjusting CSS variables or Tailwind config.
|
||
|
||
## 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
|
||
}
|
||
```
|
||
|
||
### 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
|
||
}
|
||
```
|
||
|
||
## 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
|
||
├── utils.ts # Parsing & styling utilities
|
||
├── TuiHeader.svelte # Top status bar
|
||
├── TuiBody.svelte # Scrollable content area
|
||
├── TuiFooter.svelte # Bottom status bar
|
||
├── TuiButton.svelte # Full-width button
|
||
├── TuiLink.svelte # Inline clickable link
|
||
├── TuiCard.svelte # Card with header/body/footer
|
||
├── TuiProgress.svelte # Animated progress bar
|
||
├── TuiAccordion.svelte # Collapsible sections
|
||
├── TuiTable.svelte # Data table with headers
|
||
└── TuiTooltip.svelte # Hover tooltip
|
||
```
|
||
|
||
## 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
|
||
|