feat: automate availability and fix KLZ showcase 404s
Some checks failed
Some checks failed
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
</div>
|
||||
<BodyText className="text-sm md:text-base leading-snug">
|
||||
Aktuell nehme ich Projekte für{" "}
|
||||
<span className="font-bold text-slate-900">Q2 2026</span>{" "}
|
||||
<span className="font-bold text-slate-900"><Availability /></span>{" "}
|
||||
an.
|
||||
</BodyText>
|
||||
</div>
|
||||
|
||||
26
apps/web/src/components/Availability.tsx
Normal file
26
apps/web/src/components/Availability.tsx
Normal file
@@ -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<string>("");
|
||||
|
||||
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 <span className="opacity-0">Q- 202-</span>;
|
||||
|
||||
return <>{text}</>;
|
||||
};
|
||||
@@ -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*',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user