Files
infrapuzzle/projects/moritzgrafde/src/components/ContactSection.vue
T
Moritz Graf d2dac3470f feat(moritzgrafde): rewrite personal website with Vue 3, Vite, and Tailwind CSS
- 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
2026-09-26 20:37:53 +02:00

134 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>