feat: redesign news & master classes sections, migrate middleware to proxy
- News: magazine layout with featured hero article + compact list, click-to-open modal - Master classes: fashion lookbook portrait cards with full-bleed images and overlay content - Rename middleware.ts to proxy.ts (Next.js 16 convention) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
110
src/components/ui/NewsModal.tsx
Normal file
110
src/components/ui/NewsModal.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import Image from "next/image";
|
||||
import { X, Calendar, ExternalLink } from "lucide-react";
|
||||
import type { NewsItem } from "@/types/content";
|
||||
|
||||
interface NewsModalProps {
|
||||
item: NewsItem | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString("ru-RU", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
export function NewsModal({ item, onClose }: NewsModalProps) {
|
||||
useEffect(() => {
|
||||
if (!item) return;
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") onClose();
|
||||
}
|
||||
document.addEventListener("keydown", onKey);
|
||||
return () => document.removeEventListener("keydown", onKey);
|
||||
}, [item, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
if (item) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [item]);
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className="modal-overlay fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" />
|
||||
|
||||
<div
|
||||
className="modal-content relative w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-2xl border border-white/[0.08] bg-[#0a0a0a] shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-black/50 text-neutral-400 backdrop-blur-sm transition-colors hover:bg-white/[0.1] hover:text-white cursor-pointer"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
|
||||
{item.image && (
|
||||
<div className="relative aspect-[2/1] w-full overflow-hidden rounded-t-2xl">
|
||||
<Image
|
||||
src={item.image}
|
||||
alt={item.title}
|
||||
fill
|
||||
sizes="(min-width: 768px) 672px, 100vw"
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#0a0a0a] via-transparent to-transparent" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`p-6 sm:p-8 ${item.image ? "-mt-12 relative" : ""}`}>
|
||||
<span className="inline-flex items-center gap-1.5 text-xs text-neutral-400">
|
||||
<Calendar size={12} />
|
||||
{formatDate(item.date)}
|
||||
</span>
|
||||
|
||||
<h2 className="mt-2 text-xl sm:text-2xl font-bold text-white leading-tight">
|
||||
{item.title}
|
||||
</h2>
|
||||
|
||||
<p className="mt-4 text-sm sm:text-base leading-relaxed text-neutral-300 whitespace-pre-line">
|
||||
{item.text}
|
||||
</p>
|
||||
|
||||
{item.link && (
|
||||
<a
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-6 inline-flex items-center gap-2 rounded-xl bg-gold px-5 py-2.5 text-sm font-semibold text-black transition-all hover:bg-gold-light hover:shadow-lg hover:shadow-gold/20"
|
||||
>
|
||||
Подробнее
|
||||
<ExternalLink size={14} />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user