218 lines
8.6 KiB
TypeScript
218 lines
8.6 KiB
TypeScript
import React, { useState } from "react";
|
|
import Markdown from "react-markdown";
|
|
import { Book } from "../types";
|
|
import { Sparkles, Calendar, ShoppingBag, Music, ExternalLink, Tag } from "lucide-react";
|
|
|
|
interface SpotlightSectionProps {
|
|
book: Book;
|
|
title?: string;
|
|
subtitle?: string;
|
|
accentColor: string;
|
|
secondaryColor: string;
|
|
cardBgColor: string;
|
|
}
|
|
|
|
export default function SpotlightSection({
|
|
book,
|
|
title,
|
|
subtitle,
|
|
accentColor,
|
|
secondaryColor,
|
|
cardBgColor,
|
|
}: SpotlightSectionProps) {
|
|
const [showSpotify, setShowSpotify] = useState(false);
|
|
|
|
// Helper to check if a release date is in the future
|
|
const isFutureRelease = (): boolean => {
|
|
if (!book.publishedYear) return false;
|
|
const str = String(book.publishedYear).trim();
|
|
|
|
// Check if it's a 4-digit year like "2027"
|
|
if (/^\d{4}$/.test(str)) {
|
|
return parseInt(str) > new Date().getFullYear();
|
|
}
|
|
|
|
// Check if it's a German date e.g. "15.10.2026" or "15.10.26"
|
|
const germanDateMatch = str.match(/^(\d{1,2})\.(\d{1,2})\.(\d{2,4})$/);
|
|
if (germanDateMatch) {
|
|
let [, day, month, year] = germanDateMatch;
|
|
if (year.length === 2) year = "20" + year;
|
|
const parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
|
return parsedDate > new Date();
|
|
}
|
|
|
|
// Standard JS Date parse
|
|
const parsed = Date.parse(str);
|
|
if (!isNaN(parsed)) {
|
|
return parsed > Date.now();
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const isFuture = isFutureRelease();
|
|
const badgeText = book.spotlightBadge || (isFuture ? `Erscheint am ${book.publishedYear}` : "Neuerscheinung");
|
|
|
|
return (
|
|
<section id="spotlight" className="space-y-6 my-12">
|
|
<div className="flex items-center gap-3 border-b border-slate-800 pb-3">
|
|
<div
|
|
className="p-2 rounded-xl flex items-center justify-center shadow-md"
|
|
style={{ backgroundColor: `${accentColor}20`, color: accentColor }}
|
|
>
|
|
<Sparkles className="w-6 h-6 animate-pulse" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-2xl md:text-3xl font-bold tracking-tight text-white flex items-center gap-2">
|
|
<span>{title || "Spotlight & Neuerscheinung"}</span>
|
|
</h2>
|
|
<p className="text-xs text-slate-400">{subtitle || "Besonders hervorgehobenes Werk im Fokus"}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="relative overflow-hidden border rounded-2xl p-6 md:p-8 shadow-2xl transition-all"
|
|
style={{
|
|
backgroundColor: cardBgColor,
|
|
borderColor: `${accentColor}40`,
|
|
boxShadow: `0 20px 50px -10px ${accentColor}15`
|
|
}}
|
|
>
|
|
{/* Subtle Ambient Background Gradient */}
|
|
<div
|
|
className="absolute -right-20 -bottom-20 w-80 h-80 rounded-full blur-3xl pointer-events-none opacity-20"
|
|
style={{ backgroundColor: accentColor }}
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-8 items-center relative z-10">
|
|
{/* Cover image column */}
|
|
<div className="md:col-span-4 flex flex-col items-center space-y-4">
|
|
<div className="relative group w-full max-w-[260px]">
|
|
<div
|
|
className="absolute -inset-1 rounded-2xl blur-lg opacity-40 group-hover:opacity-75 transition duration-500"
|
|
style={{ backgroundColor: accentColor }}
|
|
/>
|
|
<div className="relative aspect-[2/3] w-full rounded-xl overflow-hidden border border-slate-700/60 shadow-2xl bg-slate-900">
|
|
<img
|
|
src={book.coverUrl}
|
|
alt={book.title}
|
|
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
|
referrerPolicy="no-referrer"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{book.buyLink && (
|
|
<a
|
|
href={book.buyLink}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="w-full max-w-[260px] py-3 px-4 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 flex items-center justify-center gap-2 shadow-lg cursor-pointer hover:scale-[1.02]"
|
|
style={{ backgroundColor: accentColor, color: "#000" }}
|
|
>
|
|
<ShoppingBag className="w-4 h-4" />
|
|
<span>{isFuture ? "Jetzt vorbestellen" : "Jetzt im Handel"}</span>
|
|
<ExternalLink className="w-3.5 h-3.5 ml-auto opacity-70" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
|
|
{/* Details column */}
|
|
<div className="md:col-span-8 space-y-5">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<span
|
|
className="px-3 py-1 rounded-full text-xs font-mono font-bold tracking-wide uppercase border flex items-center gap-1.5 shadow-sm"
|
|
style={{
|
|
backgroundColor: isFuture ? `${secondaryColor}25` : `${accentColor}25`,
|
|
borderColor: isFuture ? secondaryColor : accentColor,
|
|
color: isFuture ? "#fff" : accentColor
|
|
}}
|
|
>
|
|
<Sparkles className="w-3.5 h-3.5" />
|
|
<span>{badgeText}</span>
|
|
</span>
|
|
|
|
{book.publishedYear && (
|
|
<span className="px-3 py-1 bg-slate-800/80 border border-slate-700 text-slate-300 rounded-full text-xs font-mono flex items-center gap-1.5">
|
|
<Calendar className="w-3.5 h-3.5 text-slate-400" />
|
|
<span>{isFuture ? "Erscheinungsdatum:" : "Veröffentlichung:"} {book.publishedYear}</span>
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-2xl md:text-4xl font-extrabold text-white tracking-tight">
|
|
{book.title}
|
|
</h3>
|
|
{book.subtitle && (
|
|
<p className="text-base md:text-lg font-medium mt-1 italic" style={{ color: secondaryColor }}>
|
|
{book.subtitle}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Genres */}
|
|
{book.genres && book.genres.length > 0 && (
|
|
<div className="flex flex-wrap items-center gap-1.5 pt-1">
|
|
<Tag className="w-3.5 h-3.5 text-slate-500 mr-1" />
|
|
{book.genres.map((g, idx) => (
|
|
<span
|
|
key={idx}
|
|
className="px-2.5 py-0.5 rounded-md text-[11px] font-mono border bg-slate-900/80 text-slate-300 border-slate-800"
|
|
>
|
|
{g}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Synopsis */}
|
|
<div className="prose prose-invert max-w-none text-slate-300 text-sm leading-relaxed space-y-3 pt-2 border-t border-slate-800/60">
|
|
<Markdown
|
|
components={{
|
|
a: ({ node, ...props }) => (
|
|
<a
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="underline font-medium hover:opacity-80"
|
|
style={{ color: accentColor }}
|
|
{...props}
|
|
/>
|
|
)
|
|
}}
|
|
>
|
|
{book.description}
|
|
</Markdown>
|
|
</div>
|
|
|
|
{/* Spotify Player Toggle if present */}
|
|
{book.spotifyPlaylistId && (
|
|
<div className="pt-2">
|
|
<button
|
|
onClick={() => setShowSpotify(!showSpotify)}
|
|
className="px-4 py-2 bg-slate-900 hover:bg-slate-850 border border-slate-700 rounded-xl text-xs font-mono text-slate-300 flex items-center gap-2 transition-all cursor-pointer"
|
|
>
|
|
<Music className="w-4 h-4 text-emerald-400" />
|
|
<span>{showSpotify ? "Playlist einklappen" : "Buch-Soundtrack (Spotify) anhören"}</span>
|
|
</button>
|
|
|
|
{showSpotify && (
|
|
<div className="mt-3 rounded-xl overflow-hidden border border-slate-800 shadow-xl bg-slate-950">
|
|
<iframe
|
|
src={`https://open.spotify.com/embed/playlist/${book.spotifyPlaylistId}?utm_source=generator&theme=0`}
|
|
width="100%"
|
|
height="152"
|
|
frameBorder="0"
|
|
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|