feat: 前端HESUPIPE品牌化+管道文案+GDPR提示

- 品牌色橙#f08a31→HESUPIPE蓝#066fc0(取自官网主题CSS a:hover/导航主色),7槽位色板三处同步:theme.js/widget.js内联副本/style.css :root
- chat.html/widget.js/js/chat.js:标题HESUPIPE Support、管道场景描述、欢迎语、热门话题Get Quotation/Pipe Specifications/Delivery & Shipping、WhatsApp+8615937108317、邮箱hesu@hesuu.com
- 邀请卡文案管道化(pipe specs, MOQ, delivery timing)+GDPR小字;聊天面板footer GDPR行(复用.chat-footer)
- admin.html VOLT→HESUPIPE;头像PIL重绘为品牌蓝底白H(512/128px)
- widget-test.html色值同步+断言基准更新(浏览器断言9/9 Δ=0);.gitignore排除.playwright-mcp/

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-29 18:57:49 +08:00
parent 47c27e5bd1
commit afee0dfa4d
11 changed files with 81 additions and 67 deletions
+1
View File
@@ -6,3 +6,4 @@ __pycache__/
data/ data/
.superpowers/ .superpowers/
*.db *.db
.playwright-mcp/
@@ -392,7 +392,7 @@ python -m pytest -q && git add -A && git commit -m "feat: 会话编排建材化+
**Files:** **Files:**
- Modify: `app/main.py`、`app/services/email_service.py`、`app/api/routes/chat_rest.py`、`app/services/rate_limit.py`、`app/config.py`(如有 DB 默认名) - Modify: `app/main.py`、`app/services/email_service.py`、`app/api/routes/chat_rest.py`、`app/services/rate_limit.py`、`app/config.py`(如有 DB 默认名)
- [ ] **Step 1: 逐文件更名** - [x] **Step 1: 逐文件更名**
| 文件 | 改动 | | 文件 | 改动 |
|---|---| |---|---|
@@ -402,14 +402,14 @@ python -m pytest -q && git add -A && git commit -m "feat: 会话编排建材化+
| `app/services/rate_limit.py` | docstring "Mirrors hesu's" 更新 | | `app/services/rate_limit.py` | docstring "Mirrors hesu's" 更新 |
| `app/config.py` + `.env.example` | DATABASE_URL 默认 `data/hesupipe_ai_kefu.db` | | `app/config.py` + `.env.example` | DATABASE_URL 默认 `data/hesupipe_ai_kefu.db` |
- [ ] **Step 2: 全局残留扫描** - [x] **Step 2: 全局残留扫描**
```bash ```bash
grep -rin "汽车\|vehicle\|byd\|toyota\|hesu auto\|borderlesscar\|VOLT\|XYH\|cc-ai-kefu\|cc_ai_kefu\|car\b\|cars\|什么车\|车型\|车价" app/ tests/ scripts/ --include="*.py" | grep -v "docs/" grep -rin "汽车\|vehicle\|byd\|toyota\|hesu auto\|borderlesscar\|VOLT\|XYH\|cc-ai-kefu\|cc_ai_kefu\|car\b\|cars\|什么车\|车型\|车价" app/ tests/ scripts/ --include="*.py" | grep -v "docs/"
``` ```
预期:无输出(docstring/注释里的行业残留一并清零;`car\b|cars|什么车|车型` 抓 boundary_filter 话术池等用户可见输出)。 预期:无输出(docstring/注释里的行业残留一并清零;`car\b|cars|什么车|车型` 抓 boundary_filter 话术池等用户可见输出)。
- [ ] **Step 3: 全量测试 + Commit** - [x] **Step 3: 全量测试 + Commit**
```bash ```bash
python -m pytest -q && git add -A && git commit -m "chore: 服务端品牌与行业残留更名清零" python -m pytest -q && git add -A && git commit -m "chore: 服务端品牌与行业残留更名清零"
@@ -422,7 +422,7 @@ python -m pytest -q && git add -A && git commit -m "chore: 服务端品牌与行
**Files:** **Files:**
- Modify: `docker-compose.yml`、`deploy.sh`、`openresty.conf`、`.env.example` - Modify: `docker-compose.yml`、`deploy.sh`、`openresty.conf`、`.env.example`
- [ ] **Step 1: 更名** - [x] **Step 1: 更名**
| 文件 | 改动 | | 文件 | 改动 |
|---|---| |---|---|
@@ -431,14 +431,14 @@ python -m pytest -q && git add -A && git commit -m "chore: 服务端品牌与行
| `openresty.conf` | upstream `cc_ai_kefu`→`hesupipe_ai_kefu`;127.0.0.1:8016→8017;alias `/www/cc_ai_kefu/`→`/www/hesupipe_ai_kefu/`;日志名同步(含注释掉的 HTTPS 段) | | `openresty.conf` | upstream `cc_ai_kefu`→`hesupipe_ai_kefu`;127.0.0.1:8016→8017;alias `/www/cc_ai_kefu/`→`/www/hesupipe_ai_kefu/`;日志名同步(含注释掉的 HTTPS 段) |
| `.env.example` | `APP_PORT=8017`;`PUSH_EXTERNAL_KEY_PREFIX=ai-kefu-jc`;`PUSH_ENABLED=false`(开发期) | | `.env.example` | `APP_PORT=8017`;`PUSH_EXTERNAL_KEY_PREFIX=ai-kefu-jc`;`PUSH_ENABLED=false`(开发期) |
- [ ] **Step 2: 残留扫描** - [x] **Step 2: 残留扫描**
```bash ```bash
grep -rn "cc_ai_kefu\|cc-ai-kefu\|8016" docker-compose.yml deploy.sh openresty.conf .env.example grep -rn "cc_ai_kefu\|cc-ai-kefu\|8016" docker-compose.yml deploy.sh openresty.conf .env.example
``` ```
预期:无输出。 预期:无输出。
- [ ] **Step 3: 测试 + Commit** - [x] **Step 3: 测试 + Commit**
```bash ```bash
python -m pytest -q && git add -A && git commit -m "chore: 部署配置更名(hs-ai-kefu/8017/hesupipe_ai_kefu)" python -m pytest -q && git add -A && git commit -m "chore: 部署配置更名(hs-ai-kefu/8017/hesupipe_ai_kefu)"
@@ -452,14 +452,14 @@ python -m pytest -q && git add -A && git commit -m "chore: 部署配置更名(hs
- Modify: `static/chat.html`、`static/widget.js`、`static/js/chat.js`、`static/js/theme.js`、`static/admin.html`、`static/widget-test.html` - Modify: `static/chat.html`、`static/widget.js`、`static/js/chat.js`、`static/js/theme.js`、`static/admin.html`、`static/widget-test.html`
- Test: `tests/test_invite_static.py`、`tests/test_theme_static.py` - Test: `tests/test_invite_static.py`、`tests/test_theme_static.py`
- [ ] **Step 1: 了解静态测试的真实性质(避免走错路)** - [x] **Step 1: 了解静态测试的真实性质(避免走错路)**
`tests/test_invite_static.py`/`test_theme_static.py` 是**结构回归网**,不是文案断言—— `tests/test_invite_static.py`/`test_theme_static.py` 是**结构回归网**,不是文案断言——
它们断言的是邀请卡面板出厂 hide、pointer-events、回流顺序、theme.js 与 widget.js 它们断言的是邀请卡面板出厂 hide、pointer-events、回流顺序、theme.js 与 widget.js
调色板逐字同步、无残留旧色 hex 等结构性质。所以本任务的测试策略是: 调色板逐字同步、无残留旧色 hex 等结构性质。所以本任务的测试策略是:
**直接改前端文件,然后跑结构回归测试防破坏**(它们会在你漏改某处色值/结构时指路)。 **直接改前端文件,然后跑结构回归测试防破坏**(它们会在你漏改某处色值/结构时指路)。
- [ ] **Step 2: 前端文案与配置** - [x] **Step 2: 前端文案与配置**
| 文件 | 改动 | | 文件 | 改动 |
|---|---| |---|---|
@@ -471,14 +471,14 @@ python -m pytest -q && git add -A && git commit -m "chore: 部署配置更名(hs
| `widget.js` | **GDPR 行**:首次对话邀请卡/首条消息附加 "By chatting, you agree to our privacy policy."(链接指向 hesupipe.com 隐私政策页,URL 实施时与需求方确认,可先 `https://hesupipe.com/privacy-policy/` 占位) | | `widget.js` | **GDPR 行**:首次对话邀请卡/首条消息附加 "By chatting, you agree to our privacy policy."(链接指向 hesupipe.com 隐私政策页,URL 实施时与需求方确认,可先 `https://hesupipe.com/privacy-policy/` 占位) |
| 头像 | `kefu.png`/`kefu-small.png` 替换(需求方提供 logo 或用品牌蓝底 "H" 字母临时生成) | | 头像 | `kefu.png`/`kefu-small.png` 替换(需求方提供 logo 或用品牌蓝底 "H" 字母临时生成) |
- [ ] **Step 3: 跑静态断言与全量测试至绿** - [x] **Step 3: 跑静态断言与全量测试至绿**
```bash ```bash
python -m pytest tests/test_invite_static.py tests/test_theme_static.py -q python -m pytest tests/test_invite_static.py tests/test_theme_static.py -q
python -m pytest -q python -m pytest -q
``` ```
- [ ] **Step 4: 残留扫描 + Commit** - [x] **Step 4: 残留扫描 + Commit**
```bash ```bash
grep -rin "HESU Support\|hesu auto\|vehicle\|VOLT\|borderlesscar\|1903717\|f08a31" static/ | grep -v blog grep -rin "HESU Support\|hesu auto\|vehicle\|VOLT\|borderlesscar\|1903717\|f08a31" static/ | grep -v blog
+5 -5
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VOLT - 管理后台</title> <title>HESUPIPE - 管理后台</title>
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f2f5; color: #333; height: 100vh; overflow: hidden; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f2f5; color: #333; height: 100vh; overflow: hidden; }
@@ -119,7 +119,7 @@
<div class="login-wrap" id="loginWrap"> <div class="login-wrap" id="loginWrap">
<div class="login-box"> <div class="login-box">
<h2>VOLT 管理后台</h2> <h2>HESUPIPE 管理后台</h2>
<input type="password" id="loginPwd" placeholder="请输入管理密码" autofocus> <input type="password" id="loginPwd" placeholder="请输入管理密码" autofocus>
<button onclick="doLogin()">登录</button> <button onclick="doLogin()">登录</button>
<div class="login-error" id="loginErr">密码错误</div> <div class="login-error" id="loginErr">密码错误</div>
@@ -128,7 +128,7 @@
<div class="app" id="app"> <div class="app" id="app">
<div class="header"> <div class="header">
<h1>VOLT - 沟通记录</h1> <h1>HESUPIPE - 沟通记录</h1>
<div class="header-right"> <div class="header-right">
<button class="btn-logs" id="btnLogs" onclick="toggleLogs()">推送日志</button> <button class="btn-logs" id="btnLogs" onclick="toggleLogs()">推送日志</button>
<select id="filterIntent" onchange="resetAndLoad()"> <select id="filterIntent" onchange="resetAndLoad()">
@@ -339,8 +339,8 @@ function selectConv(convId) {
var html = ''; var html = '';
d.messages.forEach(function(m) { d.messages.forEach(function(m) {
var cls = m.role === 'user' ? 'user' : 'assistant'; var cls = m.role === 'user' ? 'user' : 'assistant';
var avatar = m.role === 'user' ? '客' : 'V'; var avatar = m.role === 'user' ? '客' : 'H';
var roleLabel = m.role === 'user' ? '客户' : 'VOLT'; var roleLabel = m.role === 'user' ? '客户' : 'HESUPIPE';
html += '<div class="msg-row ' + cls + '">'; html += '<div class="msg-row ' + cls + '">';
html += '<div class="msg-avatar ' + cls + '">' + avatar + '</div>'; html += '<div class="msg-avatar ' + cls + '">' + avatar + '</div>';
+9 -7
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>VOLT</title> <title>HESUPIPE Support</title>
<link rel="stylesheet" href="/static/css/style.css"> <link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/theme.js"></script> <script src="/static/js/theme.js"></script>
</head> </head>
@@ -12,7 +12,7 @@
<div class="chat-header"> <div class="chat-header">
<div class="avatar"><span class="avatar-emoji">🤖</span><img class="avatar-img" alt="" src="/static/kefu-small.png"></div> <div class="avatar"><span class="avatar-emoji">🤖</span><img class="avatar-img" alt="" src="/static/kefu-small.png"></div>
<div class="info"> <div class="info">
<h1>HESU Support</h1> <h1>HESUPIPE Support</h1>
<div class="availability">Online now - Usually replies in minutes</div> <div class="availability">Online now - Usually replies in minutes</div>
</div> </div>
<!-- <div class="actions"> <!-- <div class="actions">
@@ -30,14 +30,14 @@
<div style="display:flex;align-items:center;gap:14px;"> <div style="display:flex;align-items:center;gap:14px;">
<div class="help-content"> <div class="help-content">
<div class="help-title">How can we help?</div> <div class="help-title">How can we help?</div>
<div class="help-description">Ask about vehicles, market support, quotation, shipping and delivery.</div> <div class="help-description">Ask about pipe specifications, quotations, MOQ, shipping and delivery.</div>
</div> </div>
<div class="help-actions"> <div class="help-actions">
<a href="https://wa.me/+8619037179250" target="_blank" class="call-btn whatsapp-btn"> <a href="https://wa.me/+8615937108317" target="_blank" class="call-btn whatsapp-btn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
<span>WhatsApp</span> <span>WhatsApp</span>
</a> </a>
<a href="mailto:info@borderlesscar.com" class="call-btn email-btn"> <a href="mailto:hesu@hesuu.com" class="call-btn email-btn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
<span>Email</span> <span>Email</span>
</a> </a>
@@ -48,7 +48,7 @@
<div class="chat-separator"></div> <div class="chat-separator"></div>
<div class="chat-messages" id="messages"> <div class="chat-messages" id="messages">
<div class="message assistant"> <div class="message assistant">
<div class="bubble">Hey there! Looking for a ride? Tell me what you need.</div> <div class="bubble">What kind of piping project are you working on?</div>
</div> </div>
</div> </div>
@@ -61,7 +61,8 @@
<div class="popular-topics-label">POPULAR TOPICS</div> <div class="popular-topics-label">POPULAR TOPICS</div>
<div class="popular-topics-grid"> <div class="popular-topics-grid">
<button class="popular-topic-btn" data-topic="Get Quotation">Get Quotation</button> <button class="popular-topic-btn" data-topic="Get Quotation">Get Quotation</button>
<button class="popular-topic-btn" data-topic="Distributor Cooperation">Distributor Cooperation</button> <button class="popular-topic-btn" data-topic="Pipe Specifications">Pipe Specifications</button>
<button class="popular-topic-btn" data-topic="Delivery & Shipping">Delivery &amp; Shipping</button>
</div> </div>
</div> </div>
<div class="chat-separator-topics"></div> <div class="chat-separator-topics"></div>
@@ -75,6 +76,7 @@
</button> </button>
</div> </div>
</div> </div>
<div class="chat-footer"><span class="footer-left">By chatting, you agree to our <a href="https://hesupipe.com/privacy-policy/" target="_blank" rel="noopener">privacy policy</a>.</span></div>
</div> </div>
<script src="/static/js/chat.js"></script> <script src="/static/js/chat.js"></script>
+12 -7
View File
@@ -5,13 +5,13 @@
} }
:root { :root {
--w-primary: #f08a31; --w-primary: #066fc0;
--w-primary-rgb: 240, 138, 49; --w-primary-rgb: 6, 111, 192;
--w-gradient-end: #f08e2c; --w-gradient-end: #0666bb;
--w-deep-text: #8a4c1d; --w-deep-text: #14517a;
--w-caption-text: #c16d2d; --w-caption-text: #2174ab;
--w-soft-bg: #fef8f4; --w-soft-bg: #f3f9fe;
--w-border-soft: #f0d3bd; --w-border-soft: #bbdcf2;
--bg: #f5f5f5; --bg: #f5f5f5;
--chat-bg: #ffffff; --chat-bg: #ffffff;
--ai-bubble: #f0f0f0; --ai-bubble: #f0f0f0;
@@ -457,6 +457,11 @@ html, body {
flex-shrink: 0; flex-shrink: 0;
} }
.chat-footer a {
color: var(--w-caption-text);
text-decoration: underline;
}
/* Mobile */ /* Mobile */
@media (max-width: 600px) { @media (max-width: 600px) {
.chat-container { .chat-container {
+2 -2
View File
@@ -29,8 +29,8 @@
(function () { (function () {
// 联系方式配置(与 widget.js 的 data-* 机制一致) // 联系方式配置(与 widget.js 的 data-* 机制一致)
var chatScript = document.currentScript || document.querySelector('script[src*="chat.js"]'); var chatScript = document.currentScript || document.querySelector('script[src*="chat.js"]');
var whatsapp = (chatScript && chatScript.getAttribute("data-whatsapp")) || "https://wa.me/+8619037179250"; var whatsapp = (chatScript && chatScript.getAttribute("data-whatsapp")) || "https://wa.me/+8615937108317";
var email = (chatScript && chatScript.getAttribute("data-email")) || "mailto:info@borderlesscar.com"; var email = (chatScript && chatScript.getAttribute("data-email")) || "mailto:hesu@hesuu.com";
var waBtn = document.querySelector('.whatsapp-btn'); var waBtn = document.querySelector('.whatsapp-btn');
var emBtn = document.querySelector('.email-btn'); var emBtn = document.querySelector('.email-btn');
if (waBtn) waBtn.href = whatsapp; if (waBtn) waBtn.href = whatsapp;
+8 -8
View File
@@ -4,16 +4,16 @@
(function (global) { (function (global) {
"use strict"; "use strict";
var DEFAULT_COLOR = "#f08a31"; var DEFAULT_COLOR = "#066fc0";
var DEFAULT_PALETTE = { var DEFAULT_PALETTE = {
primary: "#f08a31", primary: "#066fc0",
primaryRgb: "240,138,49", primaryRgb: "6,111,192",
gradientEnd: "#f08e2c", gradientEnd: "#0666bb",
deepText: "#8a4c1d", deepText: "#14517a",
captionText: "#c16d2d", captionText: "#2174ab",
softBg: "#fef8f4", softBg: "#f3f9fe",
borderSoft: "#f0d3bd" borderSoft: "#bbdcf2"
}; };
function clampNum(v, lo, hi) { function clampNum(v, lo, hi) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 904 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

+12 -12
View File
@@ -24,11 +24,11 @@
p { color: #666; line-height: 1.6; } p { color: #666; line-height: 1.6; }
ul { color: #666; } ul { color: #666; }
.test-list li { margin-bottom: 8px; } .test-list li { margin-bottom: 8px; }
.test-list li::before { content: "☐ "; color: #f08a31; font-weight: bold; } .test-list li::before { content: "☐ "; color: #066fc0; font-weight: bold; }
#assertions div { padding: 4px 0; border-bottom: 1px solid #eee; font-family: Consolas, monospace; font-size: 13px; } #assertions div { padding: 4px 0; border-bottom: 1px solid #eee; font-family: Consolas, monospace; font-size: 13px; }
#assertions div:last-child { border-bottom: none; } #assertions div:last-child { border-bottom: none; }
#colorPicker { width: 48px; height: 32px; padding: 0; border: 1px solid #ccc; border-radius: 4px; vertical-align: middle; cursor: pointer; } #colorPicker { width: 48px; height: 32px; padding: 0; border: 1px solid #ccc; border-radius: 4px; vertical-align: middle; cursor: pointer; }
.btn { padding: 8px 16px; background: #f08a31; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 6px; } .btn { padding: 8px 16px; background: #066fc0; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 6px; }
.btn:hover { opacity: 0.9; } .btn:hover { opacity: 0.9; }
</style> </style>
</head> </head>
@@ -39,11 +39,11 @@
<p><strong>Expected behavior:</strong></p> <p><strong>Expected behavior:</strong></p>
<ul class="test-list"> <ul class="test-list">
<li>Invite card appears after 5 seconds</li> <li>Invite card appears after 5 seconds</li>
<li>Invite card has orange gradient header with robot emoji</li> <li>Invite card has blue gradient header with robot emoji</li>
<li>Two topic buttons: "Get Quotation" and "Distributor Cooperation"</li> <li>Three topic buttons: "Get Quotation", "Pipe Specifications" and "Delivery &amp; Shipping"</li>
<li>Action buttons: "Start Chat" and "No Thanks"</li> <li>Action buttons: "Start Chat" and "No Thanks"</li>
<li>Click "Get Quotation" → invite closes → chat opens → message auto-sends</li> <li>Click "Get Quotation" → invite closes → chat opens → message auto-sends</li>
<li>Click "Distributor Cooperation" → invite closes → chat opens → message auto-sends</li> <li>Click "Pipe Specifications" → invite closes → chat opens → message auto-sends</li>
<li>Click "Start Chat" → invite closes → chat opens</li> <li>Click "Start Chat" → invite closes → chat opens</li>
<li>Click "No Thanks" → invite permanently closes</li> <li>Click "No Thanks" → invite permanently closes</li>
<li>Click "No Thanks" or X → refresh → invite stays hidden AND the old card area stays clickable (no invisible overlay; regression 2026-09-01)</li> <li>Click "No Thanks" or X → refresh → invite stays hidden AND the old card area stays clickable (no invisible overlay; regression 2026-09-01)</li>
@@ -64,9 +64,9 @@
<h2>Theme Color Preview</h2> <h2>Theme Color Preview</h2>
<p>Pick a color and Apply, or use a preset. Each click tears down the old widget (host + script + invite dismissal state) and re-embeds with <code>data-color</code>. "Test Broken Avatar" points <code>data-avatar</code> at a missing image to verify the 🤖 emoji fallback.</p> <p>Pick a color and Apply, or use a preset. Each click tears down the old widget (host + script + invite dismissal state) and re-embeds with <code>data-color</code>. "Test Broken Avatar" points <code>data-avatar</code> at a missing image to verify the 🤖 emoji fallback.</p>
<p> <p>
<input type="color" id="colorPicker" value="#f08a31"> <input type="color" id="colorPicker" value="#066fc0">
<button class="btn" onclick="applyColor()">Apply</button> <button class="btn" onclick="applyColor()">Apply</button>
<button class="btn" onclick="applyColor('#f08a31')">Orange</button> <button class="btn" onclick="applyColor('#066fc0')">Brand Blue</button>
<button class="btn" onclick="applyColor('#1a73e8')">Blue</button> <button class="btn" onclick="applyColor('#1a73e8')">Blue</button>
<button class="btn" onclick="applyColor('#16a34a')">Green</button> <button class="btn" onclick="applyColor('#16a34a')">Green</button>
<button class="btn" onclick="applyColor('#7c3aed')">Purple</button> <button class="btn" onclick="applyColor('#7c3aed')">Purple</button>
@@ -103,7 +103,7 @@
"data-mobile": "true", "data-mobile": "true",
"data-debug": "true", "data-debug": "true",
"data-whatsapp": "https://wa.me/8613800138000", "data-whatsapp": "https://wa.me/8613800138000",
"data-email": "mailto:demo@hesu-auto.com" "data-email": "mailto:demo@hesupipe.com"
}; };
var rebuildLoading = false, rebuildPending = null; var rebuildLoading = false, rebuildPending = null;
@@ -142,9 +142,9 @@
} }
/* ===== 断言面板(spec §8.2) ===== */ /* ===== 断言面板(spec §8.2) ===== */
var CURRENT_HARDCODED = { // 现状代表值(spec §4),Δ ≤ 8/通道 var CURRENT_HARDCODED = { // 现状代表值(HESUPIPE 品牌蓝 #066fc0 派生),Δ ≤ 8/通道
primary: "#f08a31", gradientEnd: "#F08A34", deepText: "#8C4D1D", primary: "#066fc0", gradientEnd: "#0666bb", deepText: "#14517a",
captionText: "#C16C2E", softBg: "#FFF8F3", borderSoft: "#F0D5BF" captionText: "#2174ab", softBg: "#f3f9fe", borderSoft: "#bbdcf2"
}; };
function hexToRgb6(h) { function hexToRgb6(h) {
h = String(h).replace(/^#/, ""); h = String(h).replace(/^#/, "");
@@ -161,7 +161,7 @@
if (!t || !d) { if (!t || !d) {
rows.push("❌ theme.js / widget debug 钩子未就绪"); rows.push("❌ theme.js / widget debug 钩子未就绪");
} else { } else {
var p = t.derivePalette("#f08a31"); var p = t.derivePalette("#066fc0");
Object.keys(CURRENT_HARDCODED).forEach(function (k) { Object.keys(CURRENT_HARDCODED).forEach(function (k) {
var delta = channelDelta(p[k], CURRENT_HARDCODED[k]); var delta = channelDelta(p[k], CURRENT_HARDCODED[k]);
check("default " + k + " Δ≤8", delta <= 8, p[k] + " vs " + CURRENT_HARDCODED[k] + ", Δ=" + delta); check("default " + k + " Δ≤8", delta <= 8, p[k] + " vs " + CURRENT_HARDCODED[k] + ", Δ=" + delta);
+22 -16
View File
@@ -7,25 +7,25 @@
// Save config immediately (before DOM ready) // Save config immediately (before DOM ready)
var position = script.getAttribute("data-position") || "bottom-right"; var position = script.getAttribute("data-position") || "bottom-right";
var theme = script.getAttribute("data-theme") || "light"; var theme = script.getAttribute("data-theme") || "light";
var title = script.getAttribute("data-title") || "HESU Support"; var title = script.getAttribute("data-title") || "HESUPIPE Support";
var autoOpen = script.getAttribute("data-auto-open") === "true"; var autoOpen = script.getAttribute("data-auto-open") === "true";
var whatsapp = script.getAttribute("data-whatsapp") || "https://wa.me/+8619037179250"; var whatsapp = script.getAttribute("data-whatsapp") || "https://wa.me/+8615937108317";
var email = script.getAttribute("data-email") || "mailto:info@borderlesscar.com"; var email = script.getAttribute("data-email") || "mailto:hesu@hesuu.com";
var autoInvite = script.getAttribute("data-auto-invite") !== "false"; var autoInvite = script.getAttribute("data-auto-invite") !== "false";
var isMobile = script.getAttribute("data-mobile") === "true"; var isMobile = script.getAttribute("data-mobile") === "true";
var wsBase = new URL(script.src).origin; var wsBase = new URL(script.src).origin;
/* ===== 主题派生单元:与 static/js/theme.js 逐字同步,tests/test_theme_static.py 强制;只可整块替换,不可单边修改 ===== */ /* ===== 主题派生单元:与 static/js/theme.js 逐字同步,tests/test_theme_static.py 强制;只可整块替换,不可单边修改 ===== */
var DEFAULT_COLOR = "#f08a31"; var DEFAULT_COLOR = "#066fc0";
var DEFAULT_PALETTE = { var DEFAULT_PALETTE = {
primary: "#f08a31", primary: "#066fc0",
primaryRgb: "240,138,49", primaryRgb: "6,111,192",
gradientEnd: "#f08e2c", gradientEnd: "#0666bb",
deepText: "#8a4c1d", deepText: "#14517a",
captionText: "#c16d2d", captionText: "#2174ab",
softBg: "#fef8f4", softBg: "#f3f9fe",
borderSoft: "#f0d3bd" borderSoft: "#bbdcf2"
}; };
function clampNum(v, lo, hi) { function clampNum(v, lo, hi) {
@@ -219,6 +219,7 @@
".chat-footer { padding:10px 10px; background:white; display:flex; align-items:center; justify-content:space-between; font-size:11px; color:var(--w-caption-text); gap:20px; }", ".chat-footer { padding:10px 10px; background:white; display:flex; align-items:center; justify-content:space-between; font-size:11px; color:var(--w-caption-text); gap:20px; }",
".chat-footer .footer-left { flex:1; }", ".chat-footer .footer-left { flex:1; }",
".chat-footer .footer-right { flex-shrink:0; }", ".chat-footer .footer-right { flex-shrink:0; }",
".chat-footer a { color:var(--w-caption-text); text-decoration:underline; }",
// Typing // Typing
".typing { display:none; align-self:flex-start; padding:8px 12px; background:" + c.bubble + "; border-radius:12px; }", ".typing { display:none; align-self:flex-start; padding:8px 12px; background:" + c.bubble + "; border-radius:12px; }",
@@ -275,6 +276,8 @@
".invite-start-btn:hover { opacity:0.95; }", ".invite-start-btn:hover { opacity:0.95; }",
".invite-dismiss-btn { border-radius:20px; border:1px solid var(--w-border-soft); background:white; padding:14px 20px; color:var(--w-deep-text); font-size:14px; font-weight:500; cursor:pointer; transition:background 0.2s; }", ".invite-dismiss-btn { border-radius:20px; border:1px solid var(--w-border-soft); background:white; padding:14px 20px; color:var(--w-deep-text); font-size:14px; font-weight:500; cursor:pointer; transition:background 0.2s; }",
".invite-dismiss-btn:hover { background:#FEFEFE; }", ".invite-dismiss-btn:hover { background:#FEFEFE; }",
".invite-privacy { padding:2px 16px 12px; font-size:11px; color:var(--w-caption-text); text-align:center; }",
".invite-privacy a { color:var(--w-caption-text); text-decoration:underline; }",
// Responsive // Responsive
"@media(max-width:450px){ .chat-panel{width:calc(100vw - 20px);height:calc(100vh - 200px);bottom:60px;right:0px;} .invite-panel{width:calc(100vw - 24px);} .invite-topics{grid-template-columns:1fr;} }", "@media(max-width:450px){ .chat-panel{width:calc(100vw - 20px);height:calc(100vh - 200px);bottom:60px;right:0px;} .invite-panel{width:calc(100vw - 24px);} .invite-topics{grid-template-columns:1fr;} }",
@@ -297,19 +300,20 @@
'</div></div>', '</div></div>',
'<div class="ended" id="w-ended">Chat ended. We\'ll be in touch!</div>', '<div class="ended" id="w-ended">Chat ended. We\'ll be in touch!</div>',
'<div class="panel-top">', '<div class="panel-top">',
'<div class="help-block"><div style="display:flex;align-items:center;gap:14px;"><div class="help-content"><div class="help-title">How can we help?</div><div class="help-description">Ask about vehicles, market support, quotation, shipping and delivery.</div></div><div class="help-actions"><a href="' + whatsapp + '" target="_blank" class="call-btn whatsapp-btn"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg><span>WhatsApp</span></a><a href="' + email + '" class="call-btn email-btn"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg><span>Email</span></a></div></div></div>', '<div class="help-block"><div style="display:flex;align-items:center;gap:14px;"><div class="help-content"><div class="help-title">How can we help?</div><div class="help-description">Ask about pipe specifications, quotations, MOQ, shipping and delivery.</div></div><div class="help-actions"><a href="' + whatsapp + '" target="_blank" class="call-btn whatsapp-btn"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg><span>WhatsApp</span></a><a href="' + email + '" class="call-btn email-btn"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg><span>Email</span></a></div></div></div>',
'</div>', '</div>',
'<div class="chat-separator"></div>', '<div class="chat-separator"></div>',
'<div class="panel-messages" id="w-msgs">', '<div class="panel-messages" id="w-msgs">',
'<div class="msg-wrapper"><div class="msg ai">Hey! What kind of car are you looking for?</div></div>', '<div class="msg-wrapper"><div class="msg ai">What kind of piping project are you working on?</div></div>',
'</div>', '</div>',
'<div class="typing" id="w-typing"><span></span><span></span><span></span></div>', '<div class="typing" id="w-typing"><span></span><span></span><span></span></div>',
'<div class="popular-topics-fixed" id="w-popular-topics"><div class="popular-topics-label">POPULAR TOPICS</div><div class="popular-topics-grid"><button class="popular-topic-btn" data-topic="Get Quotation">Get Quotation</button><button class="popular-topic-btn" data-topic="Distributor Cooperation">Distributor Cooperation</button></div></div>', '<div class="popular-topics-fixed" id="w-popular-topics"><div class="popular-topics-label">POPULAR TOPICS</div><div class="popular-topics-grid"><button class="popular-topic-btn" data-topic="Get Quotation">Get Quotation</button><button class="popular-topic-btn" data-topic="Pipe Specifications">Pipe Specifications</button><button class="popular-topic-btn" data-topic="Delivery &amp; Shipping">Delivery &amp; Shipping</button></div></div>',
'<div class="chat-separator-topics"></div>', '<div class="chat-separator-topics"></div>',
'<div class="input-container"><div class="input-wrapper">', '<div class="input-container"><div class="input-wrapper">',
'<input type="text" id="w-input" placeholder="Type a message...">', '<input type="text" id="w-input" placeholder="Type a message...">',
'<button id="w-send"><span style="font-size:16px;">➤</span><span>Send</span></button>', '<button id="w-send"><span style="font-size:16px;">➤</span><span>Send</span></button>',
'</div></div>', '</div></div>',
'<div class="chat-footer"><span class="footer-left">By chatting, you agree to our <a href="https://hesupipe.com/privacy-policy/" target="_blank" rel="noopener">privacy policy</a>.</span></div>',
].join(""); ].join("");
// Invite card HTML template // Invite card HTML template
@@ -320,7 +324,7 @@
'<div class="invite-icon"><span class="avatar-emoji">🤖</span><img class="avatar-img" alt="" src="'+avatarUrl+'"><span class="invite-status-dot"></span></div>', '<div class="invite-icon"><span class="avatar-emoji">🤖</span><img class="avatar-img" alt="" src="'+avatarUrl+'"><span class="invite-status-dot"></span></div>',
'</div>', '</div>',
'<div class="invite-header-content">', '<div class="invite-header-content">',
'<div class="invite-title">Chat with us for available vehicles, delivery timing and distributor cooperation.</div>', '<div class="invite-title">Chat with us for pipe specs, MOQ, delivery timing and quotations.</div>',
'</div>', '</div>',
'<div class="invite-header-actions">', '<div class="invite-header-actions">',
'<button class="invite-close-btn" aria-label="Close">', '<button class="invite-close-btn" aria-label="Close">',
@@ -339,7 +343,8 @@
'<div class="invite-body">', '<div class="invite-body">',
'<div class="invite-topics">', '<div class="invite-topics">',
'<button class="invite-topic-btn" data-topic="Get Quotation">Get Quotation</button>', '<button class="invite-topic-btn" data-topic="Get Quotation">Get Quotation</button>',
'<button class="invite-topic-btn" data-topic="Distributor Cooperation">Distributor Cooperation</button>', '<button class="invite-topic-btn" data-topic="Pipe Specifications">Pipe Specifications</button>',
'<button class="invite-topic-btn" data-topic="Delivery &amp; Shipping">Delivery &amp; Shipping</button>',
'</div>', '</div>',
'</div>', '</div>',
'<div class="chat-separator"></div>', '<div class="chat-separator"></div>',
@@ -350,6 +355,7 @@
'</button>', '</button>',
'<button class="invite-dismiss-btn">No Thanks</button>', '<button class="invite-dismiss-btn">No Thanks</button>',
'</div>', '</div>',
'<div class="invite-privacy">By chatting, you agree to our <a href="https://hesupipe.com/privacy-policy/" target="_blank" rel="noopener">privacy policy</a>.</div>',
'</div>', '</div>',
'</div>' '</div>'
].join(""); ].join("");