# 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 - Interactive command line - Type commands directly into the terminal to navigate and run actions - Theme Support - Arch, Catppuccin, Wintry, Rose, and Cerberus themes, each with dark/light variants - Dark/Light Mode - Toggle between dark and light modes - Keyboard Navigation - Navigate with arrow keys or vim-style j/k - Native Blog - Markdown posts with tags, authors, syntax highlighting, search, pagination, and an RSS feed - 3D Model Viewer - Interactive Three.js viewer for .glb models - QR Links Page - Generate a scannable QR code for any of your links - Configurable Speed - Per-page typing animation speed - Responsive - Works on desktop and mobile - Rich Text Formatting - Colors, backgrounds, text decorations, and sizeable inline icons ## Pages - Home (`/`) - Neofetch-style intro with navigation - About (`/about`) - Background and details - Portfolio (`/portfolio`) - Profile, experience, skills, and contact info - Projects (`/projects`) - Hub linking out to the three project categories below - Open Source (`/projects/opensource`) - Projects built and maintained in the open - Packages (`/projects/packages`) - Published packages and CLI tools - Hackathons (`/projects/hackathons`) - Hackathon projects, awards, and stats - Blog (`/blog`) - Native markdown blog with tags, search, pagination, and an RSS feed at `/blog/rss.xml` - Models (`/models`) - 3D model gallery with interactive viewer - Components (`/components`) - Showcase of all TUI components - Links (`/links`) - All links with on-demand QR codes The navbar exposes Home, About, Projects, and Blog. Portfolio, Models, Components, and Links are reachable directly by URL or with the terminal `cd` command (for example, `cd links`). ## Terminal Commands Every terminal page has an interactive command line at the bottom. Click the terminal (or just start typing once focused), enter a command, and press Enter. Output is printed back into the terminal. Use the Up/Down arrows to cycle through command history. | Command | Aliases | Description | |---------|---------|-------------| | `help` | `h`, `?` | List all available commands | | `clear` | `cls` | Clear the terminal | | `whoami` | | Show a short bio | | `neofetch` | `banner` | Display system info with profile picture | | `skills` | | List skills by category | | `contact` | `socials` | Show social links as buttons | | `email` | | Show contact email | | `cd ` | `goto`, `open` | Navigate to a page, including subpages (e.g. `cd projects/opensource`) | | `ls` | `dir` | List the pages you can visit as a tree, with project subpages nested under `projects/` | | `theme [name]` | | List or set the color theme | | `mode [dark\|light]` | | Toggle or set dark/light mode | | `echo ` | | Print text back | | `cowsay ` | | Make a cow say something | | `date` | | Show the current date and time (Eastern) | Command definitions live in `src/lib/components/tui/terminal-commands.ts`. Add a new entry to the `commands` list to extend the command set. Pages and their subpages are declared once in the `pageTree` structure in that file, which both `ls` (tree rendering) and `cd` (nested path resolution) read from. ## Configuration The site configuration is 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, links, skills ├── layout.ts # Layout dimensions, breakpoints, fonts, navbar, scrollbar ├── theme.ts # Colors, animations, effects, loading screen ├── content.ts # Projects, 3D models, experience, hackathon cards ├── terminal.ts # Terminal settings, TUI styling, speed presets, shortcuts ├── navigation.ts # Navigation links, site metadata, page meta └── blog.ts # Blog name and tagline shown on /blog ``` ### Import Examples ```typescript // Barrel import (backward compatible) import { user, colorPalette, projects } from '$lib/config'; // Direct imports (smaller bundles, faster builds) import { user, skills, links } from '$lib/config/user'; import { colorPalette, animations } from '$lib/config/theme'; import { openSourceProjects, cards, experience } 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`, `links` | | `layout.ts` | `layout`, `breakpoints`, `fonts`, `navbar`, `scrollbar` | | `theme.ts` | `colorPalette`, `terminalButtons`, `loadingScreen`, `effects`, `animations` | | `content.ts` | `openSourceProjects`, `packageProjects`, `models`, `experience`, `cards`, `sortedCards` + types | | `terminal.ts` | `terminalSettings`, `tuiStyle`, `tuiText`, `pageSpeedSettings`, `pageAutoscrollSettings`, `speedPresets`, `modelViewer`, `particles`, `keyboardShortcuts` | | `navigation.ts` | `navigation`, `site`, `pageMeta` | | `blog.ts` | `blogName`, `blogTagline` | ### 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', 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', }; ``` ### How to Customize - Edit `config/user.ts` to update your profile, socials, links, and skills - Edit `config/content.ts` to add projects, models, experience, 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/components/tui/terminal-commands.ts` - Interactive command definitions - `src/lib/stores/theme.ts` - Theme store & `toggleMode()` - `src/lib/index.ts` - Helper functions (barrel export) ## Links Page The `/links` page lists everything in the `links` array (a dedicated list, separate from `user.socials`) and renders a scannable QR code for whichever link is selected, plus a clickable button to open it. Add or remove entries in `src/lib/config/user.ts`: ```typescript export const links: LinkItem[] = [ { name: 'GitHub', icon: 'mdi:github', link: 'https://github.com/SirBlobby' }, { name: 'Email', icon: 'mdi:email', link: `mailto:${user.email}` }, // Add page-only links here without touching user.socials ]; ``` QR codes are generated with the `svelte-qrcode` package, loaded on the client. ## Blog The `/blog` page is a native markdown blog — no external blogging platform required. ### Writing a Post Add a markdown file to the `blogs/` directory at the project root: ```markdown --- title: My Post Title author: Your Name date: 2026-01-01 tags: meta, notes excerpt: A one line summary shown on the blog list and in the RSS feed. --- Post content goes here, written in markdown. ``` Posts are picked up automatically via `import.meta.glob` in `src/lib/blog/posts.ts` — no manual registration needed. `author` and `date` are optional; `author` falls back to `user.displayname`. ### Rendering `src/lib/blog/markdown.ts` is a small hand-written markdown-to-HTML renderer (headings, bold/italic, links, images, lists, blockquotes, and fenced code blocks) — no external markdown dependency. Fenced code blocks with a language tag (` ```js `, ` ```python `, etc.) get lightweight syntax highlighting via `src/lib/blog/highlight.ts`, and every code block gets a copy-to-clipboard button. Dates are parsed and formatted in Eastern time via `src/lib/blog/date.ts`, anchored at noon UTC to avoid off-by-one day shifts from timezone conversion. ### Features - Tags with click-to-filter on the blog list - Full-text search across title, excerpt, and tags - Pagination (5 posts per page) - Previous/next post navigation on each post page - RSS feed at `/blog/rss.xml` (`src/routes/blog/rss.xml/+server.ts`) ## 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(&)' ``` ### Inline Icons ```typescript '(&icon, mdi:github) GitHub' // Inline icon, default size '(&icon, mdi:trophy, 32) Winner' // Inline icon with a custom size in pixels ``` ### 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 heading { 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 border: false, // Disable default border (default: true) flex: true, // Grow to fill available space in a row group (default: false) } ``` ### 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' } ], cardWidth: '1/2', // Width (string fraction, decimal, or px) cardHeight: 300, // Height (string fraction, decimal, or px) cardFloat: 'center', // start | center | end } ``` ### 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 groupExpand: true, // Expand children to fill width (default: false) 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 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) - `enableCommands` - Show the interactive command input (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. ## Terminal API The `TerminalTUI` component exposes a reactive API for programmatic control via the `terminal` bindable prop. ### Setup ```svelte ``` ### 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 ``` ## 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 - Graceful Fallback: Shows an error message when WebGL is unavailable instead of crashing ### Usage ```svelte ``` 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 - QR codes: svelte-qrcode - Icons: @iconify/svelte - Font: JetBrains Mono - Runtime: Bun ## Development ```bash # Install dependencies bun install # Start dev server bun run dev # Build for production bun run build # Type-check bun run check ``` ## Keyboard Shortcuts | Key | Action | |-----|--------| | Up / k | Navigate up | | Down / j | Navigate down | | Enter | Activate button | | Y | Skip typing animation | | T | Toggle dark/light mode | When the command input is focused, these keys type normally instead of triggering navigation. ### 3D Model Viewer | Key | Action | |-----|--------| | Left | Rotate camera left | | Right | Rotate camera right | | Up | Rotate camera up | | Down | 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. Shipped themes: Arch, Catppuccin, Wintry, Rose, and Cerberus (`src/lib/stores/theme.ts`). ### Theme File Structure ```json { "name": "Theme Name", "icon": "arch", "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": { }, "colorMap": { } } } ``` ### 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 = { arch: archTheme, catppuccin: catppuccinTheme, mytheme: myTheme as ThemeJson }; ``` 4. Update the `ColorTheme` type to include your theme name ### 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