feat: Rename "Betrieb & Hosting" to "Sorglos-Paket" and refine PDF pricing module layout and text.
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 2m28s

This commit is contained in:
2026-02-05 01:53:12 +01:00
parent 3e10bca3ac
commit c5ad6108ee
9 changed files with 502 additions and 277 deletions

View File

@@ -4,7 +4,7 @@ Preise
Basis
6.000 € einmalig
4.000 € einmalig
Die Grundlage für jede Website:
• Projekt-Setup & Infrastruktur
@@ -23,7 +23,7 @@ Enthält keine Seiten, Inhalte oder Funktionen.
Seite
800 € / Seite
600 € / Seite
Individuell gestaltete Seite
mit Layout, Struktur, Textaufteilung, responsivem Design.
@@ -32,7 +32,7 @@ mit Layout, Struktur, Textaufteilung, responsivem Design.
Feature (System)
2.000 € / Feature
1.500 € / Feature
Ein in sich geschlossenes System mit Datenstruktur, Darstellung und Pflegefähigkeit.
@@ -50,7 +50,7 @@ Ein Feature erzeugt ein Datenmodell, Übersichten & Detailseiten.
Funktion (Logik)
1.000 € / Funktion
800 € / Funktion
Funktionen liefern Logik und Interaktion, z. B.:
• Kontaktformular
@@ -73,7 +73,7 @@ Jede Funktion ist ein klar umrissener Logikbaustein.
Visuelle Inszenierung
2.000 € / Abschnitt
1.500 € / Abschnitt
Erweiterte Gestaltung:
• Hero-Story
@@ -100,7 +100,7 @@ Dargestellte, interaktive UI-Erlebnisse:
Neuer Datensatz
400 € / Stück
200 € / Stück
Beispiele:
• Produkt
@@ -126,7 +126,7 @@ Datensatz anpassen
Hosting & Betrieb
12 Monate = 1440 €
12 Monate = 3.000 €
Sichert:
• Webhosting & Verfügbarkeit
@@ -195,7 +195,7 @@ Für:
API-Schnittstelle / Daten-Sync
1.000 € / Zielsystem
800 € / Zielsystem
Synchronisation zu externem System (Push):
• Produkt-Sync

View File

@@ -7,7 +7,8 @@ import { execSync } from 'node:child_process';
import axios from 'axios';
import { FileCacheAdapter } from '../src/utils/cache/file-adapter.js';
import { initialState } from '../src/logic/pricing/constants.js';
import { initialState, PRICING } from '../src/logic/pricing/constants.js';
import { calculateTotals } from '../src/logic/pricing/calculator.js';
async function main() {
const OPENROUTER_KEY = process.env.OPENROUTER_KEY;
@@ -19,6 +20,7 @@ async function main() {
let briefing = '';
let targetUrl: string | null = null;
let comments: string | null = null;
let budget: string | null = null;
let cacheKey: string | null = null;
let jsonStatePath: string | null = null;
@@ -32,6 +34,8 @@ async function main() {
targetUrl = args[++i];
} else if (arg === '--comments' || arg === '--notes') {
comments = args[++i];
} else if (arg === '--budget') {
budget = args[++i];
} else if (arg === '--cache-key') {
cacheKey = args[++i];
} else if (arg === '--json') {
@@ -77,7 +81,7 @@ async function main() {
}
const cache = new FileCacheAdapter({ prefix: 'ai_est_' });
const finalCacheKey = cacheKey || `${briefing}_${targetUrl}_${comments}`;
const finalCacheKey = cacheKey || `${briefing}_${targetUrl}_${comments}_${budget}`;
// 1. Crawl if URL provided
let crawlContext = '';
@@ -103,6 +107,11 @@ async function main() {
distilledCrawl = await distillCrawlContext(crawlContext, OPENROUTER_KEY);
await cache.set(`distilled_${targetUrl}`, distilledCrawl, 86400);
}
} else if (targetUrl) {
distilledCrawl = `WARNING: The crawl of ${targetUrl} failed (ENOTFOUND or timeout).
The AI must NOT hallucinate details about the current website.
Focus ONLY on the BRIEFING provided. If details are missing, mark them as 'unknown'.`;
console.warn('⚠️ Crawl failed. AI will be notified to avoid hallucinations.');
}
// 3. AI Prompting
@@ -124,7 +133,7 @@ async function main() {
console.log('📦 Using cached AI response.');
formState = cachedAi;
} else {
const result = await getAiEstimation(briefing, distilledCrawl, comments, OPENROUTER_KEY, principles, techStandards, tone);
const result = await getAiEstimation(briefing, distilledCrawl, comments, budget, OPENROUTER_KEY, principles, techStandards, tone);
formState = result.state;
usage = result.usage;
await cache.set(finalCacheKey, formState);
@@ -251,14 +260,24 @@ async function performCrawl(url: string): Promise<string> {
const cleanJson = (str: string) => {
// Remove markdown code blocks if present
let cleaned = str.replace(/```json\n?|```/g, '').trim();
// Remove potential control characters that break JSON.parse
cleaned = cleaned.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ");
// Remove trailing commas before closing braces/brackets
// We keep \n \r \t for now as they might be escaped or need handling
cleaned = cleaned.replace(/[\u0000-\u0009\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, " ");
// Specific fix for Gemini: raw newlines inside strings
// This is tricky. We'll try to escape newlines that are NOT followed by a quote and colon (property start)
// or a closing brace/bracket. This is heuristic.
// A better way is to replace all raw newlines that are preceded by a non-backslash with \n
// but only if they are inside double quotes.
// Simplest robust approach: Remove trailing commas and hope response_format does its job.
cleaned = cleaned.replace(/,\s*([\]}])/g, '$1');
return cleaned;
};
const getAiEstimation = async (briefing: string, distilledCrawl: string, comments: string | null, apiKey: string, principles: string, techStandards: string, tone: string) => {
const getAiEstimation = async (briefing: string, distilledCrawl: string, comments: string | null, budget: string | null, apiKey: string, principles: string, techStandards: string, tone: string) => {
let usage = { prompt: 0, completion: 0, cost: 0 };
const addUsage = (data: any) => {
if (data?.usage) {
@@ -281,90 +300,150 @@ const getAiEstimation = async (briefing: string, distilledCrawl: string, comment
You are a precision sensor. Analyze the BRIEFING and extract ONLY the raw facts.
Tone: Literal, non-interpretive.
Output language: GERMAN (Strict).
Output format: ROOT LEVEL JSON (No wrapper keys like '0' or 'data').
### MISSION:
Focus 100% on the BRIEFING text provided by the user. Use the DISTILLED_CRAWL only as background context for terms or company details. If there is a conflict, the BRIEFING is the absolute source of truth.
### OBJECTIVES:
- Extract **companyName**: The full legal and brand name (e.g., "E-TIB GmbH"). Use signatures and crawl data.
- Extract **personName**: The name of the primary human contact (e.g., "Danny Joseph"). **CRITICAL**: Check email signatures and "Mit freundlichen Grüßen" blocks. DO NOT use "Sie", "Firma" or generic terms if a name exists.
- Extract **existingWebsite**: The primary URL mentioned in the briefing or signature (e.g., "www.e-tib.com").
- Extract **websiteTopic**: A short descriptor of the CORE BUSINESS (e.g., "Kabeltiefbau"). MAX 3 WORDS.
- **isRelaunch**: Set to TRUE if the briefing mentions an existing website, a URL, or if the company is an established entity (e.g. "Gruppe", "GmbH seit 20XX"). Assume a presence exists that needs a modern "Zentrale Webpräsenz".
- **CRITICAL LOGIC**: If a URL is mentioned, isRelaunch MUST be TRUE.
- For all textual values (deadline, websiteTopic, targetAudience etc.): USE GERMAN.
- **multilang**: ONLY if the briefing mentions multiple target languages (e.g., DE/EN).
- **maps**: If "Google Maps" or location maps are mentioned or implicit (Contact page).
- **CRITICAL**: Do NOT include "social" in apiSystems unless the user explicitly wants to SYNC/POST content to social media. "Existing social media links" are NOT apiSystems.
- **CRITICAL**: "Video Player", "Cookie Banner", "Animations" are NOT features. They are visual/base. Do NOT map them to features.
### PRICING REFERENCE (FOR CALCULATION):
- Base Project (Infrastructure + 12 Months Hosting): 5.440 € (MANDATORY START)
- Additional Pages: 600 € / stk
- System-Modules (Features): 1.500 € / stk
- Logic-Functions: 800 € / stk
- API Integrations: 800 € / stk
- CMS Setup: 1.500 € (optional)
- Visual Staging/Interactions: 1.500 € - 2.000 €
### CATEGORY MAPPING (IDs ONLY):
- **selectedPages**: [Home, About, Services, Contact, Landing, Legal]
- **features**: [blog_news, products, jobs, refs, events]
- **functions**: [search, filter, pdf, forms, members, calendar, multilang, chat]
- **apiSystems**: [crm_erp, payment, marketing, ecommerce, maps, social, analytics]
- **assets**: [existing_website, logo, styleguide, content_concept, media, icons, illustrations, fonts]
${budget ? `### BUDGET LOGIC (ULTRA-STRICT):
1. **Mental Calculation**: Start with 7.000 €. Add items based on the reference above.
2. **Hard Ceiling**: If total > ${budget}, you MUST discard lower priority items.
3. **Priority**: High-End Design and Core Pages > Features.
4. **Restriction**: For ${budget}, do NOT exceed 2 features and 4 extra pages.
5. THE TOTAL COST CALCULATED BY THESE RULES MUST BE <= ${budget}.
6. Do NOT mention the budget in any string fields.` : ''}
### OUTPUT FORMAT (Strict JSON):
- ** features **: Items from the FEATURE_REFERENCE.
- ** ABSOLUTE CONSERVATIVE RULE **: Only use features if the briefing implies *dynamic complexity* (CMS, filtering, search, database).
- Simple keywords like 'Karriere', 'Referenzen', 'Messen' or lists of items MUST be treated as simple pages. Add them to 'otherPages' instead.
- If in doubt, categorizing as a PAGE is the mandatory default.
- ** otherPages **: Any specific pages mentioned (e.g. 'Historie', 'Team', 'Partner') that are not in the standard list. Use this for static lists of jobs or references too.
- ** companyName **: The full legal and brand name (e.g., "E-TIB GmbH"). Use signatures and crawl data.
- ** personName **: The name of the primary human contact (e.g., "Danny Joseph"). ** CRITICAL **: Check email signatures and "Mit freundlichen Grüßen" blocks.
- ** email **: The email address of the contact person if found in the briefing / signature.
- ** existingWebsite **: The primary URL mentioned in the briefing or signature (e.g., "www.e-tib.com").
- ** websiteTopic **: A short descriptor of the CORE BUSINESS (e.g., "Kabeltiefbau"). MAX 3 WORDS.
- ** isRelaunch **: Set to TRUE if the briefing mentions an existing website, a URL, or if the company is an established entity.
- ** CRITICAL LOGIC **: If a URL is mentioned, isRelaunch MUST be TRUE.
- For all textual values: USE GERMAN.
- ** multilang **: ONLY if the briefing mentions multiple target languages.
- ** maps **: If "Google Maps" or location maps are mentioned or implicit.
- ** CRITICAL **: Do NOT include "social" in apiSystems unless the user explicitly wants to SYNC / POST content.
### CATEGORY MAPPING(IDs ONLY):
- ** selectedPages **: [Home, About, Services, Contact, Landing, Legal]
- ** features **: [blog_news, products, jobs, refs, events]
- ** functions **: [search, filter, pdf, forms, members, calendar, multilang, chat]
- ** apiSystems **: [crm_erp, payment, marketing, ecommerce, maps, social, analytics]
- ** assets **: [existing_website, logo, styleguide, content_concept, media, icons, illustrations, fonts]
### OUTPUT FORMAT(Strict JSON - ROOT LEVEL):
{
"companyName": string,
"companyAddress": string,
"personName": string,
"existingWebsite": string,
"websiteTopic": string,
"isRelaunch": boolean,
"selectedPages": string[],
"features": string[],
"functions": string[],
"apiSystems": string[],
"assets": string[],
"deadline": string (GERMAN),
"targetAudience": "B2B" | "B2C" | "Internal" | string (GERMAN),
"expectedAdjustments": "low" | "medium" | "high" | string (GERMAN),
"employeeCount": "ca. 10+" | "ca. 50+" | "ca. 100+" | "ca. 250+" | "ca. 500+" | "ca. 1000+"
"companyName": string,
"companyAddress": string,
"personName": string,
"email": string,
"existingWebsite": string,
"websiteTopic": string,
"isRelaunch": boolean,
"selectedPages": string[],
"features": string[],
"functions": string[],
"apiSystems": string[],
"assets": string[],
"deadline": string(GERMAN),
"targetAudience": "B2B" | "B2C" | "Internal" | string(GERMAN),
"expectedAdjustments": "low" | "medium" | "high" | string(GERMAN),
"employeeCount": "ca. 10+" | "ca. 50+" | "ca. 100+" | "ca. 250+" | "ca. 500+" | "ca. 1000+"
}
`;
const pass1UserPrompt = `BRIEFING (TRUTH SOURCE):\n${briefing}\n\nCOMMENTS:\n${comments}\n\nDISTILLED_CRAWL (CONTEXT ONLY):\n${distilledCrawl}`;
const pass1UserPrompt = `BRIEFING(TRUTH SOURCE): \n${briefing} \n\nCOMMENTS: \n${comments} \n\nDISTILLED_CRAWL(CONTEXT ONLY): \n${distilledCrawl} `;
const p1Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'google/gemini-3-flash-preview',
messages: [{ role: 'system', content: pass1SystemPrompt }, { role: 'user', content: pass1UserPrompt }],
response_format: { type: 'json_object' }
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
if (!p1Resp.data.choices?.[0]?.message?.content) {
console.error('❌ Pass 1 failed. Response:', JSON.stringify(p1Resp.data, null, 2));
throw new Error('Pass 1: No content in response');
}
const facts = JSON.parse(cleanJson(p1Resp.data.choices[0].message.content));
// 1.5. PASS 1.5: The Feature Auditor (Skeptical Review)
console.log(' ↳ Pass 1.5: The Feature Auditor (Skeptical Review)...');
const pass15SystemPrompt = `
You are a "Strict Cost Controller". Your mission is to prevent over-billing.
Review the extracted FEATURES and the BRIEFING.
### RULE OF THUMB:
- A "Feature" (1.500 €) is ONLY justified for complex, dynamic systems (logic, database, CMS-driven management, advanced filtering).
- Simple lists, information sections, or static descriptions (e.g., "Messen", "Team", "Historie", "Jobs" as mere text) are ALWAYS "Pages" (600 €).
- If the briefing doesn't explicitly mention "Management System", "Filterable Database", or "Client Login", it is likely a PAGE.
### MISSION:
Analyze each feature in the list. Decide if it should stay a "Feature" or be downgraded to the "otherPages" array.
### OUTPUT FORMAT:
Return only the corrected 'features' and 'otherPages' arrays.
{
"features": string[],
"otherPages": string[]
}
`;
const p15Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'google/gemini-3-flash-preview',
messages: [
{ role: 'system', content: pass15SystemPrompt },
{ role: 'user', content: `EXTRACTED_FEATURES: ${JSON.stringify(facts.features)} \nBRIEFING: \n${briefing}` }
],
response_format: { type: 'json_object' }
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
addUsage(p15Resp.data);
const auditResult = JSON.parse(cleanJson(p15Resp.data.choices[0].message.content));
// Apply Audit: Downgrade features to otherPages
facts.features = auditResult.features || [];
facts.otherPages = Array.from(new Set([...(facts.otherPages || []), ...(auditResult.otherPages || [])]));
// 2. PASS 2: Feature Deep-Dive
console.log(' ↳ Pass 2: Feature Deep-Dive...');
const pass2SystemPrompt = `
You are a detail-oriented Solution Architect.
For EVERY item selected in Pass 1 (pages, features, functions, apiSystems), write a specific justification and technical scope.
You are a detail - oriented Solution Architect.
For EVERY item selected in Pass 1(pages, features, functions, apiSystems), write a specific justification and technical scope.
### RULES:
1. **CONCRETE & SPECIFIC**: Do NOT say "Implementation of X". Say "Displaying X with Y filters".
2. **NO EFFECTS**: Do not mention "fade-ins", "animations" or "visual styling". Focus on FUNCTION.
3. **ABSOLUTE RULE**: EVERYTHING MUST BE GERMAN.
4. **TRANSPARENCY**: Explain exactly what the USER gets.
5. **API NOTE**: For 'media' or 'video', explicitly state "Upload & Integration" (NO STREAMING).
1. ** CONCRETE & SPECIFIC **: Do NOT say "Implementation of X". Say "Displaying X with Y filters".
2. ** JUSTIFICATION (CRITICAL) **: For every entry in 'featureDetails', explicitly explain WHY this is a complex system (1.500 €) and not just a static page (600 €). If it's just a list of items, it's a PAGE.
3. ** NO EFFECTS **: Do not mention "fade-ins", "animations" or "visual styling". Focus on FUNCTION.
4. ** ABSOLUTE RULE **: EVERYTHING MUST BE GERMAN.
5. ** TRANSPARENCY **: Explain exactly what the USER gets.
6. ** API NOTE **: For 'media' or 'video', explicitly state "Upload & Integration" (NO STREAMING).
### INPUT (from Pass 1):
${JSON.stringify(facts, null, 2)}
### OUTPUT FORMAT (Strict JSON):
### OUTPUT FORMAT(Strict JSON):
{
"pageDetails": { "Home": string, ... },
"featureDetails": { "blog_news": string, ... },
"functionDetails": { "search": string, ... },
"apiDetails": { "crm_erp": string, ... }
"pageDetails": { "Home": string, ... },
"featureDetails": { "blog_news": string, ... },
"functionDetails": { "search": string, ... },
"apiDetails": { "crm_erp": string, ... }
}
`;
const p2Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'google/gemini-3-flash-preview',
messages: [{ role: 'system', content: pass2SystemPrompt }, { role: 'user', content: briefing }],
response_format: { type: 'json_object' }
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
addUsage(p2Resp.data);
if (!p2Resp.data.choices?.[0]?.message?.content) {
console.error('❌ Pass 2 failed. Response:', JSON.stringify(p2Resp.data, null, 2));
@@ -375,39 +454,39 @@ ${JSON.stringify(facts, null, 2)}
// 3. PASS 3: Strategic Content (Bespoke Strategy)
console.log(' ↳ Pass 3: Strategic Content (Bespoke Strategy)...');
const pass3SystemPrompt = `
You are a high-end Digital Architect. Your goal is to make the CUSTOMER feel 100% understood.
You are a high - end Digital Architect.Your goal is to make the CUSTOMER feel 100 % understood.
Analyze the BRIEFING and the EXISTING WEBSITE context.
### TONE & COMMUNICATION PRINCIPLES (STRICT):
### TONE & COMMUNICATION PRINCIPLES(STRICT):
${tone}
### OBJECTIVE:
3. **briefingSummary**: Ein sachlicher, tiefgehender Überblick der Unternehmenslage.
- **STIL**: Keine Ich-Form. Keine Marketing-Floskeln. Nutze präzise Fachbegriffe. Sei prägnant und effizient (ca. 70% der vorherigen Länge).
- **FORM**: EXAKT ZWEI ABSÄTZE. Insgesamt ca. 6 Sätze.
- **INHALT**: Welcher technologische Sprung ist notwendig? Was ist der Status Quo? (Bezug zur URL/Briefing).
- **ABSOLUTE REGEL**: Keine Halluzinationen über fehlende Präsenzen bei Relaunches.
- **DATENSCHUTZ**: KEINERLEI namentliche Nennungen von Personen (z. B. "Danny Joseph") in diesen Texten.
4. **designVision**: Ein abstraktes, strategisches Konzept.
- **STIL**: Rein konzeptionell. Keine Umsetzungsschritte. Keinerlei "To-dos". Keine Ich-Form. Sei prägnant.
- **FORM**: EXAKT ZWEI ABSÄTZE. Insgesamt ca. 4 Sätze.
- **DATENSCHUTZ**: KEINERLEI namentliche Nennungen von Personen in diesen Texten.
- **FOKUS**: Welche strategische Wirkung soll erzielt werden? (Z. B. "Industrielle Souveränität").
3. ** briefingSummary **: Ein sachlicher, tiefgehender Überblick der Unternehmenslage.
- ** STIL **: Keine Ich - Form.Keine Marketing - Floskeln.Nutze präzise Fachbegriffe.Sei prägnant und effizient(ca. 70 % der vorherigen Länge).
- ** FORM **: EXAKT ZWEI ABSÄTZE.Insgesamt ca. 6 Sätze.
- ** INHALT **: Welcher technologische Sprung ist notwendig ? Was ist der Status Quo ? (Bezug zur URL / Briefing).
- ** ABSOLUTE REGEL **: Keine Halluzinationen über fehlende Präsenzen bei Relaunches.
- ** DATENSCHUTZ **: KEINERLEI namentliche Nennungen von Personen(z.B. "Danny Joseph") in diesen Texten.
4. ** designVision **: Ein abstraktes, strategisches Konzept.
- ** STIL **: Rein konzeptionell.Keine Umsetzungsschritte.Keinerlei "To-dos".Keine Ich - Form.Sei prägnant.
- ** FORM **: EXAKT ZWEI ABSÄTZE.Insgesamt ca. 4 Sätze.
- ** DATENSCHUTZ **: KEINERLEI namentliche Nennungen von Personen in diesen Texten.
- ** FOKUS **: Welche strategische Wirkung soll erzielt werden ? (Z.B. "Industrielle Souveränität").
### OUTPUT FORMAT (Strict JSON):
### OUTPUT FORMAT(Strict JSON):
{
"briefingSummary": string,
"designVision": string
"briefingSummary": string,
"designVision": string
}
`;
const p3Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'google/gemini-3-flash-preview',
messages: [
{ role: 'system', content: pass3SystemPrompt },
{ role: 'user', content: `BRIEFING (TRUTH SOURCE):\n${briefing}\n\nEXISTING WEBSITE (CONTEXT):\n${distilledCrawl}\n\nEXTRACTED FACTS:\n${JSON.stringify(facts, null, 2)}` }
{ role: 'user', content: `BRIEFING(TRUTH SOURCE): \n${briefing} \n\nEXISTING WEBSITE(CONTEXT): \n${distilledCrawl} \n\nEXTRACTED FACTS: \n${JSON.stringify(facts, null, 2)} ` }
],
response_format: { type: 'json_object' }
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
addUsage(p3Resp.data);
if (!p3Resp.data.choices?.[0]?.message?.content) {
console.error('❌ Pass 3 failed. Response:', JSON.stringify(p3Resp.data, null, 2));
@@ -418,29 +497,30 @@ ${tone}
// 4. PASS 4: Information Architecture (Sitemap)
console.log(' ↳ Pass 4: Information Architecture...');
const pass4SystemPrompt = `
You are a Senior UX Architect. Design a hierarchical sitemap following the 'Industrial Logic' principle.
You are a Senior UX Architect.Design a hierarchical sitemap following the 'Industrial Logic' principle.
EVERYTHING MUST BE IN GERMAN.
### SITEMAP RULES:
1. **HIERARCHY**: Build a logical tree. Group by category (e.g., "Kern-Präsenz", "Lösungen", "Vertrauen", "Rechtliches").
2. **INTENT**: Each page MUST have a title and a brief functional conversion intent (desc).
3. **COMPREHENSIVENESS**: Ensure all 'selectedPages' and 'features' from Pass 1 are represented.
4. **LANGUAGE**: STRICT GERMAN TITLES. Do NOT use "Home", "About", "Services". Use "Startseite", "Über uns", "Leistungen".
1. ** HIERARCHY **: Build a logical tree.Group by category(e.g., "Kern-Präsenz", "Lösungen", "Vertrauen", "Rechtliches").
2. ** INTENT **: Each page MUST have a title and a brief functional conversion intent(desc).
3. ** COMPREHENSIVENESS **: Ensure all 'selectedPages' and 'features' from Pass 1 are represented.
4. ** LANGUAGE **: STRICT GERMAN TITLES.Do NOT use "Home", "About", "Services".Use "Startseite", "Über uns", "Leistungen".
5. ** NO IMPLEMENTATION NOTES **: Do NOT add implementation details in parentheses to titles (e.g. NO "Startseite (Hero-Video)", NO "About (Timeline)"). Keep titles clean and abstract.
### DATA CONTEXT:
${JSON.stringify({ facts, strategy }, null, 2)}
### OUTPUT FORMAT (Strict JSON):
### OUTPUT FORMAT(Strict JSON):
{
"websiteTopic": string,
"sitemap": [ { "category": string, "pages": [ { "title": string, "desc": string } ] } ]
"websiteTopic": string,
"sitemap": [{ "category": string, "pages": [{ "title": string, "desc": string }] }]
}
`;
const p4Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'google/gemini-3-flash-preview',
messages: [{ role: 'system', content: pass4SystemPrompt }, { role: 'user', content: `BRIEFING (TRUTH SOURCE):\n${briefing}\n\nDISTILLED_CRAWL (CONTEXT):\n${distilledCrawl}` }],
messages: [{ role: 'system', content: pass4SystemPrompt }, { role: 'user', content: `BRIEFING(TRUTH SOURCE): \n${briefing} \n\nDISTILLED_CRAWL(CONTEXT): \n${distilledCrawl} ` }],
response_format: { type: 'json_object' }
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
addUsage(p4Resp.data);
if (!p4Resp.data.choices?.[0]?.message?.content) {
console.error('❌ Pass 4 failed. Response:', JSON.stringify(p4Resp.data, null, 2));
@@ -450,57 +530,75 @@ ${JSON.stringify({ facts, strategy }, null, 2)}
// 5. PASS 5: Position Synthesis & Pricing Transparency
console.log(' ↳ Pass 5: Position Synthesis...');
// Determine which positions are actually relevant to avoid hallucinations
const requiredPositions = [
"1. Das technische Fundament",
facts.selectedPages.length + facts.otherPages.length > 0 ? "2. Individuelle Seiten" : null,
facts.features.length > 0 ? "3. System-Module (Features)" : null,
facts.functions.length > 0 ? "4. Logik-Funktionen" : null,
facts.apiSystems.length > 0 ? "5. Schnittstellen (API)" : null,
facts.cmsSetup ? "6. Inhaltsverwaltung (CMS)" : null,
"7. Inszenierung & Interaktion", // Always include for high-end strategy
facts.multilang ? "8. Mehrsprachigkeit" : null,
"9. Inhaltliche Initial-Pflege",
"10. Sorglos-Paket (Betrieb & Pflege)"
].filter(Boolean);
const pass5SystemPrompt = `
You are a Senior Solution Architect. Your goal is ABSOLUTE TRANSPARENCY and professionalism.
Each position in the quote must be perfectly justified and detailed using an objective, technical tone.
### POSITION TITLES (STRICT - MUST MATCH EXACTLY):
"1. Das technische Fundament", "2. Individuelle Seiten", "3. System-Module (Features)", "4. Logik-Funktionen", "5. Schnittstellen (API)", "6. Inhaltsverwaltung (CMS)", "7. Inszenierung & Interaktion", "8. Mehrsprachigkeit", "9. Inhaltliche Initial-Pflege", "10. Laufender Betrieb & Hosting".
### REQUIRED POSITION TITLES (STRICT - ONLY DESCRIBE THESE):
${requiredPositions.map(p => `"${p}"`).join(", ")}
### MAPPING RULES (STRICT):
- **1. Das technische Fundament**: Infrastructure, Hosting setup, SEO-Basics, Analytics, Environments.
- **2. Individuelle Seiten**: Layout/structure for specific pages (Home, About, etc.).
- **3. System-Module (Features)**: Functional systems like Blog, News, Products, Jobs, References.
- **4. Logik-Funktionen**: Logic modules like Search, Filter, Forms, PDF-Export.
- **5. Schnittstellen (API)**: Data Syncs with CRM, ERP, Payment systems.
- **6. Inhaltsverwaltung (CMS)**: Setup and mapping for CMS.
- **7. Inszenierung & Interaktion**: Hero-stories, visual effects, configurators.
- **8. Mehrsprachigkeit**: Architecture scaling for multiple languages.
- **9. Inhaltliche Initial-Pflege**: Manual data entry/cleanup.
- **10. Laufender Betrieb & Hosting**: Ongoing maintenance, updates, 24/7 monitoring.
- ** 1. Das technische Fundament **: Infrastructure, Hosting setup, SEO-Basics, Analytics, Environments.
- ** 2. Individuelle Seiten **: Layout / structure for specific pages. ** RULE **: If quantity is high (e.g. > 10), lead with "Umsetzung von [QTY] individuellen Einzelseiten...".
- ** 3. System-Module (Features) **: Functional systems like Blog, News, Products, Jobs, References. ** RULE **: Describe exactly 1 thing if qty is 1. If qty is 0, DO NOT DESCRIBE THIS.
- ** 4. Logik-Funktionen **: Logic modules like Search, Filter, Forms, PDF-Export.
- ** 5. Schnittstellen (API) **: Data Syncs with CRM, ERP, Payment systems.
- ** 6. Inhaltsverwaltung (CMS) **: Setup and mapping for CMS.
- ** 7. Inszenierung & Interaktion **: Hero-stories, visual effects, configurators.
- ** 8. Mehrsprachigkeit **: Architecture scaling for multiple languages.
- ** 9. Inhaltliche Initial-Pflege **: Manual data entry / cleanup.
- ** 10. Sorglos-Paket (Betrieb & Pflege) **: ** RULE **: Describe as "1 Jahr Sicherung des technischen Betriebs, Instandhaltung, Sicherheits-Updates und Inhalts-Aktualisierungen gemäß AGB Punkt 7a."
### RULES FOR positionDescriptions (STRICT):
1. **ABSOLUTE RULE: NO FIRST PERSON**: NEVER use "Ich", "Mein", "Wir" or "Unser". Lead with nouns or passive verbs.
2. **PROFESSIONAL TONE**: Use "Erstellung von...", "Anbindung der...", "Implementierung technischer...", "Bereitstellung von...".
3. **CONCISE & ITEM-BASED**: Use technical, high-density sentences. Name specific industry terms from context.
4. **ITEMIZED SYNTHESIS**: Mention EVERY component selected in Pass 1.
5. **HARD SPECIFICS**: If the briefing mentions "Glasfaser-Trassen" or "Schwerlast-Logistik", IT MUST BE IN THE DESCRIPTION.
6. **INDUSTRIAL AMBITION**: Describe it as a high-end technical solution. Avoid "schöne Website" or marketing fluff.
7. **PAGES**: For "2. Individuelle Seiten", list the pages (e.g., "Startseite (Hero-Video), Leistungen, Kontakt").
8. **LOGIC**: Describe the ACTUAL logic (e.g., "Volltextsuche mit Auto-Complete", not "eine Suche").
9. **KEYS**: Return EXACTLY the keys defined in "POSITION TITLES".
10. **NO AGB**: NEVER mention "AGB" or "Geschäftsbedingungen".
### RULES FOR positionDescriptions(STRICT):
1. ** ABSOLUTE RULE: NO FIRST PERSON **: NEVER use "Ich", "Mein", "Wir" or "Unser". Lead with nouns or passive verbs.
2. ** QUANTITY PARITY (ULTRA-STRICT) **: The description MUST list EXACTLY the number of items matching the 'qty' for that position. If qty is 3, describe exactly 3 items. If qty is 1, describe exactly 1 item. Do NOT "stuff" additional features into one description.
3. ** LOGIC GUARD (CMS) **: If 'cmsSetup' is false in the DATA CONTEXT, you MUST NOT mention "CMS", "Modul", "Management System" or "Inhaltsverwaltung". Use "Statische Seite" or "Darstellung".
4. ** STATIC vs DYNAMIC **: If no complex logic was extracted in Pass 2 for a feature, describe it as a technical layout/page, not as a system.
5. ** PROFESSIONAL TONE **: Use "Erstellung von...", "Anbindung der...", "Implementierung technischer...", "Bereitstellung von...".
6. ** CONCISE & ITEM-BASED **: Use technical, high-density sentences. Name specific industry terms from context.
7. ** ITEMIZED SYNTHESIS **: Mention EVERY component selected in Pass 1.
8. ** HARD SPECIFICS **: If the briefing mentions "Glasfaser-Trassen" or "Schwerlast-Logistik", IT MUST BE IN THE DESCRIPTION.
9. ** INDUSTRIAL AMBITION **: Describe it as a high-end technical solution. Avoid "schöne Website" or marketing fluff.
10. ** PAGES **: For "2. Individuelle Seiten", list the pages. ** ABSOLUTE RULE **: Do NOT add implementation details or technical notes in parentheses (e.g. NO "(Matrix-Struktur)", NO "(Timeline-Modul)"). Use clean titles like "Startseite, Über uns, Leistungen".
11. ** LOGIC **: Describe the ACTUAL logic (e.g., "Volltextsuche mit Auto-Complete", not "eine Suche").
12. ** KEYS **: Return EXACTLY the keys defined in "POSITION TITLES".
13. ** NO AGB **: NEVER mention "AGB" or "Geschäftsbedingungen".
### EXAMPLES (PASSIVE & TECHNICAL):
- **GOOD**: "Konfiguration der CMS-Infrastruktur zur unabhängigen Verwaltung von Produkt-Katalogen und News-Beiträgen."
- **GOOD**: "Implementierung einer Volltextsuche inkl. Kategorisierungs-Logik für effizientes Auffinden von Projektreferenzen."
- **GOOD**: "Native API-Anbindung an das ERP-System zur Echtzeit-Synchronisation von Bestandsdaten."
- **BAD**: "Ich richte dir das CMS ein."
- **BAD**: "Ich programmiere eine tolle Suche für deine Seite."
### EXAMPLES(PASSIVE & TECHNICAL):
- ** GOOD **: "Konfiguration der CMS-Infrastruktur zur unabhängigen Verwaltung von Produkt-Katalogen und News-Beiträgen."
- ** GOOD **: "Implementierung einer Volltextsuche inkl. Kategorisierungs-Logik für effizientes Auffinden von Projektreferenzen."
- ** GOOD **: "Native API-Anbindung an das ERP-System zur Echtzeit-Synchronisation von Bestandsdaten."
- ** BAD **: "Ich richte dir das CMS ein."
- ** BAD **: "Ich programmiere eine tolle Suche für deine Seite."
### DATA CONTEXT:
${JSON.stringify({ facts, details, strategy, ia }, null, 2)}
### OUTPUT FORMAT (Strict JSON):
### OUTPUT FORMAT(Strict JSON):
{
"positionDescriptions": { "1. Das technische Fundament": string, ... }
"positionDescriptions": { "1. Das technische Fundament": string, ... }
}
`;
const p5Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'google/gemini-3-flash-preview',
messages: [{ role: 'system', content: pass5SystemPrompt }, { role: 'user', content: briefing }],
response_format: { type: 'json_object' }
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
addUsage(p5Resp.data);
if (!p5Resp.data.choices?.[0]?.message?.content) {
console.error('❌ Pass 5 failed. Response:', JSON.stringify(p5Resp.data, null, 2));
@@ -511,31 +609,32 @@ ${JSON.stringify({ facts, details, strategy, ia }, null, 2)}
// 6. PASS 6: The Industrial Critic
console.log(' ↳ Pass 6: The Industrial Critic (Quality Gate)...');
const pass6SystemPrompt = `
You are the "Industrial Critic". Your goal is to catch quality regressions and ensure the document is bespoke, technical, and professional.
You are the "Industrial Critic".Your goal is to catch quality regressions and ensure the document is bespoke, technical, and professional.
Analyze the CURRENT_STATE against the BRIEFING_TRUTH.
### CRITICAL ERROR CHECKLIST (FAIL IF FOUND):
1. **Hallucination Leakage**: FAIL if names of people (e.g., "Frieder Helmich"), specific software versions, or invented details are used unless they appear EXACTLY in the BRIEFING.
- **CRITICAL**: Forbid "Sie", "Ansprechpartner" or "Unternehmen" for personName if a name IS in the briefing. If none is in briefing, use empty string.
2. **Logic Conflict**: FAIL if isRelaunch is true but briefingSummary claims no website exists.
### CRITICAL ERROR CHECKLIST(FAIL IF FOUND):
1. ** Hallucination Leakage **: FAIL if names of people(e.g., "Frieder Helmich"), specific software versions, or invented details are used unless they appear EXACTLY in the BRIEFING.
- ** CRITICAL **: Forbid "Sie", "Ansprechpartner" or "Unternehmen" for personName if a name IS in the briefing.If none is in briefing, use empty string.
2. ** Logic Conflict **: FAIL if isRelaunch is true but briefingSummary claims no website exists.
- FAIL if the description in positionDescriptions mentions more items than extracted in facts.
3. **Implementation Fluff**: FAIL if tech-stack details are mentioned (React, etc.). Focus on Concept & Result.
4. **Genericism Check (CRITICAL)**: FAIL if any text sounds like it could apply to ANY company. It MUST mention specific industry details (e.g., "Kabeltiefbau", "Infrastruktur-Zentrum") from the Briefing or Crawl.
6. **Namen-Verbot (STRICT)**: FAIL if any personal names (e.g. "Danny Joseph", "Joseph", etc.) appear in 'briefingSummary' or 'designVision'. Use abstract terms like "Unternehmensführung" or "Management" if necessary.
7. **AGB BAN**: FAIL if "Allgemeine Geschäftsbedingungen" or "AGB" appear anywhere.
8. **Length Check**: Briefing (ca. 6 Sätze) und Vision (ca. 4 Sätze). Kürze Texte, die zu ausschweifend sind, auf das Wesentliche.
3. ** Implementation Fluff **: FAIL if tech - stack details are mentioned(React, etc.).Focus on Concept & Result.
4. ** Genericism Check(CRITICAL) **: FAIL if any text sounds like it could apply to ANY company.It MUST mention specific industry details(e.g., "Kabeltiefbau", "Infrastruktur-Zentrum") from the Briefing or Crawl.
6. ** Namen-Verbot (STRICT) **: FAIL if any personal names (e.g. "Danny Joseph", "Joseph", etc.) appear in 'briefingSummary' or 'designVision'. Use abstract terms like "Unternehmensführung" or "Management" if necessary.
7. ** LOGIC GUARD (CMS) **: If 'cmsSetup' is false in the DATA CONTEXT, FAIL if any 'positionDescriptions' or 'briefingSummary' mentions "CMS", "Content Management System" or "Inhaltsverwaltung".
8. ** AGB BAN **: FAIL if "Allgemeine Geschäftsbedingungen" or "AGB" appear anywhere.
9. ** Length Check **: Briefing (ca. 6 Sätze) und Vision (ca. 4 Sätze). Kürze Texte, die zu ausschweifend sind, auf das Wesentliche.
### MISSION:
Return updated fields ONLY. Specifically focus on hardening 'positionDescriptions', 'sitemap', 'briefingSummary', and 'designVision'.
Return updated fields ONLY.Specifically focus on hardening 'positionDescriptions', 'sitemap', 'briefingSummary', and 'designVision'.
### DATA CONTEXT:
${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
`;
const p6Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'google/gemini-3-flash-preview',
messages: [{ role: 'system', content: pass6SystemPrompt }, { role: 'user', content: `BRIEFING_TRUTH:\n${briefing}` }],
messages: [{ role: 'system', content: pass6SystemPrompt }, { role: 'user', content: `BRIEFING_TRUTH: \n${briefing} ` }],
response_format: { type: 'json_object' }
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
addUsage(p6Resp.data);
if (!p6Resp.data.choices?.[0]?.message?.content) {
console.error('❌ Pass 6 failed. Response:', JSON.stringify(p6Resp.data, null, 2));
@@ -548,12 +647,10 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
let result = { ...state };
const unwrap = (obj: any): any => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj;
// Always unwrap "0" if it exists, regardless of other keys (AI often nests)
if (obj["0"]) return unwrap(obj["0"]);
if (obj.state) return unwrap(obj.state);
if (obj.facts) return unwrap(obj.facts);
if (obj.strategy) return unwrap(obj.strategy);
if (obj.ia) return unwrap(obj.ia);
if (obj.positionsData) return unwrap(obj.positionsData);
if (obj.state && Object.keys(obj).length === 1) return unwrap(obj.state);
if (obj.facts && Object.keys(obj).length === 1) return unwrap(obj.facts);
return obj;
};
@@ -576,6 +673,10 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
finalState.statusQuo = facts.isRelaunch ? 'Relaunch' : 'Neuentwicklung';
// Recipient Mapping
if (finalState.personName) finalState.name = finalState.personName;
if (finalState.email) finalState.email = finalState.email;
// Normalization Layer: Map hallucinated German keys back to internal keys
const normalizationMap: Record<string, string> = {
"Briefing-Zusammenfassung": "briefingSummary",
@@ -596,6 +697,27 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
}
});
// Final Logic Guard: Strictly strip CMS from ALL descriptions and fields if not enabled
if (!finalState.cmsSetup) {
const stripCMS = (obj: any): any => {
if (typeof obj === 'string') {
return obj.replace(/CMS|Content-Management-System|Inhaltsverwaltung/gi, 'Plattform-Struktur');
}
if (Array.isArray(obj)) {
return obj.map(stripCMS);
}
if (obj !== null && typeof obj === 'object') {
const newObj: any = {};
Object.entries(obj).forEach(([k, v]) => {
newObj[k] = stripCMS(v);
});
return newObj;
}
return obj;
};
finalState = stripCMS(finalState);
}
// Sitemap Normalization (German keys to internal)
if (Array.isArray(finalState.sitemap)) {
finalState.sitemap = finalState.sitemap.map((cat: any) => ({
@@ -607,14 +729,41 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
}));
}
// Position Descriptions Normalization
// Position Descriptions Normalization (Strict Title Mapping + Index-based Fallback)
if (finalState.positionDescriptions) {
const normalized: Record<string, string> = {};
Object.entries(finalState.positionDescriptions).forEach(([key, value]) => {
const normalizedKey = key === 'titel' || key === 'Title' ? 'title' : key;
const rawPositions = finalState.positionDescriptions;
// 1. Initial cleanup
Object.entries(rawPositions).forEach(([key, value]) => {
const normalizedValue = typeof value === 'object' ? (value as any).beschreibung || (value as any).description || JSON.stringify(value) : value;
normalized[normalizedKey] = normalizedValue as string;
normalized[key] = normalizedValue as string;
});
// 2. Index-based matching (Map "10. Foo" to "10. Bar")
const standardTitles = [
"1. Das technische Fundament",
"2. Individuelle Seiten",
"3. System-Module (Features)",
"4. Logik-Funktionen",
"5. Schnittstellen (API)",
"6. Inhaltsverwaltung (CMS)",
"7. Inszenierung & Interaktion",
"8. Mehrsprachigkeit",
"9. Inhaltliche Initial-Pflege",
"10. Sorglos-Paket (Betrieb & Pflege)"
];
standardTitles.forEach(std => {
const prefix = std.split('.')[0] + '.'; // e.g., "10."
// Find any key in the AI output that starts with this number
const matchingKey = Object.keys(normalized).find(k => k.trim().startsWith(prefix));
if (matchingKey && matchingKey !== std) {
normalized[std] = normalized[matchingKey];
// Keep the old key too just in case, but prioritize the standard one
}
});
finalState.positionDescriptions = normalized;
}
@@ -639,6 +788,69 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
}
}
// Final Post-Reflection Budget Sync (Hard Pruning if still over)
if (budget) {
const targetValue = parseInt(budget.replace(/[^0-9]/g, ''));
if (!isNaN(targetValue)) {
console.log(`⚖️ Final Budget Audit(${targetValue} € target)...`);
let currentTotals = calculateTotals(finalState, PRICING);
// Step-by-step pruning if too expensive
if (currentTotals.totalPrice > targetValue) {
console.log(`⚠️ Budget exceeded(${currentTotals.totalPrice} €).Pruning scope to fit ${targetValue} €...`);
// 1. Remove optional "other" stuff
finalState.otherFeatures = [];
finalState.otherFunctions = [];
finalState.otherTech = [];
// 2. Remove non-critical functions
const funcPriority = ['search', 'filter', 'calendar', 'multilang'];
for (const f of funcPriority) {
if (currentTotals.totalPrice <= targetValue) break;
if (finalState.functions.includes(f)) {
finalState.functions = finalState.functions.filter((x: string) => x !== f);
currentTotals = calculateTotals(finalState, PRICING);
}
}
// 3. Remove least critical features if still over
const featurePriority = ['events', 'blog_news', 'products'];
for (const p of featurePriority) {
if (currentTotals.totalPrice <= targetValue) break;
if (finalState.features.includes(p)) {
finalState.features = finalState.features.filter((f: string) => f !== p);
currentTotals = calculateTotals(finalState, PRICING);
}
}
// 4. Reduce page count (Selected Pages AND Sitemap)
while (currentTotals.totalPrice > targetValue && (finalState.selectedPages.length > 4 || currentTotals.totalPagesCount > 5)) {
if (finalState.selectedPages.length > 4) {
finalState.selectedPages.pop();
}
// Prune Sitemap to match
if (finalState.sitemap && Array.isArray(finalState.sitemap)) {
const lastCat = finalState.sitemap[finalState.sitemap.length - 1];
if (lastCat && lastCat.pages && lastCat.pages.length > 0) {
lastCat.pages.pop();
if (lastCat.pages.length === 0) finalState.sitemap.pop();
}
}
currentTotals = calculateTotals(finalState, PRICING);
}
// 5. Final fallback: Remove second feature if still over
if (currentTotals.totalPrice > targetValue && finalState.features.length > 1) {
finalState.features.pop();
currentTotals = calculateTotals(finalState, PRICING);
}
}
console.log(`✅ Final budget audit complete: ${currentTotals.totalPrice}`);
}
}
return { state: finalState, usage };
}

View File

@@ -104,7 +104,7 @@ export function PriceCalculation({
</div>
<div className="pt-4 border-t border-slate-200 space-y-4">
<div className="flex justify-between items-center">
<span className="text-slate-600 font-medium text-sm">Betrieb & Hosting</span>
<span className="text-slate-600 font-medium text-sm">Sorglos-Paket</span>
<span className="text-base font-bold text-slate-900">{monthlyPrice.toLocaleString()} / Monat</span>
</div>
</div>

View File

@@ -54,9 +54,9 @@ export const AboutModule = () => (
</PDFView>
<PDFView style={{ marginTop: 32, paddingVertical: 16, borderTopWidth: 1, borderTopColor: COLORS.GRID }}>
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 4 }]}>Qualitäts-Zusage</PDFText>
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 4 }]}>Infrastruktur & Souveränität</PDFText>
<PDFText style={styles.industrialText}>
Es werden keine instabilen Prototypen geliefert, sondern produktionsreife Software, die skalierbar bleibt und mit dem Unternehmen wächst. Direkt. Sauber. Ohne Ballast.
Es wird keine instabile Prototyp-Software geliefert, sondern produktionsreife Systeme, die technisch skalierbar bleiben. Die Codebasis folgt modernen Standards bei wachsenden Ansprüchen oder dem Wechsel zu einer Agentur kann der Quellcode jederzeit nahtlos übernommen und weitergeführt werden.
</PDFText>
</PDFView>
</PDFView>
@@ -65,8 +65,8 @@ export const AboutModule = () => (
export const CrossSellModule = ({ state }: any) => {
const isWebsite = state.projectType === 'website';
const title = isWebsite ? "Routine-Automatisierung" : "Websites & Ökosysteme";
const subtitle = isWebsite ? "Maßgeschneiderte Software zur Prozessoptimierung" : "Technische Infrastruktur ohne Kompromisse";
const title = isWebsite ? "Weitere Potenziale" : "Websites & Ökosysteme";
const subtitle = isWebsite ? "Automatisierung und Prozessoptimierung" : "Technische Infrastruktur ohne Kompromisse";
return (
<>
@@ -77,7 +77,7 @@ export const CrossSellModule = ({ state }: any) => {
{isWebsite ? (
<>
<PDFView style={[styles.industrialCol, { marginRight: '8%' }]}>
<PDFText style={styles.industrialTextLead}>Manuelle Alltags-Aufgaben sind teuer und fehleranfällig. In einer separaten Beauftragung können maßgeschneiderte Systeme entwickelt werden, die Routine-Prozesse automatisiert im Hintergrund verarbeiten.</PDFText>
<PDFText style={styles.industrialTextLead}>Über die klassische Webpräsenz hinaus werden maßgeschneiderte Lösungen zur Automatisierung von Routine-Prozessen angeboten. Dies ermöglicht eine signifikante Effizienzsteigerung im Tagesgeschäft.</PDFText>
<PDFText style={[styles.industrialText, { fontWeight: 'bold' }]}>Keine Abos. Keine komplexen neuen Systeme. Gezielte Zeitersparnis.</PDFText>
<PDFView style={{ marginTop: 24, padding: 16, backgroundColor: '#f8fafc', borderLeftWidth: 2, borderLeftColor: COLORS.GRID }}>
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 4 }]}>Individuelle Analyse</PDFText>

View File

@@ -5,11 +5,11 @@ import { View as PDFView, Text as PDFText, StyleSheet, Image as PDFImage } from
import { DocumentTitle, Divider, COLORS, FONT_SIZES } from '../SharedUI';
const styles = StyleSheet.create({
section: { marginBottom: 24 },
pricingGrid: { marginTop: 24 },
pricingRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.DIVIDER, paddingVertical: 10, alignItems: 'flex-start' },
pricingTitle: { width: '30%', fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.CHARCOAL },
pricingDesc: { width: '55%', fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.4 },
section: { marginBottom: 16 },
pricingGrid: { marginTop: 12 },
pricingRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.DIVIDER, paddingVertical: 12, alignItems: 'flex-start' },
pricingTitle: { width: '30%', fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.CHARCOAL, paddingRight: 15 },
pricingDesc: { width: '55%', fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.5, paddingRight: 10 },
pricingTag: { width: '15%', fontSize: FONT_SIZES.BODY, fontWeight: 'bold', textAlign: 'right', color: COLORS.CHARCOAL },
configLabel: { fontSize: FONT_SIZES.BLUEPRINT, color: COLORS.TEXT_LIGHT, textTransform: 'uppercase', marginBottom: 8 },
});
@@ -18,12 +18,11 @@ export const techPageModule = ({ techDetails, headerIcon }: any) => (
<>
<DocumentTitle title="Technische Umsetzung" />
<PDFView style={styles.section}>
<PDFText style={{ fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 16 }}>Entwicklung von Websites als moderne, performante Websysteme nach industriellen Standards.</PDFText>
<PDFView style={styles.pricingGrid}>
{techDetails?.map((item: any, i: number) => (
<PDFView key={i} style={styles.pricingRow}>
<PDFText style={[styles.pricingTitle, { width: '35%' }]}>{item.t}</PDFText>
<PDFText style={[styles.pricingDesc, { width: '65%' }]}>{item.d}</PDFText>
<PDFText style={[styles.pricingTitle, { width: '30%' }]}>{item.t}</PDFText>
<PDFText style={[styles.pricingDesc, { width: '70%', paddingRight: 0 }]}>{item.d}</PDFText>
</PDFView>
))}
</PDFView>
@@ -34,62 +33,57 @@ export const techPageModule = ({ techDetails, headerIcon }: any) => (
export const TransparenzModule = ({ pricing }: any) => (
<>
<DocumentTitle title="Preis-Transparenz & Modell" />
<PDFView style={styles.section}>
<PDFText style={{ fontSize: FONT_SIZES.BODY + 1, fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 8 }}>Festpreise statt Stundenabrechnung</PDFText>
<PDFText style={{ fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 12 }}>Maximale Planungssicherheit durch ein modulares Festpreis-System. Die Abrechnung erfolgt nach Ergebnissen statt nach reinem Zeitaufwand. Versteckte Kosten sind durch die modulare Struktur ausgeschlossen.</PDFText>
<Divider style={{ marginTop: 12 }} />
</PDFView>
<PDFView style={styles.section}>
<PDFView style={styles.pricingGrid}>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>1. Das technische Fundament</PDFText>
<PDFText style={styles.pricingDesc}>Einrichtung der technischen Infrastruktur, Hosting-Bereitstellung, SEO-Basics sowie Bereitstellung von Test-, Staging- und produktiven Live-Umgebungen.</PDFText>
<PDFText style={styles.pricingTitle}>1. Fundament</PDFText>
<PDFText style={styles.pricingDesc}>Setup, Infrastruktur, Hosting, SEO-Basics, Staging & Live-Umgebungen.</PDFText>
<PDFText style={styles.pricingTag}>{pricing.BASE_WEBSITE?.toLocaleString('de-DE')} </PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>2. Individuelle Seiten</PDFText>
<PDFText style={styles.pricingDesc}>Entwicklung individueller Layouts und Strukturen pro Seite. Optimierung für alle Endgeräte (Responsive Design) und Browser-Kompatibilität.</PDFText>
<PDFText style={styles.pricingTitle}>2. Seiten</PDFText>
<PDFText style={styles.pricingDesc}>Layout & Umsetzung individueller Seiten. Responsive Design / Cross-Browser.</PDFText>
<PDFText style={styles.pricingTag}>{pricing.PAGE?.toLocaleString('de-DE')} / Stk</PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>3. System-Module (Features)</PDFText>
<PDFText style={styles.pricingDesc}>Implementierung abgeschlossener technischer Systeme (z. B. Blog, News, Produkte). Definition notwendiger Datenfelder und Pflege-Oberflächen.</PDFText>
<PDFText style={styles.pricingTitle}>3. Features</PDFText>
<PDFText style={styles.pricingDesc}>Abgeschlossene Systeme (z. B. Blog, Jobs, Produkte) inkl. Datenstruktur.</PDFText>
<PDFText style={styles.pricingTag}>{pricing.FEATURE?.toLocaleString('de-DE')} / Stk</PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>4. Logik-Funktionen</PDFText>
<PDFText style={styles.pricingDesc}>Programmierung technischer Logik-Einheiten wie Filter, Suchen oder Kontakt-Schnittstellen zur fehlerfreien Datenverarbeitung.</PDFText>
<PDFText style={styles.pricingTitle}>4. Funktionen</PDFText>
<PDFText style={styles.pricingDesc}>Logik-Einheiten wie Filter, Suchen oder Kontakt-Schnittstellen.</PDFText>
<PDFText style={styles.pricingTag}>{pricing.FUNCTION?.toLocaleString('de-DE')} / Stk</PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>5. Schnittstellen (API)</PDFText>
<PDFText style={styles.pricingDesc}>Anbindung externer Drittsysteme (CRM, ERP, Payment-Provider) zur automatisierten Datensynchronisation und Prozessoptimierung.</PDFText>
<PDFText style={styles.pricingTitle}>5. Schnittstellen</PDFText>
<PDFText style={styles.pricingDesc}>Anbindung externer Systeme (CRM, ERP, Payment) zur Synchronisation.</PDFText>
<PDFText style={styles.pricingTag}>ab {pricing.API_INTEGRATION?.toLocaleString('de-DE')} / Stk</PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>6. Inhaltsverwaltung (CMS)</PDFText>
<PDFText style={styles.pricingDesc}>Bereitstellung und Konfiguration eines Content Management Systems (CMS) inkl. technischer Anbindung aller Module zur unabhängigen Datenpflege.</PDFText>
<PDFText style={styles.pricingTag}>{pricing.CMS_SETUP?.toLocaleString('de-DE')} </PDFText>
<PDFText style={styles.pricingTitle}>6. CMS Setup</PDFText>
<PDFText style={styles.pricingDesc}>Konfiguration Headless CMS zur unabhängigen Datenpflege aller Module.</PDFText>
<PDFText style={pricing.CMS_SETUP ? styles.pricingTag : [styles.pricingTag, { color: COLORS.TEXT_LIGHT }]}>{pricing.CMS_SETUP?.toLocaleString('de-DE')} </PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>7. Inszenierung & Interaktion</PDFText>
<PDFText style={styles.pricingDesc}>Umsetzung komplexer Interaktions-Mechanismen, Konfiguratoren oder aufwendiger visueller Storytelling-Elemente zur Steigerung der Conversion.</PDFText>
<PDFText style={styles.pricingTitle}>7. Inszenierung</PDFText>
<PDFText style={styles.pricingDesc}>Interaktions-Mechanismen, Konfiguratoren oder visuelles Storytelling.</PDFText>
<PDFText style={styles.pricingTag}>ab {pricing.VISUAL_STAGING?.toLocaleString('de-DE')} </PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>8. Mehrsprachigkeit</PDFText>
<PDFText style={styles.pricingDesc}>Skalierung der System-Architektur auf zusätzliche Sprachen. Inklusive struktureller Anpassungen und Logik für Sprachumschaltung.</PDFText>
<PDFText style={styles.pricingTitle}>8. Sprachen</PDFText>
<PDFText style={styles.pricingDesc}>Skalierung der System-Architektur auf zusätzliche Sprachversionen.</PDFText>
<PDFText style={styles.pricingTag}>+20% / Sprache</PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>9. Inhaltliche Initial-Pflege</PDFText>
<PDFText style={styles.pricingDesc}>Manuelle Übernahme und Aufbereitung von Datensätzen in das Zielsystem zur Sicherstellung einer initialen Inhaltsabdeckung.</PDFText>
<PDFText style={styles.pricingTitle}>9. Initial-Pflege</PDFText>
<PDFText style={styles.pricingDesc}>Manuelle Aufbereitung & Übernahme von Datensätzen in das Zielsystem.</PDFText>
<PDFText style={styles.pricingTag}>{pricing.NEW_DATASET?.toLocaleString('de-DE')} / Stk</PDFText>
</PDFView>
<PDFView style={styles.pricingRow}>
<PDFText style={styles.pricingTitle}>10. Laufender Betrieb & Hosting</PDFText>
<PDFText style={styles.pricingDesc}>Laufender Betrieb, Hosting, Sicherheits-Updates sowie monatliche Backups und Monitoring zur Sicherstellung der Systemverfügbarkeit.</PDFText>
<PDFText style={styles.pricingTag}>{pricing.HOSTING_MONTHLY?.toLocaleString('de-DE')} / Monat</PDFText>
<PDFText style={styles.pricingTitle}>10. Sorglos-Paket</PDFText>
<PDFText style={styles.pricingDesc}>Betrieb, Hosting, Updates & Monitoring gemäß AGB Punkt 7a.</PDFText>
<PDFText style={styles.pricingTag}>Inklusive 1 Jahr</PDFText>
</PDFView>
</PDFView>
</PDFView>
@@ -99,11 +93,11 @@ export const TransparenzModule = ({ pricing }: any) => (
export const PrinciplesModule = ({ principles }: any) => (
<>
<DocumentTitle title="Prinzipien & Standards" />
<PDFView style={styles.pricingGrid}>
<PDFView style={[styles.pricingGrid, { marginTop: 8 }]}>
{principles?.map((item: any, i: number) => (
<PDFView key={i} style={styles.pricingRow}>
<PDFText style={[styles.pricingTitle, { width: '35%' }]}>{item.t}</PDFText>
<PDFText style={[styles.pricingDesc, { width: '65%' }]}>{item.d}</PDFText>
<PDFText style={[styles.pricingTitle, { width: '30%' }]}>{item.t}</PDFText>
<PDFText style={[styles.pricingDesc, { width: '70%', paddingRight: 0 }]}>{item.d}</PDFText>
</PDFView>
))}
</PDFView>

View File

@@ -183,8 +183,8 @@ export function calculatePositions(state: FormState, pricing: any): Position[] {
const monthlyRate = pricing.HOSTING_MONTHLY + (state.storageExpansion * pricing.STORAGE_EXPANSION_MONTHLY);
positions.push({
pos: pos++,
title: '10. Laufender Betrieb & Hosting',
desc: `Bereitstellung der Infrastruktur, technische Instandhaltung, Sicherheits-Updates und Backup-Management gemäß AGB Punkt 7a. Inklusive 12 Monate Service.`,
title: '10. Sorglos-Paket (Betrieb & Pflege)',
desc: `1 Jahr Sicherung des technischen Betriebs, Instandhaltung, Sicherheits-Updates und Inhalts-Aktualisierungen gemäß AGB Punkt 7a.`,
qty: 1,
price: monthlyRate * 12
});

View File

@@ -1,16 +1,16 @@
import { FormState } from './types';
export const PRICING = {
BASE_WEBSITE: 6000,
PAGE: 800,
FEATURE: 2000,
FUNCTION: 1000,
NEW_DATASET: 400,
HOSTING_MONTHLY: 120,
BASE_WEBSITE: 4000,
PAGE: 600,
FEATURE: 1500,
FUNCTION: 800,
NEW_DATASET: 200,
HOSTING_MONTHLY: 250,
STORAGE_EXPANSION_MONTHLY: 10,
CMS_SETUP: 1500,
CMS_CONNECTION_PER_FEATURE: 800,
API_INTEGRATION: 1000,
API_INTEGRATION: 800,
APP_HOURLY: 120,
VISUAL_STAGING: 2000,
COMPLEX_INTERACTION: 1500,

View File

@@ -1,68 +1,27 @@
{
"requestsFinished": 0,
"requestsFailed": 1,
"requestsRetries": 1,
"requestsFailedPerMinute": 279,
"requestsFinishedPerMinute": 0,
"requestMinDurationMillis": null,
"requestMaxDurationMillis": 0,
"requestTotalFailedDurationMillis": 2,
"requestTotalFinishedDurationMillis": 0,
"crawlerStartedAt": "2026-02-04T17:39:09.193Z",
"crawlerFinishedAt": "2026-02-04T17:39:09.396Z",
"statsPersistedAt": "2026-02-04T17:39:09.396Z",
"crawlerRuntimeMillis": 215,
"crawlerLastStartTimestamp": 1770226749181,
"requestsFinished": 7,
"requestsFailed": 0,
"requestsRetries": 0,
"requestsFailedPerMinute": 0,
"requestsFinishedPerMinute": 596,
"requestMinDurationMillis": 72,
"requestMaxDurationMillis": 503,
"requestTotalFailedDurationMillis": 0,
"requestTotalFinishedDurationMillis": 1167,
"crawlerStartedAt": "2026-02-05T00:44:22.537Z",
"crawlerFinishedAt": "2026-02-05T00:44:23.230Z",
"statsPersistedAt": "2026-02-05T00:44:23.230Z",
"crawlerRuntimeMillis": 705,
"crawlerLastStartTimestamp": 1770252262525,
"requestRetryHistogram": [
null,
null,
null,
1
7
],
"statsId": 0,
"requestAvgFailedDurationMillis": 2,
"requestAvgFinishedDurationMillis": null,
"requestTotalDurationMillis": 2,
"requestsTotal": 1,
"requestAvgFailedDurationMillis": null,
"requestAvgFinishedDurationMillis": 167,
"requestTotalDurationMillis": 1167,
"requestsTotal": 7,
"requestsWithStatusCode": {},
"errors": {
"file:///Users/marcmintel/Projects/mintel.me/node_modules/got/dist/source/core/index.js:198:21": {
"ENOTFOUND": {
"RequestError": {
"getaddrinfo ENOTFOUND etib-et.com": {
"count": 1
}
}
}
},
"node:dns:120:26": {
"ENOTFOUND": {
"Error": {
"getaddrinfo ENOTFOUND etib-et.com": {
"count": 1
}
}
}
}
},
"retryErrors": {
"file:///Users/marcmintel/Projects/mintel.me/node_modules/got/dist/source/core/index.js:198:21": {
"ENOTFOUND": {
"RequestError": {
"getaddrinfo ENOTFOUND etib-et.com": {
"count": 3
}
}
}
},
"node:dns:120:26": {
"ENOTFOUND": {
"Error": {
"getaddrinfo ENOTFOUND etib-et.com": {
"count": 3
}
}
}
}
}
"errors": {},
"retryErrors": {}
}

View File

@@ -1,9 +1,9 @@
{
"usableSessionsCount": 4,
"usableSessionsCount": 7,
"retiredSessionsCount": 0,
"sessions": [
{
"id": "session_qS9LzMPYLU",
"id": "session_MKcXTj8jBY",
"cookieJar": {
"version": "tough-cookie@6.0.0",
"storeType": "MemoryCookieStore",
@@ -16,14 +16,14 @@
"userData": {},
"maxErrorScore": 3,
"errorScoreDecrement": 0.5,
"expiresAt": "2026-02-04T18:29:09.266Z",
"createdAt": "2026-02-04T17:39:09.266Z",
"expiresAt": "2026-02-05T01:34:22.574Z",
"createdAt": "2026-02-05T00:44:22.574Z",
"usageCount": 1,
"maxUsageCount": 50,
"errorScore": 1
"errorScore": 0
},
{
"id": "session_rQXoQ3YoSI",
"id": "session_CzBvH8k5d6",
"cookieJar": {
"version": "tough-cookie@6.0.0",
"storeType": "MemoryCookieStore",
@@ -36,14 +36,14 @@
"userData": {},
"maxErrorScore": 3,
"errorScoreDecrement": 0.5,
"expiresAt": "2026-02-04T18:29:09.369Z",
"createdAt": "2026-02-04T17:39:09.369Z",
"expiresAt": "2026-02-05T01:34:23.083Z",
"createdAt": "2026-02-05T00:44:23.083Z",
"usageCount": 1,
"maxUsageCount": 50,
"errorScore": 1
"errorScore": 0
},
{
"id": "session_VQKYMg9QOd",
"id": "session_6tYd3j1pzA",
"cookieJar": {
"version": "tough-cookie@6.0.0",
"storeType": "MemoryCookieStore",
@@ -56,14 +56,14 @@
"userData": {},
"maxErrorScore": 3,
"errorScoreDecrement": 0.5,
"expiresAt": "2026-02-04T18:29:09.376Z",
"createdAt": "2026-02-04T17:39:09.376Z",
"expiresAt": "2026-02-05T01:34:23.085Z",
"createdAt": "2026-02-05T00:44:23.085Z",
"usageCount": 1,
"maxUsageCount": 50,
"errorScore": 1
"errorScore": 0
},
{
"id": "session_Jf7MUM5INz",
"id": "session_MahMPRKWfS",
"cookieJar": {
"version": "tough-cookie@6.0.0",
"storeType": "MemoryCookieStore",
@@ -76,11 +76,71 @@
"userData": {},
"maxErrorScore": 3,
"errorScoreDecrement": 0.5,
"expiresAt": "2026-02-04T18:29:09.380Z",
"createdAt": "2026-02-04T17:39:09.380Z",
"expiresAt": "2026-02-05T01:34:23.086Z",
"createdAt": "2026-02-05T00:44:23.086Z",
"usageCount": 1,
"maxUsageCount": 50,
"errorScore": 1
"errorScore": 0
},
{
"id": "session_POCFcWGlXP",
"cookieJar": {
"version": "tough-cookie@6.0.0",
"storeType": "MemoryCookieStore",
"rejectPublicSuffixes": true,
"enableLooseMode": false,
"allowSpecialUseDomain": true,
"prefixSecurity": "silent",
"cookies": []
},
"userData": {},
"maxErrorScore": 3,
"errorScoreDecrement": 0.5,
"expiresAt": "2026-02-05T01:34:23.087Z",
"createdAt": "2026-02-05T00:44:23.087Z",
"usageCount": 1,
"maxUsageCount": 50,
"errorScore": 0
},
{
"id": "session_zra0Syci00",
"cookieJar": {
"version": "tough-cookie@6.0.0",
"storeType": "MemoryCookieStore",
"rejectPublicSuffixes": true,
"enableLooseMode": false,
"allowSpecialUseDomain": true,
"prefixSecurity": "silent",
"cookies": []
},
"userData": {},
"maxErrorScore": 3,
"errorScoreDecrement": 0.5,
"expiresAt": "2026-02-05T01:34:23.088Z",
"createdAt": "2026-02-05T00:44:23.088Z",
"usageCount": 1,
"maxUsageCount": 50,
"errorScore": 0
},
{
"id": "session_JbCC8UIBHk",
"cookieJar": {
"version": "tough-cookie@6.0.0",
"storeType": "MemoryCookieStore",
"rejectPublicSuffixes": true,
"enableLooseMode": false,
"allowSpecialUseDomain": true,
"prefixSecurity": "silent",
"cookies": []
},
"userData": {},
"maxErrorScore": 3,
"errorScoreDecrement": 0.5,
"expiresAt": "2026-02-05T01:34:23.092Z",
"createdAt": "2026-02-05T00:44:23.092Z",
"usageCount": 1,
"maxUsageCount": 50,
"errorScore": 0
}
]
}