15 KiB
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 pageslayout: Sizes and page margins (navbar height, container width)breakpoints: Responsive breakpoints (mobile/tablet/desktop)fonts: Font stacks and weightscolorPalette: Terminal and UI colors (semantic and base colors)terminalButtons: Terminal header buttons colors (close/minimize/maximize)terminalSettings: Typing presets, delays, cursor visibility, prompt styletuiStyle: Styling options for border, spacing, font sizes, buttons, etc.tuiText: Labels, hints, prefixes, and text labels for interactive elementsanimations: Animation durations and easing for UI interactionsscrollbar: Scrollbar appearance settingsnavbar: Navbar sizes and theme button settingsmodelViewer: 3D viewer camera, lighting, and text stringsparticles: 3D background particles (count, opacity, motion)loadingScreen: Loading text and colorskeyboardShortcuts: Map keyboard actions to keyseffects: Misc effects like selection background and backdrop blurpageMeta: Per-route metadata (title, description, icon, keywords)
Example: Key config snippets
// 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
colorPaletteto affect both inline coloring and semantic tokens like(&primary)/(&error). - Update
terminalSettingsto control typing speed, delays, and the TUI icon. - Modify
tuiStyleto adjust spacing, rounded corners, and button sizes. - Use
keyboardShortcutsto 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 typessrc/lib/components/tui/utils.ts— parsing utilities and style helperssrc/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
// 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:
'(&bg-red)Red background(&)'
'(&bg-blue,white)Blue bg with white text(&)'
'(&bg-surface)Surface background(&)'
'(&bg-#333333)Custom bg color(&)'
Text Styles
'(&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:
'(&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
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:
{
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)
{
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:
// 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)
{
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
{
type: 'image',
content: 'Caption text', // Optional caption
image: '/path/to/image.png',
imageAlt: 'Alt text',
imageWidth: 300, // Max width in pixels
}
Card
{
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
{
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
{
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
{
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
{
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:
<TerminalTUI
lines={lines}
title="~/directory"
interactive={true}
speed="normal"
autoscroll={true}
onComplete={() => console.log('Done!')}
/>
Props:
lines- Array of TerminalLine objectstitle- Terminal window titleinteractive- Enable keyboard navigationspeed- Typing speed preset or multiplierautoscroll- 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:
{ 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
Escapeto exit) - Ground Plane: Optional shadow-receiving ground
Usage
<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
# 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
{
"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
- Create a new file:
src/lib/assets/themes/mytheme.theme.json - Import it in
src/lib/stores/theme.ts:import myTheme from '$lib/assets/themes/mytheme.theme.json'; - Add it to the themes object:
const themes: Record<ColorTheme, ThemeJson> = { arch: archTheme, catppuccin: catppuccinTheme, mytheme: myTheme as ThemeJson }; - Update the
ColorThemetype 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,lavenderpeach,maroon,mauveflamingo,rosewater
// 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 onhtmlandbodyto prevent white bars when the page content doesn't fill the viewport - Overflow handling: Hidden horizontal scrollbar to prevent accidental horizontal scroll on mobile
