feat: dark theme foundation, new header/footer, shared site primitives
Adds dark design tokens, Inter + JetBrains Mono, a header with Notes, Process, About and a Book a call button, and a footer with the availability line driven by the tested domain core. Shared primitives live in src/components/site, site constants in src/content/site.ts. Legacy pages (blog, legal, contact, tags, technologies) keep a light surface via LegacyLightSurface. Stub routes for /notes, /process and /book. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
|
||||
|
||||
export default function LegacyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <LegacyLightSurface>{children}</LegacyLightSurface>;
|
||||
}
|
||||
@@ -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 (
|
||||
<Container className="py-16 md:py-24">
|
||||
<SectionHeader
|
||||
as="h1"
|
||||
kicker="/book"
|
||||
title="Book a call"
|
||||
lead="This page is coming soon."
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
|
||||
|
||||
export default function LegacyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <LegacyLightSurface>{children}</LegacyLightSurface>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
|
||||
|
||||
export default function LegacyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <LegacyLightSurface>{children}</LegacyLightSurface>;
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 (
|
||||
<html lang="de" className={`${inter.variable} ${newsreader.variable}`}>
|
||||
<body className="min-h-screen bg-white">
|
||||
<html
|
||||
lang="en"
|
||||
className={`${inter.variable} ${jetbrainsMono.variable} ${newsreader.variable}`}
|
||||
>
|
||||
<body className="min-h-screen bg-bg font-sans text-fg antialiased">
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
|
||||
|
||||
export default function LegacyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <LegacyLightSurface>{children}</LegacyLightSurface>;
|
||||
}
|
||||
@@ -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 (
|
||||
<Container className="py-16 md:py-24">
|
||||
<SectionHeader
|
||||
as="h1"
|
||||
kicker="/notes"
|
||||
title="Notes"
|
||||
lead="This page is coming soon."
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Container className="py-16 md:py-24">
|
||||
<SectionHeader
|
||||
as="h1"
|
||||
kicker="/process"
|
||||
title="Process"
|
||||
lead="This page is coming soon."
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
|
||||
|
||||
export default function LegacyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <LegacyLightSurface>{children}</LegacyLightSurface>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
|
||||
|
||||
export default function LegacyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <LegacyLightSurface>{children}</LegacyLightSurface>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LegacyLightSurface } from "@/src/components/LegacyLightSurface";
|
||||
|
||||
export default function LegacyLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <LegacyLightSurface>{children}</LegacyLightSurface>;
|
||||
}
|
||||
Reference in New Issue
Block a user