Setzt Teilprojekte 1-5 der Spec 2026-10-16 um: - Urkunden-Engine (lib/certificate): ein Layout fuer PDF, Vorschau und 3D-Textur, Vorlage classic@1, eingebettete OFL-Schriften, Testmatrix - Einmal-Patenschaft ueber Traegerprodukte (Kategorie PATENSCHAFT), Paket wird im Checkout in Patenschaft (Regelsatz) und Honig aufgeteilt, Kontopflicht, rein digitale Bestellungen (FulfillmentMethod DIGITAL) - Ausstellung nach Zahlung idempotent, Wiederholung im App-Prozess (instrumentation + background-jobs), Ablage in CERTIFICATE_DIR - Verkaufsseite /patenschaft mit Konfigurator und Live-Vorschau, Warenkorb mit Schluessel je Position - Kundenkonto: 3D-Ansicht von Urkunde und Medaille, Downloads, ZIP, Uebertragung per befristetem Einmal-Link, Treuestufen bis VIP - Admin: Angebote, Patenschaften, Druck, Korrektur mit Revisionen, Abos, Erntefenster, Deckung, SEPA-Frist, Treuestufen - Abo ueber Stripe Billing: Lieferung erst nach bezahlten Monaten, SEPA-Neukunden-Frist und Rueckbuchungsschutz, Honigreservierung, Kuendigungsbutton, Monatsrechnungen, Stripe-Einrichtungsskript Startangebote werden inaktiv angelegt; Livebetrieb erst nach Freigabe. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { CERT_COLORS, type ImageElement, type PathElement } from "@/lib/certificate/elements";
|
|
import { compose, polyline, scale, transformPath, translate, rect } from "@/lib/certificate/geometry";
|
|
|
|
/**
|
|
* Das Waben-Zeichen (components/logomark.tsx, lib/invoice-pdf-brand.ts) als
|
|
* Vektorpfade: Hexagon in Honiggold, darin drei Tannen in Waldgruen. Gleiche
|
|
* Geometrie wie im Web-Logo (viewBox 100). Die Strichstaerken skalieren mit,
|
|
* genau wie dort — das Zeichen sieht in jeder Groesse gleich aus.
|
|
*
|
|
* Wird gezeichnet, wenn kein Logo hochgeladen ist. Das hochgeladene Logo
|
|
* selbst wird nie nachgezeichnet oder umgefaerbt (Spec "Markengrundlage").
|
|
*/
|
|
|
|
const HEXAGON = polyline(
|
|
[
|
|
{ x: 50, y: 5 },
|
|
{ x: 88.97, y: 27.5 },
|
|
{ x: 88.97, y: 72.5 },
|
|
{ x: 50, y: 95 },
|
|
{ x: 11.03, y: 72.5 },
|
|
{ x: 11.03, y: 27.5 },
|
|
],
|
|
true
|
|
);
|
|
|
|
const TREES = [
|
|
{ x: 20, y: 45, scale: 0.7 },
|
|
{ x: 38, y: 32, scale: 0.95 },
|
|
{ x: 56, y: 45, scale: 0.7 },
|
|
] as const;
|
|
|
|
const TREE_PARTS = [
|
|
polyline([{ x: 10, y: 2 }, { x: 5, y: 13 }, { x: 15, y: 13 }], true),
|
|
polyline([{ x: 10, y: 10 }, { x: 2, y: 22 }, { x: 18, y: 22 }], true),
|
|
rect({ x: 8, y: 22, width: 4, height: 4 }),
|
|
];
|
|
|
|
/** Waben-Zeichen in einem Quadrat der Kantenlaenge `size` mit linker oberer Ecke (x, y). */
|
|
export function logomarkPaths(x: number, y: number, size: number): PathElement[] {
|
|
const unit = size / 100;
|
|
const place = compose(scale(unit), translate(x, y));
|
|
const hexagon: PathElement = {
|
|
kind: "path",
|
|
role: "logo",
|
|
d: transformPath(HEXAGON, place),
|
|
stroke: CERT_COLORS.honey,
|
|
strokeWidth: 2.5 * unit,
|
|
lineJoin: "miter",
|
|
};
|
|
const trees = TREES.map((tree): PathElement => {
|
|
const matrix = compose(scale(tree.scale), translate(tree.x, tree.y), place);
|
|
return {
|
|
kind: "path",
|
|
role: "logo",
|
|
d: TREE_PARTS.map((part) => transformPath(part, matrix)).join(""),
|
|
stroke: CERT_COLORS.forest,
|
|
strokeWidth: 1.4 * tree.scale * unit,
|
|
lineJoin: "round",
|
|
};
|
|
});
|
|
return [hexagon, ...trees];
|
|
}
|
|
|
|
/** Quadrat fuer das Waben-Zeichen innerhalb der Logo-Box, nach deren Ausrichtung. */
|
|
export function logoFallbackPaths(box: ImageElement): PathElement[] {
|
|
const size = Math.min(box.width, box.height);
|
|
const offset = box.align === "left" ? 0 : box.align === "right" ? box.width - size : (box.width - size) / 2;
|
|
return logomarkPaths(box.x + offset, box.y + (box.height - size) / 2, size);
|
|
}
|