Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 2m49s
- Team-Abschnitt mit neun Personen, Eric Heitz als Head of Modelcamp, Portraets im Rundbogen mit Initialen-Platzhaltern bis Fotos vorliegen - Unterkunftsbilder der Villa La Chiesina lokal in public/images/unterkunft (vom Auftraggeber freigegeben), Hero-Leitbild, Ausstattung, Booking-Link - Migration: Termin 08.05.-15.05.2027, Ort Arcidosso (Toskana), eigenes Bett im geteilten Zimmer, 13 Plaetze; Admin-Werte bleiben erhalten - Reveal: kein Hydration-Fehler mehr bei reduzierter Bewegung - Doku zu offenen Punkten aktualisiert Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
79 lines
3.6 KiB
TypeScript
79 lines
3.6 KiB
TypeScript
import Image from "next/image";
|
|
import type { Media, PlaceholderTone } from "@/content/site";
|
|
|
|
/*
|
|
* Platzhalter als typografische Tafeln: ruhiger Farbton aus der Palette,
|
|
* ein angeschnittener Bodoni-Buchstabe aus "ARTISTA" (bzw. die Jahreszahl
|
|
* im Cover) und ein feines Passepartout. Sie wirken gestaltet, sind aber
|
|
* eindeutig kein Foto und damit als Platzhalter erkennbar.
|
|
*/
|
|
type Plate = { background: string; glyph: string; italic?: boolean; style: React.CSSProperties };
|
|
|
|
// Groessen und Lagen in Container-Einheiten (cqh/cqw): der Anschnitt sieht
|
|
// in jeder Tafelgroesse gleich aus.
|
|
const TONES: Record<PlaceholderTone, Plate> = {
|
|
cover: { background: "#ddd0bd", glyph: "27", italic: true, style: { fontSize: "92cqh", right: "-3cqw", bottom: "-20cqh" } },
|
|
villa: { background: "#e2d6c4", glyph: "A", style: { fontSize: "105cqh", left: "5cqw", bottom: "-22cqh" } },
|
|
terrace: { background: "#d6ccb8", glyph: "R", italic: true, style: { fontSize: "118cqh", right: "-4cqw", bottom: "-30cqh" } },
|
|
room: { background: "#e8dfd2", glyph: "T", style: { fontSize: "112cqh", left: "22cqw", top: "-34cqh" } },
|
|
garden: { background: "#cfcab6", glyph: "S", italic: true, style: { fontSize: "150cqh", right: "7cqw", bottom: "-58cqh" } },
|
|
portrait: { background: "#dccfbe", glyph: "R", style: { fontSize: "92cqh", left: "-10cqw", bottom: "-14cqh" } },
|
|
studio: { background: "#d2c9bd", glyph: "A", italic: true, style: { fontSize: "96cqh", right: "-16cqw", bottom: "-16cqh" } },
|
|
};
|
|
|
|
// Initialen auf Portraet-Platzhaltern: gross, angeschnitten am unteren Rand.
|
|
const INITIALS_STYLE: React.CSSProperties = { fontSize: "34cqh", left: "8cqw", bottom: "-4cqh", letterSpacing: "-0.03em" };
|
|
|
|
type Props = {
|
|
media: Media;
|
|
/** Tailwind-Klasse fuer das Seitenverhaeltnis, z. B. "aspect-[4/5]". */
|
|
aspect: string;
|
|
sizes: string;
|
|
showLabel: boolean;
|
|
priority?: boolean;
|
|
className?: string;
|
|
/** "arch": oben rund wie ein Rundbogenfenster (Team-Portraets). */
|
|
shape?: "rect" | "arch";
|
|
};
|
|
|
|
const ARCH = "rounded-t-[50vw]";
|
|
|
|
/**
|
|
* 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 = "", shape = "rect" }: Props) {
|
|
const shapeClass = shape === "arch" ? ARCH : "";
|
|
if (media.kind === "image") {
|
|
return (
|
|
<div className={`relative overflow-hidden bg-sand ${aspect} ${shapeClass} ${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={`placeholder-plate relative isolate overflow-hidden ${aspect} ${shapeClass} ${className}`}
|
|
style={{ backgroundColor: tone.background, containerType: "size" }}
|
|
>
|
|
<span
|
|
aria-hidden
|
|
className={`display pointer-events-none absolute select-none leading-none ${media.glyph ? "text-ink/[0.13]" : "text-ink/[0.07]"} ${tone.italic && !media.glyph ? "italic" : ""}`}
|
|
style={media.glyph ? INITIALS_STYLE : tone.style}
|
|
>
|
|
{media.glyph ?? tone.glyph}
|
|
</span>
|
|
{shape === "rect" && <span aria-hidden className="pointer-events-none absolute inset-3 border border-ink/10 md:inset-4" />}
|
|
{showLabel && (
|
|
<span className="absolute bottom-6 left-6 bg-paper/90 px-2.5 py-1 text-xs font-semibold text-ink md:bottom-7 md:left-7">
|
|
Platzhalter: {media.label}
|
|
</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|