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 über den Siloabzug bis zum Tagesprotokoll.", stance: "Das Betriebsprodukt. Die ö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: [ "In der Behälterglas-Produktion ist der Tag eine Kette. Eine Rezeptversion gilt, ein Satzzettel geht an die Waage, der Verbrauch geht vom Silo ab, Oxid und Redox müssen zur Wanne passen, die Schicht übergibt, der Tag schließt. Bricht eines dieser Glieder, stehen am nächsten Morgen drei Zahlen im Raum, die nicht zusammenpassen.", "Wird das als lose Sammlung von Masken gebaut, kopiert jemand die Werte von einem Blatt auf das nächste. Der Satzzettel sagt etwas anderes als der Silostand, das Protokoll etwas anderes als beides. Die schnellste Korrektur in Software ist dann das Löschen oder Überschreiben einer Zeile. Nach dem Wiegen ist das bereits eine Lüge gegenüber Bestand und Schicht.", "Dazu kommt der Druck der Schichtübergabe. Was mündlich nachgereicht wird, steht nirgendwo. Wer morgens übernimmt, erbt eine Halle und eine Tabelle, die sich widersprechen.", ], decisions: [ "Fachliche Buchungen sind Zustände. Eine Korrektur nach dem Siloabzug ist eine Gegenbuchung, ein Storno oder ein Abgleich, kein DELETE auf der Zeile.", "Satzzettel, Gemengebuch und Tagesprotokoll gehören in dasselbe Modell. Zwei Tools an dieser Naht erzeugen denselben Papierbruch, den die Software ersetzen sollte.", "Oxid, Redox, Satzgröße und Kosten bleiben an der Rezeptversion hängen und liegen in eigenen Modulen. Die Oberfläche zeigt die Zahl, sie erfindet sie nicht.", "Schichtbeginn ist eine gemeinsame Uhr, kein voreingestelltes Feld in einem Formular. Ohne diese Uhr driftet der Tag zwischen Stationen.", "Migrationen, Sicherung und ein Abschluss, den man wiederholen kann, gehören zum Produkt. Ein System, das nur in der Demo schließt, schließt in der Halle nicht.", "Die öffentliche Demo zeigt das Modell. Kundendaten, Mengen und interne Kennzahlen bleiben außen vor.", ], outcome: [ "Ein betreibbares System mit öffentlicher Demo unter batch.w-make.com. Man kann den Weg von der Rezeptversion über den Satzzettel in das Tagesprotokoll nachgehen, ohne einen Kundenstand zu sehen.", "Vertrauliche Hallendaten, Rezepte und Mengen stehen nicht auf dieser Site. Es gibt keine veröffentlichten Durchsatzzahlen, keine Verfügbarkeitsversprechen und keine Kundennamen.", ], 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 Unumkehrbarkeit im Datenmodell steckt. Eine Validierung im Formular kommt zu spät, sobald Silo und Protokoll schon geschrieben haben.", stages: [ { title: "Rezept", text: "Zusammensetzung, Version, Temperatur und Kosten bleiben mit dem späteren Verbrauch verbunden." }, { title: "Satzzettel", text: "Der Zettel ist der Auftrag an die Waage, kein Ausdruck einer Tabelle." }, { title: "Silo", text: "Der Verbrauch geht vom Bestand ab. Danach ist die Buchung Teil der Halle." }, { title: "Gemengebuch", text: "Was eingewogen wurde, bleibt an Satz und Rezeptversion gebunden." }, { title: "Protokoll", text: "Der Tag schließt als Zustand, den die nächste Schicht vorfindet, 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 through silo deduction to the daily log.", stance: "The operations product. The 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: [ "In container-glass production the day is a chain. A recipe version holds, a batch ticket goes to the scale, consumption leaves the silo, oxide and redox have to match the furnace, the shift hands over, the day closes. If one link breaks, three figures that do not agree are waiting the next morning.", "Built as a loose set of screens, someone copies values from one sheet to the next. The ticket says one thing, the silo another, the log a third. The fastest correction in software is then to delete or overwrite a row. After weighing, that is already a lie to stock and to the shift.", "Shift handover adds pressure. What is passed on verbally is written nowhere. Whoever takes over in the morning inherits a hall and a table that contradict each other.", ], decisions: [ "Domain bookings are states. A correction after silo deduction is a reversing entry, a cancellation or a reconcile, not a DELETE on the row.", "Batch ticket, batch book and daily log belong in one model. Two tools at that seam recreate the paper fracture the software was meant to replace.", "Oxide, redox, batch size and cost stay bound to the recipe version and live in their own modules. The interface shows the figure. It does not invent it.", "Shift start is a shared clock, not a default in a form field. Without that clock the day drifts between stations.", "Migrations, backup and a close you can repeat belong to the product. A system that only closes in the demo will not close on the floor.", "The public demo shows the model. Client data, volumes and internal metrics stay off the site.", ], outcome: [ "An operable system with a public demo at batch.w-make.com. You can follow the path from recipe version through the batch ticket into the daily log without seeing a client installation.", "Confidential plant data, recipes and volumes are not on this site. There are no published throughput figures, no uptime claims and no client names.", ], 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 lives in the data model. A form validation comes too late once silo and log have already been written.", stages: [ { title: "Recipe", text: "Composition, version, temperature and cost stay tied to later consumption." }, { title: "Ticket", text: "The ticket is the order to the scale, not a printout of a table." }, { title: "Silo", text: "Consumption leaves the stock. After that the booking is part of the hall." }, { title: "Batch book", text: "What was weighed stays bound to the batch and the recipe version." }, { title: "Log", text: "The day closes as a state the next shift will find, not as 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, ohne Silos und ohne laufende Schichten.", stance: "Die Werkbank am Schreibtisch. Derselbe Fachkern wie W-Make Batch, ohne den Betrieb der Halle.", 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: Rohstoffe, Oxidrechnung, Redox, Satzgröße, Kosten, Versionen. Sie braucht nicht Silos, Schichten und laufende Buchungen. Ein volles Betriebssystem ist für die Entwicklung am Schreibtisch zu schwer. Man will eine Rezeptversion ändern, die Chemie prüfen und einen Satzzettel drucken, ohne eine Schicht zu eröffnen.", "Wer beides in ein Produkt zwingt, belastet die Werkbank mit Hallenregeln und die Halle mit Entwurfszuständen. Entwürfe geraten in den Bestand, oder der Bestand wird am Schreibtisch nachgebaut. Beides ist teuer.", ], decisions: [ "Derselbe Fachkern für Rezept, Rohstoff und Glaschemie, kleinerer Zuschnitt: keine Silos, keine Schichten, keine laufenden Buchungen.", "Eigener Server, eigene SQLite-Datei, eigene schlanke Oberfläche. Studio hängt nicht am Betriebsprodukt.", "Validierung ist ein sichtbarer Schritt. Eine Rechnung, die still im Speichern mitläuft, wird später niemand mehr finden.", "Satzzettel entstehen hier als Entwurf und Druckvorlage. Gebucht wird erst in W-Make Batch.", "Keine unbelegten Effizienzversprechen. Das Produkt ist die Werkbank, nicht ein Ersatz für den Betrieb.", ], outcome: [ "Ein eigenständig betreibbares Werkzeug für Rezeptentwicklung, Glaschemie und druckfertige Satzzettel. Man kann am Schreibtisch arbeiten, ohne eine Schicht zu simulieren.", "Die Grenze zu W-Make Batch bleibt sichtbar. Was in Studio entworfen wird, wird in Batch zum Auftrag. Die Case Study belegt den Zuschnitt, nicht einen Kundenstand.", ], proof: "Öffentliche Case Study und der getrennte Produktzuschnitt gegenüber W-Make Batch.", lesson: "Produktgrenzen sind Architektur. Ein gutes Werkzeug muss den Nachbarprozess nicht vollständig abbilden. Wer Studio und Betrieb vermischt, trägt Entwürfe in den Bestand oder den Bestand an den Schreibtisch.", stages: [ { title: "Rezepte", text: "Versionieren, vergleichen, importieren und exportieren, gebunden an Rohstoffe und Kosten." }, { title: "Rohstoffe", text: "Stammdaten, aus denen Oxid und Satz später gerechnet werden." }, { title: "Glaschemie", text: "Oxide, Redox, Physik und Rechnungen, die man prüfen kann." }, { title: "Satzzettel", text: "Entwurf, Skalierung, Vorschau, druckfertige Ausgabe." }, { title: "Validierung", text: "Ein eigener Schritt, bevor eine Version als brauchbar gilt." }, ], }, 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, without silos and without live shifts.", stance: "The desk-side workbench. The same domain core as W-Make Batch, without the hall’s operations.", 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: raw materials, oxide calculation, redox, batch size, cost, versions. It does not need silos, shifts and live bookings. A full operations system is too heavy for desk-side development. You want to change a recipe version, check the chemistry and print a ticket without opening a shift.", "Force both into one product and the workbench inherits hall rules while the hall inherits draft states. Drafts leak into stock, or stock is rebuilt at the desk. Both are expensive.", ], decisions: [ "The same domain core for recipe, raw material and glass chemistry, smaller scope: no silos, no shifts, no live bookings.", "Its own server, SQLite file and lean browser interface. Studio does not hang off the operations product.", "Validation is a visible step. A calculation that runs quietly on save will not be found later.", "Batch tickets originate here as draft and print-ready output. Booking happens in W-Make Batch.", "No unverifiable efficiency claims. The product is the workbench, not a substitute for operations.", ], outcome: [ "An independently operable tool for recipe work, glass chemistry and print-ready batch tickets. You can work at the desk without simulating a shift.", "The boundary with W-Make Batch stays visible. What is drafted in Studio becomes an order in Batch. This case study documents the cut, not a client installation.", ], proof: "This public case study and the separate product boundary against W-Make Batch.", lesson: "Product boundaries are architecture. A good tool does not have to model the neighbouring process in full. Mix studio and operations and you carry drafts into stock, or stock onto the desk.", stages: [ { title: "Recipes", text: "Version, compare, import and export, bound to raw materials and cost." }, { title: "Materials", text: "Master data from which oxide and batch size are later calculated." }, { title: "Chemistry", text: "Oxides, redox, physics, and calculations you can inspect." }, { title: "Tickets", text: "Draft, scale, preview, print-ready output." }, { title: "Validation", text: "A dedicated step before a version counts as usable." }, ], }, }, }; 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)!); }