feat: rebuild mintel.me as a single-page freelance site

The site now presents one freelance developer on one page: hero, introduction,
four typical cases (developer for a product, make an app reliable, automate a
process, a website) with what you get, how it runs and what it costs, a
process automation section, technology and project fit, how I work, two live
references, questions and contact.

- Hero and closing headline are drawn from running binary digits.
- Each case explains its four steps with an animated drawing; the automation
  section shows example workflows with work travelling between systems; a
  pipeline run shows what a test catches before a release.
- Contact is one email address, kept in parts and assembled in the browser so
  it never appears in the page source (src/domain/email.ts, spec first).
- Imprint and privacy pages added.
- The old site (blog, case studies, contact form, fixed-price pages, video
  rendering and related scripts) is removed. Its URLs redirect to the homepage.
- The deploy smoke test checks the legal pages and the redirects instead of
  the removed case study.
- Dev scripts use docker compose v2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 22:33:06 +02:00
co-authored by Claude Opus 5.5
parent 4eb663f42a
commit fa40fefe2f
781 changed files with 4925 additions and 55137 deletions
+28 -17
View File
@@ -1,6 +1,6 @@
'use client';
"use client";
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState } from "react";
export const InteractiveElements: React.FC = () => {
const [progress, setProgress] = useState(0);
@@ -9,8 +9,9 @@ export const InteractiveElements: React.FC = () => {
useEffect(() => {
const handleScroll = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const docHeight =
document.documentElement.scrollHeight - window.innerHeight;
if (docHeight > 0) {
setProgress((scrollTop / docHeight) * 100);
}
@@ -18,34 +19,44 @@ export const InteractiveElements: React.FC = () => {
setShowBackToTop(scrollTop > 300);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
window.scrollTo({ top: 0, behavior: "smooth" });
};
return (
<>
{/* Reading Progress Bar */}
<div
className="reading-progress-bar"
style={{
<div
className="reading-progress-bar"
style={{
transform: `scaleX(${progress / 100})`,
display: 'block'
}}
display: "block",
}}
/>
{/* Floating Back to Top Button */}
<button
<button
onClick={scrollToTop}
className={`floating-back-to-top ${showBackToTop ? 'visible' : ''}`}
className={`floating-back-to-top ${showBackToTop ? "visible" : ""}`}
aria-label="Back to top"
style={{ display: 'flex' }}
style={{ display: "flex" }}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 10l7-7m0 0l7 7m-7-7v18"
/>
</svg>
</button>
</>