Persönlichkeitscoach-Zertifikat über Greator Speaker
Anelias neues Greator-Zertifikat (Persönlichkeitscoach, 09.10.2026) steht in „Über Anelia“ über dem Speaker-Abzeichen, im selben Lorbeerkranz mit freigestelltem Siegel, Hover und Vorschau-Overlay. Das Siegel ist aus dem PDF freigestellt (Kreis, 150-dpi-Raster), Farbe und Form unverändert. getCertificate() wird zu getCertificates(): eine Liste, das neueste oben. Ein einzelnes Abzeichen klebt weiter auf Höhe der „04“. Zwei passen dort nicht ins Bild, also steht der Stapel still und beginnt auf Höhe der Eyebrow. Unter lg stehen sie nebeneinander, auf dem Handy untereinander. Der Abzeichen-Titel darf umbrechen (Zeilenhöhe 1,4, Ränder ausgeglichen; einzeilig unverändert). Der Dialog-Titel trennt Silben, damit „Persönlichkeitscoach“ auf dem Handy „Schließen“ nicht aus dem Bild schiebt. Jeder Dialog bekommt seine eigene Titel-ID (useId). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
30e9151de4
commit
1e7b84b27c
10 files changed
+124
-60
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -17,8 +17,8 @@ export interface AboutProps {
|
|||||||
photo: string | null;
|
photo: string | null;
|
||||||
/** Inhalt der Sektion. Ohne Angabe die deutsche Fassung – die englische reicht sie durch. */
|
/** Inhalt der Sektion. Ohne Angabe die deutsche Fassung – die englische reicht sie durch. */
|
||||||
content?: HomeContent["about"];
|
content?: HomeContent["about"];
|
||||||
/** `getCertificate()` – `null` lässt die Sektion wie vorher rendern. */
|
/** `getCertificates()` – leer lässt die Sektion wie vorher rendern. */
|
||||||
certificate?: CertificateAsset | null;
|
certificates?: readonly CertificateAsset[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* H2 on ivory: Playfair 400, max 14ch (spec §3.2 / §6.5). */
|
/* H2 on ivory: Playfair 400, max 14ch (spec §3.2 / §6.5). */
|
||||||
@@ -34,12 +34,13 @@ const BODY = "max-w-[36rem] font-sans text-[1.125rem] leading-[1.6] text-fg [hyp
|
|||||||
* verifiable facts. No counters, no logos.
|
* verifiable facts. No counters, no logos.
|
||||||
*
|
*
|
||||||
* With photo: Portrait cols 3–6 (sticky) · text + ledger cols 7–12 · marginalia and
|
* With photo: Portrait cols 3–6 (sticky) · text + ledger cols 7–12 · marginalia and
|
||||||
* certificate stacked under the ledger (cols 7–12) – the text already
|
* certificates stacked under the ledger (cols 7–12) – the text already
|
||||||
* owns the outer columns there, so nothing can stick to the right edge.
|
* owns the outer columns there, so nothing can stick to the right edge.
|
||||||
* Without: text cols 3–8 · ledger cols 3–9 · marginalia under the ledger ·
|
* Without: text cols 3–8 · ledger cols 3–9 · marginalia under the ledger ·
|
||||||
* certificate sticky in cols 10–12 (no monogram vignette – see below).
|
* certificates in cols 10–12, a single one sticky (no monogram vignette –
|
||||||
|
* see below).
|
||||||
*/
|
*/
|
||||||
export function About({ photo, content: about = aboutDe, certificate = null }: AboutProps) {
|
export function About({ photo, content: about = aboutDe, certificates = [] }: AboutProps) {
|
||||||
const paragraphs = about.paragraphs.map((p, i) =>
|
const paragraphs = about.paragraphs.map((p, i) =>
|
||||||
i === about.paragraphs.length - 1 && flags.showTwins ? `${p} ${about.twins}` : p,
|
i === about.paragraphs.length - 1 && flags.showTwins ? `${p} ${about.twins}` : p,
|
||||||
);
|
);
|
||||||
@@ -104,11 +105,13 @@ export function About({ photo, content: about = aboutDe, certificate = null }: A
|
|||||||
<Marginalia items={about.marginalia} />
|
<Marginalia items={about.marginalia} />
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
{/* Das Zertifikat ist das Gegenstück zur klebenden „04“: gleiche Höhe
|
{/* Ein einzelnes Zertifikat ist das Gegenstück zur klebenden „04“: gleiche
|
||||||
(`calc(50vh − .45em)`), gleiche Zeilen, nur am anderen Rand. Mit Foto
|
Höhe (`calc(50vh − .45em)`), gleiche Zeilen, nur am anderen Rand. Zwei
|
||||||
reicht der Text bis Spalte 12 – dort gäbe es keine freie Gutter, also
|
Abzeichen (je rund 24 rem) passen dort nicht mehr ins Bild – dann steht
|
||||||
steht es dann still unter dem Werdegang. */}
|
der Stapel still und beginnt oben auf Höhe der Eyebrow, das neueste
|
||||||
{certificate && (
|
zuerst. Mit Foto reicht der Text bis Spalte 12 – dort gäbe es keine freie
|
||||||
|
Gutter, also stehen sie dann still unter dem Werdegang. */}
|
||||||
|
{certificates.length > 0 && (
|
||||||
<Reveal
|
<Reveal
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-span-12 mt-14",
|
"col-span-12 mt-14",
|
||||||
@@ -117,8 +120,20 @@ export function About({ photo, content: about = aboutDe, certificate = null }: A
|
|||||||
: "lg:col-span-3 lg:col-start-10 lg:row-span-3 lg:row-start-1 lg:mt-0",
|
: "lg:col-span-3 lg:col-start-10 lg:row-span-3 lg:row-start-1 lg:mt-0",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={cn(!hasPhoto && "lg:sticky lg:top-[calc(50vh_-_0.45em)]")}>
|
<div
|
||||||
<Certificate content={about.certificate} asset={certificate} />
|
className={cn(
|
||||||
|
"flex flex-wrap gap-x-12 gap-y-16 lg:flex-col",
|
||||||
|
!hasPhoto && certificates.length === 1 && "lg:sticky lg:top-[calc(50vh_-_0.45em)]",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{certificates.map((asset) => (
|
||||||
|
<Certificate
|
||||||
|
key={asset.key}
|
||||||
|
content={about.certificates[asset.key]}
|
||||||
|
asset={asset}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { About } from "@/components/home/About";
|
|||||||
import { Audience } from "@/components/home/Audience";
|
import { Audience } from "@/components/home/Audience";
|
||||||
import { ProcessContact } from "@/components/home/ProcessContact";
|
import { ProcessContact } from "@/components/home/ProcessContact";
|
||||||
import type { HomeContent } from "@/content/home";
|
import type { HomeContent } from "@/content/home";
|
||||||
import { getCertificate, getPhotos } from "@/lib/assets";
|
import { getCertificates, getPhotos } from "@/lib/assets";
|
||||||
import { routesByLang, type Lang } from "@/lib/i18n";
|
import { routesByLang, type Lang } from "@/lib/i18n";
|
||||||
import { rootCopy } from "@/lib/metadata";
|
import { rootCopy } from "@/lib/metadata";
|
||||||
import { site } from "@/lib/site";
|
import { site } from "@/lib/site";
|
||||||
@@ -37,7 +37,7 @@ export function HomeBody({ lang, content }: { lang: Lang; content: HomeContent }
|
|||||||
<Positioning content={content.positioning} />
|
<Positioning content={content.positioning} />
|
||||||
<ServicesIndex content={content.services} />
|
<ServicesIndex content={content.services} />
|
||||||
<WolfPrinciple mark={<BrandMark idPrefix="prinzip" />} content={content.principle} />
|
<WolfPrinciple mark={<BrandMark idPrefix="prinzip" />} content={content.principle} />
|
||||||
<About photo={aboutPhoto} content={content.about} certificate={getCertificate()} />
|
<About photo={aboutPhoto} content={content.about} certificates={getCertificates()} />
|
||||||
<Audience content={content.audience} />
|
<Audience content={content.audience} />
|
||||||
<ProcessContact content={content.contact} privacyHref={routesByLang[lang].datenschutz} />
|
<ProcessContact content={content.contact} privacyHref={routesByLang[lang].datenschutz} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -63,11 +63,15 @@ export function Certificate({ content, asset, className }: CertificateProps) {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span className="mt-6 block font-mono text-xs uppercase leading-none tracking-[0.18em] text-accent">
|
{/* Zeilenhöhe 1,4 statt 1, damit ein umbrechender Titel („Greator
|
||||||
|
Persönlichkeitscoach“) nicht zusammenklebt. Die Ränder davor und danach
|
||||||
|
sind um den halben Durchschuss (0,15 rem) kürzer – einzeilig steht alles
|
||||||
|
wie zuvor mit `leading-none`, `mt-6` und `mt-5`. */}
|
||||||
|
<span className="mt-[1.35rem] block font-mono text-xs uppercase leading-[1.4] tracking-[0.18em] text-accent">
|
||||||
{content.title}
|
{content.title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span aria-hidden="true" className="rule-gold mt-5 block h-px w-full" />
|
<span aria-hidden="true" className="rule-gold mt-[1.1rem] block h-px w-full" />
|
||||||
|
|
||||||
<span className="mt-5 block font-sans text-[0.875rem] leading-[1.5] text-fg [hyphens:auto]">
|
<span className="mt-5 block font-sans text-[0.875rem] leading-[1.5] text-fg [hyphens:auto]">
|
||||||
{content.description}
|
{content.description}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import type { CertificateAsset } from "@/lib/assets";
|
import type { CertificateAsset } from "@/lib/assets";
|
||||||
import type { CertificateCopy } from "@/content/home";
|
import type { CertificateCopy } from "@/content/home";
|
||||||
import { gsap, DUR, EASE, MM, getLenis, refreshSoon } from "@/lib/gsap";
|
import { gsap, DUR, EASE, MM, getLenis, refreshSoon } from "@/lib/gsap";
|
||||||
|
|
||||||
/** Die Überschrift benennt den Dialog (`aria-labelledby`) – es gibt genau einen. */
|
|
||||||
const TITLE_ID = "zertifikat-titel";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kopf, Blatt und Fußzeile teilen sich eine Breite, sonst stehen sie versetzt.
|
* Kopf, Blatt und Fußzeile teilen sich eine Breite, sonst stehen sie versetzt.
|
||||||
* Auf dem Desktop richtet sie sich nach der Höhe: das Blatt soll ganz ins Bild
|
* Auf dem Desktop richtet sie sich nach der Höhe: das Blatt soll ganz ins Bild
|
||||||
@@ -39,6 +36,8 @@ export interface CertificateDialogProps {
|
|||||||
* Dialog in seinem eigenen toten Bereich.
|
* Dialog in seinem eigenen toten Bereich.
|
||||||
*/
|
*/
|
||||||
export function CertificateDialog({ open, onClose, content, asset }: CertificateDialogProps) {
|
export function CertificateDialog({ open, onClose, content, asset }: CertificateDialogProps) {
|
||||||
|
/* Die Überschrift benennt den Dialog (`aria-labelledby`) – je Zertifikat ein Dialog. */
|
||||||
|
const titleId = useId();
|
||||||
const panel = useRef<HTMLDivElement>(null);
|
const panel = useRef<HTMLDivElement>(null);
|
||||||
const onCloseRef = useRef(onClose);
|
const onCloseRef = useRef(onClose);
|
||||||
|
|
||||||
@@ -138,7 +137,7 @@ export function CertificateDialog({ open, onClose, content, asset }: Certificate
|
|||||||
ref={panel}
|
ref={panel}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby={TITLE_ID}
|
aria-labelledby={titleId}
|
||||||
data-theme="dark"
|
data-theme="dark"
|
||||||
data-lenis-prevent=""
|
data-lenis-prevent=""
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -148,12 +147,15 @@ export function CertificateDialog({ open, onClose, content, asset }: Certificate
|
|||||||
className="fixed inset-0 z-50 flex flex-col items-center overflow-y-auto overscroll-contain bg-ink px-5 py-[calc(var(--header-h,60px)/2+16px)] text-fg lg:px-10"
|
className="fixed inset-0 z-50 flex flex-col items-center overflow-y-auto overscroll-contain bg-ink px-5 py-[calc(var(--header-h,60px)/2+16px)] text-fg lg:px-10"
|
||||||
>
|
>
|
||||||
<div className={`mb-8 flex items-start justify-between gap-6 ${COLUMN}`}>
|
<div className={`mb-8 flex items-start justify-between gap-6 ${COLUMN}`}>
|
||||||
<div>
|
{/* `min-w-0` + Silbentrennung: „Persönlichkeitscoach“ ist auf dem Handy
|
||||||
|
breiter als der Platz neben „Schließen“ und schöbe den Knopf sonst
|
||||||
|
aus dem Bild. */}
|
||||||
|
<div className="min-w-0">
|
||||||
<p className="font-mono text-xs uppercase tracking-[0.12em] text-accent">{content.eyebrow}</p>
|
<p className="font-mono text-xs uppercase tracking-[0.12em] text-accent">{content.eyebrow}</p>
|
||||||
{/* Playfair hat hier den Platz für seine 28-px-Untergrenze – im Abzeichen selbst nicht. */}
|
{/* Playfair hat hier den Platz für seine 28-px-Untergrenze – im Abzeichen selbst nicht. */}
|
||||||
<h2
|
<h2
|
||||||
id={TITLE_ID}
|
id={titleId}
|
||||||
className="mt-3 font-display text-[clamp(1.75rem,1.2rem+1vw,2.25rem)] font-normal leading-none tracking-[-0.01em] text-fg"
|
className="mt-3 font-display text-[clamp(1.75rem,1.2rem+1vw,2.25rem)] font-normal leading-none tracking-[-0.01em] text-fg [hyphens:auto]"
|
||||||
>
|
>
|
||||||
{content.title}
|
{content.title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
+25
-11
@@ -138,17 +138,31 @@ export const homeEn: HomeContent = {
|
|||||||
{ value: "LinkedIn", href: site.linkedin, external: true },
|
{ value: "LinkedIn", href: site.linkedin, external: true },
|
||||||
{ value: "German · English" },
|
{ value: "German · English" },
|
||||||
],
|
],
|
||||||
certificate: {
|
certificates: {
|
||||||
title: "Greator Speaker",
|
persoenlichkeitscoach: {
|
||||||
description: "Certified graduate of the Greator Speaker programme",
|
title: "Greator Personality Coach",
|
||||||
date: "May 2026",
|
description: "Certified graduate of the Greator Personality Coach programme",
|
||||||
cta: "View certificate",
|
date: "October 2026",
|
||||||
eyebrow: "Certificate",
|
cta: "View certificate",
|
||||||
dialogMeta: "Greator TheKey · issued 16 May 2026",
|
eyebrow: "Certificate",
|
||||||
imageAlt:
|
dialogMeta: "Greator · issued 9 October 2026",
|
||||||
"Certificate: Anelia Wolf has successfully completed the certified Greator Speaker programme.",
|
imageAlt:
|
||||||
pdfLabel: "Open PDF",
|
"Certificate: Anelia Wolf has successfully completed the theoretical and practical parts of the three-month Greator Personality Coach programme.",
|
||||||
close: "Close",
|
pdfLabel: "Open PDF",
|
||||||
|
close: "Close",
|
||||||
|
},
|
||||||
|
speaker: {
|
||||||
|
title: "Greator Speaker",
|
||||||
|
description: "Certified graduate of the Greator Speaker programme",
|
||||||
|
date: "May 2026",
|
||||||
|
cta: "View certificate",
|
||||||
|
eyebrow: "Certificate",
|
||||||
|
dialogMeta: "Greator TheKey · issued 16 May 2026",
|
||||||
|
imageAlt:
|
||||||
|
"Certificate: Anelia Wolf has successfully completed the certified Greator Speaker programme.",
|
||||||
|
pdfLabel: "Open PDF",
|
||||||
|
close: "Close",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
folio: { current: "05", total: "06" },
|
folio: { current: "05", total: "06" },
|
||||||
},
|
},
|
||||||
|
|||||||
+31
-15
@@ -262,20 +262,36 @@ export const about = {
|
|||||||
{ value: "LinkedIn", href: site.linkedin, external: true },
|
{ value: "LinkedIn", href: site.linkedin, external: true },
|
||||||
{ value: "Deutsch · Englisch" },
|
{ value: "Deutsch · Englisch" },
|
||||||
] satisfies readonly MarginaliaItem[],
|
] satisfies readonly MarginaliaItem[],
|
||||||
/* Das Abzeichen in der rechten Gutter – Gegenstück zur klebenden „04“ links.
|
/* Die Abzeichen in der rechten Gutter – Gegenstück zur klebenden „04“ links.
|
||||||
`seal` steht IM Lorbeerkranz, alles darunter ist die Bildunterschrift. */
|
`seal` steht IM Lorbeerkranz, alles darunter ist die Bildunterschrift.
|
||||||
certificate: {
|
Schlüssel wie in `getCertificates()` (src/lib/assets.ts), dort auch die Reihenfolge. */
|
||||||
title: "Greator Speaker",
|
certificates: {
|
||||||
description: "Zertifizierte Absolventin der Greator Speaker Ausbildung",
|
persoenlichkeitscoach: {
|
||||||
date: "Mai 2026",
|
title: "Greator Persönlichkeitscoach",
|
||||||
cta: "Zertifikat ansehen",
|
description: "Zertifizierte Absolventin der Ausbildung zum Greator Persönlichkeitscoach",
|
||||||
/* Overlay */
|
date: "Oktober 2026",
|
||||||
eyebrow: "Zertifikat",
|
cta: "Zertifikat ansehen",
|
||||||
dialogMeta: "Greator TheKey · Ausstellungsdatum 16. Mai 2026",
|
/* Overlay */
|
||||||
imageAlt:
|
eyebrow: "Zertifikat",
|
||||||
"Zertifikat: Anelia Wolf hat die Ausbildung zum zertifizierten Greator Speaker erfolgreich abgeschlossen.",
|
dialogMeta: "Greator · Ausstellungsdatum 9. Oktober 2026",
|
||||||
pdfLabel: "PDF öffnen",
|
imageAlt:
|
||||||
close: "Schließen",
|
"Zertifikat: Anelia Wolf hat den theoretischen und praktischen Teil der dreimonatigen Ausbildung zum Greator Persönlichkeitscoach erfolgreich abgeschlossen.",
|
||||||
|
pdfLabel: "PDF öffnen",
|
||||||
|
close: "Schließen",
|
||||||
|
},
|
||||||
|
speaker: {
|
||||||
|
title: "Greator Speaker",
|
||||||
|
description: "Zertifizierte Absolventin der Greator Speaker Ausbildung",
|
||||||
|
date: "Mai 2026",
|
||||||
|
cta: "Zertifikat ansehen",
|
||||||
|
/* Overlay */
|
||||||
|
eyebrow: "Zertifikat",
|
||||||
|
dialogMeta: "Greator TheKey · Ausstellungsdatum 16. Mai 2026",
|
||||||
|
imageAlt:
|
||||||
|
"Zertifikat: Anelia Wolf hat die Ausbildung zum zertifizierten Greator Speaker erfolgreich abgeschlossen.",
|
||||||
|
pdfLabel: "PDF öffnen",
|
||||||
|
close: "Schließen",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
folio: { current: "05", total: "06" },
|
folio: { current: "05", total: "06" },
|
||||||
} as const;
|
} as const;
|
||||||
@@ -389,4 +405,4 @@ assertNoPlaceholders("src/content/home.ts", collectStrings(home));
|
|||||||
/** Struktur, nicht Wortlaut – die englische Fassung erfüllt denselben Typ. */
|
/** Struktur, nicht Wortlaut – die englische Fassung erfüllt denselben Typ. */
|
||||||
export type HomeContent = Widen<typeof home>;
|
export type HomeContent = Widen<typeof home>;
|
||||||
/** Texte des Zertifikat-Abzeichens – Abzeichen und Overlay teilen sie sich. */
|
/** Texte des Zertifikat-Abzeichens – Abzeichen und Overlay teilen sie sich. */
|
||||||
export type CertificateCopy = HomeContent["about"]["certificate"];
|
export type CertificateCopy = HomeContent["about"]["certificates"]["speaker"];
|
||||||
+23
-10
@@ -57,26 +57,39 @@ export function getSpeakerPdf(): string | null {
|
|||||||
return publicUrl("speaker-profil.pdf");
|
return publicUrl("speaker-profil.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Schlüssel in `about.certificates` (Texte) – Reihenfolge siehe `CERTIFICATES`. */
|
||||||
|
export type CertificateKey = "persoenlichkeitscoach" | "speaker";
|
||||||
|
|
||||||
export interface CertificateAsset {
|
export interface CertificateAsset {
|
||||||
/** `/zertifikate/greator-speaker.pdf` – das Original zum Öffnen/Herunterladen. */
|
key: CertificateKey;
|
||||||
|
/** z. B. `/zertifikate/greator-speaker.pdf` – das Original zum Öffnen/Herunterladen. */
|
||||||
pdf: string;
|
pdf: string;
|
||||||
/** Seite 1 als Bild für die Vorschau (kein eingebettetes PDF, kein iframe). */
|
/** Seite 1 als Bild für die Vorschau (kein eingebettetes PDF, kein iframe). */
|
||||||
image: string;
|
image: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
/** Das runde Greator-TheKey-Siegel, freigestellt – es steht im Lorbeerkranz. */
|
/** Das runde Greator-Siegel, freigestellt – es steht im Lorbeerkranz. */
|
||||||
seal: string;
|
seal: string;
|
||||||
sealSize: number;
|
sealSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Oben steht das neueste Zertifikat. */
|
||||||
|
const CERTIFICATES = [
|
||||||
|
{ key: "persoenlichkeitscoach", file: "greator-persoenlichkeitscoach", seal: "greator-siegel-coach.webp", width: 989, height: 1398 },
|
||||||
|
{ key: "speaker", file: "greator-speaker", seal: "greator-siegel.webp", width: 989, height: 1400 },
|
||||||
|
] as const satisfies readonly { key: CertificateKey; file: string; seal: string; width: number; height: number }[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Greator-Speaker-Zertifikat für die Sektion „Über Anelia“. Alle drei Dateien müssen
|
* Greator-Zertifikate für die Sektion „Über Anelia“. Ein Zertifikat erscheint nur,
|
||||||
* liegen – fehlt eine, rendert die Sektion wie vorher (Variante B, wie bei den Fotos).
|
* wenn alle drei Dateien liegen – fehlen alle, rendert die Sektion wie vorher
|
||||||
|
* (Variante B, wie bei den Fotos).
|
||||||
*/
|
*/
|
||||||
export function getCertificate(): CertificateAsset | null {
|
export function getCertificates(): CertificateAsset[] {
|
||||||
const pdf = publicUrl("zertifikate/greator-speaker.pdf");
|
return CERTIFICATES.flatMap(({ key, file, seal: sealFile, width, height }) => {
|
||||||
const image = publicUrl("zertifikate/greator-speaker.jpg");
|
const pdf = publicUrl(`zertifikate/${file}.pdf`);
|
||||||
const seal = publicUrl("zertifikate/greator-siegel.webp");
|
const image = publicUrl(`zertifikate/${file}.jpg`);
|
||||||
if (!pdf || !image || !seal) return null;
|
const seal = publicUrl(`zertifikate/${sealFile}`);
|
||||||
return { pdf, image, width: 989, height: 1400, seal, sealSize: 372 };
|
if (!pdf || !image || !seal) return [];
|
||||||
|
return [{ key, pdf, image, width, height, seal, sealSize: 372 }];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user