+
+ Ich stehe für
+ meine Arbeit gerade.
+
-
-
-
-
- Keine Hierarchien, keine Ausreden. Wenn etwas nicht passt,
- liegt die Verantwortung bei mir – und ich{" "}
-
- löse es.
+
+
+ Keine Hierarchien. Keine Ausreden. Wenn etwas nicht passt,
+ liegt die Verantwortung bei mir.
+
+
+ Ich liefere nicht nur Code, sondern{" "}
+
+ Ergebnisse
+
+
+
-
-
-
- {[
- "Vollständige Transparenz",
- "Ein Ansprechpartner",
- "Messbare Qualität",
- "Langfristige Partnerschaft",
- ].map((item, i) => (
-
-
-
- ))}
+ , auf die Sie bauen können.
+
+
+
+
+
+
+ Fixpreis-Garantie
+
+
+ Keine versteckten Kosten. Der vereinbarte Preis ist final.
+
+
+
+
+ Satisfaction Guarantee
+
+
+ Wir gehen erst live, wenn Sie zu 100% zufrieden sind.
+
+
+
+
+
-
- {/* Decorative terminal */}
-
-
-
-
+
@@ -306,9 +320,7 @@ export default function AboutPage() {
Lassen Sie uns gemeinsam etwas bauen, das{" "}
-
- wirklich funktioniert.
-
+ wirklich funktioniert.
diff --git a/apps/web/app/case-studies/klz-cables/page.tsx b/apps/web/app/case-studies/klz-cables/page.tsx
index 7f9584b..e1af421 100644
--- a/apps/web/app/case-studies/klz-cables/page.tsx
+++ b/apps/web/app/case-studies/klz-cables/page.tsx
@@ -98,7 +98,7 @@ export default function KLZCablesCaseStudy() {
Engineering eines
- B2B Commerce Systems.
+ Systems.
Vom statischen Altsystem zum industriellen Standard. Ich
@@ -172,8 +172,8 @@ export default function KLZCablesCaseStudy() {
Attribute in einer zentralen relationalen Instanz. Durch die
Implementierung nativer PHP-Services und den Verzicht auf
volatile Drittanbieter-Plugins wurde ein System geschaffen,
- das keine technologischen Überraschungen zulässt.{" "}
- Stability by Design.
+ das keine technologischen Überraschungen zulässt. Stability by{" "}
+ Design.
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index 1f178d7..8f5977f 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -25,6 +25,7 @@ import { IconList, IconListItem } from "../src/components/IconList";
import { HeroSection } from "../src/components/HeroSection";
import { GlitchText } from "../src/components/GlitchText";
import { Marker } from "../src/components/Marker";
+import { PenCircle } from "../src/components/PenCircle";
export default function LandingPage() {
return (
@@ -45,12 +46,7 @@ export default function LandingPage() {
Kein Agentur-Zirkus.
-
- Nur{" "}
-
- Ergebnisse.
-
-
+ Ergebnisse.
@@ -104,12 +100,7 @@ export default function LandingPage() {
Ich arbeite für das Ergebnis,
-
- nicht gegen die{" "}
-
- Uhr.
-
-
+ nicht gegen die Uhr.
@@ -118,14 +109,24 @@ export default function LandingPage() {
negativeLabel="Klassisch"
negativeText="Wochen in Planung, bevor eine einzige Zeile Code geschrieben wird."
positiveLabel="Mein Weg"
- positiveText="Schnelle Prototypen. Ergebnisse in Tagen, nicht Monaten."
+ positiveText={
+ <>
+ Schnelle Prototypen. Ergebnisse in{" "}
+
Tagen , nicht Monaten.
+ >
+ }
delay={0.1}
/>
+ Fixpreise. Sie wissen von
+ Anfang an, was es kostet.
+ >
+ }
reverse
delay={0.2}
/>
@@ -290,7 +291,7 @@ export default function LandingPage() {
Beschreiben Sie kurz Ihr Vorhaben. Ich melde mich{" "}
- zeitnah
+ zeitnah
{" "}
bei Ihnen.
diff --git a/apps/web/app/websites/page.tsx b/apps/web/app/websites/page.tsx
index 29d2fc0..2d9cbe0 100644
--- a/apps/web/app/websites/page.tsx
+++ b/apps/web/app/websites/page.tsx
@@ -42,11 +42,9 @@ export default function WebsitesPage() {
SYSTEM ENGINEERING
- Websites, die
+ Websites, die einfach
-
- einfach funktionieren.
-
+ funktionieren.
diff --git a/apps/web/src/components/Effects/ArchitectureVisualizer.tsx b/apps/web/src/components/Effects/ArchitectureVisualizer.tsx
index 306c4eb..741960d 100644
--- a/apps/web/src/components/Effects/ArchitectureVisualizer.tsx
+++ b/apps/web/src/components/Effects/ArchitectureVisualizer.tsx
@@ -59,19 +59,24 @@ const Node: React.FC<{
);
const Connector: React.FC<{ active?: boolean }> = ({ active }) => (
-
+
{active && (
)}
diff --git a/apps/web/src/components/Landing/ComparisonRow.tsx b/apps/web/src/components/Landing/ComparisonRow.tsx
index 131bbfc..80dcfed 100644
--- a/apps/web/src/components/Landing/ComparisonRow.tsx
+++ b/apps/web/src/components/Landing/ComparisonRow.tsx
@@ -2,6 +2,7 @@ import * as React from "react";
import { ArrowRight } from "lucide-react";
import { Reveal } from "../Reveal";
import { Label, H3, LeadText } from "../Typography";
+import { Strikethrough } from "../Strikethrough";
import { cn } from "../../utils/cn";
interface ComparisonRowProps {
@@ -38,11 +39,11 @@ export const ComparisonRow: React.FC
= ({
)}
>
-
- {negativeLabel}
+
+ {negativeLabel}
-
- {negativeText}
+
+ {negativeText}
diff --git a/apps/web/src/components/Marker.tsx b/apps/web/src/components/Marker.tsx
index e2742d3..e9270db 100644
--- a/apps/web/src/components/Marker.tsx
+++ b/apps/web/src/components/Marker.tsx
@@ -1,14 +1,9 @@
"use client";
-import React from "react";
+import React, { useId, useMemo } from "react";
import { motion } from "framer-motion";
import { cn } from "../utils/cn";
-/**
- * TECHNICAL MARKER COMPONENT
- * Implements the "hand-drawn marker" effect.
- * Animates in when entering the viewport.
- */
interface MarkerProps {
children: React.ReactNode;
delay?: number;
@@ -16,74 +11,171 @@ interface MarkerProps {
color?: string;
}
+/**
+ * Copic Marker Component
+ *
+ * Uses hand-drawn filled polygon paths (NOT rects or strokes) for an
+ * organic, human feel. Each highlight band is a wobbly shape with
+ * bezier curves, not a perfect rectangle. Second strokes are offset
+ * to simulate imperfect re-highlighting.
+ */
+
+// Seeded PRNG for SSR-safe randomness
+function createRng(seed: string) {
+ let s = 0;
+ for (let i = 0; i < seed.length; i++) {
+ s = ((s << 5) - s + seed.charCodeAt(i)) | 0;
+ }
+ return () => {
+ s = (s * 16807 + 0) % 2147483647;
+ return (s & 0x7fffffff) / 0x7fffffff;
+ };
+}
+
+/**
+ * Generate a hand-drawn highlight band path.
+ * Instead of a rectangle, we trace an organic polygon with wobbly edges.
+ *
+ * The shape goes: left edge → top edge (left to right) → right edge → bottom edge (right to left)
+ * Each edge has 1-2 control points for subtle curves.
+ *
+ * ViewBox: 0 0 100 100 with preserveAspectRatio="none"
+ * So x=0..100 maps to text width, y=0..100 maps to text height.
+ */
+function generateBandPath(rng: () => number, bandIndex: number) {
+ // Vertical position: center band in the text height
+ // First band centered around y=50, second shifted up or down
+ const yOffset = bandIndex === 0 ? 0 : (rng() - 0.5) * 20; // -10 to +10 shift
+ const yCenter = 50 + yOffset;
+
+ // Band thickness: covers ~40-50% of text height
+ const halfHeight = 20 + rng() * 8; // 20-28 units (40-56% of viewBox)
+ const yTop = yCenter - halfHeight;
+ const yBottom = yCenter + halfHeight;
+
+ // Horizontal extent: slight random overshoot/undershoot
+ const xStart = -2 + rng() * 3; // -2 to 1
+ const xEnd = 99 + rng() * 3; // 99 to 102
+
+ // Generate wobbly top edge points (left to right)
+ const topWobble1 = yTop + (rng() - 0.5) * 6;
+ const topWobble2 = yTop + (rng() - 0.5) * 6;
+ const topWobble3 = yTop + (rng() - 0.5) * 6;
+
+ // Generate wobbly bottom edge points (right to left)
+ const botWobble1 = yBottom + (rng() - 0.5) * 6;
+ const botWobble2 = yBottom + (rng() - 0.5) * 6;
+ const botWobble3 = yBottom + (rng() - 0.5) * 6;
+
+ // Slight angle on left/right edges
+ const leftTopY = yTop + (rng() - 0.5) * 4;
+ const leftBotY = yBottom + (rng() - 0.5) * 4;
+ const rightTopY = yTop + (rng() - 0.5) * 4;
+ const rightBotY = yBottom + (rng() - 0.5) * 4;
+
+ // Build the path:
+ // Start at top-left, trace top edge with curves, down right edge,
+ // trace bottom edge with curves back left, close.
+ const path = [
+ // Start top-left
+ `M ${xStart},${leftTopY}`,
+ // Top edge: 3 curve segments left→right
+ `C ${xStart + 15},${topWobble1} ${xStart + 30},${topWobble1} ${33},${topWobble2}`,
+ `C ${45},${topWobble2} ${55},${topWobble3} ${67},${topWobble3}`,
+ `C ${78},${topWobble3} ${xEnd - 10},${rightTopY} ${xEnd},${rightTopY}`,
+ // Right edge down
+ `L ${xEnd + (rng() - 0.5) * 2},${rightBotY}`,
+ // Bottom edge: 3 curve segments right→left
+ `C ${xEnd - 10},${botWobble1} ${78},${botWobble1} ${67},${botWobble2}`,
+ `C ${55},${botWobble2} ${45},${botWobble3} ${33},${botWobble3}`,
+ `C ${xStart + 30},${botWobble3} ${xStart + 15},${leftBotY} ${xStart},${leftBotY}`,
+ // Close
+ `Z`,
+ ].join(" ");
+
+ return path;
+}
+
export const Marker: React.FC = ({
children,
delay = 0,
className = "",
- color = "rgba(255,235,59,0.7)",
+ color = "rgba(250, 204, 21, 0.5)",
}) => {
+ const id = useId();
+ const filterId = `marker-rough-${id.replace(/:/g, "")}`;
+
+ const bands = useMemo(() => {
+ const rng = createRng(id);
+ const numBands = rng() > 0.5 ? 2 : 1;
+ const result = [];
+
+ for (let i = 0; i < numBands; i++) {
+ result.push({
+ d: generateBandPath(rng, i),
+ delay: delay + i * 0.15,
+ duration: 0.4 + rng() * 0.15,
+ });
+ }
+ return result;
+ }, [id, delay]);
+
return (
- {/* Organic Stroke 1: Main body */}
-
- {/* Organic Stroke 2: Variation for overlap */}
-
- {/* Organic Stroke 3: Rough edge details */}
-
+
+
+ {/* Very subtle edge wobble — alcohol ink bleed, not brush fraying */}
+
+
+
+
+
+ {bands.map((band, i) => (
+
+ ))}
- {children}
+
+ {children}
+
);
};
diff --git a/apps/web/src/components/PenCircle.tsx b/apps/web/src/components/PenCircle.tsx
new file mode 100644
index 0000000..226dd3e
--- /dev/null
+++ b/apps/web/src/components/PenCircle.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import React, { useId, useMemo } from "react";
+import { motion } from "framer-motion";
+import { cn } from "../utils/cn";
+
+interface PenCircleProps {
+ children: React.ReactNode;
+ delay?: number;
+ className?: string;
+ color?: string;
+}
+
+/**
+ * Ballpoint Pen Circle Component
+ *
+ * Draws a hand-drawn ellipse around children using SVG.
+ * Key: uses preserveAspectRatio="none" so the ellipse stretches
+ * to match the element dimensions regardless of text length.
+ */
+
+function createRng(seed: string) {
+ let s = 0;
+ for (let i = 0; i < seed.length; i++) {
+ s = ((s << 5) - s + seed.charCodeAt(i)) | 0;
+ }
+ return () => {
+ s = (s * 16807 + 0) % 2147483647;
+ return (s & 0x7fffffff) / 0x7fffffff;
+ };
+}
+
+function generateCirclePath(rng: () => number) {
+ // We draw an ellipse in a 0-100 x 0-100 viewBox.
+ // preserveAspectRatio="none" will stretch it to fit the element.
+ const cx = 50;
+ const cy = 50;
+
+ // Radii in viewBox units — will be stretched by the element
+ const rx = 50;
+ const ry = 50;
+
+ // Wobble for organic feel
+ const w = () => (rng() - 0.5) * 6;
+
+ const k = 0.5522847;
+ const kx = rx * k;
+ const ky = ry * k;
+
+ // 4 cardinal points with wobble
+ const top = { x: cx + w(), y: cy - ry + w() * 0.5 };
+ const right = { x: cx + rx + w() * 0.3, y: cy + w() };
+ const bottom = { x: cx + w(), y: cy + ry + w() * 0.5 };
+ const left = { x: cx - rx + w() * 0.3, y: cy + w() };
+
+ // End slightly offset from start for imperfect closure
+ const endOffX = (rng() - 0.5) * 8;
+ const endOffY = (rng() - 0.5) * 4;
+
+ return [
+ `M ${top.x},${top.y}`,
+ `C ${top.x + kx + w()},${top.y + w()} ${right.x + w()},${right.y - ky + w()} ${right.x},${right.y}`,
+ `C ${right.x + w()},${right.y + ky + w()} ${bottom.x + kx + w()},${bottom.y + w()} ${bottom.x},${bottom.y}`,
+ `C ${bottom.x - kx + w()},${bottom.y + w()} ${left.x + w()},${left.y + ky + w()} ${left.x},${left.y}`,
+ `C ${left.x + w()},${left.y - ky + w()} ${top.x - kx + w()},${top.y + w()} ${top.x + endOffX},${top.y + endOffY}`,
+ ].join(" ");
+}
+
+export const PenCircle: React.FC = ({
+ children,
+ delay = 0,
+ className = "",
+ color = "rgba(37, 99, 235, 0.65)", // Blue ballpoint pen
+}) => {
+ const id = useId();
+
+ const path = useMemo(() => {
+ const rng = createRng(id);
+ return generateCirclePath(rng);
+ }, [id]);
+
+ return (
+
+
+
+
+ {children}
+
+ );
+};
diff --git a/apps/web/src/components/Signature.tsx b/apps/web/src/components/Signature.tsx
new file mode 100644
index 0000000..240f176
--- /dev/null
+++ b/apps/web/src/components/Signature.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+import React from "react";
+import { motion } from "framer-motion";
+import { cn } from "../utils/cn";
+
+interface SignatureProps {
+ className?: string;
+ delay?: number;
+}
+
+export const Signature: React.FC = ({
+ className,
+ delay = 0,
+}) => {
+ return (
+
+
+ {/* M */}
+
+ {/* a r c (scribble) */}
+
+ {/* Mintel (scribble + underline) */}
+
+
+
+ Marc Mintel
+
+
+ );
+};
diff --git a/apps/web/src/components/Strikethrough.tsx b/apps/web/src/components/Strikethrough.tsx
new file mode 100644
index 0000000..56e9c82
--- /dev/null
+++ b/apps/web/src/components/Strikethrough.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import React, { useId, useMemo } from "react";
+import { motion } from "framer-motion";
+import { cn } from "../utils/cn";
+
+interface StrikethroughProps {
+ children: string; // Enforce string children for splitting
+ delay?: number;
+ className?: string;
+ color?: string;
+}
+
+/**
+ * Hand-drawn Strikethrough Component
+ *
+ * NOW SUPPORTS MULTI-LINE TEXT!
+ *
+ * Strategy: Splits the text into individual words. Each word is wrapped
+ * in a span with its own SVG strikethrough. This allows the text to
+ * wrap naturally across lines, and each word carries its strikethrough
+ * with it.
+ */
+
+function createRng(seed: string) {
+ let s = 0;
+ for (let i = 0; i < seed.length; i++) {
+ s = ((s << 5) - s + seed.charCodeAt(i)) | 0;
+ }
+ return () => {
+ s = (s * 16807 + 0) % 2147483647;
+ return (s & 0x7fffffff) / 0x7fffffff;
+ };
+}
+
+function generateStrikePath(rng: () => number) {
+ // Simple approach: just a slightly wobbly line from left to right
+ // through vertical center (y=50).
+ const points: { x: number; y: number }[] = [];
+ const numPoints = 4; // Fewer points for shorter word segments
+
+ for (let i = 0; i <= numPoints; i++) {
+ const x = (i / numPoints) * 100;
+ const y = 55 + (rng() - 0.5) * 15; // ±7.5 units wobble
+ points.push({ x, y });
+ }
+
+ // Build a smooth path through all points
+ let d = `M ${points[0].x},${points[0].y}`;
+ for (let i = 1; i < points.length - 1; i++) {
+ const midX = (points[i].x + points[i + 1].x) / 2;
+ const midY = (points[i].y + points[i + 1].y) / 2;
+ d += ` Q ${points[i].x},${points[i].y} ${midX},${midY}`;
+ }
+ const last = points[points.length - 1];
+ d += ` L ${last.x},${last.y}`;
+
+ return d;
+}
+
+const WordStrike: React.FC<{
+ word: string;
+ index: number;
+ baseDelay: number;
+ color: string;
+ seed: string;
+}> = ({ word, index, baseDelay, color, seed }) => {
+ // Unique seed per word so they look different
+ const uniqueSeed = `${seed}-${index}-${word}`;
+
+ const path = useMemo(() => {
+ const rng = createRng(uniqueSeed);
+ return generateStrikePath(rng);
+ }, [uniqueSeed]);
+
+ return (
+
+
+
+
+ {word}
+
+ );
+};
+
+export const Strikethrough: React.FC = ({
+ children,
+ delay = 0,
+ className = "",
+ color = "rgba(220, 50, 50, 0.8)",
+}) => {
+ const id = useId();
+
+ // Split by spaces but preserve them as separate elements for spacing
+ // Actually, we can just split by space and rejoin with non-struck spaces
+ const words = children.split(" ");
+
+ return (
+
+ {words.map((word, i) => (
+
+
+ {i < words.length - 1 && " "}
+
+ ))}
+
+ );
+};