design
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface BlogPostClientProps {
|
||||
@@ -88,7 +89,7 @@ export const BlogPostClient: React.FC<BlogPostClientProps> = ({ readingTime, tit
|
||||
<div className="max-w-4xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className="flex items-center gap-2 text-slate-700 hover:text-blue-600 transition-colors duration-200 group"
|
||||
className="flex items-center gap-2 text-slate-700 hover:text-slate-900 transition-colors duration-200 group"
|
||||
aria-label="Back to home"
|
||||
>
|
||||
<svg className="w-5 h-5 group-hover:-translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -103,10 +104,10 @@ export const BlogPostClient: React.FC<BlogPostClientProps> = ({ readingTime, tit
|
||||
</span>
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="share-button-top flex items-center gap-1.5 px-3 py-1.5 bg-slate-100 hover:bg-blue-50 border border-slate-200 hover:border-blue-300 rounded-full transition-all duration-200"
|
||||
className="share-button-top flex items-center gap-1.5 px-3 py-1.5 bg-slate-100 hover:bg-slate-200 border border-slate-200 hover:border-slate-300 rounded-full transition-all duration-200"
|
||||
aria-label="Share this post"
|
||||
>
|
||||
<svg className="w-4 h-4 text-slate-600 group-hover:text-blue-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-4 h-4 text-slate-600 group-hover:text-slate-900 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"/>
|
||||
</svg>
|
||||
</button>
|
||||
@@ -117,7 +118,7 @@ export const BlogPostClient: React.FC<BlogPostClientProps> = ({ readingTime, tit
|
||||
<div className="mt-16 pt-8 border-t border-slate-200 text-center">
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className="inline-flex items-center gap-2 px-6 py-3 bg-white border-2 border-slate-200 text-slate-700 rounded-full hover:border-blue-400 hover:text-blue-600 hover:shadow-md transition-all duration-300 font-medium group"
|
||||
className="inline-flex items-center gap-2 px-6 py-3 bg-white border-2 border-slate-200 text-slate-700 rounded-full hover:border-slate-900 hover:text-slate-900 hover:shadow-md transition-all duration-300 font-medium group"
|
||||
>
|
||||
<svg className="w-5 h-5 group-hover:-translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
|
||||
40
src/components/CTA.tsx
Normal file
40
src/components/CTA.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { Reveal } from './Reveal';
|
||||
|
||||
export const CTA: React.FC = () => {
|
||||
return (
|
||||
<section className="container relative py-32 md:py-48">
|
||||
<Reveal width="100%">
|
||||
<div className="relative p-12 md:p-24 border-2 border-slate-900 bg-white">
|
||||
<div className="absolute top-0 left-12 w-px h-24 bg-slate-900 -translate-y-12"></div>
|
||||
<div className="absolute -right-12 -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10">?</div>
|
||||
|
||||
<div className="space-y-16 relative z-10">
|
||||
<h2 className="text-6xl md:text-8xl font-bold tracking-tighter leading-[0.8] text-slate-900">
|
||||
Interesse? <br />
|
||||
<span className="text-slate-200">Fragen kostet nichts.</span>
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-end">
|
||||
<p className="text-2xl md:text-3xl text-slate-400 font-serif italic leading-tight">
|
||||
Schreiben Sie mir einfach, was Sie brauchen. Ich sage Ihnen ehrlich, ob ich es mache.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col items-start gap-8">
|
||||
<Link
|
||||
href="/contact"
|
||||
className="group inline-flex items-center gap-4 bg-white border border-slate-200 rounded-full px-10 py-5 text-2xl font-bold text-slate-900 hover:border-slate-900 hover:shadow-xl hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
Projekt anfragen
|
||||
<ArrowRight className="w-6 h-6 group-hover:translate-x-2 transition-transform" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,14 @@
|
||||
import React from 'react';
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
const pathname = usePathname();
|
||||
|
||||
const isActive = (path: string) => pathname === path;
|
||||
|
||||
return (
|
||||
<header className="bg-white/80 backdrop-blur-md sticky top-0 z-50">
|
||||
<div className="max-w-4xl mx-auto px-6 py-8 flex items-center justify-between">
|
||||
@@ -13,18 +20,28 @@ export const Header: React.FC = () => {
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-8">
|
||||
<Link href="/websites" className="text-xs font-bold uppercase tracking-widest text-slate-400 hover:text-slate-900 transition-colors">
|
||||
<Link
|
||||
href="/websites"
|
||||
className={`text-xs font-bold uppercase tracking-widest transition-colors ${
|
||||
isActive('/websites') ? 'text-slate-900' : 'text-slate-400 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
Websites
|
||||
</Link>
|
||||
<Link href="/blog" className="text-xs font-bold uppercase tracking-widest text-slate-400 hover:text-slate-900 transition-colors">
|
||||
<Link
|
||||
href="/blog"
|
||||
className={`text-xs font-bold uppercase tracking-widest transition-colors ${
|
||||
isActive('/blog') || pathname?.startsWith('/blog/') ? 'text-slate-900' : 'text-slate-400 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
Blog
|
||||
</Link>
|
||||
<a
|
||||
href="mailto:marc@mintel.me"
|
||||
className="text-xs font-bold uppercase tracking-widest text-slate-900 hover:text-slate-600 transition-colors"
|
||||
<Link
|
||||
href="/contact"
|
||||
className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-900 bg-white border border-slate-200 rounded-full px-5 py-2.5 hover:border-slate-900 hover:shadow-sm transition-all duration-300"
|
||||
>
|
||||
Kontakt
|
||||
</a>
|
||||
Anfrage
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BookOpen, Code2, Terminal, Wrench } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
export const Hero: React.FC = () => {
|
||||
return (
|
||||
@@ -26,19 +26,19 @@ export const Hero: React.FC = () => {
|
||||
{/* Quick stats or focus areas */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-10 animate-fade-in">
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||
<Code2 className="mx-auto mb-2 text-primary-400" size={24} />
|
||||
<Code2 className="mx-auto mb-2 text-slate-400" size={24} />
|
||||
<div className="text-sm font-mono text-slate-300">Code</div>
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||
<Wrench className="mx-auto mb-2 text-primary-400" size={24} />
|
||||
<Wrench className="mx-auto mb-2 text-slate-400" size={24} />
|
||||
<div className="text-sm font-mono text-slate-300">Tools</div>
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||
<Terminal className="mx-auto mb-2 text-primary-400" size={24} />
|
||||
<Terminal className="mx-auto mb-2 text-slate-400" size={24} />
|
||||
<div className="text-sm font-mono text-slate-300">Automation</div>
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||
<BookOpen className="mx-auto mb-2 text-primary-400" size={24} />
|
||||
<BookOpen className="mx-auto mb-2 text-slate-400" size={24} />
|
||||
<div className="text-sm font-mono text-slate-300">Learning</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
44
src/components/Reveal.tsx
Normal file
44
src/components/Reveal.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { motion, useInView, useAnimation, Variant } from 'framer-motion';
|
||||
|
||||
interface RevealProps {
|
||||
children: React.ReactNode;
|
||||
width?: 'fit-content' | '100%';
|
||||
delay?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Reveal: React.FC<RevealProps> = ({
|
||||
children,
|
||||
width = 'fit-content',
|
||||
delay = 0.25,
|
||||
className = ""
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true });
|
||||
const mainControls = useAnimation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isInView) {
|
||||
mainControls.start("visible");
|
||||
}
|
||||
}, [isInView, mainControls]);
|
||||
|
||||
return (
|
||||
<div ref={ref} style={{ position: "relative", width, overflow: "hidden" }} className={className}>
|
||||
<motion.div
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: 75 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
}}
|
||||
initial="hidden"
|
||||
animate={mainControls}
|
||||
transition={{ duration: 0.5, delay: delay, type: "spring", stiffness: 100, damping: 20 }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
interface TwitterEmbedProps {
|
||||
tweetId: string;
|
||||
@@ -41,7 +41,7 @@ export async function TwitterEmbed({
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z"/>
|
||||
</svg>
|
||||
<span>Unable to load tweet</span>
|
||||
<a href={`https://twitter.com/i/status/${tweetId}`} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:text-blue-700 font-medium text-sm">
|
||||
<a href={`https://twitter.com/i/status/${tweetId}`} target="_blank" rel="noopener noreferrer" className="text-slate-600 hover:text-slate-900 font-medium text-sm">
|
||||
View on Twitter →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user