Files
Website/README.md
2025-11-29 21:42:41 +00:00

715 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
}
```
### 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