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
+19 -54
View File
@@ -1,62 +1,27 @@
import {
parseAvailabilityConfig,
type AvailabilityConfig,
} from "@/src/domain/availability";
import { parseEmailParts, type EmailParts } from "@/src/domain/email";
export const SITE_NAME = "Marc Mintel";
export const SITE_URL = "https://mintel.me";
// TODO(owner): replace with the real Cal.com link before launch.
export const BOOKING_URL = "https://cal.com/PLACEHOLDER/intro";
// TODO(owner): replace with the real contact address before launch.
export const CONTACT_EMAIL = "hello@example.com";
// PLACEHOLDER VALUES: the owner must confirm the next free date and weekly hours.
const RAW_AVAILABILITY_CONFIG = {
freeFrom: "2026-11-01",
hoursPerWeek: 20,
} as const;
const parsedAvailability = parseAvailabilityConfig(RAW_AVAILABILITY_CONFIG);
if (parsedAvailability.ok === false) {
// The contact address, kept in parts on purpose: the complete address must never
// appear as one string in the page source (see src/components/EmailLink.tsx).
const parsedEmail = parseEmailParts({
user: "hello",
domain: "mintel",
tld: "me",
});
if (parsedEmail.ok === false) {
throw new Error(
`Invalid AVAILABILITY_CONFIG in src/content/site.ts: ${parsedAvailability.error}`,
`Invalid contact address in src/content/site.ts: ${parsedEmail.error}`,
);
}
export const AVAILABILITY_CONFIG: AvailabilityConfig = parsedAvailability.value;
export const CONTACT_EMAIL_PARTS: EmailParts = parsedEmail.value;
export type EngagementBlock = {
readonly name: string;
readonly hoursPerWeek: string;
readonly pricePerMonth: string;
};
export const ENGAGEMENT_BLOCKS: readonly EngagementBlock[] = [
{
name: "Focus",
hoursPerWeek: "15 h/week",
pricePerMonth: "6,000 EUR/month",
},
{
name: "Half-time",
hoursPerWeek: "20 h/week",
pricePerMonth: "8,000 EUR/month",
},
{
name: "Full-time",
hoursPerWeek: "35-40 h/week",
pricePerMonth: "14,000-16,000 EUR/month",
},
];
export const STACK: readonly string[] = [
"TypeScript",
"Next.js",
"React",
"Node.js",
"Rust",
"Solidity",
"PostgreSQL",
"Tailwind CSS",
];
/** Details required for the imprint (section 5 DDG). */
export const OWNER = {
name: "Marc Mintel",
street: "Georg-Meistermann-Straße 7",
city: "54586 Schüller",
country: "Germany",
vatId: "DE367588065",
} as const;