feat: extract shop from mp/shop — initial libreshop/shop
Some checks failed
Build and publish / build (push) Failing after 19s

Source moved verbatim from mp/shop/ on 2026-04-29; mp was the first
concrete adapter consuming the libreshop toolkit. Builds and publishes
git.librete.ch/libreshop/shop on every main / v* push via the standard
.gitea/workflows/build.yml shared across libreshop components.
This commit is contained in:
Michael Czechowski
2026-04-29 17:48:56 +02:00
commit 44107c0734
134 changed files with 19521 additions and 0 deletions

View File

@@ -0,0 +1,369 @@
// Product content keyed by cover type
// This content is displayed on product detail pages as storytelling modules
export type CoverType = "Hardcover" | "Softcover" | "Heft" | "Spiralbindung";
export interface FeatureModule {
eyebrow?: string;
headline: string;
subtitle?: string;
body: string;
image?: string;
imageRight?: boolean;
}
export interface UseCase {
icon: "pen" | "palette" | "clipboard" | "briefcase";
title: string;
description: string;
}
// =============================================================================
// FEATURE MODULES - Storytelling sections per cover type
// =============================================================================
export const FEATURE_MODULES: Record<CoverType, FeatureModule[]> = {
Hardcover: [
{
eyebrow: "Handarbeit",
headline: "Klassische Fadenheftung",
subtitle: "Strapazierfähig bei intensivem Gebrauch",
body: `Jedes Heft wird mit Singer-Stich-Heftung von Hand gebunden.
Das Ergebnis: Ein Notizbuch, das sich vollständig flach öffnen
lässt und auch nach Jahren intensiver Nutzung nicht auseinanderfällt.
Der rote Vorsatzbogen setzt einen dezenten Farbakzent
Deine erste Begegnung beim Öffnen.`,
image: "/images/features/hardcover/binding-closeup.jpg",
imageRight: false,
},
{
eyebrow: "100% Recycling",
headline: "VIVUS 89 Papier",
subtitle: "Außergewöhnliche Schreibqualität",
body: `VIVUS 89 ist nicht irgendein Recyclingpapier.
Die matte, ungestrichene Oberfläche mit feiner Textur verhindert
Durchschlagen selbst bei Füllfederhaltern und Aquarellfarben.
Mit 120g/qm und 1,2-fachem Volumen fühlt sich jede Seite
substanziell an, ohne starr zu wirken.`,
image: "/images/features/hardcover/paper-texture.jpg",
imageRight: true,
},
{
headline: "160 Seiten für Deine Ideen",
subtitle: "Raum für 3-6 Monate intensive Arbeit",
body: `160 Seiten bedeuten genug Platz für ein abgeschlossenes Projekt,
eine komplette Reise, oder ein Quartal Deines Bullet Journals.
Blanko-Seiten geben Dir absolute Freiheit: Skizzieren, Schreiben,
Collagieren ohne Raster, das Deine Kreativität einschränkt.`,
image: "/images/features/hardcover/lifestyle-desk.jpg",
imageRight: false,
},
{
headline: "Kein Heft gleicht dem anderen",
subtitle: "13 einzigartige Mustervarianten",
body: `Jeder Einband wird individuell mit variierenden geometrischen
Mustern bedruckt. Die Kombination aus mattem Schwarz (300g/qm Karton)
und den Mustern macht Dein Heft unverwechselbar.`,
image: "/images/features/hardcover/cover-pattern.jpg",
imageRight: true,
},
{
eyebrow: "Made in Stuttgart",
headline: "Transparente Lieferkette",
subtitle: "Messbare Nachhaltigkeit",
body: `<strong>100% Recyclingpapier</strong> (FSC® C018175)<br>
<strong>CO₂-neutral</strong> produziert<br>
<strong>Blauer Engel + EU Ecolabel</strong> zertifiziert<br>
<strong>Hergestellt in Stuttgart</strong>, Deutschland<br><br>
Keine langen Transportwege, keine Ausbeutung, kein Greenwashing.
Nur ehrliches Handwerk mit messbaren Umweltstandards.`,
image: "/images/production/04.png",
imageRight: false,
},
],
Softcover: [
{
eyebrow: "Leicht & Flexibel",
headline: "Der tägliche Begleiter",
subtitle: "Passt in jede Tasche",
body: `Das Softcover ist Dein unkomplizierter Alltagsbegleiter.
Leicht, flexibel und robust perfekt für unterwegs.
Die weiche Hülle schmiegt sich an und übersteht jeden Rucksack.`,
image: "/images/features/softcover/flexible-cover.jpg",
imageRight: false,
},
{
eyebrow: "100% Recycling",
headline: "VIVUS 89 Papier",
subtitle: "Dieselbe Qualität, leichteres Format",
body: `Auch im Softcover kommt unser bewährtes VIVUS 89 Recyclingpapier
zum Einsatz. 120g/qm für optimale Schreibqualität ohne Durchschlagen.
Perfekt für Füllfederhalter, Fineliner und Aquarellstifte.`,
image: "/images/features/softcover/paper-writing.jpg",
imageRight: true,
},
{
headline: "Praktische Steppstich-Bindung",
subtitle: "Liegt flach, bleibt offen",
body: `Die Steppstich-Bindung ermöglicht ein vollständiges Aufklappen.
Ideal zum Schreiben, Zeichnen und für alle, die beide Seiten
gleichzeitig nutzen möchten.`,
image: "/images/features/softcover/binding-detail.jpg",
imageRight: false,
},
{
eyebrow: "Made in Stuttgart",
headline: "Nachhaltig produziert",
subtitle: "Lokale Fertigung, globale Standards",
body: `<strong>FSC® zertifiziert</strong><br>
<strong>Blauer Engel</strong> Umweltzeichen<br>
<strong>CO₂-neutral</strong> hergestellt<br><br>
Kurze Wege, faire Produktion, messbare Nachhaltigkeit.`,
image: "/images/features/softcover/workshop.png",
imageRight: true,
},
],
Heft: [
{
eyebrow: "Kompakt & Praktisch",
headline: "Das klassische Notizheft",
subtitle: "Für schnelle Notizen und Ideen",
body: `Manchmal braucht man kein dickes Notizbuch sondern ein
handliches Heft für den Moment. Perfekt für Meeting-Notizen,
Einkaufslisten oder spontane Skizzen.`,
image: "/images/features/heft/compact-size.jpg",
imageRight: false,
},
{
eyebrow: "100% Recycling",
headline: "Qualität im Kleinformat",
subtitle: "VIVUS 89 auch im Heft",
body: `Unser Recyclingpapier macht auch im kleinen Format keine
Kompromisse. Die gleiche Schreibqualität, die Du von unseren
größeren Notizbüchern kennst.`,
image: "/images/features/heft/paper-quality.jpg",
imageRight: true,
},
{
headline: "Ruckzuck-Heftung",
subtitle: "Einfach, aber solide",
body: `Die klassische Rückstich-Heftung hält Dein Heft zusammen
und ermöglicht ein flaches Aufklappen. Bewährt seit Generationen,
nachhaltig für die Zukunft.`,
image: "/images/features/heft/binding.jpg",
imageRight: false,
},
],
Spiralbindung: [
{
eyebrow: "360° Flexibilität",
headline: "Wire-O-Bindung",
subtitle: "Komplett umklappbar",
body: `Die Spiralbindung lässt sich vollständig umklappen
ideal für beengte Arbeitsflächen. Schreibe auf einer Seite,
während die andere flach auf dem Tisch liegt.`,
image: "/images/features/spiral/360-flip.jpg",
imageRight: false,
},
{
eyebrow: "100% Recycling",
headline: "VIVUS 89 Papier",
subtitle: "Premium-Qualität, flexibles Format",
body: `Unser bewährtes Recyclingpapier in der praktischen
Spiralbindung. 120g/qm verhindern Durchschlagen
auch bei intensiver Nutzung mit verschiedenen Stiften.`,
image: "/images/features/spiral/paper-texture.jpg",
imageRight: true,
},
{
headline: "Seiten heraustrennbar",
subtitle: "Perforation für sauberes Abtrennen",
body: `Jede Seite lässt sich sauber heraustrennen perfekt,
wenn Du Notizen weitergeben oder Skizzen verschenken möchtest.
Die Mikroperforation sorgt für glatte Kanten.`,
image: "/images/features/spiral/tear-out.jpg",
imageRight: false,
},
{
eyebrow: "Made in Stuttgart",
headline: "Nachhaltig & Praktisch",
subtitle: "Das Beste aus beiden Welten",
body: `<strong>Recycling-Drahtbindung</strong><br>
<strong>FSC® zertifiziertes Papier</strong><br>
<strong>CO₂-neutral</strong> produziert<br><br>
Funktionalität trifft Nachhaltigkeit.`,
image: "/images/features/spiral/workshop.jpg",
imageRight: true,
},
],
};
// =============================================================================
// TECHNICAL SPECS - Collapsible details per cover type
// =============================================================================
export const TECHNICAL_SPECS: Record<CoverType, Record<string, string>> = {
Hardcover: {
Format: "150 × 210 mm (A5)",
Seitenanzahl: "160 Seiten (80 Blatt)",
Papier: "VIVUS 89, 120g/qm, 100% Recycling",
Einband: "300g/qm Recyclingkarton",
Bindung: "Klassische Fadenheftung (Singer-Stich)",
Vorsatzpapier: "Rot durchgefärbt",
Zertifizierung: "FSC® C018175, Blauer Engel, EU Ecolabel",
Herstellung: "Stuttgart, Deutschland",
},
Softcover: {
Format: "150 × 210 mm (A5)",
Seitenanzahl: "96 Seiten (48 Blatt)",
Papier: "VIVUS 89, 120g/qm, 100% Recycling",
Einband: "250g/qm Recyclingkarton, flexibel",
Bindung: "Steppstich-Heftung",
Zertifizierung: "FSC® C018175, Blauer Engel, EU Ecolabel",
Herstellung: "Stuttgart, Deutschland",
},
Heft: {
Format: "148 × 210 mm (A5)",
Seitenanzahl: "20, 40 oder 60 Seiten",
Papier: "VIVUS 89, 120g/qm, 100% Recycling",
Einband: "200g/qm Recyclingkarton",
Bindung: "Rückstich-Heftung",
Zertifizierung: "FSC® C018175, Blauer Engel",
Herstellung: "Stuttgart, Deutschland",
},
Spiralbindung: {
Format: "150 × 210 mm (A5)",
Seitenanzahl: "120 Seiten (60 Blatt)",
Papier: "VIVUS 89, 120g/qm, 100% Recycling",
Einband: "300g/qm Recyclingkarton",
Bindung: "Wire-O-Bindung (Doppeldraht)",
Perforation: "Mikroperforation zum Heraustrennen",
Zertifizierung: "FSC® C018175, Blauer Engel, EU Ecolabel",
Herstellung: "Stuttgart, Deutschland",
},
};
// =============================================================================
// USE CASES - Same for all products
// =============================================================================
export const USE_CASES: UseCase[] = [
{
icon: "pen",
title: "Schreibprojekte",
description: "Romane, Tagebuch, Morning Pages",
},
{
icon: "palette",
title: "Mixed-Media",
description: "Aquarell, Collage, Skizzen",
},
{
icon: "clipboard",
title: "Sketchbook",
description: "Layouts ohne Raster",
},
{
icon: "briefcase",
title: "Arbeit & Studium",
description: "Meetings, Forschung, Konzepte",
},
];
// =============================================================================
// HELPER: Get cover type from product.cover.name
// =============================================================================
export function getCoverType(coverName?: string): CoverType {
if (!coverName) return "Hardcover";
const name = coverName.toLowerCase();
if (name.includes("heft")) return "Heft";
if (name.includes("hardcover")) return "Hardcover";
if (name.includes("softcover")) return "Softcover";
if (name.includes("spiral") || name.includes("wire")) return "Spiralbindung";
return "Hardcover"; // fallback
}
// =============================================================================
// COMPOSABLE
// =============================================================================
import type { Product } from "~/types";
export function useProductContent(
coverName: Ref<string | undefined> | ComputedRef<string | undefined>,
product?: Ref<Product | null> | ComputedRef<Product | null>
) {
const coverType = computed(() => getCoverType(coverName.value));
const featureModules = computed(() => FEATURE_MODULES[coverType.value] ?? []);
// Derive technical specs from product data when available
const technicalSpecs = computed(() => {
const baseSpecs = TECHNICAL_SPECS[coverType.value] ?? {};
const prod = product?.value;
if (!prod) return baseSpecs;
const derivedSpecs: Record<string, string> = {};
// Format from copyText
if (prod.cover?.copyText?.format) {
derivedSpecs["Format"] = prod.cover.copyText.format;
}
// Seitenanzahl from pages
if (prod.pages?.name) {
derivedSpecs["Seitenanzahl"] = prod.pages.name;
} else if (prod.pages?.count) {
const sheets = Math.floor(prod.pages.count / 2);
derivedSpecs["Seitenanzahl"] = `${prod.pages.count} Seiten (${sheets} Blatt)`;
}
// Parse paper string: "VIVUS 89: 100% Recyclingpapier, ... Inhalt: 120g/qm, Einband: 300g/qm. Zertifizierung: ..."
if (prod.cover?.copyText?.paper) {
const paperText = prod.cover.copyText.paper;
// Extract paper name and description (before "Inhalt:")
const inhaltMatch = paperText.match(/^(.+?)(?:\s*Inhalt:|$)/);
if (inhaltMatch) {
derivedSpecs["Papier"] = inhaltMatch[1].trim().replace(/,\s*$/, "");
}
// Extract Inhalt (paper weight)
const inhaltWeightMatch = paperText.match(/Inhalt:\s*([^,]+)/);
if (inhaltWeightMatch) {
derivedSpecs["Papiergewicht"] = inhaltWeightMatch[1].trim();
}
// Extract Einband (cover weight)
const einbandMatch = paperText.match(/Einband:\s*([^.]+)/);
if (einbandMatch) {
derivedSpecs["Einband"] = einbandMatch[1].trim();
}
// Extract Zertifizierung
const zertMatch = paperText.match(/Zertifizierung:\s*(.+)$/);
if (zertMatch) {
derivedSpecs["Zertifizierung"] = zertMatch[1].trim();
}
}
// Bindung from cover binding
if (prod.cover?.binding) {
derivedSpecs["Bindung"] = prod.cover.binding;
}
// Merge: product data overrides base specs
return { ...baseSpecs, ...derivedSpecs };
});
return {
coverType,
featureModules,
technicalSpecs,
useCases: USE_CASES,
};
}