- Replace legacy Hugo static site generator and hugo-goa theme with a modern Vue 3 + Vite + TypeScript application - Implement Clean Enterprise design system using Tailwind CSS with light/dark mode support - Position website around Cloud Platform & Infrastructure Leadership (GCP & AWS central platforms, Kubernetes at scale, enterprise spin-off governance, and systems depth) without chronological resumes - Add 6 comprehensive capability dossiers and verified credential badges (CKA, PSM II, M.Sc.) - Implement interactive skill matrix with real-time text search and category filtering - Add engineering philosophy section, contact channels with one-click email copying, and Impressum modal - Configure multi-stage Dockerfile (Node builder to Nginx runtime) with custom gzip, caching, and security headers - Update build scripts (Makefile, build.sh), .gitignore, and AGENTS.md documentation across the monorepo
134 lines
5.9 KiB
Vue
134 lines
5.9 KiB
Vue
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { personalInfo, socialLinks } from '@/data/portfolioData'
|
||
import { Mail, Linkedin, Github, Copy, Check, ExternalLink, ShieldCheck } from 'lucide-vue-next'
|
||
|
||
defineEmits<{
|
||
(e: 'openImpressum'): void
|
||
}>()
|
||
|
||
const copied = ref(false)
|
||
|
||
const copyEmail = async () => {
|
||
try {
|
||
await navigator.clipboard.writeText(personalInfo.email)
|
||
copied.value = true
|
||
setTimeout(() => {
|
||
copied.value = false
|
||
}, 2000)
|
||
} catch (err) {
|
||
console.error('Failed to copy', err)
|
||
}
|
||
}
|
||
|
||
const getSocialIcon = (platform: string) => {
|
||
switch (platform) {
|
||
case 'LinkedIn': return Linkedin
|
||
case 'GitHub': return Github
|
||
case 'Email': return Mail
|
||
default: return ExternalLink
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<section id="contact" class="py-16 md:py-24 border-t border-slate-200/80 dark:border-slate-800/80 bg-slate-100/60 dark:bg-slate-900/40">
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
|
||
<div class="max-w-3xl mx-auto text-center space-y-4 mb-12">
|
||
<div class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold uppercase tracking-wider bg-blue-50 text-blue-700 dark:bg-blue-950/60 dark:text-blue-300 border border-blue-200/80 dark:border-blue-800/80">
|
||
<Mail class="w-3.5 h-3.5" />
|
||
<span>Connect & Exchange</span>
|
||
</div>
|
||
<h2 class="text-3xl sm:text-4xl font-extrabold text-slate-900 dark:text-white tracking-tight">
|
||
Let’s Talk Cloud Platforms, Systems & Scale
|
||
</h2>
|
||
<p class="text-base sm:text-lg text-slate-600 dark:text-slate-300">
|
||
Whether you want to discuss cloud platform engineering, Kubernetes at scale, enterprise carve-outs, or maker hardware craft: my inbox is open.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Contact Cards -->
|
||
<div class="max-w-2xl mx-auto grid grid-cols-1 sm:grid-cols-2 gap-4 mb-12">
|
||
<!-- Direct Email Card with Copy Button -->
|
||
<div class="p-6 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200/90 dark:border-slate-800/90 shadow-xs flex flex-col justify-between text-left">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<span class="text-xs font-bold uppercase tracking-wider text-blue-600 dark:text-blue-400">
|
||
Direct Mail
|
||
</span>
|
||
<div class="p-2 rounded-lg bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400">
|
||
<Mail class="w-4 h-4" />
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<div class="text-xs text-slate-500 dark:text-slate-400 mb-1">Send an inquiry</div>
|
||
<a
|
||
:href="`mailto:${personalInfo.email}`"
|
||
class="text-sm font-semibold text-slate-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-colors break-all"
|
||
>
|
||
{{ personalInfo.email }}
|
||
</a>
|
||
</div>
|
||
<button
|
||
@click="copyEmail"
|
||
class="inline-flex items-center justify-center gap-1.5 w-full py-2 px-3 rounded-lg text-xs font-semibold bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 text-slate-700 dark:text-slate-200 transition-colors cursor-pointer"
|
||
>
|
||
<Check v-if="copied" class="w-3.5 h-3.5 text-emerald-500" />
|
||
<Copy v-else class="w-3.5 h-3.5 text-slate-500" />
|
||
<span>{{ copied ? 'Copied to Clipboard!' : 'Copy Email Address' }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Professional Networks -->
|
||
<div class="p-6 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200/90 dark:border-slate-800/90 shadow-xs flex flex-col justify-between text-left">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<span class="text-xs font-bold uppercase tracking-wider text-blue-600 dark:text-blue-400">
|
||
Public Profiles
|
||
</span>
|
||
<div class="p-2 rounded-lg bg-indigo-50 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400">
|
||
<ExternalLink class="w-4 h-4" />
|
||
</div>
|
||
</div>
|
||
<div class="space-y-3">
|
||
<a
|
||
v-for="link in socialLinks.filter(l => l.platform !== 'Email')"
|
||
:key="link.platform"
|
||
:href="link.url"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="flex items-center justify-between p-2.5 rounded-lg border border-slate-200/80 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/60 transition-all group"
|
||
>
|
||
<div class="flex items-center gap-2.5">
|
||
<component :is="getSocialIcon(link.platform)" class="w-4 h-4 text-slate-500 group-hover:text-blue-600 transition-colors" />
|
||
<span class="text-xs font-semibold text-slate-800 dark:text-slate-200">{{ link.platform }}</span>
|
||
</div>
|
||
<span class="text-[11px] font-mono text-slate-400 group-hover:text-blue-600 dark:group-hover:text-blue-400">
|
||
/{{ link.handle }}
|
||
</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Infrastructure Tag & Impressum Trigger -->
|
||
<div class="pt-8 border-t border-slate-200/80 dark:border-slate-800/80 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-slate-500 dark:text-slate-400">
|
||
<div class="flex items-center gap-2">
|
||
<ShieldCheck class="w-4 h-4 text-emerald-500" />
|
||
<span>Self-hosted on private Kubernetes infrastructure (haumdaucher) via GitOps</span>
|
||
</div>
|
||
<div class="flex items-center gap-4">
|
||
<button
|
||
@click="$emit('openImpressum')"
|
||
class="hover:text-slate-800 dark:hover:text-slate-200 underline cursor-pointer"
|
||
>
|
||
Legal Notice (Impressum)
|
||
</button>
|
||
<span>·</span>
|
||
<span>© {{ new Date().getFullYear() }} Moritz Graf</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
</template>
|