This commit is contained in:
commit
59bb5a38a3
132 files changed
+19435
No files matched your search
@@ -0,0 +1,87 @@
|
||||
import Image from "next/image";
|
||||
import type { Media, PlaceholderTone } from "@/content/site";
|
||||
|
||||
const TONES: Record<PlaceholderTone, { background: string; shape: "arch" | "cypress" | "window" | "figure" }> = {
|
||||
villa: { background: "linear-gradient(172deg, #efe2cc 0%, #e0c7a2 52%, #c49a6c 100%)", shape: "arch" },
|
||||
terrace: { background: "linear-gradient(185deg, #e9dcc6 0%, #d7c29d 60%, #9f8b62 100%)", shape: "cypress" },
|
||||
room: { background: "linear-gradient(160deg, #f3ebdf 0%, #e3d3bb 60%, #cdb592 100%)", shape: "window" },
|
||||
garden: { background: "linear-gradient(190deg, #e6e0cc 0%, #c9c3a0 55%, #8c8a64 100%)", shape: "cypress" },
|
||||
portrait: { background: "linear-gradient(170deg, #eadccb 0%, #d4b99c 60%, #a8805f 100%)", shape: "figure" },
|
||||
studio: { background: "linear-gradient(175deg, #e4ddd3 0%, #cabdac 60%, #8f8273 100%)", shape: "figure" },
|
||||
};
|
||||
|
||||
function Shape({ shape }: { shape: (typeof TONES)[PlaceholderTone]["shape"] }) {
|
||||
const common = { fill: "currentColor", className: "absolute inset-0 h-full w-full text-ink/10" } as const;
|
||||
switch (shape) {
|
||||
case "arch":
|
||||
return (
|
||||
<svg viewBox="0 0 400 500" preserveAspectRatio="xMidYMax slice" aria-hidden {...common}>
|
||||
<path d="M120 500V250a80 80 0 0 1 160 0v250z" />
|
||||
<path d="M20 500V330a40 40 0 0 1 80 0v170zM300 500V330a40 40 0 0 1 80 0v170z" opacity=".6" />
|
||||
</svg>
|
||||
);
|
||||
case "cypress":
|
||||
return (
|
||||
<svg viewBox="0 0 400 500" preserveAspectRatio="xMidYMax slice" aria-hidden {...common}>
|
||||
<path d="M90 500c-22-90-18-230 12-330 26 100 30 240 10 330z" />
|
||||
<path d="M290 500c-18-70-15-180 10-260 21 80 24 190 8 260z" opacity=".7" />
|
||||
<rect x="0" y="440" width="400" height="60" opacity=".5" />
|
||||
</svg>
|
||||
);
|
||||
case "window":
|
||||
return (
|
||||
<svg viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" aria-hidden {...common}>
|
||||
<path d="M150 90h100v190H150z" opacity=".7" />
|
||||
<rect x="60" y="360" width="280" height="70" rx="6" />
|
||||
</svg>
|
||||
);
|
||||
case "figure":
|
||||
return (
|
||||
<svg viewBox="0 0 400 500" preserveAspectRatio="xMidYMax slice" aria-hidden {...common}>
|
||||
<circle cx="200" cy="185" r="62" />
|
||||
<path d="M80 500c0-120 54-200 120-200s120 80 120 200z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
media: Media;
|
||||
/** Tailwind-Klasse fuer das Seitenverhaeltnis, z. B. "aspect-[4/5]". */
|
||||
aspect: string;
|
||||
sizes: string;
|
||||
showLabel: boolean;
|
||||
priority?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bild oder erkennbarer Platzhalter. Platzhalter tragen in Entwicklung und
|
||||
* Staging einen sichtbaren Hinweis, damit fehlende Freigaben nicht
|
||||
* uebersehen werden.
|
||||
*/
|
||||
export function MediaFrame({ media, aspect, sizes, showLabel, priority, className = "" }: Props) {
|
||||
if (media.kind === "image") {
|
||||
return (
|
||||
<div className={`relative overflow-hidden bg-sand ${aspect} ${className}`}>
|
||||
<Image src={media.src} alt={media.alt} fill sizes={sizes} priority={priority} className="object-cover" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const tone = TONES[media.tone];
|
||||
return (
|
||||
<div
|
||||
role="img"
|
||||
aria-label={`Platzhalter: ${media.label}`}
|
||||
className={`relative overflow-hidden ${aspect} ${className}`}
|
||||
style={{ background: tone.background }}
|
||||
>
|
||||
<Shape shape={tone.shape} />
|
||||
{showLabel && (
|
||||
<span className="absolute bottom-3 left-3 rounded-full bg-paper/85 px-3 py-1 text-xs font-semibold text-ink">
|
||||
Platzhalter: {media.label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user