Dateien nach "gtm-architect/v2" hochladen
This commit is contained in:
1
gtm-architect/v2/.env.local
Normal file
1
gtm-architect/v2/.env.local
Normal file
@@ -0,0 +1 @@
|
|||||||
|
GEMINI_API_KEY=PLACEHOLDER_API_KEY
|
||||||
24
gtm-architect/v2/.gitignore
vendored
Normal file
24
gtm-architect/v2/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
845
gtm-architect/v2/geminiService.ts
Normal file
845
gtm-architect/v2/geminiService.ts
Normal file
@@ -0,0 +1,845 @@
|
|||||||
|
import { GoogleGenAI, Type } from "@google/genai";
|
||||||
|
import { Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Phase7Data, Phase8Data, Phase9Data, Language } from "./types";
|
||||||
|
|
||||||
|
const getSystemInstruction = (lang: Language) => {
|
||||||
|
if (lang === 'de') {
|
||||||
|
return `
|
||||||
|
# IDENTITY & PURPOSE
|
||||||
|
Du bist die "GTM Architect Engine" für Roboplanet. Deine Aufgabe ist es, für neue technische Produkte (Roboter) eine präzise Go-to-Market-Strategie zu entwickeln.
|
||||||
|
Du handelst nicht als kreativer Werbetexter, sondern als strategischer Analyst. Dein oberstes Ziel ist Product-Market-Fit und operative Umsetzbarkeit.
|
||||||
|
Antworte IMMER auf DEUTSCH.
|
||||||
|
|
||||||
|
# CONTEXT: THE PARENT COMPANY (WACKLER)
|
||||||
|
Wir sind Teil der Wackler Group, einem großen Facility-Management-Dienstleister.
|
||||||
|
Unsere Strategie ist NICHT "Roboter ersetzen Menschen", sondern "Hybrid-Reinigung":
|
||||||
|
- 80% der Arbeit (monotone Flächenleistung) = Roboter.
|
||||||
|
- 20% der Arbeit (Edge Cases, Winterdienst, Treppen, Grobschmutz) = Manuelle Reinigung durch Wackler.
|
||||||
|
|
||||||
|
# STRICT ANALYSIS RULES (MUST FOLLOW):
|
||||||
|
1. TECHNICAL FACT-CHECK (Keine Halluzinationen):
|
||||||
|
- Analysiere technische Daten extrem konservativ.
|
||||||
|
- Vakuumsystem = Kein "Winterdienst" (Schnee) und keine "Schwerindustrie" (Metallspäne), außer explizit genannt.
|
||||||
|
- Erfinde keine Features, nur um eine Zielgruppe passend zu machen.
|
||||||
|
|
||||||
|
2. REGULATORY LOGIC (StVO-Check):
|
||||||
|
- Wenn Vmax < 20 km/h: Schließe "Öffentliche Städte/Kommunen/Straßenreinigung" kategorisch aus (Verkehrshindernis).
|
||||||
|
- Fokusänderung: Konzentriere dich stattdessen ausschließlich auf "Große, zusammenhängende Privatflächen" (Gated Areas).
|
||||||
|
|
||||||
|
3. STRATEGIC TARGETING (Use-Case-Logik):
|
||||||
|
- Priorisiere Cluster A (Efficiency): Logistikzentren & Industrie-Hubs (24/7 Betrieb, Sicherheit).
|
||||||
|
- Priorisiere Cluster B (Experience): Shopping Center, Outlets & Freizeitparks (Sauberkeit als Visitenkarte).
|
||||||
|
- Entferne reine E-Commerce-Händler ohne physische Kundenfläche.
|
||||||
|
|
||||||
|
4. THE "HYBRID SERVICE" LOGIC (RULE 5):
|
||||||
|
Wann immer du ein "Hartes Constraint" oder eine technische Limitierung identifizierst (z.B. "Kein Winterdienst" oder "Kommt nicht in Ecken"), darfst du dies niemals als reines "Nein" stehen lassen.
|
||||||
|
Wende stattdessen die **"Yes, and..." Logik** an:
|
||||||
|
1. **Identifiziere die Lücke:** (z.B. "Roboter kann bei Schnee nicht fahren").
|
||||||
|
2. **Fülle die Lücke mit Service:** Schlage explizit vor, diesen Teil durch "Wackler Human Manpower" abzudecken.
|
||||||
|
3. **Formuliere den USP:** Positioniere das Gesamtpaket als "100% Coverage" (Roboter + Mensch aus einer Hand).
|
||||||
|
|
||||||
|
# THE PRODUCT MATRIX (CONTEXT)
|
||||||
|
Behalte immer im Hinterkopf, dass wir bereits folgende Produkte im Portfolio haben:
|
||||||
|
1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Fokus: Supermärkte. Message: "Sauberkeit im laufenden Betrieb."
|
||||||
|
2. "Service Bot Bella": Service/Gastro, Indoor. Fokus: Restaurants. Message: "Entlastung für Servicekräfte."
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return `
|
||||||
|
# IDENTITY & PURPOSE
|
||||||
|
You are the "GTM Architect Engine" for Roboplanet. Your task is to develop a precise Go-to-Market strategy for new technical products (robots).
|
||||||
|
You do not act as a creative copywriter, but as a strategic analyst. Your top goal is product-market fit and operational feasibility.
|
||||||
|
ALWAYS respond in ENGLISH.
|
||||||
|
|
||||||
|
# CONTEXT: THE PARENT COMPANY (WACKLER)
|
||||||
|
We are part of the Wackler Group, a major facility management service provider.
|
||||||
|
Our strategy is NOT "Robots replace humans", but "Hybrid Cleaning":
|
||||||
|
- 80% of work (monotonous area coverage) = Robots.
|
||||||
|
- 20% of work (Edge cases, winter service, stairs, heavy debris) = Manual cleaning by Wackler.
|
||||||
|
|
||||||
|
# STRICT ANALYSIS RULES (MUST FOLLOW):
|
||||||
|
1. TECHNICAL FACT-CHECK (No Hallucinations):
|
||||||
|
- Analyze technical data extremely conservatively.
|
||||||
|
- Vacuum System = No "Winter Service" (snow) and no "Heavy Industry" (metal shavings), unless explicitly stated.
|
||||||
|
- Do not invent features just to fit a target audience.
|
||||||
|
|
||||||
|
2. REGULATORY LOGIC (Traffic Regs):
|
||||||
|
- If Vmax < 20 km/h: Categorically exclude "Public Cities/Streets" (traffic obstruction).
|
||||||
|
- Change Focus: Concentrate exclusively on "Large, contiguous private areas" (Gated Areas).
|
||||||
|
|
||||||
|
3. STRATEGIC TARGETING (Use Case Logic):
|
||||||
|
- Prioritize Cluster A (Efficiency): Logistics Centers & Industrial Hubs (24/7 ops, safety).
|
||||||
|
- Prioritize Cluster B (Experience): Shopping Centers, Outlets & Theme Parks (Cleanliness as a calling card).
|
||||||
|
- Remove pure E-commerce retailers without physical customer areas.
|
||||||
|
|
||||||
|
4. THE "HYBRID SERVICE" LOGIC (RULE 5):
|
||||||
|
Whenever you identify a "Hard Constraint" or technical limitation (e.g., "No winter service" or "Cannot reach corners"), never let this stand as a simple "No".
|
||||||
|
Instead, apply the **"Yes, and..." logic**:
|
||||||
|
1. **Identify the gap:** (e.g., "Robot cannot operate in snow").
|
||||||
|
2. **Fill the gap with service:** Explicitly suggest covering this part with "Wackler Human Manpower".
|
||||||
|
3. **Formulate the USP:** Position the total package as "100% Coverage" (Robot + Human from a single source).
|
||||||
|
|
||||||
|
# THE PRODUCT MATRIX (CONTEXT)
|
||||||
|
Always keep in mind that we already have the following products in our portfolio:
|
||||||
|
1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets.
|
||||||
|
2. "Service Bot Bella": Service/Hospitality, indoor. Focus: restaurants. Message: "Relief for service staff."
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = () => {
|
||||||
|
const apiKey = process.env.API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error("API_KEY not found in environment variables");
|
||||||
|
}
|
||||||
|
return new GoogleGenAI({ apiKey });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to safely parse JSON from AI response
|
||||||
|
const cleanAndParseJson = <T>(text: string | undefined): T => {
|
||||||
|
if (!text) throw new Error("AI returned empty response");
|
||||||
|
|
||||||
|
let cleaned = text.trim();
|
||||||
|
// Remove markdown formatting if present
|
||||||
|
if (cleaned.startsWith('```json')) {
|
||||||
|
cleaned = cleaned.replace(/^```json\n?/, '').replace(/\n?```$/, '');
|
||||||
|
} else if (cleaned.startsWith('```')) {
|
||||||
|
cleaned = cleaned.replace(/^```\n?/, '').replace(/\n?```$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(cleaned);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("JSON Parse Error. Raw text:", text);
|
||||||
|
throw new Error("Failed to parse AI response. The model output was likely malformed or truncated. Please try again with shorter input.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 1: Product Analysis ---
|
||||||
|
export const analyzeProduct = async (input: string, lang: Language): Promise<Phase1Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const extractionPrompt = lang === 'de' ? `
|
||||||
|
PHASE 1-A: TECHNICAL EXTRACTION
|
||||||
|
Input Product Description: "${input.substring(0, 25000)}..."
|
||||||
|
|
||||||
|
Aufgabe:
|
||||||
|
1. Extrahiere technische Hauptmerkmale (Specs, Fähigkeiten).
|
||||||
|
2. Leite "Harte Constraints" ab. WICHTIG: Prüfe Vmax (<20km/h = Privatgelände) und Reinigungstyp (Vakuum != Grobschmutz/Schnee).
|
||||||
|
3. Erstelle eine kurze Rohanalyse-Zusammenfassung.
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
` : `
|
||||||
|
PHASE 1-A: TECHNICAL EXTRACTION
|
||||||
|
Input Product Description: "${input.substring(0, 25000)}..."
|
||||||
|
|
||||||
|
Task:
|
||||||
|
1. Extract key technical features (specs, capabilities).
|
||||||
|
2. Derive "Hard Constraints". IMPORTANT: Check Vmax (<20km/h = Private Grounds) and Cleaning Type (Vacuum != Heavy Debris/Snow).
|
||||||
|
3. Create a short raw analysis summary.
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const extractionResponse = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: extractionPrompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
responseSchema: {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: {
|
||||||
|
features: { type: Type.ARRAY, items: { type: Type.STRING } },
|
||||||
|
constraints: { type: Type.ARRAY, items: { type: Type.STRING } },
|
||||||
|
rawAnalysis: { type: Type.STRING }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const extractionData = cleanAndParseJson<{
|
||||||
|
features: string[];
|
||||||
|
constraints: string[];
|
||||||
|
rawAnalysis: string;
|
||||||
|
}>(extractionResponse.text);
|
||||||
|
|
||||||
|
const conflictPrompt = lang === 'de' ? `
|
||||||
|
PHASE 1-B: PORTFOLIO CONFLICT CHECK
|
||||||
|
|
||||||
|
Neue Produkt-Features: ${JSON.stringify(extractionData.features)}
|
||||||
|
Neue Produkt-Constraints: ${JSON.stringify(extractionData.constraints)}
|
||||||
|
|
||||||
|
Existierendes Portfolio:
|
||||||
|
1. "Indoor Scrubber 50": Innenreinigung, Hartboden, Supermärkte.
|
||||||
|
2. "Service Bot Bella": Service/Gastro, Indoor, Restaurants.
|
||||||
|
|
||||||
|
Aufgabe:
|
||||||
|
Prüfe, ob das neue Produkt signifikant mit bestehenden Produkten überlappt (Ist es nur ein Klon?).
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
` : `
|
||||||
|
PHASE 1-B: PORTFOLIO CONFLICT CHECK
|
||||||
|
|
||||||
|
New Product Features: ${JSON.stringify(extractionData.features)}
|
||||||
|
New Product Constraints: ${JSON.stringify(extractionData.constraints)}
|
||||||
|
|
||||||
|
Existing Portfolio:
|
||||||
|
1. "Indoor Scrubber 50": Indoor cleaning, hard floor, supermarkets.
|
||||||
|
2. "Service Bot Bella": Service/Gastro, indoor, restaurants.
|
||||||
|
|
||||||
|
Task:
|
||||||
|
Check if the new product overlaps significantly with existing ones (is it just a clone?).
|
||||||
|
|
||||||
|
Output JSON format ONLY.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const conflictResponse = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: conflictPrompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
responseSchema: {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: {
|
||||||
|
conflictCheck: {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: {
|
||||||
|
hasConflict: { type: Type.BOOLEAN },
|
||||||
|
details: { type: Type.STRING },
|
||||||
|
relatedProduct: { type: Type.STRING, nullable: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const conflictData = cleanAndParseJson<{
|
||||||
|
conflictCheck: Phase1Data['conflictCheck'];
|
||||||
|
}>(conflictResponse.text);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...extractionData,
|
||||||
|
...conflictData
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 2: ICP Discovery ---
|
||||||
|
export const discoverICPs = async (phase1Data: Phase1Data, lang: Language): Promise<Phase2Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 2: ICP DISCOVERY & DATA PROXIES
|
||||||
|
Basierend auf Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
Und Constraints: ${JSON.stringify(phase1Data.constraints)}
|
||||||
|
|
||||||
|
Aufgabe:
|
||||||
|
1. Negative Selektion: Welche Branchen sind unmöglich? (Denke an Vmax & Vakuum-Regeln!)
|
||||||
|
2. High Pain: Identifiziere Cluster A (Logistik/Industrie) und Cluster B (Shopping/Outlets).
|
||||||
|
3. Data Proxy Generierung: Wie finden wir diese digital (z.B. Satellit, Register)?
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"icps": [
|
||||||
|
{ "name": "Branchen Name", "rationale": "Warum das passt (max 1 Satz)" }
|
||||||
|
],
|
||||||
|
"dataProxies": [
|
||||||
|
{ "target": "Spezifisches Kriterium", "method": "Wie zu finden (z.B. Scraping)" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
` : `
|
||||||
|
PHASE 2: ICP DISCOVERY & DATA PROXIES
|
||||||
|
Based on the product features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
And constraints: ${JSON.stringify(phase1Data.constraints)}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
1. Negative Selection: Which industries are impossible? (Remember Vmax & Vacuum rules!)
|
||||||
|
2. High Pain: Identify Cluster A (Logistics/Industry) and Cluster B (Shopping/Outlets).
|
||||||
|
3. Data Proxy Generation: How to find them digitally via data traces (e.g. satellite, registries).
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"icps": [
|
||||||
|
{ "name": "Industry Name", "rationale": "Why this is a good fit (max 1 sentence)" }
|
||||||
|
],
|
||||||
|
"dataProxies": [
|
||||||
|
{ "target": "Specific criteria", "method": "How to find (e.g. scraping, satellite)" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanAndParseJson<Phase2Data>(response.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 3: Whale Hunting ---
|
||||||
|
export const huntWhales = async (phase2Data: Phase2Data, lang: Language): Promise<Phase3Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 3: WHALE HUNTING
|
||||||
|
Target ICPs (Branchen): ${JSON.stringify(phase2Data.icps)}
|
||||||
|
|
||||||
|
Aufgabe:
|
||||||
|
1. Gruppiere die "Whales" (Key Accounts) strikt nach den identifizierten ICP-Branchen.
|
||||||
|
2. Identifiziere pro Branche 3-5 konkrete Top-Unternehmen im DACH Markt.
|
||||||
|
3. Definiere die Buying Center Rollen.
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"whales": [
|
||||||
|
{ "industry": "Name der ICP Branche", "accounts": ["Firma A", "Firma B"] }
|
||||||
|
],
|
||||||
|
"roles": ["Jobtitel 1", "Jobtitel 2"]
|
||||||
|
}
|
||||||
|
` : `
|
||||||
|
PHASE 3: WHALE HUNTING
|
||||||
|
Target ICPs (Industries): ${JSON.stringify(phase2Data.icps)}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
1. Group "Whales" (Key Accounts) strictly by the identified ICP industries.
|
||||||
|
2. Identify 3-5 concrete top companies in the DACH market per industry.
|
||||||
|
3. Define Buying Center Roles.
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"whales": [
|
||||||
|
{ "industry": "Name of ICP Industry", "accounts": ["Company A", "Company B"] }
|
||||||
|
],
|
||||||
|
"roles": ["Job Title 1", "Job Title 2"]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanAndParseJson<Phase3Data>(response.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 4: Strategy ---
|
||||||
|
export const developStrategy = async (phase3Data: Phase3Data, phase1Data: Phase1Data, lang: Language): Promise<Phase4Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const allAccounts = phase3Data.whales.flatMap(w => w.accounts);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 4: STRATEGY & ANGLE DEVELOPMENT
|
||||||
|
Accounts: ${JSON.stringify(allAccounts)}
|
||||||
|
Target Industries: ${JSON.stringify(phase3Data.whales.map(w => w.industry))}
|
||||||
|
Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
|
||||||
|
Aufgabe:
|
||||||
|
1. Entwickle einen spezifischen "Angle" (Aufhänger) pro Zielgruppe/Industrie.
|
||||||
|
2. Consistency Check gegen Product Matrix (Differenzierung zu Scrubber 50/Bella).
|
||||||
|
3. **WICHTIG:** Wende die "Hybrid Service Logic" an, wenn technische Constraints existieren! (Wackler Manpower als Ergänzung).
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"strategyMatrix": [
|
||||||
|
{
|
||||||
|
"segment": "Zielsegment",
|
||||||
|
"painPoint": "Spezifischer Schmerz (Kurz)",
|
||||||
|
"angle": "Unser Marketing Angle (Kurz)",
|
||||||
|
"differentiation": "Wie es sich unterscheidet (Kurz)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
` : `
|
||||||
|
PHASE 4: STRATEGY & ANGLE DEVELOPMENT
|
||||||
|
Accounts: ${JSON.stringify(allAccounts)}
|
||||||
|
Target Industries: ${JSON.stringify(phase3Data.whales.map(w => w.industry))}
|
||||||
|
Product Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
1. Develop specific "Angle" per target/industry.
|
||||||
|
2. Consistency Check against Product Matrix (ensure differentiation from Scrubber 50/Bella).
|
||||||
|
3. **IMPORTANT:** Apply "Hybrid Service Logic" if technical constraints exist! (Wackler Manpower as supplement).
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"strategyMatrix": [
|
||||||
|
{
|
||||||
|
"segment": "Target Segment",
|
||||||
|
"painPoint": "Specific Pain (Short)",
|
||||||
|
"angle": "Our Marketing Angle (Short)",
|
||||||
|
"differentiation": "How it differs (Short)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanAndParseJson<Phase4Data>(response.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 5: Assets & Report ---
|
||||||
|
export const generateAssets = async (
|
||||||
|
phase4Data: Phase4Data,
|
||||||
|
phase3Data: Phase3Data,
|
||||||
|
phase2Data: Phase2Data,
|
||||||
|
phase1Data: Phase1Data,
|
||||||
|
lang: Language
|
||||||
|
): Promise<string> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 5: ASSET GENERATION & FINAL REPORT
|
||||||
|
|
||||||
|
CONTEXT DATA:
|
||||||
|
- Technical: ${JSON.stringify(phase1Data)}
|
||||||
|
- ICPs: ${JSON.stringify(phase2Data)}
|
||||||
|
- Targets (Whales): ${JSON.stringify(phase3Data)}
|
||||||
|
- Strategy: ${JSON.stringify(phase4Data)}
|
||||||
|
|
||||||
|
AUFGABE:
|
||||||
|
1. Erstelle einen "GTM STRATEGY REPORT" in Markdown.
|
||||||
|
2. Struktur des Reports:
|
||||||
|
- Executive Summary
|
||||||
|
- Produkt Analyse (Features & Constraints)
|
||||||
|
- Zielgruppen (ICPs & Data Proxies)
|
||||||
|
- Target Accounts (Whales) & Buying Center
|
||||||
|
- Strategie-Matrix (Pains & Angles)
|
||||||
|
- Assets (LinkedIn, Email, Landing Page Headline)
|
||||||
|
3. Self-Correction: Entferne "Marketing Bla-Bla". Nutze Fakten, TCO, ROI.
|
||||||
|
4. Hybrid-Check: Stelle sicher, dass die "Hybrid Service Logic" (Wackler Manpower) in der Strategie und den Assets sichtbar ist, falls Constraints vorliegen.
|
||||||
|
|
||||||
|
Output:
|
||||||
|
Gib striktes MARKDOWN zurück. Beginne mit "# GTM STRATEGY REPORT".
|
||||||
|
` : `
|
||||||
|
PHASE 5: ASSET GENERATION & FINAL REPORT
|
||||||
|
|
||||||
|
CONTEXT DATA:
|
||||||
|
- Technical: ${JSON.stringify(phase1Data)}
|
||||||
|
- ICPs: ${JSON.stringify(phase2Data)}
|
||||||
|
- Targets (Whales): ${JSON.stringify(phase3Data)}
|
||||||
|
- Strategy: ${JSON.stringify(phase4Data)}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
1. Create a "GTM STRATEGY REPORT" in Markdown.
|
||||||
|
2. Report Structure:
|
||||||
|
- Executive Summary
|
||||||
|
- Product Analysis (Features & Constraints)
|
||||||
|
- Target Audience (ICPs & Data Proxies)
|
||||||
|
- Target Accounts (Whales) & Buying Center
|
||||||
|
- Strategy Matrix (Pains & Angles)
|
||||||
|
- Assets (LinkedIn, Email, Landing Page Headline)
|
||||||
|
3. Self-Correction: Remove "Marketing Fluff". Use Facts, TCO, ROI.
|
||||||
|
4. Hybrid-Check: Ensure "Hybrid Service Logic" (Wackler Manpower) is visible in strategy and assets if constraints exist.
|
||||||
|
|
||||||
|
Output:
|
||||||
|
Return strictly MARKDOWN formatted text. Start with "# GTM STRATEGY REPORT".
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.text || "Error generating assets.";
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 6: Sales Enablement & Visuals ---
|
||||||
|
export const generateSalesEnablement = async (
|
||||||
|
phase4Data: Phase4Data,
|
||||||
|
phase3Data: Phase3Data,
|
||||||
|
phase1Data: Phase1Data,
|
||||||
|
lang: Language
|
||||||
|
): Promise<Phase6Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 6: SALES ENABLEMENT & VISUALS
|
||||||
|
|
||||||
|
CONTEXT:
|
||||||
|
- Produkt Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
- Accounts (Personas): ${JSON.stringify(phase3Data.roles)}
|
||||||
|
- Strategie: ${JSON.stringify(phase4Data.strategyMatrix)}
|
||||||
|
|
||||||
|
AUFGABE:
|
||||||
|
1. **Anticipate Friction:** Versetze dich in die Lage der identifizierten Personas. Was ist deren härtester Einwand?
|
||||||
|
2. **Deflect & Solve:** Formuliere eine präzise, faktenbasierte Antwort für den Vertriebler (Battlecard).
|
||||||
|
3. **Visual Context:** Erstelle präzise "Text-to-Image Prompts" (für Midjourney/DALL-E).
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"battlecards": [
|
||||||
|
{
|
||||||
|
"persona": "Rolle",
|
||||||
|
"objection": "Einwand",
|
||||||
|
"responseScript": "Antwort"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"visualPrompts": [
|
||||||
|
{
|
||||||
|
"title": "Titel",
|
||||||
|
"context": "Kontext",
|
||||||
|
"prompt": "Prompt..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
` : `
|
||||||
|
PHASE 6: SALES ENABLEMENT & VISUALS
|
||||||
|
|
||||||
|
CONTEXT:
|
||||||
|
- Product Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
- Accounts (Personas): ${JSON.stringify(phase3Data.roles)}
|
||||||
|
- Strategy: ${JSON.stringify(phase4Data.strategyMatrix)}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
1. **Anticipate Friction:** Identify hardest objections.
|
||||||
|
2. **Deflect & Solve:** Formulate response script (Battlecard).
|
||||||
|
3. **Visual Context:** Create "Text-to-Image Prompts".
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"battlecards": [
|
||||||
|
{
|
||||||
|
"persona": "Role",
|
||||||
|
"objection": "Objection",
|
||||||
|
"responseScript": "Response"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"visualPrompts": [
|
||||||
|
{
|
||||||
|
"title": "Title",
|
||||||
|
"context": "Context",
|
||||||
|
"prompt": "Prompt..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanAndParseJson<Phase6Data>(response.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 7: Vertical Landing Page Copy ---
|
||||||
|
export const generateLandingPageCopy = async (
|
||||||
|
phase4Data: Phase4Data,
|
||||||
|
phase2Data: Phase2Data,
|
||||||
|
lang: Language
|
||||||
|
): Promise<Phase7Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 7: VERTICAL LANDING PAGE COPY (Conversion Optimization)
|
||||||
|
|
||||||
|
ICPs: ${JSON.stringify(phase2Data.icps)}
|
||||||
|
Strategy: ${JSON.stringify(phase4Data.strategyMatrix)}
|
||||||
|
|
||||||
|
AUFGABE:
|
||||||
|
1. Identifiziere generische Aussagen und transformiere sie in spezifische Nutzenargumente für die Top 2 ICPs.
|
||||||
|
2. Wende die "Wackler-Symbiose" an: Erwähne Service/Hygiene als Trust-Element.
|
||||||
|
|
||||||
|
Erstelle für die Top 2 ICPs jeweils einen Landingpage-Entwurf (Hero Section).
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"landingPages": [
|
||||||
|
{
|
||||||
|
"industry": "Zielbranche",
|
||||||
|
"headline": "Nutzenorientierte Headline (Kein Produktname)",
|
||||||
|
"subline": "Tech Enabler + Wackler Service Garantie",
|
||||||
|
"bullets": ["Vorteil 1", "Vorteil 2", "Vorteil 3"],
|
||||||
|
"cta": "Branchen-spezifischer CTA"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
` : `
|
||||||
|
PHASE 7: VERTICAL LANDING PAGE COPY (Conversion Optimization)
|
||||||
|
|
||||||
|
ICPs: ${JSON.stringify(phase2Data.icps)}
|
||||||
|
Strategy: ${JSON.stringify(phase4Data.strategyMatrix)}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
1. Transform generic features into specific benefits for the Top 2 ICPs.
|
||||||
|
2. Apply "Wackler Symbiosis": Mention service/hygiene as trust element.
|
||||||
|
|
||||||
|
Create Landing Page Drafts (Hero Section) for Top 2 ICPs.
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"landingPages": [
|
||||||
|
{
|
||||||
|
"industry": "Target Industry",
|
||||||
|
"headline": "Benefit-oriented Headline",
|
||||||
|
"subline": "Tech Enabler + Wackler Service Guarantee",
|
||||||
|
"bullets": ["Benefit 1", "Benefit 2", "Benefit 3"],
|
||||||
|
"cta": "Industry-specific CTA"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanAndParseJson<Phase7Data>(response.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 8: Business Case Builder ---
|
||||||
|
export const buildBusinessCase = async (
|
||||||
|
phase2Data: Phase2Data,
|
||||||
|
phase1Data: Phase1Data,
|
||||||
|
lang: Language
|
||||||
|
): Promise<Phase8Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 8: BUSINESS CASE BUILDER (The CFO Pitch)
|
||||||
|
|
||||||
|
Input:
|
||||||
|
ICPs: ${JSON.stringify(phase2Data.icps)}
|
||||||
|
Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
|
||||||
|
AUFGABE:
|
||||||
|
1. Schätze Lohnkosten/Probleme der Zielgruppe (Personalmangel, Krankheitsstand).
|
||||||
|
2. Setze den Roboter (Leasing ca. 330-600€/Monat) dagegen.
|
||||||
|
3. Entwickle eine ROI-Logik.
|
||||||
|
|
||||||
|
Erstelle für jeden ICP einen "Financial Argumentation Guide".
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"businessCases": [
|
||||||
|
{
|
||||||
|
"industry": "Branche",
|
||||||
|
"costDriver": "Was kostet der Status Quo? (z.B. Laufwege)",
|
||||||
|
"efficiencyGain": "Wie amortisiert sich der Roboter?",
|
||||||
|
"riskArgument": "Warum Miete + Wackler Service sicherer ist als Kauf"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
` : `
|
||||||
|
PHASE 8: BUSINESS CASE BUILDER (The CFO Pitch)
|
||||||
|
|
||||||
|
Input:
|
||||||
|
ICPs: ${JSON.stringify(phase2Data.icps)}
|
||||||
|
Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
1. Estimate labor costs/pain points (shortage, sick leave).
|
||||||
|
2. Compare against Robot Leasing (approx 330-600€/month).
|
||||||
|
3. Develop ROI logic.
|
||||||
|
|
||||||
|
Create a "Financial Argumentation Guide" for each ICP.
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"businessCases": [
|
||||||
|
{
|
||||||
|
"industry": "Industry",
|
||||||
|
"costDriver": "Status Quo Cost",
|
||||||
|
"efficiencyGain": "Amortization Logic",
|
||||||
|
"riskArgument": "Why Lease + Service is safer than buying"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanAndParseJson<Phase8Data>(response.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Phase 9: Feature-to-Value Translator ---
|
||||||
|
export const translateTech = async (
|
||||||
|
phase1Data: Phase1Data,
|
||||||
|
phase4Data: Phase4Data,
|
||||||
|
lang: Language
|
||||||
|
): Promise<Phase9Data> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const sysInstr = getSystemInstruction(lang);
|
||||||
|
|
||||||
|
const prompt = lang === 'de' ? `
|
||||||
|
PHASE 9: THE "FEATURE-TO-VALUE" TRANSLATOR
|
||||||
|
|
||||||
|
Input Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
Strategy Pains: ${JSON.stringify(phase4Data.strategyMatrix.map(s => s.painPoint))}
|
||||||
|
|
||||||
|
AUFGABE:
|
||||||
|
1. Nimm ein technisches Feature (z.B. "LiDAR").
|
||||||
|
2. Frage: "So what?" (Was bringt das im Alltag?).
|
||||||
|
3. Frage nochmal: "So what?" (Was ist der emotionale oder finanzielle Vorteil?).
|
||||||
|
* Kette: LiDAR -> Erkennt Hindernisse -> Stößt nicht an -> Keine Unfälle/Haftung -> **Sicherheit & Ruhe**.
|
||||||
|
4. Formuliere den Benefit **ohne** Fachchinesisch. Nutze emotionale Trigger.
|
||||||
|
|
||||||
|
Erstelle eine Tabelle für die Website-Kommunikation:
|
||||||
|
* **feature:** (Klein gedruckt für Techies)
|
||||||
|
* **story:** (Die Story) Der emotionale/wirtschaftliche Nutzen in der Sprache der Zielgruppe.
|
||||||
|
* **headline:** (Die Headline) Ein knackiger Slogan für dieses Feature.
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"techTranslations": [
|
||||||
|
{
|
||||||
|
"feature": "Technisches Feature",
|
||||||
|
"story": "Benefit Story (So what chain)",
|
||||||
|
"headline": "Knackiger Slogan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
` : `
|
||||||
|
PHASE 9: THE "FEATURE-TO-VALUE" TRANSLATOR
|
||||||
|
|
||||||
|
Input Features: ${JSON.stringify(phase1Data.features)}
|
||||||
|
Strategy Pains: ${JSON.stringify(phase4Data.strategyMatrix.map(s => s.painPoint))}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
1. Take a technical feature (e.g. "LiDAR").
|
||||||
|
2. Ask: "So what?" (What does it do in daily life?).
|
||||||
|
3. Ask again: "So what?" (What is the emotional or financial benefit?).
|
||||||
|
* Chain: LiDAR -> See obstacles -> No collision -> No accidents/liability -> **Safety & Peace of Mind**.
|
||||||
|
4. Formulate the benefit **without** jargon. Use emotional triggers.
|
||||||
|
|
||||||
|
Create a table for website communication:
|
||||||
|
* **feature:** (Small print for techies)
|
||||||
|
* **story:** (The Story) The emotional/economic benefit in target language.
|
||||||
|
* **headline:** (The Headline) A punchy slogan for this feature.
|
||||||
|
|
||||||
|
Output JSON format ONLY:
|
||||||
|
{
|
||||||
|
"techTranslations": [
|
||||||
|
{
|
||||||
|
"feature": "Tech Feature",
|
||||||
|
"story": "Benefit Story (So what chain)",
|
||||||
|
"headline": "Punchy Slogan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt,
|
||||||
|
config: {
|
||||||
|
systemInstruction: sysInstr,
|
||||||
|
responseMimeType: "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanAndParseJson<Phase9Data>(response.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const translateReportToEnglish = async (reportMarkdown: string): Promise<string> => {
|
||||||
|
const ai = getClient();
|
||||||
|
const prompt = `
|
||||||
|
TASK: Translate the following GTM Strategy Report into professional Business English.
|
||||||
|
CONTEXT: It is a B2B robotic strategy document.
|
||||||
|
CONSTRAINT: Keep the Markdown structure (#, ##, -, | tables) EXACTLY as is. Only translate the content.
|
||||||
|
|
||||||
|
INPUT:
|
||||||
|
${reportMarkdown}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-3-flash-preview',
|
||||||
|
contents: prompt
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.text || reportMarkdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateConceptImage = async (prompt: string, referenceImagesBase64?: string[]): Promise<string> => {
|
||||||
|
const ai = getClient();
|
||||||
|
try {
|
||||||
|
const parts: any[] = [];
|
||||||
|
|
||||||
|
// If reference images are provided, add them to parts
|
||||||
|
if (referenceImagesBase64 && referenceImagesBase64.length > 0) {
|
||||||
|
referenceImagesBase64.forEach(img => {
|
||||||
|
const cleanBase64 = img.split(',')[1] || img;
|
||||||
|
const match = img.match(/data:(.*?);base64/);
|
||||||
|
const mimeType = match ? match[1] : 'image/png';
|
||||||
|
|
||||||
|
parts.push({
|
||||||
|
inlineData: {
|
||||||
|
mimeType: mimeType,
|
||||||
|
data: cleanBase64
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
parts.push({
|
||||||
|
text: "Show the product in these reference images in the following context. Keep the product appearance consistent. " + prompt
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Text-only prompt
|
||||||
|
parts.push({ text: prompt });
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await ai.models.generateContent({
|
||||||
|
model: 'gemini-2.5-flash-image',
|
||||||
|
contents: { parts },
|
||||||
|
config: {
|
||||||
|
imageConfig: {
|
||||||
|
aspectRatio: "16:9",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const part of response.candidates?.[0]?.content?.parts || []) {
|
||||||
|
if (part.inlineData) {
|
||||||
|
return `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("No image generated");
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("Image generation failed", e);
|
||||||
|
throw new Error(e.message || "Failed to generate image");
|
||||||
|
}
|
||||||
|
};
|
||||||
91
gtm-architect/v2/index.html
Normal file
91
gtm-architect/v2/index.html
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Roboplanet GTM Architect</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', 'sans-serif'],
|
||||||
|
mono: ['JetBrains Mono', 'monospace'],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
robo: {
|
||||||
|
900: '#0f172a',
|
||||||
|
800: '#1e293b',
|
||||||
|
700: '#334155',
|
||||||
|
500: '#3b82f6',
|
||||||
|
400: '#60a5fa',
|
||||||
|
accent: '#06b6d4'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
/* Default Light Mode colors */
|
||||||
|
background-color: #f8fafc;
|
||||||
|
color: #0f172a;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
/* Dark Mode overrides via class */
|
||||||
|
.dark body {
|
||||||
|
background-color: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1; /* slate-300 */
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8; /* slate-400 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark ::-webkit-scrollbar-track {
|
||||||
|
background: #1e293b;
|
||||||
|
}
|
||||||
|
.dark ::-webkit-scrollbar-thumb {
|
||||||
|
background: #475569;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.dark ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #64748b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@google/genai": "https://esm.sh/@google/genai@^1.34.0",
|
||||||
|
"react/": "https://esm.sh/react@^19.2.3/",
|
||||||
|
"react": "https://esm.sh/react@^19.2.3",
|
||||||
|
"react-markdown": "https://esm.sh/react-markdown@^10.1.0",
|
||||||
|
"remark-gfm": "https://esm.sh/remark-gfm@^4.0.0",
|
||||||
|
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
|
||||||
|
"lucide-react": "https://esm.sh/lucide-react@^0.562.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="/index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
gtm-architect/v2/index.tsx
Normal file
15
gtm-architect/v2/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error("Could not find root element to mount to");
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user