diff --git a/apps/web/app/(site)/globals.css b/apps/web/app/(site)/globals.css
index 4a3ff14..670000e 100644
--- a/apps/web/app/(site)/globals.css
+++ b/apps/web/app/(site)/globals.css
@@ -78,6 +78,28 @@
box-shadow: none !important;
}
+ /* Interactive elements pointer */
+ a,
+ button,
+ [role="button"],
+ input[type="button"],
+ input[type="submit"],
+ input[type="reset"],
+ select,
+ summary,
+ label[for] {
+ cursor: pointer;
+ }
+
+ /* Ensure disabled elements don't show pointer */
+ button:disabled,
+ [role="button"]:disabled,
+ input:disabled,
+ select:disabled,
+ button[disabled] {
+ cursor: not-allowed;
+ }
+
/* Remove default tap highlight on mobile */
* {
-webkit-tap-highlight-color: transparent !important;
diff --git a/apps/web/app/(site)/page.tsx b/apps/web/app/(site)/page.tsx
index 8ef5edc..563096c 100644
--- a/apps/web/app/(site)/page.tsx
+++ b/apps/web/app/(site)/page.tsx
@@ -26,6 +26,7 @@ import { HeroSection } from "@/src/components/HeroSection";
import { GlitchText } from "@/src/components/GlitchText";
import { Marker } from "@/src/components/Marker";
import { PenCircle } from "@/src/components/PenCircle";
+import { Availability } from "@/src/components/Availability";
export default function LandingPage() {
return (
@@ -313,7 +314,7 @@ export default function LandingPage() {
Aktuell nehme ich Projekte für{" "}
- Q2 2026{" "}
+ {" "}
an.
diff --git a/apps/web/src/components/Availability.tsx b/apps/web/src/components/Availability.tsx
new file mode 100644
index 0000000..fbf91d0
--- /dev/null
+++ b/apps/web/src/components/Availability.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+
+/**
+ * Automatisierte Anzeige der Projekt-Verfügbarkeit nach Quartalen.
+ * Berechnet das aktuelle Quartal basierend auf dem heutigen Datum.
+ */
+export const Availability: React.FC = () => {
+ const [text, setText] = useState("");
+
+ useEffect(() => {
+ const now = new Date();
+ const month = now.getMonth(); // 0-11
+ const year = now.getFullYear();
+ const quarter = Math.floor(month / 3) + 1;
+
+ // Aktuelles Quartal formatiert (z.B. Q2 2026)
+ setText(`Q${quarter} ${year}`);
+ }, []);
+
+ // Während Hydrierung nichts rendern, um Mismatches zu vermeiden (bei Client-only Logic)
+ if (!text) return Q- 202-;
+
+ return <>{text}>;
+};
diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts
index 7140e3a..0367ba0 100644
--- a/apps/web/src/middleware.ts
+++ b/apps/web/src/middleware.ts
@@ -11,19 +11,32 @@ export function middleware(request: NextRequest) {
// 2. Legacy Showcase Asset Mapping
- // Matches: /wp-content/*, /wp-includes/*, /assets/*, and their relative subpaths
- const showcaseAssetPattern = /^\/(?:wp-content|wp-includes|assets|(?:case-studies|work|blog)\/(?:wp-content|wp-includes|assets))\/(.*)/;
- const match = pathname.match(showcaseAssetPattern);
+ // Handles:
+ // - Global assets (/wp-content/*, /wp-includes/*, /assets/*)
+ // - Showcase-specific resources (/case-studies/klz-cables/index.html, etc.)
+
+ // Map Pattern 1: Global assets (directly under root)
+ if (pathname.match(/^\/(?:wp-content|wp-includes|assets)\//)) {
+ return NextResponse.rewrite(new URL(`/showcase/klz-cables.com${pathname}`, request.url));
+ }
- if (match) {
- const assetPath = match[0];
+ // Map Pattern 2: Showcase-prefixed assets
+ const showcaseMatch = pathname.match(/^\/(?:case-studies|work|blog)\/([^\/]+)\/(.*)/);
+ if (showcaseMatch) {
+ const [, slug, remainingPath] = showcaseMatch;
- // Normalize path by stripping the case-studies/work/blog prefix if present
- const normalizedPath = assetPath.replace(/^\/(?:case-studies|work|blog)/, '');
-
- // Map to the literal directory in public/
- // Destination: /showcase/klz-cables.com/...
- return NextResponse.rewrite(new URL(`/showcase/klz-cables.com${normalizedPath}`, request.url));
+ // Safety: Only rewrite if it looks like a static asset (has extension)
+ // or belongs to known legacy folders. This prevents catching Next.js routes.
+ if (remainingPath.match(/\.(?:html|php|js|css|png|jpg|jpeg|svg|gif|woff2?|ttf|pdf|json|xml)$/) ||
+ remainingPath.includes('wp-content/') ||
+ remainingPath.includes('wp-includes/') ||
+ remainingPath.includes('assets/')) {
+
+ // Normalize slug: klz-cables -> klz-cables.com (literal folder name)
+ const directory = slug === 'klz-cables' ? 'klz-cables.com' : slug;
+
+ return NextResponse.rewrite(new URL(`/showcase/${directory}/${remainingPath}`, request.url));
+ }
}
return NextResponse.next();
@@ -35,14 +48,8 @@ export const config = {
'/wp-content/:path*',
'/wp-includes/:path*',
'/assets/:path*',
- '/case-studies/wp-content/:path*',
- '/case-studies/wp-includes/:path*',
- '/case-studies/assets/:path*',
- '/work/wp-content/:path*',
- '/work/wp-includes/:path*',
- '/work/assets/:path*',
- '/blog/wp-content/:path*',
- '/blog/wp-includes/:path*',
- '/blog/assets/:path*',
+ '/case-studies/:path*',
+ '/work/:path*',
+ '/blog/:path*',
],
};
diff --git a/apps/web/src/styles/global.css b/apps/web/src/styles/global.css
index 3f8085a..a7de385 100644
--- a/apps/web/src/styles/global.css
+++ b/apps/web/src/styles/global.css
@@ -79,6 +79,28 @@
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
+
+ /* Interactive elements pointer */
+ a,
+ button,
+ [role="button"],
+ input[type="button"],
+ input[type="submit"],
+ input[type="reset"],
+ select,
+ summary,
+ label[for] {
+ cursor: pointer;
+ }
+
+ /* Ensure disabled elements don't show pointer */
+ button:disabled,
+ [role="button"]:disabled,
+ input:disabled,
+ select:disabled,
+ button[disabled] {
+ cursor: not-allowed;
+ }
}
/* Components - Tailwind utility classes */