webcake-storefront-mcp 1.1.0 → 1.1.2

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/dist/web-guide.js CHANGED
@@ -3,368 +3,956 @@
3
3
  *
4
4
  * Served at GET / when the client sends Accept: text/html or is a known bot UA.
5
5
  * Programmatic requests (healthcheck probes, MCP clients) still get JSON.
6
+ *
7
+ * Bilingual (vi/en): every string lives in META / T / FAQ dictionaries and
8
+ * guideHtml(origin, lang) renders one language. http.ts picks the language from
9
+ * ?lang= (falling back to vi); a toggle in the header links to the other language.
10
+ *
11
+ * Full SEO <head>: description, canonical, Open Graph, Twitter Card, JSON-LD for
12
+ * SoftwareApplication + WebSite + FAQPage so links unfurl nicely and the page
13
+ * can be indexed. ogImageSvg() is served at /og.svg; /og.png if you rasterize it.
14
+ *
15
+ * Self-contained (inline CSS + JS, no external fonts/trackers) so it loads instantly.
6
16
  */
17
+ const MCP_REMOTE_URL = "https://store.toolvn.io.vn/mcp";
18
+ const INSTALL_CMD = "npx -y webcake-storefront-mcp install";
19
+ const INSTALL_ALL_CMD = "npx -y webcake-storefront-mcp install --ide all --token &lt;TOKEN&gt; --session &lt;SESSION&gt;";
7
20
  const GITHUB_URL = "https://github.com/vuluu2k/webcake-storefront-mcp";
8
21
  const NPM_URL = "https://www.npmjs.com/package/webcake-storefront-mcp";
9
- const WEBCAKE_URL = "https://webcake.io";
10
- const FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
11
- <rect width="32" height="32" rx="7" fill="url(#g)"/>
12
- <defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
13
- <stop offset="0%" stop-color="#3FBB57"/>
14
- <stop offset="100%" stop-color="#108B67"/>
22
+ const DOCS_URL = `${GITHUB_URL}#readme`;
23
+ // ── Brand icon SVG (self-contained, no import needed) ────────────────────────
24
+ const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
25
+ <rect width="32" height="32" rx="7" fill="url(#sg)"/>
26
+ <defs><linearGradient id="sg" x1="0" y1="0" x2="1" y2="1">
27
+ <stop offset="0%" stop-color="#108B67"/>
28
+ <stop offset="100%" stop-color="#14a87c"/>
15
29
  </linearGradient></defs>
16
30
  <text x="16" y="22" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="700" font-size="17" fill="white">S</text>
17
31
  <circle cx="24" cy="9" r="4" fill="#FFD591"/>
18
32
  </svg>`;
19
- export function faviconSvg() {
20
- return FAVICON_SVG;
33
+ export const LANGS = ["vi", "en"];
34
+ export function normalizeLang(input) {
35
+ return input === "en" ? "en" : "vi";
36
+ }
37
+ // ── Inline icon set (Lucide-style, MIT, stroke icons) ────────────────────────
38
+ const ICONS = {
39
+ check: '<path d="M20 6 9 17l-5-5"/>',
40
+ brain: '<path d="M12 5a3 3 0 1 0-5.997.142 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.142 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/>',
41
+ wand: '<path d="m9.5 14.5 5-5"/><path d="M15 4V2"/><path d="M15 16v-2"/><path d="M8 9h2"/><path d="M20 9h2"/><path d="M17.8 11.8 19 13"/><path d="M17.8 6.2 19 5"/><path d="m3 21 9-9"/><path d="M12.2 6.2 11 5"/>',
42
+ check2: '<path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/>',
43
+ shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
44
+ cart: '<circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/>',
45
+ ticket: '<path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 11v2"/><path d="M13 17v2"/>',
46
+ mail: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
47
+ newspaper: '<path d="M4 3h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M8 7h8"/><path d="M8 11h8"/><path d="M8 15h5"/>',
48
+ link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
49
+ star: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>',
50
+ github: '<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/>',
51
+ rocket: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
52
+ edit: '<path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/>',
53
+ book: '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>',
54
+ package: '<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
55
+ arrow: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
56
+ clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
57
+ globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
58
+ bulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
59
+ server: '<rect width="20" height="8" x="2" y="2" rx="2"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 6h.01"/><path d="M6 18h.01"/>',
60
+ window: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M2 9h20"/><path d="M6 6.5h.01"/><path d="M9 6.5h.01"/>',
61
+ moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
62
+ sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
63
+ copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2v1"/>',
64
+ terminal: '<polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/>',
65
+ flame: '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>',
66
+ layers: '<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/>',
67
+ };
68
+ function icon(name) {
69
+ return `<svg class="i" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONS[name] ?? ""}</svg>`;
70
+ }
71
+ function tile(name) {
72
+ return `<span class="ic">${icon(name)}</span>`;
73
+ }
74
+ // ── i18n: per-language SEO metadata ──────────────────────────────────────────
75
+ const META = {
76
+ vi: {
77
+ title: "WebCake Storefront — Tạo website bán hàng chỉ bằng cách trò chuyện",
78
+ desc: "Bạn nói ý tưởng, trợ lý AI dựng trang, bạn xem trước rồi đăng lên — không cần kéo-thả hay biết lập trình. Kết nối Claude, Cursor, Windsurf với cửa hàng WebCake/StoreCake của bạn.",
79
+ keywords: "WebCake, StoreCake, storefront, AI, Claude, Cursor, Windsurf, tạo trang bán hàng, website bán hàng, no-code, bán hàng online, tạo trang bằng AI",
80
+ locale: "vi_VN",
81
+ },
82
+ en: {
83
+ title: "WebCake Storefront — Build your online store just by chatting",
84
+ desc: "Tell your AI assistant what you want, it builds the page on your WebCake store, you review and publish — no dragging blocks, no coding required. Works with Claude, Cursor, Windsurf and more.",
85
+ keywords: "WebCake, StoreCake, storefront, AI, Claude, Cursor, Windsurf, AI page builder, no-code, e-commerce, online store builder",
86
+ locale: "en_US",
87
+ },
88
+ };
89
+ // ── i18n: FAQ (also powers FAQPage JSON-LD) ──────────────────────────────────
90
+ const FAQ = {
91
+ vi: [
92
+ {
93
+ q: "Tôi không biết code có dùng được không?",
94
+ a: "Hoàn toàn được. Bạn chỉ cần nói chuyện với trợ lý AI bằng tiếng Việt thông thường — ví dụ \"Tạo cho tôi trang sản phẩm cho serum dưỡng da, có gallery ảnh và nút mua hàng\". Mọi phần kỹ thuật đều do trợ lý lo.",
95
+ },
96
+ {
97
+ q: "Có mất phí không?",
98
+ a: "Công cụ kết nối này hoàn toàn miễn phí và mã nguồn mở (MIT). Bạn chỉ cần tài khoản WebCake/StoreCake để lưu và xuất bản trang của mình.",
99
+ },
100
+ {
101
+ q: "Trang có sửa lại được không?",
102
+ a: "Có, dễ dàng. Bạn chỉ cần nói \"đổi màu nút sang tím\" hay \"thêm phần đánh giá khách hàng\" — trợ lý sửa đúng chỗ bạn muốn mà không làm hỏng phần còn lại của trang.",
103
+ },
104
+ {
105
+ q: "Dữ liệu của tôi có an toàn không?",
106
+ a: "An toàn. Mọi thay đổi đều được xem trước, bạn xác nhận mới lưu thật. Thông tin đăng nhập của bạn chỉ truyền qua HTTPS và không bao giờ bị lưu lại phía server.",
107
+ },
108
+ {
109
+ q: "Cần cài gì không?",
110
+ a: "Không bắt buộc. Bạn có thể dùng ngay qua trình duyệt (claude.ai) chỉ bằng cách thêm địa chỉ kết nối. Nếu dùng ứng dụng Claude Desktop, Cursor hay Windsurf trên máy tính, chỉ cần một lệnh cài nhanh là xong.",
111
+ },
112
+ ],
113
+ en: [
114
+ {
115
+ q: "Do I need to know how to code?",
116
+ a: "Not at all. Just describe what you want in plain words — for example \"Create a product page for my skincare serum with a photo gallery and a buy button\". The AI assistant handles everything technical.",
117
+ },
118
+ {
119
+ q: "Is it free?",
120
+ a: "Yes. This connector is completely free and open-source (MIT). You only need a WebCake/StoreCake account to save and publish your pages.",
121
+ },
122
+ {
123
+ q: "Can I edit a page after it's created?",
124
+ a: "Absolutely. Just say \"change the button colour to purple\" or \"add a customer reviews section\" — the assistant edits exactly what you asked for without touching the rest of the page.",
125
+ },
126
+ {
127
+ q: "Is my data safe?",
128
+ a: "Yes. Every change is previewed first; nothing is saved until you confirm. Your login credentials are only sent over HTTPS and are never stored server-side.",
129
+ },
130
+ {
131
+ q: "Do I need to install anything?",
132
+ a: "Not necessarily. You can use it right in your browser (claude.ai) by adding a connection address. If you use Claude Desktop, Cursor, or Windsurf on your computer, a single quick command is all it takes.",
133
+ },
134
+ ],
135
+ };
136
+ const T = {
137
+ vi: {
138
+ sub: "Tạo website bán hàng chỉ bằng cách trò chuyện — không cần biết lập trình",
139
+ running: "Đang hoạt động",
140
+ leadPre: "Bạn nói điều mình muốn, trợ lý AI dựng trang trên cửa hàng WebCake của bạn, bạn xem trước rồi ",
141
+ leadGrad: "đăng lên là xong",
142
+ leadPost: ". Không kéo-thả, không học gì thêm — chỉ cần nói chuyện như bình thường.",
143
+ ctaStart: "Bắt đầu ngay",
144
+ ctaStar: "Tặng sao trên GitHub",
145
+ flowH2: "Chỉ 4 bước đơn giản",
146
+ flow: [
147
+ { icon: "bulb", t: "Bạn nói", s: "mô tả trang bạn muốn" },
148
+ { icon: "brain", t: "Trợ lý AI", s: "Claude · Cursor · Windsurf…" },
149
+ { icon: "server", t: "WebCake", s: "lưu & kiểm tra trang" },
150
+ { icon: "window", t: "Cửa hàng của bạn", s: "trang web thật, live" },
151
+ ],
152
+ flowCap: "① Bạn nói mong muốn → ② Trợ lý AI dựng trang → ③ Bạn xem trước, chỉnh nếu thích → ④ Đăng lên là xong. Không cần biết lập trình, không cần kéo-thả block.",
153
+ howH2: "Tại sao dùng được ngay, không lo hỏng",
154
+ how: [
155
+ {
156
+ icon: "layers",
157
+ t: "Trang đẹp, đúng ý",
158
+ d: "Trợ lý hiểu đúng bố cục cửa hàng WebCake — banner, lưới sản phẩm, form đặt hàng, đếm ngược — và dựng trang trông chuyên nghiệp ngay từ đầu.",
159
+ },
160
+ {
161
+ icon: "check2",
162
+ t: "Không lo hỏng layout",
163
+ d: "Trước khi lưu, trang được kiểm tra tự động: bố cục có đúng không, các phần có khớp nhau không. Nếu có gì sai, trợ lý sẽ tự sửa.",
164
+ },
165
+ {
166
+ icon: "shield",
167
+ t: "An toàn — luôn xem trước khi lưu",
168
+ d: "Mọi thay đổi đều được hiển thị để bạn xem trước. Chưa xác nhận thì cửa hàng chưa bị đụng tới — bạn hoàn toàn kiểm soát.",
169
+ },
170
+ {
171
+ icon: "edit",
172
+ t: "Sửa nhẹ nhàng, đúng chỗ",
173
+ d: "Nói \"đổi nút sang màu tím\" hay \"thêm phần đánh giá\" — chỉ đúng chỗ đó được sửa, mọi thứ còn lại giữ nguyên.",
174
+ },
175
+ ],
176
+ buildH2: "Bạn có thể tạo những trang nào",
177
+ uses: [
178
+ {
179
+ icon: "cart",
180
+ t: "Trang bán 1 sản phẩm",
181
+ e: '"Tạo trang cho serum dưỡng da của tôi — có ảnh gallery, giá bán và nút đặt hàng."',
182
+ },
183
+ {
184
+ icon: "window",
185
+ t: "Trang chủ cửa hàng",
186
+ e: '"Tạo trang chủ — banner giới thiệu, các sản phẩm nổi bật, ô đăng ký nhận tin."',
187
+ },
188
+ {
189
+ icon: "flame",
190
+ t: "Trang flash sale",
191
+ e: '"Tạo trang flash sale — đồng hồ đếm ngược, danh sách sản phẩm giảm giá, nút mua dính cố định."',
192
+ },
193
+ {
194
+ icon: "ticket",
195
+ t: "Trang sự kiện / webinar",
196
+ e: '"Tạo trang đăng ký sự kiện — thời gian, lịch trình, form đăng ký tham dự."',
197
+ },
198
+ {
199
+ icon: "mail",
200
+ t: "Thiệp mời",
201
+ e: '"Tạo thiệp cưới online — tên, ngày tháng, địa chỉ, form xác nhận tham dự."',
202
+ },
203
+ {
204
+ icon: "newspaper",
205
+ t: "Trang blog / nội dung",
206
+ e: '"Tạo trang blog với các bài viết nổi bật và ô đăng ký nhận bản tin."',
207
+ },
208
+ {
209
+ icon: "link",
210
+ t: "Link-in-bio",
211
+ e: '"Tạo trang link-in-bio — ảnh đại diện, giới thiệu ngắn, 5 nút liên kết, mạng xã hội."',
212
+ },
213
+ {
214
+ icon: "wand",
215
+ t: "Bất cứ trang nào bạn muốn",
216
+ e: '"…rồi \"đổi màu nút\" hay \"thêm phần hỏi đáp\" — trợ lý sửa đúng chỗ đó thôi."',
217
+ },
218
+ ],
219
+ connectH2: "Kết nối trợ lý AI với cửa hàng của bạn",
220
+ m1Tag: "Cách ① · Dùng trên máy tính (Claude Desktop, Cursor, Windsurf…)",
221
+ m1Sub: "Phù hợp khi bạn dùng ứng dụng AI trên máy tính. Cần Node.js 18+ (miễn phí, tải tại nodejs.org). Chỉ một lệnh là xong:",
222
+ m1Steps: [
223
+ "<b>Cài Node.js 18+</b> (miễn phí) nếu máy chưa có — tải tại <b>nodejs.org</b>.",
224
+ "<b>Mở Terminal</b> (hoặc Command Prompt), dán lệnh sau và nhấn Enter:<pre>" + INSTALL_CMD + "</pre>",
225
+ "<b>Làm theo hướng dẫn hiện ra:</b> chọn ứng dụng bạn dùng (Claude, Cursor, Windsurf…) → đăng nhập tài khoản WebCake khi được hỏi.",
226
+ '<b>Mở lại ứng dụng AI.</b> Khi thấy <code class="inl">webcake-storefront</code> xuất hiện trong danh sách công cụ là bạn đã kết nối thành công — hãy thử nói "Tạo cho tôi trang sản phẩm…"',
227
+ ],
228
+ m1Note: "Muốn cài cho nhiều ứng dụng cùng lúc — dùng lệnh này:",
229
+ m2Tag: "Cách ② · Dùng ngay trên trình duyệt (claude.ai) — không cần cài gì",
230
+ m2Sub: "Phù hợp khi bạn dùng Claude trên web (claude.ai) hoặc không muốn cài thêm gì trên máy. Chỉ cần thêm địa chỉ kết nối, đăng nhập WebCake là dùng được.",
231
+ m2Steps: [
232
+ '<b>Copy địa chỉ kết nối</b> bên dưới:<a class="btn" href="{REMOTE}">{REMOTE} {ARROW}</a>',
233
+ '<b>Mở ứng dụng AI của bạn:</b><br>• claude.ai: vào <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: mở file <code class="inl">.mcp.json</code>',
234
+ "<b>Dán địa chỉ vừa copy vào</b> — trình duyệt sẽ mở trang đăng nhập WebCake tự động:<pre>{REMOTE}</pre>",
235
+ "<b>Bấm Thêm</b> (hoặc lưu file). Khi biểu tượng WebCake chuyển màu tím là kết nối thành công. Nói \"Tạo cho tôi trang…\" là bắt đầu được.",
236
+ ],
237
+ m2Note: "Bạn chỉ cần đăng nhập một lần. Sau đó có thể chuyển đổi giữa các cửa hàng của mình ngay trong cuộc trò chuyện.",
238
+ toolsH2: "Trợ lý có thể giúp bạn làm gì",
239
+ toolsSub: "Nói chuyện bình thường với trợ lý — bạn không cần biết tên công cụ hay lệnh nào cả.",
240
+ toolGroups: [
241
+ {
242
+ icon: "layers",
243
+ t: "Tạo & sửa trang",
244
+ d: "Dựng trang mới, thêm phần, sửa bố cục, thay nội dung, xuất bản lên cửa hàng.",
245
+ },
246
+ {
247
+ icon: "window",
248
+ t: "Quản lý trang web",
249
+ d: "Xem danh sách trang, tìm và sửa từng phần, thêm CSS/JS riêng, quản lý các phần dùng chung.",
250
+ },
251
+ {
252
+ icon: "cart",
253
+ t: "Quản lý sản phẩm & đơn hàng",
254
+ d: "Xem sản phẩm, đơn hàng, bộ sưu tập, khuyến mãi, combo và thông tin khách hàng.",
255
+ },
256
+ {
257
+ icon: "newspaper",
258
+ t: "Viết bài blog & media",
259
+ d: "Tạo và sửa bài viết, tìm ảnh miễn phí (Pexels), tải ảnh lên, đổi giao diện cửa hàng.",
260
+ },
261
+ {
262
+ icon: "server",
263
+ t: "Tính năng nâng cao",
264
+ d: "Viết & chạy code phía server (HTTP functions) cho cửa hàng của bạn.",
265
+ },
266
+ {
267
+ icon: "terminal",
268
+ t: "Chuyển đổi cửa hàng",
269
+ d: "Xem thông tin hiện tại, chuyển giữa các site, cập nhật tài khoản.",
270
+ },
271
+ {
272
+ icon: "mail",
273
+ t: "Gửi email tự động",
274
+ d: "Gửi email thông báo đơn hàng, xác nhận đăng ký và các email giao dịch khác.",
275
+ },
276
+ ],
277
+ promptH2: "Ví dụ — nói với trợ lý như thế này",
278
+ promptSub: "Bạn có thể nói tự nhiên bằng tiếng Việt. Ví dụ:",
279
+ promptEx: "Tạo cho tôi một trang sản phẩm trên WebCake cho thương hiệu [tên thương hiệu].\nTrang cần có: ảnh sản phẩm lớn, tên và giá, mô tả ngắn, nút \"Mua ngay\".\nKiểm tra kỹ trước khi lưu, rồi xuất bản lên cửa hàng của tôi.",
280
+ faqH2: "Câu hỏi thường gặp",
281
+ starH2: "Thấy hữu ích? Tặng dự án một ngôi sao nhé",
282
+ starP: "Đây là dự án miễn phí, mã nguồn mở — mỗi ngôi sao là một lời động viên để dự án tiếp tục phát triển và giúp nhiều người tìm ra nó hơn.",
283
+ starBtn: "Tặng sao trên GitHub",
284
+ footGuide: "Tài liệu",
285
+ switchLabel: "English",
286
+ nav: [
287
+ { href: "#flow", label: "Cách hoạt động" },
288
+ { href: "#how", label: "Vì sao tin" },
289
+ { href: "#build", label: "Tạo được gì" },
290
+ { href: "#connect", label: "Kết nối" },
291
+ { href: "#tools", label: "Trợ lý làm gì" },
292
+ { href: "#faq", label: "Hỏi đáp" },
293
+ ],
294
+ },
295
+ en: {
296
+ sub: "Build your online store just by chatting — no coding or drag-and-drop needed",
297
+ running: "Up and running",
298
+ leadPre: "You describe what you want, your AI assistant builds the page on your WebCake store, you review it and ",
299
+ leadGrad: "publish it — done",
300
+ leadPost: ". No dragging blocks, nothing to learn — just talk like you normally would.",
301
+ ctaStart: "Get started",
302
+ ctaStar: "Star on GitHub",
303
+ flowH2: "Just 4 simple steps",
304
+ flow: [
305
+ { icon: "bulb", t: "You describe", s: "what you want the page to be" },
306
+ { icon: "brain", t: "AI assistant", s: "Claude · Cursor · Windsurf…" },
307
+ { icon: "server", t: "WebCake", s: "saves & checks the page" },
308
+ { icon: "window", t: "Your store", s: "a real, live page" },
309
+ ],
310
+ flowCap: "① You say what you want → ② The AI builds the page → ③ You preview it, tweak if you like → ④ Publish and you're done. No coding, no drag-and-drop.",
311
+ howH2: "Why you can trust it straight away",
312
+ how: [
313
+ {
314
+ icon: "layers",
315
+ t: "Pages that look great",
316
+ d: "The assistant understands WebCake's store layout — banners, product grids, order forms, countdowns — and builds a professional-looking page right from your first message.",
317
+ },
318
+ {
319
+ icon: "check2",
320
+ t: "No broken layouts",
321
+ d: "Before saving, the page is automatically checked: is the layout correct, do the sections fit together? If anything is off, the assistant fixes it itself.",
322
+ },
323
+ {
324
+ icon: "shield",
325
+ t: "Safe — always preview before saving",
326
+ d: "Every change is shown to you first. Nothing touches your store until you confirm — you stay in full control.",
327
+ },
328
+ {
329
+ icon: "edit",
330
+ t: "Easy, precise edits",
331
+ d: "Say \"change the button to purple\" or \"add a reviews section\" — only that exact spot is updated, everything else stays as it was.",
332
+ },
333
+ ],
334
+ buildH2: "What you can create",
335
+ uses: [
336
+ {
337
+ icon: "cart",
338
+ t: "Single product page",
339
+ e: '"Create a page for my skincare serum — photo gallery, price, and an order button."',
340
+ },
341
+ {
342
+ icon: "window",
343
+ t: "Store homepage",
344
+ e: '"Create a homepage — intro banner, featured products, newsletter sign-up."',
345
+ },
346
+ {
347
+ icon: "flame",
348
+ t: "Flash sale page",
349
+ e: '"Create a flash sale page — big countdown timer, discounted products, sticky buy button."',
350
+ },
351
+ {
352
+ icon: "ticket",
353
+ t: "Event / webinar page",
354
+ e: '"Create an event sign-up page — date, schedule, registration form."',
355
+ },
356
+ {
357
+ icon: "mail",
358
+ t: "Invitation",
359
+ e: '"Create a wedding invite — names, date, location, RSVP form."',
360
+ },
361
+ {
362
+ icon: "newspaper",
363
+ t: "Blog / content page",
364
+ e: '"Create a blog page with featured posts and a newsletter subscribe box."',
365
+ },
366
+ {
367
+ icon: "link",
368
+ t: "Link-in-bio",
369
+ e: '"Create a link-in-bio — profile photo, short bio, 5 link buttons, socials."',
370
+ },
371
+ {
372
+ icon: "wand",
373
+ t: "Anything you can think of",
374
+ e: '"…then \"change the button colour\" or \"add an FAQ section\" — just that part gets updated."',
375
+ },
376
+ ],
377
+ connectH2: "Connect your AI assistant to your store",
378
+ m1Tag: "Option ① · On your computer (Claude Desktop, Cursor, Windsurf…)",
379
+ m1Sub: "Best if you use an AI app on your computer. Needs Node.js 18+ (free, from nodejs.org). One command and you're set:",
380
+ m1Steps: [
381
+ "<b>Install Node.js 18+</b> (free) if you don't have it yet — get it at <b>nodejs.org</b>.",
382
+ "<b>Open Terminal</b> (or Command Prompt), paste the command below and press Enter:<pre>" + INSTALL_CMD + "</pre>",
383
+ "<b>Follow the prompts that appear:</b> pick your app (Claude, Cursor, Windsurf…) → sign in to your WebCake account when asked.",
384
+ '<b>Reopen your AI app.</b> When you see <code class="inl">webcake-storefront</code> in the tools list, you\'re connected — try saying "Create a product page for…"',
385
+ ],
386
+ m1Note: "Want to set up multiple apps at once — use this command:",
387
+ m2Tag: "Option ② · In your browser (claude.ai) — nothing to install",
388
+ m2Sub: "Best if you use Claude on the web (claude.ai) or prefer not to install anything. Just add the connection address, sign in to WebCake, and you're ready.",
389
+ m2Steps: [
390
+ '<b>Copy the connection address</b> below:<a class="btn" href="{REMOTE}">{REMOTE} {ARROW}</a>',
391
+ '<b>Open your AI app:</b><br>• claude.ai: go to <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: open <code class="inl">.mcp.json</code>',
392
+ "<b>Paste the address you copied</b> — your browser will open the WebCake login page automatically:<pre>{REMOTE}</pre>",
393
+ "<b>Click Add</b> (or save the file). When the WebCake icon turns purple, you're connected. Just say \"Create a page for…\" to get started.",
394
+ ],
395
+ m2Note: "You only need to sign in once. After that you can switch between your stores anytime during a conversation.",
396
+ toolsH2: "What your assistant can help you do",
397
+ toolsSub: "Just talk to your assistant naturally — you don't need to know any tool names or commands.",
398
+ toolGroups: [
399
+ {
400
+ icon: "layers",
401
+ t: "Create & edit pages",
402
+ d: "Build new pages, add sections, rearrange layouts, update content, publish to your store.",
403
+ },
404
+ {
405
+ icon: "window",
406
+ t: "Manage your website",
407
+ d: "Browse your pages, find and update specific sections, add custom CSS/JS, manage shared sections.",
408
+ },
409
+ {
410
+ icon: "cart",
411
+ t: "Products & orders",
412
+ d: "View products, orders, collections, promotions, bundles, and customer information.",
413
+ },
414
+ {
415
+ icon: "newspaper",
416
+ t: "Blog posts & media",
417
+ d: "Write and edit blog articles, find free photos (Pexels), upload images, switch store themes.",
418
+ },
419
+ {
420
+ icon: "server",
421
+ t: "Advanced features",
422
+ d: "Write and run server-side code (HTTP functions) for your store.",
423
+ },
424
+ {
425
+ icon: "terminal",
426
+ t: "Switch stores",
427
+ d: "Check current context, switch between your sites, update account credentials.",
428
+ },
429
+ {
430
+ icon: "mail",
431
+ t: "Automated emails",
432
+ d: "Send order confirmations, sign-up notifications, and other transactional emails.",
433
+ },
434
+ ],
435
+ promptH2: "Example — here's what to say to your assistant",
436
+ promptSub: "You can speak naturally. For example:",
437
+ promptEx: "Create a product page on my WebCake store for [brand name].\nThe page should have: a large product image, name and price, a short description, and a \"Buy Now\" button.\nPlease check everything looks right before saving, then publish it to my store.",
438
+ faqH2: "Frequently asked questions",
439
+ starH2: "Find it useful? Give the project a star",
440
+ starP: "It's a free, open-source project — every star is a little encouragement to keep it growing and helps more people find it.",
441
+ starBtn: "Star on GitHub",
442
+ footGuide: "Docs",
443
+ switchLabel: "Tiếng Việt",
444
+ nav: [
445
+ { href: "#flow", label: "How it works" },
446
+ { href: "#how", label: "Why trust it" },
447
+ { href: "#build", label: "What you build" },
448
+ { href: "#connect", label: "Connect" },
449
+ { href: "#tools", label: "What it does" },
450
+ { href: "#faq", label: "FAQ" },
451
+ ],
452
+ },
453
+ };
454
+ function steps(items) {
455
+ return items
456
+ .map((body, i) => `<li><span class="n">${i + 1}</span><div class="body">${body}</div></li>`)
457
+ .join("\n ");
21
458
  }
22
- export function landingHtml(origin = "") {
23
- const mcpUrl = `${origin}/mcp`;
24
- const npxCmd = `npx webcake-storefront-mcp@latest`;
459
+ export function guideHtml(origin, lang = "vi") {
460
+ const L = normalizeLang(lang);
461
+ const t = T[L];
462
+ const m = META[L];
463
+ const faq = FAQ[L];
464
+ const endpoint = `${origin}/mcp`;
465
+ const ogImage = `${origin}/og.svg`;
466
+ const otherLang = L === "vi" ? "en" : "vi";
467
+ const otherHref = otherLang === "en" ? "?lang=en" : "?lang=vi";
468
+ const canonical = `${origin}/${L === "en" ? "?lang=en" : ""}`;
469
+ const selfPath = L === "en" ? "?lang=en" : "/";
470
+ const jsonLd = {
471
+ "@context": "https://schema.org",
472
+ "@graph": [
473
+ {
474
+ "@type": "SoftwareApplication",
475
+ name: "WebCake Storefront MCP",
476
+ applicationCategory: "DeveloperApplication",
477
+ operatingSystem: "Windows, macOS, Linux",
478
+ description: m.desc,
479
+ url: canonical,
480
+ image: ogImage,
481
+ offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
482
+ author: {
483
+ "@type": "Organization",
484
+ name: "WebCake",
485
+ url: "https://webcake.io",
486
+ },
487
+ softwareHelp: DOCS_URL,
488
+ installUrl: NPM_URL,
489
+ },
490
+ {
491
+ "@type": "WebSite",
492
+ name: "WebCake Storefront MCP",
493
+ url: `${origin}/`,
494
+ inLanguage: L,
495
+ },
496
+ {
497
+ "@type": "FAQPage",
498
+ mainEntity: faq.map((f) => ({
499
+ "@type": "Question",
500
+ name: f.q,
501
+ acceptedAnswer: { "@type": "Answer", text: f.a },
502
+ })),
503
+ },
504
+ ],
505
+ };
506
+ const jsonLdScript = JSON.stringify(jsonLd).replace(/</g, "\\u003c");
507
+ const fill = (s) => s
508
+ .replaceAll("{REMOTE}", MCP_REMOTE_URL)
509
+ .replaceAll("{ENDPOINT}", endpoint)
510
+ .replaceAll("{ARROW}", icon("arrow"));
25
511
  return `<!doctype html>
26
- <html lang="en">
27
- <head>
512
+ <html lang="${L}"><head>
28
513
  <meta charset="utf-8">
29
514
  <meta name="viewport" content="width=device-width,initial-scale=1">
30
- <title>WebCake Storefront MCP — Build storefronts with AI</title>
31
- <meta name="description" content="An MCP server that exposes ~101 tools for building and managing WebCake / StoreCake storefronts from any AI assistant — Claude, Cursor, Windsurf, and more.">
32
- <meta property="og:type" content="website">
33
- <meta property="og:title" content="WebCake Storefront MCP">
34
- <meta property="og:description" content="Build, edit, and publish storefront pages, products, articles, and more — straight from your AI assistant.">
35
- <meta property="og:image" content="${origin}/favicon.svg">
36
- <meta property="og:url" content="${origin}">
37
- <meta name="twitter:card" content="summary">
38
- <meta name="twitter:title" content="WebCake Storefront MCP">
39
- <meta name="twitter:description" content="~101 MCP tools for AI-powered storefront building on WebCake / StoreCake.">
40
- <meta name="twitter:image" content="${origin}/favicon.svg">
515
+ <script>(function(){document.documentElement.classList.add('js');try{var t=localStorage.getItem('wc-theme');if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t);}catch(e){}try{if('scrollRestoration' in history)history.scrollRestoration='manual';}catch(e){}})();</script>
516
+ <title>${m.title}</title>
517
+ <meta name="description" content="${m.desc}">
518
+ <meta name="keywords" content="${m.keywords}">
519
+ <meta name="author" content="WebCake">
520
+ <meta name="robots" content="index,follow">
521
+ <meta name="theme-color" content="#108B67">
522
+ <link rel="canonical" href="${canonical}">
523
+ <link rel="alternate" hreflang="vi" href="${origin}/">
524
+ <link rel="alternate" hreflang="en" href="${origin}/?lang=en">
525
+ <link rel="alternate" hreflang="x-default" href="${origin}/">
41
526
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
527
+ <meta property="og:type" content="website">
528
+ <meta property="og:site_name" content="WebCake Storefront MCP">
529
+ <meta property="og:title" content="${m.title}">
530
+ <meta property="og:description" content="${m.desc}">
531
+ <meta property="og:url" content="${canonical}">
532
+ <meta property="og:image" content="${ogImage}">
533
+ <meta property="og:image:type" content="image/svg+xml">
534
+ <meta property="og:image:width" content="1200">
535
+ <meta property="og:image:height" content="630">
536
+ <meta property="og:image:alt" content="${m.title}">
537
+ <meta property="og:locale" content="${META[L].locale}">
538
+ <meta property="og:locale:alternate" content="${META[otherLang].locale}">
539
+ <meta name="twitter:card" content="summary_large_image">
540
+ <meta name="twitter:title" content="${m.title}">
541
+ <meta name="twitter:description" content="${m.desc}">
542
+ <meta name="twitter:image" content="${ogImage}">
543
+ <meta name="twitter:image:alt" content="${m.title}">
544
+ <script type="application/ld+json">${jsonLdScript}</script>
42
545
  <style>
43
- :root {
44
- --green: #108B67;
45
- --green-light: #3FBB57;
46
- --accent: #FFD591;
47
- --bg: #f8fafc;
48
- --bg2: #f1f5f9;
49
- --text: #1e293b;
50
- --muted: #64748b;
51
- --border: rgba(0,0,0,.08);
52
- --card: #fff;
53
- color-scheme: light dark;
54
- }
55
- @media (prefers-color-scheme: dark) {
56
- :root {
57
- --bg: #0f172a;
58
- --bg2: #1e293b;
59
- --text: #e2e8f0;
60
- --muted: #94a3b8;
61
- --border: rgba(255,255,255,.08);
62
- --card: #1e293b;
546
+ :root{--g:#108B67;--g7:#0c6f52;--ink:#11121e;--mut:#5e5f7a;--bg:#f6f5ff;--card:#ffffff;
547
+ --line:rgba(16,14,40,.09);--shadow:0 1px 2px rgba(16,14,40,.05),0 6px 20px -12px rgba(16,14,40,.18);--code:#0e0d1a;
548
+ --ic-fg:#0c6f52;--btn-hover:#0c6f52;--navbg:rgba(246,245,255,.82)}
549
+ @media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--ink:#e8e6ff;--mut:#9a98b8;--bg:#0c0b14;--card:#141320;
550
+ --line:rgba(255,255,255,.07);--shadow:0 1px 2px rgba(0,0,0,.3),0 8px 24px -14px rgba(0,0,0,.7);--code:#07060f;--g7:#5fe0b3;--ic-fg:#8aecc9;--btn-hover:#16a07a;--navbg:rgba(12,11,20,.82)}}
551
+ :root[data-theme="dark"]{--ink:#e8e6ff;--mut:#9a98b8;--bg:#0c0b14;--card:#141320;
552
+ --line:rgba(255,255,255,.07);--shadow:0 1px 2px rgba(0,0,0,.3),0 8px 24px -14px rgba(0,0,0,.7);--code:#07060f;--g7:#5fe0b3;--ic-fg:#8aecc9;--btn-hover:#16a07a;--navbg:rgba(12,11,20,.82)}
553
+ *{box-sizing:border-box}
554
+ html{scroll-behavior:auto}
555
+ html.smooth{scroll-behavior:smooth}
556
+ body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:var(--ink);
557
+ background:var(--bg);line-height:1.62;overflow-x:hidden}
558
+ .blobs{position:fixed;inset:0;z-index:-1;overflow:hidden;pointer-events:none}
559
+ .blobs b{position:absolute;border-radius:50%;filter:blur(90px);opacity:.16;will-change:transform}
560
+ .blobs b:nth-child(1){width:560px;height:560px;right:-160px;top:-180px;background:radial-gradient(circle,#108B67,transparent 70%);animation:drift1 40s ease-in-out infinite}
561
+ .blobs b:nth-child(2){width:440px;height:440px;left:-160px;bottom:-160px;background:radial-gradient(circle,#14a87c,transparent 70%);animation:drift2 48s ease-in-out infinite}
562
+ @keyframes drift1{50%{transform:translate(-50px,60px)}}
563
+ @keyframes drift2{50%{transform:translate(40px,-50px)}}
564
+ .wrap{max-width:900px;margin:0 auto;padding:48px 20px 72px}
565
+ a{color:inherit}
566
+ .i{width:1.1em;height:1.1em;flex:0 0 auto;vertical-align:-.15em}
567
+ .glass{background:var(--card);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow);
568
+ transition:box-shadow .2s ease,border-color .2s ease}
569
+ header{display:flex;align-items:center;gap:14px;margin-bottom:14px}
570
+ header .logo{width:50px;height:50px;border-radius:14px;overflow:hidden;flex:0 0 auto;
571
+ box-shadow:0 6px 16px -4px rgba(16,139,103,.4)}
572
+ header .logo svg{width:100%;height:100%;display:block}
573
+ .hgrow{flex:1 1 auto;min-width:0}
574
+ .controls{margin-left:auto;flex:0 0 auto;display:flex;align-items:center;gap:8px}
575
+ .langsw{font-size:.82rem;font-weight:700;color:var(--g7);text-decoration:none;white-space:nowrap;
576
+ border:1px solid var(--line);background:var(--card);padding:7px 12px;border-radius:999px;display:inline-flex;align-items:center;gap:6px}
577
+ .langsw:hover{border-color:var(--g)}
578
+ .iconbtn{width:36px;height:36px;flex:0 0 auto;display:grid;place-items:center;cursor:pointer;color:var(--g7);
579
+ border:1px solid var(--line);background:var(--card);border-radius:10px;transition:border-color .15s ease,color .15s ease}
580
+ .iconbtn:hover{border-color:var(--g)}
581
+ .iconbtn svg{width:17px;height:17px}
582
+ h1{font-size:1.78rem;margin:0;font-weight:800;letter-spacing:-.02em}
583
+ .sub{color:var(--mut);margin:3px 0 0;font-size:.98rem}
584
+ .lead{font-size:1.16rem;margin:20px 0 18px;max-width:60ch}
585
+ .lead b{color:var(--ink)}
586
+ .grad{background:linear-gradient(95deg,#108B67,#14a87c 60%,#3fcf9e);-webkit-background-clip:text;background-clip:text;color:transparent;
587
+ background-size:200% auto;animation:shim 7s linear infinite}
588
+ @keyframes shim{to{background-position:200% center}}
589
+ .pill{display:inline-flex;align-items:center;gap:8px;padding:6px 14px;border-radius:999px;font-size:.82rem;font-weight:600;
590
+ color:var(--g7);background:rgba(16,139,103,.10);border:1px solid var(--line)}
591
+ .dot{width:8px;height:8px;border-radius:50%;background:var(--g);box-shadow:0 0 0 0 rgba(16,139,103,.5);animation:pulse 2s infinite}
592
+ @keyframes pulse{70%{box-shadow:0 0 0 7px rgba(16,139,103,0)}100%{box-shadow:0 0 0 0 rgba(16,139,103,0)}}
593
+ h2{font-size:1.32rem;margin:46px 0 16px;font-weight:800;letter-spacing:-.01em;scroll-margin-top:72px}
594
+ .ic{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;flex:0 0 auto;color:var(--ic-fg);
595
+ background:rgba(16,139,103,.11);border:1px solid var(--line);transition:transform .2s ease}
596
+ .ic .i{width:22px;height:22px}
597
+ .grid{display:grid;gap:16px;grid-template-columns:1fr 1fr}
598
+ .grid-3{display:grid;gap:16px;grid-template-columns:1fr 1fr 1fr}
599
+ @media(max-width:720px){.grid,.grid-3{grid-template-columns:1fr}}
600
+ .card{padding:22px}
601
+ .card .ic{margin-bottom:14px}
602
+ .card h3{margin:0 0 6px;font-size:1.04rem}
603
+ .card p{color:var(--mut);font-size:.93rem;margin:0}
604
+ .tag{display:inline-flex;align-items:center;gap:9px;font-size:.82rem;font-weight:800;color:var(--g7);
605
+ text-transform:uppercase;letter-spacing:.04em;flex-wrap:wrap}
606
+ .tag .ic{width:30px;height:30px;border-radius:9px}
607
+ .tag .ic .i{width:16px;height:16px}
608
+ pre{margin:0;background:var(--code);color:#e8e6ff;border-radius:11px;padding:12px 14px;overflow-x:auto;
609
+ border:1px solid rgba(255,255,255,.06);font:600 .82rem/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}
610
+ .codewrap{position:relative}
611
+ .codewrap pre{padding-right:46px}
612
+ .copy{position:absolute;top:8px;right:8px;width:30px;height:30px;display:grid;place-items:center;cursor:pointer;
613
+ border:1px solid rgba(255,255,255,.15);border-radius:8px;background:rgba(255,255,255,.06);color:#cfc9ff;
614
+ transition:background .15s ease,color .15s ease,border-color .15s ease}
615
+ .copy:hover{background:rgba(255,255,255,.13);color:#fff}
616
+ .copy svg{width:15px;height:15px}
617
+ .copy.done{color:#5fe0b3;border-color:rgba(95,224,179,.55)}
618
+ .feat{list-style:none;padding:0;margin:0;display:grid;gap:12px}
619
+ .feat li{display:flex;gap:13px;align-items:center;font-size:.97rem;padding:13px 16px}
620
+ .feat li b{color:var(--ink)}
621
+ .cta-row{display:flex;gap:12px;flex-wrap:wrap;margin:22px 0 6px}
622
+ .flow{display:flex;align-items:flex-start;gap:0;padding:24px 18px 18px;overflow-x:auto}
623
+ .flow .node{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;width:104px}
624
+ .flow .node .ic{width:54px;height:54px;border-radius:16px}
625
+ .flow .node .ic .i{width:27px;height:27px}
626
+ .flow .node b{font-size:.93rem}
627
+ .flow .node span{font-size:.75rem;color:var(--mut)}
628
+ .flow .wire{flex:1 1 auto;min-width:30px;position:relative;height:2px;margin-top:27px;
629
+ background:linear-gradient(90deg,var(--line),rgba(16,139,103,.45),var(--line))}
630
+ .flow .wire .pkt{position:absolute;top:50%;left:0;width:9px;height:9px;margin:-5px 0 0 -4px;border-radius:50%;
631
+ background:var(--g);box-shadow:0 0 9px 1px rgba(16,139,103,.7)}
632
+ .flow .wire::after{content:"";position:absolute;right:-1px;top:50%;width:7px;height:7px;margin-top:-4px;
633
+ border-top:2px solid var(--g7);border-right:2px solid var(--g7);transform:rotate(45deg)}
634
+ .flow-cap{color:var(--mut);font-size:.9rem;margin:2px 2px 0;max-width:68ch}
635
+ @media(prefers-reduced-motion:no-preference){
636
+ .flow .wire .pkt{animation:pkt 2.4s ease-in-out infinite}
637
+ @keyframes pkt{0%{left:0;opacity:0}12%{opacity:1}88%{opacity:1}100%{left:100%;opacity:0}}
638
+ .flow .node .ic{animation:nodepop 2.4s ease-in-out infinite}
63
639
  }
64
- }
65
- *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
66
- body{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;background:var(--bg);color:var(--text);line-height:1.6}
67
- a{color:var(--green);text-decoration:none}
68
- a:hover{text-decoration:underline}
69
- .container{max-width:900px;margin:0 auto;padding:0 24px}
70
-
71
- /* Nav */
72
- nav{border-bottom:1px solid var(--border);padding:14px 0}
73
- .nav-inner{display:flex;align-items:center;justify-content:space-between}
74
- .nav-logo{display:flex;align-items:center;gap:10px;font-weight:700;font-size:1.05rem;color:var(--text);text-decoration:none}
75
- .nav-logo svg{flex-shrink:0}
76
- .nav-links{display:flex;align-items:center;gap:20px;font-size:.9rem}
77
- .nav-links a{color:var(--muted)}
78
- .nav-links a:hover{color:var(--text);text-decoration:none}
79
- .btn{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border-radius:8px;font-size:.9rem;font-weight:600;cursor:pointer;border:none;transition:opacity .15s}
80
- .btn-primary{background:var(--green);color:#fff}
81
- .btn-primary:hover{opacity:.88;text-decoration:none}
82
- .btn-outline{background:transparent;color:var(--text);border:1px solid var(--border)}
83
- .btn-outline:hover{background:var(--bg2);text-decoration:none}
84
-
85
- /* Hero */
86
- .hero{padding:80px 0 64px;text-align:center}
87
- .hero-badge{display:inline-flex;align-items:center;gap:6px;background:var(--bg2);border:1px solid var(--border);border-radius:99px;padding:5px 14px;font-size:.82rem;color:var(--muted);margin-bottom:28px}
88
- .hero h1{font-size:clamp(2rem,5vw,3.2rem);font-weight:800;line-height:1.15;letter-spacing:-.02em;margin-bottom:20px}
89
- .hero h1 span{color:var(--green)}
90
- .hero p{font-size:1.15rem;color:var(--muted);max-width:580px;margin:0 auto 36px}
91
- .hero-actions{display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap}
92
- .hero-mcp{margin-top:40px;background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:18px 24px;display:inline-block;text-align:left;max-width:620px;width:100%}
93
- .hero-mcp label{font-size:.78rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;display:block;margin-bottom:8px}
94
- .hero-mcp-row{display:flex;align-items:center;gap:10px}
95
- .hero-mcp code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.95rem;flex:1;word-break:break-all;color:var(--text)}
96
- .copy-btn{background:var(--card);border:1px solid var(--border);border-radius:6px;padding:5px 10px;font-size:.8rem;color:var(--muted);cursor:pointer;white-space:nowrap;transition:background .15s}
97
- .copy-btn:hover{background:var(--bg2)}
640
+ @media(prefers-reduced-motion:reduce){.flow .wire .pkt{display:none}}
641
+ @keyframes nodepop{0%,100%{box-shadow:none}50%{box-shadow:0 0 0 4px rgba(16,139,103,.12)}}
642
+ .btn{display:inline-flex;align-items:center;gap:9px;padding:11px 19px;border-radius:11px;cursor:pointer;
643
+ background:var(--g);color:#fff;text-decoration:none;font-weight:700;font-size:.93rem;
644
+ box-shadow:0 4px 12px -4px rgba(16,139,103,.5);transition:transform .15s ease,background .15s ease}
645
+ .btn .i{width:18px;height:18px}
646
+ .btn:hover{transform:translateY(-1px);background:var(--btn-hover)}
647
+ .btn.ghost{background:var(--card);color:var(--ink);border:1px solid var(--line);box-shadow:none}
648
+ .btn.ghost:hover{border-color:var(--g);background:var(--card)}
649
+ .nav{position:sticky;top:0;z-index:60;display:flex;gap:6px;align-items:center;overflow-x:auto;
650
+ margin:18px -20px 6px;padding:9px 20px;background:var(--navbg);border-bottom:1px solid var(--line);
651
+ backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);scrollbar-width:none}
652
+ .nav::-webkit-scrollbar{display:none}
653
+ .nav a{flex:0 0 auto;font-size:.84rem;font-weight:600;color:var(--mut);text-decoration:none;
654
+ padding:7px 13px;border-radius:999px;white-space:nowrap;transition:color .15s ease,background .15s ease}
655
+ .nav a:hover{color:var(--g7);background:rgba(16,139,103,.10)}
656
+ .nav a.active{color:var(--g7);background:rgba(16,139,103,.13)}
657
+ .uses{display:grid;gap:14px;grid-template-columns:1fr 1fr;padding:0;margin:0;list-style:none}
658
+ @media(max-width:640px){.uses{grid-template-columns:1fr}}
659
+ .uses li{display:flex;gap:13px;padding:16px 18px;align-items:flex-start;transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease}
660
+ .uses li:hover{transform:translateY(-3px);border-color:rgba(16,139,103,.4);box-shadow:0 10px 26px -14px rgba(16,14,40,.4)}
661
+ .uses b{display:block;font-size:.96rem;margin-bottom:2px}
662
+ .uses span{color:var(--mut);font-size:.88rem}
663
+ .card{transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease}
664
+ .card:hover{transform:translateY(-3px);box-shadow:0 10px 26px -14px rgba(16,14,40,.4)}
665
+ .card:hover,.method:hover{border-color:rgba(16,139,103,.32)}
666
+ .method{margin-bottom:16px;padding:24px}
667
+ .method>.tag{margin-bottom:4px}
668
+ .msub{color:var(--mut);font-size:.92rem;margin:.5rem 0 1.2rem}
669
+ .steps{list-style:none;margin:0;padding:0;display:grid;gap:18px;position:relative}
670
+ .steps li{display:flex;gap:14px;align-items:flex-start;position:relative}
671
+ .steps li:not(:last-child)::after{content:"";position:absolute;left:13px;top:30px;bottom:-18px;width:2px;background:var(--line)}
672
+ .steps .n{flex:0 0 auto;width:28px;height:28px;border-radius:50%;color:var(--ic-fg);
673
+ background:rgba(16,139,103,.12);border:1px solid var(--line);
674
+ font:800 .85rem/1 system-ui;display:flex;align-items:center;justify-content:center}
675
+ .steps .body{flex:1;min-width:0;font-size:.95rem}
676
+ .steps .body pre{margin-top:9px}
677
+ .steps .body .btn{display:flex;width:fit-content;margin-top:10px}
678
+ code.inl{background:rgba(16,139,103,.13);color:var(--g7);padding:1px 6px;border-radius:6px;font-size:.85em;font-weight:600;
679
+ overflow-wrap:anywhere;word-break:break-word}
680
+ .note{font-size:.86rem;color:var(--mut);margin-top:10px}
681
+ .note + pre,.note + .codewrap{margin-top:9px}
682
+ .tip{margin-top:16px;background:rgba(16,139,103,.06);border:1px solid var(--line);border-radius:12px;padding:13px 15px}
683
+ .tip .note{margin:0}
684
+ details{padding:2px 18px;margin-bottom:11px}
685
+ details summary{cursor:pointer;font-weight:600;padding:15px 0;list-style:none;display:flex;align-items:center;gap:10px}
686
+ details summary::-webkit-details-marker{display:none}
687
+ details summary::after{content:"";margin-left:auto;width:9px;height:9px;border-right:2.5px solid var(--g7);
688
+ border-bottom:2.5px solid var(--g7);transform:rotate(45deg);transition:transform .25s ease}
689
+ details[open] summary::after{transform:rotate(-135deg)}
690
+ details p{color:var(--mut);font-size:.92rem;margin:0 0 16px;padding-left:0}
691
+ .star{margin-top:48px;text-align:center;padding:38px 24px;overflow:hidden;position:relative}
692
+ .star::before{content:"";position:absolute;inset:-40% 0 auto;height:70%;
693
+ background:radial-gradient(closest-side,rgba(16,139,103,.10),transparent);pointer-events:none}
694
+ .star h2{margin:0 0 6px;position:relative;display:inline-flex;align-items:center;gap:9px;justify-content:center}
695
+ .star h2 .i{color:var(--g7)}
696
+ .star p{color:var(--mut);max-width:48ch;margin:0 auto 18px;position:relative}
697
+ .star .btn{position:relative}
698
+ footer{margin-top:42px;padding:20px 22px;color:var(--mut);font-size:.86rem;
699
+ display:flex;gap:18px;flex-wrap:wrap;align-items:center}
700
+ footer a{color:var(--g7);font-weight:600;text-decoration:none;display:inline-flex;align-items:center;gap:6px}
701
+ footer a:hover{text-decoration:underline}
702
+ @media(max-width:640px){
703
+ .wrap{padding:30px 15px 56px}
704
+ header{flex-wrap:wrap;gap:12px}
705
+ .hgrow{order:2;flex:1 1 100%}
706
+ h1{font-size:1.4rem}
707
+ h2{font-size:1.2rem;margin:34px 0 14px}
708
+ .lead{font-size:1.05rem}
709
+ .method{padding:18px 15px}
710
+ .card{padding:18px}
711
+ .tip{padding:11px 12px}
712
+ .langsw{padding:6px 10px}
713
+ .uses li,.feat li{padding:14px}
714
+ .flow{flex-direction:column;align-items:stretch;overflow:visible;padding:16px}
715
+ .flow .node{flex-direction:row;width:auto;align-items:center;gap:13px;text-align:left}
716
+ .flow .node .ic{width:44px;height:44px;border-radius:13px}
717
+ .flow .node .ic .i{width:22px;height:22px}
718
+ .flow .node b{font-size:.95rem}
719
+ .flow .node span{font-size:.8rem}
720
+ .flow .wire{flex:0 0 auto;width:2px;height:20px;min-width:0;margin:3px 0 3px 21px;
721
+ background:linear-gradient(var(--line),var(--g))}
722
+ .flow .wire::after{content:none}
723
+ .flow .wire .pkt{display:none}
724
+ }
725
+ @media(prefers-reduced-motion:no-preference){
726
+ .js .reveal{opacity:0;transform:translateY(24px);
727
+ transition:opacity .6s ease,transform .6s cubic-bezier(.2,.7,.2,1)}
728
+ .js .reveal.in{opacity:1;transform:none}
729
+ .hero-in{animation:rise2 .8s cubic-bezier(.2,.7,.2,1) both}
730
+ @keyframes rise2{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}
731
+ }
732
+ </style></head>
733
+ <body>
734
+ <div class="blobs"><b></b><b></b></div>
735
+ <div class="wrap">
98
736
 
99
- /* Badges */
100
- .badges{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;margin:28px 0}
101
- .badges img{height:20px}
737
+ <header class="hero-in">
738
+ <span class="logo">${ICON_SVG}</span>
739
+ <div class="hgrow">
740
+ <h1>WebCake Storefront MCP</h1>
741
+ <p class="sub">${t.sub}</p>
742
+ </div>
743
+ <div class="controls">
744
+ <button class="iconbtn" id="theme" type="button" aria-label="Toggle theme" title="Theme">${icon("moon")}</button>
745
+ <a class="langsw" href="${otherHref}" hreflang="${otherLang}" rel="alternate">${icon("globe")} ${t.switchLabel}</a>
746
+ </div>
747
+ </header>
102
748
 
103
- /* Features */
104
- .section{padding:56px 0}
105
- .section-label{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--green);margin-bottom:10px}
106
- .section h2{font-size:clamp(1.5rem,3vw,2.1rem);font-weight:700;letter-spacing:-.01em;margin-bottom:12px}
107
- .section p.lead{color:var(--muted);font-size:1.05rem;max-width:560px;margin-bottom:40px}
108
- .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px}
109
- .card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:26px}
110
- .card-icon{font-size:1.6rem;margin-bottom:12px}
111
- .card h3{font-size:1rem;font-weight:700;margin-bottom:6px}
112
- .card p{font-size:.9rem;color:var(--muted)}
749
+ <p class="hero-in" style="display:flex;gap:9px;flex-wrap:wrap"><span class="pill"><span class="dot"></span> ${t.running}</span><span class="pill">WebCake · StoreCake</span></p>
113
750
 
114
- /* Tools list */
115
- .tools-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px}
116
- .tool-chip{background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:9px 14px;font-size:.83rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text)}
751
+ <p class="lead hero-in">${t.leadPre}<b class="grad">${t.leadGrad}</b>${t.leadPost}</p>
117
752
 
118
- /* Install */
119
- .install-block{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:32px;margin-top:32px}
120
- .install-block h3{font-size:1.1rem;font-weight:700;margin-bottom:6px}
121
- .install-block p{color:var(--muted);font-size:.9rem;margin-bottom:18px}
122
- .code-block{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px 20px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9rem;position:relative}
123
- .code-block .copy-btn{position:absolute;top:10px;right:10px}
124
- .install-tabs{display:flex;gap:6px;margin-bottom:16px;flex-wrap:wrap}
125
- .tab-btn{background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:6px 14px;font-size:.85rem;cursor:pointer;color:var(--muted);transition:all .15s}
126
- .tab-btn.active,.tab-btn:hover{background:var(--green);color:#fff;border-color:var(--green)}
753
+ <div class="cta-row hero-in">
754
+ <a class="btn" href="#connect">${icon("rocket")} ${t.ctaStart}</a>
755
+ <a class="btn ghost" href="${GITHUB_URL}">${icon("star")} ${t.ctaStar}</a>
756
+ </div>
127
757
 
128
- /* Footer */
129
- footer{border-top:1px solid var(--border);padding:40px 0;text-align:center;color:var(--muted);font-size:.88rem}
130
- footer a{color:var(--muted)}
131
- footer a:hover{color:var(--text)}
132
- .footer-links{display:flex;align-items:center;justify-content:center;gap:20px;flex-wrap:wrap;margin-bottom:12px}
758
+ <nav class="nav" aria-label="${L === "en" ? "Sections" : "Mục lục"}">
759
+ ${t.nav.map((n) => `<a href="${n.href}">${n.label}</a>`).join("\n ")}
760
+ </nav>
133
761
 
134
- @media(max-width:600px){
135
- .hero{padding:52px 0 40px}
136
- .hero-mcp-row{flex-direction:column;align-items:flex-start}
137
- nav .btn-outline{display:none}
138
- }
139
- </style>
140
- </head>
141
- <body>
762
+ <h2 id="flow" class="reveal">${t.flowH2}</h2>
763
+ <div class="glass flow reveal">
764
+ ${t.flow
765
+ .map((n, i) => `<div class="node"><span class="ic" style="animation-delay:${(i * 0.8).toFixed(1)}s">${icon(n.icon)}</span><b>${n.t}</b><span>${n.s}</span></div>` +
766
+ (i < t.flow.length - 1
767
+ ? `<div class="wire"><i class="pkt" style="animation-delay:${(i * 0.8).toFixed(1)}s"></i></div>`
768
+ : ""))
769
+ .join("\n ")}
770
+ </div>
771
+ <p class="flow-cap reveal">${t.flowCap}</p>
142
772
 
143
- <nav>
144
- <div class="container">
145
- <div class="nav-inner">
146
- <a class="nav-logo" href="/">
147
- <svg width="28" height="28" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
148
- <rect width="32" height="32" rx="7" fill="url(#ng)"/>
149
- <defs><linearGradient id="ng" x1="0" y1="0" x2="1" y2="1">
150
- <stop offset="0%" stop-color="#3FBB57"/><stop offset="100%" stop-color="#108B67"/>
151
- </linearGradient></defs>
152
- <text x="16" y="22" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="700" font-size="17" fill="white">S</text>
153
- <circle cx="24" cy="9" r="4" fill="#FFD591"/>
154
- </svg>
155
- WebCake Storefront MCP
156
- </a>
157
- <div class="nav-links">
158
- <a href="${GITHUB_URL}" target="_blank" rel="noopener">GitHub</a>
159
- <a href="/privacy">Privacy</a>
160
- <a href="/terms">Terms</a>
161
- <a class="btn btn-primary" href="${WEBCAKE_URL}" target="_blank" rel="noopener">Get WebCake</a>
162
- </div>
163
- </div>
773
+ <h2 id="how" class="reveal">${t.howH2}</h2>
774
+ <div class="grid">
775
+ ${t.how
776
+ .map((h) => `<div class="glass card reveal">${tile(h.icon)}<h3>${h.t}</h3><p>${h.d}</p></div>`)
777
+ .join("\n ")}
164
778
  </div>
165
- </nav>
166
779
 
167
- <main>
168
- <div class="hero">
169
- <div class="container">
170
- <div class="hero-badge">
171
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
172
- ~101 tools &middot; Model Context Protocol
173
- </div>
174
- <h1>Build your <span>storefront</span><br>from a prompt</h1>
175
- <p>An MCP server that connects any AI assistant to your WebCake / StoreCake account — create pages, manage products, publish articles, and more without leaving your IDE.</p>
176
- <div class="hero-actions">
177
- <a class="btn btn-primary" href="${GITHUB_URL}" target="_blank" rel="noopener">
178
- <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.418 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.868-.013-1.703-2.782.604-3.369-1.342-3.369-1.342-.454-1.155-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.268 2.75 1.026A9.578 9.578 0 0112 6.836a9.59 9.59 0 012.504.337c1.909-1.294 2.747-1.026 2.747-1.026.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.579.688.481C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z"/></svg>
179
- GitHub
180
- </a>
181
- <a class="btn btn-outline" href="${NPM_URL}" target="_blank" rel="noopener">npm package</a>
182
- </div>
780
+ <h2 id="build" class="reveal">${t.buildH2}</h2>
781
+ <ul class="uses">
782
+ ${t.uses
783
+ .map((u) => `<li class="glass reveal">${tile(u.icon)}<div><b>${u.t}</b><span>${u.e}</span></div></li>`)
784
+ .join("\n ")}
785
+ </ul>
183
786
 
184
- <div class="badges">
185
- <a href="${NPM_URL}" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/v/webcake-storefront-mcp?label=npm&color=108B67" alt="npm version"></a>
186
- <a href="${NPM_URL}" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/dm/webcake-storefront-mcp?color=3FBB57" alt="npm downloads"></a>
187
- <a href="${GITHUB_URL}/blob/main/LICENSE" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
188
- </div>
787
+ <h2 id="connect" class="reveal">${t.connectH2}</h2>
189
788
 
190
- <div class="hero-mcp">
191
- <label>Remote MCP URL (paste into Claude / Cursor / Windsurf)</label>
192
- <div class="hero-mcp-row">
193
- <code id="mcp-url">${mcpUrl}</code>
194
- <button class="copy-btn" onclick="copyText('mcp-url',this)">Copy</button>
195
- </div>
196
- </div>
197
- </div>
789
+ <div class="glass card method reveal">
790
+ <span class="tag">${tile("terminal")} ${t.m1Tag}</span>
791
+ <p class="msub">${t.m1Sub}</p>
792
+ <ol class="steps">
793
+ ${steps(t.m1Steps.map(fill))}
794
+ </ol>
795
+ <div class="tip"><p class="note">${t.m1Note}</p><pre>${INSTALL_ALL_CMD}</pre></div>
796
+ <details style="margin-top:18px;padding:0">
797
+ <summary>${L === "vi" ? "Dành cho người dùng nâng cao / lập trình viên" : "For advanced users / developers"}</summary>
798
+ <p>${L === "vi" ? "Danh sách đầy đủ các công cụ và lệnh kỹ thuật:" : "Full list of tools and technical commands:"}</p>
799
+ <pre>${L === "vi" ? "~101 tool: get_build_guide · list_elements · get_element · new_element · new_section · new_page_skeleton · validate_page · build_page · add_section · list_pages · get_page_source · search_page_elements · update_page_element · create_page · update_page · update_page_source · publish_site · list_cms_files · get_http_function · edit_http_function · run_function · debug_function · get_current_context · list_my_sites · switch_site · update_auth · toggle_confirm_mode · send_mail · …và nhiều hơn nữa" : "~101 tools: get_build_guide · list_elements · get_element · new_element · new_section · new_page_skeleton · validate_page · build_page · add_section · list_pages · get_page_source · search_page_elements · update_page_element · create_page · update_page · update_page_source · publish_site · list_cms_files · get_http_function · edit_http_function · run_function · debug_function · get_current_context · list_my_sites · switch_site · update_auth · toggle_confirm_mode · send_mail · …and more"}</pre>
800
+ </details>
198
801
  </div>
199
802
 
200
- <div class="section" style="background:var(--bg2);padding-top:56px;padding-bottom:56px">
201
- <div class="container">
202
- <div class="section-label">Why Storefront MCP</div>
203
- <h2>Everything you need to run a storefront, as MCP tools</h2>
204
- <p class="lead">Drop it into your AI IDE and describe what you want — the assistant calls the right tools automatically.</p>
205
- <div class="grid">
206
- <div class="card">
207
- <div class="card-icon">🏗️</div>
208
- <h3>Page builder</h3>
209
- <p>Create, edit, and publish full storefront pages — sections, layout, custom CSS/JS, and global sections — from natural language.</p>
210
- </div>
211
- <div class="card">
212
- <div class="card-icon">🛍️</div>
213
- <h3>Product & collection management</h3>
214
- <p>List collections, inspect schemas, query records, and manage the catalog that powers your store.</p>
215
- </div>
216
- <div class="card">
217
- <div class="card-icon">📝</div>
218
- <h3>Articles & content</h3>
219
- <p>Full CRUD for blog articles — create, draft, update, publish, and delete — with full content control.</p>
220
- </div>
221
- <div class="card">
222
- <div class="card-icon">⚙️</div>
223
- <h3>CMS file & function editing</h3>
224
- <p>Read, write, and deploy HTTP functions and CMS files directly. Debug and run functions in place.</p>
225
- </div>
226
- <div class="card">
227
- <div class="card-icon">👥</div>
228
- <h3>Customer & order data</h3>
229
- <p>Look up customers by ID, phone, or email. Read order data. Send transactional emails via the automation API.</p>
230
- </div>
231
- <div class="card">
232
- <div class="card-icon">🔐</div>
233
- <h3>OAuth 2.1 + PKCE</h3>
234
- <p>Secure per-user authentication — listed in the Claude Connectors Directory. No token in the URL required.</p>
235
- </div>
236
- </div>
237
- </div>
803
+ <div class="glass card method reveal">
804
+ <span class="tag">${tile("link")} ${t.m2Tag}</span>
805
+ <p class="msub">${t.m2Sub}</p>
806
+ <ol class="steps">
807
+ ${steps(t.m2Steps.map(fill))}
808
+ </ol>
809
+ <p class="note">${t.m2Note}</p>
238
810
  </div>
239
811
 
240
- <div class="section">
241
- <div class="container">
242
- <div class="section-label">Tools</div>
243
- <h2>~101 tools across 6 categories</h2>
244
- <p class="lead">Every tool is typed with Zod schemas and returns structured data the model can reason about.</p>
245
- <div class="tools-grid">
246
- <div class="tool-chip">list_pages</div>
247
- <div class="tool-chip">get_page</div>
248
- <div class="tool-chip">create_page</div>
249
- <div class="tool-chip">update_page</div>
250
- <div class="tool-chip">delete_page</div>
251
- <div class="tool-chip">publish_page</div>
252
- <div class="tool-chip">get_page_contents</div>
253
- <div class="tool-chip">update_page_contents</div>
254
- <div class="tool-chip">get_page_custom_code</div>
255
- <div class="tool-chip">update_page_custom_code</div>
256
- <div class="tool-chip">list_global_sections</div>
257
- <div class="tool-chip">get_global_section</div>
258
- <div class="tool-chip">list_cms_files</div>
259
- <div class="tool-chip">get_http_function</div>
260
- <div class="tool-chip">create_http_function</div>
261
- <div class="tool-chip">update_http_function</div>
262
- <div class="tool-chip">delete_http_function</div>
263
- <div class="tool-chip">debug_http_function</div>
264
- <div class="tool-chip">run_http_function</div>
265
- <div class="tool-chip">list_collections</div>
266
- <div class="tool-chip">get_collection_schema</div>
267
- <div class="tool-chip">query_collection</div>
268
- <div class="tool-chip">list_articles</div>
269
- <div class="tool-chip">get_article</div>
270
- <div class="tool-chip">create_article</div>
271
- <div class="tool-chip">update_article</div>
272
- <div class="tool-chip">delete_article</div>
273
- <div class="tool-chip">get_customer</div>
274
- <div class="tool-chip">send_email</div>
275
- <div class="tool-chip">get_site_custom_code</div>
276
- <div class="tool-chip">update_site_custom_code</div>
277
- <div class="tool-chip">+ many more…</div>
278
- </div>
279
- </div>
812
+ <h2 id="tools" class="reveal">${t.toolsH2}</h2>
813
+ <p class="flow-cap reveal" style="margin-bottom:16px">${t.toolsSub}</p>
814
+ <div class="grid-3">
815
+ ${t.toolGroups
816
+ .map((g) => `<div class="glass card reveal">${tile(g.icon)}<h3>${g.t}</h3><p>${g.d}</p></div>`)
817
+ .join("\n ")}
280
818
  </div>
281
819
 
282
- <div class="section" style="background:var(--bg2);padding-top:56px;padding-bottom:56px">
283
- <div class="container">
284
- <div class="section-label">Get started</div>
285
- <h2>Two ways to connect</h2>
286
- <p class="lead">Use the hosted remote URL for instant setup, or run locally via npx for full control.</p>
820
+ <h2 class="reveal">${t.promptH2}</h2>
821
+ <p class="flow-cap reveal" style="margin-bottom:12px">${t.promptSub}</p>
822
+ <ul class="feat">
823
+ <li class="glass reveal">${tile("wand")} <span><pre style="background:transparent;color:inherit;border:none;padding:0;font-size:.88rem;white-space:pre-wrap">${t.promptEx}</pre></span></li>
824
+ </ul>
287
825
 
288
- <div class="install-block">
289
- <h3>Option 1 Remote (recommended)</h3>
290
- <p>Paste the MCP URL into your AI assistant. OAuth login handles authentication automatically.</p>
291
- <div class="code-block">
292
- <code id="remote-url">${mcpUrl}</code>
293
- <button class="copy-btn" onclick="copyText('remote-url',this)">Copy</button>
294
- </div>
295
- </div>
826
+ <h2 id="faq" class="reveal">${t.faqH2}</h2>
827
+ ${faq.map((f) => `<details class="glass reveal"><summary>${f.q}</summary><p>${f.a}</p></details>`).join("\n ")}
296
828
 
297
- <div class="install-block" style="margin-top:16px">
298
- <h3>Option 2 — Local via npx</h3>
299
- <p>Run the MCP server on your own machine. Great for development or offline use.</p>
300
- <div class="install-tabs">
301
- <button class="tab-btn active" onclick="showTab('npx',this)">npx</button>
302
- <button class="tab-btn" onclick="showTab('login',this)">login flow</button>
303
- <button class="tab-btn" onclick="showTab('env',this)">env vars</button>
304
- </div>
305
- <div id="tab-npx" class="code-block">
306
- <code id="npx-cmd">${npxCmd} serve --port 3000</code>
307
- <button class="copy-btn" onclick="copyText('npx-cmd',this)">Copy</button>
308
- </div>
309
- <div id="tab-login" class="code-block" style="display:none">
310
- <code id="login-cmd">${npxCmd} login</code>
311
- <button class="copy-btn" onclick="copyText('login-cmd',this)">Copy</button>
312
- </div>
313
- <div id="tab-env" class="code-block" style="display:none">
314
- <code id="env-cmd">WEBCAKE_TOKEN=your_jwt WEBCAKE_SITE_ID=your_site ${npxCmd}</code>
315
- <button class="copy-btn" onclick="copyText('env-cmd',this)">Copy</button>
316
- </div>
317
- </div>
318
- </div>
829
+ <div class="glass star reveal">
830
+ <h2>${icon("star")} ${t.starH2}</h2>
831
+ <p>${t.starP}</p>
832
+ <a class="btn" href="${GITHUB_URL}">${icon("github")} ${t.starBtn}</a>
319
833
  </div>
320
834
 
321
- <div class="section">
322
- <div class="container" style="text-align:center">
323
- <div class="section-label">Open source</div>
324
- <h2>MIT licensed, community-driven</h2>
325
- <p class="lead" style="margin:0 auto 32px">Contributions welcome. Open an issue or PR on GitHub.</p>
326
- <a class="btn btn-primary" href="${GITHUB_URL}" target="_blank" rel="noopener" style="margin:0 auto">View on GitHub</a>
327
- </div>
328
- </div>
329
- </main>
330
-
331
- <footer>
332
- <div class="container">
333
- <div class="footer-links">
334
- <a href="${GITHUB_URL}" target="_blank" rel="noopener">GitHub</a>
335
- <a href="${NPM_URL}" target="_blank" rel="noopener">npm</a>
336
- <a href="${WEBCAKE_URL}" target="_blank" rel="noopener">WebCake</a>
337
- <a href="/privacy">Privacy Policy</a>
338
- <a href="/terms">Terms of Service</a>
339
- </div>
340
- <p>WebCake Storefront MCP &copy; ${new Date().getFullYear()} &middot; MIT License</p>
341
- </div>
342
- </footer>
835
+ <footer class="glass">
836
+ <span>Endpoint: <code class="inl">${endpoint}</code></span>
837
+ <a href="${DOCS_URL}">${icon("book")} ${t.footGuide}</a>
838
+ <a href="${GITHUB_URL}">${icon("github")} GitHub</a>
839
+ <a href="/privacy">Privacy</a>
840
+ <a href="/terms">Terms</a>
841
+ </footer>
343
842
 
843
+ </div>
344
844
  <script>
345
- function copyText(id, btn) {
346
- const el = document.getElementById(id);
347
- if (!el) return;
348
- navigator.clipboard.writeText(el.textContent || '').then(() => {
349
- const orig = btn.textContent;
350
- btn.textContent = 'Copied!';
351
- setTimeout(() => { btn.textContent = orig; }, 1500);
352
- }).catch(() => {
353
- const r = document.createRange();
354
- r.selectNodeContents(el);
355
- window.getSelection()?.removeAllRanges();
356
- window.getSelection()?.addRange(r);
845
+ (function(){
846
+ var COPY='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
847
+ var DONE='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
848
+ function copyText(t){
849
+ if(navigator.clipboard&&navigator.clipboard.writeText){return navigator.clipboard.writeText(t);}
850
+ return new Promise(function(res,rej){try{var ta=document.createElement('textarea');ta.value=t;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);res();}catch(e){rej(e);}});
851
+ }
852
+ document.querySelectorAll('pre').forEach(function(pre){
853
+ var w=document.createElement('div');w.className='codewrap';
854
+ pre.parentNode.insertBefore(w,pre);w.appendChild(pre);
855
+ var b=document.createElement('button');b.type='button';b.className='copy';b.title='Copy';b.setAttribute('aria-label','Copy');b.innerHTML=COPY;
856
+ b.addEventListener('click',function(){
857
+ copyText(pre.innerText).then(function(){b.classList.add('done');b.innerHTML=DONE;setTimeout(function(){b.classList.remove('done');b.innerHTML=COPY;},1400);}).catch(function(){});
858
+ });
859
+ w.appendChild(b);
357
860
  });
861
+
862
+ var SUN='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>';
863
+ var MOON='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>';
864
+ var html=document.documentElement,tBtn=document.getElementById('theme');
865
+ function effective(){return html.getAttribute('data-theme')||(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');}
866
+ function paint(){if(tBtn)tBtn.innerHTML=effective()==='dark'?SUN:MOON;}
867
+ paint();
868
+ if(tBtn)tBtn.addEventListener('click',function(){var next=effective()==='dark'?'light':'dark';html.setAttribute('data-theme',next);try{localStorage.setItem('wc-theme',next);}catch(e){}paint();});
869
+
870
+ var SKEY='wc-scroll:'+location.pathname+location.search;
871
+ try{var y=sessionStorage.getItem(SKEY);if(y!==null)window.scrollTo(0,parseFloat(y)||0);}catch(e){}
872
+ function saveScroll(){try{sessionStorage.setItem(SKEY,String(window.scrollY));}catch(e){}}
873
+ window.addEventListener('beforeunload',saveScroll);
874
+ window.addEventListener('pagehide',saveScroll);
875
+
876
+ var reveals=[].slice.call(document.querySelectorAll('.reveal'));
877
+ if(window.IntersectionObserver&&reveals.length){
878
+ var io=new IntersectionObserver(function(entries){
879
+ entries.forEach(function(en){if(en.isIntersecting){en.target.classList.add('in');io.unobserve(en.target);}});
880
+ },{rootMargin:'0px 0px -8% 0px'});
881
+ reveals.forEach(function(el){io.observe(el);});
882
+ }else{
883
+ reveals.forEach(function(el){el.classList.add('in');});
884
+ }
885
+
886
+ window.addEventListener('load',function(){requestAnimationFrame(function(){html.classList.add('smooth');});});
887
+
888
+ var navLinks={};
889
+ document.querySelectorAll('.nav a').forEach(function(a){navLinks[a.getAttribute('href').slice(1)]=a;});
890
+ var spySecs=[].slice.call(document.querySelectorAll('h2[id]'));
891
+ var spyTick=false;
892
+ function spy(){
893
+ spyTick=false;var cur=null;
894
+ spySecs.forEach(function(s){if(s.getBoundingClientRect().top<=96)cur=s.id;});
895
+ Object.keys(navLinks).forEach(function(id){navLinks[id].classList.toggle('active',id===cur);});
896
+ }
897
+ if(spySecs.length&&Object.keys(navLinks).length){
898
+ window.addEventListener('scroll',function(){if(!spyTick){spyTick=true;requestAnimationFrame(spy);}},{passive:true});
899
+ spy();
900
+ }
901
+ })();
902
+ </script>
903
+ </body></html>`;
358
904
  }
359
- function showTab(name, btn) {
360
- ['npx','login','env'].forEach(t => {
361
- const el = document.getElementById('tab-' + t);
362
- if (el) el.style.display = t === name ? '' : 'none';
363
- });
364
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
365
- btn.classList.add('active');
905
+ /**
906
+ * Social card SVG — 1200x630 for og:image / twitter:image.
907
+ * Served at /og.svg by http.ts.
908
+ */
909
+ export function ogImageSvg() {
910
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" fill="none" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
911
+ <defs>
912
+ <linearGradient id="bg" x1="0" y1="0" x2="1200" y2="630" gradientUnits="userSpaceOnUse">
913
+ <stop stop-color="#0c0b14"/><stop offset="1" stop-color="#16122a"/>
914
+ </linearGradient>
915
+ <radialGradient id="glow" cx="0" cy="0" r="1" gradientTransform="translate(960 70) rotate(130) scale(620)" gradientUnits="userSpaceOnUse">
916
+ <stop stop-color="#108B67" stop-opacity="0.40"/><stop offset="1" stop-color="#108B67" stop-opacity="0"/>
917
+ </radialGradient>
918
+ </defs>
919
+ <rect width="1200" height="630" fill="url(#bg)"/>
920
+ <rect width="1200" height="630" fill="url(#glow)"/>
921
+ <g transform="translate(90 96)">
922
+ <svg x="0" y="0" width="80" height="80" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
923
+ <rect width="32" height="32" rx="7" fill="url(#sg2)"/>
924
+ <defs><linearGradient id="sg2" x1="0" y1="0" x2="1" y2="1">
925
+ <stop offset="0%" stop-color="#108B67"/>
926
+ <stop offset="100%" stop-color="#14a87c"/>
927
+ </linearGradient></defs>
928
+ <text x="16" y="22" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="700" font-size="17" fill="white">S</text>
929
+ <circle cx="24" cy="9" r="4" fill="#FFD591"/>
930
+ </svg>
931
+ <text x="100" y="42" fill="#ffffff" font-size="40" font-weight="800" letter-spacing="-1">WebCake Storefront MCP</text>
932
+ <text x="100" y="74" fill="#5fe0b3" font-size="22" font-weight="600">Tạo website bán hàng chỉ bằng cách trò chuyện · Build your store just by chatting</text>
933
+ </g>
934
+ <text x="90" y="300" fill="#ffffff" font-size="64" font-weight="800" letter-spacing="-2">Bạn nói điều mình muốn —</text>
935
+ <text x="90" y="380" fill="#ffffff" font-size="64" font-weight="800" letter-spacing="-2">AI dựng trang, kiểm tra,</text>
936
+ <text x="90" y="460" fill="#108B67" font-size="64" font-weight="800" letter-spacing="-2">đăng lên là xong.</text>
937
+ <text x="90" y="534" fill="#9a98b8" font-size="28" font-weight="500">Không cần kéo-thả · Không cần biết lập trình · Luôn xem trước khi lưu</text>
938
+ <g transform="translate(90 560)">
939
+ <rect width="540" height="52" rx="12" fill="#108B67"/>
940
+ <text x="270" y="34" fill="#ffffff" font-size="22" font-weight="700" text-anchor="middle">store.toolvn.io.vn</text>
941
+ </g>
942
+ <text x="1110" y="600" fill="#5b5a7a" font-size="22" font-weight="600" text-anchor="end">github.com/vuluu2k/webcake-storefront-mcp</text>
943
+ </svg>`;
366
944
  }
367
- </script>
368
- </body>
369
- </html>`;
945
+ /**
946
+ * Favicon SVG — the branded "S" mark with yellow dot.
947
+ * Served at /favicon.svg and /favicon.ico by http.ts.
948
+ */
949
+ export function faviconSvg() {
950
+ return ICON_SVG;
951
+ }
952
+ /**
953
+ * landingHtml(origin, lang?) — kept for backward compatibility.
954
+ * Delegates to the bilingual guideHtml().
955
+ */
956
+ export function landingHtml(origin = "", lang) {
957
+ return guideHtml(origin, normalizeLang(lang));
370
958
  }