import type { Locale } from "@/i18n/routes"; export type ProjectSlug = "w-make-batch" | "batchmaker-studio"; export type ProjectImage = { src: string; de: string; en: string; }; export type LocalizedProject = { slug: ProjectSlug; number: string; href?: string; images: ProjectImage[]; status: string; label: string; title: string; summary: string; stance: string; stack: string; role: string; situation: string; decisions: string[]; outcome: string; proof: string; lesson: string; stages: { title: string; text: string }[]; }; const projects: Record; en: Omit }> = { "w-make-batch": { number: "01", href: "https://batch.w-make.com", images: [ { src: "/portfolio/batch-start.jpg", de: "Dashboard: Schicht, Alarme und der Weg in Satzzettel und Protokoll.", en: "Dashboard: shift, alerts, and the path into tickets and the daily log." }, { src: "/portfolio/batch-satzzettel.jpg", de: "Satzzettel mit Rezept, Zielsatz und sichtbarer Redox-Lage.", en: "Batch ticket with recipe, target weight, and visible redox state." }, { src: "/portfolio/batch-rezepte.jpg", de: "Rezeptarbeitsplatz: Versionen, Temperatur und Kosten als Stammdaten.", en: "Recipe workspace: versions, temperature, and cost as master data." }, { src: "/portfolio/batch-tagesprotokoll.jpg", de: "Tagesprotokoll mit Schichtübergabe und Tagesabschluss.", en: "Daily log with shift handover and day close." }, ], de: { status: "Öffentliche Produkt-Demo", label: "Industrie-Software · Produktentwicklung", title: "W-Make Batch", summary: "Batchmanagement für die Behälterglas-Produktion — vom Satzzettel bis zum Tagesprotokoll.", stance: "Das Produktionsprodukt. Eine öffentliche Demo zeigt den Ablauf, nicht den Kundenstand.", stack: "Node.js · Express · SQLite · Web Components · native Tests · Playwright", role: "Domänenmodell, Full-Stack-Entwicklung und technische Qualität.", situation: "Ein Produktionsprozess lebt von Rezepten, Materialverbrauch, Silos, Schichten und Berechnungen. Wird das als lose Sammlung von Masken gebaut, gehen fachliche Zusammenhänge verloren — und Buchungen werden zu editierbaren Tabellenzeilen.", decisions: [ "Fachliche Buchungen sind Zustände, keine CRUD-Datensätze zum beliebigen Löschen.", "Der Satzzettel und das Tagesprotokoll gehören in dasselbe Modell, nicht in getrennte Tools.", "Berechnungen und Materialflüsse bleiben nachvollziehbar, auch wenn die Oberfläche schlank bleibt.", ], outcome: "Ein betreibbares System mit öffentlicher Demo. Vertrauliche Kundendaten, Mengen und interne Kennzahlen bleiben bewusst außen vor.", proof: "Öffentliche Produkt-Demo unter batch.w-make.com. Die Case Study beschreibt das Modell, nicht den Kundenstand.", lesson: "Ein Produktionssystem wird besser, wenn die fachliche Irreversibilität Teil der Architektur ist — nicht eine Validierung im Formular.", stages: [ { title: "Rezept", text: "Zusammensetzung und Version bleiben mit dem späteren Verbrauch verbunden." }, { title: "Satz", text: "Der Satzzettel ist ein fachlicher Auftrag, kein Ausdruck einer Tabelle." }, { title: "Protokoll", text: "Der Tag schließt als nachvollziehbarer Zustand, nicht als exportierte Liste." }, ], }, en: { status: "Public product demo", label: "Industrial software · product development", title: "W-Make Batch", summary: "Batch management for container-glass production — from the batch ticket to the daily log.", stance: "The production product. A public demo shows the flow, not a client installation.", stack: "Node.js · Express · SQLite · Web Components · native tests · Playwright", role: "Domain model, full-stack development, and technical quality.", situation: "A production process lives on recipes, material use, silos, shifts, and calculations. Built as a loose set of screens, the domain relationships disappear — and bookings become editable table rows.", decisions: [ "Domain bookings are states, not CRUD records that can be deleted at will.", "The batch ticket and the daily log belong in one model, not in separate tools.", "Calculations and material flows stay traceable even when the interface stays lean.", ], outcome: "An operable system with a public demo. Confidential client data, volumes, and internal metrics stay unpublished.", proof: "Public product demo at batch.w-make.com. This case study describes the model, not a client installation.", lesson: "A production system improves when domain irreversibility is part of the architecture — not a form validation.", stages: [ { title: "Recipe", text: "Composition and version stay tied to later consumption." }, { title: "Batch", text: "The ticket is a domain order, not a printout of a table." }, { title: "Log", text: "The day closes as a traceable state, not an exported list." }, ], }, }, "batchmaker-studio": { number: "02", images: [ { src: "/portfolio/batchmaker-recipes.jpg", de: "Rezeptarbeitsplatz von Batchmaker Studio.", en: "Batchmaker Studio recipe workspace." }, { src: "/portfolio/batchmaker-validation.jpg", de: "Validierung als eigener Schritt.", en: "Validation as a dedicated step." }, { src: "/portfolio/batchmaker-settings.jpg", de: "Konfiguration für Redox, Kalibrierung und Oxide.", en: "Configuration for redox, calibration, and oxides." }, ], de: { status: "Eigenständig betreibbares Produkt", label: "Standalone · Recipe Engineering", title: "Batchmaker Studio", summary: "Schlankes Recipe-Studio für Rezepte, Rohstoffe, Satzzettel und Glaschemie.", stance: "Die Werkbank am Schreibtisch. Derselbe Fachkern, bewusst ohne Silos, Schichten und laufende Buchungen.", stack: "Node.js · Express · node:sqlite · Vanilla JS · Vite · native Tests", role: "Produktarchitektur, Full-Stack-Entwicklung und Calculation Engine.", situation: "Rezeptarbeit braucht denselben fachlichen Kern wie die Produktion — aber nicht Silos, Schichten und laufende Buchungen. Ein volles Betriebssystem ist für die Entwicklung am Schreibtisch zu schwer.", decisions: [ "Derselbe Domänenkern, bewusst kleinerer Zuschnitt.", "Eigener Server, eigene SQLite-Datei, eigene schlanke Oberfläche.", "Validierung als sichtbarer Schritt, nicht als versteckte Nebenwirkung.", ], outcome: "Ein eigenständig betreibbares Werkzeug für Rezeptentwicklung, Glaschemie und druckfertige Satzzettel. Keine unbelegten Effizienzversprechen.", proof: "Öffentliche Case Study und der bewusst getrennte Produktzuschnitt gegenüber W-Make Batch.", lesson: "Produktgrenzen sind Architektur. Ein gutes Werkzeug muss den Nachbarprozess nicht vollständig abbilden.", stages: [ { title: "Rezepte", text: "Versionieren, vergleichen, importieren und exportieren." }, { title: "Glaschemie", text: "Oxide, Redox, Physik und nachvollziehbare Berechnungen." }, { title: "Satzzettel", text: "Entwurf, Skalierung, Vorschau, druckfertige Ausgabe." }, ], }, en: { status: "Independently operable product", label: "Standalone · recipe engineering", title: "Batchmaker Studio", summary: "A lean recipe studio for recipes, raw materials, batch tickets, and glass chemistry.", stance: "The desk-side workbench. The same domain core, deliberately without silos, shifts, and live bookings.", stack: "Node.js · Express · node:sqlite · Vanilla JS · Vite · native tests", role: "Product architecture, full-stack development, and calculation engine.", situation: "Recipe work needs the same domain core as production — but not silos, shifts, and live bookings. A full operations system is too heavy for desk-side development.", decisions: [ "The same domain core, deliberately smaller scope.", "Its own server, SQLite file, and lean browser interface.", "Validation as a visible step, not a hidden side effect.", ], outcome: "An independently operable tool for recipe work, glass chemistry, and print-ready batch tickets. No unverifiable efficiency claims.", proof: "This public case study and the deliberate product boundary against W-Make Batch.", lesson: "Product boundaries are architecture. A good tool does not have to model the neighbouring process in full.", stages: [ { title: "Recipes", text: "Version, compare, import, and export." }, { title: "Chemistry", text: "Oxides, redox, physics, and traceable calculations." }, { title: "Tickets", text: "Draft, scale, preview, print-ready output." }, ], }, }, }; export const projectSlugs = Object.keys(projects) as ProjectSlug[]; export function getProject(slug: string, locale: Locale): LocalizedProject | null { if (!(slug in projects)) return null; const entry = projects[slug as ProjectSlug]; return { slug: slug as ProjectSlug, number: entry.number, href: entry.href, images: entry.images, ...entry[locale], }; } export function getProjects(locale: Locale): LocalizedProject[] { return projectSlugs.map((slug) => getProject(slug, locale)!); }