Personal portfolio website of Ferhat Tüfekçi.
Live: https://ferhattufekci.github.io
- HTML5 / CSS3 / JavaScript
- jQuery + Shuffle.js (portfolio filtering)
- GitHub Actions (Medium article sync + GitHub repo sync)
- GitHub Pages (static hosting)
.
├── index.html # Single-page portfolio (partially auto-generated,
│ # see "SEO / AI Discoverability" below)
├── css/ # Stylesheets
├── js/ # Client-side scripts
├── images/ # Images and assets
├── fonts/ # Local fonts & icons
├── data/
│ ├── medium-systems.json # Auto-generated by GitHub Actions
│ ├── medium-test.json
│ ├── medium-software.json
│ ├── medium-all.json
│ ├── meta.json
│ ├── github-repos.json # Auto-generated by GitHub Actions
│ ├── github-starred.json # Auto-generated by GitHub Actions
│ └── github-meta.json
├── scripts/
│ ├── fetch-medium.js # Medium RSS fetcher
│ ├── fetch-github.js # GitHub repos & stars fetcher
│ ├── build-static-seo.js # Regenerates crawler-visible fallback
│ │ # content in index.html + llms.txt
│ └── package.json
├── .github/workflows/
│ ├── fetch-medium.yml # Runs every 6 hours
│ └── fetch-github.yml # Runs every Monday 03:00 UTC
├── FerhatTufekci_CV.pdf
├── ads.txt
├── robots.txt
├── sitemap.xml
├── llms.txt # Plain-text index for LLM/AI answer engines
└── BingSiteAuth.xml
| Page | Description |
|---|---|
| About Me | Hero section with photo, title rotation, and "Why Work With Me?" |
| Resume | Full CV as a web page — Summary, Top Skills, Languages, Education, Experience, Skills & Toolset, and Certifications |
| Blog | Medium articles with language (EN/TR) and category filters |
| Projects | GitHub repositories with category filters, pinned repos, and starred section |
| Contact | Phone, email, Calendly interview scheduler, and links |
Articles are automatically fetched from Medium every 6 hours via GitHub Actions.
How it works:
fetch-medium.jsreadshttps://medium.com/feed/@ferhattufekci- Category is detected automatically from RSS tags (
systems/test/software) - Language is detected automatically from title (Turkish characters →
tr, otherwiseen) - Articles are saved to
data/medium-*.json - Browser reads these JSON files — no CORS issues
To exclude an article from the site:
Add its hex ID (last segment of the Medium URL) to the EXCLUDE array in scripts/fetch-medium.js.
No manual steps needed when publishing a new article on Medium.
The Blog section supports two independent filters:
| Filter | Options |
|---|---|
| Language | All / 🇬🇧 EN / 🇹🇷 TR |
| Category | All / Systems Engineering & Business Analysis / Test Engineering / Software Engineering |
Repositories are automatically fetched from GitHub every Monday via GitHub Actions.
How it works:
fetch-github.jscallsGET /users/ferhattufekci/repos(REST API)- Pinned repos are fetched via GitHub GraphQL API
- All languages per repo are fetched via
GET /repos/{owner}/{repo}/languages - Starred repos are fetched via
GET /users/ferhattufekci/starred - Category is detected automatically from repo name, description, and topics
- Results are saved to
data/github-repos.json,data/github-starred.json,data/github-meta.json
Projects page sections:
| Section | Description |
|---|---|
| 📌 PINNED | Repos pinned on GitHub profile |
| 📂 ALL REPOSITORIES | All public repos, sorted by stars |
| ⭐ STARRED | Repos starred by Ferhat — resources he finds valuable (first 20 shown, expandable) |
Category filter applies to PINNED and ALL REPOSITORIES sections.
Label updates dynamically to show the selected filter name.
To exclude a repo from the site:
Add the repo name to the EXCLUDE set in scripts/fetch-github.js.
No manual steps needed when creating or updating a repository.
The Blog and Projects sections are populated client-side via fetch(), which Googlebot renders but many AI answer-engine crawlers (GPTBot, PerplexityBot, and others) don't — they read raw HTML only. Several things exist specifically to make this site's content visible to those crawlers too:
| What | Where | Purpose |
|---|---|---|
<noscript> fallback |
index.html, inside the Projects and Blog subpages |
Real titles/links/descriptions in the raw HTML for crawlers that don't execute JavaScript. Invisible to normal browsers with JS enabled — no visual or duplicate-content impact. |
llms.txt |
site root | Plain-text index (the emerging llms.txt convention) summarizing who this site is about, its core pages, and current articles/projects — meant to be fetched directly by LLM-based answer engines. |
scripts/build-static-seo.js |
scripts/ |
Regenerates both of the above from data/github-repos.json and data/medium-all.json, so they can't go stale. Also bumps sitemap.xml's <lastmod>, but only on runs where it actually changed something. |
Person.knowsAbout |
JSON-LD in index.html <head> |
Topical signals for search/AI, drawn from the existing <meta name="keywords"> list. |
| AI crawler allowlist | robots.txt |
Explicitly lists GPTBot, ClaudeBot, PerplexityBot, Google-Extended, and others. Behavior is unchanged (the wildcard Allow: / already covered them) — this documents the opt-in. |
build-static-seo.js runs automatically as a step in both fetch-github.yml and fetch-medium.yml, right after the data fetch, so the fallback content and llms.txt stay in sync with real data on every scheduled sync. It's idempotent — re-running it with unchanged data produces no diff, so it never creates empty commits.
To regenerate manually:
node scripts/build-static-seo.js# VS Code Live Server (recommended)
# Right-click index.html → Open with Live Server
# Or Python
python3 -m http.server 8000Published for portfolio and demonstration purposes.
All personal content, articles, images, and branding are the intellectual property of Ferhat Tüfekçi.
Any commercial use, redistribution, or derivative work requires prior written permission.
Ferhat Tüfekçi'nin kişisel portföy web sitesi.
Canlı: https://ferhattufekci.github.io
- HTML5 / CSS3 / JavaScript
- jQuery + Shuffle.js (portföy filtreleme)
- GitHub Actions (Medium makale senkronizasyonu + GitHub repo senkronizasyonu)
- GitHub Pages (statik barındırma)
.
├── index.html # Tek sayfalık portföy (kısmen otomatik üretilir,
│ # bkz. aşağıdaki "SEO / AI Keşfedilebilirliği")
├── css/ # Stil dosyaları
├── js/ # İstemci tarafı betikler
├── images/ # Görseller ve kaynaklar
├── fonts/ # Yerel fontlar ve ikonlar
├── data/
│ ├── medium-systems.json # GitHub Actions tarafından otomatik oluşturulur
│ ├── medium-test.json
│ ├── medium-software.json
│ ├── medium-all.json
│ ├── meta.json
│ ├── github-repos.json # GitHub Actions tarafından otomatik oluşturulur
│ ├── github-starred.json # GitHub Actions tarafından otomatik oluşturulur
│ └── github-meta.json
├── scripts/
│ ├── fetch-medium.js # Medium RSS çekici
│ ├── fetch-github.js # GitHub repo ve yıldız çekici
│ ├── build-static-seo.js # index.html + llms.txt içindeki crawler'a
│ │ # özel yedek içeriği yeniden üretir
│ └── package.json
├── .github/workflows/
│ ├── fetch-medium.yml # Her 6 saatte bir çalışır
│ └── fetch-github.yml # Her Pazartesi 03:00 UTC'de çalışır
├── FerhatTufekci_CV.pdf
├── ads.txt
├── robots.txt
├── sitemap.xml
├── llms.txt # LLM/AI cevap motorları için düz metin özeti
└── BingSiteAuth.xml
| Sayfa | Açıklama |
|---|---|
| About Me | Fotoğraf, dönen başlık ve "Why Work With Me?" bölümü |
| Resume | Tam özgeçmiş, web sayfası olarak — Summary, Top Skills, Languages, Education, Experience, Skills & Toolset ve Certifications |
| Blog | Medium makaleleri — dil (EN/TR) ve kategori filtreleriyle |
| Projects | GitHub repoları — kategori filtresi, sabitlenmiş repolar ve yıldızlı repolar |
| Contact | Telefon, e-posta, Calendly mülakat planlayıcı ve bağlantılar |
Makaleler, GitHub Actions aracılığıyla her 6 saatte bir Medium'dan otomatik olarak çekilir.
Nasıl çalışır:
fetch-medium.js,https://medium.com/feed/@ferhattufekciadresini okur- Kategori, RSS tag'lerinden otomatik tespit edilir (
systems/test/software) - Dil, başlıktaki Türkçe karakterlerden otomatik tespit edilir (Türkçe karakter →
tr, aksi haldeen) - Makaleler
data/medium-*.jsondosyalarına kaydedilir - Tarayıcı bu JSON dosyalarını okur — CORS sorunu yok
Bir makaleyi siteden gizlemek için:
scripts/fetch-medium.js dosyasındaki EXCLUDE dizisine Medium URL'sinin sonundaki hex ID'yi ekle.
Medium'da yeni makale yayınlandığında hiçbir manuel adım gerekmez.
Blog bölümü iki bağımsız filtre destekler:
| Filtre | Seçenekler |
|---|---|
| Dil | Tümü / 🇬🇧 EN / 🇹🇷 TR |
| Kategori | Tümü / Systems Engineering & Business Analysis / Test Engineering / Software Engineering |
Repolar, GitHub Actions aracılığıyla her Pazartesi otomatik olarak çekilir.
Nasıl çalışır:
fetch-github.js,GET /users/ferhattufekci/reposçağrısı yapar (REST API)- Sabitlenmiş repolar GitHub GraphQL API ile çekilir
- Her repo için dil dağılımı
GET /repos/{owner}/{repo}/languagesile alınır - Yıldızlanan repolar
GET /users/ferhattufekci/starredile çekilir - Kategori; repo adı, açıklaması ve topic'lerinden otomatik tespit edilir
- Sonuçlar
data/github-repos.json,data/github-starred.json,data/github-meta.jsondosyalarına kaydedilir
Projects sayfası bölümleri:
| Bölüm | Açıklama |
|---|---|
| 📌 PINNED | GitHub profilinde sabitlenmiş repolar |
| 📂 ALL REPOSITORIES | Tüm public repolar, yıldız sayısına göre sıralı |
| ⭐ STARRED | Ferhat'ın yıldızladığı repolar — değerli bulduğu kaynaklar (ilk 20 gösterilir, genişletilebilir) |
Kategori filtresi PINNED ve ALL REPOSITORIES bölümlerine uygulanır.
Bölüm başlığı, seçilen filtre adını dinamik olarak gösterir.
Bir repoyu siteden gizlemek için:
scripts/fetch-github.js dosyasındaki EXCLUDE kümesine repo adını ekle.
Yeni repo oluşturulduğunda veya güncellendiğinde hiçbir manuel adım gerekmez.
Blog ve Projects bölümleri client-side fetch() ile dolduruluyor; Googlebot bunu render eder ama GPTBot, PerplexityBot gibi birçok AI cevap motoru crawler'ı render etmez — sadece ham HTML'i okur. Bu içeriği o crawler'lara da görünür kılmak için özel olarak eklenenler:
| Ne | Nerede | Amacı |
|---|---|---|
<noscript> yedek içerik |
index.html, Projects ve Blog alt sayfalarının içinde |
JavaScript çalıştırmayan crawler'lar için ham HTML'de gerçek başlık/link/açıklama. JS'li normal tarayıcılarda görünmez — görsel veya duplicate-content etkisi yok. |
llms.txt |
site köküne | Bu sitenin ne olduğunu, ana sayfalarını ve güncel makale/proje listesini özetleyen düz metin dosyası (gelişen llms.txt standardı) — LLM tabanlı cevap motorları tarafından doğrudan çekilmesi amaçlanıyor. |
scripts/build-static-seo.js |
scripts/ |
Yukarıdaki ikisini data/github-repos.json ve data/medium-all.json'dan yeniden üretir, böylece bayatlayamazlar. Ayrıca sitemap.xml'in <lastmod>'unu günceller — ama yalnızca gerçekten bir şey değiştiyse. |
Person.knowsAbout |
index.html <head>'indeki JSON-LD |
Sitede zaten var olan <meta name="keywords"> listesinden türetilmiş, arama/AI için konu sinyalleri. |
| AI crawler izin listesi | robots.txt |
GPTBot, ClaudeBot, PerplexityBot, Google-Extended ve diğerlerini açıkça listeler. Davranış değişmedi (joker Allow: / zaten kapsıyordu) — bu sadece niyeti belgeliyor. |
build-static-seo.js, hem fetch-github.yml hem fetch-medium.yml içinde veri çekme adımından hemen sonra otomatik çalışır; böylece yedek içerik ve llms.txt, her zamanlanmış senkronizasyonda gerçek veriyle senkron kalır. Idempotent'tir — veri değişmediyse yeniden çalıştırmak hiçbir fark üretmez, dolayısıyla asla boş commit oluşturmaz.
Elle yeniden üretmek için:
node scripts/build-static-seo.js# VS Code Live Server (önerilen)
# index.html üzerine sağ tıkla → Open with Live Server
# Veya Python ile
python3 -m http.server 8000Bu proje portföy ve tanıtım amaçlı yayımlanmıştır.
Tüm kişisel içerik, makaleler, görseller ve marka öğeleri Ferhat Tüfekçi'ye aittir.
Ticari kullanım, yeniden dağıtım veya türev çalışmalar için önceden yazılı izin alınması gerekmektedir.