fix: update GitHub URLs to use Riotpiaole and forgejo.riotpiao.com (#13)
CI / CI (push) Successful in 5m44s

- Change public GitHub from github.com/rockliang to github.com/Riotpiaole
- Change private GitHub to forgejo.riotpiao.com for all references
- Update both desktop and mobile contact sections

---------

Co-authored-by: poimen  <[email protected]>
Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
2026-09-09 07:43:22 +00:00
co-authored by poimen
parent 16d61c7dea
commit f5e1d07321
10 changed files with 60134 additions and 173 deletions
+259 -159
View File
@@ -2,6 +2,7 @@
import Link from 'next/link'
import { ArrowLeft } from 'lucide-react'
import { motion } from 'framer-motion'
const skillCategories = [
{
@@ -46,18 +47,10 @@ const skillCategories = [
},
]
const coreCompetencies = [
'Multi-tenant GPU scheduling (sm70/Volta constraints)',
'Zero-downtime GitOps deployments',
'Service mesh patterns without Istio overhead',
'Hybrid cloud networking (Cloudflare + bare-metal)',
'Declarative IAM with OIDC claim mapping',
]
export default function HomelabPage() {
return (
<main className="min-h-screen bg-white dark:bg-gray-950">
<div className="max-w-4xl mx-auto px-6 py-20">
<div className="max-w-6xl mx-auto px-6 py-20">
<Link
href="/#project-homelab"
className="inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:underline mb-8"
@@ -66,178 +59,285 @@ export default function HomelabPage() {
Back to Projects
</Link>
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
Homelab: Self-Hosted Cloud Platform
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 mb-12">
AWS rebuilt from scratch at homefull stack from compute to observability.
</p>
{/* Hero */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="mb-16"
>
<h1 className="text-5xl font-bold text-gray-900 dark:text-white mb-4">
Building AWS at Home
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 mb-6">
A 3-year journey through distributed systems, from Step Functions to LLM inference.
</p>
<p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed">
There's a difference between knowing how systems work in theory and building them in production. I've spent the last 3 years learning this difference the hard wayfirst at AWS, then at RBC, and now at home. This is the story of how I learned distributed systems by owning every layer: from workflow orchestration to hardware networking, from GitOps to AI agents.
</p>
</motion.div>
{/* Stats */}
<section className="bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800 rounded-lg p-6 mb-12">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-center">
<div>
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">4</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Nodes</div>
{/* Chapter 1: Homelab */}
<motion.section
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="mb-16 border-l-4 border-purple-500 pl-8"
>
<div className="mb-6">
<div className="text-sm font-semibold text-purple-600 dark:text-purple-400 uppercase tracking-wide mb-2">
The Journey
</div>
<div>
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">20+</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Services</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Homelab: Building AWS from Scratch
</h2>
<p className="text-gray-600 dark:text-gray-400 font-medium">May 2025 Present</p>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none mb-6">
<p className="text-gray-700 dark:text-gray-300">
The question: <strong>How does LLM serving work at scale?</strong> The only way to answer that was to build an entire cloud-like platform with SaaS fundamentals from scratch. 4 bare-metal machines, 1 GPU node, 1 Dell PowerEdge (now in the garage due to noise), and 2 mini-desktops. All running Talos Linux, a Kubernetes-native OS designed for immutability.
</p>
</div>
{/* Infrastructure section + diagram */}
<div className="mb-8">
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Infrastructure Layer
</h3>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800 mb-6">
<p className="text-gray-700 dark:text-gray-300 mb-4">
4-node bare-metal cluster (3 control plane + 1 worker) running Talos Linuximmutable, API-driven OS designed for Kubernetes. All wired ethernet to avoid etcd consensus issues.
</p>
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>Compute:</strong> Talos Linux nodes, machine config via Terraform</li>
<li> <strong>Networking:</strong> Cilium CNI, nginx ingress, Cloudflare Tunnel for zero-trust external access</li>
<li> <strong>Storage:</strong> Longhorn for distributed block storage with disk tagging, MinIO for S3</li>
<li> <strong>GitOps:</strong> ArgoCD with multi-source Applications and sync waves</li>
</ul>
</div>
<div>
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">99.2%</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Uptime</div>
</div>
<div>
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">60%</div>
<div className="text-sm text-gray-600 dark:text-gray-400">LLM Latency Cut</div>
{/* Cluster Topology Diagram */}
<div className="mb-6 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden hover:shadow-lg transition-shadow">
<div className="p-4 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800 border-b border-gray-200 dark:border-gray-800">
<h4 className="font-semibold text-gray-900 dark:text-white">Talos Cluster Topology</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">Control planes, worker GPU, storage, and networking architecture</p>
</div>
<div className="w-full" style={{ height: '700px' }}>
<iframe
src="/diagrams/homelab-cluster.html"
className="w-full h-full border-none"
title="Talos Cluster Topology"
/>
</div>
</div>
</div>
</section>
{/* GitOps section + diagram */}
<div className="mb-8">
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
GitOps Evolution
</h3>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800 mb-6">
<p className="text-gray-700 dark:text-gray-300 mb-4">
Started with Terraform for everythingbut constant reconciliation of pods and configmaps created chaos. Brought in ArgoCD and established a clear split:
</p>
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>Terraform:</strong> Talos machine config (barely changes, no drift)</li>
<li> <strong>ArgoCD:</strong> CRD-driven observer pattern for K8s resources (changes sync automatically)</li>
<li> <strong>Design principle:</strong> Infrastructure should be easy for AI to modify. Declarative config + clear abstractions = easier for models to reason about</li>
</ul>
</div>
{/* GitOps Workflow Diagram */}
<div className="mb-6 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden hover:shadow-lg transition-shadow">
<div className="p-4 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800 border-b border-gray-200 dark:border-gray-800">
<h4 className="font-semibold text-gray-900 dark:text-white">GitOps Deployment Flow</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">ArgoCD for Kubernetes and Terraform for Talos infrastructure</p>
</div>
<div className="w-full" style={{ height: '700px' }}>
<iframe
src="/diagrams/homelab-gitops.html"
className="w-full h-full border-none"
title="GitOps Workflow"
/>
</div>
</div>
</div>
{/* AI/ML Platform */}
<div className="mb-8">
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
AI/ML Platform
</h3>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800 mb-6">
<p className="text-gray-700 dark:text-gray-300 mb-4">
Self-hosted LLM inference with multi-tenant GPU scheduling and 60% latency reduction.
</p>
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>Inference:</strong> vLLM serving Qwen3-32B with INT4 quantization</li>
<li> <strong>Multi-model:</strong> Ollama for smaller models, hot-swapping</li>
<li> <strong>Embeddings:</strong> TEI for text embeddings and reranking</li>
<li> <strong>Orchestration:</strong> KServe + custom Go API gateway</li>
<li> <strong>Scheduling:</strong> GPU node affinity with sm70/Volta constraints</li>
</ul>
</div>
{/* LLM Stack Diagram */}
<div className="mb-6 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden hover:shadow-lg transition-shadow">
<div className="p-4 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800 border-b border-gray-200 dark:border-gray-800">
<h4 className="font-semibold text-gray-900 dark:text-white">LLM Inference Architecture</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">Gateway routing to vLLM, Ollama, and TEI on GPU worker node</p>
</div>
<div className="w-full" style={{ height: '700px' }}>
<iframe
src="/diagrams/homelab-llm-stack.html"
className="w-full h-full border-none"
title="LLM Inference Stack"
/>
</div>
</div>
{/* Request Lifecycle Diagram */}
<div className="mb-6 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden hover:shadow-lg transition-shadow">
<div className="p-4 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800 border-b border-gray-200 dark:border-gray-800">
<h4 className="font-semibold text-gray-900 dark:text-white">Request Lifecycle</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">Client nginx gateway predictor streaming response</p>
</div>
<div className="w-full" style={{ height: '700px' }}>
<iframe
src="/diagrams/homelab-api-gateway-sequence.html"
className="w-full h-full border-none"
title="API Request Flow"
/>
</div>
</div>
</div>
{/* Data & Security */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
Data Platform
</h3>
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>PostgreSQL:</strong> CloudNativePG operator with HA, automated failover</li>
<li> <strong>Vector DB:</strong> pgvector extension for AI embeddings</li>
<li> <strong>Streaming:</strong> Kafka/Redpanda for event-driven architecture</li>
<li> <strong>Workflows:</strong> Temporal for durable, long-running processes</li>
</ul>
</div>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
Identity & Security
</h3>
<ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>SSO:</strong> Authentik OIDC provider with custom claims</li>
<li> <strong>RBAC:</strong> Kubernetes RBAC synced with Authentik groups</li>
<li> <strong>Secrets:</strong> SOPS-encrypted secrets in git, decrypted at deploy time</li>
<li> <strong>TLS:</strong> cert-manager with DNS-01 ACME via Cloudflare</li>
</ul>
</div>
</div>
<div className="bg-purple-50 dark:bg-purple-900/30 border-l-4 border-purple-500 p-4 rounded-r">
<div className="font-semibold text-purple-900 dark:text-purple-200 mb-1">Key Learnings</div>
<ul className="text-purple-800 dark:text-purple-100 text-sm space-y-1">
<li> Powerline adapters killed etcd consensus at 200ms latency. Ethernet to garage solved it.</li>
<li> Network is foundationalhardware matters more than code sometimes.</li>
<li> Design infrastructure so AI can modify it easily. Declarative + immutable = no surprises.</li>
</ul>
</div>
</motion.section>
{/* Chapter 4 */}
<motion.section
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="mb-16 border-l-4 border-yellow-500 pl-8"
>
<div className="mb-6">
<div className="text-sm font-semibold text-yellow-600 dark:text-yellow-400 uppercase tracking-wide mb-2">
Next: The AI System
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Poimen: The AI Agent System
</h2>
<p className="text-gray-600 dark:text-gray-400 font-medium">Building</p>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none mb-6">
<p className="text-gray-700 dark:text-gray-300">
Current LLM architecture works seamlessly with lambda/serverless patterns. The key insight: <strong>if AI is powerful because of context, tool-calling, and memory, what if we provide the right context in the right environment?</strong>
</p>
</div>
<ul className="space-y-3 text-gray-700 dark:text-gray-300 mb-6">
<li className="flex gap-3">
<span className="text-yellow-500 font-bold flex-shrink-0"></span>
<span><strong>poimen-memory:</strong> Graph-RAG with wiki-link indexing, 7B model for instruct QA, pgvector + OpenSearch hybrid search</span>
</li>
<li className="flex gap-3">
<span className="text-yellow-500 font-bold flex-shrink-0"></span>
<span><strong>poimen-workflows:</strong> Natural language executable WorkflowSpec via reasoning model, Temporal for durable execution</span>
</li>
<li className="flex gap-3">
<span className="text-yellow-500 font-bold flex-shrink-0"></span>
<span><strong>Activity Knowledge Base:</strong> Informs LLM about timeouts, retry policies, dependencies before routing</span>
</li>
<li className="flex gap-3">
<span className="text-yellow-500 font-bold flex-shrink-0"></span>
<span><strong>Generic state machine:</strong> JSON workflow spec + JSONPath parameter chaining. Temporal enables redrive + free self-hosted</span>
</li>
</ul>
<div className="bg-yellow-50 dark:bg-yellow-900/30 border-l-4 border-yellow-500 p-4 rounded-r">
<div className="font-semibold text-yellow-900 dark:text-yellow-200 mb-1">The Vision</div>
<p className="text-yellow-800 dark:text-yellow-100 text-sm">
Build a skill factory where a model generates workflows and completes them. Each Temporal activity is independently refinedenabling a general-purpose workflow orchestrator that improves through iteration.
</p>
</div>
</motion.section>
{/* Skills Grid */}
<section className="mb-12">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
Technologies & Skills
<motion.section
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="mb-12 mt-24 pt-16 border-t border-gray-200 dark:border-gray-800"
>
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-12">
Technologies & Skills Mastered
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{skillCategories.map((category) => (
<div
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{skillCategories.map((category, idx) => (
<motion.div
key={category.title}
className="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 border border-gray-200 dark:border-gray-800"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.05 }}
className="bg-gradient-to-br from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800 rounded-lg p-8 border border-gray-200 dark:border-gray-800 hover:shadow-lg transition-shadow h-full"
>
<h3 className="font-semibold text-gray-900 dark:text-white mb-2">
<h3 className="font-bold text-gray-900 dark:text-white mb-6 text-xl">
{category.title}
</h3>
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-3">
{category.skills.map((skill) => (
<span
key={skill}
className="text-xs px-2 py-1 rounded bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"
className="text-base px-4 py-2 rounded-full bg-white dark:bg-gray-700 text-blue-700 dark:text-blue-300 font-semibold border border-blue-200 dark:border-blue-600 hover:border-blue-400 dark:hover:border-blue-400 transition-colors"
>
{skill}
</span>
))}
</div>
</div>
</motion.div>
))}
</div>
</section>
{/* Core Competencies */}
<section className="mb-12">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Core Competencies Demonstrated
</h2>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<ul className="space-y-2">
{coreCompetencies.map((item) => (
<li key={item} className="flex items-start gap-2 text-gray-700 dark:text-gray-300">
<span className="text-blue-500 mt-1"></span>
{item}
</li>
))}
</ul>
</div>
</section>
{/* Architecture Sections */}
<section className="mb-12">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
Architecture Deep Dive
</h2>
{/* Infrastructure */}
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
Infrastructure Layer
</h3>
<p className="text-gray-700 dark:text-gray-300 mb-3">
4-node bare-metal cluster (3 control plane + 1 worker) running Talos Linuximmutable, API-driven OS designed for Kubernetes.
</p>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>Compute:</strong> Talos Linux nodes, machine config via Terraform</li>
<li> <strong>Networking:</strong> Cilium CNI, nginx ingress, Cloudflare Tunnel for zero-trust external access</li>
<li> <strong>Storage:</strong> Longhorn for distributed block storage with disk tagging, MinIO for S3</li>
<li> <strong>GitOps:</strong> ArgoCD with multi-source Applications and sync waves</li>
</ul>
</div>
{/* Data Platform */}
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
Data Platform
</h3>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>PostgreSQL:</strong> CloudNativePG operator with HA, automated failover</li>
<li> <strong>Vector DB:</strong> pgvector extension for AI embeddings</li>
<li> <strong>Streaming:</strong> Kafka/Redpanda for event-driven architecture</li>
<li> <strong>Workflows:</strong> Temporal for durable, long-running processes</li>
</ul>
</div>
{/* AI/ML */}
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
AI/ML Platform
</h3>
<p className="text-gray-700 dark:text-gray-300 mb-3">
Self-hosted LLM inference with multi-tenant GPU scheduling and 60% latency reduction.
</p>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>Inference:</strong> vLLM serving Qwen3-32B with INT4 quantization</li>
<li> <strong>Multi-model:</strong> Ollama for smaller models, hot-swapping</li>
<li> <strong>Embeddings:</strong> TEI for text embeddings and reranking</li>
<li> <strong>Orchestration:</strong> KServe + custom Go API gateway</li>
<li> <strong>Scheduling:</strong> GPU node affinity with sm70/Volta constraints</li>
</ul>
</div>
{/* Security */}
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
Identity & Security
</h3>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>SSO:</strong> Authentik OIDC provider with custom claims and group mapping</li>
<li> <strong>RBAC:</strong> Kubernetes RBAC synced with Authentik groups</li>
<li> <strong>Secrets:</strong> SOPS-encrypted secrets in git, decrypted at deploy time</li>
<li> <strong>TLS:</strong> cert-manager with DNS-01 ACME challenges via Cloudflare</li>
</ul>
</div>
{/* Observability */}
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
Observability Stack
</h3>
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li> <strong>Metrics:</strong> Prometheus with custom recording rules, Grafana dashboards</li>
<li> <strong>Logs:</strong> Loki for aggregation, structured logging from all services</li>
<li> <strong>Traces:</strong> Tempo + OpenTelemetry Collector for distributed tracing</li>
<li> <strong>Alerts:</strong> AlertManager Slack integration for incident response</li>
</ul>
</div>
</section>
{/* Applications */}
<section className="mb-12">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Self-Hosted Applications
</h2>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
<div className="flex flex-wrap gap-2">
{['Paperless-ngx', 'Immich', 'Homarr', 'Portainer', 'Forgejo', 'Authentik'].map((app) => (
<span
key={app}
className="text-sm px-3 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
>
{app}
</span>
))}
</div>
</div>
</section>
</motion.section>
</div>
</main>
)
+2 -1
View File
@@ -29,6 +29,7 @@ export default function Home() {
: undefined,
videoUrl: '#',
articleUrl: index === 4 ? 'https://lnkd.in/p/gm2PZkWw' : '#',
deepDive: index === 0 ? { url: '/homelab', label: 'Architecture & Deep Dive' } : undefined,
ciRepos: index === 0 ? [
{ label: 'portfolio', repo: 'rock/riotpiao.com', forgejoBase: 'https://forgejo.riotpiao.com/rock/riotpiao.com' },
{ label: 'homelab-frontend', repo: 'rock/homelab-frontend', forgejoBase: 'https://forgejo.riotpiao.com/rock/homelab-frontend' },
@@ -136,7 +137,7 @@ export default function Home() {
<div className="max-w-6xl mx-auto text-center text-sm text-gray-600 dark:text-gray-400">
<p>{t.footer.copyright}</p>
<p className="mt-2">
<a href="https://github.com/rockliang" target="_blank" rel="noopener noreferrer" className="hover:text-blue-600 dark:hover:text-blue-400">
<a href="https://github.com/Riotpiaole" target="_blank" rel="noopener noreferrer" className="hover:text-blue-600 dark:hover:text-blue-400">
{t.footer.github}
</a>
{' • '}