blun-king-cli 4.1.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api.js +965 -0
- package/blun-cli.js +763 -0
- package/blunking-api.js +7 -0
- package/bot.js +188 -0
- package/browser-controller.js +76 -0
- package/chat-memory.js +103 -0
- package/file-helper.js +63 -0
- package/fuzzy-match.js +78 -0
- package/identities.js +106 -0
- package/installer.js +160 -0
- package/job-manager.js +146 -0
- package/local-data.js +71 -0
- package/message-builder.js +28 -0
- package/noisy-evals.js +38 -0
- package/package.json +17 -4
- package/palace-memory.js +246 -0
- package/reference-inspector.js +228 -0
- package/runtime.js +555 -0
- package/task-executor.js +104 -0
- package/tests/browser-controller.test.js +42 -0
- package/tests/cli.test.js +93 -0
- package/tests/file-helper.test.js +18 -0
- package/tests/installer.test.js +39 -0
- package/tests/job-manager.test.js +99 -0
- package/tests/merge-compat.test.js +77 -0
- package/tests/messages.test.js +23 -0
- package/tests/noisy-evals.test.js +12 -0
- package/tests/noisy-intent-corpus.test.js +45 -0
- package/tests/reference-inspector.test.js +36 -0
- package/tests/runtime.test.js +119 -0
- package/tests/task-executor.test.js +40 -0
- package/tests/tools.test.js +23 -0
- package/tests/user-profile.test.js +66 -0
- package/tests/website-builder.test.js +66 -0
- package/tmp-build-smoke/nicrazy-landing/index.html +53 -0
- package/tmp-build-smoke/nicrazy-landing/style.css +110 -0
- package/tmp-shot-smoke/website-shot-1776006760424.png +0 -0
- package/tmp-shot-smoke/website-shot-1776007850007.png +0 -0
- package/tmp-shot-smoke/website-shot-1776007886209.png +0 -0
- package/tmp-shot-smoke/website-shot-1776007903766.png +0 -0
- package/tmp-shot-smoke/website-shot-1776008737117.png +0 -0
- package/tmp-shot-smoke/website-shot-1776008988859.png +0 -0
- package/tmp-smoke/nicrazy-landing/index.html +66 -0
- package/tmp-smoke/nicrazy-landing/style.css +104 -0
- package/tools.js +177 -0
- package/user-profile.js +395 -0
- package/website-builder.js +394 -0
- package/website-shot-1776010648230.png +0 -0
- package/website_builder.txt +38 -0
- package/bin/blun.js +0 -3196
- package/setup.js +0 -30
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
function cleanText(value) {
|
|
2
|
+
return String(value || "")
|
|
3
|
+
.replace(/<[^>]+>/g, " ")
|
|
4
|
+
.replace(/\s+/g, " ")
|
|
5
|
+
.trim();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function inferBrandName(goal, references = []) {
|
|
9
|
+
const explicit = String(goal || "").match(/\b(fuer|fur)\s+([A-Za-z0-9_.-]+)/i);
|
|
10
|
+
if (explicit) return explicit[2].replace(/\.com$/i, "");
|
|
11
|
+
|
|
12
|
+
for (const ref of references) {
|
|
13
|
+
if (ref?.url) {
|
|
14
|
+
try {
|
|
15
|
+
const hostname = new URL(ref.url).hostname.replace(/^www\./i, "");
|
|
16
|
+
return hostname.split(".")[0];
|
|
17
|
+
} catch {}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (/nicrazy/i.test(goal)) return "Nicrazy";
|
|
22
|
+
return "Brand";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function collectReferenceCorpus(goal, references = []) {
|
|
26
|
+
const parts = [String(goal || "")];
|
|
27
|
+
for (const ref of references) {
|
|
28
|
+
parts.push(ref.url || "");
|
|
29
|
+
parts.push(ref.title || "");
|
|
30
|
+
parts.push((ref.h1s || []).join(" "));
|
|
31
|
+
parts.push(ref.text || "");
|
|
32
|
+
parts.push(ref.html || "");
|
|
33
|
+
}
|
|
34
|
+
return cleanText(parts.join(" ")).toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasKeyword(corpus, term) {
|
|
38
|
+
if (term.includes(" ")) return corpus.includes(term);
|
|
39
|
+
return new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i").test(corpus);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function detectIndustry(goal, references = []) {
|
|
43
|
+
const corpus = collectReferenceCorpus(goal, references);
|
|
44
|
+
const rules = [
|
|
45
|
+
{ id: "florist", terms: ["flower", "flowers", "bouquet", "bouquets", "floral", "florist", "arrangement", "wedding flowers"] },
|
|
46
|
+
{ id: "tattoo", terms: ["tattoo", "tattoos", "flash", "studio", "artist", "ink"] },
|
|
47
|
+
{ id: "jewelry", terms: ["jewelry", "jewellery", "ring", "rings", "necklace", "bracelet", "gold", "silver"] },
|
|
48
|
+
{ id: "fashion", terms: ["fashion", "streetwear", "collection", "drop", "hoodie", "tee", "shirt", "lookbook", "apparel"] },
|
|
49
|
+
{ id: "brand", terms: ["brand", "campaign", "creative", "studio", "label"] }
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
let best = { id: "brand", score: 0 };
|
|
53
|
+
for (const rule of rules) {
|
|
54
|
+
const score = rule.terms.reduce((total, term) => total + (hasKeyword(corpus, term) ? 1 : 0), 0);
|
|
55
|
+
if (score > best.score) best = { id: rule.id, score };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (best.score === 0 && /nicrazy/i.test(corpus)) return "fashion";
|
|
59
|
+
return best.id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function industryConfig(industry, brand) {
|
|
63
|
+
const configs = {
|
|
64
|
+
fashion: {
|
|
65
|
+
nav: ["Drop", "Collection", "Story"],
|
|
66
|
+
eyebrow: "Reference Build",
|
|
67
|
+
title: `${brand} baut Spannung statt Deko.`,
|
|
68
|
+
subtitle: "Eine fokussierte Fashion-Landingpage mit klarer Markenhaltung, starkem Hero und sauberer Shop-Logik.",
|
|
69
|
+
cta: "Zum Drop",
|
|
70
|
+
features: [
|
|
71
|
+
["Drop-Fokus", "Produkt, Kampagne und Kaufimpuls greifen direkt ineinander."],
|
|
72
|
+
["Markenwelt", "Typografie, Bildsprache und Copy bleiben nah an einer modernen Label-Ästhetik."],
|
|
73
|
+
["Conversion", "Hero, Collection-Teaser und CTA führen ohne Streuverlust in den Shop."]
|
|
74
|
+
],
|
|
75
|
+
sections: {
|
|
76
|
+
showcaseTitle: "Current Collection",
|
|
77
|
+
showcaseItems: ["Hero Product", "Signature Layer", "New Arrival"],
|
|
78
|
+
proofTitle: "Warum das zur Referenz passt",
|
|
79
|
+
proofBody: "Die Seite bleibt in der Welt eines Fashion- oder Brand-Auftritts und driftet nicht in branchenfremde Templates ab."
|
|
80
|
+
},
|
|
81
|
+
requiredTerms: ["drop", "collection", "shop", "brand"],
|
|
82
|
+
bannedTerms: ["flower", "bouquet", "florist", "event floristik", "abo-service"]
|
|
83
|
+
},
|
|
84
|
+
jewelry: {
|
|
85
|
+
nav: ["Pieces", "Craft", "Contact"],
|
|
86
|
+
eyebrow: "Reference Build",
|
|
87
|
+
title: `${brand} wirkt präzise und hochwertig.`,
|
|
88
|
+
subtitle: "Eine klare Jewelry-Landingpage mit Fokus auf Material, Detail und Anfrage- oder Kauf-Logik.",
|
|
89
|
+
cta: "Kollektion ansehen",
|
|
90
|
+
features: [
|
|
91
|
+
["Signature Pieces", "Die wichtigsten Produkte stehen ohne Ablenkung im Fokus."],
|
|
92
|
+
["Craft Story", "Material, Verarbeitung und Vertrauen werden sichtbar gemacht."],
|
|
93
|
+
["Clean Conversion", "Klare Wege zu Collection, Anfrage oder Direktkauf."]
|
|
94
|
+
],
|
|
95
|
+
sections: {
|
|
96
|
+
showcaseTitle: "Featured Pieces",
|
|
97
|
+
showcaseItems: ["Statement Ring", "Layered Chain", "Signature Bracelet"],
|
|
98
|
+
proofTitle: "Warum das zur Referenz passt",
|
|
99
|
+
proofBody: "Die Struktur bleibt in der Sprache von Schmuck, Material und Produktinszenierung."
|
|
100
|
+
},
|
|
101
|
+
requiredTerms: ["pieces", "collection", "material"],
|
|
102
|
+
bannedTerms: ["flower", "bouquet", "florist"]
|
|
103
|
+
},
|
|
104
|
+
tattoo: {
|
|
105
|
+
nav: ["Works", "Booking", "Studio"],
|
|
106
|
+
eyebrow: "Reference Build",
|
|
107
|
+
title: `${brand} zeigt Handschrift statt Leerlauf.`,
|
|
108
|
+
subtitle: "Ein Tattoo- oder Artist-Auftritt mit starker Portfolio-Führung und klarer Booking-Logik.",
|
|
109
|
+
cta: "Session anfragen",
|
|
110
|
+
features: [
|
|
111
|
+
["Portfolio First", "Arbeiten, Stilrichtung und Flash stehen sauber im Vordergrund."],
|
|
112
|
+
["Booking Flow", "Die CTA führt direkt in den Termin- oder Anfragepfad."],
|
|
113
|
+
["Studio Presence", "Ton, Aufbau und Hierarchie passen zu Artist- und Studio-Auftritten."]
|
|
114
|
+
],
|
|
115
|
+
sections: {
|
|
116
|
+
showcaseTitle: "Recent Works",
|
|
117
|
+
showcaseItems: ["Blackwork", "Fine Line", "Flash Sheet"],
|
|
118
|
+
proofTitle: "Warum das zur Referenz passt",
|
|
119
|
+
proofBody: "Die Seite bleibt in der Welt eines Artist- oder Studio-Auftritts statt in generischen Commerce-Slots."
|
|
120
|
+
},
|
|
121
|
+
requiredTerms: ["booking", "studio", "works"],
|
|
122
|
+
bannedTerms: ["flower", "bouquet", "abo-service"]
|
|
123
|
+
},
|
|
124
|
+
florist: {
|
|
125
|
+
nav: ["Bouquets", "Events", "Contact"],
|
|
126
|
+
eyebrow: "Reference Build",
|
|
127
|
+
title: `${brand} bleibt floral und fokussiert.`,
|
|
128
|
+
subtitle: "Ein floristischer Auftritt mit Angebot, Anlass und sauberem Anfragepfad.",
|
|
129
|
+
cta: "Bouquets entdecken",
|
|
130
|
+
features: [
|
|
131
|
+
["Anlass-Klarheit", "Produkte und Event-Angebote sind sofort verständlich."],
|
|
132
|
+
["Vertrauen", "Stil, Qualität und Service werden klar sichtbar gemacht."],
|
|
133
|
+
["Anfrage-Flow", "Kauf- oder Eventanfragen werden sauber geführt."]
|
|
134
|
+
],
|
|
135
|
+
sections: {
|
|
136
|
+
showcaseTitle: "Featured Arrangements",
|
|
137
|
+
showcaseItems: ["Seasonal Bouquet", "Wedding Florals", "Table Arrangement"],
|
|
138
|
+
proofTitle: "Warum das zur Referenz passt",
|
|
139
|
+
proofBody: "Die Seite bleibt in Angebot, Sprache und CTA-Logik klar im floristischen Kontext."
|
|
140
|
+
},
|
|
141
|
+
requiredTerms: ["bouquet", "floral", "events"],
|
|
142
|
+
bannedTerms: ["tattoo", "drop", "streetwear"]
|
|
143
|
+
},
|
|
144
|
+
brand: {
|
|
145
|
+
nav: ["Work", "Story", "Contact"],
|
|
146
|
+
eyebrow: "Reference Build",
|
|
147
|
+
title: `${brand} bekommt einen klaren Markenauftritt.`,
|
|
148
|
+
subtitle: "Eine fokussierte Brand-Landingpage mit starker Haltung, sauberer Story und klarer CTA-Führung.",
|
|
149
|
+
cta: "Marke entdecken",
|
|
150
|
+
features: [
|
|
151
|
+
["Positionierung", "Die Kernbotschaft sitzt früh und klar."],
|
|
152
|
+
["Struktur", "Hero, Proof und CTA ziehen an einem Strang."],
|
|
153
|
+
["Markenwelt", "Die Seite bleibt eigenständig statt austauschbar."]
|
|
154
|
+
],
|
|
155
|
+
sections: {
|
|
156
|
+
showcaseTitle: "Brand Highlights",
|
|
157
|
+
showcaseItems: ["Core Message", "Offer Frame", "Trust Layer"],
|
|
158
|
+
proofTitle: "Warum das zur Referenz passt",
|
|
159
|
+
proofBody: "Die Seite hält Business-Typ, Zielgruppe und Markenwelt zusammen statt generische Branche zu erfinden."
|
|
160
|
+
},
|
|
161
|
+
requiredTerms: ["brand", "story", "contact"],
|
|
162
|
+
bannedTerms: ["flower", "bouquet", "abo-service"]
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return configs[industry] || configs.brand;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function buildWebsiteProject(goal, references = []) {
|
|
170
|
+
const brand = inferBrandName(goal, references);
|
|
171
|
+
const industry = detectIndustry(goal, references);
|
|
172
|
+
const config = industryConfig(industry, brand);
|
|
173
|
+
const reference = references.find((entry) => !entry.error);
|
|
174
|
+
const refLine = reference
|
|
175
|
+
? `<p class="reference-note">Referenz gelesen: ${reference.title || reference.url}. Business-Type-Lock: ${industry}.</p>`
|
|
176
|
+
: `<p class="reference-note">Business-Type-Lock: ${industry}.</p>`;
|
|
177
|
+
|
|
178
|
+
const html = `<!doctype html>
|
|
179
|
+
<html lang="de">
|
|
180
|
+
<head>
|
|
181
|
+
<meta charset="UTF-8" />
|
|
182
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
183
|
+
<title>${brand} - Reference Build</title>
|
|
184
|
+
<link rel="stylesheet" href="./style.css" />
|
|
185
|
+
</head>
|
|
186
|
+
<body>
|
|
187
|
+
<header class="site-header">
|
|
188
|
+
<div class="logo">${brand}</div>
|
|
189
|
+
<nav>
|
|
190
|
+
<a href="#showcase">${config.nav[0]}</a>
|
|
191
|
+
<a href="#about">${config.nav[1]}</a>
|
|
192
|
+
<a href="#contact">${config.nav[2]}</a>
|
|
193
|
+
</nav>
|
|
194
|
+
</header>
|
|
195
|
+
|
|
196
|
+
<main>
|
|
197
|
+
<section class="hero">
|
|
198
|
+
<div class="hero-copy">
|
|
199
|
+
<p class="eyebrow">${config.eyebrow}</p>
|
|
200
|
+
<h1>${config.title}</h1>
|
|
201
|
+
<p>${config.subtitle}</p>
|
|
202
|
+
${refLine}
|
|
203
|
+
<a class="btn" href="#showcase">${config.cta}</a>
|
|
204
|
+
</div>
|
|
205
|
+
</section>
|
|
206
|
+
|
|
207
|
+
<section class="benefits" id="about">
|
|
208
|
+
${config.features.map(([title, body]) => `<article><h2>${title}</h2><p>${body}</p></article>`).join("")}
|
|
209
|
+
</section>
|
|
210
|
+
|
|
211
|
+
<section class="products" id="showcase">
|
|
212
|
+
<h2>${config.sections.showcaseTitle}</h2>
|
|
213
|
+
<div class="grid">
|
|
214
|
+
${config.sections.showcaseItems.map((item) => `<div class="card"><div class="thumb"></div><h3>${item}</h3><p>${brand} bleibt in einer ${industry}-nahen Markenlogik statt in generischen Platzhaltern.</p></div>`).join("")}
|
|
215
|
+
</div>
|
|
216
|
+
</section>
|
|
217
|
+
|
|
218
|
+
<section class="social-proof">
|
|
219
|
+
<h2>${config.sections.proofTitle}</h2>
|
|
220
|
+
<p>${config.sections.proofBody}</p>
|
|
221
|
+
</section>
|
|
222
|
+
|
|
223
|
+
<section class="cta" id="contact">
|
|
224
|
+
<h2>${config.cta}</h2>
|
|
225
|
+
<p>Klare CTA-Logik, saubere Struktur und keine Branchen-Drifts.</p>
|
|
226
|
+
<a class="btn" href="#showcase">${config.cta}</a>
|
|
227
|
+
</section>
|
|
228
|
+
</main>
|
|
229
|
+
</body>
|
|
230
|
+
</html>`;
|
|
231
|
+
|
|
232
|
+
const css = `:root{
|
|
233
|
+
--bg:#09090b;
|
|
234
|
+
--surface:#121826;
|
|
235
|
+
--text:#f5f5f5;
|
|
236
|
+
--muted:#a1a1aa;
|
|
237
|
+
--line:rgba(255,255,255,0.08);
|
|
238
|
+
--accent:#ff4d6d;
|
|
239
|
+
}
|
|
240
|
+
*{box-sizing:border-box}
|
|
241
|
+
html,body{margin:0;padding:0}
|
|
242
|
+
body{
|
|
243
|
+
font-family:Inter,system-ui,sans-serif;
|
|
244
|
+
background:radial-gradient(circle at top right, rgba(255,77,109,0.18), transparent 28%), var(--bg);
|
|
245
|
+
color:var(--text);
|
|
246
|
+
}
|
|
247
|
+
.site-header{
|
|
248
|
+
position:sticky;
|
|
249
|
+
top:0;
|
|
250
|
+
z-index:20;
|
|
251
|
+
display:flex;
|
|
252
|
+
justify-content:space-between;
|
|
253
|
+
align-items:center;
|
|
254
|
+
padding:18px 32px;
|
|
255
|
+
backdrop-filter:blur(12px);
|
|
256
|
+
background:rgba(9,9,11,0.88);
|
|
257
|
+
border-bottom:1px solid var(--line);
|
|
258
|
+
}
|
|
259
|
+
.site-header nav a{
|
|
260
|
+
color:var(--muted);
|
|
261
|
+
text-decoration:none;
|
|
262
|
+
margin-left:18px;
|
|
263
|
+
}
|
|
264
|
+
.logo{
|
|
265
|
+
font-weight:800;
|
|
266
|
+
letter-spacing:0.08em;
|
|
267
|
+
text-transform:uppercase;
|
|
268
|
+
}
|
|
269
|
+
.hero{
|
|
270
|
+
min-height:72vh;
|
|
271
|
+
display:grid;
|
|
272
|
+
place-items:center;
|
|
273
|
+
padding:80px 24px 56px;
|
|
274
|
+
}
|
|
275
|
+
.hero-copy{
|
|
276
|
+
max-width:820px;
|
|
277
|
+
text-align:center;
|
|
278
|
+
}
|
|
279
|
+
.eyebrow{
|
|
280
|
+
text-transform:uppercase;
|
|
281
|
+
letter-spacing:0.2em;
|
|
282
|
+
color:var(--muted);
|
|
283
|
+
font-size:12px;
|
|
284
|
+
}
|
|
285
|
+
h1{
|
|
286
|
+
font-size:clamp(42px,8vw,84px);
|
|
287
|
+
line-height:0.96;
|
|
288
|
+
margin:10px 0 18px;
|
|
289
|
+
}
|
|
290
|
+
.reference-note{
|
|
291
|
+
color:var(--muted);
|
|
292
|
+
font-size:14px;
|
|
293
|
+
}
|
|
294
|
+
.btn{
|
|
295
|
+
display:inline-block;
|
|
296
|
+
margin-top:22px;
|
|
297
|
+
padding:14px 22px;
|
|
298
|
+
border-radius:999px;
|
|
299
|
+
background:var(--accent);
|
|
300
|
+
color:#fff;
|
|
301
|
+
text-decoration:none;
|
|
302
|
+
font-weight:700;
|
|
303
|
+
}
|
|
304
|
+
.benefits,.products,.social-proof,.cta{
|
|
305
|
+
max-width:1200px;
|
|
306
|
+
margin:0 auto;
|
|
307
|
+
padding:72px 24px;
|
|
308
|
+
}
|
|
309
|
+
.benefits,.grid{
|
|
310
|
+
display:grid;
|
|
311
|
+
grid-template-columns:repeat(3,minmax(0,1fr));
|
|
312
|
+
gap:18px;
|
|
313
|
+
}
|
|
314
|
+
.benefits article,.card,.social-proof{
|
|
315
|
+
background:var(--surface);
|
|
316
|
+
border:1px solid var(--line);
|
|
317
|
+
border-radius:20px;
|
|
318
|
+
padding:24px;
|
|
319
|
+
}
|
|
320
|
+
.thumb{
|
|
321
|
+
height:240px;
|
|
322
|
+
border-radius:16px;
|
|
323
|
+
background:linear-gradient(135deg,#1f2937,#0f172a);
|
|
324
|
+
}
|
|
325
|
+
.cta{
|
|
326
|
+
text-align:center;
|
|
327
|
+
}
|
|
328
|
+
.hero p,.benefits p,.card p,.social-proof p,.cta p{
|
|
329
|
+
color:var(--muted);
|
|
330
|
+
}
|
|
331
|
+
@media (max-width:900px){
|
|
332
|
+
.benefits,.grid{
|
|
333
|
+
grid-template-columns:1fr;
|
|
334
|
+
}
|
|
335
|
+
.site-header{
|
|
336
|
+
padding:16px 18px;
|
|
337
|
+
}
|
|
338
|
+
h1{
|
|
339
|
+
font-size:44px;
|
|
340
|
+
}
|
|
341
|
+
}`;
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
brand,
|
|
345
|
+
industry,
|
|
346
|
+
config,
|
|
347
|
+
projectName: `${brand.toLowerCase().replace(/[^\w.-]/g, "-")}-landing`,
|
|
348
|
+
files: {
|
|
349
|
+
"index.html": html,
|
|
350
|
+
"style.css": css
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function validateWebsiteOutput(files, profile) {
|
|
356
|
+
const html = String(files["index.html"] || "");
|
|
357
|
+
const css = String(files["style.css"] || "");
|
|
358
|
+
const issues = [];
|
|
359
|
+
const htmlLower = html.toLowerCase();
|
|
360
|
+
const cssLower = css.toLowerCase();
|
|
361
|
+
|
|
362
|
+
if (!html.startsWith("<!doctype html>")) issues.push("missing_doctype");
|
|
363
|
+
if (!/<html[\s>]/i.test(html)) issues.push("missing_html_tag");
|
|
364
|
+
if (!/<\/html>\s*$/i.test(html)) issues.push("missing_html_close");
|
|
365
|
+
if (!/<main>/i.test(html) || !/<\/main>/i.test(html)) issues.push("main_structure_invalid");
|
|
366
|
+
if (/<p=|<p"|<p='|<\/>\s*/i.test(html)) issues.push("invalid_html_attributes");
|
|
367
|
+
if (/sans-hard|z-top\s*:|rgba\([^)]*[a-z][^)]*\)/i.test(css)) issues.push("invalid_css_tokens");
|
|
368
|
+
if (!/z-index\s*:/i.test(css)) issues.push("missing_z_index");
|
|
369
|
+
|
|
370
|
+
for (const banned of profile.config.bannedTerms) {
|
|
371
|
+
if (htmlLower.includes(banned)) issues.push(`industry_drift:${banned}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const requiredHits = profile.config.requiredTerms.filter((term) => htmlLower.includes(term));
|
|
375
|
+
if (requiredHits.length < Math.max(1, Math.min(2, profile.config.requiredTerms.length))) {
|
|
376
|
+
issues.push("reference_fidelity_low");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (/featured pieces|new era|daily layer/i.test(html) && profile.industry !== "brand") {
|
|
380
|
+
issues.push("generic_template_detected");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
ok: issues.length === 0,
|
|
385
|
+
issues
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
module.exports = {
|
|
390
|
+
inferBrandName,
|
|
391
|
+
detectIndustry,
|
|
392
|
+
buildWebsiteProject,
|
|
393
|
+
validateWebsiteOutput
|
|
394
|
+
};
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Du bist das Website Builder Brain bei BLUN KING.
|
|
2
|
+
|
|
3
|
+
DEIN JOB: Echte Websites bauen. Nicht analysieren. Nicht diskutieren. Nicht ausweichen.
|
|
4
|
+
|
|
5
|
+
HARD RULES:
|
|
6
|
+
- Wenn der User eine Landingpage/Webseite will, ist das BUILD.
|
|
7
|
+
- Referenzen sind Stilquelle und Strukturquelle, nicht Endergebnis.
|
|
8
|
+
- Liefere direkt eine umsetzbare neue Version.
|
|
9
|
+
- Wenn Datei-Erstellung möglich ist, erzeuge IMMER echte Dateien.
|
|
10
|
+
- Mindestens index.html + style.css.
|
|
11
|
+
- Wenn sinnvoll, zusätzlich script.js.
|
|
12
|
+
- Kein Blueprint als Endergebnis.
|
|
13
|
+
- Kein "kopiere das".
|
|
14
|
+
- Kein Backend/API/Testskript-Ausweichmüll.
|
|
15
|
+
- Kein SEO/Head-Audit als Hauptantwort.
|
|
16
|
+
- "Arbeite autonom" bedeutet: einfach machen.
|
|
17
|
+
|
|
18
|
+
SEITENSTRUKTUR:
|
|
19
|
+
- Header
|
|
20
|
+
- Hero mit klarer H1 und CTA
|
|
21
|
+
- Benefits / Features
|
|
22
|
+
- Social Proof / Vertrauenselemente
|
|
23
|
+
- CTA-Block
|
|
24
|
+
- Footer
|
|
25
|
+
|
|
26
|
+
DESIGN:
|
|
27
|
+
- hochwertig
|
|
28
|
+
- modern
|
|
29
|
+
- responsive
|
|
30
|
+
- starke Typografie
|
|
31
|
+
- klare Hierarchie
|
|
32
|
+
- mobile first
|
|
33
|
+
- gute Conversion-Logik
|
|
34
|
+
|
|
35
|
+
OUTPUT:
|
|
36
|
+
- echte Dateien
|
|
37
|
+
- kurze Begleitnachricht
|
|
38
|
+
- wenn möglich create_project oder write_file
|