feat: Implement AI-powered estimation system, add comprehensive usage guide, and redesign PDF sitemap module.
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 23s
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 23s
This commit is contained in:
16
README.md
16
README.md
@@ -87,4 +87,18 @@ npm run video:preview
|
||||
# Render the showcase video
|
||||
npm run video:render
|
||||
```
|
||||
See `.agent/workflows/video-toolkit.md` for more details.
|
||||
See `.agent/workflows/video-toolkit.md` for more details.
|
||||
|
||||
## 🤖 Industrial AI Estimation System
|
||||
A multi-pass AI consultation engine for generating technical project estimations and professional PDFs.
|
||||
|
||||
- **Context-Aware**: Crawls existing customer websites to understand company DNA.
|
||||
- **Industrial Standards**: Strictly follows "Industrial Engineering" aesthetics and technical wording.
|
||||
- **Transparent**: Maps scope directly to a modular pricing model.
|
||||
|
||||
### Quick Start
|
||||
```bash
|
||||
# Generate estimation with website crawl
|
||||
npm run ai-estimate -- "Project Briefing" --url https://example.com
|
||||
```
|
||||
See [ESTIMATION_GUIDE.md](docs/ESTIMATION_GUIDE.md) for full documentation.
|
||||
83
docs/ESTIMATION_GUIDE.md
Normal file
83
docs/ESTIMATION_GUIDE.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Service Estimation & AI Consultation Guide
|
||||
|
||||
This guide explains how to use the automated estimation system to generate professional PDF quotes for clients using AI-driven context analysis.
|
||||
|
||||
## 🛠 Basic Usage
|
||||
|
||||
The primary entry point is the `ai-estimate` script. It orchestrates a 6-pass AI consultation:
|
||||
1. **Fact Extraction**: Identifying company data and project scope.
|
||||
2. **Feature Deep-Dive**: Generating technical justifications for items.
|
||||
3. **Strategic Content**: Creating the Briefing Analysis and Strategic Vision.
|
||||
4. **Information Architecture**: Designing a hierarchical sitemap.
|
||||
5. **Position Synthesis**: Mapping everything to a transparent pricing model.
|
||||
6. **Industrial Critic**: Final quality gate for tone and accuracy.
|
||||
|
||||
### Generating an Estimation from Scratch
|
||||
|
||||
#### 1. With a Website URL (Recommended)
|
||||
Providing a URL allows the system to crawl the existing site to understand the "Company DNA" and services.
|
||||
```bash
|
||||
npm run ai-estimate -- "Relaunch der Website mit Fokus auf B2B Leads" --url https://example.com
|
||||
```
|
||||
|
||||
#### 2. From a Text File
|
||||
If you have a long briefing in a `.txt` file:
|
||||
```bash
|
||||
npm run ai-estimate -- @briefing.txt --url https://example.com
|
||||
```
|
||||
|
||||
#### 3. Text-Only (No URL)
|
||||
If no URL is provided, the system relies entirely on your briefing text.
|
||||
```bash
|
||||
npm run ai-estimate -- "Neuentwicklung eines Portals für XYZ"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 Output Modes
|
||||
|
||||
The system can generate two types of documents:
|
||||
|
||||
### 1. Full Quote (Default)
|
||||
Includes everything: Front Page, Briefing Analysis, Vision, Sitemap, Technical Principles, Detailed Pricing, Roadmap, and Legal Terms (AGB).
|
||||
```bash
|
||||
npm run ai-estimate -- "Project Briefing"
|
||||
```
|
||||
|
||||
### 2. Estimation Only
|
||||
A condensed version excluding legal terms and deep technical principles. Focuses purely on the strategic fit and the price.
|
||||
```bash
|
||||
npm run ai-estimate -- "Project Briefing" --estimation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Cache & Cache Management
|
||||
|
||||
To save costs and time, all AI responses and crawl results are cached in the `.cache` directory.
|
||||
|
||||
### Regenerating with Cached Data
|
||||
If you run the same command again (identical briefing and URL), the system will use the cached results and won't call the AI APIs again. This is useful if you want to tweak the PDF layout without spending tokens.
|
||||
|
||||
### Forcing a Refresh
|
||||
To ignore the cache and get a fresh AI consultation:
|
||||
```bash
|
||||
npm run ai-estimate -- "Project Briefing" --clear-cache
|
||||
```
|
||||
|
||||
### Manual Tweaking (JSON State)
|
||||
Every run saves a detailed state to `out/estimations/json/[Company]_[Timestamp].json`.
|
||||
If you want to manually edit the AI's results (e.g., fix a typo in the sitemap or description), you can edit this JSON file and then regenerate the PDF from it:
|
||||
|
||||
```bash
|
||||
npm run ai-estimate -- --json out/estimations/json/Your_Project.json
|
||||
```
|
||||
*(Add `--estimation` if you want the condensed version).*
|
||||
|
||||
---
|
||||
|
||||
## 💡 Advanced Options
|
||||
|
||||
- `--comments "..."`: Add manual notes that the AI should consider (e.g., "Customer prefers a minimalist blue theme").
|
||||
- `--clear-cache`: Purges all cached data for this project before starting.
|
||||
- `--url [URL]`: Explicitly sets the crawl target (auto-discovered from briefing if omitted).
|
||||
@@ -23,6 +23,8 @@ async function main() {
|
||||
|
||||
let jsonStatePath: string | null = null;
|
||||
|
||||
const isEstimation = process.argv.includes('--estimation') || process.argv.includes('-E');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
@@ -34,6 +36,8 @@ async function main() {
|
||||
cacheKey = args[++i];
|
||||
} else if (arg === '--json') {
|
||||
jsonStatePath = args[++i];
|
||||
} else if (arg === '--estimation' || arg === '-E') {
|
||||
// Handled above
|
||||
} else if (!arg.startsWith('--')) {
|
||||
briefing = arg;
|
||||
}
|
||||
@@ -140,7 +144,8 @@ async function main() {
|
||||
console.log(`📦 Saved detailed state to: ${finalJsonPath}`);
|
||||
console.log('📄 Generating PDF estimation...');
|
||||
try {
|
||||
execSync(`npx tsx ./scripts/generate-quote.ts --input ${tempJsonPath}`, { stdio: 'inherit' });
|
||||
const genArgs = isEstimation ? '--estimation' : '';
|
||||
execSync(`npx tsx ./scripts/generate-quote.ts --input ${tempJsonPath} ${genArgs}`, { stdio: 'inherit' });
|
||||
} finally {
|
||||
// await fs.unlink(tempJsonPath);
|
||||
}
|
||||
@@ -243,7 +248,17 @@ async function performCrawl(url: string): Promise<string> {
|
||||
return summary + pages.map(p => `--- PAGE: ${p.url} ---\n${p.content}`).join('\n\n');
|
||||
}
|
||||
|
||||
async function getAiEstimation(briefing: string, distilledCrawl: string, comments: string | null, apiKey: string, principles: string, techStandards: string, tone: 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
|
||||
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) => {
|
||||
let usage = { prompt: 0, completion: 0, cost: 0 };
|
||||
const addUsage = (data: any) => {
|
||||
if (data?.usage) {
|
||||
@@ -316,7 +331,7 @@ Focus 100% on the BRIEFING text provided by the user. Use the DISTILLED_CRAWL on
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p1Resp.data);
|
||||
const facts = JSON.parse(p1Resp.data.choices[0].message.content);
|
||||
const facts = JSON.parse(cleanJson(p1Resp.data.choices[0].message.content));
|
||||
|
||||
// 2. PASS 2: Feature Deep-Dive
|
||||
console.log(' ↳ Pass 2: Feature Deep-Dive...');
|
||||
@@ -348,7 +363,7 @@ ${JSON.stringify(facts, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p2Resp.data);
|
||||
const details = JSON.parse(p2Resp.data.choices[0].message.content);
|
||||
const details = JSON.parse(cleanJson(p2Resp.data.choices[0].message.content));
|
||||
|
||||
// 3. PASS 3: Strategic Content (Bespoke Strategy)
|
||||
console.log(' ↳ Pass 3: Strategic Content (Bespoke Strategy)...');
|
||||
@@ -360,19 +375,17 @@ Analyze the BRIEFING and the EXISTING WEBSITE context.
|
||||
${tone}
|
||||
|
||||
### OBJECTIVE:
|
||||
1. **briefingSummary**: A deep, respectful summary of the status quo and the target state.
|
||||
- **LENGTH**: EXACTLY TWO PARAGRAPHS. Minimum 8 sentences total.
|
||||
- **MIRROR TEST**: Acknowledge the EXISTING website specifically. Why does the new project make sense NOW?
|
||||
- **ABSOLUTE RULE**: DO NOT claim "keine digitale Repräsentation", "erstmals abgebildet", "Erstplatzierung" or "kommunikative Lücke" regarding existence if Pass 1 identified this as a RELAUNCH (isRelaunch=true). EXPLICITLY acknowledge the existing context and the NEED FOR EVOLUTION/MODERNIZATION.
|
||||
- **RESPECT**: Explicitly incorporate the customer's expressed wishes.
|
||||
- **ABSOLUTE RULE**: DO NOT INVENT DETAILS. Do not mention specific people (e.g., "Frieder Helmich"), software versions, or internal details NOT present in the briefing.
|
||||
- **TONE**: Natural Ich-Form. Clear, direct, zero marketing fluff.
|
||||
2. **designVision**: A high-density, professional vision of the future execution.
|
||||
- **LENGTH**: EXACTLY TWO PARAGRAPHS. Minimum 6 sentences total.
|
||||
- **INVESTMENT VALUE**: Plant a clear picture of a stable, high-quality system.
|
||||
- **TECHNICAL PRECISION**: Focus on execution (Typografie, Visual Logic, Performance).
|
||||
- **NO FLUFF**: Do NOT focus on "Full-Screen Hero Video" as the main thing. Focus on the FIRM's essence and how we translate it into a professional tool.
|
||||
- **ABSOLUTE RULE**: NO HALLUCINATIONS. Stay general yet precise. No "Verzicht auf Stockmaterial" unless explicitly stated.
|
||||
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". 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):
|
||||
{
|
||||
@@ -389,7 +402,7 @@ ${tone}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p3Resp.data);
|
||||
const strategy = JSON.parse(p3Resp.data.choices[0].message.content);
|
||||
const strategy = JSON.parse(cleanJson(p3Resp.data.choices[0].message.content));
|
||||
|
||||
// 4. PASS 4: Information Architecture (Sitemap)
|
||||
console.log(' ↳ Pass 4: Information Architecture...');
|
||||
@@ -418,7 +431,7 @@ ${JSON.stringify({ facts, strategy }, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p4Resp.data);
|
||||
const ia = JSON.parse(p4Resp.data.choices[0].message.content);
|
||||
const ia = JSON.parse(cleanJson(p4Resp.data.choices[0].message.content));
|
||||
|
||||
// 5. PASS 5: Position Synthesis & Pricing Transparency
|
||||
console.log(' ↳ Pass 5: Position Synthesis...');
|
||||
@@ -443,14 +456,15 @@ Each position in the quote must be perfectly justified and detailed.
|
||||
### RULES FOR positionDescriptions (STRICT):
|
||||
1. **NO "ICH-FORM"**: Do NOT use "Ich" or "Mein". Lead with the action or component.
|
||||
2. **CONCISE & ITEM-BASED**: Use short, technical sentences. Focus on WHAT is delivered.
|
||||
3. **ZERO GENERALIZATION**: Do NOT say "Verschiedene Funktionen".
|
||||
3. **ZERO GENERALIZATION**: Do NOT say "Verschiedene Funktionen" or "Optimierte Darstellung". Name the things.
|
||||
4. **ITEMIZED SYNTHESIS**: Mention EVERY component selected in Pass 1.
|
||||
5. **HARD SPECIFICS**: Preserve technical details from the briefing (e.g., "110 kV", "HDD-Bohrtechnik").
|
||||
6. **STYLE**: Direct, engineering-grade, 100% GERMAN.
|
||||
5. **HARD SPECIFICS**: Preserve technical details from the briefing and CURRENT WEBSITE (distilledCrawl). If they mention "HDD-Bohrtechnik" or "110kV Kabelanlagen", IT MUST BE IN THE DESCRIPTION.
|
||||
6. **INDUSTRIAL AMBITION**: Describe it as a high-end technical solution, not a cheap website.
|
||||
7. **SPECIFIC - PAGES**: For "Individuelle Seiten", list the pages as a comma-separated list.
|
||||
8. **SPECIFIC - HOSTING**: Always append: "Inkl. 20GB Speicher."
|
||||
9. **SPECIFIC - LOGIC**: Describe the ACTUAL logic. NEVER use generic terms.
|
||||
10. **STRICT KEYS**: Keys MUST be EXACTLY the ones defined in POSITION TITLES.
|
||||
11. **AGB BAN (CRITICAL)**: NEVER mention "Allgemeine Geschäftsbedingungen" or "AGGs" in any text. They are NOT part of this offer.
|
||||
|
||||
### EXAMPLES (FEW-SHOT):
|
||||
- **BAD**: "Ich entwickle die Seiten: Startseite, Leistungen, Kontakt."
|
||||
@@ -481,7 +495,7 @@ ${JSON.stringify({ facts, details, strategy, ia }, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p5Resp.data);
|
||||
const positionsData = JSON.parse(p5Resp.data.choices[0].message.content);
|
||||
const positionsData = JSON.parse(cleanJson(p5Resp.data.choices[0].message.content));
|
||||
|
||||
// 6. PASS 6: The Industrial Critic
|
||||
console.log(' ↳ Pass 6: The Industrial Critic (Quality Gate)...');
|
||||
@@ -492,10 +506,13 @@ 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 or uses phrases like "Da aktuell keine digitale Repräsentation vorliegt", "erstmals abgebildet", "Erstplatzierung" or "Lücke schließen" (regarding existence).
|
||||
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 "React", "Next.js", "TypeScript", "Tailwind" or other tech-stack details are mentioned. Focus on Concept & Result.
|
||||
4. **Length Check**: Briefing and Vision MUST be significantly long (EXACTLY 2 paragraphs each, minimum 8 sentences for briefing, 6 for vision).
|
||||
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.
|
||||
|
||||
### MISSION:
|
||||
Return updated fields ONLY. Specifically focus on hardening 'positionDescriptions', 'sitemap', 'briefingSummary', and 'designVision'.
|
||||
@@ -509,7 +526,7 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p6Resp.data);
|
||||
const reflection = JSON.parse(p6Resp.data.choices[0].message.content);
|
||||
const reflection = JSON.parse(cleanJson(p6Resp.data.choices[0].message.content));
|
||||
|
||||
// 6. Reflection Merge Utility
|
||||
const mergeReflection = (state: any, reflection: any) => {
|
||||
|
||||
@@ -16,13 +16,13 @@ export const COLORS = {
|
||||
};
|
||||
|
||||
export const FONT_SIZES = {
|
||||
H1: 24,
|
||||
H2: 18,
|
||||
H3: 12,
|
||||
BODY: 9,
|
||||
TINY: 7,
|
||||
SUB: 8,
|
||||
BLUEPRINT: 5
|
||||
H1: 28,
|
||||
H2: 20,
|
||||
H3: 14,
|
||||
BODY: 11,
|
||||
TINY: 9,
|
||||
SUB: 10,
|
||||
BLUEPRINT: 8
|
||||
};
|
||||
|
||||
// Register a more technical font if possible, or use Helvetica with varying weights
|
||||
@@ -94,8 +94,7 @@ export const pdfStyles = StyleSheet.create({
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 4,
|
||||
color: COLORS.CHARCOAL,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
subTitle: {
|
||||
fontSize: FONT_SIZES.BODY,
|
||||
|
||||
@@ -6,14 +6,11 @@ import { IndustrialListItem, IndustrialCard, Divider, COLORS, FONT_SIZES } from
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
industrialTitle: { fontSize: FONT_SIZES.H1, fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 6, letterSpacing: -1 },
|
||||
industrialSubtitle: { fontSize: FONT_SIZES.SUB, fontWeight: 'bold', color: COLORS.TEXT_LIGHT, textTransform: 'uppercase', marginBottom: 16, letterSpacing: 2 },
|
||||
industrialTextLead: { fontSize: FONT_SIZES.H3, color: COLORS.TEXT_MAIN, lineHeight: 1.6, marginBottom: 12 },
|
||||
industrialText: { fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 8 },
|
||||
industrialGrid2: { flexDirection: 'row', gap: 32 },
|
||||
industrialCol: { width: '48%' },
|
||||
darkBox: { marginTop: 32, padding: 24, backgroundColor: COLORS.CHARCOAL, color: COLORS.WHITE },
|
||||
darkTitle: { fontSize: FONT_SIZES.H2, fontWeight: 'bold', color: COLORS.WHITE, marginBottom: 8 },
|
||||
darkText: { fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_LIGHT, lineHeight: 1.6 },
|
||||
industrialSubtitle: { fontSize: FONT_SIZES.SUB, fontWeight: 'bold', color: COLORS.TEXT_LIGHT, marginBottom: 16, letterSpacing: 0.5 },
|
||||
industrialTextLead: { fontSize: FONT_SIZES.H3, color: COLORS.TEXT_MAIN, lineHeight: 1.6, marginBottom: 16 },
|
||||
industrialText: { fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 12 },
|
||||
industrialGrid2: { flexDirection: 'row' },
|
||||
industrialCol: { width: '46%' },
|
||||
industrialBulletBox: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
@@ -26,30 +23,41 @@ const styles = StyleSheet.create({
|
||||
export const AboutModule = () => (
|
||||
<>
|
||||
<PDFText style={styles.industrialTitle}>Über mich</PDFText>
|
||||
<PDFText style={styles.industrialSubtitle}>Direkt. Sauber. Verantwortlich.</PDFText>
|
||||
<PDFText style={styles.industrialSubtitle}>Senior Software Developer & Technischer Partner</PDFText>
|
||||
<Divider style={{ marginVertical: 16, backgroundColor: COLORS.GRID }} />
|
||||
<PDFView style={[styles.industrialGrid2, { marginTop: 20 }]}>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<PDFText style={styles.industrialTextLead}>Seit 15 Jahren entstehen unter meiner Leitung Websysteme für Agenturen, Konzerne und Startups. Ich arbeite bewusst alleine, um die volle Verantwortung für jedes Projekt zu tragen.</PDFText>
|
||||
<PDFText style={styles.industrialText}>Technik scheitert selten an Bits und Bytes, sondern meist an unklaren Zuständigkeiten. Als Ihr direkter Ansprechpartner treffe ich die notwendigen Entscheidungen und löse technische Probleme ohne Umwege.</PDFText>
|
||||
<PDFView style={{ marginTop: 16 }}>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold' }]}>Mein Standard:</PDFText>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Code-Lieferung ohne technische Altlasten.</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Entwicklung von Systemen, die autark operieren.</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Verzicht auf Overhead und Kommunikationsverluste.</PDFText></IndustrialListItem>
|
||||
|
||||
<PDFView style={{ marginTop: 24 }}>
|
||||
<PDFText style={styles.industrialTextLead}>
|
||||
Ich begleite mittelständische Unternehmen und Agenturen bei der Realisierung anspruchsvoller Web-Projekte. Als Senior Software Developer mit über 15 Jahren Erfahrung biete ich das gesamte technische Spektrum – von der Architektur bis zum fertigen Produkt.
|
||||
</PDFText>
|
||||
|
||||
<PDFView style={[styles.industrialGrid2, { marginTop: 20 }]}>
|
||||
<PDFView style={[styles.industrialCol, { marginRight: '8%' }]}>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 8 }]}>Erfahrung & Substanz</PDFText>
|
||||
<PDFText style={styles.industrialText}>
|
||||
Mein Weg führte mich durch alle Ebenen der Webentwicklung: vom Junior zum Teamleiter, von der kleinen Kreativagentur bis zur Softwareentwicklung für internationale Konzerne.
|
||||
</PDFText>
|
||||
<PDFText style={styles.industrialText}>
|
||||
Ich kenne die Prozesse hinter großen Systemen ebenso gut wie die Agilität, die im Mittelstand gefordert wird. Dieses Wissen nutze ich heute, um Lösungen zu bauen, die technologisch auf Augenhöhe mit den Großen sind, aber ohne deren bürokratischen Overhead auskommen.
|
||||
</PDFText>
|
||||
</PDFView>
|
||||
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 8 }]}>Fokus Einzelentwicklung</PDFText>
|
||||
<PDFText style={styles.industrialText}>
|
||||
Ich arbeite bewusst als Einzelentwickler. Für Sie bedeutet das: maximale Geschwindigkeit und volle Verantwortung. Es gibt keine Informationsverluste zwischen Projektmanagern, Vertrieblern und Entwicklern.
|
||||
</PDFText>
|
||||
<PDFText style={styles.industrialText}>
|
||||
Sie haben einen direkten technischen Sparringspartner, der Ihren Code von der ersten bis zur letzten Zeile kennt und dafür einsteht. Diese Unmittelbarkeit garantiert Ergebnisse, die technisch sauber und wirtschaftlich sinnvoll sind.
|
||||
</PDFText>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<IndustrialCard title="ZUSTÄNDIGKEIT">
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Ich bin der einzige Entwickler.</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Ich antworte direkt auf technische Fragen.</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Ich garantiere die Umsetzung.</PDFText></IndustrialListItem>
|
||||
</IndustrialCard>
|
||||
<IndustrialCard title="KEIN BALLAST" style={{ backgroundColor: COLORS.WHITE, borderColor: COLORS.DIVIDER }}>
|
||||
<PDFText style={[styles.industrialText, { color: COLORS.TEXT_LIGHT }]}>Keine Projektmanager.</PDFText>
|
||||
<PDFText style={[styles.industrialText, { color: COLORS.TEXT_LIGHT }]}>Keine Vertriebler.</PDFText>
|
||||
<PDFText style={[styles.industrialText, { color: COLORS.TEXT_LIGHT }]}>Kein Ticket-Chaos.</PDFText>
|
||||
</IndustrialCard>
|
||||
|
||||
<PDFView style={{ marginTop: 32, paddingVertical: 16, borderTopWidth: 1, borderTopColor: COLORS.GRID }}>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 4 }]}>Meine Zusage</PDFText>
|
||||
<PDFText style={styles.industrialText}>
|
||||
Ich baue keine Prototypen, die beim ersten Nutzeransturm zusammenbrechen. Ich liefere produktionsreife Software, die wartbar bleibt und mit Ihrem Unternehmen mitwächst. Direkt. Sauber. Ohne Ballast.
|
||||
</PDFText>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</>
|
||||
@@ -57,34 +65,34 @@ export const AboutModule = () => (
|
||||
|
||||
export const CrossSellModule = ({ state }: any) => {
|
||||
const isWebsite = state.projectType === 'website';
|
||||
const title = isWebsite ? "Digitale Routine" : "Websites & Ökosysteme";
|
||||
const subtitle = isWebsite ? "Automatisierung statt Fleißarbeit" : "Technische Infrastruktur ohne Kompromisse";
|
||||
const title = isWebsite ? "Routine-Automatisierung" : "Websites & Ökosysteme";
|
||||
const subtitle = isWebsite ? "Maßgeschneiderte Software zur Prozessoptimierung" : "Technische Infrastruktur ohne Kompromisse";
|
||||
|
||||
return (
|
||||
<>
|
||||
<PDFText style={styles.industrialTitle}>{title}</PDFText>
|
||||
<PDFText style={styles.industrialSubtitle}>{subtitle}</PDFText>
|
||||
<Divider style={{ marginVertical: 16, backgroundColor: COLORS.GRID }} />
|
||||
<PDFView style={[styles.industrialGrid2, { marginTop: 12 }]} >
|
||||
<PDFView style={[styles.industrialGrid2, { marginTop: 16 }]} >
|
||||
{isWebsite ? (
|
||||
<>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<PDFText style={styles.industrialTextLead}>Manuelle Abläufe binden Kapazitäten. Durch maßgeschneiderte Software ersetze ich fehleranfällige Prozesse und eliminiere Zeitfresser in Ihrem Unternehmen.</PDFText>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold' }]}>Ziel ist der Gewinn wertvoller Arbeitszeit. Digitalisierung ordnet das Chaos.</PDFText>
|
||||
<PDFView style={styles.darkBox}>
|
||||
<PDFText style={styles.darkTitle}>Individuelle Prüfung</PDFText>
|
||||
<PDFText style={styles.darkText}>Ich analysiere Ihren spezifischen Prozess auf technisches Automatisierungspotenzial. Das Ergebnis liefert Klarheit darüber, ob eine Umsetzung wirtschaftlich sinnvoll ist.</PDFText>
|
||||
<PDFView style={[styles.industrialCol, { marginRight: '8%' }]}>
|
||||
<PDFText style={styles.industrialTextLead}>Manuelle Alltags-Aufgaben sind teuer und fehleranfällig. In einer separaten Beauftragung entwickle ich maßgeschneiderte „Helfer“, die Ihre Routine-Prozesse lautlos im Hintergrund automatisieren.</PDFText>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold' }]}>Keine Abos. Keine komplexen neuen Systeme. Nur 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 Prüfung</PDFText>
|
||||
<PDFText style={styles.industrialText}>Ich analysiere Ihren spezifischen Prozess auf technisches Automatisierungspotenzial. Das Ergebnis liefert Klarheit darüber, ob eine Umsetzung wirtschaftlich sinnvoll ist.</PDFText>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<IndustrialCard title="PDF-GENERATOREN">
|
||||
<PDFText style={styles.industrialText}>Ich automatisiere Angebote und Berichte. Ich reduziere den Zeitaufwand auf 5 Minuten. Ich garantiere fehlerfreie Exporte.</PDFText>
|
||||
<IndustrialCard title="DOKUMENT-AUTOMATION">
|
||||
<PDFText style={styles.industrialText}>Erstellung von PDF-Angeboten, Berichten oder Protokollen in Sekunden statt Stunden.</PDFText>
|
||||
</IndustrialCard>
|
||||
<IndustrialCard title="EXCEL-AUTOMATISIERUNG">
|
||||
<PDFText style={styles.industrialText}>Ich synchronisiere Ihre Datenbestände. Ich schaffe Echtzeit-Transparenz ohne manuelle Listenpflege.</PDFText>
|
||||
<IndustrialCard title="EXCEL-LOGIK">
|
||||
<PDFText style={styles.industrialText}>Intelligente Tabellen und automatisierte Auswertungen Ihrer Bestandsdaten.</PDFText>
|
||||
</IndustrialCard>
|
||||
<IndustrialCard title="DATEN-SCANN">
|
||||
<PDFText style={styles.industrialText}>Ich implementiere Schrifterkennung für analoge Dokumente. Ich übertrage Daten direkt in Ihre Systeme.</PDFText>
|
||||
<IndustrialCard title="KI-ASSISTENZ">
|
||||
<PDFText style={styles.industrialText}>Effizientes Einlesen und Verarbeiten von analogen Dokumenten oder handschriftlichen Notizen.</PDFText>
|
||||
</IndustrialCard>
|
||||
</PDFView>
|
||||
</>
|
||||
|
||||
@@ -7,10 +7,6 @@ import { DocumentTitle, COLORS, FONT_SIZES } from '../SharedUI';
|
||||
const styles = StyleSheet.create({
|
||||
section: { marginBottom: 24 },
|
||||
sectionTitle: { fontSize: FONT_SIZES.BODY + 1, fontWeight: 'bold', marginBottom: 8, color: COLORS.CHARCOAL },
|
||||
configGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginTop: 6 },
|
||||
configItem: { width: '24%', marginBottom: 4 },
|
||||
configLabel: { fontSize: FONT_SIZES.BLUEPRINT, color: COLORS.TEXT_LIGHT, textTransform: 'uppercase', marginBottom: 2 },
|
||||
configValue: { fontSize: FONT_SIZES.TINY, color: COLORS.CHARCOAL, fontWeight: 'bold' },
|
||||
visionText: { fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_MAIN, lineHeight: 1.8, textAlign: 'justify' },
|
||||
});
|
||||
|
||||
@@ -20,22 +16,13 @@ export const BriefingModule = ({ state }: any) => (
|
||||
{state.briefingSummary && (
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={styles.sectionTitle}>Briefing Analyse</PDFText>
|
||||
<PDFText style={{ fontSize: 8, color: '#334155', lineHeight: 1.6, textAlign: 'justify' }}>{state.briefingSummary}</PDFText>
|
||||
<PDFText style={{ fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_MAIN, lineHeight: 1.6, textAlign: 'justify' }}>{state.briefingSummary}</PDFText>
|
||||
</PDFView>
|
||||
)}
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={styles.sectionTitle}>Kern-Informationen</PDFText>
|
||||
<PDFView style={styles.configGrid}>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Kontakt</PDFText><PDFText style={styles.configValue}>{state.personName || "—"}</PDFText></PDFView>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Projekttyp</PDFText><PDFText style={styles.configValue}>{state.isRelaunch ? 'Website Evolution' : (state.statusQuo || 'Neukonzeption')}</PDFText></PDFView>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Mitarbeiter</PDFText><PDFText style={styles.configValue}>{state.employeeCount || "—"}</PDFText></PDFView>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Zeitplan</PDFText><PDFText style={styles.configValue}>{state.deadline || 'Flexibel'}</PDFText></PDFView>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
{state.designVision && (
|
||||
<PDFView style={[styles.section, { padding: 16, borderLeftWidth: 2, borderLeftColor: COLORS.DIVIDER, backgroundColor: COLORS.GRID }]}>
|
||||
<PDFText style={[styles.sectionTitle, { color: COLORS.CHARCOAL }]}>Strategische Vision</PDFText>
|
||||
<PDFText style={styles.visionText}>{state.designVision}</PDFText>
|
||||
<PDFView style={[styles.section, { padding: 12, borderLeftWidth: 2, borderLeftColor: COLORS.DIVIDER, backgroundColor: COLORS.GRID }]}>
|
||||
<PDFText style={[styles.sectionTitle, { color: COLORS.CHARCOAL, marginBottom: 4 }]}>Strategische Vision</PDFText>
|
||||
<PDFText style={[styles.visionText, { lineHeight: 1.6 }]}>{state.designVision}</PDFText>
|
||||
</PDFView>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -97,3 +97,4 @@ export const PrinciplesModule = ({ principles }: any) => (
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ export const FrontPageModule = ({ state, headerIcon, date }: any) => {
|
||||
{headerIcon ? <PDFImage src={headerIcon} style={{ width: 40, height: 40 }} /> : <PDFText style={styles.brandIconText}>M</PDFText>}
|
||||
</PDFView>
|
||||
<PDFText style={[styles.titleProjectName, { fontSize }]}>{fullTitle}</PDFText>
|
||||
<PDFView style={styles.titleDivider} />
|
||||
<PDFView style={{ marginBottom: 40 }} />
|
||||
<PDFText style={styles.titleDate}>{date} | Marc Mintel</PDFText>
|
||||
</PDFView>
|
||||
);
|
||||
|
||||
@@ -5,43 +5,67 @@ import { View as PDFView, Text as PDFText, StyleSheet } from '@react-pdf/rendere
|
||||
import { DocumentTitle, Divider, COLORS, FONT_SIZES } from '../SharedUI';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: { marginBottom: 24 },
|
||||
sitemapTree: { marginTop: 20 },
|
||||
sitemapRootNode: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
|
||||
sitemapRootDot: { width: 6, height: 6, borderRadius: 3, backgroundColor: COLORS.CHARCOAL, marginRight: 10 },
|
||||
sitemapRootTitle: { fontSize: FONT_SIZES.H3, fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: 1, color: COLORS.CHARCOAL },
|
||||
sitemapMainLine: { position: 'absolute', left: 2, top: 20, bottom: 0, width: 0.5, backgroundColor: COLORS.DIVIDER },
|
||||
sitemapBranch: { marginLeft: 20, marginBottom: 12, position: 'relative' },
|
||||
sitemapNode: { flexDirection: 'row', alignItems: 'center', marginBottom: 4 },
|
||||
sitemapRootIcon: { width: 4, height: 4, backgroundColor: COLORS.CHARCOAL, marginRight: 8 },
|
||||
sitemapBranchTitle: { fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.TEXT_MAIN },
|
||||
sitemapLeaf: { marginLeft: 12, borderLeftWidth: 0.5, borderLeftColor: COLORS.DIVIDER, paddingLeft: 12, marginTop: 4 },
|
||||
sitemapLeafNode: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 6 },
|
||||
sitemapLeafPointer: { fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_LIGHT, marginRight: 6 },
|
||||
sitemapLeafTitle: { fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.TEXT_MAIN },
|
||||
sitemapLeafDesc: { fontSize: FONT_SIZES.TINY, color: COLORS.TEXT_DIM, lineHeight: 1.3, marginTop: 1 },
|
||||
section: { marginBottom: 32 },
|
||||
intro: { fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 24, textAlign: 'justify' },
|
||||
sitemapTree: { marginTop: 8 },
|
||||
rootNode: {
|
||||
padding: 12,
|
||||
backgroundColor: COLORS.CHARCOAL,
|
||||
marginBottom: 20,
|
||||
borderLeftWidth: 3,
|
||||
borderLeftColor: COLORS.TEXT_LIGHT
|
||||
},
|
||||
rootTitle: { fontSize: FONT_SIZES.H3, fontWeight: 'bold', color: COLORS.WHITE, letterSpacing: 0.5 },
|
||||
categorySection: { marginBottom: 20 },
|
||||
categoryHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 6,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: COLORS.BLUEPRINT,
|
||||
marginBottom: 10
|
||||
},
|
||||
categoryIcon: { width: 8, height: 8, backgroundColor: COLORS.CHARCOAL, marginRight: 10 },
|
||||
categoryTitle: { fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.CHARCOAL, textTransform: 'uppercase', letterSpacing: 1 },
|
||||
pagesGrid: { flexDirection: 'row', flexWrap: 'wrap' },
|
||||
pageCard: {
|
||||
width: '48%',
|
||||
marginRight: '2%',
|
||||
marginBottom: 12,
|
||||
padding: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: COLORS.GRID,
|
||||
backgroundColor: '#fafafa'
|
||||
},
|
||||
pageTitle: { fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.TEXT_MAIN, marginBottom: 2 },
|
||||
pageDesc: { fontSize: FONT_SIZES.TINY, color: COLORS.TEXT_DIM, lineHeight: 1.4 },
|
||||
});
|
||||
|
||||
export const SitemapModule = ({ state }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Seitenstruktur" />
|
||||
<DocumentTitle title="Informationsarchitektur" />
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={{ fontSize: FONT_SIZES.BODY, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 16 }}>Die folgende Struktur bildet das Fundament für die Benutzerführung und Informationsarchitektur Ihres Projekts.</PDFText>
|
||||
<Divider style={{ marginBottom: 20 }} />
|
||||
<PDFText style={styles.intro}>
|
||||
Die folgende Struktur definiert die logische Hierarchie und Benutzerführung. Sie dient als Bauplan für die technische Umsetzung und stellt sicher, dass alle relevanten Geschäftsbereiche intuitiv auffindbar sind.
|
||||
</PDFText>
|
||||
|
||||
<PDFView style={styles.sitemapTree}>
|
||||
<PDFView style={styles.sitemapRootNode}><PDFView style={styles.sitemapRootDot} /><PDFText style={styles.sitemapRootTitle}>{state.websiteTopic || 'Digitales Ökosystem'}</PDFText></PDFView>
|
||||
<PDFView style={styles.sitemapMainLine} />
|
||||
<PDFView style={styles.rootNode}>
|
||||
<PDFText style={styles.rootTitle}>{state.websiteTopic || 'Digitales Ökosystem'}</PDFText>
|
||||
</PDFView>
|
||||
|
||||
{state.sitemap?.map((cat: any, i: number) => (
|
||||
<PDFView key={i} style={styles.sitemapBranch}>
|
||||
<PDFView style={styles.sitemapNode}><PDFView style={styles.sitemapRootIcon} /><PDFText style={styles.sitemapBranchTitle}>{cat.category}</PDFText></PDFView>
|
||||
<PDFView style={styles.sitemapLeaf}>
|
||||
<PDFView key={i} style={styles.categorySection} wrap={false}>
|
||||
<PDFView style={styles.categoryHeader}>
|
||||
<PDFView style={styles.categoryIcon} />
|
||||
<PDFText style={styles.categoryTitle}>{cat.category}</PDFText>
|
||||
</PDFView>
|
||||
|
||||
<PDFView style={styles.pagesGrid}>
|
||||
{cat.pages.map((p: any, j: number) => (
|
||||
<PDFView key={j} style={styles.sitemapLeafNode}>
|
||||
<PDFText style={styles.sitemapLeafPointer}>└─</PDFText>
|
||||
<PDFView style={{ flex: 1 }}>
|
||||
<PDFText style={styles.sitemapLeafTitle}>{p.title}</PDFText>
|
||||
{p.desc && <PDFText style={styles.sitemapLeafDesc}>{p.desc}</PDFText>}
|
||||
</PDFView>
|
||||
<PDFView key={j} style={[styles.pageCard, j % 2 === 1 ? { marginRight: 0 } : {}]}>
|
||||
<PDFText style={styles.pageTitle}>{p.title}</PDFText>
|
||||
{p.desc && <PDFText style={styles.pageDesc}>{p.desc}</PDFText>}
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
|
||||
Reference in New Issue
Block a user