webcake-landing-mcp 1.0.42 → 1.0.44
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/README.md +2 -1
- package/dist/changelog.json +14 -14
- package/dist/domains/landing/instructions.js +2 -2
- package/dist/persistence/webcake-client.js +27 -0
- package/dist/tools/persistence.js +47 -1
- package/dist/web-guide.js +135 -117
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ persists it (source-only — the page opens in the editor where re-saving render
|
|
|
89
89
|
| **npx (local)** — runs on your machine | Personal daily use, full control | browser `login`, a JWT, or none (reference tools) |
|
|
90
90
|
| **Hosted URL** — use our live server, nothing to install | No Node.js, teams, the claude.ai dialog | your personal `?jwt=` link / `x-webcake-jwt` header |
|
|
91
91
|
|
|
92
|
-
The **reference + generation tools** (`get_generation_guide`, `list_elements`, `validate_page`, …) and the **ingest tools** (`ingest_html`, `ingest_url` — turn an existing HTML or URL into a layout anchor so the AI can recreate or adapt it) work with **zero config**; only the **persistence tools** (`create_page`, `update_page`, `add_section`, `list_pages`, `get_page`, `list_organizations`) need a token. Credentials resolve in order: **per-request header → env var → saved `auth.json`** (`login`).
|
|
92
|
+
The **reference + generation tools** (`get_generation_guide`, `list_elements`, `validate_page`, …) and the **ingest tools** (`ingest_html`, `ingest_url` — turn an existing HTML or URL into a layout anchor so the AI can recreate or adapt it) work with **zero config**; only the **persistence tools** (`create_page`, `update_page`, `add_section`, `list_pages`, `find_pages`, `get_page`, `list_organizations`) need a token. Credentials resolve in order: **per-request header → env var → saved `auth.json`** (`login`).
|
|
93
93
|
|
|
94
94
|
> 🛠️ Prefer a shell-script installer (`install.sh`/`install.ps1`), a cloned local build, or hand-written per-IDE config? See **[docs/manual-install.md](docs/manual-install.md)**.
|
|
95
95
|
|
|
@@ -464,6 +464,7 @@ Both `create_page` and `update_page` **default to `dry_run=true`** (validate and
|
|
|
464
464
|
| `list_organizations` | List the account's organizations (id, name, is_default). Default = the `is_default` org. |
|
|
465
465
|
| `create_page` | Persist a generated source as a new page (source-only). **Defaults to `dry_run=true`.** |
|
|
466
466
|
| `list_pages` | List the account's pages (id, name, organization_id, updated_at) to pick one to edit. |
|
|
467
|
+
| `find_pages` | Search the account's pages by name, domain, and/or page id (AND-combined) to locate one to edit; returns id, name, org, custom/default domain, updated_at. |
|
|
467
468
|
| `get_page` | Fetch an existing page's decoded source tree so you can edit it. |
|
|
468
469
|
| `update_page` | Overwrite an existing page's source with an edited tree. **Defaults to `dry_run=true`.** |
|
|
469
470
|
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.0.44",
|
|
4
|
+
"d": "09/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New find_pages tool searches the account's pages by name, domain (matches custom_domain or default_domain), and/or page id (filters are…",
|
|
7
|
+
"vi": "Công cụ find_pages mới tìm kiếm các trang trong tài khoản theo tên, domain (khớp với custom_domain hoặc default_domain), và/hoặc page id (các bộ lọc…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.0.43",
|
|
11
|
+
"d": "09/06/2026",
|
|
12
|
+
"type": "Changed",
|
|
13
|
+
"en": "The GET / web guide page has refreshed copy throughout: updated page title and meta description, simplified FAQ answers in both English and…",
|
|
14
|
+
"vi": "Trang hướng dẫn GET / được làm mới toàn bộ nội dung: cập nhật tiêu đề trang và meta description, đơn giản hóa câu trả lời FAQ bằng cả tiếng Anh và…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.0.42",
|
|
4
18
|
"d": "09/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Internal",
|
|
27
41
|
"en": "Added server.json MCP Registry manifest (namespace io.github.vuluu2k/webcake-landing-mcp) and the corresponding mcpName field in package.json so the…",
|
|
28
42
|
"vi": "Thêm manifest MCP Registry server.json (namespace io.github.vuluu2k/webcake-landing-mcp) và trường mcpName tương ứng trong package.json để MCP…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.0.38",
|
|
32
|
-
"d": "08/06/2026",
|
|
33
|
-
"type": "Added",
|
|
34
|
-
"en": "New add_section tool appends one or more sections to an existing page without re-sending the full source: the server fetches the current page,…",
|
|
35
|
-
"vi": "Công cụ add_section mới cho phép gắn thêm một hoặc nhiều section vào trang hiện có mà không cần gửi lại toàn bộ source: server lấy trang hiện tại,…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.0.37",
|
|
39
|
-
"d": "08/06/2026",
|
|
40
|
-
"type": "Changed",
|
|
41
|
-
"en": "get_generation_guide now includes a HEADER section build hint directing the agent to place every header child (logo, brand text, CTA button) on a…",
|
|
42
|
-
"vi": "get_generation_guide nay bổ sung gợi ý dựng section HEADER yêu cầu agent đặt mọi phần tử con trong header (logo, tên thương hiệu, nút CTA) trên cùng…"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -13,7 +13,7 @@ RULES (follow for every request):
|
|
|
13
13
|
- BUILD THE SOURCE IN ONE PASS — gather everything you need BEFORE assembling the source, then build the FULL tree once. BATCH the reads: when a section needs several element types (section + text-block + image-block + button + form + input), call get_element({types:[…]}) ONCE instead of one call per type — same for images, call search_images({queries:[…]}) ONCE with one query per image slot (it dedups + parallelizes and returns one best photo per query). Do NOT interleave get_element calls between create_page previews and rebuild. create_page/update_page take the entire source as input, so each call re-ships the whole page — re-previewing repeatedly wastes the request.
|
|
14
14
|
- create_page and update_page DEFAULT to dry_run=true (a safety net for ambiguous requests). When the user's intent is clear AND validate_page already passed (no errors), SKIP the dry-run and call with dry_run=false directly — saves one round-trip. Use dry_run=true only when (a) the request is ambiguous about target/content, (b) the user explicitly asks to "preview" or "xem trước", (c) this is an update_page that overwrites significant existing content, or (d) you genuinely need to inspect the redacted payload. Never loop dry-runs to "check" the source — validate_page is the validator. Do not run dry-run then dry-run again before the real write.
|
|
15
15
|
- LARGE PAGES (4+ sections) — build INCREMENTALLY to avoid the giant single create_page payload that can drop the connection: create_page with a SMALL skeleton (empty/near-empty page) to get a page_id, then call add_section once per section (each call ships ONLY that section; the backend appends it server-side and rejects duplicate ids — no whole-source get+put). Small pages can still go in one create_page pass.
|
|
16
|
-
- EDIT existing pages surgically: get_page → change ONLY what was asked → keep every other element, its id, and coordinates → validate_page → update_page. Never regenerate the whole tree for a small change.
|
|
16
|
+
- EDIT existing pages surgically: find_pages (locate the page by name/domain/id when you don't already have a page_id) → get_page → change ONLY what was asked → keep every other element, its id, and coordinates → validate_page → update_page. Never regenerate the whole tree for a small change.
|
|
17
17
|
- Organizations: call list_organizations and ask which to use; default to the is_default org. Endpoints are owner-scoped (only the account's own pages).
|
|
18
18
|
- REFERENCE INPUT — if the user provides a layout reference, USE it as the layout anchor (don't ignore it, don't re-invent from scratch). Three input modes: (1) IMAGE/screenshot attached in chat → analyze it natively (no tool call): identify section flow (hero/features/form/cta/footer), heading hierarchy, dominant colors, font feel, then map sections to Webcake elements. (2) HTML string → call ingest_html(html) to get a compact AST. (3) URL → call ingest_url(url) for the same AST. The AST classifies sections by role and lists headings/subheadings/ctas/images/form_fields plus brand hints (colors/fonts) — use it for LAYOUT + HIERARCHY, then generate FRESH content tailored to the user's brand (don't 1:1 copy text). intent='clone' only when the user explicitly asks to mirror the original; default intent='adapt'. The reference workflow PRESERVES craft rules above (centering, page margin, premium spacing, real images) — apply them on top of the reference layout, don't bypass them.
|
|
19
19
|
|
|
@@ -28,4 +28,4 @@ MODEL (essentials):
|
|
|
28
28
|
- Visible content lives in specials (text, src, field_name…), never in styles. Colors as rgba(). Animation in config.animation={name,delay,duration,repeat}. Form inputs need a unique specials.field_name (use canonical keys: full_name, phone_number, email, address, quantity).
|
|
29
29
|
- IMAGES: include them (hero/product, feature icons, about photo). PREFER REAL PHOTOS — call search_images with a short English subject (e.g. 'fresh coffee cup') and put a returned URL (src.large for a hero/banner, src.medium for a card/thumb) into the image-block specials.src; it works out of the box (a shared proxy supplies images). Only if search_images returns ok:false, FALL BACK to a PLACEHOLDER sized to the box: https://placehold.co/<width>x<height>. (gallery.media = array of OBJECTS {type:'image',link:'<url>',linkVideo:'',typeVideo:'youtube',imageCompression:true} — NOT plain strings, the gallery reads item.link; video.specials.img = poster). NEVER leave src empty (renders blank). Ensure text contrasts with its section background.
|
|
30
30
|
|
|
31
|
-
Start by calling get_generation_guide. Tools: get_generation_guide, list_elements, get_element, new_element, new_page_skeleton, get_page_schema, validate_page, search_images, ingest_html, ingest_url, list_organizations, create_page, list_pages, get_page, update_page, add_section.`;
|
|
31
|
+
Start by calling get_generation_guide. Tools: get_generation_guide, list_elements, get_element, new_element, new_page_skeleton, get_page_schema, validate_page, search_images, ingest_html, ingest_url, list_organizations, create_page, list_pages, find_pages, get_page, update_page, add_section.`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const CREATE_ENDPOINT = "/api/v1/ai/create_page_from_source";
|
|
2
2
|
const ORGS_ENDPOINT = "/api/v1/org/organizations";
|
|
3
3
|
const PAGES_ENDPOINT = "/api/v1/ai/pages";
|
|
4
|
+
const SEARCH_PAGES_ENDPOINT = "/api/v1/ai/search_pages";
|
|
4
5
|
const PAGE_SOURCE_ENDPOINT = "/api/v1/ai/page_source";
|
|
5
6
|
const UPDATE_ENDPOINT = "/api/v1/ai/update_page_source";
|
|
6
7
|
const APPEND_ENDPOINT = "/api/v1/ai/append_section";
|
|
@@ -167,6 +168,32 @@ export async function listPages(config) {
|
|
|
167
168
|
const pages = r.json?.data?.pages ?? r.json?.pages ?? [];
|
|
168
169
|
return { ok: true, status: r.status, pages };
|
|
169
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Search the account's pages by name / domain / id via the dedicated backend
|
|
173
|
+
* endpoint. Filters are AND-combined server-side; each row carries the page's
|
|
174
|
+
* `custom_domain` + `default_domain` so the caller can disambiguate by URL.
|
|
175
|
+
* Returns `endpoint_missing:true` on a 404 so the caller can fall back to
|
|
176
|
+
* filtering `listPages` client-side against an older backend lacking the route.
|
|
177
|
+
*/
|
|
178
|
+
export async function searchPages(config, filters) {
|
|
179
|
+
const qs = new URLSearchParams();
|
|
180
|
+
if (filters.name)
|
|
181
|
+
qs.set("name", filters.name);
|
|
182
|
+
if (filters.domain)
|
|
183
|
+
qs.set("domain", filters.domain);
|
|
184
|
+
if (filters.id)
|
|
185
|
+
qs.set("id", filters.id);
|
|
186
|
+
if (filters.limit != null)
|
|
187
|
+
qs.set("limit", `${filters.limit}`);
|
|
188
|
+
const url = `${config.base}${SEARCH_PAGES_ENDPOINT}${qs.toString() ? `?${qs}` : ""}`;
|
|
189
|
+
const r = await getJson(url, config);
|
|
190
|
+
if (r.status === 404)
|
|
191
|
+
return { ok: false, status: 404, endpoint_missing: true, error: "search_pages endpoint not found on backend" };
|
|
192
|
+
if (!r.ok)
|
|
193
|
+
return { ok: false, status: r.status, error: r.error };
|
|
194
|
+
const pages = r.json?.data?.pages ?? r.json?.pages ?? [];
|
|
195
|
+
return { ok: true, status: r.status, pages };
|
|
196
|
+
}
|
|
170
197
|
/** Read a page's decoded source tree (must be owned by the account). */
|
|
171
198
|
export async function getPageSource(config, pageId) {
|
|
172
199
|
const url = `${config.base}${PAGE_SOURCE_ENDPOINT}?page_id=${encodeURIComponent(pageId)}`;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { text } from "../mcp/response.js";
|
|
14
14
|
import { readConfig, configFromHeaders } from "../persistence/config.js";
|
|
15
|
-
import { buildRequestRedacted, buildUpdateRequestRedacted, buildAppendRequestRedacted, createPage, listOrganizations, listPages, getPageSource, updatePageSource, appendSection, } from "../persistence/webcake-client.js";
|
|
15
|
+
import { buildRequestRedacted, buildUpdateRequestRedacted, buildAppendRequestRedacted, createPage, listOrganizations, listPages, searchPages, getPageSource, updatePageSource, appendSection, } from "../persistence/webcake-client.js";
|
|
16
16
|
export function registerPersistenceTools(server, domain) {
|
|
17
17
|
// Resolve config from THIS request's headers (remote per-user JWT) first, then env.
|
|
18
18
|
const cfgFor = (extra) => readConfig(configFromHeaders(extra?.requestInfo?.headers));
|
|
@@ -107,6 +107,52 @@ export function registerPersistenceTools(server, domain) {
|
|
|
107
107
|
return text({ ok: false, reason: "missing_env", missing_env: missing });
|
|
108
108
|
return text(await listPages(config));
|
|
109
109
|
});
|
|
110
|
+
// 10b) Find pages (search by name / domain / id) ----------------------------
|
|
111
|
+
// The lookup step before an edit: locate the page the user means by name,
|
|
112
|
+
// domain (custom OR default), and/or page id, then feed its id to get_page →
|
|
113
|
+
// (edit) → update_page/add_section. Filters are AND-combined server-side and
|
|
114
|
+
// results carry both domain fields so the model can disambiguate by URL.
|
|
115
|
+
//
|
|
116
|
+
// Searches via the dedicated /api/v1/ai/search_pages endpoint (proper DB query,
|
|
117
|
+
// not limited to the 50 most-recent). If that route is missing (older backend
|
|
118
|
+
// → 404) it FALLS BACK to listing pages and filtering client-side by name/id
|
|
119
|
+
// (domain search is unavailable in the fallback — list_pages omits domains).
|
|
120
|
+
server.tool("find_pages", "Searches the account's pages by name, domain, and/or page id so you can locate the page to edit, then pass its id to get_page → update_page/add_section. Filters are AND-combined (e.g. name='sale' + domain='shop.com'). Each result includes id, name, organization_id, custom_domain, default_domain, updated_at. With no filters it returns the most-recent pages (like list_pages). Needs WEBCAKE_API_BASE + WEBCAKE_JWT.", {
|
|
121
|
+
name: z.string().optional().describe("Case-insensitive substring of the page name to match."),
|
|
122
|
+
domain: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Case-insensitive substring of the page's domain (matches custom_domain OR default_domain)."),
|
|
126
|
+
page_id: z.string().optional().describe("Exact page id — narrows to that single page (useful to confirm it exists/owned)."),
|
|
127
|
+
limit: z.number().int().positive().max(100).optional().describe("Max results (default 50, capped at 100)."),
|
|
128
|
+
}, { title: "Find Webcake Pages", readOnlyHint: true, openWorldHint: true }, async ({ name, domain, page_id, limit }, extra) => {
|
|
129
|
+
const { config, missing } = cfgFor(extra);
|
|
130
|
+
if (!config)
|
|
131
|
+
return text({ ok: false, reason: "missing_env", missing_env: missing });
|
|
132
|
+
const res = await searchPages(config, { name, domain, id: page_id, limit });
|
|
133
|
+
if (!res.endpoint_missing) {
|
|
134
|
+
return text({ ok: res.ok, pages: res.pages, count: res.pages?.length ?? 0, status: res.status, error: res.error });
|
|
135
|
+
}
|
|
136
|
+
// Fallback: older backend without /search_pages — list and filter client-side.
|
|
137
|
+
const listed = await listPages(config);
|
|
138
|
+
if (!listed.ok)
|
|
139
|
+
return text({ ok: false, status: listed.status, error: listed.error });
|
|
140
|
+
let pages = listed.pages ?? [];
|
|
141
|
+
const nameQ = name?.toLowerCase();
|
|
142
|
+
if (nameQ)
|
|
143
|
+
pages = pages.filter((p) => p.name?.toLowerCase().includes(nameQ));
|
|
144
|
+
if (page_id)
|
|
145
|
+
pages = pages.filter((p) => `${p.id}` === page_id);
|
|
146
|
+
if (limit != null)
|
|
147
|
+
pages = pages.slice(0, limit);
|
|
148
|
+
return text({
|
|
149
|
+
ok: true,
|
|
150
|
+
pages,
|
|
151
|
+
count: pages.length,
|
|
152
|
+
via: "legacy_list_filter_fallback",
|
|
153
|
+
...(domain ? { note: "Domain search is unavailable on this backend (list_pages omits domains); the domain filter was ignored." } : {}),
|
|
154
|
+
});
|
|
155
|
+
});
|
|
110
156
|
// 11) Get page (read source) ------------------------------------------------
|
|
111
157
|
server.tool("get_page", "Fetches an existing page's decoded source tree { page, popup, settings, options, cartConfigs } plus name and organization_id. Needs WEBCAKE_API_BASE + WEBCAKE_JWT.", { page_id: z.string().describe("The page id (from list_pages or a URL).") }, { title: "Get Webcake Page Source", readOnlyHint: true, openWorldHint: true }, async ({ page_id }, extra) => {
|
|
112
158
|
const { config, missing } = cfgFor(extra);
|
package/dist/web-guide.js
CHANGED
|
@@ -78,14 +78,14 @@ function tile(name) {
|
|
|
78
78
|
// ── i18n: per-language SEO metadata ───────────────────────────────────────────
|
|
79
79
|
const META = {
|
|
80
80
|
vi: {
|
|
81
|
-
title: "Webcake Landing MCP — AI dựng landing page Webcake
|
|
82
|
-
desc: "
|
|
81
|
+
title: "Webcake Landing MCP — AI tự dựng landing page Webcake cho bạn",
|
|
82
|
+
desc: "Bạn chỉ cần mô tả bằng lời, AI (Claude, Cursor, Windsurf…) sẽ tự dựng, tự kiểm tra và lưu landing page Webcake cho bạn. Không cần kéo-thả, không cần biết code, kết nối một lần là xong.",
|
|
83
83
|
keywords: "Webcake, landing page, MCP, Model Context Protocol, AI, Claude, Cursor, Windsurf, tạo landing page bằng AI, no-code, COD, lead generation",
|
|
84
84
|
locale: "vi_VN",
|
|
85
85
|
},
|
|
86
86
|
en: {
|
|
87
|
-
title: "Webcake Landing MCP — let AI build Webcake landing pages
|
|
88
|
-
desc: "
|
|
87
|
+
title: "Webcake Landing MCP — let AI build Webcake landing pages for you",
|
|
88
|
+
desc: "Just describe it in plain words and AI (Claude, Cursor, Windsurf…) builds, checks and saves a Webcake landing page for you. No drag-and-drop, no coding — connect once and you're set.",
|
|
89
89
|
keywords: "Webcake, landing page, MCP, Model Context Protocol, AI, Claude, Cursor, Windsurf, AI website builder, no-code, COD, lead generation",
|
|
90
90
|
locale: "en_US",
|
|
91
91
|
},
|
|
@@ -95,210 +95,210 @@ const FAQ = {
|
|
|
95
95
|
vi: [
|
|
96
96
|
{
|
|
97
97
|
q: "Webcake Landing MCP là gì?",
|
|
98
|
-
a: "
|
|
98
|
+
a: "Là một công cụ giúp AI (như Claude, Cursor) tự dựng landing page Webcake cho bạn từ một câu mô tả, tự kiểm tra rồi lưu vào tài khoản Webcake của bạn. Công cụ lo phần khó; bạn chỉ cần mô tả và xem lại.",
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
q: "Tôi có cần biết lập trình không?",
|
|
102
|
-
a: "Không. Bạn chỉ cần mô tả trang bằng
|
|
102
|
+
a: "Không. Bạn chỉ cần mô tả trang bằng lời, ngay trong ứng dụng AI mà bạn đang dùng. Mọi phần kỹ thuật để công cụ tự lo.",
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
q: "Có miễn phí không?",
|
|
106
|
-
a: "Có.
|
|
106
|
+
a: "Có. Công cụ miễn phí và mã nguồn mở. Bạn chỉ cần một tài khoản Webcake để lưu trang.",
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
|
-
q: "Dùng được với
|
|
110
|
-
a: "Claude
|
|
109
|
+
q: "Dùng được với ứng dụng nào?",
|
|
110
|
+
a: "Claude (máy tính, web, Claude Code), Cursor, Windsurf, Augment, Codex — và bất kỳ ứng dụng AI nào hỗ trợ chuẩn kết nối MCP.",
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
|
-
q: "
|
|
114
|
-
a: "Có.
|
|
113
|
+
q: "Thông tin của tôi có an toàn không?",
|
|
114
|
+
a: "Có. Mã đăng nhập là của riêng bạn và không bị lưu lại. Ngoài ra, mỗi lần lưu trang công cụ đều cho bạn xem trước, chắc chắn rồi mới thực hiện.",
|
|
115
115
|
},
|
|
116
116
|
],
|
|
117
117
|
en: [
|
|
118
118
|
{
|
|
119
119
|
q: "What is Webcake Landing MCP?",
|
|
120
|
-
a: "
|
|
120
|
+
a: "It's a tool that lets AI (like Claude or Cursor) build a Webcake landing page for you from a single description, check it, then save it to your Webcake account. The tool does the hard part; you just describe and review.",
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
q: "Do I need to know how to code?",
|
|
124
|
-
a: "No. You just describe the page in
|
|
124
|
+
a: "No. You just describe the page in plain words, right inside the AI app you already use. The tool handles all the technical parts.",
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
q: "Is it free?",
|
|
128
|
-
a: "Yes. The
|
|
128
|
+
a: "Yes. The tool is free and open-source. You only need a Webcake account to save pages.",
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
|
-
q: "Which
|
|
132
|
-
a: "Claude
|
|
131
|
+
q: "Which apps work with it?",
|
|
132
|
+
a: "Claude (desktop, web, Claude Code), Cursor, Windsurf, Augment, Codex — and any AI app that supports the MCP connection standard.",
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
|
-
q: "Is my
|
|
136
|
-
a: "Yes.
|
|
135
|
+
q: "Is my information safe?",
|
|
136
|
+
a: "Yes. Your login code is yours alone and is never stored. On top of that, every save shows you a preview first, so nothing happens until you're sure.",
|
|
137
137
|
},
|
|
138
138
|
],
|
|
139
139
|
};
|
|
140
140
|
const T = {
|
|
141
141
|
vi: {
|
|
142
|
-
sub: "
|
|
143
|
-
running: "
|
|
144
|
-
leadPre: "Bạn gõ yêu cầu, AI (Claude, Cursor…) dựng
|
|
142
|
+
sub: "Để AI tự dựng & sửa landing page Webcake cho bạn, chỉ bằng lời nói",
|
|
143
|
+
running: "Đang hoạt động",
|
|
144
|
+
leadPre: "Bạn gõ yêu cầu, AI (như Claude, Cursor…) tự dựng cả trang landing page rồi ",
|
|
145
145
|
leadGrad: "lưu thẳng vào tài khoản Webcake",
|
|
146
|
-
leadPost: " của bạn. Không kéo thả, không cài
|
|
146
|
+
leadPost: " của bạn. Không cần kéo thả, không cần cài đặt phức tạp — kết nối một lần là xong.",
|
|
147
147
|
ctaStart: "Bắt đầu kết nối",
|
|
148
|
-
ctaStar: "
|
|
149
|
-
flowH2: "
|
|
148
|
+
ctaStar: "Tặng sao trên GitHub",
|
|
149
|
+
flowH2: "Mọi thứ diễn ra thế nào",
|
|
150
150
|
flow: [
|
|
151
|
-
{ icon: "bulb", t: "Bạn", s: "ý tưởng" },
|
|
152
|
-
{ icon: "brain", t: "Trợ lý AI", s: "Claude ·
|
|
153
|
-
{ icon: "server", t: "MCP", s: "
|
|
154
|
-
{ icon: "window", t: "
|
|
151
|
+
{ icon: "bulb", t: "Bạn", s: "ý tưởng của bạn" },
|
|
152
|
+
{ icon: "brain", t: "Trợ lý AI", s: "Claude · Cursor · Codex…" },
|
|
153
|
+
{ icon: "server", t: "Webcake MCP", s: "cầu nối thông minh" },
|
|
154
|
+
{ icon: "window", t: "Webcake", s: "trang web thật" },
|
|
155
155
|
],
|
|
156
|
-
flowCap: "Bạn
|
|
157
|
-
howH2: "
|
|
156
|
+
flowCap: "Bạn nói ý tưởng → AI hiểu cách Webcake hoạt động → tự dựng trang và tự kiểm tra → trang được lưu thật vào Webcake. Bạn nhận link, mở lên xem và đăng trang.",
|
|
157
|
+
howH2: "Vì sao bạn không cần biết kỹ thuật",
|
|
158
158
|
how: [
|
|
159
159
|
{
|
|
160
160
|
icon: "brain",
|
|
161
|
-
t: "AI
|
|
162
|
-
d:
|
|
161
|
+
t: "AI lo phần dựng trang",
|
|
162
|
+
d: "AI tự sắp xếp chữ, hình ảnh, nút bấm, biểu mẫu… theo đúng chuẩn của Webcake. Bạn không phải đụng tới.",
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
icon: "check2",
|
|
166
|
-
t: "
|
|
167
|
-
d: "
|
|
166
|
+
t: "Hệ thống tự kiểm tra",
|
|
167
|
+
d: "Trang được dựng và kiểm tra tự động trước khi lưu, nên hiếm khi lỗi — bạn không cần biết gì về kỹ thuật.",
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
icon: "edit",
|
|
171
|
-
t: "Bạn chỉ
|
|
172
|
-
d: "Mở trang trong
|
|
171
|
+
t: "Bạn chỉ việc xem lại",
|
|
172
|
+
d: "Mở trang trong trình chỉnh sửa Webcake, sửa vài chỗ nếu thích, bấm lưu là trang hiển thị. Vậy là xong.",
|
|
173
173
|
},
|
|
174
174
|
],
|
|
175
|
-
buildH2: "Bạn
|
|
175
|
+
buildH2: "Bạn có thể tạo những gì",
|
|
176
176
|
uses: [
|
|
177
177
|
{
|
|
178
178
|
icon: "magnet",
|
|
179
|
-
t: "Trang thu
|
|
180
|
-
e: '"Trang
|
|
179
|
+
t: "Trang thu khách tiềm năng",
|
|
180
|
+
e: '"Trang đăng ký nhận tin cho ứng dụng — phần đầu nổi bật, 3 lợi ích, ô nhập email."',
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
icon: "cart",
|
|
184
|
-
t: "Bán hàng COD / online",
|
|
185
|
-
e: '"Trang một sản phẩm —
|
|
184
|
+
t: "Bán hàng (COD / online)",
|
|
185
|
+
e: '"Trang bán một sản phẩm — ảnh, giá, lựa chọn mẫu mã, form đặt hàng."',
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
icon: "ticket",
|
|
189
|
-
t: "Sự kiện /
|
|
190
|
-
e: '"Trang đăng ký — đếm ngược,
|
|
189
|
+
t: "Sự kiện / hội thảo",
|
|
190
|
+
e: '"Trang đăng ký tham gia — đồng hồ đếm ngược, lịch trình, form đăng ký."',
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
icon: "mail",
|
|
194
194
|
t: "Thiệp mời",
|
|
195
|
-
e: '"Thiệp cưới — tên
|
|
195
|
+
e: '"Thiệp cưới — tên cô dâu chú rể, ngày giờ, bản đồ, form xác nhận tham dự."',
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
icon: "phone",
|
|
199
|
-
t: "
|
|
200
|
-
e: '"
|
|
199
|
+
t: "Giới thiệu ứng dụng",
|
|
200
|
+
e: '"Ảnh điện thoại, danh sách tính năng, nút tải trên App Store và Google Play."',
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
icon: "flame",
|
|
204
|
-
t: "
|
|
205
|
-
e: '"Đồng hồ đếm ngược
|
|
204
|
+
t: "Sale chớp nhoáng",
|
|
205
|
+
e: '"Đồng hồ đếm ngược cỡ lớn, lưới sản phẩm giảm giá, nút Mua luôn nổi."',
|
|
206
206
|
},
|
|
207
207
|
],
|
|
208
208
|
connectH2: "Kết nối — chọn 1 trong 2 cách",
|
|
209
|
-
m1Tag: "Cách ① ·
|
|
210
|
-
m1Sub: "
|
|
209
|
+
m1Tag: "Cách ① · Cài trên máy của bạn",
|
|
210
|
+
m1Sub: "Phù hợp khi bạn tự dùng và muốn chủ động. Cần có sẵn Node.js (một phần mềm miễn phí giúp chạy lệnh).",
|
|
211
211
|
m1Steps: [
|
|
212
|
-
|
|
213
|
-
"<b>Mở Terminal
|
|
214
|
-
'<b>Làm theo
|
|
215
|
-
'<b>
|
|
212
|
+
"<b>Cài Node.js</b> (miễn phí) nếu máy chưa có — tải tại <b>nodejs.org</b>. Đây là phần mềm giúp máy chạy được công cụ ở bước sau.",
|
|
213
|
+
"<b>Mở Terminal</b> (cửa sổ gõ lệnh — trên Mac tìm “Terminal”, trên Windows tìm “Command Prompt”), dán dòng dưới đây rồi nhấn Enter:<pre>npx -y webcake-landing-mcp install</pre>",
|
|
214
|
+
'<b>Làm theo hướng dẫn hiện ra:</b> chọn <code class="inl">prod</code> → đăng nhập Webcake bằng trình duyệt → chọn ứng dụng AI bạn đang dùng (Claude, Cursor…).',
|
|
215
|
+
'<b>Mở lại ứng dụng AI.</b> Thấy chữ <code class="inl">webcake-landing</code> xuất hiện là đã kết nối thành công.',
|
|
216
216
|
],
|
|
217
|
-
m1Note: "Muốn
|
|
218
|
-
m2Tag: "Cách ② ·
|
|
219
|
-
m2Sub: "
|
|
217
|
+
m1Note: "Muốn cài cho tất cả ứng dụng cùng lúc, dùng dòng lệnh này:",
|
|
218
|
+
m2Tag: "Cách ② · Dùng link — không cần cài gì",
|
|
219
|
+
m2Sub: "Phù hợp khi máy không cài được phần mềm, dùng theo nhóm, hoặc dùng Claude trên web (claude.ai).",
|
|
220
220
|
m2Steps: [
|
|
221
|
-
'<b>Lấy link
|
|
222
|
-
'<b>
|
|
223
|
-
"<b>Dán link</b> vừa copy (
|
|
224
|
-
"<b>Bấm Add</b> (hoặc lưu file)
|
|
221
|
+
'<b>Lấy link riêng của bạn</b> (đã gắn sẵn mã đăng nhập) — mở trang dưới đây rồi bấm <b>Copy</b>:<a class="btn" href="{REMOTE}">Mở {REMOTE_HOST} {ARROW}</a>',
|
|
222
|
+
'<b>Vào nơi thêm kết nối</b> trong ứng dụng:<br>• claude.ai: <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: mở file <code class="inl">.mcp.json</code>',
|
|
223
|
+
"<b>Dán link</b> bạn vừa copy (trông giống như):<pre>{ENDPOINT}?jwt=<MÃ CỦA BẠN></pre>",
|
|
224
|
+
"<b>Bấm Add</b> (hoặc lưu file) rồi chờ một chút. Khi biểu tượng Webcake chuyển xanh là dùng được.",
|
|
225
225
|
],
|
|
226
|
-
m2Note: "⚠️ Link chứa
|
|
227
|
-
afterH2: "
|
|
226
|
+
m2Note: "⚠️ Link có chứa mã đăng nhập riêng của bạn — hãy coi như mật khẩu, đừng chia sẻ cho ai.",
|
|
227
|
+
afterH2: "Kết nối xong, bạn chỉ cần nói",
|
|
228
228
|
examples: [
|
|
229
229
|
{
|
|
230
230
|
icon: "wand",
|
|
231
|
-
t: '"Dựng
|
|
231
|
+
t: '"Dựng trang bán khóa học, tông màu xanh, có form đăng ký và nút Zalo."',
|
|
232
232
|
},
|
|
233
233
|
{
|
|
234
234
|
icon: "edit",
|
|
235
|
-
t: "\"Mở trang <i>sale-hè</i>, đổi tiêu đề thành 'Giảm 50%', nút sang màu đỏ.\"",
|
|
235
|
+
t: "\"Mở trang <i>sale-hè</i>, đổi tiêu đề thành 'Giảm 50%', đổi nút sang màu đỏ.\"",
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
icon: "clock",
|
|
239
|
-
t: '"Thêm
|
|
239
|
+
t: '"Thêm phần đếm ngược và 3 lời nhận xét của khách vào cuối trang."',
|
|
240
240
|
},
|
|
241
241
|
],
|
|
242
242
|
newH2: "Có gì mới",
|
|
243
243
|
newBadge: "MỚI",
|
|
244
|
-
clMore: "Xem
|
|
244
|
+
clMore: "Xem tất cả thay đổi",
|
|
245
245
|
faqH2: "Câu hỏi thường gặp",
|
|
246
|
-
starH2: "Thấy hữu ích?
|
|
247
|
-
starP: "Đây là dự án mã nguồn mở — mỗi
|
|
248
|
-
starBtn: "
|
|
246
|
+
starH2: "Thấy hữu ích? Tặng dự án một ngôi sao nhé",
|
|
247
|
+
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 biết đến hơn.",
|
|
248
|
+
starBtn: "Tặng sao trên GitHub",
|
|
249
249
|
footGuide: "Hướng dẫn",
|
|
250
250
|
switchLabel: "English",
|
|
251
251
|
},
|
|
252
252
|
en: {
|
|
253
|
-
sub: "Let AI build & edit Webcake landing pages
|
|
254
|
-
running: "
|
|
255
|
-
leadPre: "You type
|
|
253
|
+
sub: "Let AI build & edit your Webcake landing pages, just by talking to it",
|
|
254
|
+
running: "Up and running",
|
|
255
|
+
leadPre: "You type what you want, AI (like Claude or Cursor) builds the whole landing page and ",
|
|
256
256
|
leadGrad: "saves it straight to your Webcake account",
|
|
257
|
-
leadPost: ". No drag-and-drop, no
|
|
257
|
+
leadPost: ". No drag-and-drop, no complicated setup — connect once and you're done.",
|
|
258
258
|
ctaStart: "Get connected",
|
|
259
259
|
ctaStar: "Star on GitHub",
|
|
260
|
-
flowH2: "How it
|
|
260
|
+
flowH2: "How it all happens",
|
|
261
261
|
flow: [
|
|
262
262
|
{ icon: "bulb", t: "You", s: "your idea" },
|
|
263
|
-
{ icon: "brain", t: "AI assistant", s: "Claude ·
|
|
264
|
-
{ icon: "server", t: "MCP", s: "
|
|
265
|
-
{ icon: "window", t: "
|
|
263
|
+
{ icon: "brain", t: "AI assistant", s: "Claude · Cursor · Codex…" },
|
|
264
|
+
{ icon: "server", t: "Webcake MCP", s: "the smart bridge" },
|
|
265
|
+
{ icon: "window", t: "Webcake", s: "a real page" },
|
|
266
266
|
],
|
|
267
|
-
flowCap: "You
|
|
268
|
-
howH2: "
|
|
267
|
+
flowCap: "You say your idea → the AI learns how Webcake works → it builds the page and checks it → the page is saved for real in Webcake. You get a link, open it, and publish.",
|
|
268
|
+
howH2: "Why you don't need to be techy",
|
|
269
269
|
how: [
|
|
270
270
|
{
|
|
271
271
|
icon: "brain",
|
|
272
|
-
t: "AI
|
|
273
|
-
d: "
|
|
272
|
+
t: "AI does the building",
|
|
273
|
+
d: "The AI arranges the text, images, buttons and forms the way Webcake expects. You never have to touch any of it.",
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
276
|
icon: "check2",
|
|
277
|
-
t: "
|
|
278
|
-
d: "
|
|
277
|
+
t: "It checks itself",
|
|
278
|
+
d: "Every page is built and checked automatically before it's saved, so things rarely break — no technical know-how needed.",
|
|
279
279
|
},
|
|
280
280
|
{
|
|
281
281
|
icon: "edit",
|
|
282
282
|
t: "You just review",
|
|
283
|
-
d: "Open the page in the Webcake editor, tweak
|
|
283
|
+
d: "Open the page in the Webcake editor, tweak anything you like, hit save and it goes live. That's it.",
|
|
284
284
|
},
|
|
285
285
|
],
|
|
286
286
|
buildH2: "What you can build",
|
|
287
287
|
uses: [
|
|
288
288
|
{
|
|
289
289
|
icon: "magnet",
|
|
290
|
-
t: "Lead-
|
|
291
|
-
e: '"A
|
|
290
|
+
t: "Lead-capture page",
|
|
291
|
+
e: '"A waitlist for an app — a bold header, 3 benefits, an email box."',
|
|
292
292
|
},
|
|
293
293
|
{
|
|
294
294
|
icon: "cart",
|
|
295
|
-
t: "COD / online
|
|
296
|
-
e: '"A one-product page —
|
|
295
|
+
t: "Sell products (COD / online)",
|
|
296
|
+
e: '"A one-product page — photos, price, options, an order form."',
|
|
297
297
|
},
|
|
298
298
|
{
|
|
299
299
|
icon: "ticket",
|
|
300
300
|
t: "Event / webinar",
|
|
301
|
-
e: '"A
|
|
301
|
+
e: '"A sign-up page — a countdown, the agenda, a registration form."',
|
|
302
302
|
},
|
|
303
303
|
{
|
|
304
304
|
icon: "mail",
|
|
@@ -308,38 +308,38 @@ const T = {
|
|
|
308
308
|
{
|
|
309
309
|
icon: "phone",
|
|
310
310
|
t: "App promo",
|
|
311
|
-
e: '"Phone
|
|
311
|
+
e: '"Phone photos, a feature list, App Store and Google Play buttons."',
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
icon: "flame",
|
|
315
315
|
t: "Flash sale",
|
|
316
|
-
e: '"A big countdown, a discounted
|
|
316
|
+
e: '"A big countdown, a grid of discounted products, a Buy button that follows you."',
|
|
317
317
|
},
|
|
318
318
|
],
|
|
319
319
|
connectH2: "Connect — pick one of two ways",
|
|
320
|
-
m1Tag: "Way ① ·
|
|
321
|
-
m1Sub: "Best for
|
|
320
|
+
m1Tag: "Way ① · Install on your computer",
|
|
321
|
+
m1Sub: "Best when it's just for you and you want full control. Needs Node.js (a free program that runs commands).",
|
|
322
322
|
m1Steps: [
|
|
323
|
-
|
|
324
|
-
"<b>Open a terminal and
|
|
325
|
-
'<b>Follow the prompts:</b>
|
|
326
|
-
'<b>
|
|
323
|
+
"<b>Install Node.js</b> (free) if you don't have it — get it at <b>nodejs.org</b>. It's the program that lets your computer run the tool in the next step.",
|
|
324
|
+
"<b>Open a terminal</b> (the command window — “Terminal” on Mac, “Command Prompt” on Windows), paste the line below and press Enter:<pre>npx -y webcake-landing-mcp install</pre>",
|
|
325
|
+
'<b>Follow the prompts:</b> choose <code class="inl">prod</code> → sign in to Webcake in your browser → pick the AI app you use (Claude, Cursor…).',
|
|
326
|
+
'<b>Reopen your AI app.</b> When you see <code class="inl">webcake-landing</code> listed, you\'re connected.',
|
|
327
327
|
],
|
|
328
|
-
m1Note: "
|
|
329
|
-
m2Tag: "Way ② ·
|
|
330
|
-
m2Sub: "Best when you
|
|
328
|
+
m1Note: "Set it up for every app at once with this command:",
|
|
329
|
+
m2Tag: "Way ② · Use a link — nothing to install",
|
|
330
|
+
m2Sub: "Best when you can't install software, work in a team, or use Claude on the web (claude.ai).",
|
|
331
331
|
m2Steps: [
|
|
332
|
-
'<b>Get your personal link</b> (
|
|
333
|
-
'<b>
|
|
334
|
-
"<b>Paste the link</b> you copied (looks like):<pre>{ENDPOINT}?jwt=<
|
|
335
|
-
"<b>Hit Add</b> (or save the file)
|
|
332
|
+
'<b>Get your personal link</b> (your login is built in) — open the page below and hit <b>Copy</b>:<a class="btn" href="{REMOTE}">Open {REMOTE_HOST} {ARROW}</a>',
|
|
333
|
+
'<b>Go to where you add a connection</b> in your app:<br>• claude.ai: <i>Settings → Connectors → Add custom connector</i><br>• Cursor / Claude Code: open <code class="inl">.mcp.json</code>',
|
|
334
|
+
"<b>Paste the link</b> you just copied (it looks like):<pre>{ENDPOINT}?jwt=<YOUR CODE></pre>",
|
|
335
|
+
"<b>Hit Add</b> (or save the file) and wait a moment. When the Webcake icon turns green, you're good to go.",
|
|
336
336
|
],
|
|
337
|
-
m2Note: "⚠️ The link
|
|
338
|
-
afterH2: "Once connected,
|
|
337
|
+
m2Note: "⚠️ The link contains your personal login code — treat it like a password and never share it.",
|
|
338
|
+
afterH2: "Once connected, just say",
|
|
339
339
|
examples: [
|
|
340
340
|
{
|
|
341
341
|
icon: "wand",
|
|
342
|
-
t: '"Build a
|
|
342
|
+
t: '"Build a page to sell a course, green theme, with a sign-up form and a Zalo button."',
|
|
343
343
|
},
|
|
344
344
|
{
|
|
345
345
|
icon: "edit",
|
|
@@ -347,15 +347,15 @@ const T = {
|
|
|
347
347
|
},
|
|
348
348
|
{
|
|
349
349
|
icon: "clock",
|
|
350
|
-
t: '"Add a countdown
|
|
350
|
+
t: '"Add a countdown and 3 customer reviews to the bottom of the page."',
|
|
351
351
|
},
|
|
352
352
|
],
|
|
353
353
|
newH2: "What's new",
|
|
354
354
|
newBadge: "NEW",
|
|
355
|
-
clMore: "See
|
|
355
|
+
clMore: "See all changes",
|
|
356
356
|
faqH2: "FAQ",
|
|
357
|
-
starH2: "Find it useful?
|
|
358
|
-
starP: "It's
|
|
357
|
+
starH2: "Find it useful? Give the project a star",
|
|
358
|
+
starP: "It's a free, open-source project — every star is a little encouragement to keep it growing and helps more people find it.",
|
|
359
359
|
starBtn: "Star on GitHub",
|
|
360
360
|
footGuide: "Docs",
|
|
361
361
|
switchLabel: "Tiếng Việt",
|
|
@@ -456,7 +456,7 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
456
456
|
<html lang="${L}"><head>
|
|
457
457
|
<meta charset="utf-8">
|
|
458
458
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
459
|
-
<script>(function(){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>
|
|
459
|
+
<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>
|
|
460
460
|
<title>${m.title}</title>
|
|
461
461
|
<meta name="description" content="${m.desc}">
|
|
462
462
|
<meta name="keywords" content="${m.keywords}">
|
|
@@ -692,10 +692,14 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
692
692
|
.flow .wire .pkt{display:none}
|
|
693
693
|
}
|
|
694
694
|
@media(prefers-reduced-motion:no-preference){
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
695
|
+
/* One-shot reveal: JS adds .in the first time an element scrolls into view and
|
|
696
|
+
never removes it. Gated on .js so content is always visible without
|
|
697
|
+
JavaScript. We deliberately DON'T use a scroll-driven (animation-timeline)
|
|
698
|
+
reveal here — that ties opacity to scroll position and fades content back
|
|
699
|
+
out when you scroll up, which reads as the page flickering. */
|
|
700
|
+
.js .reveal{opacity:0;transform:translateY(24px);
|
|
701
|
+
transition:opacity .6s ease,transform .6s cubic-bezier(.2,.7,.2,1)}
|
|
702
|
+
.js .reveal.in{opacity:1;transform:none}
|
|
699
703
|
.hero-in{animation:rise2 .8s cubic-bezier(.2,.7,.2,1) both}
|
|
700
704
|
@keyframes rise2{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}
|
|
701
705
|
}
|
|
@@ -843,6 +847,20 @@ export function guideHtml(origin, lang = "vi") {
|
|
|
843
847
|
window.addEventListener('beforeunload',saveScroll);
|
|
844
848
|
window.addEventListener('pagehide',saveScroll);
|
|
845
849
|
|
|
850
|
+
// One-shot reveal-on-scroll. Each .reveal fades in once when it enters the
|
|
851
|
+
// viewport and then keeps its class forever — replacing the old scroll-driven
|
|
852
|
+
// CSS reveal that re-ran (and flickered) when scrolling back up. Falls back to
|
|
853
|
+
// showing everything if IntersectionObserver is missing.
|
|
854
|
+
var reveals=[].slice.call(document.querySelectorAll('.reveal'));
|
|
855
|
+
if(window.IntersectionObserver&&reveals.length){
|
|
856
|
+
var io=new IntersectionObserver(function(entries){
|
|
857
|
+
entries.forEach(function(en){if(en.isIntersecting){en.target.classList.add('in');io.unobserve(en.target);}});
|
|
858
|
+
},{rootMargin:'0px 0px -8% 0px'});
|
|
859
|
+
reveals.forEach(function(el){io.observe(el);});
|
|
860
|
+
}else{
|
|
861
|
+
reveals.forEach(function(el){el.classList.add('in');});
|
|
862
|
+
}
|
|
863
|
+
|
|
846
864
|
// Enable smooth scrolling only after the browser has restored scroll position
|
|
847
865
|
// on (re)load — applying it globally animates that restore into a jerky scroll.
|
|
848
866
|
window.addEventListener('load',function(){requestAnimationFrame(function(){html.classList.add('smooth');});});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-landing-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"description": "MCP server exposing Webcake landing-page element schemas + AI usage hints, and persisting LLM-generated page sources to a Webcake backend.",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/webcake-landing-mcp",
|
|
6
6
|
"type": "module",
|
|
@@ -14,10 +14,15 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=18"
|
|
16
16
|
},
|
|
17
|
+
"author": "vuluu2k (https://github.com/vuluu2k)",
|
|
18
|
+
"homepage": "https://mcp.toolvn.io.vn",
|
|
17
19
|
"repository": {
|
|
18
20
|
"type": "git",
|
|
19
21
|
"url": "git+https://github.com/vuluu2k/webcake-landing-mcp.git"
|
|
20
22
|
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/vuluu2k/webcake-landing-mcp/issues"
|
|
25
|
+
},
|
|
21
26
|
"scripts": {
|
|
22
27
|
"build": "node scripts/gen-changelog.mjs && tsc && node scripts/copy-assets.mjs",
|
|
23
28
|
"start": "node dist/index.js",
|