designnn 0.2.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/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "designnn",
3
+ "version": "0.2.0",
4
+ "description": "Trend-driven design prompt engine for Figma AI. Generate high-quality UI/UX prompts powered by real-time design trend analysis.",
5
+ "type": "module",
6
+ "bin": {
7
+ "designnn": "./dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "bun build src/cli.ts --outdir dist --target node",
11
+ "dev": "bun run src/cli.ts",
12
+ "start": "node dist/cli.js"
13
+ },
14
+ "keywords": [
15
+ "design",
16
+ "figma",
17
+ "ai",
18
+ "prompt",
19
+ "ui",
20
+ "ux",
21
+ "trend",
22
+ "mcp",
23
+ "cli"
24
+ ],
25
+ "author": "concepter (Yoshihisa Abe)",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/abe430/designnn"
30
+ },
31
+ "files": [
32
+ "dist/",
33
+ "public/",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.27.1",
42
+ "@types/express": "^5.0.6",
43
+ "chalk": "^5.6.2",
44
+ "commander": "^14.0.3",
45
+ "express": "^5.2.1",
46
+ "openai": "^6.29.0",
47
+ "ora": "^9.3.0",
48
+ "zod": "^4.3.6"
49
+ },
50
+ "devDependencies": {
51
+ "@types/bun": "latest",
52
+ "typescript": "^5.9.3"
53
+ }
54
+ }
package/public/app.js ADDED
@@ -0,0 +1,277 @@
1
+ // ============================================
2
+ // DESIGNNN Web UI — Client Application
3
+ // ============================================
4
+
5
+ (function () {
6
+ "use strict";
7
+
8
+ // --- State ---
9
+ let trends = [];
10
+ let currentFilter = "all";
11
+
12
+ // --- DOM References ---
13
+ const navBtns = document.querySelectorAll(".nav-btn");
14
+ const tabContents = document.querySelectorAll(".tab-content");
15
+ const chatInput = document.getElementById("chat-input");
16
+ const chatSubmit = document.getElementById("chat-submit");
17
+ const chatResult = document.getElementById("chat-result");
18
+ const quickBtns = document.querySelectorAll(".quick-btn");
19
+ const filterBtns = document.querySelectorAll(".filter-btn");
20
+ const trendsGrid = document.getElementById("trends-grid");
21
+ const exploreResult = document.getElementById("explore-result");
22
+ const mixSelect1 = document.getElementById("mix-select-1");
23
+ const mixSelect2 = document.getElementById("mix-select-2");
24
+ const mixContext = document.getElementById("mix-context");
25
+ const mixSubmit = document.getElementById("mix-submit");
26
+ const mixResult = document.getElementById("mix-result");
27
+
28
+ // --- Tab Navigation ---
29
+ navBtns.forEach((btn) => {
30
+ btn.addEventListener("click", () => {
31
+ const tab = btn.dataset.tab;
32
+ navBtns.forEach((b) => b.classList.remove("active"));
33
+ tabContents.forEach((t) => t.classList.remove("active"));
34
+ btn.classList.add("active");
35
+ document.getElementById(`tab-${tab}`).classList.add("active");
36
+ });
37
+ });
38
+
39
+ // --- Utility: Show Loading ---
40
+ function showLoading(container) {
41
+ container.classList.remove("hidden");
42
+ container.innerHTML = `
43
+ <div class="loading-indicator">
44
+ <div class="loading-dots">
45
+ <span></span><span></span><span></span>
46
+ </div>
47
+ <span>Generating prompt...</span>
48
+ </div>
49
+ `;
50
+ }
51
+
52
+ // --- Utility: Show Result ---
53
+ function showResult(container, prompt, label) {
54
+ container.classList.remove("hidden");
55
+ container.innerHTML = `
56
+ <div class="result-header">
57
+ <div class="result-title">${label || "Generated Prompt"}</div>
58
+ <button class="btn-copy" onclick="copyPrompt(this)">
59
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
60
+ Copy
61
+ </button>
62
+ </div>
63
+ <div class="result-body">
64
+ <pre>${escapeHtml(prompt)}</pre>
65
+ </div>
66
+ <div class="result-footer">
67
+ Paste this prompt into Figma AI (Ctrl+I / Cmd+I)
68
+ </div>
69
+ `;
70
+ }
71
+
72
+ // --- Utility: Show Error ---
73
+ function showError(container, message) {
74
+ container.classList.remove("hidden");
75
+ container.innerHTML = `
76
+ <div class="error-message">${escapeHtml(message)}</div>
77
+ `;
78
+ }
79
+
80
+ // --- Utility: Escape HTML ---
81
+ function escapeHtml(text) {
82
+ const div = document.createElement("div");
83
+ div.textContent = text;
84
+ return div.innerHTML;
85
+ }
86
+
87
+ // --- Copy to Clipboard ---
88
+ window.copyPrompt = function (btn) {
89
+ const pre = btn.closest(".result-area").querySelector("pre");
90
+ if (!pre) return;
91
+ navigator.clipboard.writeText(pre.textContent).then(() => {
92
+ btn.classList.add("copied");
93
+ btn.innerHTML = `
94
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
95
+ Copied!
96
+ `;
97
+ setTimeout(() => {
98
+ btn.classList.remove("copied");
99
+ btn.innerHTML = `
100
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
101
+ Copy
102
+ `;
103
+ }, 2000);
104
+ });
105
+ };
106
+
107
+ // ============================================
108
+ // CHAT
109
+ // ============================================
110
+ async function handleChat(message) {
111
+ if (!message.trim()) return;
112
+ chatSubmit.disabled = true;
113
+ chatSubmit.textContent = "Generating...";
114
+ showLoading(chatResult);
115
+
116
+ try {
117
+ const res = await fetch("/api/chat", {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify({ message }),
121
+ });
122
+ const data = await res.json();
123
+ if (!res.ok) throw new Error(data.error || "Failed to generate");
124
+ showResult(chatResult, data.prompt, `Prompt for: "${message}"`);
125
+ } catch (err) {
126
+ showError(chatResult, err.message);
127
+ } finally {
128
+ chatSubmit.disabled = false;
129
+ chatSubmit.textContent = "Generate";
130
+ }
131
+ }
132
+
133
+ chatSubmit.addEventListener("click", () => handleChat(chatInput.value));
134
+ chatInput.addEventListener("keydown", (e) => {
135
+ if (e.key === "Enter") handleChat(chatInput.value);
136
+ });
137
+
138
+ quickBtns.forEach((btn) => {
139
+ btn.addEventListener("click", () => {
140
+ chatInput.value = btn.dataset.prompt;
141
+ handleChat(btn.dataset.prompt);
142
+ });
143
+ });
144
+
145
+ // ============================================
146
+ // EXPLORE
147
+ // ============================================
148
+ async function loadTrends() {
149
+ try {
150
+ const res = await fetch("/api/trends");
151
+ const data = await res.json();
152
+ trends = data.trends;
153
+ renderTrends(trends);
154
+ populateMixSelects(trends);
155
+ } catch (err) {
156
+ trendsGrid.innerHTML = `<div class="error-message">Failed to load trends</div>`;
157
+ }
158
+ }
159
+
160
+ function renderTrends(list) {
161
+ trendsGrid.innerHTML = list
162
+ .map(
163
+ (t) => `
164
+ <div class="trend-card" data-id="${t.id}">
165
+ <div class="trend-card-header">
166
+ <span class="trend-name">${escapeHtml(t.name)}</span>
167
+ <span class="trend-category">${t.category}</span>
168
+ </div>
169
+ <div class="trend-desc">${escapeHtml(t.description)}</div>
170
+ <div class="trend-popularity">
171
+ <div class="popularity-bar">
172
+ <div class="popularity-fill" style="width: ${t.popularity}%"></div>
173
+ </div>
174
+ <span class="popularity-value">${t.popularity}%</span>
175
+ </div>
176
+ <div class="trend-keywords">
177
+ ${t.keywords.map((k) => `<span class="keyword-tag">${escapeHtml(k)}</span>`).join("")}
178
+ </div>
179
+ <div class="trend-card-action">
180
+ <button class="btn-generate" onclick="generateFromTrend('${t.id}')">Generate Prompt</button>
181
+ </div>
182
+ </div>
183
+ `
184
+ )
185
+ .join("");
186
+ }
187
+
188
+ filterBtns.forEach((btn) => {
189
+ btn.addEventListener("click", () => {
190
+ currentFilter = btn.dataset.category;
191
+ filterBtns.forEach((b) => b.classList.remove("active"));
192
+ btn.classList.add("active");
193
+
194
+ if (currentFilter === "all") {
195
+ renderTrends(trends);
196
+ } else {
197
+ renderTrends(trends.filter((t) => t.category === currentFilter));
198
+ }
199
+ exploreResult.classList.add("hidden");
200
+ });
201
+ });
202
+
203
+ window.generateFromTrend = async function (trendId) {
204
+ showLoading(exploreResult);
205
+ // Scroll to result
206
+ exploreResult.scrollIntoView({ behavior: "smooth", block: "nearest" });
207
+
208
+ try {
209
+ const res = await fetch("/api/explore/generate", {
210
+ method: "POST",
211
+ headers: { "Content-Type": "application/json" },
212
+ body: JSON.stringify({ trendId }),
213
+ });
214
+ const data = await res.json();
215
+ if (!res.ok) throw new Error(data.error || "Failed to generate");
216
+ showResult(exploreResult, data.prompt, `Trend: ${data.trend.name}`);
217
+ } catch (err) {
218
+ showError(exploreResult, err.message);
219
+ }
220
+ };
221
+
222
+ // ============================================
223
+ // MIX
224
+ // ============================================
225
+ function populateMixSelects(list) {
226
+ const options = list
227
+ .map((t) => `<option value="${t.id}">${t.name} [${t.category}]</option>`)
228
+ .join("");
229
+ const placeholder = `<option value="">Select a trend...</option>`;
230
+ mixSelect1.innerHTML = placeholder + options;
231
+ mixSelect2.innerHTML = placeholder + options;
232
+ }
233
+
234
+ async function handleMix() {
235
+ const t1 = mixSelect1.value;
236
+ const t2 = mixSelect2.value;
237
+ const ctx = mixContext.value;
238
+
239
+ if (!t1 || !t2) {
240
+ showError(mixResult, "Please select two trends to mix.");
241
+ return;
242
+ }
243
+ if (t1 === t2) {
244
+ showError(mixResult, "Please select two different trends.");
245
+ return;
246
+ }
247
+
248
+ mixSubmit.disabled = true;
249
+ mixSubmit.textContent = "Mixing...";
250
+ showLoading(mixResult);
251
+
252
+ try {
253
+ const res = await fetch("/api/mix", {
254
+ method: "POST",
255
+ headers: { "Content-Type": "application/json" },
256
+ body: JSON.stringify({ trend1Id: t1, trend2Id: t2, context: ctx || undefined }),
257
+ });
258
+ const data = await res.json();
259
+ if (!res.ok) throw new Error(data.error || "Failed to mix");
260
+ showResult(
261
+ mixResult,
262
+ data.prompt,
263
+ `Mix: ${data.trend1.name} × ${data.trend2.name}`
264
+ );
265
+ } catch (err) {
266
+ showError(mixResult, err.message);
267
+ } finally {
268
+ mixSubmit.disabled = false;
269
+ mixSubmit.textContent = "Mix & Generate";
270
+ }
271
+ }
272
+
273
+ mixSubmit.addEventListener("click", handleMix);
274
+
275
+ // --- Init ---
276
+ loadTrends();
277
+ })();
@@ -0,0 +1,114 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>DESIGNNN — Trend-driven Design Prompt Engine</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
10
+ <link rel="stylesheet" href="/style.css" />
11
+ </head>
12
+ <body>
13
+ <!-- Header -->
14
+ <header class="header">
15
+ <div class="header-left">
16
+ <h1 class="logo">DESIGNNN</h1>
17
+ <span class="tagline">for Figma AI</span>
18
+ </div>
19
+ <nav class="nav">
20
+ <button class="nav-btn active" data-tab="chat">Chat</button>
21
+ <button class="nav-btn" data-tab="explore">Explore</button>
22
+ <button class="nav-btn" data-tab="mix">Mix</button>
23
+ </nav>
24
+ <a href="https://github.com/abe430/designnn" target="_blank" class="github-link">
25
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
26
+ </a>
27
+ </header>
28
+
29
+ <!-- Main Content -->
30
+ <main class="main">
31
+ <!-- Chat Tab -->
32
+ <section class="tab-content active" id="tab-chat">
33
+ <div class="tab-hero">
34
+ <h2>Chat to Prompt</h2>
35
+ <p>Describe what you want to design. DESIGNNN will generate an optimized Figma AI prompt.</p>
36
+ </div>
37
+ <div class="chat-container">
38
+ <div class="input-group">
39
+ <input type="text" id="chat-input" placeholder="e.g., SaaS dashboard with analytics charts and dark theme" autocomplete="off" />
40
+ <button id="chat-submit" class="btn-primary">Generate</button>
41
+ </div>
42
+ <div class="quick-prompts">
43
+ <span class="quick-label">Try:</span>
44
+ <button class="quick-btn" data-prompt="Landing page for a fitness app with bold typography">Fitness Landing</button>
45
+ <button class="quick-btn" data-prompt="E-commerce product page with glassmorphism style">E-commerce Glass</button>
46
+ <button class="quick-btn" data-prompt="Mobile onboarding flow for a meditation app">Meditation Onboarding</button>
47
+ <button class="quick-btn" data-prompt="SaaS pricing page with dark theme and neon accents">SaaS Pricing</button>
48
+ </div>
49
+ <div id="chat-result" class="result-area hidden"></div>
50
+ </div>
51
+ </section>
52
+
53
+ <!-- Explore Tab -->
54
+ <section class="tab-content" id="tab-explore">
55
+ <div class="tab-hero">
56
+ <h2>Explore Trends</h2>
57
+ <p>Browse 18+ curated UI/UX design trends. Click any trend to generate a Figma AI prompt.</p>
58
+ </div>
59
+ <div class="explore-container">
60
+ <div class="filter-bar">
61
+ <button class="filter-btn active" data-category="all">All</button>
62
+ <button class="filter-btn" data-category="style">Style</button>
63
+ <button class="filter-btn" data-category="component">Component</button>
64
+ <button class="filter-btn" data-category="pattern">Pattern</button>
65
+ <button class="filter-btn" data-category="layout">Layout</button>
66
+ <button class="filter-btn" data-category="interaction">Interaction</button>
67
+ </div>
68
+ <div id="trends-grid" class="trends-grid"></div>
69
+ <div id="explore-result" class="result-area hidden"></div>
70
+ </div>
71
+ </section>
72
+
73
+ <!-- Mix Tab -->
74
+ <section class="tab-content" id="tab-mix">
75
+ <div class="tab-hero">
76
+ <h2>Trend Mixer</h2>
77
+ <p>Combine two design trends into something fresh. Cross-pollinate styles for innovative UI.</p>
78
+ </div>
79
+ <div class="mix-container">
80
+ <div class="mix-selectors">
81
+ <div class="mix-slot" id="mix-slot-1">
82
+ <div class="slot-label">Trend 1</div>
83
+ <select id="mix-select-1" class="mix-select">
84
+ <option value="">Select a trend...</option>
85
+ </select>
86
+ </div>
87
+ <div class="mix-icon">
88
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v1.59L18 8z"/><circle cx="12" cy="12" r="3"/><path d="M6 16h1v2c0 2.76 2.24 5 5 5s5-2.24 5-5v-1.59"/></svg>
89
+ <span>MIX</span>
90
+ </div>
91
+ <div class="mix-slot" id="mix-slot-2">
92
+ <div class="slot-label">Trend 2</div>
93
+ <select id="mix-select-2" class="mix-select">
94
+ <option value="">Select a trend...</option>
95
+ </select>
96
+ </div>
97
+ </div>
98
+ <div class="context-group">
99
+ <input type="text" id="mix-context" placeholder="Additional context (optional): e.g., for a music streaming app" />
100
+ </div>
101
+ <button id="mix-submit" class="btn-primary btn-mix">Mix & Generate</button>
102
+ <div id="mix-result" class="result-area hidden"></div>
103
+ </div>
104
+ </section>
105
+ </main>
106
+
107
+ <!-- Footer -->
108
+ <footer class="footer">
109
+ <p>DESIGNNN v0.1.0 — Made with energy by <a href="https://github.com/abe430" target="_blank">concepter</a></p>
110
+ </footer>
111
+
112
+ <script src="/app.js"></script>
113
+ </body>
114
+ </html>