feat: Bienenpatenschaft mit Urkunde, Abo, Medaillen und 3D-Konto
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]>
This commit is contained in:
1 parent
dcb578f062
commit
f227aeb8f9
269 files changed
+28054
-141
No files matched your search
@@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
Reference in new issue
Block a user