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:
2026-03-16 18:49:13 +03:00
parent 4a1a2d7512
commit 26cb9a9772
4 changed files with 336 additions and 136 deletions

View 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
);
}