Skip to content

wa1939/termfolio

Repository files navigation

termfolio

termfolio

Your portfolio should boot like a terminal, not load like a Squarespace.

Write in Obsidian. Push to GitHub. Your site updates.
No database. No CMS. No writing in two places.


Next.js React TypeScript Tailwind Obsidian Vercel


Stars Forks License Last Commit Visitors

Live Demo Β· Deploy Your Own Β· Full Tutorial


Deploy in 3 Minutes

No local setup required. Click, paste, done.

Step Action Time
1 Click Deploy with Vercel below β€” it forks the repo and creates your project 30s
2 Vercel asks for 3 env variables β€” get them from resend.com (free tier) 90s
3 Click Deploy β€” your site is live 60s

Deploy with Vercel

How to get the 3 env variables (2 minutes)
  1. Sign up at resend.com (free β€” 3,000 emails/month)
  2. RESEND_API_KEY β€” API Keys β†’ Create API Key β†’ copy
  3. RESEND_AUDIENCE_ID β€” Audiences β†’ Create Audience β†’ copy the ID
  4. NOTIFY_SECRET β€” Any random string. Generate one: openssl rand -hex 32

After deploy, clone your fork and edit one file β€” content/site.ts β€” to make it yours. Push. Done.

Want a full walkthrough with screenshots? Read the step-by-step tutorial.


See It in Action

Home Blog
Home β€” Boot sequence, interactive terminal, halftone profile Journal β€” Search, tag filters, writing activity heatmap
Blog Post About
Blog Post β€” 3 reading themes, ToC, focus mode, progress bar About β€” Experience timeline, skills, certifications
Contact Mobile
Contact β€” Cal.com scheduling embed, newsletter signup Card β€” Digital business card with vCard, QR, WhatsApp

Why termfolio?

Most portfolio templates make you choose: look good or easy to maintain.

Traditional portfolio termfolio
Write content CMS dashboard Obsidian / VS Code / any editor
Content storage Database (Postgres, Notion, Contentful) Plain .md files in your repo
Write in one place No β€” CMS + code Yes β€” just markdown
Vendor lock-in Tied to CMS provider Zero β€” it's just files
Works offline No Yes
Deploy Complex pipeline git push
Customize Dig through 50+ files Edit one file (content/site.ts)

What's Inside

The Best Reading Experience

We obsessed over reading UX so your visitors actually finish your posts:

  • 3 reading themes β€” Terminal (dark), Light, and Sepia. Readers switch mid-article without losing their place
  • Adjustable font size β€” Small, medium, large. Readers pick what's comfortable
  • Focus mode β€” Hides everything except the article. No nav, no sidebar, no distractions
  • Reading progress bar β€” Shows how far through the post they are
  • Table of contents β€” Auto-generated from headings, highlights current section as you scroll
  • Estimated reading time β€” Shown before they start
  • Syntax highlighting β€” Code blocks with proper language coloring
  • Full RTL/Arabic support β€” Set language: "ar" in frontmatter and the entire post flips β€” layout, fonts, everything

14 Built-in Terminal Commands

Your visitors won't just read β€” they'll play. The home page terminal is a real command parser:

Command What it does
snake Classic Snake game β€” playable right in the terminal
pokedex Browse Pokemon with stats, types, and pixel art
typing-test Speed typing challenge with WPM tracking
starmap Interactive constellation map based on your coordinates
worldmap SVG world map highlighting your city
json Paste and format/validate JSON instantly
dashboard System dashboard with live clock and stats
base64 Encode/decode Base64 strings
wordcount Count words, characters, and lines
epoch Convert Unix timestamps to human dates
uuid Generate random UUIDs
whoami Shows your identity (reads from config)
skills Shows your skills (reads from config)
theme Toggle light/dark mode

Every command reads from your config β€” whoami outputs your name, starmap shows your sky.

Write in Obsidian, Publish Everywhere

Obsidian (write) β†’ content/posts/*.md β†’ git push β†’ Live site

Your markdown files are the blog. No database. No CMS. No API calls to fetch content.

  • Symlink your vault β†’ posts update when you save in Obsidian
  • GitHub Action included β†’ auto-syncs from a separate vault repo on push
  • Works with any editor β€” VS Code, Vim, iA Writer β€” if it saves .md files, it works

See Obsidian Integration for setup instructions.

Comments Powered by GitHub

Readers comment on your posts using their GitHub account. Comments live in your repo's Discussions tab β€” no database, no moderation dashboard, no third-party service.

Setup takes 2 minutes:

  1. Go to giscus.app
  2. Enter your repo name β€” it checks if Discussions are enabled
  3. Pick a category (use "Announcements")
  4. Copy the 4 values it gives you
  5. Add them to your .env.local:
NEXT_PUBLIC_GISCUS_REPO=your-username/termfolio
NEXT_PUBLIC_GISCUS_REPO_ID=R_xxxxxxxxxx
NEXT_PUBLIC_GISCUS_CATEGORY=Announcements
NEXT_PUBLIC_GISCUS_CATEGORY_ID=DIC_xxxxxxxxxx

Comments appear at the bottom of every blog post. Moderate them from GitHub Discussions.

Everything Else

  • Digital business card β€” Shareable /card page with vCard download, QR code, WhatsApp, and Apple Wallet pass. Data lives in a single YAML file (content/card.md) β€” edit in Obsidian, no code changes needed
  • Newsletter β€” Email subscriptions + new-post notifications via Resend (free: 3,000 emails/month)
  • Halftone image effect β€” Your profile photo renders as a canvas-based halftone
  • Animated starfield β€” Star field background on the home page
  • Writing heatmap β€” GitHub-style contribution graph for your blog activity
  • Cal.com embed β€” Scheduling widget on the contact page
  • Spotify widget β€” Links to your music profile
  • SEO optimized β€” Dynamic OG images, sitemap, robots.txt, structured metadata
  • Vercel Analytics β€” Built-in analytics and speed insights
  • Security hardened β€” HSTS, CSP headers, rate limiting, timing-safe auth, input validation

Configure from One File

Open content/site.ts and make it yours. This single file controls your entire site:

export const siteConfig = {
  name: "Your Name",           // site title, metadata, emails, footer
  handle: "you",               // terminal prompt, top bar
  tagline: "your Β· tagline",   // below ASCII art
  email: "you@example.com",    // contact page, comment fallback
  siteUrl: "https://you.com",  // metadata, sitemap, emails

  // Terminal commands read from these
  whoami: { focus: "what you do", status: "what you're up to" },
  terminalSkills: ["skill1 // skill2 // skill3"],

  // Your location powers the star map and world map
  coordinates: { lat: 40.7128, lon: -74.0060, label: "New York" },

  // Social links in the nav bar
  socials: {
    github: { url: "https://github.com/you", label: "GitHub", icon: "</>" },
    linkedin: { url: "https://linkedin.com/in/you", label: "LinkedIn", icon: "[in]" },
  },

  // Generate ASCII art at patorjk.com/software/taag
  asciiArt: { home: [...], about: [...] },

  // About page
  bio: [...], experience: [...], skills: [...], certifications: [...],
}
Full field reference
Field Controls
name Site title, metadata, email sender, footer
handle Terminal prompt, top bar display
title Meta title, about page header
tagline Text below ASCII art on home page
description Meta/OG description across all pages
email Contact page, comment fallback
siteUrl Metadata, sitemap, robots.txt, email links
twitterHandle Twitter/X card metadata
calUrl / calEmbedUrl Contact page calendar widget
spotifyUrl Music widget link
coordinates Star map location, world map pin
asciiArt.home / asciiArt.about ASCII banners (generate here)
socials Nav bar links (GitHub, LinkedIn, X, etc.)
whoami Terminal whoami command output
terminalSkills Terminal skills command output
terminalPrompt Shell prompt (e.g. root@you:~)
developedBy Footer attribution (links to this repo)
customizedBy Your attribution β€” { name: "You", url: "..." }
bio, stats, experience, skills, certifications, credentials About page content

Blog Posts

Create .md files in content/posts/. That's your entire CMS:

---
title: "My First Post"
date: "2026-01-15"
excerpt: "A brief description for cards and SEO"
tags: ["topic", "another"]
status: "published"
language: "en"
---

Your markdown content here. Supports GFM: tables, task lists,
strikethrough, footnotes, syntax-highlighted code blocks.
Field Required Notes
title Yes Post title
date Yes ISO date (YYYY-MM-DD)
status Yes "published" or "draft" (drafts are hidden)
excerpt No Used in cards, SEO, and email notifications
tags No Array of strings for filtering
language No "en" (default) or "ar" for full RTL Arabic
coverImage No Path relative to public/
author No Defaults to siteConfig.name

Obsidian Integration

Option A: Copy files (simplest)

Copy .md files from your Obsidian vault to content/posts/.

Option B: Symlink (best for local dev)

Point content/posts/ at your vault. Posts update live when you save in Obsidian:

# macOS/Linux
ln -s ~/obsidian-vault/published content/posts

# Windows (PowerShell as Admin)
New-Item -ItemType SymbolicLink -Path content\posts -Target C:\Users\you\obsidian-vault\published
Option C: GitHub Action (fully automated)

A ready-made workflow is included at .github/workflows/sync-obsidian.yml. It syncs posts from a separate Obsidian vault repo automatically.

Setup:

  1. Store your Obsidian posts in a separate GitHub repo (e.g. your-username/obsidian-vault)
  2. Create a Personal Access Token with repo scope
  3. In this repo β†’ Settings β†’ Secrets β†’ Actions, add:
    • VAULT_REPO β€” your-username/obsidian-vault
    • VAULT_PAT β€” your PAT
    • VAULT_PATH β€” folder inside the vault repo (default: published)
  4. Runs daily at 6 AM UTC, or trigger manually from the Actions tab

Auto-trigger on vault push β€” add this workflow to your vault repo:

name: Trigger site sync
on:
  push:
    paths: ['published/**']
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - run: |
          curl -X POST \
            -H "Accept: application/vnd.github.v3+json" \
            -H "Authorization: token ${{ secrets.SITE_REPO_PAT }}" \
            https://api.github.com/repos/YOUR_USERNAME/termfolio/dispatches \
            -d '{"event_type":"sync-obsidian"}'

Now every vault push auto-updates your site.

Obsidian frontmatter template
---
title: "{{title}}"
date: "{{date:YYYY-MM-DD}}"
author: "Your Name"
excerpt: ""
coverImage: ""
tags: []
status: "draft"
language: "en"
---

Environment Variables

Variable Required Description
RESEND_API_KEY Yes Resend API key (free: 3,000 emails/month)
RESEND_AUDIENCE_ID Yes Resend audience ID (Audiences β†’ Create β†’ copy ID)
NOTIFY_SECRET Yes Any random string β€” protects the notification endpoint
RESEND_FROM_EMAIL No Sender address (default: Name <noreply@yourdomain.com>)
NEXT_PUBLIC_SITE_URL No Override site URL (default from siteConfig.siteUrl)
NEXT_PUBLIC_NASA_API_KEY No NASA API key for APOD widget
NEXT_PUBLIC_GISCUS_REPO No Repo name for Giscus comments
NEXT_PUBLIC_GISCUS_REPO_ID No Giscus repo ID
NEXT_PUBLIC_GISCUS_CATEGORY No Giscus category (use "Announcements")
NEXT_PUBLIC_GISCUS_CATEGORY_ID No Giscus category ID
WALLETWALLET_API_KEY No WalletWallet API key for Apple Wallet passes on /card

Tech Stack

Layer Technology
Framework Next.js 15 (App Router)
UI React 19, Tailwind CSS 3, CSS design tokens (--term-*)
Content Local Markdown with gray-matter β€” no database
Markdown unified β†’ remark-parse β†’ remark-gfm β†’ remark-rehype β†’ rehype-slug β†’ rehype-highlight β†’ rehype-react
Email Resend API (newsletter + new-post notifications)
Comments Giscus (GitHub Discussions β€” no database)
Analytics Vercel Analytics + Speed Insights
Fonts IBM Plex Mono (UI), Source Serif 4 (reading), Noto Naskh Arabic (RTL)

Project Structure

content/
  site.ts               ← THE file. Your entire site config.
  posts/*.md            ← Your blog posts. Drop markdown, done.
  card.md               ← Digital business card data (YAML frontmatter).

app/
  page.tsx              Home (boot terminal, hero, recent posts)
  about/page.tsx        About/resume page
  blog/page.tsx         Journal (search, heatmap, tag filters)
  blog/[slug]/          Blog post (3 themes, ToC, focus mode, comments)
  contact/page.tsx      Contact (Cal.com embed, newsletter)
  card/page.tsx         Digital business card (vCard, QR, WhatsApp)
  api/subscribe/        Newsletter subscription endpoint
  api/notify/           New-post notification endpoint
  api/card/vcard/       vCard (.vcf) download endpoint
  api/card/wallet/      Apple Wallet pass endpoint

components/             30+ components including:
  boot-terminal.tsx     Interactive terminal with 14 commands
  reading-controls.tsx  3 themes, font size, focus mode, progress bar
  writing-heatmap.tsx   GitHub-style contribution graph
  halftone-image.tsx    Canvas halftone image effect
  star-map.tsx          Interactive constellation renderer
  world-map.tsx         SVG world map with location pin

Dev Commands

npm run dev        # Development server
npm run build      # Production build
npm run lint       # ESLint
npm run typecheck  # TypeScript check
npm run check      # Lint + typecheck + build

Contributing

Contributions welcome:

  1. Fork the repo
  2. Create your branch (git checkout -b feat/cool-feature)
  3. Commit (git commit -m 'feat: add cool feature')
  4. Push (git push origin feat/cool-feature)
  5. Open a Pull Request

Report bugs or request features β†’

Star History

Star History Chart

Attribution

Keep the "developed by" footer link or add your own name next to it in content/site.ts:

customizedBy: { name: "Your Name", url: "https://your-site.com" }
// β†’ "developed by waleed alhamed Β· customized by your name"

License

MIT β€” use it however you want.


If this helped you build something cool, give it a ⭐

Built by Waleed Alhamed

About

A terminal/CLI-themed personal portfolio, blog, and digital business card. Syncs directly with your Obsidian vault - No database required. Just fork it, paste your CV into any AI to generate your config file, and deploy in minutes. Built with Next.js 15, React 19, TypeScript, and Tailwind CSS

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors