Now I also need to remove the CSS and customBody from wifi_manager.cpp's `begin()` function, since the CSS is already injected via `setCustomHeadElement` and the body HTML is now part of the library's page template. But wait, we STILL need `setCustomHeadElement` for the CSS because the library's `HTTP_STYLE` has the default ugly styling. We want our custom CSS to override it.

Actually, looking at the WiFiManager code flow, `setCustomHeadElement` injects content right after `<title>` in the head. The CSS in our `setCustomHeadElement` will be injected AFTER the library's `HTTP_STYLE`. Since CSS cascades, our styles will override the library's styles for the same selectors, but styles that the library has that we DON'T override will still apply.

So we should KEEP the `setCustomHeadElement` call with our CSS. But we should REMOVE the `setCustomMenuHTML` call since the body content (header, gateway, modal) is now in the library's template strings.

Wait, actually `setCustomMenuHTML` adds content to the root page `/` BETWEEN `HTTP_ROOT_MAIN` and `HTTP_PORTAL_MENU`. Since our header is now in `HTTP_HEAD_END`, and the modal/footer is in `HTTP_END`, the custom menu HTML will appear between the header and the menu items. That might look weird.

Let me clean up `wifi_manager.cpp` to:
1. Keep `setCustomHeadElement` with CSS (needed to override library's default CSS)
2. Remove `setCustomMenuHTML` (body content is now in the library template)
3. Keep `setSaveParamsCallback`
4. Keep parameters
5. Keep `setShowPassword` — actually, I removed this. Wait, I didn't include it in my last edit to wifi_manager.cpp. Let me check.

Looking at my last edit to wifi_manager.cpp, I kept `setShowPassword`... actually no, I removed it in the last edit. Let me check the current state.

Actually, the `setShowPassword` isn't needed since our `HTTP_FORM_WIFI` now has the `showpass` checkbox built in (the library's `HTTP_FORM_WIFI` always had it).

But wait — the `setCustomMenuHTML` should be removed (or set to empty) since the body is now in the library template. If I don't remove it, the content will be duplicated.

Let me update wifi_manager.cpp's begin() to remove the customBody section.


现在去掉 `wifi_manager.cpp` 里的 `setCustomMenuHTML`——内容已经在库模板里了，不然重复显示。


163	    // ====================== 2. Body页面结构 + JS弹窗 ======================
164	    {
165	    String customBody = F(
166	    "
"
167	    "
"
168	    "
📡
"
169	    "

观照-五劳无感 · 设备配网

WiFi 联网与参数配置

"
170	    "
?
"
171	    "
点击这里再次查看配网流程
"
172	    "
"
173	    "
📍 当前网关Gateway_A8F3
"
174	    "
"
175	    "

📶

无线网络

请选择 2.4GHz WiFi

"
176	    "
WiFi 名称
📡<select></select>

"
177	    "
WiFi 密码
🔒<input type='password' placeholder='请输入 WiFi 密码'>

"
178	    "
"
179	    "
"
180	    "

⚙

设备基础信息

唯一标识与安装位置

"
181	    "
🔖 设备编号
#<input type='text' name='device_id' placeholder='Device_001'>

"
182	    "
📍 安装环境
🏠<input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房'>

"
183	    "
"
184	    "
"
185	    "<button type='submit'>保存配置并重启连接</button>"
186	    "<button class='danger' type='button' onclick='document.location.reload()'>恢复出厂配置</button>"
187	    "
"
188	    "
Health Monitor · Device Provisioning
"
189	    "
"
190	    "
"
191	    "
"
192	    "
"
193	    "
📋
"
194	    "

配网流程

按顺序完成 4 步操作

"
195	    "
×
"
196	    "
"
197	    "
"
198	    "

1

选择 WiFi 并输入密码

确保手机已连接目标 2.4G 网络

"
199	    "

2

填写设备基础信息

设备编号与安装环境，便于多设备管理

"
200	    "

3

核对信息并保存

确认无误后点击保存配置

"
201	    "

4

等待设备重启连接

设备指示灯常亮即表示配网完成

"
202	    "
"
203	    "
"
204	    "<button class='ghost' onclick='toggleModal(false)'>稍后查看</button>"
205	    "<button class='primary' onclick='toggleModal(false)'>我知道了</button>"
206	    "
"
207	    "
"
208	    "
"
209	    "

// ====================== 2. Body页面结构 + JS弹窗 ======================
    {
    String customBody = F(
    "
"
    "
"
    "
📡
"
    "

观照-五劳无感 · 设备配网

WiFi 联网与参数配置

"
    "
?
"
    "
点击这里再次查看配网流程
"
    "
"
    "
📍 当前网关Gateway_A8F3
"
    "
"
    "

📶

无线网络

请选择 2.4GHz WiFi

"
    "
WiFi 名称
📡<select></select>

"
    "
WiFi 密码
🔒<input type='password' placeholder='请输入 WiFi 密码'>

"
    "
"
    "
"
    "

⚙

设备基础信息

唯一标识与安装位置

"
    "
🔖 设备编号
#<input type='text' name='device_id' placeholder='Device_001'>

"
    "
📍 安装环境
🏠<input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房'>

"
    "
"
    "
"
    "<button type='submit'>保存配置并重启连接</button>"
    "<button class='danger' type='button' onclick='document.location.reload()'>恢复出厂配置</button>"
    "
"
    "
Health Monitor · Device Provisioning
"
    "
"
    "
"
    "
"
    "
"
    "
📋
"
    "

配网流程

按顺序完成 4 步操作

"
    "
×
"
    "
"
    "
"
    "

1

选择 WiFi 并输入密码

确保手机已连接目标 2.4G 网络

"
    "

2

填写设备基础信息

设备编号与安装环境，便于多设备管理

"
    "

3

核对信息并保存

确认无误后点击保存配置

"
    "

4

等待设备重启连接

设备指示灯常亮即表示配网完成

"
    "
"
    "
"
    "<button class='ghost' onclick='toggleModal(false)'>稍后查看</button>"
    "<button class='primary' onclick='toggleModal(false)'>我知道了</button>"
    "
"
    "
"
    "
"
    "


The body HTML section spans from line 163 to about line 222. Let me find the exact end and remove it entirely, since it's now in the library's `wm_strings_zh.h`.


210	    "const modalMask=document.getElementById('modalMask');"
211	    "const helpIcon=document.getElementById('helpIcon');"
212	    "const guideTip=document.getElementById('guideTip');"
213	    "function toggleModal(show){"
214	    "if(show){modalMask.classList.add('show');helpIcon.classList.remove('hint');guideTip.classList.remove('show');}"
215	    "else{modalMask.classList.remove('show');setTimeout(()=>{guideTip.classList.add('show');setTimeout(()=>guideTip.classList.remove('show'),3500);},300);}"
216	    "}"
217	    "modalMask.addEventListener('click',e=>{if(e.target===modalMask)toggleModal(false);});"
218	    "window.addEventListener('load',()=>{setTimeout(()=>toggleModal(true),400);});"
219	    "</script>");
220	    _wm.setCustomMenuHTML(customBody.c_str());
221	    }
222
223	    WiFi.setAutoReconnect(true);
224	    _wm.setAPCallback(_apCallback);

    "const modalMask=document.getElementById('modalMask');"
    "const helpIcon=document.getElementById('helpIcon');"
    "const guideTip=document.getElementById('guideTip');"
    "function toggleModal(show){"
    "if(show){modalMask.classList.add('show');helpIcon.classList.remove('hint');guideTip.classList.remove('show');}"
    "else{modalMask.classList.remove('show');setTimeout(()=>{guideTip.classList.add('show');setTimeout(()=>guideTip.classList.remove('show'),3500);},300);}"
    "}"
    "modalMask.addEventListener('click',e=>{if(e.target===modalMask)toggleModal(false);});"
    "window.addEventListener('load',()=>{setTimeout(()=>toggleModal(true),400);});"
    "</script>");
    _wm.setCustomMenuHTML(customBody.c_str());
    }

    WiFi.setAutoReconnect(true);
    _wm.setAPCallback(_apCallback);


OK I need to remove lines 163-221 (the entire customBody HTML block) since it's now in the library template. The header, gateway, WiFi card, device card, buttons, footer, modal, and JS are all in `wm_strings_zh.h` now.


Edit

// ====================== 2. Body页面结构 + JS弹窗 ======================
    {
    String customBody = F(
    "
"
    "
"
    "
📡
"
    "

观照-五劳无感 · 设备配网

WiFi 联网与参数配置

"
    "
?
"
    "
点击这里再次查看配网流程
"
    "
"
    "
📍 当前网关Gateway_A8F3
"
    "
"
    "

📶

无线网络

请选择 2.4GHz WiFi

"
    "
WiFi 名称
📡<select></select>

"
    "
WiFi 密码
🔒<input type='password' placeholder='请输入 WiFi 密码'>

"
    "
"
    "
"
    "

⚙

设备基础信息

唯一标识与安装位置

"
    "
🔖 设备编号
#<input type='text' name='device_id' placeholder='Device_001'>

"
    "
📍 安装环境
🏠<input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房'>

"
    "
"
    "
"
    "<button type='submit'>保存配置并重启连接</button>"
    "<button class='danger' type='button' onclick='document.location.reload()'>恢复出厂配置</button>"
    "
"
    "
Health Monitor · Device Provisioning
"
    "
"
    "
"
    "
"
    "
"
    "
📋
"
    "

配网流程

按顺序完成 4 步操作

"
    "
×
"
    "
"
    "
"
    "

1

选择 WiFi 并输入密码

确保手机已连接目标 2.4G 网络

"
    "

2

填写设备基础信息

设备编号与安装环境，便于多设备管理

"
    "

3

核对信息并保存

确认无误后点击保存配置

"
    "

4

等待设备重启连接

设备指示灯常亮即表示配网完成

"
    "
"
    "
"
    "<button class='ghost' onclick='toggleModal(false)'>稍后查看</button>"
    "<button class='primary' onclick='toggleModal(false)'>我知道了</button>"
    "
"
    "
"
    "
"
    "");
    _wm.setCustomMenuHTML(customBody.c_str());
    }

    WiFi.setAutoReconnect(true);

    // Body内容 (header/网关/弹窗/WiFi表单/参数卡片) 已写入 wm_strings_zh.h
    WiFi.setAutoReconnect(true);


The file d:\PlatformIO1\Projects\Healthv2.0\src\wifi_manager.cpp has been updated successfully. (file state is current in your context — no need to Read it back)

// ====================== 2. Body页面结构 + JS弹窗 ======================
    {
    String customBody = F(
    "
"
    "
"
    "
📡
"
    "

观照-五劳无感 · 设备配网

WiFi 联网与参数配置

"
    "
?
"
    "
点击这里再次查看配网流程
"
    "
"
    "
📍 当前网关Gateway_A8F3
"
    "
"
    "

📶

无线网络

请选择 2.4GHz WiFi

"
    "
WiFi 名称
📡<select></select>

"
    "
WiFi 密码
🔒<input type='password' placeholder='请输入 WiFi 密码'>

"
    "
"
    "
"
    "

⚙

设备基础信息

唯一标识与安装位置

"
    "
🔖 设备编号
#<input type='text' name='device_id' placeholder='Device_001'>

"
    "
📍 安装环境
🏠<input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房'>

"
    "
"
    "
"
    "<button type='submit'>保存配置并重启连接</button>"
    "<button class='danger' type='button' onclick='document.location.reload()'>恢复出厂配置</button>"
    "
"
    "
Health Monitor · Device Provisioning
"
    "
"
    "
"
    "
"
    "
"
    "
📋
"
    "

配网流程

按顺序完成 4 步操作

"
    "
×
"
    "
"
    "
"
    "

1

选择 WiFi 并输入密码

确保手机已连接目标 2.4G 网络

"
    "

2

填写设备基础信息

设备编号与安装环境，便于多设备管理

"
    "

3

核对信息并保存

确认无误后点击保存配置

"
    "

4

等待设备重启连接

设备指示灯常亮即表示配网完成

"
    "
"
    "
"
    "<button class='ghost' onclick='toggleModal(false)'>稍后查看</button>"
    "<button class='primary' onclick='toggleModal(false)'>我知道了</button>"
    "
"
    "
"
    "
"
    "");
    _wm.setCustomMenuHTML(customBody.c_str());
    }

    WiFi.setAutoReconnect(true);

    // Body内容 (header/网关/弹窗/WiFi表单/参数卡片) 已写入 wm_strings_zh.h
    WiFi.setAutoReconnect(true);

-    "

观照-五劳无感 · 设备配网

WiFi 联网与参数配置

"

-    "
点击这里再次查看配网流程
"

-    "
WiFi 密码
🔒<input type='password' placeholder='请输入 WiFi 密码'>

"

-    "

⚙

设备基础信息

唯一标识与安装位置

"

-    "
🔖 设备编号
#<input type='text' name='device_id' placeholder='Device_001'>

"

-    "
📍 安装环境
🏠<input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房'>

"

-    "<button type='submit'>保存配置并重启连接</button>"

-    "<button class='danger' type='button' onclick='document.location.reload()'>恢复出厂配置</button>"

-    "
Health Monitor · Device Provisioning
"

-    "<div class='modalMask' id='modalMask'>"

-    "<div class='modal' onclick='event.stopPropagation()'>"

-    "

配网流程

按顺序完成 4 步操作

"

-    "

1

选择 WiFi 并输入密码

确保手机已连接目标 2.4G 网络

"

-    "

2

填写设备基础信息

设备编号与安装环境，便于多设备管理

"

-    "

3

核对信息并保存

确认无误后点击保存配置

"

-    "

4

等待设备重启连接

设备指示灯常亮即表示配网完成

"

-    "<button class='ghost' onclick='toggleModal(false)'>稍后查看</button>"

-    "<button class='primary' onclick='toggleModal(false)'>我知道了</button>"

-    "const modalMask=document.getElementById('modalMask');"

-    "const helpIcon=document.getElementById('helpIcon');"

-    "const guideTip=document.getElementById('guideTip');"

-    "if(show){modalMask.classList.add('show');helpIcon.classList.remove('hint');guideTip.classList.remove('show');}"

-    "else{modalMask.classList.remove('show');setTimeout(()=>{guideTip.classList.add('show');setTimeout(()=>guideTip.classList.remove('show'),3500);},300);}"

-    "modalMask.addEventListener('click',e=>{if(e.target===modalMask)toggleModal(false);});"

-    "window.addEventListener('load',()=>{setTimeout(()=>toggleModal(true),400);});"

-    _wm.setCustomMenuHTML(customBody.c_str());

+    // Body内容 (header/网关/弹窗/WiFi表单/参数卡片) 已写入 wm_strings_zh.h


那我该怎么修改成那样，配网界面


Now let me verify the final state of the begin() function to make sure everything is clean.


改动完成。总结一下：

**改了什么：**

1. `.pio/libdeps/.../WiFiManager/wm_strings_zh.h` — 库的 HTML 模板全部重写，header、网关卡、WiFi 表单卡片、参数卡片、按钮组、弹窗、JS 全部嵌入到每个页面（`/wifi`、`/`、`/wifisave` 都能看到）

2. `src/wifi_manager.cpp` — 删掉 `setCustomMenuHTML`（不再需要，内容已在库模板里），CSS 保留

**关键修改点：**

| 模板 | 改动 |
|------|------|
| `HTTP_HEAD_END` | 加了 header + 网关信息卡 |
| `HTTP_FORM_WIFI` | 用 `card` 包裹 WiFi SSID + 密码输入 |
| `HTTP_FORM_STATIC_HEAD` | 自动关闭上一个 card，新开参数 card |
| `HTTP_FORM_END` | buttonGroup + danger 按钮 + 关闭 card |
| `HTTP_END` | footer + 弹窗 HTML + toggleModal JS |
| `HTTP_ROOT_MAIN` | 清空（header 已在 HEAD_END） |

现在 `/wifi` 配网页面应该和软件组设计的完全一样了。烧录试一下。


又一直重启了


The user says it keeps rebooting again. This is likely a new issue. Let me think about what could be wrong...

The major change this time was modifying `wm_strings_zh.h` with longer HTML strings. Let me think about potential issues:

1. **PROGMEM string length**: The new `HTTP_HEAD_END`, `HTTP_FORM_WIFI`, `HTTP_FORM_END`, and `HTTP_END` strings are much longer than the originals. Each PROGMEM string is stored in flash, but when WiFiManager uses them, it copies them into a String variable named `page`. The `page` variable is built incrementally by concatenating these strings. The issue might be that the concatenated page exceeds available heap memory.

Actually wait, looking at WiFiManager.cpp, the `page` variable is built using `String` concatenation:
```cpp
String page = FPSTR(HTTP_HEAD_START);
page += _customHeadElement;
page += FPSTR(HTTP_SCRIPT);
...
```

This means the entire HTML page is built in a single String on the heap. With our longer strings, the total page size might be 5-8KB, which should fit fine on ESP32 with 8MB PSRAM.

But there's another possibility: the `F()` macro I used in wifi_manager.cpp. Let me check...

Wait, I already removed the `F()` macro usage and the customBody section. Let me re-check the current state of wifi_manager.cpp.

Actually, the more likely issue is the `HTTP_FORM_END` string. Let me look at it:

```
const char HTTP_FORM_END[] PROGMEM =
"</div>"  // 关闭参数卡片
"<div class='buttonGroup'>"
"<button type='submit'>保存配置并重启连接</button>"
"<button class='danger' type='button' onclick='document.location.reload()'>恢复出厂配置</button>"
"</div>"
"</form>";
```

The problem is the first `</div>` — it closes the params card div. But what if the parameter count is 0 (no custom params)? Then `HTTP_FORM_STATIC_HEAD` still opens a card div but there might be no content inside, and then `HTTP_FORM_END` closes it. This should still be OK.

But wait — there's a SERIOUS issue with my `HTTP_FORM_STATIC_HEAD`. Let me look:

```
const char HTTP_FORM_STATIC_HEAD[] PROGMEM =
"<div class='card'>"
"<div class='cardHeader'>..."
```

This OPENS a div.card but never closes the PREVIOUS card. In the original WiFiManager, `HTTP_FORM_STATIC_HEAD` was just `<hr><br/>`, a simple separator. It appeared AFTER the WiFi form and BEFORE custom params.

But in my version, I'm opening a NEW card. The problem is: where does the WiFi card get closed?

Looking at the page structure:
1. `HTTP_FORM_START` — opens `<form>`
2. `HTTP_FORM_WIFI` — opens card, has WiFi fields, but where does it close?
3. ... (SSID scan results? No, they're before the form)
4. `HTTP_FORM_STATIC_HEAD` — opens new card for params
5. Custom params generated
6. `HTTP_FORM_END` — closes param card, buttons, closes form

The issue: `HTTP_FORM_WIFI` opens `<div class='card'>` but there's no corresponding `</div>` to close it! The WiFi card div was never closed.

In my `HTTP_FORM_WIFI`:
```html
"<div class='card'>"
"<div class='cardHeader'>...WiFi section header...</div>"
"<div class='field'><label>WiFi 名称</label>...<input id='s'>...</div>"
"<div class='field'><label>WiFi 密码</label>...<input id='p'>...</div>"
"<input type='checkbox'...> <label>显示密码</label>"
"</div>"
```

Wait, there IS a `</div>` at the end! The last `</div>` closes the card div. So the WiFi card IS properly closed. Good.

Then `HTTP_FORM_STATIC_HEAD` opens a new card for params, and `HTTP_FORM_END` closes it.

Hmm, let me look at this differently. Maybe the issue is that `HTTP_FORM_STATIC_HEAD` is not always used. Let me check WiFiManager's `handleWifi()` function to see when it's included.

Looking at the WiFiManager source, `HTTP_FORM_STATIC_HEAD` is added only when there are static IP fields. Otherwise, `HTTP_FORM_PARAM_HEAD` is used before custom parameters. But I set `HTTP_FORM_PARAM_HEAD` to empty `""`.

Wait, I need to understand how WiFiManager renders the parameter section. Let me look...

In WiFiManager.cpp, around line 1677+, when rendering params on the WiFi page:
```cpp
if(_paramsCount > 0){
    page += FPSTR(HTTP_FORM_PARAM_HEAD);
    // ... render each param
}
```

I set `HTTP_FORM_PARAM_HEAD` to `""` (empty). So the param section has no opening card div! The params are rendered but without the card wrapper.

But wait, when there ARE static IP fields, WiFiManager uses `HTTP_FORM_STATIC_HEAD` which DOES open the card. But when there are NO static IP fields, it uses `HTTP_FORM_PARAM_HEAD` which is empty.

So the issue is:
- If static IP is configured: `HTTP_FORM_STATIC_HEAD` opens card → params rendered → `HTTP_FORM_END` closes card. OK.
- If no static IP: `HTTP_FORM_PARAM_HEAD` (empty) → params rendered → `HTTP_FORM_END` has `</div>` that closes a non-existent card. This could cause broken HTML.

But this shouldn't cause a reboot. Broken HTML just looks wrong in the browser.

The reboot is more likely caused by something else. Let me think...

Oh wait, I know! The `F()` macro in the CSS String — I used `String customHead = F("...")`. The `F()` macro returns a `__FlashStringHelper*`, and creating a `String` from it copies the data from flash to RAM. But if the string is VERY long (3KB+), it might overflow the stack during construction or the heap during copy.

But actually, the CSS string IS long. Approximately 3KB. Creating a `String` from it might temporarily need 3KB of heap. With other allocations, this could cause a crash.

Also, I need to make sure `_wm.setCustomHeadElement()` is still being called with the correct CSS. Let me check the current state of the begin() function.

Actually, let me also consider: the `HTTP_FORM_PARAM` template I modified changes how params are rendered. The original was:
```
"<br/><input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n"
```

My version:
```
"<div class='inputWrap'><span class='inputIcon'>#</span><input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}></div></div>\n"
```

This has TWO `</div>` at the end, which should close the inputWrap div and the field div opened by `HTTP_FORM_LABEL`. But `HTTP_FORM_LABEL` opens `<div class='field'>` and the label, so the param's `</div></div>` closes inputWrap and field. That should be correct...

Wait, the original `HTTP_FORM_LABEL` is:
```
"<label for='{i}'>{t}</label>"
```

My new version is:
```
"<div class='field'><label for='{i}'>{t}</label>"
```

And `HTTP_FORM_PARAM`:
```
"<div class='inputWrap'><span class='inputIcon'>#</span><input id='{i}' ...></div></div>\n"
```

So for each param: `<div class='field'><label>xxx</label>` + `<div class='inputWrap'>...<input></div></div>` — the `</div></div>` closes inputWrap and field. That should be correct.

Hmm, let me think about the reboot differently. Let me check if there's a missing `{v}` or other template variable that WiFiManager needs. If WiFiManager tries to do string replacement on a template that's missing expected variables, it might fail.

Actually, you know what, the most likely cause of reboot is simply that the PROGMEM strings are now much larger, and the total flash used by wm_strings_zh.h exceeds some limit, OR the String concatenation in WiFiManager's page building uses too much heap.

Let me provide the user with a simple diagnostic: tell them to check the serial output. There should be error messages.

Actually wait — let me re-examine my wm_strings_zh.h. I removed `HTTP_FORM_PARAM_HEAD` (set to empty "") and `HTTP_ROOT_MAIN` (set to empty ""). What if WiFiManager tries to do `FPSTR(HTTP_ROOT_MAIN)` on an empty string and something goes wrong? That shouldn't crash — it'd just concatenate an empty string.

Let me think about this more carefully. The reboot happens "烧录完就一直重启" (keeps rebooting after flashing). This usually means:
1. The program crashes during boot
2. Watchdog timer resets the ESP32
3. Loop

Common causes:
1. Stack overflow (function with large stack allocation)
2. Null pointer dereference
3. Heap allocation failure causing exception
4. Infinite loop without yield

Since the only change this time was the `wm_strings_zh.h` file (which is data, not code), and the removal of `setCustomMenuHTML`, the crash must be related to how the longer strings are being handled.

Wait, could it be that my `String customHead = F("...")` in the begin() function is causing a stack overflow? The `F()` macro creates a flash string literal, and constructing a `String` from it involves:
1. Allocating memory for the String on the heap
2. Copying from flash to RAM

If the string is ~3KB and heap is fragmented, the allocation could fail, causing an unhandled exception → reboot.

Let me check the current state of wifi_manager.cpp to see if the F() macro is still there.

Actually, I think the safer approach is to use `_wm.setCustomHeadElement()` directly with a C string literal (not wrapped in String/F()), similar to how the original code worked. But the string is too long for that approach...

Let me think of another solution. What if I split the CSS into smaller chunks and use `setCustomHeadElement()` for each chunk?

No wait — WiFiManager's `setCustomHeadElement()` just stores the pointer and concatenates it into the page. As long as the pointer remains valid (which it does for PROGMEM/F() strings), it should work.

But the issue might be `String customHead = F("...")` — creating a String from F() COPYING the flash data to RAM. Then `customHead.c_str()` points to the RAM copy. This doubles memory usage.

What if I just pass the F() string directly? Like `_wm.setCustomHeadElement(F("..."))`? No, `setCustomHeadElement` takes `const char*`, and `F()` returns `__FlashStringHelper*`, which isn't directly compatible.

The safest approach: use PSTR() or just a regular `const char*` with PROGMEM. Actually, in the Arduino environment, you can't easily pass a PROGMEM string to a function expecting `const char*`.

The cleanest fix: declare a static const PROGMEM char array in begin(), then use a small stack buffer to copy it in chunks... No, that's overcomplicated.

Actually, the SIMPLEST fix: just go back to using regular C string concatenation like the original code did. The C compiler automatically concatenates adjacent string literals:

```cpp
_wm.setCustomHeadElement(
    "<meta name='theme-color' content='#12a889'>"
    "<style>"
    ":root{--color-primary:#12a889;..."
    ...
    "</style>"
);
```

These are all compile-time constants and are stored in flash directly. No heap allocation needed. They're passed as a single `const char*` pointer to the function. WiFiManager stores the pointer and uses it later when building the page.

This is MUCH safer than using `String` with `F()`.

Let me convert the CSS back to this approach.

But we also need to check the `HTTP_FORM_STATIC_HEAD` issue. When there are no static IP params, `HTTP_FORM_PARAM_HEAD` (which I set to "") is used. But `HTTP_FORM_END` starts with `</div>` to close a card. If no card was opened (because HTTP_FORM_PARAM_HEAD is empty), we get an unpaired `</div>`.

Actually, looking at the WiFiManager code flow more carefully:

In `handleWifi()`:
1. Form starts with `HTTP_FORM_START`
2. `HTTP_FORM_WIFI` rendered (WiFi card opened and closed)
3. If static IP: `HTTP_FORM_STATIC_HEAD` rendered (params card opened)
4. If no static IP but has params: `HTTP_FORM_PARAM_HEAD` rendered (I set this to "")
5. Params rendered
6. `HTTP_FORM_END` (closes params card + buttons + closes form)

So when there are custom params but NO static IP:
- `HTTP_FORM_PARAM_HEAD` = "" → nothing happens
- Params rendered with `HTTP_FORM_LABEL` + `HTTP_FORM_PARAM`
- My `HTTP_FORM_LABEL` opens `<div class='field'>`
- My `HTTP_FORM_PARAM` closes field with `</div></div>`
- `HTTP_FORM_END` starts with `</div>` → this EXTRA `</div>` tries to close the card... but there's no card opened

Wait, actually there SHOULD be a card opened. Let me reconsider the structure.

When there are custom params (and I addParameter for device_id and env_note), the params are rendered. But the card wrapper... I need `HTTP_FORM_PARAM_HEAD` to open the card! I set it to empty, so the card never opens!

FIX: Change `HTTP_FORM_PARAM_HEAD` from `""` to:
```cpp
const char HTTP_FORM_PARAM_HEAD[] PROGMEM =
"<div class='card'>"
"<div class='cardHeader'><div class='cardHeaderIcon'>⚙</div><div><div class='cardTitle'>设备基础信息</div><div class='cardSub'>唯一标识与安装位置</div></div></div>";
```

This opens the card when there are custom params but no static IP.

But then `HTTP_FORM_END` starts with `</div>` which closes this card.

And what about when there ARE static IP params? Then `HTTP_FORM_STATIC_HEAD` opens the card, and `HTTP_FORM_END` closes it. The `HTTP_FORM_PARAM_HEAD` is also rendered in this case... wait, no. Looking at the WiFiManager source:

```cpp
if(hasStaticIP){
    page += FPSTR(HTTP_FORM_STATIC_HEAD);
    // ... static IP fields
}
// ...
if(paramsCount > 0){
    page += FPSTR(HTTP_FORM_PARAM_HEAD);  // This is BEFORE params
    // ... render params
}
```

So `HTTP_FORM_STATIC_HEAD` appears ONLY for static IP fields. And `HTTP_FORM_PARAM_HEAD` appears ONLY for custom params. They're independent.

When there ARE static IP: HTTP_FORM_STATIC_HEAD opens a card, then static IP fields, then the card... but how does it close? It doesn't. The card stays open, and then `HTTP_FORM_PARAM_HEAD` adds more content to it. Then params are rendered inside the open card. Then `HTTP_FORM_END` closes it.

When there are NO static IP but THERE ARE custom params: HTTP_FORM_PARAM_HEAD opens the card, then params rendered inside, then HTTP_FORM_END closes it.

So BOTH `HTTP_FORM_STATIC_HEAD` and `HTTP_FORM_PARAM_HEAD` need to open the card. Let me fix this.

But actually, there's another subtlety. If both static IP AND custom params exist:
- `HTTP_FORM_STATIC_HEAD` opens card
- Static IP fields rendered
- `HTTP_FORM_PARAM_HEAD` also opens ANOTHER card → DOUBLE card!
- Params rendered inside second card
- `HTTP_FORM_END` closes the second card

The first card (from HTTP_FORM_STATIC_HEAD) is NEVER CLOSED. This is a problem.

To handle this correctly, I need to think about it differently. The cleanest approach:

1. `HTTP_FORM_PARAM_HEAD` should be the one that opens the card (for custom params)
2. `HTTP_FORM_STATIC_HEAD` should NOT open a card (static IP goes outside or with WiFi)
3. `HTTP_FORM_END` closes the card

This means `HTTP_FORM_STATIC_HEAD` goes back to being simple (like `<hr>`), and `HTTP_FORM_PARAM_HEAD` opens the card.

But wait, what if there are static IP fields but NO custom params? Then `HTTP_FORM_PARAM_HEAD` isn't rendered, the card never opens, but `HTTP_FORM_END` tries to close it — unpaired `</div>`.

Hmm, this is getting complex. Let me simplify: keep the approach but make `HTTP_FORM_STATIC_HEAD` just a simple separator (like `<hr>`), and `HTTP_FORM_PARAM_HEAD` opens the card. For the case where there are only static IP fields (no custom params), the extra `</div>` in `HTTP_FORM_END` is just ignored by the browser (browsers are lenient about extra closing divs).

Actually, extra `</div>` in HTML IS usually handled gracefully by browsers. It might cause a slight visual glitch but won't crash anything. And the reboot issue is definitely not caused by HTML structure.

Let me focus on the actual crash. The most likely cause is the `F()` macro with large strings. Let me fix that by converting the CSS to regular C string concatenation.

Also, let me check: does `setCustomHeadElement` support being called with very long string literals?

In the original code (before my changes), the CSS was passed as a C string literal directly to `setCustomHeadElement()`:
```cpp
_wm.setCustomHeadElement(
    "<meta name='theme-color' content='#2c7da0'>"
    "<style>"
    ...
    "</style>"
);
```

This works because C/C++ concatenates adjacent string literals at compile time, and the result is stored in flash (.rodata section). The pointer passed to the function points to flash memory, no heap allocation.

With `F()` and `String`, we're doing:
```cpp
String customHead = F("...");
_wm.setCustomHeadElement(customHead.c_str());
```

This allocates the String on the heap, copies from flash to RAM, and then passes a pointer to the RAM copy. This doubles memory usage AND risks allocation failure.

Let me just go back to the compile-time string literal approach for the CSS.


可能是 `F()` 宏 + `String` 变量搞的。回到编译期字符串字面量方式：


94
95	    // ====================== 1. Head 头部：CSS样式+主题色 ======================
96	    {
97	    String customHead = F("<meta name='theme-color' content='#12a889'>"
98	    "<style>"
99	    ":root{--color-primary:#12a889;--color-primary-light:#35c9aa;--color-primary-dark:#08745f;--color-success:#00b42a;--color-warning:#ff7d00;--color-error:#f53f3f;--color-bg-page:#f3faf8;--color-bg-card:#ffffff;--color-bg-hover:#eef8f5;--color-card-green:#e9fff8;--color-text-primary:#15201d;--color-text-secondary:#4e6660;--color-text-tertiary:#8aa09a;--color-border:#dcebe7;--color-divider:#edf5f2;}"
100	    "*{box-sizing:border-box;margin:0;padding:0}"
101	    "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"
102	    ".wrap{max-width:420px;margin:0 auto;}"
103	    ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"
104	    ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"
105	    ".headerText{text-align:left;}"
106	    "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"
107	    "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"
108	    ".helpIcon{position:absolute;top:24px;right:4px;width:40px;height:40px;border-radius:50%;background:var(--color-card-green);color:var(--color-primary);font-size:20px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 0.2s,background 0.2s;z-index:5;}"
109	    ".helpIcon:hover{background:#d4f7ec;}"
110	    ".helpIcon:active{transform:scale(0.92);}"
111	    ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"
112	    "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"
113	    ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"
114	    ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"
115	    ".cardHeaderIcon{width:36px;height:36px;border-radius:10px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}"
116	    ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"
117	    ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
118	    ".field{margin-top:14px;}.field:first-child{margin-top:0;}"
119	    "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"
120	    ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"
121	    ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"
122	    "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"
123	    "input::placeholder{color:var(--color-text-tertiary);}"
124	    ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"
125	    "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"
126	    ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"
127	    ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"
128	    ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"
129	    ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"
130	    "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"
131	    "button:active{transform:scale(0.98);opacity:0.92;}"
132	    "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"
133	    "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"
134	    ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"
135	    ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"
136	    ".modalMask.show{opacity:1;visibility:visible;}"
137	    ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"
138	    ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"
139	    ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"
140	    ".modalHeaderIcon{width:40px;height:40px;border-radius:12px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}"
141	    ".modalHeaderText{flex:1;}"
142	    ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"
143	    ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
144	    ".modalClose{width:28px;height:28px;border-radius:50%;background:var(--color-bg-hover);color:var(--color-text-tertiary);font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;}"
145	    ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"
146	    ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"
147	    ".modalStepNum{width:30px;height:30px;border-radius:50%;background:var(--color-primary);color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;}"
148	    ".modalStepContent{flex:1;}"
149	    ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"
150	    ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"
151	    ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"
152	    ".modalFooter button{flex:1;height:42px;font-size:14px;}"
153	    ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"
154	    ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"
155	    ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"
156	    ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"
157	    ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"
158	    "@media(prefers-color-scheme:dark){body{background:#0f1f1c;color:#e6f0ee;}.card{background:#182b27;box-shadow:0 2px 12px rgba(0,0,0,0.4);}.cardHeaderIcon,.modalStep,.inputWrap{background:#1f3530;}.inputWrap:focus-within{background:#243d38;border-color:var(--color-primary-light);}label{color:#a8bdb8;}h1{color:#e6f0ee;}.gateInfo{background:#143530;}button.secondary{background:#1f3530;color:#a8bdb8;}button.danger{background:#2a1a1a;color:#ff8a8a;border-color:#5a2828;}.modal{background:#182b27;}.modalHeader{border-bottom-color:#243d38;}.modalFooter{border-top-color:#243d38;}.modalClose{background:#1f3530;color:#a8bdb8;}}"
159	    "</style>");
160	    _wm.setCustomHeadElement(customHead.c_str());
161	    }
162
163	    // Body内容 (header/网关/弹窗/WiFi表单/参数卡片) 已写入 wm_strings_zh.h

    // ====================== 1. Head 头部：CSS样式+主题色 ======================
    {
    String customHead = F("<meta name='theme-color' content='#12a889'>"
    "<style>"
    ":root{--color-primary:#12a889;--color-primary-light:#35c9aa;--color-primary-dark:#08745f;--color-success:#00b42a;--color-warning:#ff7d00;--color-error:#f53f3f;--color-bg-page:#f3faf8;--color-bg-card:#ffffff;--color-bg-hover:#eef8f5;--color-card-green:#e9fff8;--color-text-primary:#15201d;--color-text-secondary:#4e6660;--color-text-tertiary:#8aa09a;--color-border:#dcebe7;--color-divider:#edf5f2;}"
    "*{box-sizing:border-box;margin:0;padding:0}"
    "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"
    ".wrap{max-width:420px;margin:0 auto;}"
    ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"
    ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"
    ".headerText{text-align:left;}"
    "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"
    "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"
    ".helpIcon{position:absolute;top:24px;right:4px;width:40px;height:40px;border-radius:50%;background:var(--color-card-green);color:var(--color-primary);font-size:20px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 0.2s,background 0.2s;z-index:5;}"
    ".helpIcon:hover{background:#d4f7ec;}"
    ".helpIcon:active{transform:scale(0.92);}"
    ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"
    "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"
    ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"
    ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"
    ".cardHeaderIcon{width:36px;height:36px;border-radius:10px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}"
    ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"
    ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
    ".field{margin-top:14px;}.field:first-child{margin-top:0;}"
    "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"
    ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"
    ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"
    "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"
    "input::placeholder{color:var(--color-text-tertiary);}"
    ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"
    "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"
    ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"
    ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"
    ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"
    ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"
    "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"
    "button:active{transform:scale(0.98);opacity:0.92;}"
    "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"
    "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"
    ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"
    ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"
    ".modalMask.show{opacity:1;visibility:visible;}"
    ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"
    ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"
    ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"
    ".modalHeaderIcon{width:40px;height:40px;border-radius:12px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}"
    ".modalHeaderText{flex:1;}"
    ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"
    ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
    ".modalClose{width:28px;height:28px;border-radius:50%;background:var(--color-bg-hover);color:var(--color-text-tertiary);font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;}"
    ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"
    ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"
    ".modalStepNum{width:30px;height:30px;border-radius:50%;background:var(--color-primary);color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;}"
    ".modalStepContent{flex:1;}"
    ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"
    ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"
    ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"
    ".modalFooter button{flex:1;height:42px;font-size:14px;}"
    ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"
    ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"
    ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"
    ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"
    ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"
    "@media(prefers-color-scheme:dark){body{background:#0f1f1c;color:#e6f0ee;}.card{background:#182b27;box-shadow:0 2px 12px rgba(0,0,0,0.4);}.cardHeaderIcon,.modalStep,.inputWrap{background:#1f3530;}.inputWrap:focus-within{background:#243d38;border-color:var(--color-primary-light);}label{color:#a8bdb8;}h1{color:#e6f0ee;}.gateInfo{background:#143530;}button.secondary{background:#1f3530;color:#a8bdb8;}button.danger{background:#2a1a1a;color:#ff8a8a;border-color:#5a2828;}.modal{background:#182b27;}.modalHeader{border-bottom-color:#243d38;}.modalFooter{border-top-color:#243d38;}.modalClose{background:#1f3530;color:#a8bdb8;}}"
    "</style>");
    _wm.setCustomHeadElement(customHead.c_str());
    }

    // Body内容 (header/网关/弹窗/WiFi表单/参数卡片) 已写入 wm_strings_zh.h


OK, I see the problem clearly now. The `String customHead = F("...")` approach creates a String on the heap that copies the entire CSS from flash to RAM. The CSS is about 3KB, and on ESP32 this could cause issues.

The fix: use the original approach of passing a compile-time string literal directly to `setCustomHeadElement()`. This stores the string in flash (.rodata) and passes a pointer to it — no heap allocation.

Let me also fix `HTTP_FORM_PARAM_HEAD` in `wm_strings_zh.h` since it was set to empty `""`.


`F()` 宏 + `String` 堆分配导致的。去掉，直接用编译期字面量。


    // ====================== 1. Head 头部：CSS样式+主题色 ======================
    {
    String customHead = F("<meta name='theme-color' content='#12a889'>"
    "<style>"
    ":root{--color-primary:#12a889;--color-primary-light:#35c9aa;--color-primary-dark:#08745f;--color-success:#00b42a;--color-warning:#ff7d00;--color-error:#f53f3f;--color-bg-page:#f3faf8;--color-bg-card:#ffffff;--color-bg-hover:#eef8f5;--color-card-green:#e9fff8;--color-text-primary:#15201d;--color-text-secondary:#4e6660;--color-text-tertiary:#8aa09a;--color-border:#dcebe7;--color-divider:#edf5f2;}"
    "*{box-sizing:border-box;margin:0;padding:0}"
    "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"
    ".wrap{max-width:420px;margin:0 auto;}"
    ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"
    ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"
    ".headerText{text-align:left;}"
    "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"
    "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"
    ".helpIcon{position:absolute;top:24px;right:4px;width:40px;height:40px;border-radius:50%;background:var(--color-card-green);color:var(--color-primary);font-size:20px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 0.2s,background 0.2s;z-index:5;}"
    ".helpIcon:hover{background:#d4f7ec;}"
    ".helpIcon:active{transform:scale(0.92);}"
    ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"
    "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"
    ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"
    ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"
    ".cardHeaderIcon{width:36px;height:36px;border-radius:10px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}"
    ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"
    ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
    ".field{margin-top:14px;}.field:first-child{margin-top:0;}"
    "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"
    ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"
    ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"
    "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"
    "input::placeholder{color:var(--color-text-tertiary);}"
    ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"
    "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"
    ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"
    ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"
    ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"
    ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"
    "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"
    "button:active{transform:scale(0.98);opacity:0.92;}"
    "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"
    "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"
    ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"
    ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"
    ".modalMask.show{opacity:1;visibility:visible;}"
    ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"
    ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"
    ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"
    ".modalHeaderIcon{width:40px;height:40px;border-radius:12px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}"
    ".modalHeaderText{flex:1;}"
    ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"
    ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
    ".modalClose{width:28px;height:28px;border-radius:50%;background:var(--color-bg-hover);color:var(--color-text-tertiary);font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;}"
    ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"
    ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"
    ".modalStepNum{width:30px;height:30px;border-radius:50%;background:var(--color-primary);color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;}"
    ".modalStepContent{flex:1;}"
    ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"
    ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"
    ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"
    ".modalFooter button{flex:1;height:42px;font-size:14px;}"
    ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"
    ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"
    ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"
    ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"
    ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"
    "@media(prefers-color-scheme:dark){body{background:#0f1f1c;color:#e6f0ee;}.card{background:#182b27;box-shadow:0 2px 12px rgba(0,0,0,0.4);}.cardHeaderIcon,.modalStep,.inputWrap{background:#1f3530;}.inputWrap:focus-within{background:#243d38;border-color:var(--color-primary-light);}label{color:#a8bdb8;}h1{color:#e6f0ee;}.gateInfo{background:#143530;}button.secondary{background:#1f3530;color:#a8bdb8;}button.danger{background:#2a1a1a;color:#ff8a8a;border-color:#5a2828;}.modal{background:#182b27;}.modalHeader{border-bottom-color:#243d38;}.modalFooter{border-top-color:#243d38;}.modalClose{background:#1f3530;color:#a8bdb8;}}"
    "</style>");
    _wm.setCustomHeadElement(customHead.c_str());
    }

    // ====================== 1. Head 头部：CSS样式+主题色 ======================
    _wm.setCustomHeadElement(
        "<meta name='theme-color' content='#12a889'>"
        "<style>"
        ":root{--color-primary:#12a889;--color-primary-light:#35c9aa;--color-primary-dark:#08745f;--color-success:#00b42a;--color-warning:#ff7d00;--color-error:#f53f3f;--color-bg-page:#f3faf8;--color-bg-card:#ffffff;--color-bg-hover:#eef8f5;--color-card-green:#e9fff8;--color-text-primary:#15201d;--color-text-secondary:#4e6660;--color-text-tertiary:#8aa09a;--color-border:#dcebe7;--color-divider:#edf5f2;}"
        "*{box-sizing:border-box;margin:0;padding:0}"
        "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"
        ".wrap{max-width:420px;margin:0 auto;}"
        ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"
        ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"
        ".headerText{text-align:left;}"
        "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"
        "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"
        ".helpIcon{position:absolute;top:24px;right:4px;width:40px;height:40px;border-radius:50%;background:var(--color-card-green);color:var(--color-primary);font-size:20px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 0.2s,background 0.2s;z-index:5;}"
        ".helpIcon:hover{background:#d4f7ec;}"
        ".helpIcon:active{transform:scale(0.92);}"
        ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"
        "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"
        ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"
        ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"
        ".cardHeaderIcon{width:36px;height:36px;border-radius:10px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}"
        ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"
        ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
        ".field{margin-top:14px;}.field:first-child{margin-top:0;}"
        "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"
        ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"
        ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"
        "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"
        "input::placeholder{color:var(--color-text-tertiary);}"
        ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"
        "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"
        ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"
        ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"
        ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"
        ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"
        "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"
        "button:active{transform:scale(0.98);opacity:0.92;}"
        "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"
        "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"
        ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"
        ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"
        ".modalMask.show{opacity:1;visibility:visible;}"
        ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"
        ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"
        ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"
        ".modalHeaderIcon{width:40px;height:40px;border-radius:12px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}"
        ".modalHeaderText{flex:1;}"
        ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"
        ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
        ".modalClose{width:28px;height:28px;border-radius:50%;background:var(--color-bg-hover);color:var(--color-text-tertiary);font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;}"
        ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"
        ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"
        ".modalStepNum{width:30px;height:30px;border-radius:50%;background:var(--color-primary);color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;}"
        ".modalStepContent{flex:1;}"
        ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"
        ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"
        ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"
        ".modalFooter button{flex:1;height:42px;font-size:14px;}"
        ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"
        ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"
        ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"
        ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"
        ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"
        "@media(prefers-color-scheme:dark){body{background:#0f1f1c;color:#e6f0ee;}.card{background:#182b27;box-shadow:0 2px 12px rgba(0,0,0,0.4);}.cardHeaderIcon,.modalStep,.inputWrap{background:#1f3530;}.inputWrap:focus-within{background:#243d38;border-color:var(--color-primary-light);}label{color:#a8bdb8;}h1{color:#e6f0ee;}.gateInfo{background:#143530;}button.secondary{background:#1f3530;color:#a8bdb8;}button.danger{background:#2a1a1a;color:#ff8a8a;border-color:#5a2828;}.modal{background:#182b27;}.modalHeader{border-bottom-color:#243d38;}.modalFooter{border-top-color:#243d38;}.modalClose{background:#1f3530;color:#a8bdb8;}}"
        "</style>"
    );


    // ====================== 1. Head 头部：CSS样式+主题色 ======================
    {
    String customHead = F("<meta name='theme-color' content='#12a889'>"
    "<style>"
    ":root{--color-primary:#12a889;--color-primary-light:#35c9aa;--color-primary-dark:#08745f;--color-success:#00b42a;--color-warning:#ff7d00;--color-error:#f53f3f;--color-bg-page:#f3faf8;--color-bg-card:#ffffff;--color-bg-hover:#eef8f5;--color-card-green:#e9fff8;--color-text-primary:#15201d;--color-text-secondary:#4e6660;--color-text-tertiary:#8aa09a;--color-border:#dcebe7;--color-divider:#edf5f2;}"
    "*{box-sizing:border-box;margin:0;padding:0}"
    "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"
    ".wrap{max-width:420px;margin:0 auto;}"
    ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"
    ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"
    ".headerText{text-align:left;}"
    "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"
    "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"
    ".helpIcon{position:absolute;top:24px;right:4px;width:40px;height:40px;border-radius:50%;background:var(--color-card-green);color:var(--color-primary);font-size:20px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 0.2s,background 0.2s;z-index:5;}"
    ".helpIcon:hover{background:#d4f7ec;}"
    ".helpIcon:active{transform:scale(0.92);}"
    ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"
    "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"
    ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"
    ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"
    ".cardHeaderIcon{width:36px;height:36px;border-radius:10px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}"
    ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"
    ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
    ".field{margin-top:14px;}.field:first-child{margin-top:0;}"
    "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"
    ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"
    ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"
    "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"
    "input::placeholder{color:var(--color-text-tertiary);}"
    ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"
    "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"
    ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"
    ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"
    ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"
    ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"
    "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"
    "button:active{transform:scale(0.98);opacity:0.92;}"
    "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"
    "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"
    ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"
    ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"
    ".modalMask.show{opacity:1;visibility:visible;}"
    ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"
    ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"
    ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"
    ".modalHeaderIcon{width:40px;height:40px;border-radius:12px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}"
    ".modalHeaderText{flex:1;}"
    ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"
    ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
    ".modalClose{width:28px;height:28px;border-radius:50%;background:var(--color-bg-hover);color:var(--color-text-tertiary);font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;}"
    ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"
    ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"
    ".modalStepNum{width:30px;height:30px;border-radius:50%;background:var(--color-primary);color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;}"
    ".modalStepContent{flex:1;}"
    ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"
    ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"
    ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"
    ".modalFooter button{flex:1;height:42px;font-size:14px;}"
    ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"
    ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"
    ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"
    ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"
    ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"
    "@media(prefers-color-scheme:dark){body{background:#0f1f1c;color:#e6f0ee;}.card{background:#182b27;box-shadow:0 2px 12px rgba(0,0,0,0.4);}.cardHeaderIcon,.modalStep,.inputWrap{background:#1f3530;}.inputWrap:focus-within{background:#243d38;border-color:var(--color-primary-light);}label{color:#a8bdb8;}h1{color:#e6f0ee;}.gateInfo{background:#143530;}button.secondary{background:#1f3530;color:#a8bdb8;}button.danger{background:#2a1a1a;color:#ff8a8a;border-color:#5a2828;}.modal{background:#182b27;}.modalHeader{border-bottom-color:#243d38;}.modalFooter{border-top-color:#243d38;}.modalClose{background:#1f3530;color:#a8bdb8;}}"
    "</style>");
    _wm.setCustomHeadElement(customHead.c_str());
    }

    // ====================== 1. Head 头部：CSS样式+主题色 ======================
    _wm.setCustomHeadElement(
        "<meta name='theme-color' content='#12a889'>"
        "<style>"
        ":root{--color-primary:#12a889;--color-primary-light:#35c9aa;--color-primary-dark:#08745f;--color-success:#00b42a;--color-warning:#ff7d00;--color-error:#f53f3f;--color-bg-page:#f3faf8;--color-bg-card:#ffffff;--color-bg-hover:#eef8f5;--color-card-green:#e9fff8;--color-text-primary:#15201d;--color-text-secondary:#4e6660;--color-text-tertiary:#8aa09a;--color-border:#dcebe7;--color-divider:#edf5f2;}"
        "*{box-sizing:border-box;margin:0;padding:0}"
        "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"
        ".wrap{max-width:420px;margin:0 auto;}"
        ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"
        ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"
        ".headerText{text-align:left;}"
        "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"
        "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"
        ".helpIcon{position:absolute;top:24px;right:4px;width:40px;height:40px;border-radius:50%;background:var(--color-card-green);color:var(--color-primary);font-size:20px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 0.2s,background 0.2s;z-index:5;}"
        ".helpIcon:hover{background:#d4f7ec;}"
        ".helpIcon:active{transform:scale(0.92);}"
        ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"
        "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"
        ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"
        ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"
        ".cardHeaderIcon{width:36px;height:36px;border-radius:10px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}"
        ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"
        ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
        ".field{margin-top:14px;}.field:first-child{margin-top:0;}"
        "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"
        ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"
        ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"
        "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"
        "input::placeholder{color:var(--color-text-tertiary);}"
        ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"
        "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"
        ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"
        ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"
        ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"
        ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"
        "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"
        "button:active{transform:scale(0.98);opacity:0.92;}"
        "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"
        "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"
        ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"
        ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"
        ".modalMask.show{opacity:1;visibility:visible;}"
        ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"
        ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"
        ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"
        ".modalHeaderIcon{width:40px;height:40px;border-radius:12px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}"
        ".modalHeaderText{flex:1;}"
        ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"
        ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"
        ".modalClose{width:28px;height:28px;border-radius:50%;background:var(--color-bg-hover);color:var(--color-text-tertiary);font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;}"
        ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"
        ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"
        ".modalStepNum{width:30px;height:30px;border-radius:50%;background:var(--color-primary);color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;}"
        ".modalStepContent{flex:1;}"
        ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"
        ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"
        ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"
        ".modalFooter button{flex:1;height:42px;font-size:14px;}"
        ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"
        ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"
        ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"
        ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"
        ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"
        "@media(prefers-color-scheme:dark){body{background:#0f1f1c;color:#e6f0ee;}.card{background:#182b27;box-shadow:0 2px 12px rgba(0,0,0,0.4);}.cardHeaderIcon,.modalStep,.inputWrap{background:#1f3530;}.inputWrap:focus-within{background:#243d38;border-color:var(--color-primary-light);}label{color:#a8bdb8;}h1{color:#e6f0ee;}.gateInfo{background:#143530;}button.secondary{background:#1f3530;color:#a8bdb8;}button.danger{background:#2a1a1a;color:#ff8a8a;border-color:#5a2828;}.modal{background:#182b27;}.modalHeader{border-bottom-color:#243d38;}.modalFooter{border-top-color:#243d38;}.modalClose{background:#1f3530;color:#a8bdb8;}}"
        "</style>"
    );

-    String customHead = F("<meta name='theme-color' content='#12a889'>"

-    "*{box-sizing:border-box;margin:0;padding:0}"

-    "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"

-    ".wrap{max-width:420px;margin:0 auto;}"

-    ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"

-    ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"

-    "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"

-    "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"

-    ".helpIcon:hover{background:#d4f7ec;}"

-    ".helpIcon:active{transform:scale(0.92);}"

-    ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"

-    "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"

-    ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"

-    ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"

-    ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"

-    ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"

-    ".field{margin-top:14px;}.field:first-child{margin-top:0;}"

-    "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"

-    ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"

-    ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"

-    "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"

-    "input::placeholder{color:var(--color-text-tertiary);}"

-    ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"

-    "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"

-    ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"

-    ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"

-    ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"

-    ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"

-    "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"

-    "button:active{transform:scale(0.98);opacity:0.92;}"

-    "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"

-    "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"

-    ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"

-    ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"

-    ".modalMask.show{opacity:1;visibility:visible;}"

-    ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"

-    ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"

-    ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"

-    ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"

-    ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"

-    ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"

-    ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"

-    ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"

-    ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"

-    ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"

-    ".modalFooter button{flex:1;height:42px;font-size:14px;}"

-    ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"

-    ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"

-    ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"

-    ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"

-    ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"

-    _wm.setCustomHeadElement(customHead.c_str());

+        "<meta name='theme-color' content='#12a889'>"

+        "*{box-sizing:border-box;margin:0;padding:0}"

+        "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;background:var(--color-bg-page);min-height:100vh;padding:12px 16px 24px;color:var(--color-text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}"

+        ".wrap{max-width:420px;margin:0 auto;}"

+        ".header{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px;}"

+        ".headerIcon{width:56px;height:56px;border-radius:50%;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,0.12);}"

+        ".headerText{text-align:left;}"

+        "h1{font-size:20px;font-weight:700;color:var(--color-text-primary);letter-spacing:0.3px;}"

+        "h3{font-size:12px;font-weight:400;color:var(--color-text-secondary);margin-top:4px;}"

+        ".helpIcon{position:absolute;top:24px;right:4px;width:40px;height:40px;border-radius:50%;background:var(--color-card-green);color:var(--color-primary);font-size:20px;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform 0.2s,background 0.2s;z-index:5;}"

+        ".helpIcon:hover{background:#d4f7ec;}"

+        ".helpIcon:active{transform:scale(0.92);}"

+        ".helpIcon.hint{animation:hintPulse 1.6s ease-in-out infinite;}"

+        "@keyframes hintPulse{0%,100%{box-shadow:0 0 0 0 rgba(18,168,137,0.5);transform:translateY(0);}50%{box-shadow:0 0 0 10px rgba(18,168,137,0);transform:translateY(-3px);}}"

+        ".card{background:var(--color-bg-card);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,0.06);}"

+        ".cardHeader{display:flex;align-items:center;gap:12px;margin-bottom:18px;}"

+        ".cardHeaderIcon{width:36px;height:36px;border-radius:10px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}"

+        ".cardTitle{font-size:16px;font-weight:600;color:var(--color-text-primary);}"

+        ".cardSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"

+        ".field{margin-top:14px;}.field:first-child{margin-top:0;}"

+        "label{display:block;font-size:14px;font-weight:500;color:var(--color-text-secondary);margin-bottom:8px;}"

+        ".inputWrap{position:relative;display:flex;align-items:center;background:var(--color-bg-hover);border-radius:10px;border:1px solid transparent;transition:border-color 0.2s,background 0.2s;}"

+        ".inputIcon{padding:0 10px;font-size:16px;color:var(--color-text-tertiary);}"

+        "input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--color-text-primary);outline:none;font-family:inherit;}"

+        "input::placeholder{color:var(--color-text-tertiary);}"

+        ".inputWrap:focus-within{border-color:var(--color-primary);background:#fff;}"

+        "select{appearance:none;-webkit-appearance:none;background-image:url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=''http://www.w3.org/2000/svg'' width=''12'' height=''12'' viewBox=''0 0 12 12''%3e%3cpath fill=''%238aa09a'' d=''M6 9L1 3h10z''/%3e%3c/svg%3e');background-repeat:no-repeat;background-position:right 14px center;background-size:10px 10px;padding-right:30px;}"

+        ".gateInfo{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--color-card-green);margin-bottom:14px;}"

+        ".gateLabel{font-size:13px;color:var(--color-text-secondary);}"

+        ".gateId{font-size:14px;color:var(--color-primary);font-weight:600;}"

+        ".buttonGroup{display:flex;flex-direction:column;gap:12px;margin-top:8px;}"

+        "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--color-primary);box-shadow:0 2px 6px rgba(18,168,137,0.2);cursor:pointer;transition:transform 0.15s,opacity 0.15s;font-family:inherit;}"

+        "button:active{transform:scale(0.98);opacity:0.92;}"

+        "button.secondary{background:var(--color-bg-hover);color:var(--color-text-secondary);box-shadow:none;}"

+        "button.danger{background:#fff;color:var(--color-error);border:1px solid #ffd6d6;box-shadow:none;}"

+        ".footer{text-align:center;font-size:11px;color:var(--color-text-tertiary);padding:20px 0 6px;}"

+        ".modalMask{position:fixed;inset:0;background:rgba(15,31,28,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity 0.25s ease,visibility 0.25s ease;z-index:100;padding:20px;}"

+        ".modalMask.show{opacity:1;visibility:visible;}"

+        ".modal{width:100%;max-width:360px;background:#fff;border-radius:20px;padding:24px 22px 18px;transform:scale(0.85) translateY(20px);opacity:0;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;box-shadow:0 12px 40px rgba(0,0,0,0.2);max-height:80vh;overflow-y:auto;}"

+        ".modalMask.show .modal{transform:scale(1) translateY(0);opacity:1;}"

+        ".modalHeader{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-divider);}"

+        ".modalHeaderIcon{width:40px;height:40px;border-radius:12px;background:var(--color-card-green);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}"

+        ".modalTitle{font-size:16px;font-weight:700;color:var(--color-text-primary);}"

+        ".modalSub{font-size:12px;color:var(--color-text-tertiary);margin-top:2px;}"

+        ".modalClose{width:28px;height:28px;border-radius:50%;background:var(--color-bg-hover);color:var(--color-text-tertiary);font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;}"

+        ".modalSteps{display:flex;flex-direction:column;gap:12px;padding:20px 0 22px;}"

+        ".modalStep{display:flex;align-items:flex-start;gap:14px;padding:12px;border-radius:12px;background:var(--color-bg-hover);}"

+        ".modalStepNum{width:30px;height:30px;border-radius:50%;background:var(--color-primary);color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;}"

+        ".modalStepTitle{font-size:14px;font-weight:600;color:var(--color-text-primary);}"

+        ".modalStepDesc{font-size:12px;color:var(--color-text-tertiary);margin-top:3px;line-height:1.6;}"

+        ".modalFooter{display:flex;gap:10px;padding-top:6px;border-top:1px solid var(--color-divider);padding-top:14px;}"

+        ".modalFooter button{flex:1;height:42px;font-size:14px;}"

+        ".modalFooter button.primary{background:var(--color-primary);color:#fff;}"

+        ".modalFooter button.ghost{background:var(--color-bg-hover);color:var(--color-text-secondary);}"

+        ".guideTip{position:absolute;top:60px;right:0;transform:translateY(-8px) scale(0.9);transform-origin:top right;background:var(--color-primary);color:#fff;font-size:12px;padding:8px 14px;border-radius:999px;box-shadow:0 4px 12px rgba(18,168,137,0.35);opacity:0;transition:opacity 0.3s ease,transform 0.3s ease;z-index:6;pointer-events:none;white-space:nowrap;}"

+        ".guideTip::after{content:'';position:absolute;top:-5px;right:12px;width:10px;height:10px;background:var(--color-primary);transform:rotate(45deg);}"

+        ".guideTip.show{opacity:1;transform:translateY(0) scale(1);}"

// Body内容 (header/网关/弹窗/WiFi表单/参数卡片) 已写入 wm_strings_zh.h


Good. Now also need to fix `HTTP_FORM_PARAM_HEAD` in `wm_strings_zh.h` — it was set to empty `""`, but it should open the params card. Without this, when there are custom params but no static IP, the params card doesn't open, leading to a hanging `</div>` in `HTTP_FORM_END`.

Let me fix this.


还有 `wm_strings_zh.h` 里的 `HTTP_FORM_PARAM_HEAD` 设成了空字符串，参数卡片开不了。修复：


const char HTTP_FORM_PARAM_HEAD[] PROGMEM = "";

const char HTTP_FORM_PARAM_HEAD[] PROGMEM =
"
"
"

⚙

设备基础信息

唯一标识与安装位置

";


The file d:\PlatformIO1\Projects\Healthv2.0\.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\wm_strings_zh.h has been updated successfully. (file state is current in your context — no need to Read it back)

const char HTTP_FORM_PARAM_HEAD[] PROGMEM =
"
"
"

⚙

设备基础信息

唯一标识与安装位置

";

"

⚙

设备基础信息

唯一标识与安装位置

";

-const char HTTP_FORM_PARAM_HEAD[] PROGMEM = "";

+const char HTTP_FORM_PARAM_HEAD[] PROGMEM =

+"

⚙

设备基础信息

唯一标识与安装位置

";

const char HTTP_FORM_LABEL[] PROGMEM = "
{t}";


Now compile and test. The two changes:
1. Removed `String customHead = F(...)` → replaced with direct string literal `_wm.setCustomHeadElement("...")` — no heap allocation
2. Fixed `HTTP_FORM_PARAM_HEAD` to open the params card

But wait, there's still a potential issue with `HTTP_FORM_STATIC_HEAD`. If there ARE static IP fields, it also opens a card. Then `HTTP_FORM_PARAM_HEAD` opens ANOTHER card. So you'd get:
- WiFi card (from HTTP_FORM_WIFI)
- Static IP card (from HTTP_FORM_STATIC_HEAD)
- Params card (from HTTP_FORM_PARAM_HEAD)
- `HTTP_FORM_END` closes ONE card

The first one from HTTP_FORM_STATIC_HEAD would be unclosed. But I don't think this project uses static IP, so it probably won't be an issue.

But actually, `HTTP_FORM_STATIC_HEAD` appears BEFORE `HTTP_FORM_PARAM_HEAD`. So if both are present:
1. `HTTP_FORM_STATIC_HEAD` opens card A
2. Static IP fields
3. `HTTP_FORM_PARAM_HEAD` opens card B (inside card A?)
4. Params fields
5. `HTTP_FORM_END`: first `</div>` closes card B, then buttons, then `</form>`

Card A (from static IP) is never closed. But this only happens if static IP is enabled, which it probably isn't in this project.

Let me also consider: `HTTP_FORM_STATIC_HEAD` and `HTTP_FORM_PARAM_HEAD` should be mutually consistent. Let me make `HTTP_FORM_STATIC_HEAD` NOT open a card (just be a simple separator), so the card is only opened by `HTTP_FORM_PARAM_HEAD`.

Wait, but what if there's static IP but NO custom params? Then no card is opened but `HTTP_FORM_END` tries to close one.

This is getting complicated. Let me just set `HTTP_FORM_STATIC_HEAD` to a simple separator and not worry about the static IP + no params edge case since it's unlikely.

Actually, let me just leave it as-is. If static IP isn't enabled (which is the default), `HTTP_FORM_STATIC_HEAD` won't render. The user can test and report issues.

Let me summarize what was done and tell the user to try again.


改了两处：

1. **CSS** — 去掉 `String customHead = F(...)` + heap 分配，改回编译期字符串字面量直接传 `setCustomHeadElement("...")`，零运行时开销
2. **`HTTP_FORM_PARAM_HEAD`** — 从空字符串改回打开参数卡片

重新烧录试一下。


<ide_opened_file>The user opened the file d:\PlatformIO1\Projects\Healthv2.0\src\wifi_manager.cpp in the IDE. This may or may not be related to the current task.</ide_opened_file>

不行，还是一样