130 lines
5.8 KiB
TypeScript
130 lines
5.8 KiB
TypeScript
import { cn } from "@/lib/utils";
|
||
|
||
export interface CouponContent {
|
||
/** Mono-Caps über dem Satz, z. B. „Ihr Messe-Special“. */
|
||
kicker: string;
|
||
/** Der Nachlass in Display-Ziffern, z. B. „−20“. */
|
||
rate: string;
|
||
/** Einheit neben der Zahl, z. B. „%“. */
|
||
unit: string;
|
||
/** Worauf er gilt – der eine Satz, der den Gutschein trägt. */
|
||
on: string;
|
||
/** Was drinsteckt. Eine Zeile je Leistung, mit goldenem Strich davor. */
|
||
includes: readonly string[];
|
||
/** Frist und Stichwort im Abriss unter der Perforation. */
|
||
valid: { label: string; value: string };
|
||
code: { label: string; value: string };
|
||
}
|
||
|
||
export interface CouponProps {
|
||
content: CouponContent;
|
||
className?: string;
|
||
}
|
||
|
||
const MONO = "font-mono text-xs tracking-[0.06em] tabular-nums";
|
||
|
||
/**
|
||
* Der Grund eines Gutscheins: blasses Gold, goldene Haarlinie, runde Ecken.
|
||
*
|
||
* Exportiert, weil zwei Bauteile denselben Gegenstand darstellen – der große
|
||
* Gutschein im Hero und der kleine Hinweis über dem Kontaktformular, dass der
|
||
* Vorteil schon anhängt. Sie müssen gleich aussehen, also gibt es die Werte
|
||
* einmal. Die runden Ecken sind die ausdrückliche Ausnahme von „Radius 0“
|
||
* (Kundenwunsch 10.09.2026) und gelten nur für diese beiden Stellen.
|
||
*/
|
||
export const COUPON_SURFACE = "rounded-[14px] border border-gold-700 bg-gold-100 text-ink";
|
||
|
||
/**
|
||
* Die Reißkante: gestrichelte Haarlinie über die volle Breite, an beiden Enden
|
||
* eine Kerbe. Die Kerben sind auf die Ecke gestellte Quadrate in der Grundfarbe
|
||
* des umgebenden Abschnitts (`bg-bg`) – sie sehen aus wie gestanzt und kommen
|
||
* ohne einen einzigen Eckenradius aus. Auf hellem Grund elfenbein, auf dunklem
|
||
* tintenschwarz, ganz von selbst.
|
||
*/
|
||
export function CouponPerforation() {
|
||
return (
|
||
<div aria-hidden="true" className="relative border-t border-dashed border-gold-700">
|
||
<span className="absolute left-0 top-0 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rotate-45 bg-bg" />
|
||
<span className="absolute right-0 top-0 h-3.5 w-3.5 translate-x-1/2 -translate-y-1/2 rotate-45 bg-bg" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Der Messe-Gutschein (/her-career/, Sektion 01).
|
||
*
|
||
* Er soll als Gutschein erkennbar sein, ohne aus dem Haus-Stil zu fallen –
|
||
* also kein abgerundetes Ticket mit Schatten und Fremdschrift, sondern die
|
||
* drei Merkmale, die ein Gutschein wirklich braucht, in der Sprache der Seite:
|
||
*
|
||
* 0. Runde Ecken. Die einzige Stelle der Website mit einem Eckenradius
|
||
* (Projektregel ist sonst Radius 0, ausdrückliche Ausnahme vom 10.09.2026):
|
||
* Der Gutschein stellt einen Gegenstand dar, kein Interface-Element, und
|
||
* ein Gutschein mit scharfen Ecken sieht aus wie eine Tabelle. Der Radius
|
||
* bleibt auf dieses eine Bauteil beschränkt – Knöpfe, Rahmen und Tafeln
|
||
* ringsum sind weiter eckig.
|
||
* 1. Ein eigener Grund. `--gold-100` ist der blasse Goldton der Palette –
|
||
* bis hierher nirgends als Fläche benutzt und damit frei für genau das
|
||
* eine Objekt, das sich vom Elfenbein abheben soll. Die Schrift darauf ist
|
||
* Tinte, nicht Gold: Gold auf Gold liest sich nicht, und goldene Schrift
|
||
* bleibt ohnehin dem dunklen Grund vorbehalten.
|
||
* 2. Eine Reißkante. Gestrichelte Haarlinie über die volle Breite, an beiden
|
||
* Enden eine Kerbe. Die Kerben sind auf die Ecke gestellte Quadrate in der
|
||
* Grundfarbe der Sektion – gestanzt aussehende Löcher ohne einen einzigen
|
||
* Eckenradius.
|
||
* 3. Ein Abriss. Unter der Kante stehen Frist und Stichwort, das Stichwort im
|
||
* eigenen Kästchen wie ein Stempelfeld.
|
||
*
|
||
* Der Block steht immer auf `data-theme="light"`; die Kerben nehmen mit `bg-bg`
|
||
* den Grund der Sektion an.
|
||
*/
|
||
export function Coupon({ content, className }: CouponProps) {
|
||
const { kicker, rate, unit, on, includes, valid, code } = content;
|
||
|
||
return (
|
||
<div className={cn("relative", COUPON_SURFACE, className)}>
|
||
<div className="p-7 sm:p-12">
|
||
<p className={cn(MONO, "uppercase tracking-[0.16em] text-gold-900")}>{kicker}</p>
|
||
|
||
{/* Zahl und Einheit auf einer Grundlinie; die Einheit misst sich an der Zahl. */}
|
||
<p className="mt-5 flex items-baseline gap-[0.1em] font-display font-normal leading-[0.82] tracking-[-0.02em] [font-variant-numeric:lining-nums]">
|
||
<span className="text-[clamp(4.5rem,2.2rem+9vw,8.5rem)]">{rate}</span>
|
||
<span className="text-[clamp(1.75rem,1rem+3vw,3rem)]">{unit}</span>
|
||
</p>
|
||
|
||
<p className="mt-6 max-w-[22ch] font-display text-[clamp(1.75rem,1.35rem+1.5vw,2.75rem)] font-normal leading-[1.08] tracking-[-0.01em] [text-wrap:balance]">
|
||
{on}
|
||
</p>
|
||
|
||
{/* Dieselben Schienen wie <Facts variant="stack"/>: Strich | Text. */}
|
||
<ul className={cn(MONO, "mt-9 grid gap-y-3")}>
|
||
{includes.map((item) => (
|
||
<li key={item} className="grid grid-cols-[1.5rem_1fr] gap-x-3">
|
||
<span aria-hidden="true" className="mt-[0.55em] h-px w-6 self-start bg-gold-900" />
|
||
<span>{item}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<CouponPerforation />
|
||
|
||
{/* Der Abriss: Frist links, Stichwort rechts im Stempelfeld. */}
|
||
<dl className={cn(MONO, "flex flex-wrap items-center justify-between gap-x-10 gap-y-4 p-7 sm:px-12 sm:py-8")}>
|
||
<div className="flex items-baseline gap-2">
|
||
<dt className="text-gold-900">{valid.label}</dt>
|
||
<dd>{valid.value}</dd>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<dt className="text-gold-900">{code.label}</dt>
|
||
{/* Kein `uppercase`: „herCAREER“ ist gemischt geschrieben, Versalien
|
||
machten daraus ein „HERCAREER“, das die Messe so nicht führt. */}
|
||
<dd className="rounded-[6px] border border-gold-700 px-3 py-1.5 tracking-[0.14em]">{code.value}</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Coupon;
|