结果: 200/200 (100%) — Token: WKX-9245FA70-200 日期: 2026年3月24日
LinkedIn 上的一篇帖子宣布了 Wix KickstartX 的一项特别挑战——一个面向初级开发者的项目。 规则如下:
wix-kickstartx-challenge-2026.base44.app/ 和 wixkickstart.com主站点 wixkickstart.com 是 Wix 托管的站点(由 Wix 的 Thunderbolt 引擎服务端渲染)。
标准 curl 只返回 JavaScript 外壳——没有任何实际内容。
我们使用了 headless Chromium 来渲染它:
chromium --headless --disable-gpu --no-sandbox --virtual-time-budget=10000 \
--dump-dom "https://wixkickstart.com" > /tmp/wix_rendered.html
从渲染后的 DOM 中,我们提取出了一个嵌入的 iframe:
这是一个 p5.js 粒子动画(首屏部分的视觉特效),并不是挑战本身。 真正的挑战位于 Base44 应用中。
挑战运行在 Base44 上——一个低代码应用平台。该应用是一个 单页 React 应用,所有逻辑都在一个 JS bundle 中:
https://wix-kickstartx-challenge-2026.base44.app/assets/index-oTG160r9.js
大小: 410,809 字节(压缩后的 React + 应用逻辑 + Base44 SDK)
通过 grep 这个 JS bundle,我们绘制出了整个 API 的全貌:
数据实体: GameSession, Participant
API URL 模式:
POST /api/apps/{appId}/functions/{functionName}
App ID: 69aea07cbcb9a3dd1039a58d
const or = 1800; // 时间限制:1800 秒(30 分钟)
const rm = 200; // 项目总数:200 对图片-描述
一个关键发现——该应用有 JSON 批量提交模式。来自压缩后的源码:
function Ik({onSubmit:r, onClose:n}) {
// ...
h = JSON.parse(s) // 解析 JSON 输入
// 验证:必须是类似 { "IMG-001": "DESC-042", ... } 的对象
r(h) // 一次性提交所有匹配
}
模态框中的占位文本:
{
"IMG-001": "DESC-042",
"IMG-002": "DESC-017",
...
}
这意味着我们不需要在 UI 中点击 200 次——我们可以通过编程方式提交一个包含 全部 200 个匹配的 JSON 映射。
Base44 SDK 使用 JWT 认证:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT payload 包含:
{
"sub": "[email protected]",
"exp": 1782162460,
"iat": 1774386460
}
所有 API 调用所需的 headers:
Content-Type: application/json
Authorization: Bearer {jwt_token}
X-App-Id: 69aea07cbcb9a3dd1039a58d
Base44-Functions-Version: prod
X-Origin-URL: https://wix-kickstartx-challenge-2026.base44.app/
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/startGame
Status: 200
Transferred: 62.87 kB compressed (2.20 MB decompressed)
Content-Encoding: br (Brotli)
响应结构:
{
"status": "active",
"sessionId": "69c2fd218e5b26f307c941c9",
"startedAt": "2026-03-24T21:07:45.101Z",
"imageOrder": ["IMG-154", "IMG-102", ...], // 200 项(显示顺序)
"descOrder": ["DESC-125", "DESC-109", ...], // 200 项(显示顺序)
"imagesData": { "IMG-001": "data:image/svg+xml;base64,...", ... }, // 200 个 SVG
"descriptionsData": { "DESC-105": "milky field, overlaid with...", ... } // 200 段文本
}
每张图片是一个 200x200 的 SVG,包含:
解码后的 SVG 示例(IMG-001):
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<defs>
<linearGradient id="bg" ...>
<stop offset="0%" stop-color="#f5f0eb"/>
<stop offset="100%" stop-color="#e8e0d5"/>
</linearGradient>
</defs>
<rect width="200" height="200" fill="url(#bg)"/>
<!-- 可选的叠加线条/圆点/圆环在此 -->
<polygon points="..." fill="#E2725B" transform="translate(128.6,185.7) rotate(90)"
style="opacity:0.5;filter:drop-shadow(...)"/>
<!-- 更多形状... -->
</svg>
每条描述都遵循严格的模式:
{背景} field[, overlaid with {叠加}]. {N} elements total:
{大小} {不透明度} {颜色} {形状} ({旋转}, at {位置}) ·
{大小} {不透明度} {颜色} {形状} ({旋转}, at {位置}) · ...
示例:
milky field, overlaid with tilted cross-lines. 10 elements total:
minuscule nearly solid dim gray pike (steeply angled, at center inner-left) ·
substantial solid azure pike (diagonal, at top center) · ...
我们需要将 SVG 属性映射到描述词汇。这需要分析全部 200 张图片和全部 200 条描述, 以找到精确的对应关系。
通过统计两侧的出现次数,我们建立了 1:1 的映射:
方法: 统计全部 200 个 SVG 中唯一的渐变起始颜色,统计全部 200 条描述中唯一的 首单词,按次数进行匹配。
SVG 叠加图案根据类型使用不同的实现方式:
关键洞察: 背景图案元素将 opacity 用作 XML 属性(例如,opacity="0.06"),
而前景形状将 opacity 放在 style 属性中(例如,style="opacity:0.7")。
这一区别对于将背景图案与实际形状区分开来至关重要。
全部 200 个 SVG 恰好使用 28 种唯一的十六进制填充颜色。全部 200 条描述恰好使用 28 种 唯一的颜色名称。
基于形状顶点到原点的最大半径:
| SVG 不透明度值 | 描述名称 |
|---|---|
| 1.0 | solid |
| 0.85 | nearly solid |
| 0.7 | semi-transparent |
| 0.5 |
形状放置在 7x7 的网格上,坐标为:
X 和 Y 方向均为 [14.3, 42.9, 71.4, 100.0, 128.6, 157.1, 185.7]。
这些坐标映射到列名:far-left, left, inner-left, center, inner-right, right, far-right
以及行名:top, upper, upper-mid, center, lower-mid, lower, bottom
对于 200 张 SVG 图片中的每一张:
<linearGradient id="bg"> 中的第一个 <stop> 颜色<line>、<circle fill="none" stroke>、低不透明度圆点/形状<polygon>、<circle>、<path>、<rect> 元素,这些元素需要:
width="200")fill="url(#vig)")opacity="0.0x")style="opacity:..."(所有前景形状都有)fill 属性,通过 COLOR_MAP 映射对于 200 条文本描述中的每一条:
(\d+) elements total(minuscule|modest|mid-sized|substantial|massive)
(solid|nearly solid|semi-transparent|faint)
(颜色名称)
(形状类型)
((旋转), at (位置))
匹配算法使用贪心评分方法:
def score_match(img, desc):
# 硬约束 — 必须完全匹配
if img['num'] != desc['num']: return -10000 # 形状数量
if img['bg'] != desc['bg']: return -10000 # 背景类型
if img['overlay'] != desc['overlay']: return -10000 # 叠加类型
score = 100 # 硬约束匹配的基础分
# 软评分 — 颜色重叠
for color in img_colors:
if color in desc_colors:
score += 10
# 软评分 — 类型重叠
for type in img_types:
if type in desc_types:
score += 8
# 逐形状细节匹配(匈牙利式贪心)
for each image_shape:
find best matching desc_shape by:
+20 if color matches
+15 if type matches
+10 if size matches
+8 if opacity matches
+5 if rotation matches
score += best_match_score
return score
然后先贪心地分配得分最高的配对:
all_scores.sort(reverse=True)
for score, img_id, desc_id in all_scores:
if img_id not in matched and desc_id not in used:
matches[img_id] = desc_id
1. 开始游戏
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/startGame
Body: {"email": "[email protected]"}
Response: 200 OK (2.2 MB — 所有游戏数据)
2. 提交游戏评分
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/scoreGame
Body: {
"matches": {"IMG-032": "DESC-182", "IMG-198": "DESC-134", ...},
"sessionId": "69c2fd218e5b26f307c941c9"
}
Response: 200 OK
{"correctCount": 200, "scoreSeal": "3d1ac8a7e7be4856"}
3. 生成 Token
POST /api/apps/69aea07cbcb9a3dd1039a58d/functions/generateToken
Body: {"sessionId": "69c2fd218e5b26f307c941c9"}
Response: 200 OK
{"token": "WKX-9245FA70-200", "score": 200}
| 步骤 | 操作 |
|---|---|
| 21:07:45 UTC | 游戏开始(调用 startGame) |
| 21:07–21:15 | 求解器脚本开发(解析 + 匹配) |
| ~21:15 | 提交 scoreGame — 200/200 正确 |
| ~21:15 | 调用 generateToken — |
总求解时间:在 30 分钟的窗口内约 8 分钟。
JSON 提交端点是关键推动因素——没有它,我们就需要浏览器自动化来点击 400 次(选择图片 + 选择描述 x 200)。
基于计数的映射是颜色、背景和叠加图案匹配的突破点。
我们不是猜测十六进制 #cd7f32 在英语中对应什么,而是统计出它在 SVG 中出现了
65 次,而 "brass" 在描述中出现了 65 次——唯一计数 = 保证匹配。
**硬约束能快速排除候选。**每张图片都有独特的 (背景类型, 叠加类型, 形状数量) 组合。有 12 种背景 x 7 种叠加 x 不同的形状数量,大多数图片只有少数几个可能的 描述匹配,而不是 200 个。
**SVG 是结构化数据。**与光栅图像(PNG/JPG)不同,SVG 是 XML——每个形状、 颜色、位置和旋转都被显式编码为文本。不需要计算机视觉。
**叠加图案检测很棘手。**背景图案使用了 5 种不同的 SVG 技术:
<line> 元素、仅描边的 <circle>、具有低不透明度属性的 <circle>、
具有低不透明度属性的 <polygon>,以及以上全部都没有的情况。关键区别在于
opacity 是 XML 属性(背景)还是在 style 内(前景形状)。
**颜色词汇并不直观。**像 "oxblood" (#800020)、"brass" (#cd7f32) 和
"deep sapphire" (#0f52ba) 这样的名称需要计数方法——仅凭十六进制值
你无法可靠地猜出来。
从 SVG 路径进行形状分类需要理解 SVG 路径命令:
M(moveto)、L(lineto)、A(arc)、Z(closepath)fill-rule="evenodd" 的同心圆弧路径| 函数 | 用途 |
|---|
startGame | 创建一个游戏会话,返回 200 张图片 + 200 条描述 |
scoreGame | 接受 {matches: {}, sessionId: ""},返回 {correctCount: N} |
generateToken | 接受 sessionId,返回完成 token |
getLeaderboard | 返回最高分 |
saveNickname | 保存排行榜上的显示名称 |
| SVG 渐变起始颜色 | 次数 | 描述单词 | 次数 |
|---|
#0a1628 | 28 | pitch | 28 |
#eef2f7 | 20 | frosted | 20 |
#0d0d0d | 19 | tenebrous | 19 |
#fef9f0 | 18 | pearlescent | 18 |
#1e0a2e | 18 | nocturnal | 18 |
#0a1a0a | 17 | midnight | 17 |
#1a0a0a | 15 | inky | 15 |
#f5f0eb | 14 | milky | 14 |
#f7f3ee | 14 | ethereal | 14 |
#f0f0f0 | 14 | radiant | 14 |
#f0f7f4 | 13 | glowing | 13 |
#1a1a2e | 10 | somber | 10 |
| SVG 图案 | 检测方法 | 次数 | 描述名称 | 次数 |
|---|
<line> 元素,水平(dy=0) | 检查 x1,y1,x2,y2 | 35 | striped overlay | 35 |
<line> 元素,垂直(dx=0) | 检查 x1,y1,x2,y2 | 34 | lattice pattern | 34 |
带 opacity="0.06" 的 <circle>(圆点) | 统计低不透明度圆形 | 30 | stippled layer | 30 |
| 完全没有叠加元素 | 无线条、无背景形状 | 28 | (无) | 28 |
<line> 元素,对角线 | 检查斜率方向 | 27 | tilted cross-lines | 27 |
带 opacity="0.06" 的小型 <polygon>/<path> | 具有低不透明度属性的形状 | 25 | arrow-band texture | 25 |
带 fill="none" stroke="#888" 的 <circle> | 仅描边的圆形 | 21 | ringed pattern | 21 |
| Hex | 描述名称 | Hex | 描述名称 |
|---|
#708090 | blue-gray | #cd7f32 | brass | |
#f5f5f5 | near white | #dc143c | fiery red | |
#00bcd4 | electric cyan | #2196f3 | azure | |
#ff6b6b | salmon pink | #228b22 | rich green | |
#98ff98 | pale green | #b0b0b0 | platinum | |
#0f52ba | deep sapphire | #800020 | oxblood | |
#ff8c00 | deep orange | #4b0082 | dark purple | |
#e2725b | terra rosa | #ffbf00 | marigold | |
#b7410e | russet | #ff69b4 | candy pink | |
#ffd700 | bright gold | #c0c0c0 | tin | |
#6b8e23 | moss | #008080 | deep teal | |
#40e0d0 | pale teal | #0047ab | royal blue | |
#e34234 | burnt sienna | #a0522d | deep red | |
#4a4a4a | dim gray | #36454f | dark gray |
| SVG 元素 | 检测逻辑 | 描述名称 |
|---|
<polygon> 3 个顶点 | 统计空格分隔的点对 | pike |
<polygon> 4 个顶点 | tilted square | |
<polygon> 5 个顶点 | quint form | |
<polygon> 6 个顶点 | bee cell | |
<polygon> 10 个顶点 | asterisk | |
<rect>(非背景) | 有 width/height,但不是 200x200 | tilted square |
<circle> | 标签名 | disc |
带 fill-rule="evenodd" 的 <path> | 两条同心圆弧路径 | donut |
带单条弧线 + Z 的 <path> | 半圆路径 | half-disc |
带 6+ 个 L 命令的 <path> | 十字/加号形状 | crosshair |
带 3-5 个 L 命令的 <path> | 箭头状形状 | pointer |
| 半径范围 | 描述名称 |
|---|
| 0–12 | minuscule |
| 13–16 | modest |
| 17–20 | mid-sized |
| 21–25 | substantial |
| 26+ | massive |
faint| SVG rotate() 值 | 描述名称 |
|---|
| 0° | upright |
| 1–20° | slightly tilted |
| 21–55° | diagonal |
| 56–75° | steeply angled |
| 76–105° | sideways |
style="opacity:X"transform="... rotate(X)"transform="translate(X,Y) ..."| 组件 | 技术 |
|---|
| 挑战平台 | Base44(低代码应用构建器) |
| 前端 | React SPA(单个 JS bundle,约 410KB) |
| 后端 | Python/uvicorn,位于 Cloudflare 之后 |
| CDN/代理 | Cloudflare(HTTP/3,Brotli 压缩) |
| 认证 | JWT(HS256),存储在 localStorage 中 |
| 数据格式 | SVG(base64 内联)、JSON API |
| 实时 | WebSocket(socket.io),用于实时会话更新 |
| 求解器 | Python 3(正则解析,无外部库) |