Website config Update
This commit is contained in:
@@ -1,777 +1,20 @@
|
||||
|
||||
// ============================================================================
|
||||
// USER PROFILE
|
||||
// CONFIGURATION - BARREL EXPORT
|
||||
// ============================================================================
|
||||
|
||||
export const user = {
|
||||
name: 'Gagan M',
|
||||
displayname: 'Sir Blob',
|
||||
username: 'sirblob',
|
||||
hostname: 'engineering',
|
||||
title: 'Engineering Student',
|
||||
email: 'sirblob0@gmail.com',
|
||||
location: 'Washington DC-Baltimore Area',
|
||||
bio: `Hi, I am Sir Blob — a engineer who loves making things. ` +
|
||||
`I build fun coding projects, participate in game jams and hackathons, and enjoy games like Minecraft and Pokémon TCG Live.`,
|
||||
|
||||
// Prefer an absolute avatar URL if you want to pull directly from GitHub
|
||||
avatar: '/blob_nerd.png',
|
||||
|
||||
// Social links - array of { name, icon (Iconify), link }
|
||||
socials: [
|
||||
{ name: 'GitHub', icon: 'mdi:github', link: 'https://github.com/SirBlobby' },
|
||||
{ name: 'LinkedIn', icon: 'mdi:linkedin', link: 'https://www.linkedin.com/in/gmanjunatha/' },
|
||||
{ name: 'Devpost', icon: 'simple-icons:devpost', link: 'https://devpost.com/Sir_Blob_' },
|
||||
{ name: 'Discord', icon: 'mdi:discord', link: 'https://discord.com/users/sir_blob_' }
|
||||
]
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// LAYOUT & DIMENSIONS
|
||||
// ============================================================================
|
||||
|
||||
export const layout = {
|
||||
// Navbar
|
||||
navbarHeight: 60, // px
|
||||
navbarMaxWidth: 1400, // px
|
||||
navbarZIndex: 1000,
|
||||
|
||||
// Container
|
||||
containerMaxWidth: 1200, // px
|
||||
containerPadding: '1.5rem',
|
||||
|
||||
// Grid
|
||||
gridGap: '1.5rem',
|
||||
gridMinColumnWidth: 300, // px
|
||||
|
||||
// Page margins
|
||||
pageBottomMargin: 80, // px (desktop)
|
||||
pageMobileBottomMargin: 60, // px (mobile)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RESPONSIVE BREAKPOINTS
|
||||
// ============================================================================
|
||||
|
||||
export const breakpoints = {
|
||||
mobile: 768, // px
|
||||
tablet: 1024, // px
|
||||
desktop: 1400, // px
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// FONTS
|
||||
// ============================================================================
|
||||
|
||||
export const fonts = {
|
||||
mono: "'JetBrains Mono', 'Fira Code', 'Consolas', 'Monaco', monospace",
|
||||
sans: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
||||
monoWeight: 600,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// COLOR PALETTE
|
||||
// ============================================================================
|
||||
// These colors are used for terminal text formatting and UI elements
|
||||
// Colors follow Catppuccin Mocha theme by default
|
||||
|
||||
export const colorPalette = {
|
||||
// Basic colors
|
||||
red: '#f38ba8',
|
||||
green: '#a6e3a1',
|
||||
yellow: '#f9e2af',
|
||||
blue: '#89b4fa',
|
||||
magenta: '#cba6f7',
|
||||
cyan: '#94e2d5',
|
||||
white: '#cdd6f4',
|
||||
gray: '#6c7086',
|
||||
orange: '#fab387',
|
||||
pink: '#f5c2e7',
|
||||
black: '#1e1e2e',
|
||||
surface: '#313244',
|
||||
|
||||
// Semantic colors (map to basic colors by default)
|
||||
error: '#f38ba8', // red
|
||||
success: '#a6e3a1', // green
|
||||
warning: '#f9e2af', // yellow
|
||||
info: '#89b4fa', // blue
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TERMINAL WINDOW BUTTONS (traffic lights)
|
||||
// ============================================================================
|
||||
|
||||
export const terminalButtons = {
|
||||
close: '#ff5f56',
|
||||
minimize: '#ffbd2e',
|
||||
maximize: '#27ca40',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SKILLS
|
||||
// ============================================================================
|
||||
|
||||
export const skills = {
|
||||
languages: ['Python', 'JavaScript', 'TypeScript', 'C', 'C++', 'Java', 'Node.js'],
|
||||
frameworks: ['Arduino', 'Bootstrap', 'TailwindCSS', 'Discord.js', 'React', 'Electron', 'Svelte'],
|
||||
applications: ['Windows', 'Linux', 'macOS', 'IntelliJ', 'VS Code', 'Git', 'Blender', 'Godot'],
|
||||
platforms: ['Windows', 'Linux', 'macOS', 'Arduino', 'Raspberry Pi'],
|
||||
tools: ['Git', 'Docker', 'Neovim', 'VS Code'],
|
||||
databases: ['MongoDB', 'Redis', 'SQLite'],
|
||||
interests: ['Open Source', '3D Graphics', 'CLI Tools', 'Game Dev']
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PROJECTS
|
||||
// ============================================================================
|
||||
|
||||
export interface Project {
|
||||
name: string;
|
||||
description: string;
|
||||
tech: string[];
|
||||
github?: string;
|
||||
live?: string;
|
||||
image?: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export const projects: Project[] = [
|
||||
{
|
||||
name: 'PokemonTCGAPI',
|
||||
description: 'Official NPM package wrapper for the PokemonTCG API — utilities and helpers for working with Pokemon TCG data.',
|
||||
tech: ['TypeScript', 'Node.js'],
|
||||
live: 'https://www.npmjs.com/package/@bosstop/pokemontcgapi',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png',
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
name: 'MCSS TS API',
|
||||
description: 'TypeScript API for MCServersSoft services, published to NPM.',
|
||||
tech: ['TypeScript', 'Node.js'],
|
||||
live: 'https://www.npmjs.com/package/@mcserversoft/mcss-api',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png',
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
name: 'MCP Selenium',
|
||||
description: 'Selenium automation utilities for MCP workflows, packaged for reuse.',
|
||||
tech: ['TypeScript', 'Selenium'],
|
||||
live: 'https://www.npmjs.com/package/@sirblob/mcp-selenium',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png',
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
name: 'Pkit',
|
||||
description: 'CLI toolkit and utilities (Rust project) — small developer-focused CLI.',
|
||||
tech: ['Rust', 'CLI'],
|
||||
github: 'https://github.com/dead-projects-inc/pkit-cli',
|
||||
image: 'https://icons.veryicon.com/png/o/business/vscode-program-item-icon/rust-1.png',
|
||||
featured: false
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// 3D MODELS
|
||||
// ============================================================================
|
||||
|
||||
export interface Model3D {
|
||||
name: string;
|
||||
description: string;
|
||||
thumbnail: string;
|
||||
modelUrl?: string; // URL to .glb/.gltf file for preview
|
||||
downloadUrl?: string;
|
||||
software: string[];
|
||||
polyCount?: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export const models: Model3D[] = [
|
||||
{
|
||||
name: 'Character Model',
|
||||
description: 'A stylized character model with full rig',
|
||||
thumbnail: '/models/character-thumb.png',
|
||||
modelUrl: '/models/character.glb',
|
||||
software: ['Blender', 'Substance Painter'],
|
||||
polyCount: '15k tris',
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
name: 'Environment Asset Pack',
|
||||
description: 'Low-poly environment assets for game dev',
|
||||
thumbnail: '/models/environment-thumb.png',
|
||||
software: ['Blender'],
|
||||
polyCount: '2k-5k tris',
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
name: 'Weapon Collection',
|
||||
description: 'Sci-fi weapon models with PBR textures',
|
||||
thumbnail: '/models/weapons-thumb.png',
|
||||
software: ['Blender', 'Substance Painter'],
|
||||
downloadUrl: 'https://example.com/download'
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// HACKATHONS
|
||||
// ============================================================================
|
||||
|
||||
export type Card = {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
link?: string;
|
||||
repo?: string;
|
||||
devpost?: string;
|
||||
hackathonName?: string;
|
||||
university?: string;
|
||||
location?: string;
|
||||
year?: string;
|
||||
tags?: string[];
|
||||
featured?: boolean;
|
||||
awards?: { track: string; place: string }[];
|
||||
liveWarning?: boolean;
|
||||
};
|
||||
|
||||
export const cards: Card[] = [
|
||||
{
|
||||
image: "/hacks/fooddecisive.png",
|
||||
title: "Food Decisive",
|
||||
description:
|
||||
"Ever felt the indecisiveness of choosing what to eat? Don't fret! Decide your next bite with Food Decisive",
|
||||
link: "https://fooddecisive.sirblob.co/",
|
||||
repo: "https://github.com/SirBlobby/VTHacks-12",
|
||||
devpost: "https://devpost.com/software/food-decisive",
|
||||
hackathonName: "VTHacks 12",
|
||||
university: "Virginia Tech",
|
||||
location: "Blacksburg, VA",
|
||||
year: "2024",
|
||||
tags: ["AI", "Llama3.1", "Svelte", "Node.js"],
|
||||
featured: false,
|
||||
liveWarning: true
|
||||
},
|
||||
{
|
||||
image: "/hacks/carbin.png",
|
||||
title: "Carbin",
|
||||
description:
|
||||
"Encourage student participation in responsible waste management with smart bins that guide proper disposal.",
|
||||
repo: "https://github.com/SirBlobby/patriotHacks2024",
|
||||
devpost: "https://devpost.com/software/carboniferous-akc4mj",
|
||||
hackathonName: "PatriotHacks 2024",
|
||||
university: "George Mason University",
|
||||
location: "Fairfax, VA",
|
||||
year: "2024",
|
||||
tags: ["AI", "Azure", "CloudConvert", "Python", "React", "TypeScript", "API"],
|
||||
featured: true,
|
||||
awards: [
|
||||
{ track: "Save the World", place: "2nd Place" },
|
||||
{ track: "Microsoft X Cloudforce", place: "2nd Place" }
|
||||
]
|
||||
},
|
||||
{
|
||||
image: "/hacks/patsafe.png",
|
||||
title: "PatSafe",
|
||||
description:
|
||||
"Bridging the gap between doctors and patients for seamless post-discharge care",
|
||||
link: "https://hoya-hax2025.vercel.app/",
|
||||
repo: "https://github.com/SirBlobby/HoyaHax2025",
|
||||
devpost: "https://devpost.com/software/patsafe",
|
||||
hackathonName: "HoyaHax 2025",
|
||||
university: "Georgetown University",
|
||||
location: "Washington, D.C.",
|
||||
year: "2025",
|
||||
tags: ["Javascript", "Next.js", "React", "TypeScript", "LangChain", "OpenAI"],
|
||||
},
|
||||
{
|
||||
image:"https://placehold.co/800x400/334155/94a3b8?text=Fauxcall",
|
||||
title: "Fauxcall",
|
||||
description:
|
||||
"This product is perfect for situations where you are walking at night and you feel unsafe from someone. Fauxcall lets users create a convincing fake phone call to deter potential attackers and provide a quick escape mechanism.",
|
||||
link: "",
|
||||
repo: "https://github.com/SirBlobby/HooHacks-12",
|
||||
devpost: "https://devpost.com/software/fauxcall",
|
||||
hackathonName: "HooHacks 2025",
|
||||
university: "University of Virginia",
|
||||
location: "Charlottesville, VA",
|
||||
year: "2025",
|
||||
tags: ["mongodb", "next.js", "python", "react", "sesame.com", "skeleton", "twilio"],
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
image: "/hacks/drinkhappy.png",
|
||||
title: "Drink Happy",
|
||||
description:
|
||||
"drinkhappy.tech is a gamified hydration wellness app that helps users track drinks, earn points for healthy choices, and compete with friends. It's powered by Gemini AI for smart drink recognition.",
|
||||
link: "https://drinkhappy.tech",
|
||||
repo: "https://github.com/SirBlobby/Bitcamp-2025",
|
||||
devpost: "https://devpost.com/software/drink-happy",
|
||||
hackathonName: "Bitcamp",
|
||||
university: "University of Maryland",
|
||||
location: "College Park, MD",
|
||||
year: "2025",
|
||||
tags: ["api", "auth0", "gemini", "github", "javascript", "mongodb", "nextjs", "react", "tailwind", "vercel"],
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
image: "/hacks/roadcast.png",
|
||||
title: "Roadcast",
|
||||
description:
|
||||
"Roadcast provides drivers with crash data and weather insights to choose safer routes home. The project combines historical crash data, weather feeds, and spatial analysis to surface hazardous areas and route-level safety recommendations.",
|
||||
link: "https://roadcast.sirblob.co/",
|
||||
repo: "https://github.com/SirBlobby/roadcast",
|
||||
devpost: "https://devpost.com/software/roadcast",
|
||||
hackathonName: "VTHacks 13",
|
||||
university: "Virginia Tech",
|
||||
location: "Blacksburg, VA",
|
||||
year: "2025",
|
||||
tags: ["react", "node.js", "python", "mongodb", "geospatial", "mapbox"],
|
||||
featured: true,
|
||||
liveWarning: true
|
||||
}
|
||||
];
|
||||
|
||||
// Sort cards to show featured first
|
||||
export const sortedCards = [...cards].sort((a, b) => {
|
||||
if (a.featured && !b.featured) return -1;
|
||||
if (!a.featured && b.featured) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// TERMINAL DISPLAY SETTINGS
|
||||
// ============================================================================
|
||||
|
||||
export type SpeedPreset = 'instant' | 'fast' | 'normal' | 'slow' | 'typewriter';
|
||||
|
||||
export const terminalSettings = {
|
||||
// Base typing speed in ms per character (will be adjusted by content length)
|
||||
baseTypeSpeed: 20,
|
||||
// Minimum typing speed (fastest)
|
||||
minTypeSpeed: 5,
|
||||
// Maximum typing speed (slowest)
|
||||
maxTypeSpeed: 50,
|
||||
// Delay before starting to type (ms)
|
||||
startDelay: 300,
|
||||
// Delay between lines (ms)
|
||||
lineDelay: 100,
|
||||
// Show cursor
|
||||
showCursor: true,
|
||||
// Prompt style
|
||||
promptStyle: 'full' as 'full' | 'short' | 'minimal',
|
||||
// Terminal icon (emoji or text)
|
||||
icon: '🐧',
|
||||
// Scroll margin when navigating buttons (px)
|
||||
scrollMargin: 80,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TUI (Terminal UI) STYLING
|
||||
// ============================================================================
|
||||
|
||||
export const tuiStyle = {
|
||||
// Border & container
|
||||
borderRadius: 8, // px
|
||||
borderWidth: 2, // px
|
||||
width: '95%',
|
||||
borderGlowOpacity: 0.5,
|
||||
borderGlowBlur: 4, // px
|
||||
|
||||
// Header
|
||||
headerPadding: '0.5rem 0.75rem',
|
||||
headerFontSize: '0.8rem',
|
||||
|
||||
// Body
|
||||
bodyPadding: '1rem 1.25rem 2rem 1.25rem',
|
||||
bodyFontSize: '0.9rem',
|
||||
lineHeight: 1.7,
|
||||
lineMinHeight: '1.7em',
|
||||
blankLineHeight: '0.5em',
|
||||
|
||||
// Divider
|
||||
dividerMargin: '0.75rem 0',
|
||||
|
||||
// Images
|
||||
imageBorderRadius: 6, // px
|
||||
defaultImageWidth: 300, // px
|
||||
|
||||
// Cursor
|
||||
cursorWidth: 8, // px
|
||||
|
||||
// Scrollbar
|
||||
scrollbarWidth: 6, // px
|
||||
|
||||
// Footer/Statusbar
|
||||
statusbarPadding: '0.4rem 0.75rem',
|
||||
statusbarFontSize: '0.75rem',
|
||||
hintFontSize: '0.7rem',
|
||||
hintBorderRadius: 3, // px
|
||||
|
||||
// Buttons
|
||||
buttonPadding: '0.5rem 0.75rem',
|
||||
buttonMargin: '0.2rem 0',
|
||||
buttonBorderRadius: 4, // px
|
||||
buttonFontSize: '0.9rem',
|
||||
buttonIndicatorFontSize: '0.8rem',
|
||||
buttonIndicatorWidth: '1rem',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TUI TEXT & INDICATORS
|
||||
// ============================================================================
|
||||
|
||||
export const tuiText = {
|
||||
// Status text
|
||||
loading: 'Loading...',
|
||||
ready: 'Ready',
|
||||
skipButton: 'Skip (Y)',
|
||||
tuiLabel: 'TUI',
|
||||
|
||||
// Keyboard hints
|
||||
hints: {
|
||||
navigate: '↑↓ navigate',
|
||||
select: '⏎ select',
|
||||
toggle: 'T theme',
|
||||
},
|
||||
|
||||
// Line prefixes
|
||||
prefixes: {
|
||||
error: '✗ ',
|
||||
success: '✓ ',
|
||||
info: '› ',
|
||||
warning: '⚠ ',
|
||||
command: '$ ',
|
||||
},
|
||||
|
||||
// Button indicators
|
||||
buttonIndicator: {
|
||||
selected: '▶',
|
||||
unselected: ' ',
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// COLOR FORMATTING
|
||||
// ============================================================================
|
||||
// Use (&color)text(&) syntax in terminal content for colored text
|
||||
// Use (&icon, iconName) syntax for inline icons
|
||||
//
|
||||
// Available colors:
|
||||
// red, green, yellow, blue, magenta, cyan, white, gray, orange, pink
|
||||
// primary, accent, muted, error, success, warning, info
|
||||
// This file re-exports all configuration from the modular config/ directory.
|
||||
// For backward compatibility, you can continue importing from '$lib/config'.
|
||||
//
|
||||
// Available styles:
|
||||
// bold, dim, italic, underline
|
||||
// The configuration is now split into smaller, focused files:
|
||||
// - config/user.ts → User profile and skills
|
||||
// - config/layout.ts → Layout, breakpoints, fonts, navbar, scrollbar
|
||||
// - config/theme.ts → Colors, animations, effects, loading screen
|
||||
// - config/content.ts → Projects, models, hackathon cards
|
||||
// - config/terminal.ts → Terminal settings, TUI styling, shortcuts
|
||||
// - config/navigation.ts → Navigation links, site metadata, page meta
|
||||
//
|
||||
// Examples:
|
||||
// "Hello (&red)world(&)!" -> red "world"
|
||||
// "(&bold,green)Success(&): Task done" -> bold green "Success"
|
||||
// "(&italic,cyan)Note(&): See docs" -> italic cyan "Note"
|
||||
// "Status: (&primary)Active(&)" -> theme primary color
|
||||
// "(&dim)This is muted text(&)" -> dimmed text
|
||||
// "(&bold,underline,yellow)Warning(&)" -> bold underlined yellow
|
||||
// "(&error)Failed(&) - (&success)Passed(&)" -> multiple colors
|
||||
// "(&icon, mdi:github) GitHub" -> inline GitHub icon
|
||||
// "Check (&icon, mdi:check) Done" -> inline check icon
|
||||
|
||||
// Per-page speed settings (override global settings)
|
||||
// Use preset names or custom multiplier (0.1 = 10x faster, 2 = 2x slower)
|
||||
export const pageSpeedSettings: Record<string, SpeedPreset | number> = {
|
||||
// Examples:
|
||||
// 'home': 'fast', // Fast typing on home page
|
||||
// 'portfolio': 'normal', // Normal speed
|
||||
// 'models': 'instant', // No typing animation
|
||||
// 'hackathons': 0.5, // Custom: 2x faster than normal
|
||||
'home': 'fast',
|
||||
'portfolio': 'fast',
|
||||
'models': 'fast',
|
||||
'projects': 'normal'
|
||||
};
|
||||
|
||||
// Per-page autoscroll settings (whether to auto-scroll as content types)
|
||||
// Set to false to disable autoscroll on specific pages
|
||||
export const pageAutoscrollSettings: Record<string, boolean> = {
|
||||
// Examples:
|
||||
// 'home': true, // Enable autoscroll
|
||||
// 'portfolio': false, // Disable autoscroll
|
||||
'home': true,
|
||||
'portfolio': false,
|
||||
'models': false,
|
||||
'projects': false,
|
||||
'components': false
|
||||
};
|
||||
|
||||
// Speed preset multipliers (lower = faster)
|
||||
export const speedPresets: Record<SpeedPreset, number> = {
|
||||
'instant': 0, // No delay, instant display
|
||||
'fast': 0.1, // 3x faster
|
||||
'normal': 0.5, // Default speed
|
||||
'slow': 1.5, // 2x slower
|
||||
'typewriter': 2 // 3x slower, classic typewriter feel
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ANIMATIONS
|
||||
// You can import directly from individual files for smaller bundles:
|
||||
// import { user } from '$lib/config/user';
|
||||
// import { colorPalette } from '$lib/config/theme';
|
||||
// ============================================================================
|
||||
|
||||
export const animations = {
|
||||
// Terminal/TUI animations
|
||||
terminalFadeIn: { duration: '0.4s', easing: 'ease-out' },
|
||||
tuiFadeIn: { duration: '0.4s', easing: 'ease-out' },
|
||||
lineSlideIn: { duration: '0.15s', easing: 'ease-out' },
|
||||
cursorBlink: { duration: '1s' },
|
||||
borderGlow: { duration: '8s', transition: '0.3s' },
|
||||
|
||||
// Button/interaction animations
|
||||
buttonHover: { duration: '0.2s' },
|
||||
buttonTransition: { duration: '0.15s' },
|
||||
|
||||
// Navigation animations
|
||||
hamburgerTransition: { duration: '0.3s' },
|
||||
navLinkTransition: { duration: '0.2s' },
|
||||
linkUnderline: { duration: '0.3s' },
|
||||
dropdown: { flyDuration: 200, fadeDuration: 150 }, // ms
|
||||
|
||||
// Model viewer
|
||||
modelViewerTransition: { duration: '0.3s' },
|
||||
spin: { duration: '1s' },
|
||||
|
||||
// General
|
||||
pulse: { duration: '2s' },
|
||||
float: { duration: '3s' },
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SCROLLBAR
|
||||
// ============================================================================
|
||||
|
||||
export const scrollbar = {
|
||||
width: 8, // px
|
||||
borderRadius: 4, // px
|
||||
trackColor: 'rgba(0, 0, 0, 0.1)',
|
||||
thumbColor: 'rgba(128, 128, 128, 0.5)',
|
||||
thumbHoverColor: 'rgba(128, 128, 128, 0.7)',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// NAVBAR
|
||||
// ============================================================================
|
||||
|
||||
export const navbar = {
|
||||
height: 60, // px - also update layout.navbarHeight if changing
|
||||
maxWidth: 1400, // px
|
||||
padding: '0.75rem 1.5rem',
|
||||
gap: '2rem',
|
||||
zIndex: 1000,
|
||||
|
||||
// Brand
|
||||
brandFontSize: '0.9rem',
|
||||
cursorWidth: 8, // px
|
||||
|
||||
// Hamburger menu
|
||||
hamburgerWidth: 24, // px
|
||||
hamburgerHeight: 2, // px
|
||||
hamburgerSpacing: 7, // px
|
||||
|
||||
// Links
|
||||
linksGap: '1.5rem',
|
||||
linkFontSize: '0.85rem',
|
||||
linkPadding: '0.5rem 0.75rem',
|
||||
linkBorderRadius: 4, // px
|
||||
linkUnderlineHeight: 2, // px
|
||||
|
||||
// Mode toggle
|
||||
modeToggleSize: 40, // px
|
||||
modeToggleBorderRadius: 8, // px
|
||||
themeButtonBorderRadius: 6, // px
|
||||
|
||||
// Dropdown
|
||||
dropdownMinWidth: 180, // px
|
||||
dropdownBorderRadius: 8, // px
|
||||
dropdownZIndex: 1001,
|
||||
backdropZIndex: 999,
|
||||
mobileExpandedHeight: 200, // px
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MODEL VIEWER (3D)
|
||||
// ============================================================================
|
||||
|
||||
export const modelViewer = {
|
||||
// Container
|
||||
borderRadius: 8, // px
|
||||
minHeight: 300, // px
|
||||
headerPadding: '0.5rem 0.75rem',
|
||||
nameFontSize: '0.85rem',
|
||||
|
||||
// Controls
|
||||
controlButtonSize: 28, // px
|
||||
controlButtonBorderRadius: 4, // px
|
||||
fullscreenZIndex: 100,
|
||||
fullscreenTopOffset: 60, // px (navbar height)
|
||||
|
||||
// Camera
|
||||
cameraFov: 45,
|
||||
cameraNear: 0.1,
|
||||
cameraFar: 1000,
|
||||
cameraZ: 2.5, // initial camera distance
|
||||
|
||||
// Controls behavior
|
||||
controlsMinDistance: 1,
|
||||
controlsMaxDistance: 10,
|
||||
dampingFactor: 0.05,
|
||||
autoRotateSpeed: 2,
|
||||
|
||||
// Model
|
||||
modelScale: 1.5,
|
||||
|
||||
// Ground plane
|
||||
groundColor: 0x222222,
|
||||
groundOpacity: 0.3,
|
||||
|
||||
// Lighting
|
||||
ambientIntensity: 0.6,
|
||||
directionalIntensity: 0.8,
|
||||
lightColor: 0xffffff,
|
||||
rimLightColor: 0x88ccff,
|
||||
|
||||
// Text
|
||||
loadingText: 'Loading model...',
|
||||
errorText: 'Failed to load model',
|
||||
hints: {
|
||||
rotate: 'Drag to rotate',
|
||||
zoom: 'Scroll to zoom',
|
||||
},
|
||||
|
||||
// Timing
|
||||
resizeTimeout: 100, // ms
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PARTICLES (3D Background)
|
||||
// ============================================================================
|
||||
|
||||
export const particles = {
|
||||
count: 600,
|
||||
spreadArea: 20,
|
||||
size: 0.05,
|
||||
velocityRange: 0.01,
|
||||
|
||||
// Opacity
|
||||
darkOpacity: 0.6,
|
||||
lightOpacity: 0.4,
|
||||
|
||||
// Motion
|
||||
waveAmplitude: 0.001,
|
||||
waveFrequency: 0.1,
|
||||
boundary: 10, // wrap boundary
|
||||
rotationSpeedY: 0.05,
|
||||
rotationSpeedX: 0.02,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// LOADING SCREEN
|
||||
// ============================================================================
|
||||
|
||||
export const loadingScreen = {
|
||||
background: '#0d1117',
|
||||
textColor: '#c9d1d9',
|
||||
cursorColor: '#1793d1',
|
||||
text: "Blob's Portfolio",
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SITE METADATA
|
||||
// ============================================================================
|
||||
|
||||
export const site = {
|
||||
title: `${user.displayname} | Portfolio`,
|
||||
description: `${user.title} - ${user.bio}`,
|
||||
keywords: ['developer', 'portfolio', 'programming', ...skills.languages, ...skills.frameworks],
|
||||
ogImage: '/og-image.png',
|
||||
twitterHandle: '@yourusername'
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PAGE META (per-route)
|
||||
// ============================================================================
|
||||
export interface PageMeta {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: string; // Iconify id or path to an image (e.g. 'mdi:home' or '/icons/home.svg')
|
||||
keywords?: string[];
|
||||
ogImage?: string;
|
||||
}
|
||||
|
||||
// Map route => meta. Use route paths as keys (e.g. '/', '/portfolio')
|
||||
export const pageMeta: Record<string, PageMeta> = {
|
||||
'/': {
|
||||
title: `${user.displayname} — Home`,
|
||||
description: `Home — ${user.title} portfolio and projects.`,
|
||||
icon: 'mdi:home',
|
||||
keywords: ['home', 'portfolio', 'about']
|
||||
},
|
||||
'/portfolio': {
|
||||
title: `${user.displayname} — Portfolio`,
|
||||
description: 'Selected projects, highlights and case studies.',
|
||||
icon: 'mdi:folder-multiple',
|
||||
keywords: ['projects', 'portfolio', 'showcase']
|
||||
},
|
||||
'/models': {
|
||||
title: `${user.displayname} — 3D Models`,
|
||||
description: 'A curated collection of 3D models and previews.',
|
||||
icon: 'mdi:cube-outline',
|
||||
keywords: ['3d', 'models', 'glb', 'gltf']
|
||||
},
|
||||
'/projects': {
|
||||
title: `${user.displayname} — Projects`,
|
||||
description: 'Hackathon projects, demos and awards.',
|
||||
icon: 'mdi:trophy',
|
||||
keywords: ['hackathon', 'projects', 'events']
|
||||
},
|
||||
'/components': {
|
||||
title: `${user.displayname} — Components`,
|
||||
description: 'Terminal UI components showcase and documentation.',
|
||||
icon: 'mdi:puzzle',
|
||||
keywords: ['components', 'ui', 'terminal', 'tui']
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// KEYBOARD 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'],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION
|
||||
// ============================================================================
|
||||
|
||||
export const navigation = [
|
||||
{ name: 'home', path: '/', icon: '~' },
|
||||
{ name: 'portfolio', path: '/portfolio', icon: '📁' },
|
||||
{ name: 'models', path: '/models', icon: '🎨' },
|
||||
{ name: 'projects', path: '/projects', icon: '🏆' },
|
||||
// { name: 'components', path: '/components', icon: '🧩' },
|
||||
{ name: 'blog', path: 'https://blog.sirblob.co', icon: '📝', external: true }
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
export const effects = {
|
||||
backdropBlur: 10, // px
|
||||
selectionBackground: 'rgba(23, 147, 209, 0.3)',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
// NOTE: Helper functions were moved to `src/lib/index.ts` to provide a central
|
||||
// barrel and avoid duplication. Import helpers from `$lib` instead of `$lib/config`.
|
||||
export * from './config/index';
|
||||
|
||||
209
src/lib/config/content.ts
Normal file
209
src/lib/config/content.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
// ============================================================================
|
||||
// PROJECTS
|
||||
// ============================================================================
|
||||
|
||||
export interface Project {
|
||||
name: string;
|
||||
description: string;
|
||||
tech: string[];
|
||||
github?: string;
|
||||
live?: string;
|
||||
image?: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export const projects: Project[] = [
|
||||
{
|
||||
name: 'PokemonTCGAPI',
|
||||
description: 'Official NPM package wrapper for the PokemonTCG API — utilities and helpers for working with Pokemon TCG data.',
|
||||
tech: ['TypeScript', 'Node.js'],
|
||||
live: 'https://www.npmjs.com/package/@bosstop/pokemontcgapi',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png',
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
name: 'MCSS TS API',
|
||||
description: 'TypeScript API for MCServersSoft services, published to NPM.',
|
||||
tech: ['TypeScript', 'Node.js'],
|
||||
live: 'https://www.npmjs.com/package/@mcserversoft/mcss-api',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png',
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
name: 'MCP Selenium',
|
||||
description: 'Selenium automation utilities for MCP workflows, packaged for reuse.',
|
||||
tech: ['TypeScript', 'Selenium'],
|
||||
live: 'https://www.npmjs.com/package/@sirblob/mcp-selenium',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png',
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
name: 'Pkit',
|
||||
description: 'CLI toolkit and utilities (Rust project) — small developer-focused CLI.',
|
||||
tech: ['Rust', 'CLI'],
|
||||
github: 'https://github.com/dead-projects-inc/pkit-cli',
|
||||
image: 'https://icons.veryicon.com/png/o/business/vscode-program-item-icon/rust-1.png',
|
||||
featured: false
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// 3D MODELS
|
||||
// ============================================================================
|
||||
|
||||
export interface Model3D {
|
||||
name: string;
|
||||
description: string;
|
||||
thumbnail: string;
|
||||
modelUrl?: string; // URL to .glb/.gltf file for preview
|
||||
downloadUrl?: string;
|
||||
software: string[];
|
||||
polyCount?: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export const models: Model3D[] = [
|
||||
{
|
||||
name: 'Bake Potato',
|
||||
description: 'A fun baked-potato 3D model for demos and testing.',
|
||||
thumbnail: '/models/BakePotato-thumb.png',
|
||||
modelUrl: '/models/BakePotato.glb',
|
||||
software: ['Blender'],
|
||||
polyCount: '1k tris',
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
name: 'Soda Can',
|
||||
description: 'A realistic soda can GLB used for materials/tests.',
|
||||
thumbnail: '/models/Soda-thumb.png',
|
||||
modelUrl: '/models/Soda.glb',
|
||||
software: ['Blender'],
|
||||
polyCount: '800 tris',
|
||||
featured: false
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// HACKATHONS
|
||||
// ============================================================================
|
||||
|
||||
export type Card = {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
link?: string;
|
||||
repo?: string;
|
||||
devpost?: string;
|
||||
hackathonName?: string;
|
||||
university?: string;
|
||||
location?: string;
|
||||
year?: string;
|
||||
tags?: string[];
|
||||
featured?: boolean;
|
||||
awards?: { track: string; place: string }[];
|
||||
liveWarning?: boolean;
|
||||
};
|
||||
|
||||
export const cards: Card[] = [
|
||||
{
|
||||
image: "/hacks/fooddecisive.png",
|
||||
title: "Food Decisive",
|
||||
description:
|
||||
"Ever felt the indecisiveness of choosing what to eat? Don't fret! Decide your next bite with Food Decisive",
|
||||
link: "https://fooddecisive.sirblob.co/",
|
||||
repo: "https://github.com/SirBlobby/VTHacks-12",
|
||||
devpost: "https://devpost.com/software/food-decisive",
|
||||
hackathonName: "VTHacks 12",
|
||||
university: "Virginia Tech",
|
||||
location: "Blacksburg, VA",
|
||||
year: "2024",
|
||||
tags: ["AI", "Llama3.1", "Svelte", "Node.js"],
|
||||
featured: false,
|
||||
liveWarning: true
|
||||
},
|
||||
{
|
||||
image: "/hacks/carbin.png",
|
||||
title: "Carbin",
|
||||
description:
|
||||
"Encourage student participation in responsible waste management with smart bins that guide proper disposal.",
|
||||
repo: "https://github.com/SirBlobby/patriotHacks2024",
|
||||
devpost: "https://devpost.com/software/carboniferous-akc4mj",
|
||||
hackathonName: "PatriotHacks 2024",
|
||||
university: "George Mason University",
|
||||
location: "Fairfax, VA",
|
||||
year: "2024",
|
||||
tags: ["AI", "Azure", "CloudConvert", "Python", "React", "TypeScript", "API"],
|
||||
featured: true,
|
||||
awards: [
|
||||
{ track: "Save the World", place: "2nd Place" },
|
||||
{ track: "Microsoft X Cloudforce", place: "2nd Place" }
|
||||
]
|
||||
},
|
||||
{
|
||||
image: "/hacks/patsafe.png",
|
||||
title: "PatSafe",
|
||||
description:
|
||||
"Bridging the gap between doctors and patients for seamless post-discharge care",
|
||||
link: "https://hoya-hax2025.vercel.app/",
|
||||
repo: "https://github.com/SirBlobby/HoyaHax2025",
|
||||
devpost: "https://devpost.com/software/patsafe",
|
||||
hackathonName: "HoyaHax 2025",
|
||||
university: "Georgetown University",
|
||||
location: "Washington, D.C.",
|
||||
year: "2025",
|
||||
tags: ["Javascript", "Next.js", "React", "TypeScript", "LangChain", "OpenAI"],
|
||||
},
|
||||
{
|
||||
image: "https://placehold.co/800x400/334155/94a3b8?text=Fauxcall",
|
||||
title: "Fauxcall",
|
||||
description:
|
||||
"This product is perfect for situations where you are walking at night and you feel unsafe from someone. Fauxcall lets users create a convincing fake phone call to deter potential attackers and provide a quick escape mechanism.",
|
||||
link: "",
|
||||
repo: "https://github.com/SirBlobby/HooHacks-12",
|
||||
devpost: "https://devpost.com/software/fauxcall",
|
||||
hackathonName: "HooHacks 2025",
|
||||
university: "University of Virginia",
|
||||
location: "Charlottesville, VA",
|
||||
year: "2025",
|
||||
tags: ["mongodb", "next.js", "python", "react", "sesame.com", "skeleton", "twilio"],
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
image: "/hacks/drinkhappy.png",
|
||||
title: "Drink Happy",
|
||||
description:
|
||||
"drinkhappy.tech is a gamified hydration wellness app that helps users track drinks, earn points for healthy choices, and compete with friends. It's powered by Gemini AI for smart drink recognition.",
|
||||
link: "https://drinkhappy.tech",
|
||||
repo: "https://github.com/SirBlobby/Bitcamp-2025",
|
||||
devpost: "https://devpost.com/software/drink-happy",
|
||||
hackathonName: "Bitcamp",
|
||||
university: "University of Maryland",
|
||||
location: "College Park, MD",
|
||||
year: "2025",
|
||||
tags: ["api", "auth0", "gemini", "github", "javascript", "mongodb", "nextjs", "react", "tailwind", "vercel"],
|
||||
featured: false
|
||||
},
|
||||
{
|
||||
image: "/hacks/roadcast.png",
|
||||
title: "Roadcast",
|
||||
description:
|
||||
"Roadcast provides drivers with crash data and weather insights to choose safer routes home. The project combines historical crash data, weather feeds, and spatial analysis to surface hazardous areas and route-level safety recommendations.",
|
||||
link: "https://roadcast.sirblob.co/",
|
||||
repo: "https://github.com/SirBlobby/roadcast",
|
||||
devpost: "https://devpost.com/software/roadcast",
|
||||
hackathonName: "VTHacks 13",
|
||||
university: "Virginia Tech",
|
||||
location: "Blacksburg, VA",
|
||||
year: "2025",
|
||||
tags: ["react", "node.js", "python", "mongodb", "geospatial", "mapbox"],
|
||||
featured: true,
|
||||
liveWarning: true
|
||||
}
|
||||
];
|
||||
|
||||
// Sort cards to show featured first
|
||||
export const sortedCards = [...cards].sort((a, b) => {
|
||||
if (a.featured && !b.featured) return -1;
|
||||
if (!a.featured && b.featured) return 1;
|
||||
return 0;
|
||||
});
|
||||
78
src/lib/config/index.ts
Normal file
78
src/lib/config/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
// ============================================================================
|
||||
// CONFIGURATION INDEX
|
||||
// ============================================================================
|
||||
// This file re-exports all configuration modules for backward compatibility.
|
||||
// You can import from here or directly from the individual config files.
|
||||
//
|
||||
// Import examples:
|
||||
// import { user, skills } from '$lib/config'; // via barrel
|
||||
// import { user } from '$lib/config/user'; // direct import
|
||||
// import { colorPalette } from '$lib/config/theme'; // direct import
|
||||
// ============================================================================
|
||||
|
||||
// User profile and skills
|
||||
export { user, skills } from './user';
|
||||
|
||||
// Layout, breakpoints, fonts, navbar, scrollbar
|
||||
export { layout, breakpoints, fonts, navbar, scrollbar } from './layout';
|
||||
|
||||
// Theme colors, buttons, animations, effects, loading screen
|
||||
export {
|
||||
colorPalette,
|
||||
terminalButtons,
|
||||
loadingScreen,
|
||||
effects,
|
||||
animations
|
||||
} from './theme';
|
||||
|
||||
// Content: projects, models, hackathon cards
|
||||
export type { Project, Model3D, Card } from './content';
|
||||
export { projects, models, cards, sortedCards } from './content';
|
||||
|
||||
// Terminal settings, TUI styling, speed presets, model viewer, particles, shortcuts
|
||||
export type { SpeedPreset } from './terminal';
|
||||
export {
|
||||
terminalSettings,
|
||||
tuiStyle,
|
||||
tuiText,
|
||||
pageSpeedSettings,
|
||||
pageAutoscrollSettings,
|
||||
speedPresets,
|
||||
modelViewer,
|
||||
particles,
|
||||
keyboardShortcuts
|
||||
} from './terminal';
|
||||
|
||||
// Navigation, site metadata, page meta
|
||||
export type { PageMeta } from './navigation';
|
||||
export { navigation, site, pageMeta } from './navigation';
|
||||
|
||||
// ============================================================================
|
||||
// COLOR FORMATTING REFERENCE
|
||||
// ============================================================================
|
||||
// Use (&color)text(&) syntax in terminal content for colored text
|
||||
// Use (&icon, iconName) syntax for inline icons
|
||||
//
|
||||
// Available colors:
|
||||
// red, green, yellow, blue, magenta, cyan, white, gray, orange, pink
|
||||
// primary, accent, muted, error, success, warning, info
|
||||
//
|
||||
// Available styles:
|
||||
// bold, dim, italic, underline
|
||||
//
|
||||
// Examples:
|
||||
// "Hello (&red)world(&)!" -> red "world"
|
||||
// "(&bold,green)Success(&): Task done" -> bold green "Success"
|
||||
// "(&italic,cyan)Note(&): See docs" -> italic cyan "Note"
|
||||
// "Status: (&primary)Active(&)" -> theme primary color
|
||||
// "(&dim)This is muted text(&)" -> dimmed text
|
||||
// "(&bold,underline,yellow)Warning(&)" -> bold underlined yellow
|
||||
// "(&error)Failed(&) - (&success)Passed(&)" -> multiple colors
|
||||
// "(&icon, mdi:github) GitHub" -> inline GitHub icon
|
||||
// "Check (&icon, mdi:check) Done" -> inline check icon
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
// NOTE: Helper functions are in `src/lib/index.ts` to provide a central
|
||||
// barrel and avoid duplication. Import helpers from `$lib` instead of `$lib/config`.
|
||||
94
src/lib/config/layout.ts
Normal file
94
src/lib/config/layout.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
// ============================================================================
|
||||
// LAYOUT & DIMENSIONS
|
||||
// ============================================================================
|
||||
|
||||
export const layout = {
|
||||
// Navbar
|
||||
navbarHeight: 60, // px
|
||||
navbarMaxWidth: 1400, // px
|
||||
navbarZIndex: 1000,
|
||||
|
||||
// Container
|
||||
containerMaxWidth: 1200, // px
|
||||
containerPadding: '1.5rem',
|
||||
|
||||
// Grid
|
||||
gridGap: '1.5rem',
|
||||
gridMinColumnWidth: 300, // px
|
||||
|
||||
// Page margins
|
||||
pageBottomMargin: 80, // px (desktop)
|
||||
pageMobileBottomMargin: 60, // px (mobile)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RESPONSIVE BREAKPOINTS
|
||||
// ============================================================================
|
||||
|
||||
export const breakpoints = {
|
||||
mobile: 768, // px
|
||||
tablet: 1024, // px
|
||||
desktop: 1400, // px
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// FONTS
|
||||
// ============================================================================
|
||||
|
||||
export const fonts = {
|
||||
mono: "'JetBrains Mono', 'Fira Code', 'Consolas', 'Monaco', monospace",
|
||||
sans: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
||||
monoWeight: 600,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// NAVBAR
|
||||
// ============================================================================
|
||||
|
||||
export const navbar = {
|
||||
height: 60, // px - also update layout.navbarHeight if changing
|
||||
maxWidth: 1400, // px
|
||||
padding: '0.75rem 1.5rem',
|
||||
gap: '2rem',
|
||||
zIndex: 1000,
|
||||
|
||||
// Brand
|
||||
brandFontSize: '0.9rem',
|
||||
cursorWidth: 8, // px
|
||||
|
||||
// Hamburger menu
|
||||
hamburgerWidth: 24, // px
|
||||
hamburgerHeight: 2, // px
|
||||
hamburgerSpacing: 7, // px
|
||||
|
||||
// Links
|
||||
linksGap: '1.5rem',
|
||||
linkFontSize: '0.85rem',
|
||||
linkPadding: '0.5rem 0.75rem',
|
||||
linkBorderRadius: 4, // px
|
||||
linkUnderlineHeight: 2, // px
|
||||
|
||||
// Mode toggle
|
||||
modeToggleSize: 40, // px
|
||||
modeToggleBorderRadius: 8, // px
|
||||
themeButtonBorderRadius: 6, // px
|
||||
|
||||
// Dropdown
|
||||
dropdownMinWidth: 180, // px
|
||||
dropdownBorderRadius: 8, // px
|
||||
dropdownZIndex: 1001,
|
||||
backdropZIndex: 999,
|
||||
mobileExpandedHeight: 200, // px
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SCROLLBAR
|
||||
// ============================================================================
|
||||
|
||||
export const scrollbar = {
|
||||
width: 8, // px
|
||||
borderRadius: 4, // px
|
||||
trackColor: 'rgba(0, 0, 0, 0.1)',
|
||||
thumbColor: 'rgba(128, 128, 128, 0.5)',
|
||||
thumbHoverColor: 'rgba(128, 128, 128, 0.7)',
|
||||
};
|
||||
72
src/lib/config/navigation.ts
Normal file
72
src/lib/config/navigation.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { user, skills } from './user';
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION
|
||||
// ============================================================================
|
||||
|
||||
export const navigation = [
|
||||
{ name: 'home', path: '/', icon: '~' },
|
||||
{ name: 'portfolio', path: '/portfolio', icon: '📁' },
|
||||
{ name: 'models', path: '/models', icon: '🎨' },
|
||||
{ name: 'projects', path: '/projects', icon: '🏆' },
|
||||
// { name: 'components', path: '/components', icon: '🧩' },
|
||||
{ name: 'blog', path: 'https://blog.sirblob.co', icon: '📝', external: true }
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// SITE METADATA
|
||||
// ============================================================================
|
||||
|
||||
export const site = {
|
||||
title: `${user.displayname} | Portfolio`,
|
||||
description: `${user.title} - ${user.bio}`,
|
||||
keywords: ['developer', 'portfolio', 'programming', ...skills.languages, ...skills.frameworks],
|
||||
ogImage: '/og-image.png',
|
||||
twitterHandle: '@yourusername'
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PAGE META (per-route)
|
||||
// ============================================================================
|
||||
|
||||
export interface PageMeta {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: string; // Iconify id or path to an image (e.g. 'mdi:home' or '/icons/home.svg')
|
||||
keywords?: string[];
|
||||
ogImage?: string;
|
||||
}
|
||||
|
||||
// Map route => meta. Use route paths as keys (e.g. '/', '/portfolio')
|
||||
export const pageMeta: Record<string, PageMeta> = {
|
||||
'/': {
|
||||
title: `${user.displayname} — Home`,
|
||||
description: `Home — ${user.title} portfolio and projects.`,
|
||||
icon: 'mdi:home',
|
||||
keywords: ['home', 'portfolio', 'about']
|
||||
},
|
||||
'/portfolio': {
|
||||
title: `${user.displayname} — Portfolio`,
|
||||
description: 'Selected projects, highlights and case studies.',
|
||||
icon: 'mdi:folder-multiple',
|
||||
keywords: ['projects', 'portfolio', 'showcase']
|
||||
},
|
||||
'/models': {
|
||||
title: `${user.displayname} — 3D Models`,
|
||||
description: 'A curated collection of 3D models and previews.',
|
||||
icon: 'mdi:cube-outline',
|
||||
keywords: ['3d', 'models', 'glb', 'gltf']
|
||||
},
|
||||
'/projects': {
|
||||
title: `${user.displayname} — Projects`,
|
||||
description: 'Hackathon projects, demos and awards.',
|
||||
icon: 'mdi:trophy',
|
||||
keywords: ['hackathon', 'projects', 'events']
|
||||
},
|
||||
'/components': {
|
||||
title: `${user.displayname} — Components`,
|
||||
description: 'Terminal UI components showcase and documentation.',
|
||||
icon: 'mdi:puzzle',
|
||||
keywords: ['components', 'ui', 'terminal', 'tui']
|
||||
}
|
||||
};
|
||||
238
src/lib/config/terminal.ts
Normal file
238
src/lib/config/terminal.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
// ============================================================================
|
||||
// TERMINAL DISPLAY SETTINGS
|
||||
// ============================================================================
|
||||
|
||||
export type SpeedPreset = 'instant' | 'fast' | 'normal' | 'slow' | 'typewriter';
|
||||
|
||||
export const terminalSettings = {
|
||||
// Base typing speed in ms per character (will be adjusted by content length)
|
||||
baseTypeSpeed: 20,
|
||||
// Minimum typing speed (fastest)
|
||||
minTypeSpeed: 5,
|
||||
// Maximum typing speed (slowest)
|
||||
maxTypeSpeed: 50,
|
||||
// Delay before starting to type (ms)
|
||||
startDelay: 300,
|
||||
// Delay between lines (ms)
|
||||
lineDelay: 100,
|
||||
// Show cursor
|
||||
showCursor: true,
|
||||
// Prompt style
|
||||
promptStyle: 'full' as 'full' | 'short' | 'minimal',
|
||||
// Terminal icon (emoji or text)
|
||||
icon: '🐧',
|
||||
// Scroll margin when navigating buttons (px)
|
||||
scrollMargin: 80,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TUI (Terminal UI) STYLING
|
||||
// ============================================================================
|
||||
|
||||
export const tuiStyle = {
|
||||
// Border & container
|
||||
borderRadius: 8, // px
|
||||
borderWidth: 2, // px
|
||||
width: '95%',
|
||||
borderGlowOpacity: 0.5,
|
||||
borderGlowBlur: 4, // px
|
||||
|
||||
// Header
|
||||
headerPadding: '0.5rem 0.75rem',
|
||||
headerFontSize: '0.8rem',
|
||||
|
||||
// Body
|
||||
bodyPadding: '1rem 1.25rem 2rem 1.25rem',
|
||||
bodyFontSize: '0.9rem',
|
||||
lineHeight: 1.7,
|
||||
lineMinHeight: '1.7em',
|
||||
blankLineHeight: '0.5em',
|
||||
|
||||
// Divider
|
||||
dividerMargin: '0.75rem 0',
|
||||
|
||||
// Images
|
||||
imageBorderRadius: 6, // px
|
||||
defaultImageWidth: 300, // px
|
||||
|
||||
// Cursor
|
||||
cursorWidth: 8, // px
|
||||
|
||||
// Scrollbar
|
||||
scrollbarWidth: 6, // px
|
||||
|
||||
// Footer/Statusbar
|
||||
statusbarPadding: '0.4rem 0.75rem',
|
||||
statusbarFontSize: '0.75rem',
|
||||
hintFontSize: '0.7rem',
|
||||
hintBorderRadius: 3, // px
|
||||
|
||||
// Buttons
|
||||
buttonPadding: '0.5rem 0.75rem',
|
||||
buttonMargin: '0.2rem 0',
|
||||
buttonBorderRadius: 4, // px
|
||||
buttonFontSize: '0.9rem',
|
||||
buttonIndicatorFontSize: '0.8rem',
|
||||
buttonIndicatorWidth: '1rem',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TUI TEXT & INDICATORS
|
||||
// ============================================================================
|
||||
|
||||
export const tuiText = {
|
||||
// Status text
|
||||
loading: 'Loading...',
|
||||
ready: 'Ready',
|
||||
skipButton: 'Skip (Y)',
|
||||
tuiLabel: 'TUI',
|
||||
|
||||
// Keyboard hints
|
||||
hints: {
|
||||
navigate: '↑↓ navigate',
|
||||
select: '⏎ select',
|
||||
toggle: 'T theme',
|
||||
},
|
||||
|
||||
// Line prefixes
|
||||
prefixes: {
|
||||
error: '✗ ',
|
||||
success: '✓ ',
|
||||
info: '› ',
|
||||
warning: '⚠ ',
|
||||
command: '$ ',
|
||||
},
|
||||
|
||||
// Button indicators
|
||||
buttonIndicator: {
|
||||
selected: '▶',
|
||||
unselected: ' ',
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Per-page speed settings (override global settings)
|
||||
// Use preset names or custom multiplier (0.1 = 10x faster, 2 = 2x slower)
|
||||
// ============================================================================
|
||||
|
||||
export const pageSpeedSettings: Record<string, SpeedPreset | number> = {
|
||||
// Examples:
|
||||
// 'home': 'fast', // Fast typing on home page
|
||||
// 'portfolio': 'normal', // Normal speed
|
||||
// 'models': 'instant', // No typing animation
|
||||
// 'hackathons': 0.5, // Custom: 2x faster than normal
|
||||
'home': 'fast',
|
||||
'portfolio': 'fast',
|
||||
'models': 'fast',
|
||||
'projects': 'fast'
|
||||
};
|
||||
|
||||
// Per-page autoscroll settings (whether to auto-scroll as content types)
|
||||
// Set to false to disable autoscroll on specific pages
|
||||
export const pageAutoscrollSettings: Record<string, boolean> = {
|
||||
// Examples:
|
||||
// 'home': true, // Enable autoscroll
|
||||
// 'portfolio': false, // Disable autoscroll
|
||||
'home': true,
|
||||
'portfolio': false,
|
||||
'models': false,
|
||||
'projects': false,
|
||||
'components': false
|
||||
};
|
||||
|
||||
// Speed preset multipliers (lower = faster)
|
||||
export const speedPresets: Record<SpeedPreset, number> = {
|
||||
'instant': 0, // No delay, instant display
|
||||
'fast': 0.1, // 3x faster
|
||||
'normal': 0.5, // Default speed
|
||||
'slow': 1.5, // 2x slower
|
||||
'typewriter': 2 // 3x slower, classic typewriter feel
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MODEL VIEWER (3D)
|
||||
// ============================================================================
|
||||
|
||||
export const modelViewer = {
|
||||
// Container
|
||||
borderRadius: 8, // px
|
||||
minHeight: 300, // px
|
||||
headerPadding: '0.5rem 0.75rem',
|
||||
nameFontSize: '0.85rem',
|
||||
|
||||
// Controls
|
||||
controlButtonSize: 28, // px
|
||||
controlButtonBorderRadius: 4, // px
|
||||
fullscreenZIndex: 100,
|
||||
fullscreenTopOffset: 60, // px (navbar height)
|
||||
|
||||
// Camera
|
||||
cameraFov: 45,
|
||||
cameraNear: 0.1,
|
||||
cameraFar: 1000,
|
||||
cameraZ: 2.5, // initial camera distance
|
||||
|
||||
// Controls behavior
|
||||
controlsMinDistance: 1,
|
||||
controlsMaxDistance: 10,
|
||||
dampingFactor: 0.05,
|
||||
autoRotateSpeed: 2,
|
||||
|
||||
// Model
|
||||
modelScale: 1.5,
|
||||
|
||||
// Ground plane
|
||||
groundColor: 0x222222,
|
||||
groundOpacity: 0.3,
|
||||
|
||||
// Lighting
|
||||
ambientIntensity: 0.6,
|
||||
directionalIntensity: 0.8,
|
||||
lightColor: 0xffffff,
|
||||
rimLightColor: 0x88ccff,
|
||||
|
||||
// Text
|
||||
loadingText: 'Loading model...',
|
||||
errorText: 'Failed to load model',
|
||||
hints: {
|
||||
rotate: 'Drag to rotate',
|
||||
zoom: 'Scroll to zoom',
|
||||
},
|
||||
|
||||
// Timing
|
||||
resizeTimeout: 100, // ms
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PARTICLES (3D Background)
|
||||
// ============================================================================
|
||||
|
||||
export const particles = {
|
||||
count: 600,
|
||||
spreadArea: 20,
|
||||
size: 0.05,
|
||||
velocityRange: 0.01,
|
||||
|
||||
// Opacity
|
||||
darkOpacity: 0.6,
|
||||
lightOpacity: 0.4,
|
||||
|
||||
// Motion
|
||||
waveAmplitude: 0.001,
|
||||
waveFrequency: 0.1,
|
||||
boundary: 10, // wrap boundary
|
||||
rotationSpeedY: 0.05,
|
||||
rotationSpeedX: 0.02,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// KEYBOARD 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'],
|
||||
};
|
||||
88
src/lib/config/theme.ts
Normal file
88
src/lib/config/theme.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
// ============================================================================
|
||||
// COLOR PALETTE
|
||||
// ============================================================================
|
||||
// These colors are used for terminal text formatting and UI elements
|
||||
// Colors follow Catppuccin Mocha theme by default
|
||||
|
||||
export const colorPalette = {
|
||||
// Basic colors
|
||||
red: '#f38ba8',
|
||||
green: '#a6e3a1',
|
||||
yellow: '#f9e2af',
|
||||
blue: '#89b4fa',
|
||||
magenta: '#cba6f7',
|
||||
cyan: '#94e2d5',
|
||||
white: '#cdd6f4',
|
||||
gray: '#6c7086',
|
||||
orange: '#fab387',
|
||||
pink: '#f5c2e7',
|
||||
black: '#1e1e2e',
|
||||
surface: '#313244',
|
||||
|
||||
// Semantic colors (map to basic colors by default)
|
||||
error: '#f38ba8', // red
|
||||
success: '#a6e3a1', // green
|
||||
warning: '#f9e2af', // yellow
|
||||
info: '#89b4fa', // blue
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TERMINAL WINDOW BUTTONS (traffic lights)
|
||||
// ============================================================================
|
||||
|
||||
export const terminalButtons = {
|
||||
close: '#ff5f56',
|
||||
minimize: '#ffbd2e',
|
||||
maximize: '#27ca40',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// LOADING SCREEN
|
||||
// ============================================================================
|
||||
|
||||
export const loadingScreen = {
|
||||
background: '#0d1117',
|
||||
textColor: '#c9d1d9',
|
||||
cursorColor: '#1793d1',
|
||||
text: "Blob's Portfolio",
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
export const effects = {
|
||||
backdropBlur: 10, // px
|
||||
selectionBackground: 'rgba(23, 147, 209, 0.3)',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ANIMATIONS
|
||||
// ============================================================================
|
||||
|
||||
export const animations = {
|
||||
// Terminal/TUI animations
|
||||
terminalFadeIn: { duration: '0.4s', easing: 'ease-out' },
|
||||
tuiFadeIn: { duration: '0.4s', easing: 'ease-out' },
|
||||
lineSlideIn: { duration: '0.15s', easing: 'ease-out' },
|
||||
cursorBlink: { duration: '1s' },
|
||||
borderGlow: { duration: '8s', transition: '0.3s' },
|
||||
|
||||
// Button/interaction animations
|
||||
buttonHover: { duration: '0.2s' },
|
||||
buttonTransition: { duration: '0.15s' },
|
||||
|
||||
// Navigation animations
|
||||
hamburgerTransition: { duration: '0.3s' },
|
||||
navLinkTransition: { duration: '0.2s' },
|
||||
linkUnderline: { duration: '0.3s' },
|
||||
dropdown: { flyDuration: 200, fadeDuration: 150 }, // ms
|
||||
|
||||
// Model viewer
|
||||
modelViewerTransition: { duration: '0.3s' },
|
||||
spin: { duration: '1s' },
|
||||
|
||||
// General
|
||||
pulse: { duration: '2s' },
|
||||
float: { duration: '3s' },
|
||||
};
|
||||
41
src/lib/config/user.ts
Normal file
41
src/lib/config/user.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// ============================================================================
|
||||
// USER PROFILE
|
||||
// ============================================================================
|
||||
|
||||
export const user = {
|
||||
name: 'Gagan M',
|
||||
displayname: 'Sir Blob',
|
||||
username: 'sirblob',
|
||||
hostname: 'engineering',
|
||||
title: 'Engineering Student',
|
||||
email: 'sirblob0@gmail.com',
|
||||
location: 'Washington DC-Baltimore Area',
|
||||
bio: `Hi, I am Sir Blob — a engineer who loves making things. ` +
|
||||
`I build fun coding projects, participate in game jams and hackathons, and enjoy games like Minecraft and Pokémon TCG Live.`,
|
||||
|
||||
// Prefer an absolute avatar URL if you want to pull directly from GitHub
|
||||
avatar: '/blob_nerd.png',
|
||||
|
||||
// Social links - array of { name, icon (Iconify), link }
|
||||
socials: [
|
||||
{ name: 'GitHub', icon: 'mdi:github', link: 'https://github.com/SirBlobby' },
|
||||
{ name: 'Gitea', icon: 'simple-icons:gitea', link: 'https://git.sirblob.co/SirBlob' },
|
||||
{ name: 'LinkedIn', icon: 'mdi:linkedin', link: 'https://www.linkedin.com/in/gmanjunatha/' },
|
||||
{ name: 'Devpost', icon: 'simple-icons:devpost', link: 'https://devpost.com/Sir_Blob_' },
|
||||
{ name: 'Discord', icon: 'ic:baseline-discord', link: 'https://discord.com/users/sir_blob_' }
|
||||
]
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SKILLS
|
||||
// ============================================================================
|
||||
|
||||
export const skills = {
|
||||
languages: ['Python', 'JavaScript', 'TypeScript', 'C', 'C++', 'Java', 'Node.js'],
|
||||
frameworks: ['Arduino', 'Bootstrap', 'TailwindCSS', 'Discord.js', 'React', 'Electron', 'Svelte'],
|
||||
applications: ['Windows', 'Linux', 'macOS', 'IntelliJ', 'VS Code', 'Git', 'Blender', 'Godot'],
|
||||
platforms: ['Windows', 'Linux', 'macOS', 'Arduino', 'Raspberry Pi'],
|
||||
tools: ['Git', 'Docker', 'Neovim', 'VS Code'],
|
||||
databases: ['MongoDB', 'Redis', 'SQLite'],
|
||||
interests: ['Open Source', '3D Graphics', 'Game Development', 'Embedded Systems', 'AI/ML']
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
import TerminalTUI from '$lib/components/TerminalTUI.svelte';
|
||||
import type { TerminalLine } from '$lib/components/tui/types';
|
||||
import ModelViewer from '$lib/components/ModelViewer.svelte';
|
||||
import { user } from '$lib/config';
|
||||
import { user, models as configModels } from '$lib/config';
|
||||
import { getPageSpeedMultiplier, getPageAutoscroll } from '$lib';
|
||||
import { themeColors } from '$lib/stores/theme';
|
||||
import Icon from '@iconify/svelte';
|
||||
@@ -10,13 +10,23 @@
|
||||
const speed = getPageSpeedMultiplier('models');
|
||||
const autoscroll = getPageAutoscroll('models');
|
||||
|
||||
// Available GLB files in static/models/
|
||||
const glbModels = [
|
||||
{ name: 'Bake Potato', path: '/models/BakePotato.glb' },
|
||||
{ name: 'Soda Can', path: '/models/Soda.glb' }
|
||||
];
|
||||
// Build GLB model list from config (`models`) while preserving metadata.
|
||||
// Keep `name`, `path`, `thumbnail`, and `description` for richer UI use.
|
||||
const glbModels = (configModels && configModels.length > 0)
|
||||
? configModels
|
||||
.map(m => ({
|
||||
name: m.name,
|
||||
path: m.modelUrl ?? m.downloadUrl ?? m.thumbnail ?? '',
|
||||
thumbnail: m.thumbnail ?? '',
|
||||
description: m.description ?? ''
|
||||
}))
|
||||
.filter(m => m.path)
|
||||
: [
|
||||
{ name: 'Bake Potato', path: '/models/BakePotato.glb', thumbnail: '/models/BakePotato-thumb.png', description: 'A fun baked-potato 3D model.' },
|
||||
{ name: 'Soda Can', path: '/models/Soda.glb', thumbnail: '/models/Soda-thumb.png', description: 'A realistic soda can model.' }
|
||||
];
|
||||
|
||||
let selectedModel = $state(glbModels[0]);
|
||||
let selectedModel = $state(glbModels[0] ?? { name: '', path: '', thumbnail: '', description: '' });
|
||||
let showViewer = $state(false);
|
||||
|
||||
function selectModel(model: typeof glbModels[0]) {
|
||||
@@ -26,7 +36,7 @@
|
||||
|
||||
// Build the terminal lines for the models page
|
||||
const lines: TerminalLine[] = [
|
||||
{ type: 'command', content: 'ls ~/3d-models/' },
|
||||
{ type: 'command', content: 'ls ~/models/' },
|
||||
{ type: 'blank', content: '' },
|
||||
|
||||
{ type: 'header', content: '(&primary,bold)3D Models(&)' },
|
||||
@@ -63,7 +73,7 @@
|
||||
>
|
||||
<!-- Terminal Section -->
|
||||
<div class="terminal-section">
|
||||
<TerminalTUI {lines} title="~/3d-models" interactive={true} {speed} {autoscroll} />
|
||||
<TerminalTUI {lines} title="~/models" interactive={true} {speed} {autoscroll} />
|
||||
</div>
|
||||
|
||||
<!-- 3D Viewer Section -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import TerminalTUI from '$lib/components/TerminalTUI.svelte';
|
||||
import type { TerminalLine } from '$lib/components/tui/types';
|
||||
import { user, sortedCards } from '$lib/config';
|
||||
import { user, sortedCards, projects } from '$lib/config';
|
||||
import { getPageSpeedMultiplier, getPageAutoscroll } from '$lib';
|
||||
|
||||
const speed = getPageSpeedMultiplier('projects');
|
||||
@@ -17,6 +17,47 @@
|
||||
{ type: 'divider', content: 'PROJECTS' },
|
||||
{ type: 'command', content: 'ls ~/projects --grid' },
|
||||
{ type: 'blank', content: '' },
|
||||
...projects.filter(p => p.featured).flatMap(project => [
|
||||
{ type: 'header' as const, content: `(&primary,bold)${project.name}(&)` },
|
||||
{ type: 'output' as const, content: `(&muted)${project.description}(&)` },
|
||||
{ type: 'info' as const, content: `(&info)Tech: (&primary)${project.tech.join(', ')}(&)` },
|
||||
...(project.github ? [{
|
||||
type: 'button' as const,
|
||||
content: 'View on GitHub',
|
||||
icon: 'mdi:github',
|
||||
style: 'accent' as const,
|
||||
href: project.github
|
||||
}] : []),
|
||||
...(project.live ? [{
|
||||
type: 'button' as const,
|
||||
content: 'View Live Demo',
|
||||
icon: 'mdi:open-in-new',
|
||||
style: 'accent' as const,
|
||||
href: project.live
|
||||
}] : []),
|
||||
{ type: 'blank' as const, content: '' }
|
||||
]),
|
||||
...projects.filter(p => !p.featured).flatMap(project => [
|
||||
{ type: 'success' as const, content: `(&success)${project.name}(&)` },
|
||||
{ type: 'output' as const, content: `(&muted)${project.description}(&)` },
|
||||
{ type: 'info' as const, content: `(&info)Tech:(&) (&primary)${project.tech.join(', ')}(&)` },
|
||||
...(project.github ? [{
|
||||
type: 'button' as const,
|
||||
content: 'View on GitHub',
|
||||
icon: 'mdi:github',
|
||||
style: 'accent' as const,
|
||||
href: project.github
|
||||
}] : []),
|
||||
...(project.live ? [{
|
||||
type: 'button' as const,
|
||||
content: 'View Live',
|
||||
icon: 'mdi:open-in-new',
|
||||
style: 'accent' as const,
|
||||
href: project.live
|
||||
}] : []),
|
||||
{ type: 'blank' as const, content: '' }
|
||||
]),
|
||||
{ type: 'blank', content: '' },
|
||||
{ type: 'divider', content: 'HACKATHONS' },
|
||||
{ type: 'command', content: 'ls ~/hackathons --grid' },
|
||||
{ type: 'blank', content: '' },
|
||||
|
||||
Reference in New Issue
Block a user