diff --git a/CLAUDE.md b/CLAUDE.md index 4bd27e3..72884dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,26 +26,29 @@ fmeyer.dev/ ├── app/ # Nuxt application code │ ├── app.vue # Root app component (global SEO, skip link, canonical URL, JSON-LD) │ ├── app.config.ts # Runtime app config (profile pic, email, footer links, UI theme) -│ ├── assets/css/main.css # Global CSS (animations, prefers-reduced-motion) +│ ├── assets/css/ +│ │ ├── main.css # Tailwind/Nuxt UI imports, fonts (@theme), focus ring, global reduced-motion +│ │ └── signal.css # Site theme: tokens, Nuxt UI overrides, `signal-*` component classes │ ├── composables/ -│ │ └── usePageSeo.ts # Sets useSeoMeta from a content page's seo/title/description +│ │ ├── usePageSeo.ts # Sets useSeoMeta from a content page's seo/title/description +│ │ └── useNow.ts # Hydration-safe "now" for time-dependent UI (Upcoming badges) │ ├── components/ -│ │ ├── AppHeader.vue # Floating pill navigation bar (role="banner") -│ │ ├── AppFooter.vue # Footer with social links (role="contentinfo") +│ │ ├── AppHeader.vue # Floating glass navigation bar (role="banner") +│ │ ├── AppFooter.vue # "Let's talk" contact card + credits (role="contentinfo") │ │ ├── ColorModeButton.vue # Dark/light mode toggle -│ │ ├── LabCard.vue # Card for a lab entry in the grid -│ │ ├── OgImage/ -│ │ │ └── NuxtSeoSatori.satori.vue # OG image template -│ │ ├── landing/ # Landing page section components -│ │ │ ├── Hero.vue -│ │ │ ├── Focus.vue -│ │ │ ├── WorkExperience.vue -│ │ │ ├── LabsTeaser.vue -│ │ │ └── SpeakingTeaser.vue -│ │ └── talks/ -│ │ └── TalkPreviewCard.vue +│ │ ├── HomeView.vue # Homepage: hero + agent-session terminal, focus, experience, speaking, labs +│ │ ├── LabsView.vue # Labs listing +│ │ ├── LabDetailView.vue # Lab detail: manifest panel, challenge → approach → next steps +│ │ ├── SpeakingView.vue # Speaking listing +│ │ ├── TalkDetailView.vue # Talk detail: session spec sheet, abstract, resources +│ │ ├── AgentSession.vue # Hero terminal (spec → code → verify) +│ │ ├── LabCard.vue # Lab card (whole card is a stretched link) +│ │ ├── TalkRow.vue # Talk row with date block (whole row is a stretched link) +│ │ ├── SectionHeader.vue # Numbered section eyebrow + heading +│ │ └── OgImage/ +│ │ └── NuxtSeoSatori.satori.vue # OG image template │ ├── layouts/ -│ │ └── default.vue # Wraps every page: UContainer + AppHeader + slot + AppFooter +│ │ └── default.vue # Backdrop (grid + glow) + AppHeader + slot + AppFooter │ ├── pages/ │ │ ├── index.vue # Homepage (queries index, labs, talks collections) │ │ ├── labs/ @@ -71,7 +74,6 @@ fmeyer.dev/ │ └── speaking/ # One .yml file per talk ├── public/ # Static assets served as-is │ ├── favicon.ico -│ ├── hero/ # Hero images (random-1.avif … random-9.avif) │ ├── profile/ # Profile photo │ └── robots.txt ├── content.config.ts # Nuxt Content collection schemas (Zod) @@ -192,8 +194,12 @@ resources: # optional list of linked assets - Use ` + + diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 0379427..79062c3 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -1,29 +1,70 @@ diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index 4a708c7..bfc5cbf 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -1,30 +1,58 @@ diff --git a/app/components/HomeView.vue b/app/components/HomeView.vue new file mode 100644 index 0000000..4437e6d --- /dev/null +++ b/app/components/HomeView.vue @@ -0,0 +1,414 @@ + + + diff --git a/app/components/LabCard.vue b/app/components/LabCard.vue index 50f249f..4ea4ee2 100644 --- a/app/components/LabCard.vue +++ b/app/components/LabCard.vue @@ -1,92 +1,168 @@ diff --git a/app/components/LabDetailView.vue b/app/components/LabDetailView.vue new file mode 100644 index 0000000..1fb3905 --- /dev/null +++ b/app/components/LabDetailView.vue @@ -0,0 +1,397 @@ + + + diff --git a/app/components/LabsView.vue b/app/components/LabsView.vue new file mode 100644 index 0000000..8a9c980 --- /dev/null +++ b/app/components/LabsView.vue @@ -0,0 +1,86 @@ + + + diff --git a/app/components/SectionHeader.vue b/app/components/SectionHeader.vue new file mode 100644 index 0000000..7b90f4d --- /dev/null +++ b/app/components/SectionHeader.vue @@ -0,0 +1,57 @@ + + + diff --git a/app/components/SpeakingView.vue b/app/components/SpeakingView.vue new file mode 100644 index 0000000..8f732f4 --- /dev/null +++ b/app/components/SpeakingView.vue @@ -0,0 +1,86 @@ + + + diff --git a/app/components/TalkDetailView.vue b/app/components/TalkDetailView.vue new file mode 100644 index 0000000..d267648 --- /dev/null +++ b/app/components/TalkDetailView.vue @@ -0,0 +1,379 @@ + + + diff --git a/app/components/TalkRow.vue b/app/components/TalkRow.vue new file mode 100644 index 0000000..6767ee4 --- /dev/null +++ b/app/components/TalkRow.vue @@ -0,0 +1,122 @@ + + + diff --git a/app/components/landing/Focus.vue b/app/components/landing/Focus.vue deleted file mode 100644 index f9255c2..0000000 --- a/app/components/landing/Focus.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/app/components/landing/Hero.vue b/app/components/landing/Hero.vue deleted file mode 100644 index 7928629..0000000 --- a/app/components/landing/Hero.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - diff --git a/app/components/landing/LabsTeaser.vue b/app/components/landing/LabsTeaser.vue deleted file mode 100644 index b9db905..0000000 --- a/app/components/landing/LabsTeaser.vue +++ /dev/null @@ -1,79 +0,0 @@ - - - diff --git a/app/components/landing/SpeakingTeaser.vue b/app/components/landing/SpeakingTeaser.vue deleted file mode 100644 index 7bae280..0000000 --- a/app/components/landing/SpeakingTeaser.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - diff --git a/app/components/landing/WorkExperience.vue b/app/components/landing/WorkExperience.vue deleted file mode 100644 index 6f5fe4d..0000000 --- a/app/components/landing/WorkExperience.vue +++ /dev/null @@ -1,133 +0,0 @@ - - - diff --git a/app/components/talks/TalkPreviewCard.vue b/app/components/talks/TalkPreviewCard.vue deleted file mode 100644 index 8d85c0d..0000000 --- a/app/components/talks/TalkPreviewCard.vue +++ /dev/null @@ -1,158 +0,0 @@ - - - diff --git a/app/composables/useNow.ts b/app/composables/useNow.ts new file mode 100644 index 0000000..f446108 --- /dev/null +++ b/app/composables/useNow.ts @@ -0,0 +1,13 @@ +// Time-dependent UI (e.g. "Upcoming" badges) must render the same on the +// server and during hydration. The prerender time is shared via the payload, +// then replaced with the visitor's clock after mount so badges stay correct +// even when the static build is older than a talk's date. +export const useNow = () => { + const now = useState('now', () => Date.now()) + + onMounted(() => { + now.value = Date.now() + }) + + return now +} diff --git a/app/error.vue b/app/error.vue index c8127bd..79d91f9 100644 --- a/app/error.vue +++ b/app/error.vue @@ -21,19 +21,15 @@ useSeoMeta({ diff --git a/app/layouts/default.vue b/app/layouts/default.vue index 408f8e9..28deff8 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -1,12 +1,17 @@ diff --git a/app/pages/index.vue b/app/pages/index.vue index 6c8cc33..fcb8650 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -3,16 +3,16 @@ const { data: page } = await useAsyncData('index', () => { return queryCollection('index').first() }) -const { data: latestLab } = await useAsyncData('latest-lab', async () => { +const { data: labs } = await useAsyncData('home-labs', async () => { const entries = await queryCollection('labs').all() - return sortLabs(entries)[0] + return sortLabs(entries) }) -const { data: latestTalk } = await useAsyncData('latest-talk', async () => { +const { data: talks } = await useAsyncData('home-talks', async () => { const entries = await queryCollection('talks').all() - return getLatestTalk(entries) + return sortTalks(entries) }) if (!page.value) { @@ -39,17 +39,10 @@ useSchemaOrg([ diff --git a/app/pages/labs/[slug].vue b/app/pages/labs/[slug].vue index 8796680..183f7cb 100644 --- a/app/pages/labs/[slug].vue +++ b/app/pages/labs/[slug].vue @@ -1,5 +1,5 @@ diff --git a/app/pages/labs/index.vue b/app/pages/labs/index.vue index c7bbb9a..c271816 100644 --- a/app/pages/labs/index.vue +++ b/app/pages/labs/index.vue @@ -41,52 +41,9 @@ useSchemaOrg([ diff --git a/app/pages/speaking/[slug].vue b/app/pages/speaking/[slug].vue index 5ac8480..6c946d1 100644 --- a/app/pages/speaking/[slug].vue +++ b/app/pages/speaking/[slug].vue @@ -1,5 +1,5 @@ diff --git a/app/pages/speaking/index.vue b/app/pages/speaking/index.vue index cad4a58..1c77629 100644 --- a/app/pages/speaking/index.vue +++ b/app/pages/speaking/index.vue @@ -41,61 +41,9 @@ useSchemaOrg([ diff --git a/app/utils/labs.ts b/app/utils/labs.ts index e153870..666478d 100644 --- a/app/utils/labs.ts +++ b/app/utils/labs.ts @@ -19,18 +19,15 @@ export type LabEntry = { note?: string } -export const labStatusMap: Record = { +export const labStatusMap: Record = { wip: { - label: 'WIP', - color: 'warning' + label: 'WIP' }, prototype: { - label: 'Prototype', - color: 'info' + label: 'Prototype' }, paused: { - label: 'Paused', - color: 'neutral' + label: 'Paused' } } diff --git a/content/index.yml b/content/index.yml index a78eb6f..375d872 100644 --- a/content/index.yml +++ b/content/index.yml @@ -6,13 +6,13 @@ description: Staff Agentic Engineer at Cordes & Graefe KG focused on organisatio hero: name: Fabian Meyer role: "Staff Agentic Engineer at Cordes\u00A0&\u00A0Graefe\u00A0KG" - intro: I am moving Cordes & Graefe to agentic engineering. That means shaping how teams work with agents, building the tooling and workflows that make the change stick, and staying hands-on so the practices hold up in real delivery. + intro: I help companies make the move to agentic engineering. That means shaping how teams work with agents, building the tooling and workflows that make the change stick, and staying hands-on so the practices hold up in real delivery. focus: title: Focus description: What I spend time on right now. items: - title: Agentic engineering transformation - description: Moving the organisation to agentic engineering by defining the target way of working, guiding teams through the shift, and making sure adoption shows up as real delivery impact. + description: Moving companies to agentic engineering by defining the target way of working, guiding teams through the shift, and making sure adoption shows up as real delivery impact. - title: Agent-ready tooling and workflows description: Integrating agents into existing delivery tooling and building spec-driven development workflows that let engineers move from executing process to reviewing outcomes. - title: Enablement and coaching diff --git a/nuxt.config.ts b/nuxt.config.ts index 885d279..c9d71d9 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -45,6 +45,21 @@ export default defineNuxtConfig({ }, hooks: { + 'build:manifest'(manifest) { + // Talk slides (app/utils/talkAssets.ts) are bundled so their URLs are + // hashed, but they are only opened on demand. Without this every page + // emits for ~3 MB of PDFs, which competes with + // the page's own resources on slow connections and hurts LCP. + for (const entry of Object.values(manifest)) { + if (entry.file?.endsWith('.pdf')) { + entry.prefetch = false + entry.preload = false + } + if (entry.assets) { + entry.assets = entry.assets.filter(asset => !asset.endsWith('.pdf')) + } + } + }, 'nitro:init'(nitro) { // Resolve __BASE__ in public/.htaccess.tpl using the runtime base URL // and write the result to .output/public/.htaccess. Templating happens at diff --git a/public/hero/random-1.avif b/public/hero/random-1.avif deleted file mode 100644 index f1f3663..0000000 Binary files a/public/hero/random-1.avif and /dev/null differ diff --git a/public/hero/random-2.avif b/public/hero/random-2.avif deleted file mode 100644 index 691e1ea..0000000 Binary files a/public/hero/random-2.avif and /dev/null differ diff --git a/public/hero/random-3.avif b/public/hero/random-3.avif deleted file mode 100644 index 1b62e43..0000000 Binary files a/public/hero/random-3.avif and /dev/null differ diff --git a/public/hero/random-4.avif b/public/hero/random-4.avif deleted file mode 100644 index 14f24d4..0000000 Binary files a/public/hero/random-4.avif and /dev/null differ diff --git a/public/hero/random-5.avif b/public/hero/random-5.avif deleted file mode 100644 index 78a0aa1..0000000 Binary files a/public/hero/random-5.avif and /dev/null differ diff --git a/public/hero/random-6.avif b/public/hero/random-6.avif deleted file mode 100644 index 16c95b5..0000000 Binary files a/public/hero/random-6.avif and /dev/null differ diff --git a/public/hero/random-7.avif b/public/hero/random-7.avif deleted file mode 100644 index 964eb5a..0000000 Binary files a/public/hero/random-7.avif and /dev/null differ diff --git a/public/hero/random-8.avif b/public/hero/random-8.avif deleted file mode 100644 index 2f9a31e..0000000 Binary files a/public/hero/random-8.avif and /dev/null differ diff --git a/public/hero/random-9.avif b/public/hero/random-9.avif deleted file mode 100644 index 14ff836..0000000 Binary files a/public/hero/random-9.avif and /dev/null differ