diff --git a/apps/web/app/(site)/blog/layout.tsx b/apps/web/app/(site)/blog/layout.tsx
new file mode 100644
index 0000000..1191fef
--- /dev/null
+++ b/apps/web/app/(site)/blog/layout.tsx
@@ -0,0 +1,9 @@
+import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
+
+export default function LegacyLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/apps/web/app/(site)/book/page.tsx b/apps/web/app/(site)/book/page.tsx
new file mode 100644
index 0000000..de7fa0a
--- /dev/null
+++ b/apps/web/app/(site)/book/page.tsx
@@ -0,0 +1,18 @@
+import type { Metadata } from "next";
+import { Container } from "@/src/components/site/Container";
+import { SectionHeader } from "@/src/components/site/SectionHeader";
+
+export const metadata: Metadata = { title: "Book a call" };
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/app/(site)/case-studies/layout.tsx b/apps/web/app/(site)/case-studies/layout.tsx
new file mode 100644
index 0000000..1191fef
--- /dev/null
+++ b/apps/web/app/(site)/case-studies/layout.tsx
@@ -0,0 +1,9 @@
+import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
+
+export default function LegacyLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/apps/web/app/(site)/contact/layout.tsx b/apps/web/app/(site)/contact/layout.tsx
new file mode 100644
index 0000000..1191fef
--- /dev/null
+++ b/apps/web/app/(site)/contact/layout.tsx
@@ -0,0 +1,9 @@
+import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
+
+export default function LegacyLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/apps/web/app/(site)/globals.css b/apps/web/app/(site)/globals.css
index 670000e..e1d1060 100644
--- a/apps/web/app/(site)/globals.css
+++ b/apps/web/app/(site)/globals.css
@@ -2,6 +2,40 @@
@tailwind components;
@tailwind utilities;
+/* Theme tokens. Dark is the site default; legacy pages (blog, legal, ...) opt into
+ the light variant by wrapping their content in `.theme-light` (LegacyLightSurface). */
+:root {
+ --color-bg: #0a0d12;
+ --color-surface: #0d1219;
+ --color-fg: #e6edf3;
+ --color-muted: #8b98a8;
+ --color-faint: #5b6879;
+ --color-border: #1f2630;
+ --color-border-strong: #2b3544;
+ --color-accent: #3fb98a;
+ --color-accent-hover: #55c99d;
+ --color-accent-fg: #0a0d12;
+ --color-code-bg: #0d1219;
+ --color-code-border: #1f2630;
+ --color-scrollbar-track: #0a0d12;
+ --color-scrollbar-thumb: #2b3544;
+}
+
+.theme-light {
+ --color-bg: #ffffff;
+ --color-surface: #f8fafc;
+ --color-fg: #0f172a;
+ --color-muted: #334155;
+ --color-faint: #64748b;
+ --color-border: #e2e8f0;
+ --color-border-strong: #cbd5e1;
+ --color-code-bg: #f8fafc;
+ --color-code-border: #f1f5f9;
+ background-color: var(--color-bg);
+ color: #1e293b;
+ font-family: var(--font-newsreader), Georgia, serif;
+}
+
/* Base styles - Tailwind only */
@layer base {
html {
@@ -9,7 +43,7 @@
}
body {
- @apply bg-white text-slate-800 font-serif antialiased selection:bg-slate-900 selection:text-white;
+ @apply bg-bg text-fg font-sans antialiased;
line-height: 1.6;
}
@@ -20,7 +54,7 @@
h4,
h5,
h6 {
- @apply font-sans font-bold text-slate-900 tracking-tighter;
+ @apply font-sans font-bold text-fg tracking-tighter;
}
h1 {
@@ -40,7 +74,7 @@
}
p {
- @apply mb-6 text-base leading-relaxed text-slate-700;
+ @apply mb-6 text-base leading-relaxed text-muted;
}
.lead {
@@ -49,7 +83,7 @@
}
a {
- @apply text-slate-900 hover:text-slate-700 transition-colors no-underline;
+ @apply text-fg hover:text-muted transition-colors no-underline;
}
ul,
@@ -62,22 +96,34 @@
}
code:not([class*="language-"]) {
- @apply bg-slate-50 px-1.5 py-0.5 rounded-md font-mono text-[0.9em] text-slate-800 border border-slate-100;
+ @apply px-1.5 py-0.5 rounded-md font-mono text-[0.9em] text-fg border;
+ background-color: var(--color-code-bg);
+ border-color: var(--color-code-border);
}
blockquote {
- @apply border-l-4 border-slate-900 pl-6 italic text-slate-700 my-8 text-lg md:text-2xl font-serif;
+ @apply border-l-4 border-fg pl-6 italic text-muted my-8 text-lg md:text-2xl font-serif;
}
/* Focus states */
- a:focus,
- button:focus,
input:focus,
textarea:focus {
outline: none !important;
box-shadow: none !important;
}
+ /* Keep mouse focus quiet, but never hide the keyboard focus ring */
+ a:focus:not(:focus-visible),
+ button:focus:not(:focus-visible) {
+ outline: none;
+ }
+
+ a:focus-visible,
+ button:focus-visible {
+ outline: 2px solid var(--color-accent);
+ outline-offset: 2px;
+ }
+
/* Interactive elements pointer */
a,
button,
@@ -328,11 +374,11 @@
}
::-webkit-scrollbar-track {
- background: #f1f5f9;
+ background: var(--color-scrollbar-track);
}
::-webkit-scrollbar-thumb {
- background: #cbd5e1;
+ background: var(--color-scrollbar-thumb);
border-radius: 4px;
transition: background 0.2s ease;
}
@@ -343,8 +389,8 @@
/* Selection color */
::selection {
- background: #0f172a;
- color: #ffffff;
+ background: var(--color-accent);
+ color: var(--color-accent-fg);
}
/* Tag Styles */
diff --git a/apps/web/app/(site)/layout.tsx b/apps/web/app/(site)/layout.tsx
index ac56257..3d82c4f 100644
--- a/apps/web/app/(site)/layout.tsx
+++ b/apps/web/app/(site)/layout.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { Inter, Newsreader } from "next/font/google";
+import { Inter, JetBrains_Mono, Newsreader } from "next/font/google";
import { Analytics } from "@/src/components/Analytics";
import { Footer } from "@/src/components/Footer";
import { Header } from "@/src/components/Header";
@@ -7,6 +7,11 @@ import { InteractiveElements } from "@/src/components/InteractiveElements";
import "./globals.css";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
+const jetbrainsMono = JetBrains_Mono({
+ subsets: ["latin"],
+ variable: "--font-jetbrains-mono",
+ display: "swap",
+});
const newsreader = Newsreader({
subsets: ["latin"],
variable: "--font-newsreader",
@@ -14,14 +19,25 @@ const newsreader = Newsreader({
display: "swap",
});
+const DESCRIPTION =
+ "Senior engineer for teams that build fast with AI and don't want chaos. Strong in design and frontend, provably clean in architecture and tests. Remote, booked in weekly capacity blocks.";
+
export const metadata: Metadata = {
title: {
- default: "Marc Mintel — Professionelle Websites für Unternehmen",
+ default: "Marc Mintel — Senior engineer for AI-built software",
template: "%s | Marc Mintel",
},
- description:
- "Professionelle Websites zum Fixpreis. Ein Entwickler, ein Ansprechpartner — von der Idee bis zum fertigen Ergebnis. Inklusive Sorglos-Paket: Änderungen, Wartung und Sicherheit komplett abgedeckt.",
+ description: DESCRIPTION,
metadataBase: new URL("https://mintel.me"),
+ openGraph: {
+ type: "website",
+ siteName: "Marc Mintel",
+ title: "Marc Mintel — Senior engineer for AI-built software",
+ description: DESCRIPTION,
+ url: "https://mintel.me",
+ locale: "en_US",
+ },
+ twitter: { card: "summary_large_image" },
icons: {
icon: "/favicon.svg",
},
@@ -33,8 +49,11 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
-
-
+
+
{children}
diff --git a/apps/web/app/(site)/legal/layout.tsx b/apps/web/app/(site)/legal/layout.tsx
new file mode 100644
index 0000000..1191fef
--- /dev/null
+++ b/apps/web/app/(site)/legal/layout.tsx
@@ -0,0 +1,9 @@
+import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
+
+export default function LegacyLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/apps/web/app/(site)/notes/page.tsx b/apps/web/app/(site)/notes/page.tsx
new file mode 100644
index 0000000..46afd26
--- /dev/null
+++ b/apps/web/app/(site)/notes/page.tsx
@@ -0,0 +1,18 @@
+import type { Metadata } from "next";
+import { Container } from "@/src/components/site/Container";
+import { SectionHeader } from "@/src/components/site/SectionHeader";
+
+export const metadata: Metadata = { title: "Notes" };
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/app/(site)/process/page.tsx b/apps/web/app/(site)/process/page.tsx
new file mode 100644
index 0000000..f9e74be
--- /dev/null
+++ b/apps/web/app/(site)/process/page.tsx
@@ -0,0 +1,18 @@
+import type { Metadata } from "next";
+import { Container } from "@/src/components/site/Container";
+import { SectionHeader } from "@/src/components/site/SectionHeader";
+
+export const metadata: Metadata = { title: "Process" };
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/app/(site)/tags/layout.tsx b/apps/web/app/(site)/tags/layout.tsx
new file mode 100644
index 0000000..1191fef
--- /dev/null
+++ b/apps/web/app/(site)/tags/layout.tsx
@@ -0,0 +1,9 @@
+import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
+
+export default function LegacyLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/apps/web/app/(site)/technologies/layout.tsx b/apps/web/app/(site)/technologies/layout.tsx
new file mode 100644
index 0000000..1191fef
--- /dev/null
+++ b/apps/web/app/(site)/technologies/layout.tsx
@@ -0,0 +1,9 @@
+import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
+
+export default function LegacyLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/apps/web/app/(site)/websites/layout.tsx b/apps/web/app/(site)/websites/layout.tsx
new file mode 100644
index 0000000..1191fef
--- /dev/null
+++ b/apps/web/app/(site)/websites/layout.tsx
@@ -0,0 +1,9 @@
+import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
+
+export default function LegacyLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/apps/web/src/components/Footer.tsx b/apps/web/src/components/Footer.tsx
index 976e54b..ee84ae9 100644
--- a/apps/web/src/components/Footer.tsx
+++ b/apps/web/src/components/Footer.tsx
@@ -1,66 +1,71 @@
-import Image from "next/image";
+import Link from "next/link";
import * as React from "react";
-import LogoBlack from "../assets/logo/Logo-Black-Transparent.svg";
+import { AVAILABILITY_CONFIG } from "../content/site";
+import {
+ formatAvailability,
+ resolveAvailability,
+} from "../domain/availability";
+
+const NAV_LINKS = [
+ { href: "/notes", label: "Notes" },
+ { href: "/process", label: "Process" },
+ { href: "/about", label: "About" },
+ { href: "/book", label: "Book a call" },
+] as const;
+
+const LEGAL_LINKS = [
+ { href: "/legal/impressum", label: "Imprint" },
+ { href: "/legal/datenschutz", label: "Privacy" },
+ { href: "/legal/agb", label: "Terms" },
+] as const;
+
+const LINK_CLASS = "text-muted transition-colors hover:text-fg";
export const Footer: React.FC = () => {
- const currentYear = new Date().getFullYear();
+ const now = new Date();
+ const today = now.toISOString().slice(0, 10);
+ const availability = formatAvailability(
+ resolveAvailability(AVAILABILITY_CONFIG, today),
+ );
return (
-