12 files changed
+228
-4
assets/aso/microsoftstore/ar.md
+1
-1
@@ -109,7 +109,7 @@
109
110
تتضمن هذه الإصدارة تحسينات في الأداء وإصلاحات للأخطاء.
111
112
-## Search Terms (7 terms, max 30 characters each)
112
+## Search Terms (7 terms, max 40 characters each, max 21 words total)
113
1. مساعد ذكاء اصطناعي
114
2. ذكاء اصطناعي خاص
115
3. محادثة ذكاء بدون انترنت
assets/aso/microsoftstore/en-US.md
+1
-1
@@ -109,7 +109,7 @@ Built with modern native technology for a fast, lightweight experience on your W
109
110
Performance improvements and bug fixes included in this release.
111
112
-## Search Terms (7 terms, max 30 characters each)
112
+## Search Terms (7 terms, max 40 characters each, max 21 words total)
113
1. AI assistant
114
2. private AI
115
3. offline AI chat
assets/aso/microsoftstore/id.md
+1
-1
@@ -109,7 +109,7 @@ Dibuat dengan teknologi natif modern untuk pengalaman cepat dan ringan di PC Win
109
110
Peningkatan performa dan perbaikan bug disertakan dalam rilis ini.
111
112
-## Search Terms (7 terms, max 30 characters each)
112
+## Search Terms (7 terms, max 40 characters each, max 21 words total)
113
1. asisten AI
114
2. AI privat
115
3. obrolan AI offline
assets/aso/microsoftstore/screenshots/generate.mjs
new
+224
@@ -0,0 +1,224 @@
1
+// Microsoft Store screenshot generator — Siti AI.
2
+// Renders editorial frames using Siti AI's REAL chat UI markup and its own
3
+// stylesheet (src/App.css), framed with a caption in the app's minimal,
4
+// monochrome Scandinavian style, at the Windows Store desktop size
5
+// (1920x1080 landscape) via headless chromium.
6
+//
7
+// node assets/aso/microsoftstore/screenshots/generate.mjs (run from repo root)
8
+//
9
+// Output: assets/aso/microsoftstore/screenshots/windows/NN-<slug>.png (1920x1080)
10
+// Playwright is borrowed from the sibling SplitFire repo's shared cache
11
+// (playwright is repo-agnostic); Siti AI has none of its own.
12
+//
13
+// Honest feature set only (see assets/aso/microsoftstore/en-US.md): a private,
14
+// on-device AI assistant — ask anything, draft, summarize, translate; no
15
+// cloud, no account, works offline after a one-time model download.
16
+
17
+import { createRequire } from "node:module";
18
+import { existsSync, mkdirSync, readFileSync } from "node:fs";
19
+import { dirname, resolve } from "node:path";
20
+import { fileURLToPath } from "node:url";
21
+
22
+const HERE = dirname(fileURLToPath(import.meta.url));
23
+const REPO = resolve(HERE, "../../../..");
24
+const require = createRequire(import.meta.url);
25
+const pw = [
26
+ process.env.HOME + "/Repositories/splitfire/.ds-sync/node_modules/playwright",
27
+ resolve(REPO, ".ds-sync/node_modules/playwright"),
28
+].find((p) => existsSync(p));
29
+if (!pw) throw new Error("playwright not found (borrow from splitfire/.ds-sync)");
30
+const { chromium } = require(pw);
31
+
32
+// The app's real stylesheet is the source of truth for the chat UI look.
33
+const APP_CSS = readFileSync(resolve(REPO, "src/App.css"), "utf8");
34
+
35
+const PLATFORMS = { windows: { w: 1920, h: 1080, zoom: 1.15, cap: 62, sub: 25 } };
36
+
37
+// ── Real Siti AI markup (classes from src/App.tsx + src/App.css) ────────────
38
+const bubble = (role, text) =>
39
+ `<div class="message-row ${role}"><div class="bubble">${text}</div></div>`;
40
+
41
+const settingsIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`;
42
+const sendIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4 20-7z"/></svg>`;
43
+
44
+// A bounded chat window holding the real .app markup.
45
+const chatWindow = (subtitle, rows, placeholder = "Message Siti…") => `
46
+ <div class="siti-window">
47
+ <div class="app">
48
+ <header class="header">
49
+ <div class="header-title">
50
+ <h1>Siti AI</h1>
51
+ <span class="header-subtitle">${subtitle}</span>
52
+ </div>
53
+ <div class="header-actions">
54
+ <span class="status-dot"></span>
55
+ <button class="icon-button">${settingsIcon}</button>
56
+ </div>
57
+ </header>
58
+ <div class="messages">${rows}</div>
59
+ <div class="input-area">
60
+ <div class="input-form">
61
+ <input class="input-field" value="" placeholder="${placeholder}" readonly />
62
+ <button class="send-button">${sendIcon}</button>
63
+ </div>
64
+ <div class="input-hint">Private · runs on your PC</div>
65
+ </div>
66
+ </div>
67
+ </div>`;
68
+
69
+const settingsWindow = () => `
70
+ <div class="siti-window">
71
+ <div class="app">
72
+ <div class="settings-header">
73
+ <h1>Settings</h1>
74
+ <button class="settings-done">Done</button>
75
+ </div>
76
+ <div class="settings-content">
77
+ <div class="settings-section">
78
+ <div class="section-label">Model</div>
79
+ <div class="settings-card">
80
+ <div class="settings-row">
81
+ <span class="row-label">On-device model</span>
82
+ <span class="row-value">Gemma 2 · 2B</span>
83
+ </div>
84
+ <div class="model-detail">
85
+ <div class="model-org">Google DeepMind</div>
86
+ <div class="model-desc">Runs entirely on your PC. Downloaded once, then works offline.</div>
87
+ <div class="model-status ready"><span class="model-status-dot"></span>Ready</div>
88
+ </div>
89
+ </div>
90
+ <div class="settings-note">No account, no cloud, no tracking. Your conversations never leave this device.</div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>`;
95
+
96
+// ── Frames (honest: private, on-device, capable, offline) ───────────────────
97
+const FRAMES = [
98
+ {
99
+ slug: "ask-siti-anything",
100
+ caption: "Ask Siti anything",
101
+ sub: "A capable assistant for questions, drafts, and ideas",
102
+ stage: () =>
103
+ chatWindow("Gemma 2 · 2B · private", [
104
+ bubble("user", "Explain vector embeddings like I'm five."),
105
+ bubble(
106
+ "assistant",
107
+ "Imagine every word gets a spot on a huge map. Words that mean similar things sit close together — so the computer can tell that “cat” and “kitten” are neighbours.",
108
+ ),
109
+ bubble("user", "Nice. Now give me three names for a coffee app."),
110
+ bubble("assistant", "Sure — Dailygrind, Pour, and Warmup. Want a few more playful ones?"),
111
+ ].join("")),
112
+ },
113
+ {
114
+ slug: "private-on-device",
115
+ caption: "Private by design",
116
+ sub: "Every conversation stays on your own machine",
117
+ stage: () =>
118
+ chatWindow("Gemma 2 · 2B · private", [
119
+ bubble("user", "Is any of this sent to the cloud?"),
120
+ bubble(
121
+ "assistant",
122
+ "No. Everything runs on your PC. Your messages never leave this device — no account, no servers, no tracking.",
123
+ ),
124
+ bubble("user", "So I can paste something sensitive?"),
125
+ bubble("assistant", "Yes. Nothing you type here is uploaded. It is yours alone."),
126
+ ].join("")),
127
+ },
128
+ {
129
+ slug: "draft-and-summarize",
130
+ caption: "Draft, summarise, translate",
131
+ sub: "Turn a rough note into something finished",
132
+ stage: () =>
133
+ chatWindow("Gemma 2 · 2B · private", [
134
+ bubble("user", "Summarise this in one line: the team agreed to ship the beta on Friday, Alex owns QA, Sam writes the release notes."),
135
+ bubble("assistant", "Beta ships Friday — Alex on QA, Sam on release notes."),
136
+ bubble("user", "Now say it in Indonesian."),
137
+ bubble("assistant", "Beta rilis Jumat — Alex urus QA, Sam tulis catatan rilis."),
138
+ ].join("")),
139
+ },
140
+ {
141
+ slug: "works-offline",
142
+ caption: "Works offline, anywhere",
143
+ sub: "After a one-time download, no connection needed",
144
+ stage: () =>
145
+ chatWindow("Gemma 2 · 2B · offline", [
146
+ bubble("user", "Do I need internet for this?"),
147
+ bubble(
148
+ "assistant",
149
+ "Nope. Once the model is downloaded, Siti works fully offline — on a plane, on a commute, or off the grid, it still answers.",
150
+ ),
151
+ bubble("user", "Perfect for the flight tomorrow."),
152
+ bubble("assistant", "Exactly. Airplane mode, no problem."),
153
+ ].join("")),
154
+ },
155
+ {
156
+ slug: "on-device-model",
157
+ caption: "The model lives on your PC",
158
+ sub: "One download, then it is yours — no sign-in, no cloud",
159
+ stage: () => settingsWindow(),
160
+ },
161
+];
162
+
163
+// ── Page ────────────────────────────────────────────────────────────────────
164
+const WIN_W = 560;
165
+const WIN_H = 672;
166
+
167
+const page = (p, frame, idx) => `<!doctype html><html><head><meta charset="utf-8"><style>
168
+ ${APP_CSS}
169
+ /* Force the light theme regardless of the headless colour-scheme. */
170
+ :root{--bg:#fafafa;--surface:#ffffff;--border:#e8e8e8;--text-primary:#1a1a1a;--text-secondary:#8a8a8a;--accent:#2d2d2d;--accent-hover:#1a1a1a;--user-bubble:#1a1a1a;--user-bubble-text:#fff;--ai-bubble:#f0f0f0;--ai-bubble-text:#1a1a1a;--input-bg:#f5f5f5}
171
+ html,body{margin:0;width:${p.w}px;height:${p.h}px;overflow:hidden}
172
+ .shot{width:${p.w}px;height:${p.h}px;display:flex;flex-direction:column;
173
+ font-family:"Inter",-apple-system,"Segoe UI",Roboto,sans-serif;color:#1a1a1a;
174
+ background:
175
+ radial-gradient(1100px 640px at 82% 14%, rgba(0,0,0,0.035), transparent 55%),
176
+ linear-gradient(165deg,#f7f7f5 0%,#eeeeeb 100%);
177
+ background-color:#f2f2ef}
178
+ .cap-wrap{padding:${Math.round(p.h * 0.055)}px ${Math.round(p.w * 0.07)}px 0}
179
+ .cap-index{display:inline-block;background:#1a1a1a;color:#fafafa;padding:5px 11px;border-radius:6px;
180
+ font-size:${Math.round(p.sub * 0.72)}px;font-weight:700;letter-spacing:0.16em;text-transform:uppercase}
181
+ .cap{font-size:${p.cap}px;line-height:1.04;font-weight:700;letter-spacing:-0.03em;margin-top:20px;color:#141414}
182
+ .sub{font-size:${p.sub}px;margin-top:15px;color:#6a6a6a}
183
+ .stage{flex:1;display:flex;align-items:center;justify-content:center;padding:${Math.round(p.h * 0.035)}px}
184
+ .stage-inner{zoom:${p.zoom}}
185
+ /* Bounded desktop window that holds the real .app markup. */
186
+ .siti-window{width:${WIN_W}px;height:${WIN_H}px;background:var(--bg);border:1px solid var(--border);
187
+ border-radius:22px;overflow:hidden;box-shadow:0 34px 80px rgba(20,20,20,0.16)}
188
+ .siti-window .app{height:100%}
189
+ .siti-window .header,.siti-window .input-area{padding-left:22px;padding-right:22px}
190
+ .siti-window .header{padding-top:20px;padding-bottom:16px}
191
+ .siti-window .input-area{padding-top:16px;padding-bottom:18px}
192
+ .siti-window .settings-header{padding:20px}
193
+ .siti-window .messages{padding:20px 0}
194
+ </style></head>
195
+ <body>
196
+ <div class="shot">
197
+ <div class="cap-wrap">
198
+ <span class="cap-index">Siti AI — 0${idx + 1}</span>
199
+ <div class="cap">${frame.caption}</div>
200
+ <div class="sub">${frame.sub}</div>
201
+ </div>
202
+ <div class="stage"><div class="stage-inner">${frame.stage(p)}</div></div>
203
+ </div>
204
+ </body></html>`;
205
+
206
+// ── Render ──────────────────────────────────────────────────────────────────
207
+const browser = await chromium.launch();
208
+try {
209
+ for (const [key, p] of Object.entries(PLATFORMS)) {
210
+ const dir = resolve(HERE, key);
211
+ mkdirSync(dir, { recursive: true });
212
+ const ctx = await browser.newContext({ viewport: { width: p.w, height: p.h }, deviceScaleFactor: 1 });
213
+ const pg = await ctx.newPage();
214
+ for (const [i, frame] of FRAMES.entries()) {
215
+ await pg.setContent(page(p, frame, i), { waitUntil: "load" });
216
+ const file = resolve(dir, `0${i + 1}-${frame.slug}.png`);
217
+ await pg.screenshot({ path: file, clip: { x: 0, y: 0, width: p.w, height: p.h } });
218
+ console.log("wrote", file.replace(REPO + "/", ""));
219
+ }
220
+ await ctx.close();
221
+ }
222
+} finally {
223
+ await browser.close();
224
+}
assets/aso/microsoftstore/screenshots/windows/01-ask-siti-anything.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/01-ask-siti-anything.png differ
assets/aso/microsoftstore/screenshots/windows/02-private-on-device.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/02-private-on-device.png differ
assets/aso/microsoftstore/screenshots/windows/03-draft-and-summarize.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/03-draft-and-summarize.png differ
assets/aso/microsoftstore/screenshots/windows/04-works-offline.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/04-works-offline.png differ
assets/aso/microsoftstore/screenshots/windows/05-on-device-model.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/05-on-device-model.png differ
assets/aso/microsoftstore/store-listing/app-tile-icon-300.png
Binary files /dev/null and b/assets/aso/microsoftstore/store-listing/app-tile-icon-300.png differ
assets/aso/microsoftstore/store-listing/hero-art-1920x1080.png
Binary files /dev/null and b/assets/aso/microsoftstore/store-listing/hero-art-1920x1080.png differ
assets/aso/microsoftstore/sv-SE.md
+1
-1
@@ -109,7 +109,7 @@ Byggd med modern nativ teknik för en snabb och lätt upplevelse på din Windows
109
110
Prestandaförbättringar och buggfixar ingår i den här versionen.
111
112
-## Search Terms (7 terms, max 30 characters each)
112
+## Search Terms (7 terms, max 40 characters each, max 21 words total)
113
1. AI-assistent
114
2. privat AI
115
3. offline AI-chatt