From 601451f22b4bfb810e3327eb1f15cce3a3f6c685 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?=
Date: Fri, 23 Oct 2026 12:31:03 +0800
Subject: [PATCH] =?UTF-8?q?upgrade:=20adopt=20V8.21.3=20(V8.13=E2=86=928.2?=
=?UTF-8?q?1.3:=20P5=20SFTP/S3,=20P6=20multilingual,=20editor=20overhaul,?=
=?UTF-8?q?=20enterprise=20hardening)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.
New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
under the V8.15 locale-rewrite pipeline.
compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).
Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.
Co-Authored-By: Claude Code
---
.env.example | 10 +-
.full_results.txt | 19 +
.github/workflows/ci.yml | 63 ++-
.../workflows/siteforge-v817-acceptance.yml | 69 +++
README.md | 533 +++++++++---------
README_V8_18.md | 14 +
SHA256SUMS.txt | 527 +++++++++++++++++
app/assembly.py | 79 ++-
app/backup.py | 3 +
app/builder.py | 26 +-
app/console_v819.py | 74 +++
app/core.py | 5 +-
app/db.py | 10 +-
app/deploy_agent.py | 118 +++-
app/enterprise.py | 6 +-
app/factory_api.py | 6 +-
app/healthchecks.py | 25 +
app/importers.py | 16 +-
app/job_runner.py | 3 +
app/jobs.py | 47 +-
app/main.py | 27 +-
app/multilingual.py | 265 +++++++++
app/pg_compat.py | 103 +++-
app/pg_disaster_recovery.py | 239 ++++++++
app/release_api.py | 216 +++++--
app/release_targets.py | 367 ++++++++++++
app/s3_gateway.py | 71 +++
app/site_design_v87.py | 44 ++
app/sqlite_pg_transfer.py | 154 +++++
app/static/commercial.css | 51 ++
app/static/editor-helpers.js | 39 ++
app/static/editor.css | 86 +++
app/static/editor.js | 164 +++++-
app/static/factory-v2.js | 23 +-
app/static/operations.js | 12 +-
app/static/pro.js | 46 ++
app/static/workspace.css | 1 +
app/static/workspace.js | 67 ++-
app/studio_v86.py | 72 ++-
app/templates/editor.html | 85 ++-
app/templates/login.html | 4 +-
app/templates/operations.html | 2 +-
app/templates/production-v8.html | 8 +-
app/templates/workspace.html | 1 +
app/theme_pro.py | 9 +-
app/theme_store.py | 2 +-
app/v8_workflows.py | 22 +-
app/version.py | 2 +-
app/worker.py | 14 +-
app/worker_health.py | 27 +-
app/wp_complex_v820.py | 183 ++++++
app/wp_migration_v82.py | 39 +-
compose.agent-secrets.example.yaml | 5 +
compose.enterprise.yaml | 14 +-
compose.publish-targets.example.yaml | 40 ++
compose.release-agent.yaml | 5 +-
compose.release-secrets.example.yaml | 6 +
compose.s3-gateway.example.yaml | 27 +
compose.yaml | 16 +-
.../siteforge-nginx.conf.example | 40 ++
deploy/native-release/README.md | 64 +++
docs/HISTORY_V8_12_README.md | 298 ++++++++++
docs/P6_QUICKSTART.md | 12 +
docs/V8_13_P5_ACCEPTANCE.md | 43 ++
docs/V8_14_P5_ACCEPTANCE.md | 32 ++
docs/V8_15_P6_ACCEPTANCE.md | 34 ++
docs/V8_16_PRODUCTION_ACCEPTANCE.md | 40 ++
docs/V8_17_ACCEPTANCE_RUNBOOK.md | 92 +++
docs/V8_17_INTEGRATION_RELEASE_NOTES.md | 17 +
docs/V8_17_INTEGRATION_TEST_RESULTS.md | 31 +
docs/V8_18_ENTERPRISE_HARDENING.md | 65 +++
docs/V8_19_ART_UNIFIED_BACKEND.md | 78 +++
docs/V8_20_COMPLEX_WORDPRESS.md | 48 ++
docs/V8_21_1_STUDIO_UX.md | 51 ++
docs/V8_21_2_EASY_STUDIO.md | 61 ++
docs/V8_21_3_MOBILE_THEMES.md | 46 ++
docs/V8_21_EDITOR_AND_MAPPING.md | 50 ++
examples/Studio_V8_21_1_交互预览.html | 13 +
.../Studio_V8_21_2_新手友好版交互预览.html | 37 ++
examples/evidence.template.json | 12 +
.../mobile-showcase/automotive/article.html | 13 +
.../automotive/assets/blocks.css | 29 +
.../automotive/assets/commercial.css | 84 +++
.../automotive/assets/custom.css | 1 +
.../automotive/assets/factory.css | 27 +
.../automotive/assets/pro-base.css | 21 +
.../mobile-showcase/automotive/assets/pro.css | 8 +
.../mobile-showcase/automotive/assets/pro.js | 72 +++
.../automotive/assets/search-index.json | 1 +
.../automotive/assets/site-runtime.js | 31 +
.../automotive/assets/theme.css | 1 +
.../automotive/assets/visual/hero.svg | 1 +
.../mobile-showcase/automotive/index.html | 24 +
.../mobile-showcase/automotive/listing.html | 22 +
.../mobile-showcase/automotive/product.html | 17 +
.../mobile-showcase/corporate/article.html | 13 +
.../corporate/assets/blocks.css | 29 +
.../corporate/assets/commercial.css | 84 +++
.../corporate/assets/custom.css | 1 +
.../corporate/assets/factory.css | 27 +
.../corporate/assets/pro-base.css | 21 +
.../mobile-showcase/corporate/assets/pro.css | 7 +
.../mobile-showcase/corporate/assets/pro.js | 72 +++
.../corporate/assets/search-index.json | 1 +
.../corporate/assets/site-runtime.js | 31 +
.../corporate/assets/theme.css | 1 +
.../corporate/assets/visual/hero.svg | 1 +
examples/mobile-showcase/corporate/index.html | 16 +
.../mobile-showcase/corporate/listing.html | 22 +
.../mobile-showcase/corporate/product.html | 17 +
examples/mobile-showcase/index.html | 3 +
.../mobile-showcase/industrial/article.html | 13 +
.../industrial/assets/blocks.css | 29 +
.../industrial/assets/commercial.css | 84 +++
.../industrial/assets/custom.css | 1 +
.../industrial/assets/factory.css | 27 +
.../industrial/assets/pro-base.css | 21 +
.../mobile-showcase/industrial/assets/pro.css | 7 +
.../mobile-showcase/industrial/assets/pro.js | 72 +++
.../industrial/assets/search-index.json | 1 +
.../industrial/assets/site-runtime.js | 31 +
.../industrial/assets/theme.css | 1 +
.../industrial/assets/visual/hero.svg | 1 +
.../mobile-showcase/industrial/index.html | 24 +
.../mobile-showcase/industrial/listing.html | 22 +
.../mobile-showcase/industrial/product.html | 17 +
examples/mobile-showcase/minimal/article.html | 13 +
.../mobile-showcase/minimal/assets/blocks.css | 29 +
.../minimal/assets/commercial.css | 84 +++
.../mobile-showcase/minimal/assets/custom.css | 1 +
.../minimal/assets/factory.css | 27 +
.../minimal/assets/pro-base.css | 21 +
.../mobile-showcase/minimal/assets/pro.css | 8 +
.../mobile-showcase/minimal/assets/pro.js | 72 +++
.../minimal/assets/search-index.json | 1 +
.../minimal/assets/site-runtime.js | 31 +
.../mobile-showcase/minimal/assets/theme.css | 1 +
.../minimal/assets/visual/hero.svg | 1 +
examples/mobile-showcase/minimal/index.html | 23 +
examples/mobile-showcase/minimal/listing.html | 22 +
examples/mobile-showcase/minimal/product.html | 17 +
examples/old_urls.example.txt | 6 +
fixtures/v820/wordPress-complex-sample.xml | 13 +
integrations/art-design-pro/README.md | 34 +-
.../src/views/siteforge/ConsoleHome.vue | 34 ++
.../src/views/siteforge/ContentCenter.vue | 40 ++
.../src/views/siteforge/EnterpriseCenter.vue | 8 +-
.../src/views/siteforge/MigrationCenter.vue | 8 +-
.../src/views/siteforge/ReleaseCenter.vue | 8 +-
.../src/views/siteforge/ThemeCenter.vue | 12 +-
.../src/views/siteforge/V8Production.vue | 11 +-
.../src/views/siteforge/VisualEditor.vue | 8 +-
.../src/views/siteforge/WebsiteFactory.vue | 11 +-
.../src/views/siteforge/_shared.ts | 53 ++
reports/V8_21_3_移动端验收总览.html | 2 +
reports/V8_21_3_移动端验收总览.md | 10 +
reports/mobile-v8213-automotive.png | Bin 0 -> 261037 bytes
reports/mobile-v8213-browser.json | 38 ++
reports/v8213-final-regression.html | 1 +
reports/v8213-final-regression.json | 163 ++++++
reports/v8213-final-regression.md | 19 +
reports/v8213-regression.html | 1 +
reports/v8213-regression.json | 163 ++++++
reports/v8213-regression.md | 19 +
requirements-browser.txt | 2 +
requirements-dev.lock | 13 +
requirements.lock | 13 +
requirements.txt | 4 +
scripts/browser_mobile_v8213.py | 71 +++
scripts/browser_smoke.py | 87 +++
scripts/browser_studio_v8211.py | 69 +++
scripts/check.ps1 | 2 +-
scripts/check.sh | 5 +-
scripts/demo_multilingual_v815.py | 32 ++
scripts/export_wordpress_url_baseline.py | 100 ++++
scripts/http_end_to_end.py | 77 +++
scripts/init_config.py | 2 +-
scripts/install_into_art_design_pro.py | 192 +++++--
scripts/migrate_sqlite_to_postgres.py | 30 +
scripts/pg_disaster_recovery.py | 37 ++
scripts/regression_v817.py | 130 +++++
scripts/regression_v820.py | 85 +++
scripts/regression_v821.py | 102 ++++
scripts/siteforge_v817_audit.py | 444 +++++++++++++++
scripts/verify_art_design_pro_v819.py | 57 ++
scripts/verify_pg_backup_restore.py | 62 ++
scripts/verify_pg_bootstrap.py | 97 +++-
scripts/verify_redis_signal.py | 18 +
scripts/verify_release.py | 2 +-
scripts/verify_sqlite_pg_transfer.py | 68 +++
scripts/wp_v820_preflight.py | 74 +++
scripts/write_v818_acceptance.py | 74 +++
scripts/write_v819_acceptance.py | 34 ++
tests/test_console_v819.py | 120 ++++
tests/test_editor_easy_v8212.py | 33 ++
tests/test_editor_ux_v8211.py | 23 +
tests/test_mobile_tabs_v8213.py | 83 +++
tests/test_multilingual_v815.py | 107 ++++
tests/test_production_v816.py | 88 +++
tests/test_professional_ui_v811.py | 3 +-
tests/test_regression_runner_v817.py | 47 ++
tests/test_release_native_v814.py | 186 ++++++
tests/test_release_v813.py | 196 +++++++
tests/test_siteforge_v817_audit.py | 173 ++++++
tests/test_theme_pro_v3.py | 23 +-
tests/test_theme_registry.py | 3 +
tests/test_v818_pg_recovery.py | 141 +++++
tests/test_v818_sqlite_pg_transfer.py | 78 +++
tests/test_v821_editor_history_and_mapping.py | 112 ++++
tests/test_v8_workflows.py | 3 +-
tests/test_wordpress_baseline.py | 47 ++
tests/test_workspace_v812.py | 3 +-
tests/test_wp_complex_v820.py | 124 ++++
tests_js/editor_ux.test.cjs | 44 ++
themes/automotive/pro.html | 3 +
themes/corporate/pro.html | 3 +
themes/industrial/pro.html | 3 +
themes/minimal/pro.html | 3 +
218 files changed, 10441 insertions(+), 628 deletions(-)
create mode 100644 .full_results.txt
create mode 100644 .github/workflows/siteforge-v817-acceptance.yml
create mode 100644 README_V8_18.md
create mode 100644 SHA256SUMS.txt
create mode 100644 app/console_v819.py
create mode 100644 app/healthchecks.py
create mode 100644 app/multilingual.py
create mode 100644 app/pg_disaster_recovery.py
create mode 100644 app/release_targets.py
create mode 100644 app/s3_gateway.py
create mode 100644 app/sqlite_pg_transfer.py
create mode 100644 app/static/editor-helpers.js
create mode 100644 app/wp_complex_v820.py
create mode 100644 compose.agent-secrets.example.yaml
create mode 100644 compose.publish-targets.example.yaml
create mode 100644 compose.release-secrets.example.yaml
create mode 100644 compose.s3-gateway.example.yaml
create mode 100644 deploy/art-design-pro/siteforge-nginx.conf.example
create mode 100644 deploy/native-release/README.md
create mode 100644 docs/HISTORY_V8_12_README.md
create mode 100644 docs/P6_QUICKSTART.md
create mode 100644 docs/V8_13_P5_ACCEPTANCE.md
create mode 100644 docs/V8_14_P5_ACCEPTANCE.md
create mode 100644 docs/V8_15_P6_ACCEPTANCE.md
create mode 100644 docs/V8_16_PRODUCTION_ACCEPTANCE.md
create mode 100644 docs/V8_17_ACCEPTANCE_RUNBOOK.md
create mode 100644 docs/V8_17_INTEGRATION_RELEASE_NOTES.md
create mode 100644 docs/V8_17_INTEGRATION_TEST_RESULTS.md
create mode 100644 docs/V8_18_ENTERPRISE_HARDENING.md
create mode 100644 docs/V8_19_ART_UNIFIED_BACKEND.md
create mode 100644 docs/V8_20_COMPLEX_WORDPRESS.md
create mode 100644 docs/V8_21_1_STUDIO_UX.md
create mode 100644 docs/V8_21_2_EASY_STUDIO.md
create mode 100644 docs/V8_21_3_MOBILE_THEMES.md
create mode 100644 docs/V8_21_EDITOR_AND_MAPPING.md
create mode 100644 examples/Studio_V8_21_1_交互预览.html
create mode 100644 examples/Studio_V8_21_2_新手友好版交互预览.html
create mode 100644 examples/evidence.template.json
create mode 100644 examples/mobile-showcase/automotive/article.html
create mode 100644 examples/mobile-showcase/automotive/assets/blocks.css
create mode 100644 examples/mobile-showcase/automotive/assets/commercial.css
create mode 100644 examples/mobile-showcase/automotive/assets/custom.css
create mode 100644 examples/mobile-showcase/automotive/assets/factory.css
create mode 100644 examples/mobile-showcase/automotive/assets/pro-base.css
create mode 100644 examples/mobile-showcase/automotive/assets/pro.css
create mode 100644 examples/mobile-showcase/automotive/assets/pro.js
create mode 100644 examples/mobile-showcase/automotive/assets/search-index.json
create mode 100644 examples/mobile-showcase/automotive/assets/site-runtime.js
create mode 100644 examples/mobile-showcase/automotive/assets/theme.css
create mode 100644 examples/mobile-showcase/automotive/assets/visual/hero.svg
create mode 100644 examples/mobile-showcase/automotive/index.html
create mode 100644 examples/mobile-showcase/automotive/listing.html
create mode 100644 examples/mobile-showcase/automotive/product.html
create mode 100644 examples/mobile-showcase/corporate/article.html
create mode 100644 examples/mobile-showcase/corporate/assets/blocks.css
create mode 100644 examples/mobile-showcase/corporate/assets/commercial.css
create mode 100644 examples/mobile-showcase/corporate/assets/custom.css
create mode 100644 examples/mobile-showcase/corporate/assets/factory.css
create mode 100644 examples/mobile-showcase/corporate/assets/pro-base.css
create mode 100644 examples/mobile-showcase/corporate/assets/pro.css
create mode 100644 examples/mobile-showcase/corporate/assets/pro.js
create mode 100644 examples/mobile-showcase/corporate/assets/search-index.json
create mode 100644 examples/mobile-showcase/corporate/assets/site-runtime.js
create mode 100644 examples/mobile-showcase/corporate/assets/theme.css
create mode 100644 examples/mobile-showcase/corporate/assets/visual/hero.svg
create mode 100644 examples/mobile-showcase/corporate/index.html
create mode 100644 examples/mobile-showcase/corporate/listing.html
create mode 100644 examples/mobile-showcase/corporate/product.html
create mode 100644 examples/mobile-showcase/index.html
create mode 100644 examples/mobile-showcase/industrial/article.html
create mode 100644 examples/mobile-showcase/industrial/assets/blocks.css
create mode 100644 examples/mobile-showcase/industrial/assets/commercial.css
create mode 100644 examples/mobile-showcase/industrial/assets/custom.css
create mode 100644 examples/mobile-showcase/industrial/assets/factory.css
create mode 100644 examples/mobile-showcase/industrial/assets/pro-base.css
create mode 100644 examples/mobile-showcase/industrial/assets/pro.css
create mode 100644 examples/mobile-showcase/industrial/assets/pro.js
create mode 100644 examples/mobile-showcase/industrial/assets/search-index.json
create mode 100644 examples/mobile-showcase/industrial/assets/site-runtime.js
create mode 100644 examples/mobile-showcase/industrial/assets/theme.css
create mode 100644 examples/mobile-showcase/industrial/assets/visual/hero.svg
create mode 100644 examples/mobile-showcase/industrial/index.html
create mode 100644 examples/mobile-showcase/industrial/listing.html
create mode 100644 examples/mobile-showcase/industrial/product.html
create mode 100644 examples/mobile-showcase/minimal/article.html
create mode 100644 examples/mobile-showcase/minimal/assets/blocks.css
create mode 100644 examples/mobile-showcase/minimal/assets/commercial.css
create mode 100644 examples/mobile-showcase/minimal/assets/custom.css
create mode 100644 examples/mobile-showcase/minimal/assets/factory.css
create mode 100644 examples/mobile-showcase/minimal/assets/pro-base.css
create mode 100644 examples/mobile-showcase/minimal/assets/pro.css
create mode 100644 examples/mobile-showcase/minimal/assets/pro.js
create mode 100644 examples/mobile-showcase/minimal/assets/search-index.json
create mode 100644 examples/mobile-showcase/minimal/assets/site-runtime.js
create mode 100644 examples/mobile-showcase/minimal/assets/theme.css
create mode 100644 examples/mobile-showcase/minimal/assets/visual/hero.svg
create mode 100644 examples/mobile-showcase/minimal/index.html
create mode 100644 examples/mobile-showcase/minimal/listing.html
create mode 100644 examples/mobile-showcase/minimal/product.html
create mode 100644 examples/old_urls.example.txt
create mode 100644 fixtures/v820/wordPress-complex-sample.xml
create mode 100644 integrations/art-design-pro/src/views/siteforge/ConsoleHome.vue
create mode 100644 integrations/art-design-pro/src/views/siteforge/ContentCenter.vue
create mode 100644 integrations/art-design-pro/src/views/siteforge/_shared.ts
create mode 100644 reports/V8_21_3_移动端验收总览.html
create mode 100644 reports/V8_21_3_移动端验收总览.md
create mode 100644 reports/mobile-v8213-automotive.png
create mode 100644 reports/mobile-v8213-browser.json
create mode 100644 reports/v8213-final-regression.html
create mode 100644 reports/v8213-final-regression.json
create mode 100644 reports/v8213-final-regression.md
create mode 100644 reports/v8213-regression.html
create mode 100644 reports/v8213-regression.json
create mode 100644 reports/v8213-regression.md
create mode 100644 requirements-browser.txt
create mode 100644 scripts/browser_mobile_v8213.py
create mode 100644 scripts/browser_smoke.py
create mode 100644 scripts/browser_studio_v8211.py
create mode 100644 scripts/demo_multilingual_v815.py
create mode 100644 scripts/export_wordpress_url_baseline.py
create mode 100644 scripts/http_end_to_end.py
create mode 100644 scripts/migrate_sqlite_to_postgres.py
create mode 100644 scripts/pg_disaster_recovery.py
create mode 100644 scripts/regression_v817.py
create mode 100644 scripts/regression_v820.py
create mode 100644 scripts/regression_v821.py
create mode 100644 scripts/siteforge_v817_audit.py
create mode 100644 scripts/verify_art_design_pro_v819.py
create mode 100644 scripts/verify_pg_backup_restore.py
create mode 100644 scripts/verify_redis_signal.py
create mode 100644 scripts/verify_sqlite_pg_transfer.py
create mode 100644 scripts/wp_v820_preflight.py
create mode 100644 scripts/write_v818_acceptance.py
create mode 100644 scripts/write_v819_acceptance.py
create mode 100644 tests/test_console_v819.py
create mode 100644 tests/test_editor_easy_v8212.py
create mode 100644 tests/test_editor_ux_v8211.py
create mode 100644 tests/test_mobile_tabs_v8213.py
create mode 100644 tests/test_multilingual_v815.py
create mode 100644 tests/test_production_v816.py
create mode 100644 tests/test_regression_runner_v817.py
create mode 100644 tests/test_release_native_v814.py
create mode 100644 tests/test_release_v813.py
create mode 100644 tests/test_siteforge_v817_audit.py
create mode 100644 tests/test_v818_pg_recovery.py
create mode 100644 tests/test_v818_sqlite_pg_transfer.py
create mode 100644 tests/test_v821_editor_history_and_mapping.py
create mode 100644 tests/test_wordpress_baseline.py
create mode 100644 tests/test_wp_complex_v820.py
create mode 100644 tests_js/editor_ux.test.cjs
diff --git a/.env.example b/.env.example
index b1f0949..762b994 100644
--- a/.env.example
+++ b/.env.example
@@ -13,9 +13,9 @@ SITEFORGE_PG_PASSWORD=GENERATE_STRONG_URL_SAFE_PASSWORD
SITEFORGE_PG_DB=siteforge
# Must be HTTPS and trusted deployment endpoint; never set to an arbitrary user URL
SITEFORGE_DEPLOY_STAGING_URL=
-SITEFORGE_DEPLOY_STAGING_SECRET=
+SITEFORGE_DEPLOY_STAGING_SECRET_REF=
SITEFORGE_DEPLOY_PRODUCTION_URL=
-SITEFORGE_DEPLOY_PRODUCTION_SECRET=
+SITEFORGE_DEPLOY_PRODUCTION_SECRET_REF=
# Release agent is deployed as an isolated service on the receiving host
SITEFORGE_DEPLOY_SECRET=
@@ -34,5 +34,9 @@ SITEFORGE_MYSQL_CA_FILE=
# SITEFORGE_MYSQL_READ_USER=
`;
+ inspector(){let target=$('#inspector');let b=blocks[selected];$('#blockTypeLabel').textContent=b?schema.blocks[b.type].name:'页面';if(!b){target.innerHTML='还没有选中内容先到左侧选择一个区块,或点击“添加内容区块”。
';return}const spec=schema.blocks[b.type];let out=`${html(spec.name)}
`;
for(const field of spec.fields){let value=b[field]??'',label=({title:'标题',subtitle:'副标题',description:'简介',image:'图片地址',body:'正文 HTML',button:'按钮文字',href:'跳转路径',source:'绑定内容类型',limit:'显示条数',columns:'每行列数',items:'重复条目',images:'画廊图片',image_side:'图片方向',align:'标题对齐',height:'间隔高度'})[field]||field;
out+=`
`;
- if(field==='items'||field==='images')out+=`请输入 JSON 数组,可编辑多个项目`;
+ if(field==='items'||field==='images')out+=Studio.arrayEditor(field,value,b.type);
else if(field==='source')out+=`网站已有内容将自动绑定并生成卡片。`;
else if(field==='image_side'||field==='align')out+=``;
else if(field==='body'||field==='description')out+=``;
@@ -41,6 +77,7 @@ const Studio={
if(['title','subtitle','description','image','body'].includes(field))out+='可输入 {{site.name}}、{{page.title}} 等变量。';
out+='
';
}
+ out+='
`;
out+='';
- target.innerHTML=out;
+ target.innerHTML=out;Studio.applyInspectorTab();
},
undo(){restoreSnapshot(snapshotCursor-1)},
redo(){restoreSnapshot(snapshotCursor+1)},
- modified(){recordSnapshot();dirty=true;$('#saveStatus').textContent='未保存更改';$('#saveStatus').classList.add('dirty');clearTimeout(timer);timer=setTimeout(()=>Studio.refreshPreview(),500)},
- async refreshPreview(){if(!pageId)return;try{let frame=$('#sitePreview');let doc=await api(`/api/sites/${siteId}/pages/${pageId}/editor-preview`,req('POST',{blocks,page_template:kind,layout_mode:layoutMode,site_design:siteDesignPreview}));frame.srcdoc=doc}catch(e){toast('预览失败:'+e.message,true)}},
- device(type){$('#deviceFrame').className='device-frame '+(type==='desktop'?'':type);for(let name of ['Desktop','Tablet','Phone'])$('#device'+name).classList.toggle('on',name.toLowerCase()===type)},
+ modified(continuous=false){
+ const now=Date.now(),snap=JSON.stringify({blocks,kind,layoutMode});
+ if(continuous&&lastContinuousEdit&&now-lastContinuousEdit<850&&snapshotCursor>0&&snapshotCursor===snapshots.length-1){snapshots[snapshotCursor]=snap;}
+ else recordSnapshot();
+ lastContinuousEdit=continuous?now:0;
+ dirty=true;$('#saveStatus').textContent='未保存更改';$('#saveStatus').classList.add('dirty');
+ Studio.updateHistoryButtons();Studio.updateGuidance();clearTimeout(timer);timer=setTimeout(()=>Studio.refreshPreview(),550);
+ },
+ async refreshPreview(){
+ if(!pageId)return;
+ const page=pageId,seq=++previewRequest;
+ if(previewController)previewController.abort();previewController=new AbortController();
+ const frame=$('#sitePreview');$('#previewState').textContent='正在更新预览…';$('#previewDot').classList.add('loading');
+ try{
+ const doc=await api(`/api/sites/${siteId}/pages/${page}/editor-preview`,{...req('POST',{blocks,page_template:kind,layout_mode:layoutMode,site_design:siteDesignPreview}),signal:previewController.signal});
+ if(!editorTools.shouldAcceptPreview(page,seq,pageId,previewRequest))return;
+ frame.srcdoc=doc;$('#previewState').textContent='实时预览 · 已更新';$('#previewDot').classList.remove('loading','error');
+ }catch(e){
+ if(!editorTools.shouldAcceptPreview(page,seq,pageId,previewRequest)||e.name==='AbortError')return;
+ $('#previewState').textContent='预览更新失败';$('#previewDot').classList.remove('loading');$('#previewDot').classList.add('error');toast('预览失败:'+e.message,true);
+ }
+ },
+ device(type){$('#deviceFrame').className='device-frame '+(type==='desktop'?'':type);$('#deviceDetails').textContent=({desktop:'桌面端 · 自适应宽度',tablet:'平板端 · 780px',phone:'手机端 · 390px'})[type]||'桌面端';for(let name of ['Desktop','Tablet','Phone'])$('#device'+name).classList.toggle('on',name.toLowerCase()===type)},
changeTemplateKind(t){kind=t;Studio.modified()},
setLayoutMode(v){layoutMode=v;Studio.modified()},
async resetPreset(){if(!confirm('用当前主题的预设布局替换草稿?不会更改正式页面。'))return;try{const result=await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft/preset?page_template=${encodeURIComponent(kind)}`);blocks=result.blocks;selected=blocks.length?0:-1;Studio.modified();Studio.draw();toast('已加载预设到草稿,记得保存')}catch(e){toast(e.message,true)}},
- async save(){try{const result=await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft`,req('PUT',{blocks,page_template:kind,layout_mode:layoutMode,expected_revision:draftRevision}));draftRevision=result.revision;dirty=false;$('#saveStatus').textContent='草稿 v'+draftRevision;$('#saveStatus').classList.remove('dirty');toast('设计草稿已保存,正式构建不受影响')}catch(e){toast('草稿保存失败:'+e.message,true)}},
- async publishDesign(){if(dirty){await Studio.save();if(dirty)return}if(!draftRevision)return toast('当前没有待发布草稿');if(!confirm('发布设计将更新内容库中的页面布局,下次静态构建才会生效。是否发布?'))return;try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft/publish`,req('POST',{expected_revision:draftRevision}));draftRevision=0;$('#saveStatus').textContent='已发布到内容库';toast('设计已发布;请构建网站生成新的正式静态文件')}catch(e){toast('发布失败:'+e.message,true)}},
+ async showHistory(){
+ if(!pageId)return toast('请先选择页面',true);
+ $('#studioHistoryModal').showModal();$('#studioHistoryItems').textContent='读取历史记录中…';
+ try{const data=await api(`/api/sites/${siteId}/pages/${pageId}/studio-history?limit=50`);
+ $('#studioHistoryItems').innerHTML=(data.items||[]).map(x=>`${x.kind==='published'?'已发布':'草稿'} · v${html(x.revision)}${html(x.created_at)} · ${html(x.actor)} · SHA ${html(x.snapshot_hash.slice(0,12))}
`).join('')||'当前页面尚无保存记录。
';
+ }catch(e){$('#studioHistoryItems').textContent='读取失败:'+e.message}
+ },
+ async restoreHistory(id){
+ if(dirty&&!confirm('当前有未保存的本地修改。恢复历史记录将舍弃这些本地修改,继续吗?'))return;
+ if(!confirm('确定以这个历史快照创建一个新的设计草稿?已发布的页面不会立即改变。'))return;
+ try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-history/${encodeURIComponent(id)}/restore`,req('POST',{expected_revision:draftRevision}));
+ dirty=false;$('#studioHistoryModal').close();await Studio.choosePage(pageId);
+ toast('历史版本已恢复为新草稿。核对后再点击发布设计。');
+ }catch(e){toast('历史恢复失败:'+e.message,true)}
+ },
+ async save(){
+ if(savePromise)return savePromise;
+ const page=pageId;const snapshot=JSON.stringify({blocks,kind,layoutMode});const frozen=JSON.parse(snapshot);
+ $('#saveStatus').textContent='保存中…';
+ savePromise=(async()=>{
+ try{
+ const result=await api(`/api/sites/${siteId}/pages/${page}/studio-draft`,req('PUT',{blocks:frozen.blocks,page_template:frozen.kind,layout_mode:frozen.layoutMode,expected_revision:draftRevision}));
+ if(page!==pageId)return;
+ draftRevision=result.revision;
+ const unchanged=snapshot===JSON.stringify({blocks,kind,layoutMode});
+ dirty=!unchanged;$('#saveStatus').textContent=unchanged?'草稿 v'+draftRevision:'有新修改待保存';
+ $('#saveStatus').classList.toggle('dirty',dirty);
+ toast(unchanged?'设计草稿已保存,正式构建不受影响':'草稿已保存,但有新修改,需再次保存');
+ }catch(e){dirty=true;$('#saveStatus').textContent='保存失败';$('#saveStatus').classList.add('dirty');toast('草稿保存失败:'+e.message,true)}
+ finally{savePromise=null;Studio.updateGuidance()}
+ })();return savePromise;
+ },
+ async publishDesign(){if(dirty){await Studio.save();if(dirty)return}if(!draftRevision)return toast('当前没有待发布草稿');if(!confirm('发布设计将更新内容库中的页面布局,下次静态构建才会生效。是否发布?'))return;try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft/publish`,req('POST',{expected_revision:draftRevision}));draftRevision=0;$('#saveStatus').textContent='已发布到内容库';Studio.updateGuidance();toast('设计已发布到内容库。要更新线上网站,请在“更多”中构建并部署。')}catch(e){toast('发布失败:'+e.message,true)}},
async discardDraft(){if(!draftRevision)return;if(!confirm('确定放弃草稿?'))return;try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft`,{method:'DELETE'});dirty=false;await Studio.choosePage(pageId);toast('草稿已放弃')}catch(e){toast(e.message,true)}},
dragChild(event,col,index){draggedChild={container:selected,col,index};event.dataTransfer.setData('application/siteforge-child','1');event.dataTransfer.effectAllowed='move';event.stopPropagation()},
dropChild(event,destCol){if(!draggedChild)return;event.preventDefault();event.stopPropagation();const drag=draggedChild;draggedChild=null;const src=blocks[drag.container],dest=blocks[selected];if(!src||!dest||src.type!=='layout'||dest.type!=='layout')return;const moved=src.columns?.[drag.col]?.blocks.splice(drag.index,1)[0];if(!moved)return;dest.columns[destCol].blocks.push(moved);Studio.modified();Studio.draw()},
@@ -80,15 +169,36 @@ const Studio={
for(const [name,value] of Object.entries(f)){const key='footer_'+name,el=form.elements.namedItem(key);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
for(const [name,value] of Object.entries(r)){const el=form.elements.namedItem(name);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
form.elements.namedItem('nav').value=JSON.stringify(siteDesign.nav,null,2);
+ form.elements.namedItem('mobile_tabs_enabled').checked=Boolean(siteDesign.mobile_tabs?.enabled);
+ Studio.drawMobileTabs(siteDesign.mobile_tabs?.items||[]);
+ Studio.toggleMobileTabsForm();
$('#siteDesignStatus').textContent=result.state==='draft'?`待发布的全站草稿 v${siteDesignRevision}${result.conflict?' · 网站已被其他人修改':''}`:'当前为已发布设计';
$('#siteDesignModal').showModal();
}catch(e){toast('全站设计读取失败:'+e.message,true)}},
+ drawMobileTabs(items){const root=$('#mobileTabsRows');root.replaceChildren();items.forEach((item,i)=>Studio.addMobileTab(item,i));},
+ toggleMobileTabsForm(){const on=$('#siteDesignForm').elements.namedItem('mobile_tabs_enabled').checked;$('#mobileTabsEditor').hidden=!on;},
+ addMobileTab(item={title:'新导航',icon:'grid',route:'/',action:'link'},index){
+ const root=$('#mobileTabsRows');if(root.children.length>=5){toast('底部最多支持 5 个 Tab',true);return}
+ const n=root.children.length;
+ const row=document.createElement('div');row.className='mobile-tab-row';row.style.cssText='display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:12px;border:1px solid #dce3ed;border-radius:10px;background:#fafcff';
+ const label=document.createElement('label');label.textContent='按钮名称';const title=document.createElement('input');title.className='mt-title';title.maxLength=12;title.value=item.title||'';label.append(title);
+ const iconLabel=document.createElement('label');iconLabel.textContent='按钮图标';const icon=document.createElement('select');icon.className='mt-icon';
+ for(const [v,t] of Object.entries({home:'⌂ 首页',grid:'▦ 栏目',products:'▤ 产品',news:'☷ 文章',contact:'✉ 联系',phone:'☎ 电话',info:'ⓘ 关于',search:'⌕ 搜索',menu:'☰ 菜单',star:'☆ 收藏'})){const opt=document.createElement('option');opt.value=v;opt.textContent=t;icon.append(opt)}icon.value=item.icon||'grid';iconLabel.append(icon);
+ const actionLabel=document.createElement('label');actionLabel.textContent='点击操作';const action=document.createElement('select');action.className='mt-action';for(const [v,t] of [['link','打开站内页面'],['menu','打开导航抽屉']]){const o=document.createElement('option');o.value=v;o.textContent=t;action.append(o)}action.value=item.action||'link';actionLabel.append(action);
+ const routeLabel=document.createElement('label');routeLabel.textContent='链接地址';const route=document.createElement('input');route.className='mt-route';route.placeholder='/products/、https://wa.me/... 或 tel:+...';route.value=item.route||'/';routeLabel.append(route);
+ function updateRoute(){routeLabel.hidden=action.value==='menu'}action.addEventListener('change',updateRoute);updateRoute();
+ const tools=document.createElement('div');tools.style.cssText='grid-column:1/-1;display:flex;gap:7px;justify-content:flex-end';
+ for(const [name,fn] of [['↑ 上移',()=>{const prev=row.previousElementSibling;if(prev)root.insertBefore(row,prev)}],['↓ 下移',()=>{const next=row.nextElementSibling;if(next)root.insertBefore(next,row)}],['删除',()=>row.remove()]]){const b=document.createElement('button');b.type='button';b.className='ui-btn ghost';b.textContent=name;b.addEventListener('click',fn);tools.append(b)}
+ row.append(label,iconLabel,actionLabel,routeLabel,tools);root.append(row);
+ },
collectSiteDesign(){const form=$('#siteDesignForm');if(!siteDesign)throw Error('请先打开全站设计');
const v=(n)=>form.elements.namedItem(n).value;
const yes=(n)=>form.elements.namedItem(n).checked;
let nav;try{nav=JSON.parse(v('nav'))}catch(e){throw Error('导航必须是合法 JSON:'+e.message)}
if(!Array.isArray(nav))throw Error('导航必须是数组');
- return {header:{announcement:v('announcement'),show_announcement:yes('show_announcement'),show_search:yes('show_search'),
+ const tabs=[...$('#mobileTabsRows').children].map(row=>({title:row.querySelector('.mt-title').value.trim(),icon:row.querySelector('.mt-icon').value,route:row.querySelector('.mt-route').value.trim(),action:row.querySelector('.mt-action').value}));
+ if(yes('mobile_tabs_enabled')&&!tabs.length)throw Error('开启底部 Tabs 后,请至少添加一个按钮');
+ return {tokens:structuredClone(siteDesign.tokens),mobile_tabs:{enabled:yes('mobile_tabs_enabled'),items:tabs},header:{announcement:v('announcement'),show_announcement:yes('show_announcement'),show_search:yes('show_search'),
show_cta:yes('show_cta'),cta_text:v('cta_text'),cta_href:v('cta_href')},
footer:{title:v('footer_title'),description:v('footer_description'),email:v('footer_email'),show_nav:yes('footer_show_nav')},nav,
responsive:{tablet_gap:Number(v('tablet_gap')),mobile_gap:Number(v('mobile_gap')),
@@ -143,6 +253,12 @@ window.addEventListener('message',event=>{
const frame=document.querySelector('#sitePreview');
if(!frame||event.source!==frame.contentWindow)return;
const data=event.data||{};
+ if(data.source==='siteforge-select-block'){
+ if(typeof data.blockId!=='string'||data.blockId.length>150)return;
+ const findTop=rows=>{for(let i=0;icol.blocks?.some(child=>child.id===data.blockId)))return i}return -1};
+ const index=findTop(blocks);if(index>=0){selected=index;Studio.draw();if(window.matchMedia('(max-width:1060px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}}
+ return;
+ }
if(data.source!=='siteforge-inline-editor')return;
const findChild=rows=>{for(const row of rows){if(row.id===data.blockId)return {block:row,parent:row};if(row.type==='layout'){for(const col of row.columns||[]){const child=findChild(col.blocks||[]);if(child)return {block:child.block,parent:row};}}}return null};
const found=findChild(blocks),b=found?.block;
@@ -152,4 +268,12 @@ window.addEventListener('message',event=>{
toast('已修改文字,点击保存页面写入数据库');
});
-window.addEventListener('keydown',event=>{if(!(event.ctrlKey||event.metaKey)||event.altKey)return;const el=document.activeElement;if(el&&['INPUT','TEXTAREA'].includes(el.tagName))return;if(event.key.toLowerCase()==='z'){event.preventDefault();event.shiftKey?Studio.redo():Studio.undo()}else if(event.key.toLowerCase()==='y'){event.preventDefault();Studio.redo()}});
+window.addEventListener('keydown',event=>{
+ if(!(event.ctrlKey||event.metaKey)||event.altKey)return;
+ const key=event.key.toLowerCase();
+ if(key==='s'){event.preventDefault();Studio.save();return}
+ const el=document.activeElement;
+ if(el&&(['INPUT','TEXTAREA'].includes(el.tagName)||el.isContentEditable))return;
+ if(key==='z'){event.preventDefault();event.shiftKey?Studio.redo():Studio.undo()}
+ else if(key==='y'){event.preventDefault();Studio.redo()}
+});
diff --git a/app/static/factory-v2.js b/app/static/factory-v2.js
index b8d5071..6d9d66e 100644
--- a/app/static/factory-v2.js
+++ b/app/static/factory-v2.js
@@ -43,11 +43,24 @@ async function readData(){let f=$('#dataFile').files?.[0];if(!f)return toast('
async function previewRelations(){try{const r=await api(factoryUrl('/relationships'));$('#relationReport').innerHTML=`产品-品牌关联检查已有品牌 ${r.brands.length} 个、分类 ${r.categories.length} 个;缺少独立品牌页 ${r.brand_pages_missing.length} 个
${H(r.brand_pages_missing.map(x=>x.brand).join(' / ')||'无')}
${r.issues.slice(0,10).map(x=>`
${H(x.route)} · ${H(x.message)}
`).join('')}
`;}catch(e){toast(e.message,true)}}
async function assembleRelations(){if(!confirm('依据真实产品品牌字段创建缺少的品牌页面?不会自动生成虚构文案。'))return;try{const r=await api(factoryUrl('/relationships/assemble'),'POST',{});toast(`已创建 ${r.count} 个品牌页面`);await previewRelations()}catch(e){toast(e.message,true)}}
-function drawMapping(){let fields=['title','route','summary','body','cover','brand','category','model','energy','range','gallery','specs','sku','price','seo_title','seo_description','canonical'];let labels={title:'标题(必填)',route:'旧站 URL',summary:'摘要',body:'详情正文',cover:'封面',brand:'品牌',category:'分类',seo_title:'SEO标题',seo_description:'SEO描述',canonical:'Canonical',model:'型号',energy:'动力形式',range:'续航',gallery:'图库(JSON数组或分号分隔)',specs:'技术参数',sku:'SKU',price:'价格'};
- $('#mappingUI').innerHTML=`字段映射(已读取 ${state.importRows.length} 行)
${fields.map(t=>`
${H(labels[t])}
`).join('')}
`;}
-function gather(){let rules={};$$('.mapfield').forEach(x=>{if(x.value)rules[x.dataset.target]=x.value});return {rows:state.importRows,rules,content_type:$('#mapKind').value}}
-async function mappingPreview(){try{let r=await api(factoryUrl('/mapping/run'),'POST',{...gather(),dry_run:true});$('#mappingReport').innerHTML=`预览:${r.mapped} 条可映射错误 ${r.errors.length} 条,URL 冲突 ${r.conflicts.length} 条
${r.errors.length?'
'+H(JSON.stringify(r.errors.slice(0,6)))+'
':''}
`}catch(e){toast(e.message,true)}}
-async function mappingCommit(){if(!confirm('确认将已映射的数据写入当前网站?'))return;try{let r=await api(factoryUrl('/mapping/run'),'POST',{...gather(),dry_run:false,on_conflict:'skip'});toast(`新增 ${r.created} 条,跳过 ${r.skipped} 条,错误 ${r.errors.length} 条`);await render()}catch(e){toast(e.message,true)}}
+function drawMapping(){
+ const fields=['title','route','summary','body','cover','brand','category','model','year','energy','sku','seo_title','seo_description'];
+ const labels={title:'标题',route:'URL 原路径',summary:'摘要',body:'正文',cover:'封面',brand:'品牌',category:'分类',model:'型号',year:'年份',energy:'动力类型',sku:'SKU',seo_title:'SEO 标题',seo_description:'SEO 描述'};
+ state.mappingPlanHash=null;
+ $('#mappingUI').innerHTML=`字段映射(已读取 ${state.importRows.length} 行)
可以为每个字段选择大小写变换;所有转换都由固定规则执行,不运行代码。
${fields.map(t=>`
${H(labels[t])}
`).join('')}
`;
+}
+function gather(){let rules={};$$('.mapfield').forEach(x=>{if(x.value){const mode=document.querySelector(`[data-transform="${x.dataset.target}"]`)?.value||'none';rules[x.dataset.target]=mode==='none'?x.value:{source:x.value,case:mode}}});return {rows:state.importRows,rules,content_type:$('#mapKind').value}}
+
+async function mappingPreview(){try{const payload=gather(),r=await api(factoryUrl('/mapping/run'),'POST',{...payload,dry_run:true});state.mappingPlanHash=r.plan_hash;$('#mappingReport').innerHTML=`预览:${r.mapped} 条可映射错误 ${r.errors.length} 条,URL 冲突 ${r.conflicts.length} 条
预检指纹:${H(r.plan_hash.slice(0,16))}…(提交时验证内容未变化)
${r.errors.length?'
'+H(JSON.stringify(r.errors.slice(0,6)))+'
':''}
`}catch(e){state.mappingPlanHash=null;toast(e.message,true)}}
+
+async function mappingCommit(){
+ if(!state.mappingPlanHash)return toast('请先执行映射预览,确认 URL 和冲突后再导入',true);
+ const policy=$('#mapConflict').value;
+ if(policy==='replace'&&!confirm('覆盖模式将修改已有同 URL 内容。请确认已备份并核对预览。'))return;
+ if(!confirm('将当前预检结果写入当前网站?'))return;
+ try{const r=await api(factoryUrl('/mapping/run'),'POST',{...gather(),dry_run:false,on_conflict:policy,expected_plan_hash:state.mappingPlanHash});state.mappingPlanHash=null;toast(`新增 ${r.created} 条,更新 ${r.updated} 条,跳过 ${r.skipped} 条,错误 ${r.errors.length} 条`);await render()}
+ catch(e){state.mappingPlanHash=null;toast('导入失败,请重新预检:'+e.message,true)}
+}
async function migration(){let [data,assets,builds]=await Promise.all([api(factoryUrl('/migration/readiness')),api(factoryUrl('/assets/inventory')),api(siteUrl('/builds'))]);
let audit=builds.length?await api(`/api/audit/sites/${state.site.id}/builds/${builds[0].id}/report`).catch(()=>null):null;const gate=data.gates||[];$('#panel').innerHTML=`旧站 URL 基线${data.baseline.baseline_count}应保持有效的历史路径
缺失 URL${data.baseline.errors}发布阻断项
SEO 差异${data.baseline.warnings}需要人工核查
资源引用${assets.references}实际已识别的内容图片
迁移验收门禁
${gate.length?gate.map(g=>`
${g.level==='block'?'阻止发布':'需人工复核'}${H(g.message)}
`).join(''):'
当前已检测范围内未发现阻断项。
'}
提示:完整旧站 URL 基线应来自迁移前的原网站清单,不要用迁移后不完整的网站替代。
实际生成 HTML 与旧站对比
${audit?`
旧站 ${audit.baseline_count} 条 / 已匹配 ${audit.matched_pages||0} 条;阻断 ${audit.errors} 项;待复核 ${audit.unreviewed_warnings||0} 项
下载差异 CSV`:'
请先构建新站,再与旧站基线对比。
'}
前 12 项差异
${data.baseline.issues.slice(0,12).map(x=>`
${H(x.route)}${H(x.field)} · ${H(x.severity)}
`).join('')||'
没有基线差异。
'}
`;}
async function saveContactSettings(){try{await api(siteUrl('/contact-settings'),'PUT',{email:$('#configEmail').value,form_endpoint:$('#configForm').value,footer_text:$('#configFooter').value});toast('网站联系配置已保存');await refresh()}catch(e){toast(e.message,true)}}
diff --git a/app/static/operations.js b/app/static/operations.js
index 2e631b1..8010975 100644
--- a/app/static/operations.js
+++ b/app/static/operations.js
@@ -14,14 +14,15 @@ function formatJobs(list){return list.length?list.map(j=>`
async function chooseSite(){siteId=$('#site').value;window.SiteForgeShell?.selectSite(siteId);lastPreview=null;$('#commitImport').disabled=true;await reload()}
async function init(){try{csrf=(await request('/api/auth/me')).csrf;sites=await request('/api/sites');$('#site').innerHTML=sites.map(s=>`
`).join('');siteId=sites[0]?.id||'';$('#site').onchange=chooseSite;$('#reload').onclick=reload;$('#previewImport').onclick=()=>importJSON(false);$('#commitImport').onclick=()=>importJSON(true);$('#build').onclick=buildSite;$('#deploy').onclick=deploySite;$('#refreshRelease').onclick=reload;await reload()}catch(e){notify(e.message,true)}}
async function reload(){if(!siteId)return;try{
- const [list,jobsData,buildData,approvals,history,reviewData]=await Promise.all([
- request(`/api/sites/${siteId}`),request(`/api/enterprise/sites/${siteId}/jobs`),request(`/api/sites/${siteId}/builds`),request(`/api/factory/sites/${siteId}/release-requests`),request(`/api/enterprise/sites/${siteId}/releases`),request(`/api/migrations/sites/${siteId}/reviews`)]);
+ const [list,jobsData,buildData,approvals,history,reviewData,deliveryData]=await Promise.all([
+ request(`/api/sites/${siteId}`),request(`/api/enterprise/sites/${siteId}/jobs`),request(`/api/sites/${siteId}/builds`),request(`/api/factory/sites/${siteId}/release-requests`),request(`/api/enterprise/sites/${siteId}/releases`),request(`/api/migrations/sites/${siteId}/reviews`),request(`/api/production/sites/${siteId}/delivery-status`)]);
jobs=jobsData;builds=buildData;releases=history;$('#statBuild').textContent=builds.length;$('#statJobs').textContent=jobs.filter(j=>['queued','running','retrying'].includes(j.state)).length;$('#statApprovals').textContent=approvals.filter(j=>j.state==='pending').length;$('#statMode').textContent=list.mode==='migration'?'迁移':'新建';$('#jobList').innerHTML=formatJobs(jobs);
$('#buildList').innerHTML=builds.length?builds.slice(0,14).map(b=>`
${esc(b.id)}${esc(b.created_at)} · ${esc(b.total_pages)} 页
`).join(''):'当前没有构建';
$('#releaseBuild').innerHTML=builds.map(b=>`
`).join('');
$('#approvals').innerHTML=approvals.length?approvals.slice(0,12).map(a=>`
${esc(a.build_id)} ${esc(a.state)}由 ${esc(a.requested_by)} 提交
${a.state==='pending'?`
`:''}
`).join(''):'暂无审批记录';
$('#reviews').innerHTML=reviewData.items.length?reviewData.items.map(x=>`
${esc(x.platform)} · 第 ${esc(x.row_number)} 条${esc(x.issue)}
`).join(''):'暂无待复核事项';
- $('#releases').innerHTML=history.length?history.slice(0,10).map(x=>`
${esc(x.build_id)} · ${esc(x.action)}${esc(x.created_at)}
`).join(''):'暂无部署记录';
+ $('#releaseBackend').textContent=['staging','production'].map(t=>`${t}: ${deliveryData.backends?.[t]||'agent'} · ${deliveryData.configured?.[t]?'已配置':'未配置'}`).join(' | ');
+ $('#releases').innerHTML=deliveryData.history?.length?deliveryData.history.slice(0,10).map(x=>`
${esc(x.build_id)} · ${esc(x.target)} · ${esc(x.release_id)}${esc(x.deployed_at)}
`).join(''): '暂无远程发布记录';
const entries=await request(`/api/sites/${siteId}/entries`);const entry=entries.find(p=>p.route==='/')||entries[0];$('#edit').href=entry?`/editor/${siteId}/${entry.id}`:'/factory-studio';
}catch(e){notify(e.message,true)}}
async function importJSON(commit){const file=$('#file').files?.[0];if(!file)return notify('请选择 JSON 文件',true);if(!siteId)return;const platform=$('#platform').value;
@@ -37,6 +38,9 @@ async function buildSite(){try{const task=await post(`/api/sites/${siteId}/build
async function cancelJob(id){try{await post(`/api/enterprise/sites/${siteId}/jobs/${id}/cancel`,{});await reload()}catch(e){notify(e.message,true)}}
async function requestApproval(build){try{await post(`/api/factory/sites/${siteId}/release-requests`,{build_id:build});notify('已提交发布审批');await reload()}catch(e){notify(e.message,true)}}
async function approve(id){if(!confirm('审批人必须与申请人不同。确定审批通过吗?'))return;try{await post(`/api/factory/sites/${siteId}/release-requests/${id}/review`,{state:'approved'});notify('审批已完成');await reload()}catch(e){notify(e.message,true)}}
-async function deploySite(){if(!confirm('请确保目标环境已配置 HTTPS 和发布 Agent。继续发布?'))return;try{const data=await post(`/api/production/sites/${siteId}/deploy`,{build_id:$('#releaseBuild').value,target:$('#target').value});notify(`已发布:${data.remote.release}`);await reload()}catch(e){notify(e.message,true)}}
+async function deploySite(){if(!confirm('系统将使用目标环境预先配置的发布适配器。请确认构建已审批、DNS 与版本回退方案已准备好。继续发布?'))return;try{const data=await post(`/api/production/sites/${siteId}/deploy`,{build_id:$('#releaseBuild').value,target:$('#target').value});notify(`发布任务 ${data.id} 已提交,Worker 将先上传、校验,再切换线上版本。`);await reload()}catch(e){notify(e.message,true)}}
+async function rollbackRelease(release,target){if(!confirm(`确认将 ${target} 恢复为历史版本 ${release}?`))return;
+ try{const result=await post(`/api/production/sites/${siteId}/rollback`,{release_id:release,target});notify(`回滚任务 ${result.id} 已进入队列`);await reload()}catch(e){notify(e.message,true)}}
+window.rollbackRelease=rollbackRelease;
window.resolveReview=resolveReview;window.cancelJob=cancelJob;window.requestApproval=requestApproval;window.approve=approve;
window.addEventListener('DOMContentLoaded',init);
diff --git a/app/static/pro.js b/app/static/pro.js
index 29312a1..9cf100d 100644
--- a/app/static/pro.js
+++ b/app/static/pro.js
@@ -1,6 +1,52 @@
'use strict';(()=>{
const menu=document.querySelector('[data-pro-menu]'); const nav=document.querySelector('#pro-navigation');
if(menu&&nav){menu.addEventListener('click',()=>{const open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));nav.classList.toggle('is-open',open);menu.setAttribute('aria-label',open?'关闭导航':'打开导航');});}
+ // Drawer navigation. The same markup is used across the four professional themes.
+ const mobileNav=document.querySelector('#pro-navigation');
+ let drawerBackdrop=null,drawerClose=null,returnFocus=null;
+ if(menu&&mobileNav){
+ const originalParent=mobileNav.parentNode,originalNext=mobileNav.nextSibling;
+ const heading=document.createElement('div');heading.className='sf-drawer-header';
+ const title=document.createElement('strong');title.textContent='网站导航';
+ drawerClose=document.createElement('button');drawerClose.type='button';drawerClose.className='sf-drawer-close';drawerClose.textContent='×';drawerClose.setAttribute('aria-label','关闭网站导航');
+ heading.append(title,drawerClose);mobileNav.prepend(heading);
+ drawerBackdrop=document.createElement('div');drawerBackdrop.className='sf-mobile-drawer-backdrop';drawerBackdrop.hidden=false;document.body.append(drawerBackdrop);
+ function setDrawer(open,focusBack=true){
+ const isMobile=window.matchMedia('(max-width:1024px)').matches;
+ open=Boolean(open&&isMobile);
+ if(open){returnFocus=document.activeElement;document.body.append(mobileNav);}
+ else if(mobileNav.parentNode!==originalParent){if(originalNext&&originalNext.parentNode===originalParent)originalParent.insertBefore(mobileNav,originalNext);else originalParent.append(mobileNav);}
+ mobileNav.classList.toggle('is-open',open);drawerBackdrop.classList.toggle('is-open',open);
+ document.body.classList.toggle('sf-drawer-open',open);
+ menu.setAttribute('aria-expanded',String(open));menu.setAttribute('aria-label',open?'关闭导航':'打开导航');
+ mobileNav.setAttribute('aria-hidden',String(!open&&isMobile));
+ if('inert' in mobileNav)mobileNav.inert=!open&&isMobile;
+ if(open)drawerClose.focus();
+ else if(focusBack&&returnFocus instanceof HTMLElement){returnFocus.focus();returnFocus=null;}
+ }
+ // Replace the old inline-expansion listener: use capture to stop the old listener.
+ menu.addEventListener('click',e=>{if(!window.matchMedia('(max-width:1024px)').matches)return;e.stopImmediatePropagation();setDrawer(!mobileNav.classList.contains('is-open'));},true);
+ drawerClose.addEventListener('click',()=>setDrawer(false));
+ drawerBackdrop.addEventListener('click',()=>setDrawer(false));
+ document.querySelectorAll('[data-mobile-open-menu]').forEach(b=>b.addEventListener('click',()=>setDrawer(true)));
+ mobileNav.addEventListener('click',e=>{if(e.target.closest('a'))setDrawer(false,false)});
+ document.addEventListener('keydown',e=>{
+ if(e.key==='Escape'&&mobileNav.classList.contains('is-open')){e.preventDefault();setDrawer(false);}
+ if(e.key!=='Tab'||!mobileNav.classList.contains('is-open'))return;
+ const els=[...mobileNav.querySelectorAll('button:not([disabled]),a[href]')].filter(el=>el.getClientRects().length&&!el.closest('[hidden]'));
+ if(!els.length)return;const first=els[0],last=els[els.length-1];
+ if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
+ else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
+ });
+ window.matchMedia('(max-width:1024px)').addEventListener('change',()=>setDrawer(false,false));
+ if('inert' in mobileNav&&window.matchMedia('(max-width:1024px)').matches)mobileNav.inert=true;
+ // Do not strand catalogue links when the old mobile mega-button is hidden.
+ const megaLinks=document.querySelectorAll('[data-pro-mega] a');
+ if(megaLinks.length){const wrap=document.createElement('div');wrap.className='sf-drawer-extra';const h=document.createElement('strong');h.textContent='更多分类与系列';wrap.append(h);
+ [...megaLinks].slice(0,20).forEach(a=>{const clone=a.cloneNode(true);wrap.append(clone);});mobileNav.append(wrap);}
+ }
+ const tabbar=document.querySelector('[data-mobile-tabs]');
+ if(tabbar){tabbar.style.setProperty('--sf-mobile-tab-count',String(tabbar.children.length));}
const megaButton=document.querySelector('[data-pro-catalog]'), mega=document.querySelector('[data-pro-mega]');
if(megaButton&&mega){function setMega(open){mega.hidden=!open;mega.classList.toggle('is-open',open);megaButton.setAttribute('aria-expanded',String(open));}
megaButton.addEventListener('click',()=>setMega(mega.hidden));
diff --git a/app/static/workspace.css b/app/static/workspace.css
index c03a9cb..a3f3fb9 100644
--- a/app/static/workspace.css
+++ b/app/static/workspace.css
@@ -2,4 +2,5 @@
/* V8.12: task progress and dangerous actions, consistent focus and mobile sizing */
.btn-danger{border:1px solid #f1c3c7;color:#a92840;background:#fff5f6}.btn-danger:hover{background:#feecee}.btn:disabled{opacity:.55;cursor:not-allowed}.build-row{min-width:0}.build-meta{overflow-wrap:anywhere}.site-card:focus-visible,.quick-card:focus-visible{outline:3px solid #80aaf6;outline-offset:2px}@media(max-width:570px){.build-row{align-items:flex-start;flex-wrap:wrap}.build-row>.inline-actions{width:100%;padding-left:0}.site-picker-wrap select{max-width:130px}}
+
.theme-preview.trade-pro{background:linear-gradient(125deg,#0b3d91,#3d6dc1);color:#e8f1fb}.site-icon.trade-pro{background:#e8f1fb;color:#0b3d91}
diff --git a/app/static/workspace.js b/app/static/workspace.js
index 4c79288..e9b78a5 100644
--- a/app/static/workspace.js
+++ b/app/static/workspace.js
@@ -7,8 +7,8 @@ const typeName={page:'页面',product:'产品',vehicle:'车型',post:'新闻',br
const fmtDate=t=>{if(!t)return '—';const d=new Date(t);return Number.isNaN(d.getTime())?String(t).slice(0,16):d.toLocaleDateString('zh-CN',{year:'numeric',month:'2-digit',day:'2-digit'})};
const sfSiteKey='siteforge:selected-site';
const sfRemember=id=>{try{if(id)localStorage.setItem(sfSiteKey,id)}catch{}};
-const state={sites:[],site:null,entries:[],builds:[],jobs:[],view:'home',csrf:'',search:'',typ:'',loading:false,job:null,selectionToken:0,building:false};
-const headings={home:'工作台',sites:'我的网站',overview:'网站概览',content:'内容与页面',design:'外观与设计',import:'导入与迁移',build:'构建与交付',settings:'网站设置'};
+const state={sites:[],site:null,entries:[],builds:[],jobs:[],view:'home',csrf:'',search:'',typ:'',loading:false,job:null,selectionToken:0,building:false,delivery:{jobs:[],history:[],configured:{}},language:{group:null,sites:[],units:[]},languageGroups:[],languageEntries:[]};
+const headings={home:'工作台',sites:'我的网站',overview:'网站概览',content:'内容与页面',design:'外观与设计',import:'导入与迁移',build:'构建与交付',languages:'多语言管理',settings:'网站设置'};
async function api(url,method='GET',data=null,extraHeaders={}){
const headers={...extraHeaders};if(method!=='GET'&&method!=='HEAD')headers['X-SiteForge-CSRF']=state.csrf;
let body=data;if(data!=null && !(data instanceof FormData)){headers['Content-Type']='application/json';body=JSON.stringify(data)}
@@ -86,28 +86,61 @@ function builds(){if(!currentSite())return noSite();const latest=latestDone();co
(state.jobs.length?state.jobs.slice(0,8).map(j=>`
${j.state==='done'?'✓':j.state==='failed'?'!':'◷'}${['queued','running','retrying'].includes(j.state)?`
`:''}
`).join(''):'
当前网站还没有后台构建任务。
')+`
`+
`
构建版本
建议先预览和核对质量报告,再下载 ZIP。预览不代表正式发布。
`+
(state.builds.length?state.builds.slice(0,20).map(b=>`
${['done','completed','success','complete'].includes(b.status)?'✓':'◷'}${['done','completed','success','complete'].includes(b.status)?`
预览 ↗下载 ZIP`:'
尚未完成,暂不能下载'}
`).join(''):emptyState('尚无完成版本','准备好网站数据后,在上方提交构建任务。'))+
- `
`;
+ `
`+
+ `正式发布与回滚
先完成质量检查与独立审批,Worker 负责上传、校验和切换版本。没有配置目标服务器时不能发布。
审批与复核工具 ↗
+
+
+
+
+
最近发布任务
${(state.delivery.jobs||[]).length?state.delivery.jobs.slice(0,8).map(j=>`
${['queued','running','retrying'].includes(j.state)?`
`:''}
`).join(''):'
暂无发布任务。
'}
+
已发布版本
${(state.delivery.history||[]).length?state.delivery.history.slice(0,8).map(r=>`
`).join(''):'
当前没有远程部署记录。
'}
`;
}
function settings(){if(!currentSite())return noSite();const s=state.site;return htmlHeading('网站设置','这里只管理公司资料、域名与站点属性。网页样式请到“外观与设计”修改。')+
`基本资料
没有域名也可以制作与预览网站,正式上线前再填写即可。
`;
}
+function languages(){
+ if(!currentSite())return noSite();
+ const d=state.language||{group:null,sites:[],units:[]};const g=d.group;
+ const groups=state.languageGroups||[];
+ const header=htmlHeading('多语言管理','多个语言版本分别拥有独立的网站、域名和内容。系统只关联已审核的翻译,不会自动翻译企业事实。','');
+ if(!g)return header+``+
+ (groups.length?`加入已有语言组
仅可关联您有管理权限、且已配置独立 HTTPS 域名的网站。
`:'')+
+ `上线前提示:网站设置中的正式域名应为 https://example.com 形式;不同语言网站不能共用一个域名。当前域名:${esc(state.site.domain||'未设置')}
`;
+ const groupSites=d.sites||[]; const uniqueUnits=[...new Map((d.units||[]).map(u=>[u.id,u])).values()];
+ const canJoin=state.sites.filter(x=>!groupSites.some(m=>m.site_id===x.id));
+ return header+`${esc(g.name)}
当前站语言 ${esc(g.locale)} · 默认语言 ${esc(g.default_locale)} · ${groupSites.length} 个语言站点
| 语言 | 网站 | 正式域名 |
${groupSites.map(x=>`| ${esc(x.locale)} | ${esc(x.name)} | ${esc(x.domain)} |
`).join('')}
`+
+ `第二步 · 添加其他语言网站
先通过“我的网站”创建另一语言站并设置 HTTPS 域名,再加入语言组。
`+
+ `第三步 · 建立翻译内容关系
相同产品、文章或页面可以使用不同 URL。用一个内容标识关联它们,只有正式发布且审核通过的内容才会生成 hreflang。
`+
+ `第四步 · 关联各语言的实际内容
`+
+ `已有语言关系
没有已审核的双向内容关系,就不会输出假语言链接。
${d.units.length?`
| 内容组 | 语言 | 内容 / URL | 状态 |
${d.units.map(u=>`| ${esc(u.reference_key)} | ${esc(u.locale)} | ${esc(u.title)} ${esc(u.route)} | ${u.state==='approved'?'已审核':'待翻译'} |
`).join('')}
`:'
还没有关联内容。
'}
`;
+}
+async function loadLanguage(){const sid=state.site?.id;if(!sid)return;try{
+ const [data,groups]=await Promise.all([api('/api/i18n/sites/'+encodeURIComponent(sid)),api('/api/i18n/groups')]);
+ if(state.site?.id!==sid)return;state.language=data;state.languageGroups=groups;await loadLanguageEntries(sid,false);if(state.view==='languages')rerender();
+ }catch(e){notifyError(e)}}
+async function loadLanguageEntries(siteId,redraw=true){if(!siteId)return;try{const rows=await api('/api/sites/'+encodeURIComponent(siteId)+'/entries');state.languageEntries=rows;if(redraw){const target=$('#langMemberEntry');if(target)target.innerHTML=rows.map(x=>``).join('')}}catch(e){notifyError(e);state.languageEntries=[]}}
+async function submitLanguageGroup(ev){ev.preventDefault();if(!state.site)return;try{const d=Object.fromEntries(new FormData(ev.target));const locale=d.current_locale;delete d.current_locale;const group=await api('/api/i18n/groups','POST',d);await api('/api/i18n/groups/'+group.id+'/sites','POST',{site_id:state.site.id,locale});toast('语言组已创建');await loadLanguage()}catch(e){notifyError(e)}}
+async function submitLanguageJoin(ev){ev.preventDefault();try{const d=Object.fromEntries(new FormData(ev.target));await api('/api/i18n/groups/'+d.group_id+'/sites','POST',{site_id:d.site_id||state.site.id,locale:d.locale});toast('语言站已关联');await loadLanguage()}catch(e){notifyError(e)}}
+async function submitLanguageUnit(ev){ev.preventDefault();const d=Object.fromEntries(new FormData(ev.target));try{await api('/api/i18n/groups/'+state.language.group.group_id+'/units','POST',d);toast('翻译组已创建');await loadLanguage()}catch(e){notifyError(e)}}
+async function submitLanguageLink(ev){ev.preventDefault();const d=Object.fromEntries(new FormData(ev.target));try{await api('/api/i18n/groups/'+state.language.group.group_id+'/units/'+encodeURIComponent(d.unit_id)+'/members','POST',{site_id:d.site_id,entry_id:d.entry_id,state:d.state});toast('内容翻译关系已保存');await loadLanguage()}catch(e){notifyError(e)}}
function rerender(){const area=$('#mainContent');const selected=state.view;$('#breadcrumbTitle').textContent=headings[selected]||'工作台';$('#sideSiteName').textContent=state.site?.name||'请先选择网站';
document.querySelectorAll('[data-view].nav-item').forEach(el=>{const yes=el.dataset.view===selected;el.classList.toggle('active',yes);yes?el.setAttribute('aria-current','page'):el.removeAttribute('aria-current')});
- area.innerHTML=({home,sites,overview,content,design,import:imports,build:builds,settings}[selected]||home)();
+ area.innerHTML=({home,sites,overview,content,design,import:imports,build:builds,languages,settings}[selected]||home)();
}
async function loadSites(){state.sites=await api('/api/sites');const picker=$('#sitePicker'),prev=state.site?.id||'';picker.innerHTML=''+state.sites.map(s=>``).join('');picker.value=state.sites.some(s=>s.id===prev)?prev:''}
+async function readDelivery(siteId){try{return await api(`/api/production/sites/${encodeURIComponent(siteId)}/delivery-status`)}catch(e){return {jobs:[],history:[],configured:{}}}}
async function readJobs(siteId){try{return await api(`/api/enterprise/sites/${encodeURIComponent(siteId)}/jobs`)}catch(e){if(![403,404].includes(Number(e.status)))console.warn('加载任务状态失败',e);return []}}
async function selectSite(id,go='overview',historyMode='push'){
const token=++state.selectionToken;
- if(!id){state.site=null;state.entries=[];state.builds=[];state.jobs=[];state.job=null;state.view=go==='home'?'home':'sites';$('#sitePicker').value='';rerender();if(historyMode!=='none')rememberView(historyMode==='replace');return}
+ if(!id){state.site=null;state.entries=[];state.builds=[];state.jobs=[];state.delivery={jobs:[],history:[],configured:{}};state.job=null;state.view=go==='home'?'home':'sites';$('#sitePicker').value='';rerender();if(historyMode!=='none')rememberView(historyMode==='replace');return}
if(!state.sites.some(x=>x.id===id)){toast('该网站不在您的可见列表中',true);return}
- try{const [site,entries,builds,jobs]=await Promise.all([api(`/api/sites/${encodeURIComponent(id)}`),api(`/api/sites/${encodeURIComponent(id)}/entries`),api(`/api/sites/${encodeURIComponent(id)}/builds`),readJobs(id)]);
+ try{const [site,entries,builds,jobs,delivery]=await Promise.all([api(`/api/sites/${encodeURIComponent(id)}`),api(`/api/sites/${encodeURIComponent(id)}/entries`),api(`/api/sites/${encodeURIComponent(id)}/builds`),readJobs(id),readDelivery(id)]);
if(token!==state.selectionToken)return;
- state.site=site;sfRemember(id);state.entries=entries;state.builds=builds;state.jobs=jobs;state.job=null;state.view=go;state.search='';state.typ='';$('#sitePicker').value=id;rerender();if(historyMode!=='none')rememberView(historyMode==='replace');closeMobile();window.scrollTo({top:0,behavior:'instant'});
+ state.site=site;sfRemember(id);state.entries=entries;state.builds=builds;state.jobs=jobs;state.delivery=delivery;state.job=null;state.view=go;state.search='';state.typ='';$('#sitePicker').value=id;rerender();if(historyMode!=='none')rememberView(historyMode==='replace');if(go==='languages')loadLanguage();closeMobile();window.scrollTo({top:0,behavior:'instant'});
}catch(e){if(token===state.selectionToken)notifyError(e)}
}
-function navigate(to,historyMode='push'){if(!state.site&&!['home','sites'].includes(to)){state.view='sites';rerender();toast('请先选择网站项目');return}state.view=to;rerender();closeMobile();if(historyMode!=='none')rememberView(historyMode==='replace');if(to==='build')refreshBuilds();window.scrollTo({top:0,behavior:'instant'})}
-async function refreshBuilds(){const id=state.site?.id;if(!id)return;try{const previouslyRunning=activeJobs().length>0;const [jobs,builds]=await Promise.all([readJobs(id),api(`/api/sites/${encodeURIComponent(id)}/builds`)]);if(state.site?.id!==id)return;state.jobs=jobs;state.builds=builds;if(previouslyRunning&&!activeJobs().length){const last=jobs[0];state.job=last?.state==='done'?'后台构建已完成,请预览并查看质量报告。':last?.state==='failed'?'本次构建失败,请在任务中心检查原因。':last?.state==='cancelled'?'本次构建已取消。':null}if(state.view==='build')rerender()}catch(e){notifyError(e)}}
+function navigate(to,historyMode='push'){if(!state.site&&!['home','sites'].includes(to)){state.view='sites';rerender();toast('请先选择网站项目');return}state.view=to;rerender();closeMobile();if(historyMode!=='none')rememberView(historyMode==='replace');if(to==='build')refreshBuilds();if(to==='languages')loadLanguage();window.scrollTo({top:0,behavior:'instant'})}
+async function refreshBuilds(){const id=state.site?.id;if(!id)return;try{const previouslyRunning=activeJobs().length>0;const [jobs,builds,delivery]=await Promise.all([readJobs(id),api(`/api/sites/${encodeURIComponent(id)}/builds`),readDelivery(id)]);if(state.site?.id!==id)return;state.jobs=jobs;state.builds=builds;state.delivery=delivery;if(previouslyRunning&&!activeJobs().length){const last=jobs[0];state.job=last?.state==='done'?'后台构建已完成,请预览并查看质量报告。':last?.state==='failed'?'本次构建失败,请在任务中心检查原因。':last?.state==='cancelled'?'本次构建已取消。':null}if(state.view==='build')rerender()}catch(e){notifyError(e)}}
function openModal(name){const node=$('#'+name);node.showModal();node.querySelector('input:not([type=hidden]),select')?.focus()}
function closeModal(name){$('#'+name)?.close()}
function openEntry(id){const form=$('#entryForm');form.reset();form.elements.entry_id.value='';$('#entryModalTitle').textContent=id?'修改内容':'添加内容';if(id){const e=state.entries.find(x=>x.id===id);if(!e)return toast('这条内容不存在',true);for(const field of ['title','route','summary','body','seo_title','seo_description','type','status']){if(form.elements[field])form.elements[field].value=e[field]||''}form.elements.entry_id.value=id}openModal('entryModal')}
@@ -125,6 +158,12 @@ async function buildNow(){if(!state.site)return toast('请先选择网站',true)
try{navigate('build');state.job='正在提交构建任务…';rerender();const job=await api(`/api/sites/${encodeURIComponent(id)}/builds`,'POST',{mirror_images:false},{'Idempotency-Key':crypto.randomUUID()});
if(state.site?.id!==id)return;state.job=`任务 ${job.id?.slice(0,12)||'已提交'} 已进入队列,可以离开页面;重新打开“构建与交付”查看结果。`;toast('已提交构建任务');await refreshBuilds();
}catch(e){notifyError(e)}finally{state.building=false;if(state.site?.id===id&&state.view==='build')rerender()}}
+function selectedReleaseBuild(){const id=$('#deliveryBuild')?.value;if(!id)throw Error('没有可发布的构建版本,请先完成网站构建');return id}
+function showDeliveryMessage(message){const el=$('#deliveryNotice');if(el){el.hidden=false;el.textContent=message}toast(message)}
+async function releasePreflight(){if(!state.site)return;try{const id=selectedReleaseBuild(),s=state.site.id;const r=await api(`/api/production/sites/${encodeURIComponent(s)}/preflight/${encodeURIComponent(id)}`);if(state.site?.id!==s)return;showDeliveryMessage(r.ready?'已通过发布前置检查:'+r.message:'尚不能发布:'+r.message)}catch(e){notifyError(e)}}
+async function releaseApproval(){if(!state.site)return;try{const id=selectedReleaseBuild(),s=state.site.id;const r=await api(`/api/factory/sites/${encodeURIComponent(s)}/release-requests`,'POST',{build_id:id});showDeliveryMessage('审批申请已创建,请由另一名有审核权限的人员批准。申请编号:'+r.id)}catch(e){notifyError(e)}}
+async function releaseDeploy(){if(!state.site)return;try{const id=selectedReleaseBuild(),s=state.site.id,target=$('#deliveryTarget')?.value;if(!state.delivery.configured?.[target])throw Error('尚未配置此目标服务器,请管理员配置目标发布方式(Agent/SFTP/S3)、服务器凭据与 HTTPS 验证地址');if(!confirm(`确定将构建 ${id.slice(0,12)} 提交到${target==='production'?'正式':'测试'}环境吗?必须已有独立审批。`))return;const r=await api(`/api/production/sites/${encodeURIComponent(s)}/deploy`,'POST',{build_id:id,target},{'Idempotency-Key':crypto.randomUUID()});showDeliveryMessage(`发布任务 ${r.id} 已排队。Worker 会先暂存和校验,再激活新版本。`);await refreshBuilds()}catch(e){notifyError(e)}}
+async function releaseRollback(target,release){if(!state.site)return;if(!confirm(`确定在 ${target} 环境恢复历史版本 ${release} 吗?请确认其内容仍符合当前业务要求。`))return;try{const r=await api(`/api/production/sites/${encodeURIComponent(state.site.id)}/rollback`,'POST',{target,release_id:release},{'Idempotency-Key':crypto.randomUUID()});showDeliveryMessage(`回滚任务 ${r.id} 已排队,请在任务列表查看结果。`);await refreshBuilds()}catch(e){notifyError(e)}}
async function checkReadiness(){if(!state.site)return;const siteId=state.site.id;
try{const result=await api(`/api/factory/sites/${encodeURIComponent(siteId)}/migration/readiness`);
if(state.site?.id!==siteId)return;
@@ -139,21 +178,21 @@ function closeMobile(){$('#sideMenu').classList.remove('open');$('#mobileBackdro
async function init(){try{const me=await api('/api/auth/me');state.csrf=me.csrf;await loadSites();const health=await api('/api/health');$('#appVersion').textContent='版本 '+health.version;
// Restore URL first, then a previously selected site *only* after verifying /api/sites.
const params=new URLSearchParams(location.search),requested=params.get('site');
- const target=params.get('view');const allowed=new Set(['home','sites','overview','content','design','import','build','settings']);
+ const target=params.get('view');const allowed=new Set(['home','sites','overview','content','design','import','build','languages','settings']);
let stored='';try{stored=localStorage.getItem(sfSiteKey)||''}catch{}
const chosen=[requested,stored].find(id=>id&&state.sites.some(x=>x.id===id));
if(chosen){await selectSite(chosen,allowed.has(target)?target:'overview','replace')}
else {state.view=target==='sites'?'sites':'home';rerender();rememberView(true)}
}catch(e){notifyError(e);$('#mainContent').innerHTML=emptyState('后台加载失败',e.message,'')}}
// Delegated UI interactions keep navigation and form actions centrally auditable.
-document.addEventListener('click',async ev=>{const control=ev.target.closest('[data-action]');if(!control)return;const action=control.dataset.action;try{if(action==='navigate')navigate(control.dataset.view);if(action==='new-site')openModal('siteModal');if(action==='select-site')await selectSite(control.dataset.id);if(action==='new-entry')openEntry();if(action==='edit-entry')openEntry(control.dataset.id);if(action==='set-theme')await setTheme(control.dataset.theme);if(action==='build-now')await buildNow();if(action==='refresh-builds')await refreshBuilds();if(action==='check-readiness')await checkReadiness();if(action==='cancel-job')await cancelJob(control.dataset.id);if(action==='report')await showReport(control.dataset.id)}catch(e){notifyError(e)}});
+document.addEventListener('click',async ev=>{const control=ev.target.closest('[data-action]');if(!control)return;const action=control.dataset.action;try{if(action==='navigate')navigate(control.dataset.view);if(action==='language-refresh')await loadLanguage();if(action==='new-site')openModal('siteModal');if(action==='select-site')await selectSite(control.dataset.id);if(action==='new-entry')openEntry();if(action==='edit-entry')openEntry(control.dataset.id);if(action==='set-theme')await setTheme(control.dataset.theme);if(action==='build-now')await buildNow();if(action==='refresh-builds')await refreshBuilds();if(action==='check-readiness')await checkReadiness();if(action==='cancel-job')await cancelJob(control.dataset.id);if(action==='report')await showReport(control.dataset.id);if(action==='release-preflight')await releasePreflight();if(action==='release-approval')await releaseApproval();if(action==='release-deploy')await releaseDeploy();if(action==='release-rollback')await releaseRollback(control.dataset.target,control.dataset.id)}catch(e){notifyError(e)}});
document.addEventListener('click',ev=>{const btn=ev.target.closest('[data-close]');if(btn)closeModal(btn.dataset.close)});
$('#siteForm').addEventListener('submit',saveSite);$('#entryForm').addEventListener('submit',saveEntry);
-document.addEventListener('submit',ev=>{if(ev.target.id==='settingsForm')saveSettings(ev);if(ev.target.id==='importForm')importFile(ev)});
+document.addEventListener('submit',ev=>{if(ev.target.id==='settingsForm')saveSettings(ev);if(ev.target.id==='importForm')importFile(ev);if(ev.target.id==='langGroupForm')submitLanguageGroup(ev);if(ev.target.id==='langJoinForm')submitLanguageJoin(ev);if(ev.target.id==='langUnitForm')submitLanguageUnit(ev);if(ev.target.id==='langLinkForm')submitLanguageLink(ev)});
document.addEventListener('input',ev=>{if(ev.target.id==='contentSearch'){state.search=ev.target.value;const cursor=ev.target.selectionStart;const type=state.typ;rerender();const el=$('#contentSearch');el.focus();el.setSelectionRange(cursor,cursor);state.typ=type}});
-document.addEventListener('change',ev=>{if(ev.target.id==='contentType'){state.typ=ev.target.value;rerender()}});
+document.addEventListener('change',ev=>{if(ev.target.id==='contentType'){state.typ=ev.target.value;rerender()}if(ev.target.id==='langMemberSite')loadLanguageEntries(ev.target.value)});
$('#sitePicker').addEventListener('change',ev=>selectSite(ev.target.value));
-window.addEventListener('popstate',async()=>{const p=new URLSearchParams(location.search);const site=p.get('site'),view=p.get('view')||'home';const allowed=['home','sites','overview','content','design','import','build','settings'];if(site&&state.sites.some(s=>s.id===site))await selectSite(site,allowed.includes(view)?view:'overview','none');else{state.selectionToken++;state.site=null;state.entries=[];state.jobs=[];state.builds=[];state.view=view==='sites'?'sites':'home';rerender()}});
+window.addEventListener('popstate',async()=>{const p=new URLSearchParams(location.search);const site=p.get('site'),view=p.get('view')||'home';const allowed=['home','sites','overview','content','design','import','build','languages','settings'];if(site&&state.sites.some(s=>s.id===site))await selectSite(site,allowed.includes(view)?view:'overview','none');else{state.selectionToken++;state.site=null;state.entries=[];state.jobs=[];state.builds=[];state.view=view==='sites'?'sites':'home';rerender()}});
setInterval(()=>{if(!document.hidden&&state.view==='build'&&activeJobs().length)refreshBuilds()},5000);
$('#newSiteTop').addEventListener('click',()=>openModal('siteModal'));
$('#logoutBtn').addEventListener('click',async()=>{try{await api('/api/auth/logout','POST');location.href='/login'}catch(e){notifyError(e)}});
diff --git a/app/studio_v86.py b/app/studio_v86.py
index d1cb6ff..661482d 100644
--- a/app/studio_v86.py
+++ b/app/studio_v86.py
@@ -6,6 +6,7 @@ studio uses only /studio-draft endpoints; a build never consumes these drafts.
from __future__ import annotations
import hashlib
import json
+import uuid
from fastapi import APIRouter, Body, HTTPException, Request
from . import db, blueprints, enterprise
from .core import save_entry
@@ -24,6 +25,14 @@ def setup():
id TEXT PRIMARY KEY, site_id TEXT NOT NULL, entry_id TEXT NOT NULL,
revision INTEGER NOT NULL, actor TEXT NOT NULL, published_at TEXT NOT NULL,
blueprint_hash TEXT NOT NULL)''')
+ c.execute('''CREATE TABLE IF NOT EXISTS studio_history (
+ id TEXT PRIMARY KEY,site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
+ entry_id TEXT NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
+ revision INTEGER NOT NULL,kind TEXT NOT NULL,
+ snapshot_json TEXT NOT NULL,snapshot_hash TEXT NOT NULL,
+ actor TEXT NOT NULL,created_at TEXT NOT NULL)''')
+ c.execute('''CREATE INDEX IF NOT EXISTS idx_studio_history_site_page
+ ON studio_history(site_id,entry_id,created_at,revision)''')
def _entry(site_id, entry_id):
@@ -59,7 +68,7 @@ def read(site_id,entry_id):
'layout_mode':meta.get('layout_mode','append'),'revision':0,'conflict':False,'state':'published', 'entry':{'id':row['id'],'title':row['title'],'route':row['route']}}
-def write(site_id,entry_id,payload):
+def write(site_id,entry_id,payload,actor="system"):
row=_entry(site_id,entry_id)
plan=_validate(payload)
# A draft is detached from the public entry. Only the final publish
@@ -76,6 +85,7 @@ def write(site_id,entry_id,payload):
ON CONFLICT(site_id,entry_id) DO UPDATE SET revision=excluded.revision,
blueprint_json=excluded.blueprint_json,updated_at=excluded.updated_at''',
(site_id,entry_id,next_rev,base,json.dumps(plan,ensure_ascii=False),db.now()))
+ _journal(c,site_id,entry_id,next_rev,'draft',plan,actor)
return {'saved':True,'state':'draft','revision':next_rev,'published_unchanged':True}
@@ -101,11 +111,69 @@ def publish(site_id,entry_id,expected_revision,actor):
pid=uuid.uuid4().hex
c.execute('INSERT INTO studio_publications VALUES (?,?,?,?,?,?,?)',
(pid,site_id,entry_id,draft['revision'],actor,db.now(),hashlib.sha256(json.dumps(plan,sort_keys=True).encode()).hexdigest()))
+ _journal(c,site_id,entry_id,draft['revision'],'published',plan,actor)
c.execute('DELETE FROM studio_drafts WHERE site_id=? AND entry_id=?',(site_id,entry_id))
return {'published':True,'publication_id':pid,'revision':draft['revision'],
'build_required':True,'message':'页面设计已发布到内容库;需要重新构建静态站点才会上线'}
+def _journal(connection, site_id, entry_id, revision, kind, plan, actor):
+ # Store a complete, immutable validated snapshot; never executable user code.
+ canonical=json.dumps(plan,ensure_ascii=False,sort_keys=True,separators=(',',':'))
+ connection.execute('''INSERT INTO studio_history VALUES (?,?,?,?,?,?,?,?,?)''',
+ (uuid.uuid4().hex,site_id,entry_id,revision,kind,canonical,
+ hashlib.sha256(canonical.encode('utf-8')).hexdigest(),actor,db.now()))
+
+
+def history(site_id,entry_id,limit=30):
+ _entry(site_id,entry_id)
+ if type(limit) is not int or not 1<=limit<=100:raise HTTPException(400,'历史条数必须为1-100')
+ rows=db.rows('''SELECT id,revision,kind,snapshot_hash,actor,created_at
+ FROM studio_history WHERE site_id=? AND entry_id=?
+ ORDER BY created_at DESC, revision DESC, id DESC LIMIT ?''',(site_id,entry_id,limit))
+ return {'items':rows,'note':'历史记录不会直接修改已发布页面;恢复时只生成新的草稿修订'}
+
+
+def history_snapshot(site_id,entry_id,history_id):
+ _entry(site_id,entry_id)
+ row=db.one('SELECT * FROM studio_history WHERE site_id=? AND entry_id=? AND id=?',
+ (site_id,entry_id,history_id))
+ if not row:raise HTTPException(404,'历史记录不存在')
+ if hashlib.sha256(row['snapshot_json'].encode('utf-8')).hexdigest()!=row['snapshot_hash']:
+ raise HTTPException(409,'历史快照完整性校验失败,不允许恢复')
+ plan=_validate(json.loads(row['snapshot_json']))
+ return {'id':row['id'],'revision':row['revision'],'kind':row['kind'],
+ 'snapshot_hash':row['snapshot_hash'],'plan':plan}
+
+
+def restore_history(site_id,entry_id,history_id,expected_revision,actor):
+ if type(expected_revision) is not int:raise HTTPException(400,'必须指定当前草稿修订号')
+ previous=history_snapshot(site_id,entry_id,history_id)
+ # Optimistic revision is enforced by write(). Restore never directly alters
+ # published content and cannot bypass a conflicting published page.
+ plan={**previous['plan'],'expected_revision':expected_revision}
+ result=write(site_id,entry_id,plan,actor)
+ return {**result,'restored_from':history_id,'requires_publish':True}
+
+
+@router.get('/{site_id}/pages/{entry_id}/studio-history')
+def api_history(request:Request,site_id:str,entry_id:str,limit:int=30):
+ enterprise.require(request,site_id,'viewer')
+ return history(site_id,entry_id,limit)
+
+
+@router.get('/{site_id}/pages/{entry_id}/studio-history/{history_id}')
+def api_history_snapshot(request:Request,site_id:str,entry_id:str,history_id:str):
+ enterprise.require(request,site_id,'viewer')
+ return history_snapshot(site_id,entry_id,history_id)
+
+
+@router.post('/{site_id}/pages/{entry_id}/studio-history/{history_id}/restore')
+def api_history_restore(request:Request,site_id:str,entry_id:str,history_id:str,payload:dict=Body(...)):
+ enterprise.require(request,site_id,'editor')
+ return restore_history(site_id,entry_id,history_id,payload.get('expected_revision'),enterprise.actor(request))
+
+
@router.get('/{site_id}/pages/{entry_id}/studio-draft')
def api_read(request:Request,site_id:str,entry_id:str):
enterprise.require(request,site_id,'viewer')
@@ -115,7 +183,7 @@ def api_read(request:Request,site_id:str,entry_id:str):
@router.put('/{site_id}/pages/{entry_id}/studio-draft')
def api_write(request:Request,site_id:str,entry_id:str,payload:dict=Body(...)):
enterprise.require(request,site_id,'editor')
- return write(site_id,entry_id,payload)
+ return write(site_id,entry_id,payload,enterprise.actor(request))
@router.post('/{site_id}/pages/{entry_id}/studio-draft/publish')
diff --git a/app/templates/editor.html b/app/templates/editor.html
index d2f84c7..37e47bf 100644
--- a/app/templates/editor.html
+++ b/app/templates/editor.html
@@ -1,27 +1,61 @@
-SiteForge Studio · 可视化建站
+SiteForge Studio · 可视化建站
diff --git a/app/templates/production-v8.html b/app/templates/production-v8.html
index 1bdd79e..4043afd 100644
--- a/app/templates/production-v8.html
+++ b/app/templates/production-v8.html
@@ -4,7 +4,7 @@
[hidden]{display:none!important}.tool-switch{display:flex;flex-wrap:wrap;gap:9px;background:#fff;padding:12px;margin:18px 0 0;border-radius:12px;border:1px solid #e1e8f1}.tool-btn{background:#f6f8fc;color:#50627c;border-color:transparent;padding:11px 14px}.tool-btn.active{background:#e8f0ff;color:#174ea7;border:1px solid #a5c5f5}.tool-help{font-size:12px;color:#64748b;margin:12px 5px 16px}.advanced-layout{grid-template-columns:minmax(0,1fr)}.advanced-layout>.panel{width:100%}.nav a:first-child{border:1px solid #7793bd;padding:9px 11px;border-radius:7px;background:#fff1}@media(max-width:720px){.tool-switch{gap:5px}.tool-btn{padding:9px 11px}}
-
+
选择要操作的网站
所有批次、模板和构建记录均按网站隔离;新网站可先在网站工作台创建。
@@ -51,6 +51,11 @@
+
+
+
+
+ 上传有权迁移的 WordPress WXR 文件,可检查路径冲突、复杂字段和动态功能。预检不等于迁移完成。
选择站点并新建迁移会话。每页请求独立保存检查点;采集完成后可明确审核 URL、SEO、缺失内容。
@@ -128,6 +133,7 @@ $('preflight').onclick=()=>applyTemplate(false);$('apply').onclick=()=>applyTemp
$('build').onclick=async()=>{try{show('buildResult',await api(`/api/sites/${current()}/builds`,'POST',{mirror_images:false}))}catch(e){show('buildResult','错误:'+e.message)}};
async function loadWpSessions(){try{if(!current())return;const result=await api(`/api/migrations/sites/${current()}/wordpress/sessions`);const select=$('wpSession');const chosen=select.value;select.replaceChildren(...result.items.map(s=>opt(s.id,`${s.origin} · ${s.state} · ${s.fetched} 条`)));if(result.items.some(s=>s.id===chosen))select.value=chosen;if(select.value){const item=await api(`/api/migrations/sites/${current()}/wordpress/sessions/${select.value}`);show('wpResult',item)}}catch(e){show('wpResult','查询错误:'+e.message)}}
async function wpAction(action){const sid=$('wpSession').value;if(!sid)throw Error('请先创建迁移会话');return api(`/api/migrations/sites/${current()}/wordpress/sessions/${sid}/${action}`,'POST',{})}
+$('wpPreflight').onclick=async()=>{try{if(!current())throw Error('请先选择网站');const file=$('wpPreflightFile').files[0];if(!file)throw Error('请选择 WordPress WXR XML 文件');const form=new FormData();form.append('file',file);const result=await api(`/api/migrations/sites/${current()}/wordpress/wxr-preflight`,'POST',form);show('wpPreflightResult',result)}catch(e){show('wpPreflightResult','兼容性预检失败:'+e.message)}};
$('wpCreate').onclick=async()=>{try{const data=await api(`/api/migrations/sites/${current()}/wordpress/sessions`,'POST',{origin:$('wpOrigin').value,max_items:Number($('wpLimit').value)});await loadWpSessions();$('wpSession').value=data.id;show('wpResult',data)}catch(e){show('wpResult','错误:'+e.message)}};
let wpAutoRunning=false;
$('wpAuto').onclick=async()=>{if(wpAutoRunning)return;wpAutoRunning=true;let count=0;try{while(wpAutoRunning&&count<200){const r=await wpAction('advance');show('wpResult',r);count++;if(['ready','completed','cancelled','limit_reached'].includes(r.state))break;await new Promise(done=>setTimeout(done,350))}await loadWpSessions()}catch(e){show('wpResult','暂停:'+e.message)}finally{wpAutoRunning=false}};
diff --git a/app/templates/workspace.html b/app/templates/workspace.html
index be67c9f..a08dd11 100644
--- a/app/templates/workspace.html
+++ b/app/templates/workspace.html
@@ -16,6 +16,7 @@
+
⌘高级工具 ⌄
diff --git a/app/theme_pro.py b/app/theme_pro.py
index cd2f16e..49c30e1 100644
--- a/app/theme_pro.py
+++ b/app/theme_pro.py
@@ -16,15 +16,15 @@ from markupsafe import Markup
from .core import sanitize, route_path, THEMES
ROOT=Path(__file__).resolve().parent.parent
+LOADER=Environment(loader=FileSystemLoader(str(ROOT/'themes')),autoescape=select_autoescape(['html','xml']))
+
_ENVS={}
def env_for(theme):
"""Per-theme Jinja env: theme dir first, shared themes/ root as fallback.
Lets a theme override shared macros (_professional_components.html) while
- inheriting everything else. Cached: one env per theme across large builds.
- Only built-in theme names are accepted; callers are expected to enforce the
- THEMES whitelist too (render does), this is defense in depth.
+ inheriting everything else. Cached per theme; degraded envs are not cached.
"""
if theme not in THEMES:raise ValueError('未知主题')
env=_ENVS.get(theme)
@@ -33,7 +33,7 @@ def env_for(theme):
canonical=theme_dir.is_dir()
search=[str(theme_dir),str(ROOT/'themes')] if canonical else [str(ROOT/'themes')]
env=Environment(loader=FileSystemLoader(search),autoescape=select_autoescape(['html','xml']))
- if canonical:_ENVS[theme]=env # degraded envs are not cached
+ if canonical:_ENVS[theme]=env
return env
TITLES={'product':'产品中心','vehicle':'车型中心','post':'新闻资讯','case':'项目案例','brand':'品牌中心','service':'服务与解决方案'}
@@ -189,6 +189,7 @@ def make_context(site,page,records,mode='detail',listing_items=None,prepared=Non
'features':groups['service'][:3], 'stories':groups['post'][:3], 'cases':groups['case'][:3],
'brands':assets['brands'],'categories':assets['categories'], 'nav':nav,
'chrome':chrome,'chrome_is_custom':bool(settings.get('site_design')),
+ 'mobile_tab_icons':{'home':'⌂','grid':'▦','products':'▤','news':'☷','contact':'✉','phone':'☎','info':'ⓘ','search':'⌕','menu':'☰','star':'☆'},
'footer_nav':nav if chrome['footer']['show_nav'] else [],'specs':spec,
'related':related,'fields':detail_fields,'content':Markup(sanitize(pg.get('body') or '')),
'title':title, 'seo_title':pg.get('seo_title') or title,'seo_description':pg.get('seo_description') or pg.get('summary') or site.get('tagline') or '',
diff --git a/app/theme_store.py b/app/theme_store.py
index 6536327..3be3a1e 100644
--- a/app/theme_store.py
+++ b/app/theme_store.py
@@ -8,10 +8,10 @@ from __future__ import annotations
import io, json, re, zipfile, shutil
from pathlib import Path
from . import db
-from .core import THEMES as BUILTIN
BASE=Path(__file__).resolve().parent.parent/'themes'
THEME_ROOT=db.DATA/'theme_packages'
+from .core import THEMES as BUILTIN
THEME_ID=re.compile(r'^[a-z][a-z0-9-]{2,39}$')
VERSION=re.compile(r'^\d+\.\d+\.\d+$')
diff --git a/app/v8_workflows.py b/app/v8_workflows.py
index cd1acb3..81ff642 100644
--- a/app/v8_workflows.py
+++ b/app/v8_workflows.py
@@ -192,6 +192,13 @@ def parse_wxr(content):
if len(content)>MAX_UPLOAD:raise ValueError('XML 文件超过限制')
root=ET.fromstring(content)
if root.tag!='rss':raise ValueError('这不是 WordPress WXR RSS 导出')
+ # Stage only unambiguous permanent paths; dynamic functions remain review items.
+ from .wp_complex_v820 import inspect_wxr
+ checks=inspect_wxr(content)
+ blockers=[x for x in checks['issues'] if x['code'] in ('invalid_permalink','duplicate_route')]
+ if blockers or (checks['findings_truncated'] and checks['blocking_issues']):
+ raise ValueError('存在不能安全映射的 WordPress URL,请先运行 V8.20 WXR 预检并修正:'+
+ ', '.join(x['code']+':'+x['route'] for x in blockers[:5]))
rows=[];ids=[];warnings=[]
for it in root.findall('./channel/item'):
get=lambda name:next((str(node.text or '') for node in it if node.tag.endswith('}'+name) or node.tag==name),'')
@@ -216,12 +223,23 @@ def parse_wxr(content):
if k:metas[k]=v[:10000]
if any(k in metas for k in ('_elementor_data','_product_attributes')):
warnings.append({'row':len(rows)+1,'issue':'含复杂 Elementor/WooCommerce 组件,导入后需人工核对'})
- rows.append({'标题':title,'路径':route,'正文':get('encoded'),
+ rows.append({'标题':title,'路径':route,'正文':get('encoded'),'类型':raw_typ,
+
'SEO标题':metas.get('_yoast_wpseo_title') or metas.get('rank_math_title',''),
'SEO描述':metas.get('_yoast_wpseo_metadesc') or metas.get('rank_math_description',''),
'分类':raw_typ})
ids.append(f'wordpress:{wid}' if wid else f'wordpress:path:{route}')
- return rows,ids,warnings
+ for finding in checks['issues']:
+ if finding['severity'] not in ('review','blocker'):continue
+ if finding['code'] in ('invalid_permalink','duplicate_route'):continue
+ route=finding['route']
+ index=next((i+1 for i,row in enumerate(rows) if row['路径']==route),0)
+ if index:
+ if finding['code'].startswith('elementor') and any(w.get('row')==index and 'Elementor' in w.get('issue','') for w in warnings):
+ continue
+ warnings.append({'row':index,'issue':finding['code']+': '+finding['message']})
+ # Bound warnings written into batch metadata and shown for human review.
+ return rows,ids,warnings[:200]
@router.post('/{site_id}/staging')
def api_stage(request:Request,site_id:str,payload:dict=Body(...)):
diff --git a/app/version.py b/app/version.py
index 8daa9d3..f995492 100644
--- a/app/version.py
+++ b/app/version.py
@@ -1,5 +1,5 @@
"""Single source of truth for the SiteForge platform release identity."""
import os
-VERSION = "8.12.0-dev.1"
+VERSION = "8.21.3-dev.1"
PLATFORM_NAME = "SiteForge Factory"
BUILD_SHA = os.environ.get("SITEFORGE_GIT_SHA", "unknown").strip()[:40] or "unknown"
diff --git a/app/worker.py b/app/worker.py
index 70203de..48bd122 100644
--- a/app/worker.py
+++ b/app/worker.py
@@ -6,7 +6,7 @@ import subprocess
import sys
import time
import uuid
-from . import db, enterprise, builder, jobs
+from . import db, enterprise, builder, jobs, worker_health
running=True
WORKER_ID=uuid.uuid4().hex[:12]
@@ -17,7 +17,7 @@ def heartbeat():
This single-worker probe is deliberately not used as a multi-worker
membership system; multi-worker orchestration needs separate leases.
"""
- path=db.DATA/'worker-heartbeat'
+ path=worker_health.heartbeat_path()
path.parent.mkdir(parents=True,exist_ok=True)
temp=path.with_suffix(f'.{WORKER_ID}.tmp')
temp.write_text(WORKER_ID,encoding='ascii')
@@ -49,11 +49,17 @@ def complete(job, value=None, error=None, retryable=False, cancelled=False, time
current=c.execute('SELECT * FROM build_jobs WHERE id=?',(job['id'],)).fetchone()
if not current or current['state']!='running' or current['worker_id']!=WORKER_ID:
return
+ # A networked release may already have changed the remote pointer.
+ # Cancellation/timeout/crash is UNKNOWN remote outcome, not safe retry.
+ external=current['kind'] in ('remote_deploy','remote_rollback')
if cancelled or current['cancel_requested']:
- state='cancelled'
+ if external:
+ state='failed'
+ error='Release interrupted; reconcile target before manual retry'
+ else:state='cancelled'
elif error is None:
state='done'
- elif retryable and current['attempts']MAX_META:raise ValueError('单篇文章元数据字段超过安全上限')
+ return result,duplicate[:30]
+
+
+def extract_acf(meta):
+ """Bounded, literal ACF postmeta extraction. Never PHP-unserialize or infer field schema.
+
+ Can safely represent flat fields and simple `repeater_0_subfield` records.
+ Repeater field types are not guaranteed by the exported meta alone.
+ """
+ if len(meta)>MAX_META:raise ValueError('ACF meta exceeds safety limit')
+ scalar={};repeaters={};warnings=[]
+ referenced={k[1:] for k,v in meta.items() if k.startswith('_') and isinstance(v,str) and v.startswith('field_')}
+ for key in sorted(referenced):
+ if not key or key not in meta:continue
+ value=meta[key]
+ if not isinstance(value,str):continue
+ if SERIALIZED.match(value.strip()):
+ warnings.append(f'ACF 字段 {key[:75]} 含 PHP 序列化结构,未进行反序列化')
+ continue
+ if len(value.encode('utf-8'))>10_000:
+ warnings.append(f'ACF 字段 {key[:75]} 超出单值安全上限')
+ continue
+ match=re.fullmatch(r'(.{1,80})_(\d{1,2})_([A-Za-z][A-Za-z0-9_]*)',key)
+ if match:
+ group,index,field=match.group(1),int(match.group(2)),match.group(3)
+ if index>=50:continue
+ repeaters.setdefault(group,{}).setdefault(index,{})[field]=value
+ elif re.fullmatch(r'[A-Za-z][A-Za-z0-9_]{0,100}',key):
+ scalar[key]=value
+ result={}
+ for group,values in sorted(repeaters.items()):
+ count=meta.get(group)
+ if not (isinstance(count,str) and count.isdigit() and 1<=int(count)<=50):
+ warnings.append(f'ACF 类重复字段 {group} 缺少有效行数,保留原始键供人工核对')
+ continue
+ expected=int(count)
+ if any(i>=expected for i in values):
+ warnings.append(f'ACF 类重复字段 {group} 存在超出声明行数的字段')
+ # Only expose a complete contiguous set. Partial repeaters remain metadata-only.
+ if any(i not in values for i in range(expected)):
+ warnings.append(f'ACF 类重复字段 {group} 行不连续,必须人工复核')
+ continue
+ if sum(len(x) for x in values.values())>1500:
+ warnings.append(f'ACF 类重复字段 {group} 子字段超出安全上限')
+ continue
+ result[group]=[values[i] for i in range(expected)]
+ if repeaters:
+ warnings.append('ACF Repeater 由字段命名模式推断;必须与源站字段组定义核对')
+ return {'fields':scalar,'repeaters':result,'warnings':list(dict.fromkeys(warnings))[:80]}
+
+
+def inspect_wxr(data:bytes):
+ if not data or len(data)>MAX_XML:raise ValueError('WXR 必须非空且不超过 16 MB')
+ root=SafeET.fromstring(data)
+ if root.tag.rsplit('}',1)[-1]!='rss':raise ValueError('需要 WordPress WXR RSS XML')
+ items=root.findall('./channel/item')
+ if not items:raise ValueError('WXR 缺少 channel/item 记录')
+ if len(items)>MAX_ITEMS:raise ValueError('WXR 超过 20000 条记录上限')
+ counts=Counter();issues=[];routes={};features=Counter();example=[];hosts=set()
+ def issue(severity,code,wp_id,message,route=''):
+ counts['findings_'+severity]+=1
+ if len(issues)10:break
+ if meta.get('_elementor_data'):
+ features['elementor']+=1
+ result=wordpress_compat.elementor_to_html(meta['_elementor_data'])
+ if result['warnings']:
+ issue('review','elementor',wp_id,'Elementor 非无损转换:'+result['warnings'][0],route)
+ else:issue('review','elementor_layout',wp_id,'Elementor 样式、容器、响应式布局需要视觉对照验收',route)
+ body=next((e.text or '' for e in node if e.tag.startswith('{http://purl.org/rss/1.0/modules/content/}') and e.tag.endswith('encoded')),'')
+ s=BeautifulSoup(body,'html.parser')
+ if SHORTCODE.search(body):
+ features['dynamic_shortcodes']+=1
+ issue('blocker','dynamic_shortcode',wp_id,'存在动态短代码,纯静态导出不能提供同等业务行为',route)
+ if s.select('form,iframe'):
+ features['dynamic_html']+=1
+ issue('review','dynamic_html',wp_id,'表单或 iframe 需单独确认服务、第三方依赖与可用性',route)
+ if any(n.get('src','').startswith(('http://','https://')) for n in s.select('img[src]')):
+ features['external_images']+=1
+ issue('review','external_media',wp_id,'正文使用远程图片;需执行本地化及完整性对账',route)
+ if kind not in ('post','page','product','service','case','brand','vehicle'):
+ features['custom_post_types']+=1
+ issue('review','custom_post_type',wp_id,'自定义文章类型 '+kind[:60]+' 将映射 other;模板/字段关系需人工确认',route)
+ blocked=counts['findings_blocker']
+ from .version import VERSION
+ return {'version':VERSION,'input_bytes':len(data),'counts':dict(counts),
+ 'unique_published_routes':len(routes),'features':dict(features),
+ 'issues':issues,'findings_truncated':sum(v for k,v in counts.items() if k.startswith('findings_'))>len(issues),
+ 'sample_routes':example,'source_hosts':sorted(hosts),'ready_for_automatic_publish':False,
+ 'blocking_issues':blocked,
+ 'note':'仅离线识别兼容性与风险;预检不改动站点、不执行 PHP,不构成 1:1 迁移验收。'}
+
+
+@router.post('/{site_id}/wordpress/wxr-preflight')
+async def wxr_preflight(request:Request,site_id:str,file:UploadFile=File(...)):
+ enterprise.require(request,site_id,'editor')
+ data=await file.read(MAX_XML+1)
+ try:return inspect_wxr(data)
+ except (ValueError,SafeET.ParseError) as exc:raise HTTPException(400,str(exc))
diff --git a/app/wp_migration_v82.py b/app/wp_migration_v82.py
index d391102..340982f 100644
--- a/app/wp_migration_v82.py
+++ b/app/wp_migration_v82.py
@@ -3,6 +3,7 @@ and durable worker-backed REST scanning. All flows require explicit authorizatio
and remain subject to the existing SiteForge release-review gates.
"""
from __future__ import annotations
+import re
import hashlib
import json
import uuid
@@ -160,15 +161,36 @@ def _srcset_urls(value):
return result
+# CSS background images are common in Elementor containers and ACF galleries.
+# Restrict to syntactically explicit remote URLs, never evaluate arbitrary CSS.
+CSS_URL = re.compile(r'''url\(\s*([\"\']?)((?:https?:)?//[^\"\')\s]+)\1\s*\)''', re.I)
+
+
+def _css_urls(raw):
+ return [match.group(2) for match in CSS_URL.finditer(str(raw or ''))]
+
+
+def _rewrite_css_urls(raw,converted):
+ def replace(match):
+ url=match.group(2)
+ if url not in converted:return match.group(0)
+ quote=match.group(1)
+ return f'url({quote}{converted[url]}{quote})'
+ updated=CSS_URL.sub(replace,str(raw or ''))
+ return updated,updated!=str(raw or '')
+
+
def _media_urls(content):
- """Collect all remote media references, including responsive and lazy images."""
+ """Collect images plus responsive/lazy/CSS backgrounds for explicit review."""
urls=[content.get('cover') or '']
soup=BeautifulSoup(content.get('body') or '','html.parser')
- for tag in soup.find_all(['img','source','video']):
- for key in ('src','data-src','poster'):
+ for tag in soup.find_all(True):
+ for key in ('src','data-src','poster','data-background','data-background-image'):
if tag.get(key):urls.append(tag[key])
for key in ('srcset','data-srcset'):
urls.extend(_srcset_urls(tag.get(key)))
+ if tag.get('style'):urls.extend(_css_urls(tag['style']))
+ if tag.name=='style':urls.extend(_css_urls(tag.get_text()))
return list(dict.fromkeys(u for u in urls if isinstance(u,str)
and u.startswith(('https://','http://','//'))))
@@ -187,8 +209,8 @@ def _rewrite_srcset(value,converted):
def _rewrite_media_html(body,converted):
soup=BeautifulSoup(body,'html.parser');changed=False
- for tag in soup.find_all(['img','source','video']):
- for key in ('src','data-src','poster'):
+ for tag in soup.find_all(True):
+ for key in ('src','data-src','poster','data-background','data-background-image'):
if tag.get(key) in converted:
tag[key]=converted[tag[key]];changed=True
for key in ('srcset','data-srcset'):
@@ -196,11 +218,16 @@ def _rewrite_media_html(body,converted):
new,updated=_rewrite_srcset(tag[key],converted)
if updated:
tag[key]=new;changed=True
- # A single redundant srcset has no value and can be removed.
if tag.name=='img' and key=='srcset' and len(new.split(','))==1:
fields=new.strip().split()
if fields and fields[0]==tag.get('src'):
tag.attrs.pop(key,None)
+ if tag.get('style'):
+ value,updated=_rewrite_css_urls(tag['style'],converted)
+ if updated:tag['style']=value;changed=True
+ if tag.name=='style':
+ value,updated=_rewrite_css_urls(tag.string if tag.string is not None else tag.get_text(),converted)
+ if updated:tag.string=value;changed=True
return str(soup) if changed else body,changed
diff --git a/compose.agent-secrets.example.yaml b/compose.agent-secrets.example.yaml
new file mode 100644
index 0000000..4abfb0a
--- /dev/null
+++ b/compose.agent-secrets.example.yaml
@@ -0,0 +1,5 @@
+# Run on the isolated remote Agent host; never commit ./private-secrets.
+services:
+ agent:
+ volumes:
+ - ./private-secrets:/run/secrets:ro
diff --git a/compose.enterprise.yaml b/compose.enterprise.yaml
index 76b6a5c..f886952 100644
--- a/compose.enterprise.yaml
+++ b/compose.enterprise.yaml
@@ -1,4 +1,4 @@
-name: siteforge-v8-12-enterprise
+name: siteforge-v8-18-enterprise
services:
siteforge:
build:
@@ -6,7 +6,7 @@ services:
dockerfile: Dockerfile.enterprise
args:
SITEFORGE_GIT_SHA: ${SITEFORGE_GIT_SHA:-unknown}
- image: siteforge/web:8.12.0-dev.1
+ image: siteforge/web:8.21.3-dev.1
init: true
restart: unless-stopped
ports:
@@ -42,13 +42,13 @@ services:
cap_drop: [ALL]
security_opt: ['no-new-privileges:true']
healthcheck:
- test: ['CMD','python','-c',"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8787/api/health',timeout=3)"]
+ test: ['CMD','python','-c',"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8787/api/ready',timeout=3)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
worker:
- image: siteforge/web:8.12.0-dev.1
+ image: siteforge/web:8.21.3-dev.1
command: ['python','-m','app.worker']
init: true
restart: unless-stopped
@@ -64,6 +64,12 @@ services:
SITEFORGE_MYSQL_READ_PASSWORD: ${SITEFORGE_MYSQL_READ_PASSWORD:-}
SITEFORGE_DATABASE_URL: postgresql://${SITEFORGE_PG_USER:-siteforge}:${SITEFORGE_PG_PASSWORD:?Set PG password}@postgres:5432/${SITEFORGE_PG_DB:-siteforge}
SITEFORGE_REDIS_URL: redis://redis:6379/0
+ SITEFORGE_DEPLOY_STAGING_URL: ${SITEFORGE_DEPLOY_STAGING_URL:-}
+ SITEFORGE_DEPLOY_STAGING_SECRET_REF: ${SITEFORGE_DEPLOY_STAGING_SECRET_REF:-}
+ SITEFORGE_DEPLOY_PRODUCTION_URL: ${SITEFORGE_DEPLOY_PRODUCTION_URL:-}
+ SITEFORGE_DEPLOY_PRODUCTION_SECRET_REF: ${SITEFORGE_DEPLOY_PRODUCTION_SECRET_REF:-}
+ SITEFORGE_STAGING_TOKEN: ${SITEFORGE_STAGING_TOKEN:-}
+ SITEFORGE_PRODUCTION_TOKEN: ${SITEFORGE_PRODUCTION_TOKEN:-}
SITEFORGE_BUILD_TIMEOUT_SECONDS: ${SITEFORGE_BUILD_TIMEOUT_SECONDS:-300}
SITEFORGE_BUILD_MAX_ATTEMPTS: ${SITEFORGE_BUILD_MAX_ATTEMPTS:-3}
depends_on:
diff --git a/compose.publish-targets.example.yaml b/compose.publish-targets.example.yaml
new file mode 100644
index 0000000..01b0a07
--- /dev/null
+++ b/compose.publish-targets.example.yaml
@@ -0,0 +1,40 @@
+# Example only: enable with docker compose -f compose.yaml -f compose.publish-targets.example.yaml up -d --build
+# Web receives metadata/config; private keys and IAM credentials are used ONLY by worker.
+services:
+ siteforge:
+ environment:
+ SITEFORGE_DEPLOY_STAGING_TYPE: sftp
+ SITEFORGE_DEPLOY_STAGING_HOST: ${SITEFORGE_STAGING_SFTP_HOST:?Provide authorized host}
+ SITEFORGE_DEPLOY_STAGING_PORT: "${SITEFORGE_STAGING_SFTP_PORT:-22}"
+ SITEFORGE_DEPLOY_STAGING_USERNAME: ${SITEFORGE_STAGING_SFTP_USER:?Provide publisher account}
+ SITEFORGE_DEPLOY_STAGING_REMOTE_ROOT: ${SITEFORGE_STAGING_REMOTE_ROOT:-/srv/siteforge/sites}
+ SITEFORGE_DEPLOY_STAGING_KNOWN_HOSTS_FILE: /run/secrets/siteforge_known_hosts
+ SITEFORGE_DEPLOY_STAGING_SSH_KEY_FILE: /run/secrets/siteforge_publish_key
+ SITEFORGE_DEPLOY_STAGING_PUBLIC_URL: ${SITEFORGE_STAGING_PUBLIC_URL:?Set HTTPS public site URL}
+ SITEFORGE_DEPLOY_PRODUCTION_TYPE: s3
+ SITEFORGE_DEPLOY_PRODUCTION_BUCKET: ${SITEFORGE_PRODUCTION_S3_BUCKET:?Set bucket}
+ SITEFORGE_DEPLOY_PRODUCTION_REGION: ${SITEFORGE_PRODUCTION_S3_REGION:?Set AWS region}
+ SITEFORGE_DEPLOY_PRODUCTION_PREFIX: ${SITEFORGE_PRODUCTION_S3_PREFIX:-siteforge}
+ SITEFORGE_DEPLOY_PRODUCTION_POINTER_AWARE: "1"
+ SITEFORGE_DEPLOY_PRODUCTION_PUBLIC_URL: ${SITEFORGE_PRODUCTION_PUBLIC_URL:?Set pointer-aware gateway HTTPS URL}
+ worker:
+ environment:
+ SITEFORGE_DEPLOY_STAGING_TYPE: sftp
+ SITEFORGE_DEPLOY_STAGING_HOST: ${SITEFORGE_STAGING_SFTP_HOST:?Provide authorized host}
+ SITEFORGE_DEPLOY_STAGING_PORT: "${SITEFORGE_STAGING_SFTP_PORT:-22}"
+ SITEFORGE_DEPLOY_STAGING_USERNAME: ${SITEFORGE_STAGING_SFTP_USER:?Provide publisher account}
+ SITEFORGE_DEPLOY_STAGING_REMOTE_ROOT: ${SITEFORGE_STAGING_REMOTE_ROOT:-/srv/siteforge/sites}
+ SITEFORGE_DEPLOY_STAGING_KNOWN_HOSTS_FILE: /run/secrets/siteforge_known_hosts
+ SITEFORGE_DEPLOY_STAGING_SSH_KEY_FILE: /run/secrets/siteforge_publish_key
+ SITEFORGE_DEPLOY_STAGING_PUBLIC_URL: ${SITEFORGE_STAGING_PUBLIC_URL:?Set HTTPS public site URL}
+ SITEFORGE_DEPLOY_PRODUCTION_TYPE: s3
+ SITEFORGE_DEPLOY_PRODUCTION_BUCKET: ${SITEFORGE_PRODUCTION_S3_BUCKET:?Set bucket}
+ SITEFORGE_DEPLOY_PRODUCTION_REGION: ${SITEFORGE_PRODUCTION_S3_REGION:?Set AWS region}
+ SITEFORGE_DEPLOY_PRODUCTION_PREFIX: ${SITEFORGE_PRODUCTION_S3_PREFIX:-siteforge}
+ SITEFORGE_DEPLOY_PRODUCTION_POINTER_AWARE: "1"
+ SITEFORGE_DEPLOY_PRODUCTION_PUBLIC_URL: ${SITEFORGE_PRODUCTION_PUBLIC_URL:?Set pointer-aware gateway HTTPS URL}
+ AWS_REGION: ${SITEFORGE_PRODUCTION_S3_REGION:?Set AWS region}
+ # Prefer IAM instance/service role; never put AWS static keys into .env.
+ volumes:
+ - ./secrets/publish_known_hosts:/run/secrets/siteforge_known_hosts:ro
+ - ./secrets/publish_key:/run/secrets/siteforge_publish_key:ro
diff --git a/compose.release-agent.yaml b/compose.release-agent.yaml
index 1833b2a..192a2a4 100644
--- a/compose.release-agent.yaml
+++ b/compose.release-agent.yaml
@@ -6,11 +6,12 @@ services:
dockerfile: Dockerfile.enterprise
args:
SITEFORGE_GIT_SHA: ${SITEFORGE_GIT_SHA:-unknown}
- image: siteforge/web:8.12.0-dev.1
+ image: siteforge/web:8.21.0-dev.1
command: ['python','-m','uvicorn','app.deploy_agent:app','--host','0.0.0.0','--port','8790']
restart: unless-stopped
environment:
- SITEFORGE_DEPLOY_SECRET: ${SITEFORGE_DEPLOY_SECRET:?Set 32+ character release secret}
+ SITEFORGE_DEPLOY_SECRET: ${SITEFORGE_DEPLOY_SECRET:-}
+ SITEFORGE_DEPLOY_SECRET_REF: ${SITEFORGE_DEPLOY_SECRET_REF:-}
SITEFORGE_DEPLOY_ROOT: /srv/releases
volumes:
- releases:/srv/releases
diff --git a/compose.release-secrets.example.yaml b/compose.release-secrets.example.yaml
new file mode 100644
index 0000000..6a3cfbd
--- /dev/null
+++ b/compose.release-secrets.example.yaml
@@ -0,0 +1,6 @@
+# OPTIONAL deployment overlay; copy/rename and adjust paths under local administrator control.
+# WARNING: Do not place private keys or real tokens in this repository.
+services:
+ worker:
+ volumes:
+ - ./private-secrets:/run/secrets:ro
diff --git a/compose.s3-gateway.example.yaml b/compose.s3-gateway.example.yaml
new file mode 100644
index 0000000..60c28d0
--- /dev/null
+++ b/compose.s3-gateway.example.yaml
@@ -0,0 +1,27 @@
+# Deploy separately behind a proper TLS proxy. No publication credentials.
+# Example: docker compose -f compose.yaml -f compose.s3-gateway.example.yaml up -d --build
+services:
+ s3-site-gateway:
+ image: siteforge/web:8.21.0-dev.1
+ depends_on:
+ siteforge: {condition: service_started}
+ command: ["python", "-m", "uvicorn", "app.s3_gateway:app", "--host", "0.0.0.0", "--port", "8791"]
+ restart: unless-stopped
+ environment:
+ SITEFORGE_S3_GATEWAY_BUCKET: ${SITEFORGE_PRODUCTION_S3_BUCKET:?Set S3 bucket}
+ SITEFORGE_S3_GATEWAY_REGION: ${SITEFORGE_PRODUCTION_S3_REGION:?Set region}
+ SITEFORGE_S3_GATEWAY_PREFIX: ${SITEFORGE_PRODUCTION_S3_PREFIX:-siteforge}
+ # Supply read-only IAM role via workload identity; do not put keys in source files.
+ ports:
+ - "127.0.0.1:${SITEFORGE_GATEWAY_PORT:-8791}:8791"
+ read_only: true
+ tmpfs:
+ - /tmp:size=64m,mode=1777
+ cap_drop: [ALL]
+ security_opt: ["no-new-privileges:true"]
+ pids_limit: 64
+ healthcheck:
+ test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8791/health', timeout=3)"]
+ interval: 30s
+ timeout: 5s
+ retries: 3
diff --git a/compose.yaml b/compose.yaml
index 192966b..25d47ee 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -6,7 +6,7 @@ services:
dockerfile: Dockerfile
args:
SITEFORGE_GIT_SHA: ${SITEFORGE_GIT_SHA:-unknown}
- image: siteforge/web:8.12.0-dev.1
+ image: siteforge/web:8.21.3-dev.1
init: true
restart: unless-stopped
ports:
@@ -21,6 +21,10 @@ services:
SITEFORGE_ADMIN_PASSWORD: ${SITEFORGE_ADMIN_PASSWORD:?Set SITEFORGE_ADMIN_PASSWORD in .env}
SITEFORGE_SESSION_SECRET: ${SITEFORGE_SESSION_SECRET:?Set SITEFORGE_SESSION_SECRET in .env}
SITEFORGE_COOKIE_SECURE: "${SITEFORGE_COOKIE_SECURE:-false}"
+ SITEFORGE_DEPLOY_STAGING_URL: ${SITEFORGE_DEPLOY_STAGING_URL:-}
+ SITEFORGE_DEPLOY_STAGING_SECRET_REF: ${SITEFORGE_DEPLOY_STAGING_SECRET_REF:-}
+ SITEFORGE_DEPLOY_PRODUCTION_URL: ${SITEFORGE_DEPLOY_PRODUCTION_URL:-}
+ SITEFORGE_DEPLOY_PRODUCTION_SECRET_REF: ${SITEFORGE_DEPLOY_PRODUCTION_SECRET_REF:-}
SITEFORGE_PUBLISH_ROOT: /published
SITEFORGE_REQUIRE_RELEASE_APPROVAL: "true"
volumes:
@@ -35,13 +39,13 @@ services:
- ALL
pids_limit: 128
healthcheck:
- test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8787/api/health', timeout=3)"]
+ test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8787/api/ready', timeout=3)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
worker:
- image: siteforge/web:8.12.0-dev.1
+ image: siteforge/web:8.21.3-dev.1
depends_on:
siteforge: {condition: service_started}
init: true
@@ -58,6 +62,12 @@ services:
# Development-only env secret references; use compose.mysql-secrets.yaml for production files.
SITEFORGE_MYSQL_READ_USER: ${SITEFORGE_MYSQL_READ_USER:-}
SITEFORGE_MYSQL_READ_PASSWORD: ${SITEFORGE_MYSQL_READ_PASSWORD:-}
+ SITEFORGE_DEPLOY_STAGING_URL: ${SITEFORGE_DEPLOY_STAGING_URL:-}
+ SITEFORGE_DEPLOY_STAGING_SECRET_REF: ${SITEFORGE_DEPLOY_STAGING_SECRET_REF:-}
+ SITEFORGE_DEPLOY_PRODUCTION_URL: ${SITEFORGE_DEPLOY_PRODUCTION_URL:-}
+ SITEFORGE_DEPLOY_PRODUCTION_SECRET_REF: ${SITEFORGE_DEPLOY_PRODUCTION_SECRET_REF:-}
+ SITEFORGE_STAGING_TOKEN: ${SITEFORGE_STAGING_TOKEN:-}
+ SITEFORGE_PRODUCTION_TOKEN: ${SITEFORGE_PRODUCTION_TOKEN:-}
SITEFORGE_JOB_POLL_SECONDS: "1.5"
SITEFORGE_BUILD_TIMEOUT_SECONDS: "${SITEFORGE_BUILD_TIMEOUT_SECONDS:-300}"
SITEFORGE_BUILD_MAX_ATTEMPTS: "${SITEFORGE_BUILD_MAX_ATTEMPTS:-3}"
diff --git a/deploy/art-design-pro/siteforge-nginx.conf.example b/deploy/art-design-pro/siteforge-nginx.conf.example
new file mode 100644
index 0000000..70e29fe
--- /dev/null
+++ b/deploy/art-design-pro/siteforge-nginx.conf.example
@@ -0,0 +1,40 @@
+# EXAMPLE ONLY. Replace DOMAIN, absolute path, and upstream host before use.
+# The Art Design Pro SPA and SiteForge FastAPI MUST share an HTTPS origin.
+# Restrict this admin hostname with your organization's access policy.
+upstream siteforge_api {
+ server 127.0.0.1:8787;
+ keepalive 16;
+}
+server {
+ listen 443 ssl http2;
+ server_name admin.example.com;
+ ssl_certificate /etc/letsencrypt/live/admin.example.com/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;
+ root /srv/siteforge-art/dist;
+ index index.html;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Referrer-Policy strict-origin-when-cross-origin always;
+ # Configure organization-specific access control, HSTS, rate limits and logs.
+
+ # Do not cache API responses or auth redirects.
+ location ~ ^/(api/|login$|editor/|preview/|ui/|local-media/|favicon\.ico$|factory-studio$|operations$|theme-gallery$|production-v8$) {
+ proxy_pass http://siteforge_api;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Proto https;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_connect_timeout 8s;
+ proxy_read_timeout 90s;
+ add_header Cache-Control "private, no-store" always;
+ }
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
+server {
+ listen 80;
+ server_name admin.example.com;
+ return 301 https://$host$request_uri;
+}
diff --git a/deploy/native-release/README.md b/deploy/native-release/README.md
new file mode 100644
index 0000000..2d47cac
--- /dev/null
+++ b/deploy/native-release/README.md
@@ -0,0 +1,64 @@
+# V8.14 原生发布目标(SFTP / S3)运行配置
+
+> 本文件包含可以部署的代码与操作步骤。真实服务器、证书、IAM、DNS 和缓存策略必须由部署人员配置并验证。
+
+## 运行方式
+
+默认仍采用原 V8.13 HTTPS 发布 Agent,不修改已有目标配置即可继续使用。每个目标分别设定 `SITEFORGE_DEPLOY_{STAGING|PRODUCTION}_TYPE` 为 `agent`、`sftp` 或 `s3`。
+
+可选适配器及依赖版本已纳入 `requirements.lock` 和 Docker 镜像;如果镜像构建不能联网获取依赖,不会自动降级成无校验发布。
+
+### SFTP
+
+1. 创建**专用无 shell/受限权限**的发布账号,仅允许访问专属发布根目录;安装并配置支持 OpenSSH `posix-rename@openssh.com` 扩展的 SFTP 服务器。Worker 不执行任意远程 shell 命令。
+2. 在管理服务器上由管理员手动核对目标主机 SSH 公钥指纹,保存 `secrets/publish_known_hosts`。不可使用 `AutoAddPolicy` 或 `StrictHostKeyChecking=no`。
+3. 为 Worker 安装只读私钥文件 `secrets/publish_key`;强烈建议独立测试/生产密钥及不同账号,挂载到 `/run/secrets/siteforge_publish_key`。
+4. 设置目标 `HOST`、`PORT`、`USERNAME`、`REMOTE_ROOT`、`KNOWN_HOSTS_FILE`、`SSH_KEY_FILE`、`PUBLIC_URL`;`PUBLIC_URL` 必须是**真实 HTTPS 站点首页地址**(如 `https://staging.example.com/`),必须映射到该站点 `current` 目录。
+5. 执行 `docker compose -f compose.yaml -f compose.publish-targets.example.yaml up -d --build`。示例同时演示 Staging SFTP、Production S3,实际部署时应依安全需求拆分环境和变量。
+6. 执行两次不同版本的审批发布、模拟校验失败与回滚;确认当前站点始终是已经完整验证的版本。
+
+SFTP 文件布局:`REMOTE_ROOT/{site}/releases/{sha256[:20]}/...`,`current -> releases/{sha256[:20]}`。生成临时符号链接后使用原子 `posix_rename` 切换;若目标服务器不支持该扩展,则**拒绝部署**,不会使用有中间空窗的 fallback。
+
+当前原生 SFTP 使用不带口令交互的 SSH 私钥文件。如果组织要求硬件密钥、交互式证书或私钥密码,需要在运维方案中对接安全代理,不要把口令写入前端或数据库。
+
+### S3 / S3-compatible
+
+1. 创建独立存储桶或隔离前缀,赋予发布 Worker 最小必要的对象读写权限;站点网关使用**不同的、只读** IAM 身份(建议使用实例角色/工作负载身份,不要硬编码 Access Key)。
+2. 设置 `SITEFORGE_DEPLOY_PRODUCTION_TYPE=s3`、`BUCKET`、`REGION`、`PREFIX`、`POINTER_AWARE=1`、`PUBLIC_URL`。可选 `ENDPOINT_URL` 必须是 HTTPS 根地址。
+3. 发布会上传不可变 `sites/{site}/releases/{digest}/` 对象,对每个文件进行 SHA256 复核;最后条件更新 `sites/{site}/current.json`。并发指针修改会被 S3 ETag/If-Match 限制阻断。
+4. **必须部署指针感知网站网关** `app.s3_gateway:app`(示例 `compose.s3-gateway.example.yaml`),让 `https://web.example.com/{site}/` 根据 `current.json` 读取正确版本。不能直接把裸 S3 Static Website Hosting 端点当作已经实现原子切换。
+5. 网关示例为读路径提供 no-cache 和安全路径验证。对 CDN 的改写规则、主站根目录映射、缓存失效与回滚速度,必须在真实域名上验收。S3-compatible 需要支持条件写入;不支持的供应商不得声明原子发布。
+
+### 发布安全门禁
+
+- 构建状态必须是 `complete`,ZIP 路径必须处于系统归档根目录,静态质量和迁移审计满足要求。
+- 由独立审批人批准的版本才能部署;网页只提交异步任务,发布由 Worker 执行。
+- 上传/完整性校验失败不得改变当前发布指针;上线后首页内容哈希不匹配会尝试切换回旧版本。
+- 当前系统仍不保证 Worker 被强制杀死、网络分区发生在指针切换瞬间时具有跨进程数据库+远程站点的**严格分布式事务**;应当增加带审计的 reconciliation 机制后才能视为生产最终验收通过。
+
+### 测试和运维验收
+
+本地测试:`python -m pytest -q tests/test_release_native_v814.py tests/test_release_v813.py`。
+
+生产验收必须测试:凭据错误、SSH 主机密钥变化、上传中断、哈希错误、审批撤销、重复任务、目标网关故障、回滚、旧版本路径、500/超时、CDN 缓存以及 API 日志中无凭据泄漏。
+
+### S3 网站独立域名的反向代理(必做)
+
+本项目生成的静态网站可能使用 `/products/`、`/about/` 等**根路径链接**。示例网关的内部路由是 `/{site}/products/`。正式域名必须在反向代理层把根路径映射到正确的站点前缀,不能直接将网关根目录暴露为某个企业网站。
+
+以下仅是 Nginx 的 HTTP 内网转发结构示例,真实外网必须先配置受信 HTTPS 证书和正确的 DNS:
+
+```nginx
+server {
+ listen 127.0.0.1:8088;
+ server_name www.example.com;
+ location / {
+ proxy_set_header Host $host;
+ proxy_pass http://127.0.0.1:8791/my_site_id/;
+ }
+}
+```
+
+这样 `https://www.example.com/products/` 才会被反代为网关的 `/my_site_id/products/`。公网 443/TLS、缓存层和域名验证需由实际反向代理负责,**不要原样复制演示域名到生产环境**。
+
+针对每个独立网站设置不同的 server_name、站点 ID 和相应的 `PUBLIC_URL`。使用 CDN 时,对根路径 HTML、current.json 的旧版本缓存必须进行专项验证;无明确缓存失效机制前使用 `no-cache` 更安全。
diff --git a/docs/HISTORY_V8_12_README.md b/docs/HISTORY_V8_12_README.md
new file mode 100644
index 0000000..a5d7491
--- /dev/null
+++ b/docs/HISTORY_V8_12_README.md
@@ -0,0 +1,298 @@
+# SiteForge Enterprise V8.12.0-dev.1 — 工作台流程与任务状态增强
+
+> **开发集成版,尚未通过 P0–P4 全部生产验收门禁。** 完整工程在 V8.11 基线上升级;P5/多语言和 Art Design Pro 官方工程等未在本轮解决。
+
+
+## V8.11:管理后台重新设计(本轮重点)
+
+默认 `http://localhost:8787/` 现使用 **简洁工作台**,保留原有业务 API 和数据结构,不迁移数据库。
+
+- **工作台**:网站数量、操作引导、最近网站;不要求初学者理解 Worker/SEO/主题 Schema。
+- **我的网站**:一个地方选择所有网站;创建新站的表单只保留必要字段。
+- **网站概览**:四步流程(建立网站、准备内容、完善设计、构建预览),动态提示下一步。
+- **内容与页面**:页面、产品、新闻统一管理,可快捷修改文字或进入可视化设计器。
+- **外观与设计**:行业主题选择与实际页面编辑;草稿/发布隔离仍由原编辑器负责。
+- **导入与迁移**:默认进入带预演、复核与批次回滚的导入工作台;小文件直写保留为高级操作且必须确认。
+- **构建与交付**:使用既有异步 Worker,预览、静态 ZIP、质量报告集中呈现;正式审批和远程发布保留在企业交付中心。
+- **网站设置**:站点名称、域名、公司标语、Logo、类型等从设计功能中独立出来。
+- **高级工具**:折叠保留原有生产控制台、任务与审批、主题预览以及历史后台 `/classic`,确保不丢功能。
+
+新增页面在 `app/templates/workspace.html`,样式和功能逻辑在 `app/static/workspace.css`、`app/static/workspace.js`。
+专业页面已经使用 V8.11 的统一导航与分组交互;Art Design Pro 官方完整工程仍未正式编译整合。
+
+**运行前后检查**:登录、创建站点、内容编辑、主题切换、文件导入、异步构建、预览/ZIP、手机宽度侧边栏。
+
+**注意**:新 UI 不改变业务 API 或权限模型;`/classic` 留作过渡。真实浏览器/生产 Docker 的全面实机回归仍应在目标服务器执行。
+
+---
+
+## V8.9 已有能力(原样保留)
+
+
+
+1. **P0 离线恢复安全**:恢复前校验 ZIP 路径、内容范围和数据库完整性;同文件系统暂存目录重命名切换,保留原数据目录以便回退,失败不先覆盖旧数据库。使用 `python scripts/restore_backup.py backup.zip --data /path --confirmed-offline`(务必先停 Web/Worker),完整备份生成用 `python -m app.backup`。
+2. **P2 WordPress 响应式图片**:解析 `srcset`、`data-src`、`data-srcset`、`picture/source` 和视频 poster 的外部资源引用;受限本地化后保留宽度/倍数提示及懒加载 URL,无法处理的外链进入迁移复核,绝不静默删除 CDN 引用。外部 CSS 背景图、任意复杂插件布局仍未全面迁移。
+3. **P3 5000 条真实构建**:独立 SQLite 夹具 10×500 记录实际导入 5,000 条并生成超 5,000 个静态 HTML;构建器预计算品牌/分类索引,交付检查和质量审计改为流式处理,避免随内容数平方增长;正式 SEO 警告仍需清理。
+4. **P4 维持现有设计器与模板**:保留跨设备列设置、页面草稿、全站设计草稿、Owner 整站套版与事务回滚;针对优化过的构建器/质量检查加入回归测试。
+
+**验收报告与限制:** [P0–P4 V8.9 评估](docs/P0_P4_V8_9_VERIFICATION.md) | [原验收矩阵](docs/P0_P4_ACCEPTANCE_MATRIX.md)
+
+## 启动与测试(Windows PowerShell / Docker Desktop)
+
+```powershell
+# 在解压后的项目根目录执行,首次生成 .env
+# 不能直接覆写旧项目数据;务必使用新测试卷
+docker run --rm -v "${PWD}:/project" -w /project python:3.12-slim python scripts/init_config.py
+docker compose up -d --build
+docker compose ps
+```
+
+- 简洁管理后台(默认推荐):`http://localhost:8787/`
+- 旧版专业操作页:`http://localhost:8787/classic`
+- 高级数据与迁移工具:`http://localhost:8787/production-v8`
+- 主题中心:`http://localhost:8787/theme-gallery`
+- `/api/health` 应返回平台版本 `8.10.0-dev.1` 和构建提交标识。
+- 本地测试:`bash scripts/check.sh` 或 Windows PowerShell `scripts/check.ps1`。
+- 独立 5000 条验收:`python scripts/pilot_5000.py --report pilot.json`(默认只验证 5,000 条导入)。有足够资源的测试机上加 `--build` 同时检查整站构建;仍需分开核查 SEO 警告。。
+
+## 迁移及回滚注意
+
+离线恢复在目标目录旁保留 `*.pre-restore-*` 原数据目录;恢复完成后先验证旧构建、媒体、权限和管理员登录,再确认是否可以清理原目录。请先把备份 ZIP 拷贝到 Docker 卷之外。此恢复脚本适用于 SQLite 单机数据;**不是 PostgreSQL 恢复工具**。恢复前 Web 与 Worker 必须全部停止。
+
+## 开发验收边界
+
+此版本未在当前环境执行 Docker Engine、GitHub Actions 云端 CI、真实授权 WordPress 全站迁移、MySQL TLS 实机、生产密钥轮换以及桌面/移动浏览器完整视觉验收。P0–P4 尚不能全部标为生产交付完成。跨域 CDN、CSS 背景资源、付费/私有 WordPress 数据及复杂 WooCommerce/Elementor 页仍需定制适配与人工复核。
+
+---
+
+## 历史功能说明(原 V8.8/之前版本,仅供追溯)
+
+# SiteForge Enterprise V8.8.0-dev.1 — P0–P4 集成增强版
+
+> **交付状态:开发集成版,尚未通过 P0–P4 全部门禁,不应作为正式商业验收报告。**
+
+本版延续 V8.7 的完整源码,聚焦 P4(响应式独立列布局、图片素材库、整站模板两步审核与多页原子操作),同时修复深层页面 CTA/媒体根路径问题、版本与 Compose 遗留问题。旧站迁移、数据装配、安全 HTTP、Worker、四套主题继续保留。
+
+- **P4 设备布局**:页面中的多列区块可设置平板/手机独立列数、排序、跨度和显示隐藏;所有自定义 CSS 经过严格参数校验,并按页面作用域生成。
+- **P4 模板套版**:生产控制台先预检、再暂存,最后 Owner 发布;冲突拒绝、失败回滚,同步保证数据与 SEO 不被模板业务内容覆盖。旧版直接应用 API 需显式设置开发兼容开关。
+- **P4 媒体素材**:编辑器新增本站图片库选择,支持图片组件、画廊和轮播;深层页面正确使用根路径。
+- **验证状态与全部遗留**:[P0–P4 验收矩阵](docs/P0_P4_ACCEPTANCE_MATRIX.md)。
+- **运行**:与 V8.7 一样,首先在独立测试目录执行 `python scripts/init_config.py`(或 Docker 运行该脚本),再 `docker compose up -d --build`。不得覆盖运行中的旧版数据卷。
+
+---
+
+## V8.7 历史项目说明(保留升级依据)
+
+# SiteForge Enterprise V8.7.0-dev.1 — 全站设计草稿与响应式管理
+
+> **交付状态:开发试点版。** 本轮基于 V8.6 延续原 Web + Docker 工程,不是单独演示网站。
+
+## V8.7 本轮实际实现
+
+- **全站设计草稿**:`GET/PUT/DELETE /api/sites/{site}/site-design` 管理独立草稿;`POST /site-design/publish` 才更新用于正式构建的全站配置。对 `revision` 和底层设置指纹做并发冲突检查,发布记录保存在 `site_design_publications`。
+- **统一页眉/页脚/导航**:支持安全的三级导航、搜索显示、顶部公告、联系 CTA、页脚标题/介绍/邮箱、页脚菜单显示选项;四套专业主题的最终 HTML 均消费同一份配置。导航只允许站内路径,严禁任意脚本、外部链接注入。
+- **响应式控制**:平板与手机的区块间距、手机内容左右内边距、移动端公告和页脚导航显隐;已写入最终静态 `assets/custom.css` 和可视化编辑器预览 CSS。它是可交付的响应式参数编辑,**并非**完整独立设备画布。
+- **设计令牌统一入草稿**:字体、标题缩放、版心宽度、圆角等改为全站草稿保存/发布;未经发布不会更改新构建。编辑器内可在“设计规范”和“全站页眉与页脚”面板操作。
+- **关闭旧版直写绕过**:`/blueprint` 直写、旧导航直写、旧设计令牌直写及 `PATCH entry.meta` 的布局直写,在生产环境默认返回 HTTP 409。旧脚本若必须临时过渡,可显式设 `SITEFORGE_ALLOW_LEGACY_DIRECT_DESIGN_WRITE=1`;**正式生产不应启用此选项**。原后台导航表单与企业设计令牌页已改写全站草稿。
+- **测试**:在现有 168 项回归基础上新增 13 项专项测试,验证四主题、草稿构建隔离、发布、设计令牌、安全输入、跨站隔离、并发拒绝、旧接口安全门禁。
+
+### 使用方法
+
+1. 解压后先在**独立测试环境**启动 Docker(以下为 PowerShell):
+
+ ```powershell
+ docker run --rm -v "${PWD}:/project" -w /project python:3.12-slim python scripts/init_config.py
+ docker compose up -d --build
+ ```
+
+2. 登录 `http://localhost:8787/`,进入任意站点页面的 `/editor/{site_id}/{entry_id}` 设计器。
+3. 点击 **全站页眉与页脚**,调整导航/页眉/页脚/手机与平板设置,点击 **预览草稿**,随后 **保存全站草稿**。
+4. 由网站 Owner 点击 **发布全站设计**,然后触发 Worker 网站构建。现有已发布静态版本不会因保存或发布设计而被原位篡改。
+5. CI 中可执行 `pytest`、Python 编译检查、`node --check`;Docker、真实浏览器和服务器发布需要独立实机验收。
+
+### API 及兼容性
+
+| 方法与路径 | 行为 |
+|---|---|
+| `GET /api/sites/{site}/site-design` | 返回已发布配置或草稿、版本、冲突状态 |
+| `PUT /api/sites/{site}/site-design` | 携带 `config` 与 `expected_revision` 保存草稿 |
+| `POST /api/sites/{site}/site-design/publish` | Owner 审核发布,要求版本号 |
+| `DELETE /api/sites/{site}/site-design` | 删除未发布草稿 |
+| `GET /api/sites/{site}/site-design/publications` | 查询最近的发布审计记录 |
+| `POST /api/sites/{site}/pages/{entry}/editor-preview` | 请求体的 `site_design` 可预览未发布的整站草稿 |
+
+### 明确未完成
+
+- 目前并非 Figma 式自由定位编辑;完整 PC/平板/手机**独立布局**、全站模板可视化拖拽、实时跨设备视觉验收仍待开发。
+- 旧版批量套版及部分内容创建接口仍可能更新布局,需在后续将所有设计变更统一纳入发布工作流;本次仅限制上述直接绕过入口。
+- Art Design Pro 官方工程、复杂 CMS 无损迁移、PostgreSQL/Redis 多实例、SFTP/S3 正式发布仍未完成完整实机验收。
+- 当前环境无 Docker Engine,**不能宣称** `docker compose up` 已实机通过;亦未完成 GitHub 云端 CI 和真机 Chrome 视觉回归。
+
+---
+
+## 上一版 V8.6 的开发记录(保留供升级追踪)
+
+# SiteForge Enterprise V8.6.0-dev.1
+
+> **状态:开发试点版,P4 设计器与整站模板增强。** 同一主线延续 V8.5 的认证 API、只读 MySQL、V8.4 XML/API、WordPress 迁移、四套主题、构建 Worker 和发布审批。尚未完成正式商业/生产验收。
+
+## 这一轮新增(P4)
+
+- **六种声明式组件**:`carousel` 图片滚动展示、`video` YouTube/Vimeo 白名单视频、`tabs` 可访问详情分组、`pricing` 服务方案、`map` 可信地图外链、`form_link` 询盘入口。都使用可信渲染器输出,不允许用户提交 JavaScript。
+- **独立设计草稿**:V8.6 可视化编辑器的“保存草稿”只写入 `studio_drafts`,不会修改正式 entries 或被静态构建读取;“发布设计”才会原子写入正式页面,随后需重新构建静态网站。
+- **并发保护**:每次草稿更新携带 `expected_revision`;正式页面被其他人改动后,拒绝发布,防止静默覆盖。放弃草稿会恢复正式版设计。操作支持站点权限隔离与审计。
+- **整站模板兼容性**:模板带主题版本声明;新增 `/whole-site-templates/preflight` 无副作用检查,验证主题是否存在、指定版本是否可用、已安装组件、已有路径,后台预检后再应用。
+- **前端操作入口**:原 `/editor/{site_id}/{entry_id}` 页面新增保存草稿、发布设计、放弃草稿;整站模板控制台在应用前执行独立兼容性检查。原 `/blueprint` 直写接口保留兼容旧系统,尚未完全禁用。
+
+### Docker 运行
+
+Windows PowerShell(在解压后的项目根目录):
+
+```powershell
+docker run --rm -v "${PWD}:/project" -w /project python:3.12-slim python scripts/init_config.py
+docker compose up -d --build
+docker compose ps
+```
+
+页面:管理台 `http://localhost:8787/`;生产控制台 `/production-v8`;主题 `/theme-gallery`;设计器从网站页面进入 `/editor/{site_id}/{entry_id}`;API 版本 `/api/health`。
+
+首次升级必须使用**独立测试环境和数据副本**,备份 `.env`、数据库、媒体及归档。草稿表通过幂等方式初始化,未在原有生产数据库上完成实际迁移演练。
+
+### P4 接口
+
+| HTTP 接口 | 用途 |
+|---|---|
+| `GET /api/editor/blocks` | 读取组件注册表与属性契约 |
+| `GET /api/sites/{site}/pages/{entry}/studio-draft` | 获取当前草稿或正式布局 |
+| `GET /api/sites/{site}/pages/{entry}/studio-draft/preset?page_template=home` | 获取主题预设,不修改正式页 |
+| `PUT /api/sites/{site}/pages/{entry}/studio-draft` | 保存草稿,要求 `expected_revision` |
+| `POST /api/sites/{site}/pages/{entry}/studio-draft/publish` | 发布设计,需要 owner 角色和 `expected_revision` |
+| `DELETE /api/sites/{site}/pages/{entry}/studio-draft` | 放弃草稿 |
+| `POST /api/factory/sites/{site}/whole-site-templates/preflight` | 整站模板无副作用兼容检查 |
+
+开发环境已经完成 **168 项本地 pytest**、全部 JavaScript 语法与版本检查,另有“草稿构建不包含未发布内容 → 发布后重新构建包含内容”的实测回归。
+
+**未验收**:Docker Engine 实机、GitHub 云端 CI、浏览器 PC/手机端手工交互、真实客户内容视觉检查、Art Design Pro 官方工程编译、复杂 WordPress 无损迁移、PostgreSQL/Redis 多实例能力、SFTP/S3 正式发布。
+
+详细信息见 [V8.6 验收与剩余需求](docs/V8_6_RELEASE_STATUS.md)。
+
+---
+
+## 历史版本功能参考(以下内容为 V8.5 及更早阶段的开发记录,不是 V8.6 最新状态)
+
+---
+
+## V8.4 历史能力(继续保留)
+
+
+> 当前阶段:统一网站生产工厂,**P3 数据连接器第一轮(安全 XML、公开 JSON API + Worker)**。本版本是开发试点版,并非所有网站工厂需求均已实现。
+
+## V8.4 本轮新增
+
+- **安全 XML 导入**:`/production-v8` 可以上传 XML,选择记录节点、内容类型和字段映射,支持扁平或两级结构;使用 `defusedxml` 禁用 DTD 与外部实体,并限制最大 8 MB、1000 条记录及节点/深度上限。
+- **公开 HTTPS JSON API**:设置 `url`、JSON 列表路径(例如 `data.items`)、字段映射和分页;创建后提交 Worker 后台采集,使用既有租约/取消/重试/超时机制。每页形成独立暂存批次,可以审批提交或回滚。所有采集均不自动上线。
+- **进度和误差边界**:最多 50 页 × 每页 100 条,合计 5000 条;发生中断从已保存页码继续,复用已有同页批次避免重试重复写入;达到最大页数而上页满额会显示 `limit_reached`,不能视为采集完整。
+- **统一后台**:新增 XML 文件上传、API 创建、采集任务、取消和进度查询操作区域,并保留 WordPress 迁移、主题和编辑器。
+- **版本与 CI**:统一应用版本标记、Compose 名称、脚本和 CI 版本断言为 `8.5.0-dev.1`;**144 项本地自动化测试通过**。
+
+[详细功能、接口、安全限制与剩余需求](docs/V8_4_RELEASE_STATUS.md)
+
+**边界**:目前 API **只支持无需认证的公开 JSON**,不支持带密钥 API;MySQL 只读连接器、完整 Art Design Pro 编译和生产级外部发布也尚未完成。没有运行 Docker 实机或云端 CI,因此不得将该版本称为企业正式上线完成版。
+
+---
+
+## 既有 V8.3 迁移与建站功能(本轮保留)
+
+
+> SiteForge Enterprise 统一开发主线:**WordPress 可恢复迁移 + Sitemap 完整性审计 + 有界媒体本地化 + Worker 后台采集**。
+>
+> 版本状态:开发试点候选版,不可直接对外宣称完整 WordPress 无损迁移或企业生产部署已验收。V8.1 网站、主题、编辑器、导入批次和静态构建功能均在此版本继续维护。
+
+## 快速启动(Docker Desktop / Linux Docker)
+
+项目根目录第一次初始化:
+
+```bash
+# PowerShell 将 `$(pwd)` 改为 `${PWD}`
+docker run --rm -v "$(pwd):/project" -w /project python:3.12-slim python scripts/init_config.py
+docker compose up -d --build
+docker compose ps
+```
+
+- 登录:`http://localhost:8787/`
+- **V8.4 生产控制台**:`http://localhost:8787/production-v8`
+- 主题管理:`http://localhost:8787/theme-gallery`
+- 页面编辑:`http://localhost:8787/factory-studio`
+- 任务中心:`http://localhost:8787/operations`
+- 构建版本:`http://localhost:8787/api/health`;当前应显示 `8.5.0-dev.1`。
+
+**升级注意:** 不要直接覆盖 V8.1 正在运行的存储卷。先备份 `.env`、数据库、媒体和生成文件,在独立 Compose 项目中完成验收,然后安排升级与回滚。新版本表结构采用幂等的 `CREATE TABLE IF NOT EXISTS`,但并未进行真实旧数据库原地升级演练。
+
+## V8.3 新增:WordPress 关系数据与归档 URL 保真
+
+- 为每次 WordPress 迁移会话采集公开 REST 分类、标签和媒体库**元数据**,分别保存页码和已采集数量。关闭页面后可以继续下一页。
+- 原始 WordPress ID、分类/标签/特色图片的关联 ID 写入批次记录和关系索引;导入确认前,在已同步名称的情况下补充分类/标签字段;绝不凭空创建不存在的分类内容。
+- 分类/标签来源归档 URL 纳入旧站 SEO 基线。无对应的新页面或批准重定向时,需要通过差异检查处理。
+- 对缺失的标签/分类、未知特色图片、ACF 和页面构建器内容保留人工审核事项。公开 ACF 只保留有界快照,不承诺原样转换。
+- 控制台按钮:选择“分类 / 标签 / 媒体库”→ “同步目录下一页”→ “目录进度”,完成后再核对媒体和 Sitemap,最后确认导入。
+
+详细的操作与验收边界见 [V8.3 开发验收报告](docs/V8_3_RELEASE_STATUS.md)。
+
+## V8.3 迁移流程与增强
+
+1. 创建迁移会话:指定授权 WordPress 来源和最多采集条数。
+2. **提交 Worker 后台采集**:一次提交到统一数据库任务队列,Worker 子进程逐页采集 WordPress REST `pages/posts`,保持每页检查点。浏览器关闭后可以继续执行。
+3. 在任务中心监控扫描的排队、执行、取消、重试、超时等状态。扫描任务不会自动提交暂存内容、不会直接上线。
+4. **发现 Sitemap**:单独读取 WordPress `wp-sitemap.xml`;支持 `sitemapindex` 嵌套与有界 gzip XML,记录未从 REST 采集到的 URL。若使用 Yoast 或 RankMath 自定义 Sitemap,可以通过 API 指定同源 URL。
+5. **媒体本地化**:在最终导入前,手动分批读取(每次最多 40 个)暂存文章中的图片,限制大小、格式、原站域名,自动重写正文及特色图片路径。其他 CDN 域名默认拒绝自动下载,并形成待复核项。
+6. 检查扫描结果、Sitemap 缺页和图片缺失,明确点击“确认并导入”。将旧 URL / SEO 清单加入审计;存在未处理差异的迁移站继续受发布审批门禁限制。
+7. 选择主题后使用原有 Worker 构建网站,查看 QA 和 ZIP。
+
+**重要边界**:Sitemap 只用于发现遗漏,并不自动解析任意 Sitemap 页面内容;完整的 `srcset`、CSS 背景图片、跨域 CDN、附件、视频、登录后数据及复杂 Elementor/ACF/WooCommerce 迁移仍需要额外适配和人工复核。未运行 Sitemap 完整性审计时,系统不能保证完整发现旧站 URL。WP 媒体本地化是用户主动触发的、同步分批执行的步骤,不会在后台无界下载资源。
+
+## V8.3 主要接口
+
+所有接口需要原有 Session + CSRF + 网站权限;`POST` 不允许匿名调用。
+
+| 接口 | 说明 |
+|---|---|
+| `POST /api/migrations/sites/{site}/wordpress/sessions` | 创建迁移会话 |
+| `POST /api/migrations/sites/{site}/wordpress/sessions/{session}/scan` | 将 REST 扫描加入 Worker 队列;可传 `idempotency_key` |
+| `GET /api/migrations/sites/{site}/wordpress/sessions/{session}` | 查询检查点、页数和警告 |
+| `POST /api/migrations/sites/{site}/wordpress/sessions/{session}/sitemap` | 安全发现同源 Sitemap,JSON 可传 `url` |
+| `GET /api/migrations/sites/{site}/wordpress/sessions/{session}/sitemap` | 已发现 URL 数、缺失计数、前 100 项示例 |
+| `POST /api/migrations/sites/{site}/wordpress/sessions/{session}/media/localize` | 分批媒体本地化,JSON `limit` 1–40 |
+| `POST /api/migrations/sites/{site}/wordpress/sessions/{session}/finalize` | 明确确认提交,建立 URL/SEO 基线与复核项 |
+| `POST /api/migrations/sites/{site}/wordpress/sessions/{session}/cancel` | 取消会话;正在运行的任务另在任务中心申请取消 |
+
+后台扫描任务使用 `build_jobs.kind='wordpress_scan'`,与原网站构建共享 Worker/容量限制/租约/退避。可使用 `/operations` 监控或使用既有企业任务 API。**扫描完成不代表迁移交付完成**。
+
+## 测试与已知限制
+
+```bash
+bash scripts/check.sh
+python scripts/verify_release.py
+```
+
+截至本次代码交付,130 项自动化测试在当前开发环境全部通过,包含 V8.2 的 120 项和本轮的 10 项专项测试。测试覆盖嵌套 Sitemap、压缩炸弹、XXE、缺页审计、媒体格式与同源限制、子进程任务派发、幂等、容量门禁、API 鉴权等。
+
+本环境没有 Docker Engine;没有实际执行 Docker 实机启动、GitHub 云端 CI、官方 Art Design Pro 编译、真实 WordPress 全量迁移、浏览器全流程或远端正式发布。是否能交付企业生产仍需上述验收。
+
+接下来的 P2 收尾任务:自动尝试多种 Sitemap 路径、WordPress 媒体库/分类标签迁移、REST 采集的统计/重试监控、媒体 CDN 授权白名单和批量迁移压测。之后再推进 P3 XML/API/只读数据库连接器、P4 整站编辑器、P5 安全发布、多语言与 PostgreSQL/Redis 生产化。
+
+更多细节:[V8.3 验收与剩余功能](docs/V8_3_RELEASE_STATUS.md)。
+
+## V8.11:统一专业管理体验
+
+本版在 V8.10 基础上统一五个专业工具页面的导航、当前网站选择、业务提示、表单焦点和操作按钮。编辑器将“草稿/预览/正式发布”分组,平板和手机提供结构/预览/属性面板切换;任务中心按迁移、构建、审批发布分为三个阶段。所有原业务 API 与 Docker 服务保持不变。
+
+- 默认工作台:`http://localhost:8787/`
+- 主题预览:`/theme-gallery`
+- 页面编辑器:从工作台的“外观与设计”进入
+- 高级建站:`/factory-studio`
+- 数据迁移:`/production-v8`
+- 任务与交付:`/operations`
+
+详细功能对照与人工验收清单见 [`docs/V8_11_UNIFIED_ADMIN.md`](docs/V8_11_UNIFIED_ADMIN.md)。
+
+运行测试:`python -m pytest -q --disable-warnings`。本版在当前环境通过 218 项测试。Docker 实机与浏览器跨设备回归仍需部署后完成。
diff --git a/docs/P6_QUICKSTART.md b/docs/P6_QUICKSTART.md
new file mode 100644
index 0000000..9dba395
--- /dev/null
+++ b/docs/P6_QUICKSTART.md
@@ -0,0 +1,12 @@
+# P6 快速操作
+
+登录 `http://localhost:8787/` → 我的站点 → 建立中文与英文两个独立 HTTPS 站。
+
+1. 进入“多语言管理”,创建语言组与两个语言站成员。
+2. 在中文站与英文站分别录入已翻译页面,各自设置 `status=published`。
+3. 建立翻译内容组(例如 `product:example`),将两个实际页面 ID 分别关联为 `approved`。
+4. 重新构建两个站点,并查看生成 HTML 与 Sitemap;无对应翻译的页面不会显示语言互链。
+5. 待翻译文案先存为 `draft`,可以关联为 `pending`,待审核确认以后改为 `published` 并设为 `approved`。
+6. 使用“构建与交付”审批发布。上线前仍需在真实域名上逐一核对 canonical、链接、翻译质量和 SEO。
+
+数据库升级为加法建表,不会自动复制或修改原网站内容。升级前请先备份现有卷。
diff --git a/docs/V8_13_P5_ACCEPTANCE.md b/docs/V8_13_P5_ACCEPTANCE.md
new file mode 100644
index 0000000..5da086b
--- /dev/null
+++ b/docs/V8_13_P5_ACCEPTANCE.md
@@ -0,0 +1,43 @@
+# SiteForge V8.13 — P5 发布闭环的交付与验收清单
+
+版本:`8.13.0-dev.1`。此报告区分 **源代码实现/本地自动化测试** 与 **Docker/外部生产实机验收**。
+
+## 本轮实现与已有能力
+
+| 验收点 | 当前状态 | 证据 / 限制 |
+|---|---|---|
+| 构建完成且 ZIP 可用 | 已实现与单元测试 | `release_api.approved` + `_archive_for` |
+| 静态质量、SEO 迁移审核、独立审批 | 已有代码 + 本轮 API 专项测试 | 未审批返回 409,迁移站差异会阻断 |
+| Web 不执行远程大文件上传 | 已实现 | API 提交 `remote_deploy` / `remote_rollback` 任务,返回 HTTP 202 |
+| 任务超时、幂等、容量、取消 | 已有 Worker + 本轮补充测试 | 单机 SQLite 租约;不是多区域事务 |
+| 远程 ZIP 不可变暂存 | 已实现与签名 Agent 模拟测试 | 按存档 SHA256 前 20 位命名,不更改 current |
+| 远程文件 SHA256、清单核验 | 已实现与模拟测试 | 篡改文件后拒绝激活 |
+| 单次版本切换 | 已实现与模拟测试 | `current` 指针文件通过 `os.replace` 原子替换 |
+| 激活后二次校验与首页探针 | 已实现与模拟测试 | 比对 Agent 公开首页的 SHA256;不等于真实域名探测 |
+| 异步历史版本回滚 | 已实现与模拟测试 | 仅回滚数据库记录中该站点该目标的版本;验真后切换 |
+| 目标密钥不进入前端 | 已实现的环境引用方案 | 推荐挂载到 `/run/secrets`,待生产轮换验收 |
+| SFTP / S3 原生连接器 | **未实现** | HTTPS 发布 Agent 为当前唯一可运行的远程目标类型 |
+| 真实 Docker、外网 TLS、CDN、域名 | **待实机验收** | 当前环境无 Docker Engine;没有公网发布测试服务器 |
+| 发布取消在切换瞬间的跨服务一致性 | **未完全解决** | 需增加远程状态 reconciliation / 运维确认 |
+| PostgreSQL/Redis 多 Worker | **待生产验收** | 独立 Compose 已配置但未完成真实高可用测试 |
+
+## 确定性专项测试
+
+- 不经过激活就上传两个版本时,当前线上指针保持不变。
+- 篡改 staging 文件后,Agent 拒绝激活且旧站保持可访问。
+- 伪造签名、非法 ZIP 路径、保留文件名、同名冲突会被拒绝。
+- 已批准构建通过异步 API 提交,在队列中去重;未批准构建禁止提交。
+- 运行签名 Agent 的完整暂存、校验、激活、再次验证、回滚逻辑(本地 TestClient 模拟)。
+- 任务数量达到上限时拒绝新任务;取消排队任务不影响已发布内容。
+
+## 正式上线前需要的人工/环境门禁
+
+1. Docker 一条命令在全新宿主机成功启动 Web 与 Worker,并验证 `/api/health` 精确报告版本/提交。
+2. 将 Agent 部署于真实 HTTPS 测试域名,独立密钥及 TLS 证书有效;仅开放预期入口。
+3. 在授权测试网站完成两轮发布,故障注入覆盖上传中断、校验失败、切换成功后 Web 超时、Worker 取消。
+4. 核对站点正式域名的首页、产品详情、404、canonical、sitemap、robots、媒体与链接。
+5. 验证真实站点切换/回滚并确认时间、缓存失效、老 URL 保留。
+6. 审计密钥轮换、管理员/审核人权限、备份/恢复和日志中的敏感数据。
+7. 完成 SFTP/S3 等其它发布方式后,逐个通过等价事务性/故障恢复门禁。
+
+**结论**:V8.13 的 HTTPS Agent 远程发布形成了本地可测试的发布闭环;尚不符合全部 P5 生产验收条件。
diff --git a/docs/V8_14_P5_ACCEPTANCE.md b/docs/V8_14_P5_ACCEPTANCE.md
new file mode 100644
index 0000000..85e8315
--- /dev/null
+++ b/docs/V8_14_P5_ACCEPTANCE.md
@@ -0,0 +1,32 @@
+# SiteForge Enterprise V8.14 — P5 原生发布验收记录
+
+版本:`8.14.0-dev.1`。本版在 V8.13 上添加 SFTP / S3 原生发布适配器、只读 S3 发布指针网关、统一发布目标状态与回滚入口。
+
+## 本轮已实现且有确定性测试覆盖
+
+| 能力 | 实际实现 | 测试边界 |
+|---|---|---|
+| 默认 HTTPS 签名 Agent 向后兼容 | 原发布 API、队列、签名与审批不变 | 继承 V8.13 专项回归 |
+| SFTP 主机密钥校验 | Paramiko RejectPolicy + 必须预置 known_hosts 和 SSH 私钥文件 | 配置与仿真 SFTP 文件系统测试,**未真实握手** |
+| SFTP 不可变目录与原子激活 | 分版本目录、SHA256 manifest、原子符号链接切换与回滚 | 本地仿真、注入损坏/探针失败;OpenSSH 实机待验收 |
+| S3 分版本对象上传 | 不变对象键、SHA256 内容校验、条件写入 current.json | FakeS3 条件语义测试,真实 AWS API 待验收 |
+| S3 网站读取服务 | 只读 gateway 根据版本指针解析站点资源,不直接曝光存储凭据 | ASGI TestClient + FakeS3 测试 |
+| 远端失败恢复 | 上传/校验失败保持旧版本;探针失败尝试恢复前一版本 | 本地故障注入测试 |
+| 审批、Worker、部署记录 | 原 API 选择后端、异步执行、记录发布与回滚 | 自动化测试 |
+| 后台展示 | 新增发布类型/目标配置状态、已发布版本回滚按钮 | JS 语法与组件级回归,真实浏览器待验证 |
+| 依赖与 Docker | 添加 boto3/Paramiko 完整依赖与 Compose 使用示例 | 配置审查;无 Docker Engine 实机验证 |
+
+## 不应宣称已完成的事项
+
+- SFTP 真实 SSH/TLS、主机指纹、私钥权限与 OpenSSH 原子扩展实测;环境中无法安装 Paramiko 验证真实服务器操作。
+- AWS/S3 真实凭据、桶 IAM、S3-compatible 条件写入能力及 CDN/域名部署。
+- Docker 单机真实启动、Linux CI 云端执行、浏览器桌面/移动端流程、各生产环境实际回滚演练。
+- 发布切换瞬间 Worker 崩溃的跨系统状态对账、长时间任务协调、完整运维监控、缓存刷新和 DNS 自动化。
+
+## 后续门禁
+
+1. 实际 Docker Compose clean room 部署+版本核查;两个账户完成一次分权审批。
+2. SFTP 容器夹具:上传、异常断网、错误 host key、目录符号链接攻击、原子切换和回滚。
+3. MinIO/S3 实机夹具:不可变对象校验、并发指针更新、被篡改对象、只读网关、旧版本回滚。
+4. 正式测试域名:主页、产品详情、404、canonical、sitemap、robots、媒体、缓存状态、安全响应头。
+5. 中断/重试后远端实际 current 与数据库发布历史 reconciliation;完成后才能标记 P5 生产通过。
diff --git a/docs/V8_15_P6_ACCEPTANCE.md b/docs/V8_15_P6_ACCEPTANCE.md
new file mode 100644
index 0000000..2f4436b
--- /dev/null
+++ b/docs/V8_15_P6_ACCEPTANCE.md
@@ -0,0 +1,34 @@
+# SiteForge Enterprise V8.15 — P6 多语言建设与验收
+
+## 本轮完成的能力
+
+| 验收点 | 状态 | 证据 |
+|---|---|---|
+| 多语言站点组和默认语言 | 本地通过 | `language_groups` / `language_sites` / API |
+| 同一内容关联不同语言页面 | 本地通过 | `translation_units` / `translation_members` |
+| 已审核、已发布页面生成双向 hreflang | 本地通过 | 中英两站实际构建 + 自动化测试 |
+| sitemap 含 XHTML hreflang | 本地通过 | 两站 sitemap.xml 验证 |
+| 不存在的翻译不生成伪造链接 | 本地通过 | 缺失、草稿、noindex 测试 |
+| 待翻译内容不能意外发布 | 本地通过 | 仅 draft 可 pending,误设 published 时拒绝静态构建 |
+| x-default 指向默认语言 | 本地通过 | 明确默认语且存在审批页面才输出 |
+| 前台语言切换导航 | 代码完成 | 动态注入,并需真实浏览器视觉验收 |
+| 后台多语言菜单及表单 | 代码完成 | 集成 JS / API,真实浏览器端操作仍待验收 |
+| 原 243 项测试兼容 | 本地通过 | 本地 pytest 248 passed(含 5 项 P6 专项) |
+| 真实 Docker、Chrome、正式域名 | 待验收 | 当前环境无完整 Docker/远程域名实机测试 |
+
+## 主要限制
+
+1. **域名模式**:目前一个语言站一个独立 HTTPS 域名或子域名;同域子路径需进一步设计反向代理、站点构建根路径和 sitemap。
+2. **翻译质量**:不自动翻译事实,`approved` 表示人工确认的状态标记,不包含另一人独立翻译审批的完整审计流程。
+3. **动态语言链接**:已发布数据可生成对应 URL;必须额外核对真正上线服务器是否已有该版本,不等于远程可访问性证明。
+4. **各主题国际化**:四套主题中部分组件和预设仍为中文。正式外语商业交付需按语言提供真实导航、字段标题和空状态文案。
+5. **企业生产**:Art Design Pro 官方完整工程、PostgreSQL/Redis 多实例、真实 SFTP/S3 等依旧需要实机验收。
+
+## 手工部署验收步骤
+
+1. 在新 Docker 项目卷启动 V8.15;核对 `/api/health` 返回 `8.15.0-dev.1` 与期望提交 SHA。
+2. 创建两个真实的 HTTPS 语言域名站,填写翻译过的公司主页和产品页。
+3. 建立一组真实对应关系;分别构建和下载静态站,验证 URL、html lang、canonical、sitemap、hreflang 的对称性。
+4. 随机挑选缺翻译、草稿和 noindex 的页面,确认其不会产生无效的互链;将 pending 内容错误设为 published,确认构建被拒绝。
+5. Chrome/手机端点击前台语言切换,验证页面存在、焦点可达、移动布局无溢出。
+6. 正式发布前审批,两站完成远程发布后从公网检查首页和内页的 hreflang 相互指向,不将示例 `.example.org` 域名当作有效服务。
diff --git a/docs/V8_16_PRODUCTION_ACCEPTANCE.md b/docs/V8_16_PRODUCTION_ACCEPTANCE.md
new file mode 100644
index 0000000..6dc61b5
--- /dev/null
+++ b/docs/V8_16_PRODUCTION_ACCEPTANCE.md
@@ -0,0 +1,40 @@
+# SiteForge Enterprise V8.16 — 企业部署基础验收记录
+
+版本:`8.16.0-dev.1`;阶段:**开发集成(非生产认证)**。
+
+## 本轮新增与测试门禁
+
+| 验收项 | 验证路径 | 当前结果 |
+| --- | --- | --- |
+| PostgreSQL 查询行数字/字段访问 | `tests/test_production_v816.py` | 本地模拟测试通过 |
+| PostgreSQL SQL 占位符、注释及 DDL 分句 | 同上 | 本地单元测试通过 |
+| PostgreSQL 初始化及 schema ledger | `scripts/verify_pg_bootstrap.py` | 脚本已扩展;需要 PostgreSQL 实例实测 |
+| PostgreSQL Worker `FOR UPDATE SKIP LOCKED`、事务与任务恢复 | CI `postgres-smoke` | 自动检查代码已接入;本机没有 PG 服务,未实际通过 |
+| Worker 按实例隔离心跳 | `tests/test_production_v816.py` | 本地通过 |
+| `/api/ready` 探针成功/失败及脱敏 | `tests/test_production_v816.py` | 本地通过 |
+| 全平台自动化回归 | `scripts/check.sh` | 按模块三组通过:95+82+78 = **255 项** |
+| HTTP + Worker 实际建站链路 | `python scripts/http_end_to_end.py` | **本地通过**:建站、保存、队列、静态 ZIP、预览 |
+| Chromium 桌面/移动端真实交互 | `scripts/browser_smoke.py` | **环境阻断**:`ERR_BLOCKED_BY_ADMINISTRATOR`,未通过 |
+| Docker Engine Web + Worker | `docker compose up -d --build` | 本机无 Docker Engine,未运行 |
+| Redis 通知故障降级 | 历史单元测试 | 通过;真实 Redis 容器未运行 |
+| Art Design Pro 官方构建 | 用户提供官方完整源码后执行 pnpm/构建 | **未完成**;交付包含安装脚本/适配页面,不含官方完整项目 |
+| 公网 SFTP/S3/HTTPS 上线和回滚 | 独立预生产环境 | **未完成** |
+
+说明:单进程连续执行所有 pytest 在约 84% 时超时;上述 255 项是三个独立 pytest 进程(95/82/78)分别完成并通过,不等同于单进程全量成功。新增真实 HTTP 集成构建了 10 个 HTML 页面,仍有缺少正式域名、未填充栏目内容的质量警告;只证明代码路径畅通。
+
+## 生产环境验收操作建议(必须在预生产进行)
+
+1. 对 V8.15 的 SQLite 数据卷和所有媒体、归档、发布目录做完整备份,先演练恢复;保留生产回退窗口。
+2. 在独立 PG 16+ 测试数据库执行 `scripts/verify_pg_bootstrap.py`。验证 Web、Worker 启动与并发 claim;**不把原 SQLite 文件当作 PostgreSQL 数据迁移**。SQLite → PG 的正式数据迁移、外键校验、内容数量和媒体对账还未实现为可一键生产执行的迁移器。
+3. `docker compose -f compose.enterprise.yaml config`、`up -d --build`,分别核对 Web `/api/ready`、Worker 本地健康以及 PostgreSQL/Redis 服务健康。
+4. 将两个 Worker 实例分别配置唯一的 `SITEFORGE_WORKER_INSTANCE`,断开其中一个,确认另一个健康状态不会使坏实例误报正常;断电中断后要检查租约回收与远程发布状态对账。
+5. 用完整真实数据做创建网站、WordPress 迁移、5000 条产品导入、构建、主题预览和多语言 SEO 检验。在 Chromium 1440/1024/390/360 视口进行后台与前台访问性/响应式验证。
+6. 在预生产的实际 SFTP/S3/HTTPS Agent 发布,逐步演练上传失败、激活失败、CDN 缓存、健康检查失败及人工回滚;审批人与执行人分离。
+7. 获取合法的 Art Design Pro 官方工程后执行 `scripts/install_into_art_design_pro.py`,完成实际路由/会话/CSRF 联调及 `pnpm build`。目前属于可选桥接,不要把它计为完成。
+
+## 风险边界
+
+- PostgreSQL 兼容桥目前在 PG + Psycopg 真机上仍需完整持续验证,不能因为 SQLite 测试通过就宣称 PG 生产稳定。
+- Redis 当前主要用于任务唤醒,任务权威状态仍在数据库;并非完整的分布式消息可靠性交付。
+- SFTP/S3 原生发布已支持本地仿真,但公网环境、强制 TLS、服务端权限和切换时缓存行为仍需验收。
+- 新版未添加 SSO、MFA 或企业级集中监控系统,也未完成 Art Design Pro 官方源码集成。
diff --git a/docs/V8_17_ACCEPTANCE_RUNBOOK.md b/docs/V8_17_ACCEPTANCE_RUNBOOK.md
new file mode 100644
index 0000000..4a589f9
--- /dev/null
+++ b/docs/V8_17_ACCEPTANCE_RUNBOOK.md
@@ -0,0 +1,92 @@
+# SiteForge V8.17 — 生产交付门禁执行手册
+
+> 状态:独立增量验收工具;不包含 V8.16 原项目源码、Art Design Pro 官方完整工程或真实测试环境。本文的项目结构以所获 V8.16 README 为依据。生产演练与授权审批必须由真实环境执行并存档。
+
+## 一、目标与范围
+
+- V8.16 具备 HTTP `/api/health`、`/api/ready`,PG 兼容层、Worker 实例标识与 HTTP/PG 浏览器测试脚本。
+- V8.17 增量提供不侵入应用数据的统一检查入口,报告 JSON + Markdown + 离线可视化 HTML。
+- **不替代** `scripts/check.sh`、`scripts/http_end_to_end.py`、`scripts/browser_smoke.py`、`scripts/verify_pg_bootstrap.py`;这些是应用级端到端验证。
+- **不会执行** Docker up/down/build、数据库迁移、写入、发布/回滚、备份/恢复、人工审批。
+
+## 二、集成路径
+
+1. 解压原 V8.16 完整工程到**隔离测试目录**,不要覆盖正式数据卷。
+2. 将本补充包 `scripts/siteforge_v817_audit.py` 和 `tests/test_siteforge_v817_audit.py` 分别复制到原项目同名目录中;不会替换已有模块。
+3. 按需复制 `docs/V8_17_ACCEPTANCE_RUNBOOK.md` 和 `examples/`。
+4. 运行 `python -m unittest discover -s tests -p 'test_siteforge_v817_audit.py' -v`,再执行以下命令。
+
+## 三、执行命令
+
+最小安全检查(无网络、无需 Docker):
+
+```bash
+python scripts/siteforge_v817_audit.py --repo . --output reports/siteforge-v817
+```
+
+隔离 Docker 企业 Compose 已启动后:
+
+```bash
+python scripts/siteforge_v817_audit.py \
+ --repo . --docker --url http://127.0.0.1:8787 \
+ --output reports/enterprise-staging
+```
+
+使用已有隔离数据库配置;**PG 仅执行 SELECT 1(事务只读),Redis 仅执行 PING**:
+
+```bash
+# 以下环境变量需由运维通过私密渠道事先配置,切勿提交 .env 或 DSN 到仓库。
+python scripts/siteforge_v817_audit.py \
+ --postgres --redis --output reports/datastores
+```
+
+静态构建产物 URL 校验(old_urls.txt 需人工从旧站导出,不自动抓取):
+
+```bash
+python scripts/siteforge_v817_audit.py \
+ --build-dir /path/to/generated/site \
+ --url-baseline old_urls.txt \
+ --output reports/static-seo
+```
+
+验收报告:`reports/*.html` 离线打开、`reports/*.json` 可机器读取、`reports/*.md` 可归档。返回码 2 代表存在 FAIL;WARN/SKIP 仅表示需要进一步处理,不视为生产通过。
+
+## 四、关键门禁和接受标准
+
+| 门禁 | 自动审计可做 | 仍需人工与真实环境执行 |
+|---|---|---|
+| 代码与 Compose | 文件完整性、Python AST、Compose 配置、容器状态 | 应用全测试集、供应链审计、CI 与镜像签名 |
+| Web 与 Worker | HTTP readiness、容器运行/health、Worker 服务发现 | 真实异步任务、租约竞争、断网恢复、容量压测 |
+| PostgreSQL | 真正连通后的 `SELECT 1` | **隔离测试库**运行原项目 PG bootstrap、迁移校验、恢复演练 |
+| Redis | PING | 队列可靠性、幂等、故障切换、持久化和告警演练 |
+| URL / SEO | html lang、canonical 规则、内部链接、旧路径覆盖、sitemap | 迁移映射审定、301/404、robots、实际源站比对、GSC 验收 |
+| 多语言 | hreflang 格式与语言标签 | 关联双方对称、翻译审核、独立域名 HTTPS |
+| 发布与回滚 | 读取人工证据表 | 真实 staging 激活、不可变版本、校验 SHA、回滚演练、审批隔离 |
+| Art Design Pro | 检查当前 `workspace` 静态入口 | **获得官方工程并构建**,完成组件/路由/权限/API 接入和验收 |
+
+## 五、证据记录规范
+
+以 `examples/evidence.template.json` 为模板记录 10 个阶段。每阶段保留 `status`、`operator`、`date`、`evidence`,例如证据可以是带日期的运行日志路径或测试报告 URL。工具只验证字段存在,**不核验证据内容真实性**。`pending` 永远不等同 `passed`。
+
+**回滚演练**:先备份隔离测试环境,生成构建 A,部署到 staging,生成 B,再回滚至 A;比对前后首页和关键 URL 的 SHA/状态码,确认旧版恢复。测试的真实日志由人工归档。不得自动在生产环境执行此脚本包的任何写入动作。
+
+**SSRF 风险**:HTTP 模式默认仅 localhost。远端只允许在操作者明确指定 `--allow-remote` 且获得目标授权时检查;URL 不得包含用户名、密码、查询或 fragment。代理/CDN 的响应和 TLS 配置需人工复核。
+
+## 六、管理后台 UX 接入约定(下一层)
+
+- 导航继续沿用“工作台 / 我的网站 / 内容页面 / 外观设计 / 导入迁移 / 构建交付 / 网站设置”;技术工具统一收纳高级工具。
+- 验收中心可在**构建交付 > 验收报告**展示 JSON:优先级为 FAIL、WARN、SKIP、PASS,附修复建议,保留站点上下文。
+- 不在编辑页弹出多层确认;破坏性/正式发布才要求明确确认或审批。
+- 未获得 Art Design Pro 官方完整源码前,该交付**不能称为官方集成**;正式集成需基于实际 Vue 项目文件、授权版本、组件结构和构建配置开展。
+
+## 七、Known limitations
+
+- 文件可用性为当前 V8.16 README 描述,不保证用户本地工程文件路径完全相同,缺项会被标注 WARN。
+- 报告 **不是渗透测试、上线证明、真实发布演练或真实数据备份报告**。
+- 单站静态链接检查不能证明跨语言站的双向 hreflang;多语言配对需单独验证。
+- 对于在 CDN 或独立路径进行的预览,构建本地链接存在不代表预览 rewrite 逻辑完全正确,需要另行浏览器检查。
+- 使用 JSON 证据表并不代表完成任何业务审批或合规认证。
+
+## 八、隔离 CI
+
+提供 `.github/workflows/siteforge-v817-acceptance.yml` 作为**人工触发**的独立验收工作流:一组运行只读源码检查/测试,一组在临时 PostgreSQL 16 和 Redis 7 容器中执行最小存活检查,并上传脱敏报告。该 CI **不能替代**在实际 SiteForge 企业 Compose 下的完整业务链路和故障注入。
diff --git a/docs/V8_17_INTEGRATION_RELEASE_NOTES.md b/docs/V8_17_INTEGRATION_RELEASE_NOTES.md
new file mode 100644
index 0000000..01af231
--- /dev/null
+++ b/docs/V8_17_INTEGRATION_RELEASE_NOTES.md
@@ -0,0 +1,17 @@
+# V8.17 Integration Release Notes
+
+## 基线
+- V8.16.0-dev.1 完整源码,提供 30 个 pytest 测试模块;V8.16 既有报告记录 255 例通过,但官方脚本曾偶发卡住。
+- V8.17 以真实源码整合,而非仅以 Markdown 方案代替实现。
+
+## 变更范围
+- 新增 `scripts/regression_v817.py` 并统一 `.sh/.ps1` 回归入口;逐批超时、隔离诊断、可机器读取报告、失败保留证据。
+- V8.17 audit、WordPress URL baseline 工具及单元测试集成。
+- 修复 Chromium desktop/mobile 登录上下文共享,CI 增加真实浏览器检查。
+- 主项目版本、Compose 镜像版本、相关断言同步更新。
+
+## 未完成
+- 当前工作容器不具备 Docker Engine、隔离 PostgreSQL/Redis 服务器和正常可访问 localhost 的 Chromium policy;这些并不作为通过项。
+- 不自动覆盖生产卷或执行现有部署迁移。
+- 不包含 Art Design Pro 官方可编译完整工程,不声称完成前后端集成。
+- 完整历史版本说明保留在 README 后半部分。
diff --git a/docs/V8_17_INTEGRATION_TEST_RESULTS.md b/docs/V8_17_INTEGRATION_TEST_RESULTS.md
new file mode 100644
index 0000000..fbb9d34
--- /dev/null
+++ b/docs/V8_17_INTEGRATION_TEST_RESULTS.md
@@ -0,0 +1,31 @@
+# SiteForge Enterprise V8.17.0-dev.1 — 源码集成回归结论
+
+**结论:V8.17 完整源码已整合并通过隔离本地集成回归。生产上线验收尚未完成。**
+
+## 已实际执行
+
+- 完整项目现有 + 新增自动化测试:**288 项/轮,连续 2 轮均通过**(总计 576 次通过,0 失败、0 超时)。详细见 `reports/v817-regression.json` / `*.html` 与 8 份 verbose 日志。
+- V8.17 新增 33 项专项测试包含异常超时检测、报告落地与旧 URL 路径一致性检查。
+- HTTP 真实端到端:`/api/ready` → 登录/CSRF → 新建站点、页面 → Worker 异步构建 → ZIP 下载 → HTML 预览,通过。
+- `scripts/verify_release.py`、全体应用 Python 编译、前端 JS 语法检查:通过。
+- V8.17 只读仓库审计:**12 PASS / 0 FAIL / 0 WARN / 2 SKIP**(未提供实际 `.env` 和正式发布证据)。
+- GitHub Actions 两套 CI YAML 结构能正常解析,但**没有在 GitHub Actions 运行**。
+
+## 仍然未通过 / 尚未执行
+
+- Chromium 当前环境:使用 `/usr/bin/chromium` 仍遇到 `net::ERR_BLOCKED_BY_ADMINISTRATOR`,属于执行环境策略限制;新增独立浏览器 CI,需在允许访问 localhost 的 Runner 中验收。
+- 当前容器没有 Docker Engine;PostgreSQL/Redis、多 Worker、远程 SFTP/S3/Agent 发布与真实回滚,均未执行真实集成验收。
+- Art Design Pro 官方完整源代码未提供;没有进行官方前端编译集成。
+- V8.16 曾完成的 5,000 车型导入/构建压力测试,本轮未重复执行,不把历史结果标记为新版本重测结果。
+
+## 修改范围
+
+- `scripts/regression_v817.py`、`.sh/.ps1`:可终止的批量回归和逐模块异常隔离,报告 JSON/Markdown/HTML + 日志。
+- `scripts/siteforge_v817_audit.py`、`scripts/export_wordpress_url_baseline.py`:无写入生产副作用的验收与 URL 路径检查。
+- `scripts/browser_smoke.py`:登录上下文复用、Playwright Chromium 自动查找、用户可覆写可执行文件路径。
+- `.github/workflows/`:连续两轮 Python 测试、真实 Chromium CI 以及独立 PostgreSQL/Redis 只读检查。
+- `app/version.py`、Compose 镜像、相关测试断言、README、文档:版本升级与项目交付文档。
+
+## 推荐下一验收步骤
+
+将此 ZIP 在隔离环境部署,先跑 `bash scripts/check.sh` 与 `python scripts/http_end_to_end.py`,随后用已配置安全凭据的独立测试 PG/Redis + Docker 验收,确认浏览器和部署链路;最后复核真实 WordPress URL 及正式发布、回滚和备份恢复。
diff --git a/docs/V8_18_ENTERPRISE_HARDENING.md b/docs/V8_18_ENTERPRISE_HARDENING.md
new file mode 100644
index 0000000..642b491
--- /dev/null
+++ b/docs/V8_18_ENTERPRISE_HARDENING.md
@@ -0,0 +1,65 @@
+# SiteForge Enterprise V8.18 — 企业底座实现与验收说明
+
+> 2026-10-23 · 版本 `8.18.0-dev.1` · **开发集成版(不是生产上线批准)**
+
+## 本轮已实施代码
+
+1. **PostgreSQL 灾备误操作保护**:当 `SITEFORGE_DATABASE_URL` 指向 PostgreSQL,原 SQLite `python -m app.backup` 立即拒绝执行,不会错误创建一份空 SQLite 备份。
+2. **PostgreSQL 专属离线灾备**:`app.pg_disaster_recovery` + `scripts/pg_disaster_recovery.py` 使用客户端 `pg_dump` custom 格式、`pg_restore --list`、媒体/构建文件清单和 SHA-256 校验,最后原子更名 ZIP;离线校验无需数据库。恢复仅允许**空 PostgreSQL 数据库 + 不存在的数据目录**,使用单事务 `pg_restore`,不覆盖线上站点。机密信息不会放在子进程命令行参数中。
+3. **SQLite→PostgreSQL 内容迁移**:`app.sqlite_pg_transfer` + `scripts/migrate_sqlite_to_postgres.py`:SQLite 在线备份 API 制作快照(前提仍要求业务停写),按外键顺序处理当前 51 张表,检查列覆盖和目标表为空(迁移登记表例外、必须 checksum 匹配),单 PostgreSQL 事务写入,逐表行数与 SHA-256 对账。目标库必须在停机维护期间运行全套初始化。
+4. **发布状态不确定时防重复执行**:`remote_deploy`/`remote_rollback` 在 Worker 租约过期、网络失败、超时或运行中取消后标记失败并要求管理员核对远端指针,禁止自动重试外部副作用。普通构建仍保留原有重试能力。
+5. **CI 验收扩充**:新增隔离 PostgreSQL 数据迁移端到端验证脚本;本地测试使用模拟 PG 协议和工具进程,并且严格区分模拟与真实 PG 证据。
+
+## 一、禁止直接更新生产卷
+
+新建独立测试环境,备份 V8.17 的数据库和共享存储。切换 SQLite→PG 是**运维迁移**而非自动覆盖 `.env`:不能仅把 SQLite 环境变量指向 PostgreSQL 后直接运行现有容器。系统代码、数据库、媒体目录和 `published` 卷是不同组件,必须统一维护窗口。
+
+## 二、PostgreSQL 备份(维护窗口)
+
+以下命令在有 `pg_dump`、`pg_restore`、`psql` 的管理主机执行;客户端版本应不低于数据库服务器主版本(示例 PG 16)。须先停 Web 和所有 Worker,阻止数据库写入与媒体上传。
+
+```bash
+export SITEFORGE_DATABASE_URL='postgresql://backup_user:***@db-host:5432/siteforge?sslmode=verify-full&sslrootcert=/secure/ca.pem'
+python scripts/pg_disaster_recovery.py backup --data /path/to/siteforge-data --output /external/backup-20261023.zip --confirmed-offline
+python scripts/pg_disaster_recovery.py verify /external/backup-20261023.zip
+```
+
+不要把真实密码写到共享日志或 shell 历史;上例为占位符。建议使用安全的环境注入或管理员端凭据文件。`manifest.json` 的 SHA-256 证明读取后的完整性,**不证明备份来自可信发布者,也不代表进行了加密**。备份需转移到其他设备,配置保留策略和访问限制;`published` 独立卷还需要独立备份。
+
+恢复必须**事先创建空 PostgreSQL 目标库**和**不存在的共享数据目录**,并使用新测试实例恢复验证:
+
+```bash
+python scripts/pg_disaster_recovery.py restore-new /external/backup-20261023.zip --data /path/to/new-storage --confirmed-offline
+```
+
+不会自动切换服务或覆盖目标数据库。恢复后需再跑 `/api/ready`、内容/媒体、构建和发布链路的 E2E。数据库恢复和文件重命名跨系统不属于一个原子事务,失败时目标必须保持隔离,直到人工对账完成。
+
+## 三、SQLite 数据升级到 PostgreSQL(数据库部分)
+
+1. **停站**:关闭 Web、所有 Worker、文件上传与计划任务,确保 SQLite 和媒体不再变动。
+2. **双重备份**:运行既有 SQLite 备份与源文件目录副本;将外部备份单独存储。
+3. **全新 PostgreSQL 库**:使用独立新库运行完整 SiteForge schema 初始化;允许 ledger 的既定行,其他业务表必须为空。
+4. **迁移数据**:
+
+```bash
+export SITEFORGE_DATABASE_URL='postgresql://migration_user:***@db-host:5432/siteforge_stage?sslmode=require'
+python scripts/migrate_sqlite_to_postgres.py --source /path/to/old/siteforge.sqlite3 --confirmed-offline --report /external/migration-evidence.json
+```
+
+5. **同步外部文件**:`/data/media`、`legacy`、`builds`、`archives` 与 `published` 卷独立同步/校验(迁移工具**不会**代替 rsync/对象存储迁移);不要把两个新旧服务同时指向可写目录。
+6. **验证**:逐表行数/哈希、媒体 URL、静态质量报告和线上正式域名一致性;如有失败保持旧站继续服务。
+7. **切换**:预生产全部通过后再人工批准业务切换,保留源库与可回滚路径。
+
+工具对旧 SQLite 数据库进行**只读备份快照**;向新 PostgreSQL 写入是显式 `--confirmed-offline` 才能执行的操作。导入使用单事务;建表(初始化)先于该事务,不等于整个目标库已经过生产级迁移验收。
+
+## 四、多 Worker / Redis
+
+PostgreSQL 是权威任务队列,Redis 只负责唤醒通知;Redis 不可用时 Worker 回退数据库轮询,不能把 Redis 看作持久化任务存储。V8.18 对发布任务加上“结果不确定则不重试”防护,但**未获得真实 Docker 2+ Worker 并发、断电、丢包和故障切换证明**,不能宣布多实例高可用。
+
+## 五、本轮验收证据限制
+
+本地环境未安装 Docker Engine、PostgreSQL 客户端或 psycopg,无法声称完成真实 PG/Redis Docker 验收。新增脚本和自动化测试提供代码及 CI 入口;应在 GitHub Actions 隔离 postgres 服务上实际运行 `scripts/verify_pg_bootstrap.py` 和 `scripts/verify_sqlite_pg_transfer.py`,保存日志,然后在运维侧完成正式恢复演练。
+
+## 六、尚未关闭的 V9.0 要求
+
+Art Design Pro 官方前端完整工程编译集成、复杂 WordPress 插件迁移、企业 SSO/MFA、长期容灾、同域路径多语言、真实服务器发布验收,以及完整移动端体验均仍在后续工作范围内。
diff --git a/docs/V8_19_ART_UNIFIED_BACKEND.md b/docs/V8_19_ART_UNIFIED_BACKEND.md
new file mode 100644
index 0000000..2490765
--- /dev/null
+++ b/docs/V8_19_ART_UNIFIED_BACKEND.md
@@ -0,0 +1,78 @@
+# SiteForge Enterprise V8.19 — 统一管理后台集成与验收手册
+
+**版本**:8.19.0-dev.1(开发集成版)
+**基线**:V8.18 完整源码
+**日期**:2026-10-23
+**状态**:业务代码、路由适配及后端接口已集成;Art Design Pro 官方完整源码不在交付包内,因此未执行官方 `pnpm build`,也未通过真实浏览器/线上反代验收。
+
+## 1. 范围与已实现
+
+- 新增 FastAPI `GET /api/console/bootstrap`:会话绑定的管理菜单、可见站点清单、指定站点权限能力清单;未授权站点返回 403。纯查询,不传递密钥或 CSRF token。
+- 新增 Vue `ConsoleHome.vue` 工作台、`ContentCenter.vue` 内容 CRUD 页面(调用已有 API,不复制业务数据库逻辑)。
+- 统一 Art 前端七个既有页面的请求错误/CSRF 同源处理与当前网站选择;通过 `_shared.ts` 获取服务端会话,权限**仍由后端检查**。
+- 官方路由只注册 **一个 `/siteforge` 导航树**,含工作台、网站管理、内容管理、网站设计、数据与迁移、构建与发布、系统管理;主题与 V8 高级工具隐藏为子路由,避免重复一级菜单。
+- 安装器支持 dry-run、幂等、文件冲突保护、备份、显式升级旧标记、SHA-256 安装核对。
+- SiteForge 登录页支持同站安全 `next=/siteforge/...` 返回路径;无跨域任意跳转。
+
+## 2. 前置要求
+
+1. 从官方网站获取 **Art Design Pro 官方完整源代码**、合法依赖和版本锁文件,确认对应的 `src/router/routes/asyncRoutes.ts` 结构。未提供源码时不得称为官方已经集成、编译成功。
+2. Node、pnpm 版本以该 Art 官方提交/锁文件要求为准。
+3. 采用 **一个 HTTPS 域名**承载 Art SPA 和 FastAPI API,统一 Session Cookie、CSRF 与 Host;不要通过跨域代理绕过后端 Session 与 CSRF。
+4. FastAPI 需保留 `/api/auth/login`、`/api/auth/me`、`/api/auth/logout` 权威登录路径。Art 演示账户和假 Token 不可作为正式 SiteForge 认证。
+
+## 3. 安装程序
+
+```bash
+# 在 SiteForge 根目录(以下路径自行替换)
+python scripts/install_into_art_design_pro.py /path/to/art-design-pro
+python scripts/install_into_art_design_pro.py /path/to/art-design-pro --apply
+python scripts/verify_art_design_pro_v819.py /path/to/art-design-pro
+
+# 安装了官方 npm 依赖后运行真正的编译(不是模拟)
+cd /path/to/art-design-pro
+pnpm install --frozen-lockfile
+cd /path/to/siteforge
+python scripts/verify_art_design_pro_v819.py /path/to/art-design-pro --build --report reports/v819-art-build.json
+```
+
+如果官方源码里已经存在旧 SiteForge 路由,**先备份源码**,并显式执行:
+
+```bash
+python scripts/install_into_art_design_pro.py /path/to/art-design-pro --apply --migrate-legacy --overwrite-views
+```
+
+安装器不会删除第三方业务路由;当人工调整过已经安装的 V8.19 路由时,会拒绝自动覆盖,并要求人工审查差异。若旧业务页面曾被人工修改,升级前先对比 `.siteforge-backup`。
+
+## 4. 同源反代
+
+使用 `deploy/art-design-pro/siteforge-nginx.conf.example` 作为 **示例**,人工配置证书、SPA 根目录、Host、上游 IP。正式生产必须启用 `SITEFORGE_COOKIE_SECURE=true`。完整反向代理后逐一检查:
+
+- 登录页 `/login`、认证 `/api/auth/me`、管理接口 `/api/console/bootstrap`;
+- Art 页面 `/siteforge/dashboard` 与 `/siteforge/content`;
+- 页面设计 `/editor/{site}/{entry}`、预览 `/preview/...`、媒体 `/local-media/...`;
+- 静态站构建与文件下载;
+- 401/403 行为、CSRF POST 保护、Session 过期后跳转,以及移动端导航。
+
+注意:Art 自带演示登录/用户菜单与 SiteForge 账户是**两套系统**。正式部署时必须关闭演示权限作为真实权限的假设,使用 SiteForge 会话作为权威身份;Art 路由的可见性只是界面提示,不可替代服务端授权。尚未实施企业 SSO。
+
+## 5. 已完成/未完成门禁
+
+| 验收项 | 状态 |
+|---|---|
+| 原项目自动化测试 | 本地可重复回归通过;原始日志随包交付 |
+| 新工作台授权 API | 自动化测试通过 |
+| Art 安装器模拟工程测试 | 自动化测试通过(仅验证适配逻辑) |
+| HTTP 站点创建 → Worker 构建 → 下载 → 预览 | 本地端到端通过 |
+| Art 官方 `pnpm build` | **未执行**(官方完整源码/依赖未提供) |
+| PC 与移动端真实 Art UI | **未执行**(需官方工程构建和正常浏览器环境) |
+| Nginx HTTPS + SameSite + Cookie/CSRF | **未执行**(需独立测试域名) |
+| PostgreSQL/Redis/多 Worker/发布灾备 | 延续 V8.18 的独立生产验收门禁 |
+
+## 6. 尚需完善(下阶段)
+
+- 将 Art 登录体验与 SiteForge 后端 Session 完整合一(实际官方仓库必须可编译、完成端到端路由守卫)。
+- 企业功能导航按站点角色动态显示(本轮后端已提供能力清单,但 Art 全局动态菜单权限筛选未能在官方工程中实测)。
+- 完整移动端和桌面端视觉验收、内容编辑历史、发布审批操作的一致性审查。
+- 将高级生产控制台原有大部分遗留界面彻底迁入统一模块,避免跳出 SPA。
+- 生产 Nginx、真实 Art build、数据库、Redis 和部署回滚的独立环境验收。
diff --git a/docs/V8_20_COMPLEX_WORDPRESS.md b/docs/V8_20_COMPLEX_WORDPRESS.md
new file mode 100644
index 0000000..8a8b35b
--- /dev/null
+++ b/docs/V8_20_COMPLEX_WORDPRESS.md
@@ -0,0 +1,48 @@
+# SiteForge Enterprise V8.20 — WordPress 复杂迁移增强
+
+版本:`8.20.0-dev.1`。此版本属于**开发集成版**,在 V8.19 完整源码上增加离线兼容预检、简单 ACF Repeater 结构保留、CSS 背景图本地化、批次暂存路径阻断。**不代表复杂 Elementor/ACF/WooCommerce 功能或设计已实现无损迁移。**
+
+## 1. 新增实际功能
+
+- 新增 `app/wp_complex_v820.py`,使用 `defusedxml` 安全解析最多 16 MB、20,000 条记录的 WXR,按已发布内容发现 URL 冲突、动态参数 URL、复杂 Elementor、ACF 字段、PHP 序列化、自定义文章类型、WooCommerce 和动态短代码。
+- 新增后台 `POST /api/migrations/sites/{site_id}/wordpress/wxr-preflight`:`multipart/form-data` 的 `file` 字段,登录、CSRF 和站点 Editor 权限均必需。只读返回 JSON,不写站点数据,不访问外网。
+- 现有 `/production-v8` WordPress 面板新增“兼容性预检”按钮和结果区。
+- `app/importers.py` 直接 WXR 导入:带参数的动态 URL 或缺失原始路径现在拒绝导入,不再凭 slug 猜测路径;ACF 简单 Repeater 结构保留在 `meta.fields` 和 `meta.acf_repeaters` 中,原始字段仍保留供核对。
+- `app/v8_workflows.py` WXR 暂存:复用预检阻断危险旧 URL;不再将自定义文章类型错当成分类;复杂组件形成发布审核项。
+- `app/wp_migration_v82.py` 媒体本地化扩展至行内 CSS `url(https://...)`、`data-background`、`data-background-image` 与已有 `srcset`。只允许已授权主机的资源进入自动下载,其他 URL 需要单独审核。
+- 新增 `scripts/wp_v820_preflight.py`:离线生成 JSON/HTML 风险报告;可选 `--dist` 对比静态 HTML 路径覆盖。
+
+## 2. 直接运行
+
+在项目根目录:
+
+```bash
+python scripts/wp_v820_preflight.py fixtures/v820/wordPress-complex-sample.xml --output reports/v820-demo.json
+# 可选,待已经执行静态构建后:
+python scripts/wp_v820_preflight.py export.xml --dist /absolute/path/to/build/dist --output reports/my-site.json
+```
+
+脚本返回码:`0` 表示没有识别到阻断问题且(如指定 `--dist`)没有发现缺失的 HTML 路径;`1` 表示出现阻断或缺失路径;`2` 表示输入、读取或 XML 解析失败。**退出码 0 不意味着布局或业务功能无损,人工审核仍是发布条件。**
+
+在统一后台选择网站 → 网站生产控制台 → WordPress 迁移 → 选择 WXR 文件 → 点击“检查旧 URL / Elementor / ACF / 动态功能”。
+
+## 3. 风险与未完成项目
+
+| 方向 | 当前实现 | 未完成的生产验收 |
+|---|---|---|
+| Elementor | 识别已支持/未知组件,有限静态 HTML 转换,保留原始 JSON | 容器布局、动态标签、全局样式、响应式精确还原、交互插件 |
+| ACF | 单值字段、简单 Repeater 的有界结构提取;PHP 序列化只标记,不执行 | 字段组 schema、Flexible Content、嵌套 Repeater、关系字段、文件字段 |
+| WooCommerce | 识别产品相关数据并列入审核 | 变体、价格策略、库存、购物车、结算、订单等动态服务 |
+| 媒体 | 同源 HTTPS/HTTP 图片与 CSS 背景图引用分析及本地化 | 跨域 CDN 授权、尺寸变体资源、未引用附件、CSS 外链文件及版权许可 |
+| URL/SEO | WXR 旧路径预检,静态构建路径核查,原有 SEO 差异工具 | 真正旧站的完整 Sitemap + robots + 分页 + 301 + Canonical 实测核对 |
+| 插件功能 | 表单、动态短代码、iframe 标为风险 | CRM/表单业务重建与实际浏览器 E2E |
+
+WXR XML **不包含附件原文件,也不包含完整站点设置和全部插件配置**;需另行取得对应 WordPress 资源以及已获授权的插件数据。
+
+## 4. 迁移作业建议
+
+先备份 WordPress 原站、数据库与媒体文件;离线预检 WXR 和 Sitemap;修复阻断问题后按站点逐步暂存/提交数据;迁移 CSS/响应式图片及全部媒体;新旧站逐 URL 对账,再在独立预生产环境实测 PC/移动端布局、交互、SEO、动态表单及发布回滚。上线前必须完成每一个已识别风险的人工复核。
+
+## 5. 测试证据边界
+
+自动化使用本地固定 WXR、TestClient 和模拟媒体,禁止把这些结果描述为真实客户网站的迁移通过。V8.18 生产 PostgreSQL/Redis/Docker 多 Worker 与 Art Design Pro 官方工程编译仍未完成真实现场验收。本包不会自行连接或更改用户的生产网站。
diff --git a/docs/V8_21_1_STUDIO_UX.md b/docs/V8_21_1_STUDIO_UX.md
new file mode 100644
index 0000000..54bf50b
--- /dev/null
+++ b/docs/V8_21_1_STUDIO_UX.md
@@ -0,0 +1,51 @@
+# SiteForge V8.21.1 — 可视化编辑器体验增强
+
+状态:`8.21.1-dev.1`,在 V8.21 源码基础上做前端编辑器的向后兼容升级。
+
+## 已实际开发的内容
+
+1. **页面区块面板**:按标题、类型、嵌套区块内容搜索;拖拽排序继续保留;新增上/下移动、显隐、复制和删除快捷操作;支持 Alt+↑/↓ 按键移动聚焦的结构行。
+2. **属性分区**:从单一长滚动面板拆分为「内容」「样式」「响应式」,多列布局与平板/手机跨度和显隐均放在响应式面板。
+3. **PC/平板/手机预览**:桌面自适应宽度、平板 780px、手机 390px;展示明确设备标签,窄屏可分别展开区块侧栏和属性侧栏。
+4. **页面刷新安全性**:所有预览请求使用 `AbortController` 取消上一个请求,并通过请求序号和页面 ID 双重核对;旧请求不得覆盖新预览。
+5. **草稿可靠保存**:调用保存接口前冻结请求数据,写入过程中若发生额外本地修改不会错误清除 dirty 标志;同一时间仅处理一项保存请求,页面切换时阻止混淆。
+6. **撤销与重做**:历史操作按钮可用状态实时更新;连续输入编辑合并短时间内的快照,提供 Ctrl/Cmd+S 快捷保存(前端输入框内保留浏览器默认编辑撤销行为)。
+7. **顶部操作层级**:保留高频保存/发布操作,低频的版本历史、全站设计、构建等放入「更多操作」菜单。移动端保存按钮仍可见。
+8. **测试辅助**:纯函数 `editor-helpers.js` 以 Node Test Runner 检验,新增浏览器烟测脚本(需要运行环境允许本机 HTTP)和可离线打开的静态交互演示。
+
+## 安全与兼容约束
+
+- 不修改已有页面 `blocks` JSON 数据模型、不新增生产数据库迁移;旧页面和已有 V8.21 草稿可以继续使用。
+- 仍使用原有 `PUT /studio-draft` 的 `expected_revision` 乐观锁、发布接口、站点权限及审批逻辑。页面草稿保存不会自动发布静态网站。
+- 编辑器 iframe 仍使用 `sandbox="allow-scripts"`,未添加 `allow-same-origin`。UI 表单不执行用户自定义代码。
+- **没有**实现无限自由定位、Figma 式像素编辑、复杂组件级协作锁、跨页面模板继承或官方 Art Design Pro 全工程真实编译。相关模块需要后续专项开发。
+
+## 本次验证结果
+
+- JavaScript 纯函数:6/6 通过(`node --test tests_js/editor_ux.test.cjs`)。
+- Python 编辑器专项和既有历史/映射:9/9 通过;新静态界面测试:3/3。
+- 现有 39 个 Python 测试模块**分段或独立运行**,累计 328 个用例通过;其中前六批计 215 项、批次 7 拆分运行计 26 项、余下独立模块 87 项。
+- HTTP + Worker 实际建站构建:通过(隔离临时 SQLite 环境)。
+- Python 编译、全部 `app/static/*.js` 语法校验、工程静态发布检查:通过。
+- 一键完整回归:**未通过完整单次执行门槛**,第七批发生进程停滞,已记录。独立模块通过不代表一键执行器稳定。
+- 真实 Chromium 操作:当前执行环境 `ERR_BLOCKED_BY_ADMINISTRATOR`,不计为通过;保留 `scripts/browser_studio_v8211.py` 供在 CI/开发机复测。
+- Docker/PostgreSQL/Redis/Art Design Pro 官方工程:未进行本轮真实环境验收。
+
+## 运行与使用
+
+```bash
+python scripts/http_end_to_end.py
+node --test tests_js/editor_ux.test.cjs
+python -m pytest -q tests/test_editor_ux_v8211.py
+python scripts/browser_studio_v8211.py # 需本地 Chromium、Playwright、本机 HTTP 访问权限
+```
+
+网站编辑器继续通过 `/editor/{site_id}/{entry_id}` 访问。单独查看视觉交互可直接在浏览器打开 `examples/Studio_V8_21_1_交互预览.html`,该文件为**示例 UI**,不访问数据库也不是真实发布端。
+
+## 后续计划
+
+- 进行真实浏览器的 PC/平板/手机 E2E,并按操作可用性修复视觉缺陷;
+- 实现嵌套 layout 子组件的独立属性选择、拖拽落点可视化与组件版本继承;
+- 增加数据源联动的结构化表单编辑,而非复杂 JSON 文本框;
+- 拿到 Art Design Pro 官方项目后进行完整编译及权限/路由集成验收;
+- 排查一键测试执行器跨模块进程停滞并在 CI 设置稳定性门槛。
diff --git a/docs/V8_21_2_EASY_STUDIO.md b/docs/V8_21_2_EASY_STUDIO.md
new file mode 100644
index 0000000..e19e6a1
--- /dev/null
+++ b/docs/V8_21_2_EASY_STUDIO.md
@@ -0,0 +1,61 @@
+# SiteForge V8.21.2 — 新手友好可视化编辑器
+
+> 基于 V8.21.1 完整源码的易用性专项迭代。交付性质:**开发集成版**,未改动正式生产数据和已有网站区块 JSON 模型。
+
+## 一、面向使用者的新操作流程
+
+**1. 选择内容**:左侧选择正在编辑的页面;点击一个已有内容区块,或点击醒目的「+ 添加内容区块」。区块库在不使用时自动折叠,左侧结构支持搜索和排序。
+
+**2. 修改内容**:右侧默认展示内容字段;只有需要时才点击「样式」或「手机/平板」。支持在中间预览区直接单击一个带有 `data-block` 的区块,自动定位到右侧属性面板;原有双击文字编辑继续保留。
+
+**3. 保存与发布**:右上角固定显示「保存草稿」「发布设计」。保存草稿使用原有 CSRF 与 `expected_revision` 乐观锁,不会自动改变正式构建;发布设计更新内容库,网站仍需完成后续静态构建与正式部署。版本历史、全站设置、构建等较少使用的操作集中在「更多」。
+
+页眉下方新增 3 步提示,以及自愿打开的「新手使用指南」对话框,避免强制遮挡使用者。
+
+## 二、实现改动
+
+| 文件 | 说明 |
+|---|---|
+| `app/templates/editor.html` | 重排顶部主操作、三级简洁步骤、左侧页面/内容优先、结构化侧栏、帮助对话框;修复旧版更多菜单标记不清晰问题 |
+| `app/static/editor.css` | 桌面/平板/手机布局、表单可读性、强调主操作、低频内容折叠、表单列表样式 |
+| `app/static/editor.js` | 区块库显隐、步骤反馈、选择后定位属性面板、逐项编辑重复内容、预览点击区块的父页面消息处理 |
+| `app/main.py` | 对编辑器预览中的 `[data-block]` 添加点击选择的桥接消息,并保持 `sandbox="allow-scripts"`(不启用同源权限) |
+| `tests/test_editor_easy_v8212.py` | 布局唯一 ID、主操作位置、预览消息限制、后端兼容性等静态检查 |
+| `examples/Studio_V8_21_2_新手友好版交互预览.html` | 离线、模拟数据的可操作示例;不写入任何真实站点 |
+
+### 对非技术人员的重大改善
+
+- `features` 核心优势、`stats` 数字指标、`faq` 常见问题、`gallery` 图片画廊、`carousel` 轮播、`tabs` 标签页、`pricing` 服务方案:从 JSON 数组文本框改为「第 N 项 / 普通输入框 / 添加 / 删除 / 上下移动」。
+- 原有项目后台接口、页面数据结构、草稿快照及发布审批流程保持兼容;列表编辑只是前端输入方式改变。
+- 中间预览可点击区块实现选中;安全边界在父窗口同时检查 `event.source === frame.contentWindow` 与真实已加载 block ID。
+
+## 三、当前测试记录
+
+- 编辑器/设计器相关 pytest:28 项通过。
+- 内容历史、核心业务、旧版流程 pytest:30 项通过。
+- Node.js 编辑器工具函数:6 项通过。
+- Python 源码编译和 `editor.js` 语法检查通过;离线示例 JS 通过 Node.js 语法检查。
+- `python scripts/http_end_to_end.py`:通过 HTTP/CSRF/建站/Worker 构建/ZIP/预览。
+- **未执行**全项目一次性回归;既有版本的回归执行器有过偶发进程停滞,需在隔离 CI 上再做全量测试。
+- **未完成**完整 Chromium PC/移动端操作验收:本运行环境的 Chromium 截图启动超时,故不能宣称浏览器 E2E 通过。
+- **未完成**官方 Art Design Pro 全工程编译、生产 PostgreSQL/Redis 与真实发布环境验收。
+
+## 四、运行方法
+
+```bash
+# 运行原 SiteForge 后端(环境配置保持原项目方式)
+# 编辑器入口:/editor/{site_id}/{entry_id}
+python -m pytest -q tests/test_editor_easy_v8212.py tests/test_editor_ux_v8211.py tests/test_studio_v86.py tests/test_site_design_v87.py
+node --test tests_js/editor_ux.test.cjs
+python scripts/http_end_to_end.py
+```
+
+离线预览可直接双击 `examples/Studio_V8_21_2_新手友好版交互预览.html`。演示中的保存/发布仅改变页面临时示例状态,不会调用任何线上 API。
+
+## 五、仍需产品验收的体验问题
+
+1. 真实 PC/移动端浏览器中验证侧栏展开、点击画布选中、键盘和焦点移动。
+2. 对复杂 `layout` 嵌套子组件实现更细的画布选中与属性编辑(当前点击子组件会定位其父布局区块)。
+3. 对图片上传和媒体选择增加直观的缩略图与拖拽;当前仍沿用旧媒体库交互。
+4. 组织非技术同事完成一次创建区块→修改→保存→发布的实操测试,依据其反馈再做收敛。
+5. 后续将本版业务页面真正装入 Art Design Pro 官方工程并执行 `pnpm build` 与浏览器集成。
diff --git a/docs/V8_21_3_MOBILE_THEMES.md b/docs/V8_21_3_MOBILE_THEMES.md
new file mode 100644
index 0000000..a1848f1
--- /dev/null
+++ b/docs/V8_21_3_MOBILE_THEMES.md
@@ -0,0 +1,46 @@
+# SiteForge V8.21.3 — 四套主题移动端统一优化说明
+
+## 当前基线结论
+
+原 V8.21.2 的 automotive、industrial、corporate、minimal 四套内置专业主题具有基本响应式 CSS(移动端单列卡片、缩小标题、部分间距调整),但原导航在手机上是页眉下方的纵向展开列表,**不是独立抽屉**;没有底部快捷 Tabs 配置;因此不能将旧版称为全面完成移动端优化。V8.21.3 针对这些明确缺口做了源码级修改,并完成指定场景的浏览器验收。不能把 4 种代表页的验收当成所有页面和所有机型的充分验收。
+
+## 新版功能
+
+- 四套主题在 1024px 及以下使用右侧抽屉,点击汉堡按钮展开,多级目录可单独展开;移动端原“分类 Mega 菜单”链接合并进入抽屉。
+- 抽屉支持关闭按钮、遮罩、Escape、Tab 焦点约束以及关闭时焦点返回;支持页面跳转时关闭菜单。桌面导航保持原有样式。
+- 手机 767px 及以下可显示可选底部 Tabs,**默认关闭**,无任何按钮时也无需配置。启用后可添加 1–5 项,删除、上下排序、选择标题和预置图标、填写 URL,也可以配置一个“打开菜单”操作。
+- 链接允许站内路径(`/products/`)、`https://` 安全外链(如 WhatsApp)、`tel:` 与 `mailto:`;不允许 `javascript:`、`data:`、`http:`、协议相对地址或带用户名/密码的 HTTPS URL。存入数据库前全部由服务端校验。
+- 底栏自动适配设备安全区,启用时为页面底部预留空间以免遮挡内容;停用后没有额外留白。
+- 手机端卡片、图片、标题和文字区域增强窄屏 CSS;保留桌面模板、页面路径、SEO 数据、全站设计版本及发布审批模型。
+
+## 管理后台操作
+
+1. 进入目标网站的“可视化编辑器”,打开“全站页眉与页脚”。
+2. 找到“手机底部快捷导航 Tabs(可选)”。不开启时,手机页面**不显示**底栏。
+3. 开启后添加 1–5 个项目,分别填写文字、选择图标和点击操作;可上移、下移和删除。
+4. 可点击“预览草稿”查看当前编辑器预览;“保存全站草稿”**不会**修改已生成的网站;“发布全站设计”后,还需要重新构建网站并按既有流程部署。
+5. 手机菜单仍使用全站导航树,并继承二级、三级菜单结构。高级导航树的内容编辑入口仍保留原有 JSON 编辑方式;本轮重点是手机导航呈现,不是完全替换所有导航的配置方式。
+
+## 实现文件
+
+- `app/site_design_v87.py`:增加 `mobile_tabs` 配置、默认关闭、链接与图标字段校验、草稿隔离。
+- `app/theme_pro.py`:四套主题渲染上下文与预置图标。
+- `themes/{automotive,industrial,corporate,minimal}/pro.html`:按配置渲染底部栏与激活态。
+- `app/static/commercial.css`:统一右侧抽屉、遮罩、Tabs、手机端布局。
+- `app/static/pro.js`:菜单打开/关闭、焦点管理、Esc、滚动锁定、抽屉分类链接及底部菜单按钮。
+- `app/templates/editor.html`、`app/static/editor.js`:非 JSON 的 Tabs 增删、排序、名称、图标、路径配置。
+- `tests/test_mobile_tabs_v8213.py`、`scripts/browser_mobile_v8213.py`:专项验证。
+
+## 验证范围
+
+- Python:四个内置主题的真实静态构建、草稿隔离、发布生效、关闭后无 Tabs、非法链接与数据结构拦截。
+- Chromium:利用实际模板 HTML/CSS/JS 在 `page.set_content` 离线渲染,验证抽屉、遮罩、Esc、底栏菜单、桌面隐藏、脚本错误。对首页、产品列表、产品详情、文章详情分别用 375/390/430px 宽度检测水平溢出。
+- HTTP+Worker:原有登录、CSRF、建站、后台任务、构建、ZIP 和预览链路再次通过。
+- 浏览器执行环境不允许 Chrome 导航到 `file://` 和 localhost,因此浏览器检查采用 HTML/CSS/JS 内联加载方式;这**不等于**真实域名/CDN/移动设备端到端验收。
+
+## 未关闭事项
+
+- 用户上传的自定义第三方主题不在本轮四套内置主题的统一抽屉改造范围内。
+- 真实 iOS Safari、Android Chrome 实机和全部内页(如询盘、搜索、案例、FAQ)仍需专项验收。
+- Art Design Pro 官方完整工程的构建验证、正式 PostgreSQL/Redis/Docker 生产环境验收沿用前一版未关闭项。
+- 本版本只是开发集成版;发布静态站不会自动覆盖线上生产数据。
diff --git a/docs/V8_21_EDITOR_AND_MAPPING.md b/docs/V8_21_EDITOR_AND_MAPPING.md
new file mode 100644
index 0000000..01ce017
--- /dev/null
+++ b/docs/V8_21_EDITOR_AND_MAPPING.md
@@ -0,0 +1,50 @@
+# SiteForge V8.21:可执行开发说明和验收边界
+
+状态:**8.21.0-dev.1 开发集成版**。基于 V8.20 完整源码开发,未对用户生产环境执行升级。
+
+## 页面编辑器历史
+
+- 数据表 `studio_history`:site_id / entry_id / revision / kind(draft,published) / snapshot_json / snapshot_hash / actor / created_at。支持删除站点/页面后的级联清理,不额外泄露其他站点记录。
+- `GET /api/sites/{site}/pages/{entry}/studio-history?limit=30`:查看当前网站当前页面最近版本(1–100);最低 viewer 权限。
+- `GET .../studio-history/{history_id}`:读取单个历史快照并验证 SHA-256;最低 viewer 权限。
+- `POST .../studio-history/{history_id}/restore`:请求体 `{"expected_revision":2}`;最低 editor 权限;采用乐观修订检查,只创建新的 studio_drafts 草稿,不调用静态构建,也不修改已发布内容。
+- 发布仍需要 owner 权限;发布后的页面必须单独执行正式静态构建和审批才能上线。
+- 历史表目前**未配置保留上限/外部归档**,大规模多人协作前应定义存储周期与审计导出策略。原先保存在本机浏览器会话里的撤销/重做仍可用,不属于持久化历史。
+
+## 数据映射
+
+旧式 `{ "title": "name" }` 规则保持兼容。新增可声明的字段处理(不能传入任意 SQL、JavaScript 或 Python):
+
+```json
+{
+ "title": {"source":"name", "trim":true, "case":"upper", "prefix":"MODEL: "},
+ "brand": {"source":"brand", "case":"lower", "default":"Unspecified"},
+ "route": "old_url"
+}
+```
+
+- `POST /api/factory/sites/{site}/mapping/run` 带 `dry_run:true` 返回 `plan_hash`、冲突列表、错误、示例数据。
+- 再调用该 API,传 `dry_run:false`、`expected_plan_hash` 及 `on_conflict: "abort"|"skip"|"replace"`。预检被其他人修改后拒绝旧指纹。建议以 `abort` 开始试点,数据修改需要明确选择 `replace`。
+- `plan_hash` 包含映射后的内容及相关已有记录完整状态,不包含远程认证信息;它是乐观变更检查**不是防篡改签名**,也不是跨进程数据库锁。
+- **限制**:当前装配持久化沿用原有逐条 `save_entry` 路径,不保证批次级 ACID 原子回滚。`abort` 可阻止预检时已知的错误和冲突,但不能承诺极端并发写入期间整批原子提交。批次回滚及并发事务化属于后续 P1 任务。
+- 每批最多 1,000 条,保持原有容量上限;映射不运行代码,也不会自动创造业务数据。
+
+## UI
+
+- 原生编辑器 `/editor/{site}/{entry}` 新增「版本历史」弹窗与确认恢复;所有文案提示恢复仅生成草稿。
+- 原生网站工厂 `/factory-studio` 的内容导入新增字段变换、严格冲突策略和预检绑定;原有 Art Design Pro 官方工程尚未获得,因此未验证其实际 `pnpm build` 和完整业务端集成。
+
+## 本地验收记录
+
+- `38` 个测试模块分 10 组独立运行,共 `325 passed`;所有分组原始输出见 `reports/v821_manual_verified/`。
+- `python scripts/http_end_to_end.py`:通过登录/CSRF → 创建网站与页面 → Worker 构建 → ZIP → 预览。
+- `python scripts/verify_release.py`、`python -m compileall -q app` 以及 `node --check app/static/*.js`:通过。
+- 一键长流程在当前环境出现过停滞。已对 V8.21 执行器增加进程组超时清理,但其完整单次运行稳定性仍需独立 CI 复验;不计入上面的“通过”判定。
+- **未验收**:真实 PostgreSQL/Redis、Docker 多 Worker、浏览器/移动端视觉交互、真实 WordPress 复杂迁移、官方 Art Design Pro 编译、远程发布与回滚。
+
+## 后续门槛
+
+1. 建立隔离生产仿真环境,在 PostgreSQL 模式跑迁移/数据装配/编辑历史多进程用例并演练恢复。
+2. 使批量导入具有数据库级事务一致性、导入版本差异和可回滚工作流。
+3. 将编辑器版本历史组件并入 **官方** Art Design Pro 工程,覆盖桌面与移动端真实浏览器 E2E。
+4. 扩展整站模板与组件级版本继承、协作审批及内容设计依赖图。
diff --git a/examples/Studio_V8_21_1_交互预览.html b/examples/Studio_V8_21_1_交互预览.html
new file mode 100644
index 0000000..3bdfca9
--- /dev/null
+++ b/examples/Studio_V8_21_1_交互预览.html
@@ -0,0 +1,13 @@
+SiteForge Studio UX · 离线演示
+
+◈ SiteForgeVisual Studio · V8.21.1首页 / 版式编辑
+
+HESU AUTOHOME PRODUCTS GLOBAL NETWORK ABOUT CONTACT
CHINA SOURCING · GLOBAL DELIVERYYour Global Automotive Partner
Connecting vehicle sourcing, global logistics and local delivery through one integrated solution.
EXPLORE SOLUTIONS → Global SourcingDiscover a wide range of automotive solutions.
Reliable DeliveryInternational logistics and export expertise.
Local SupportAfter-sales and overseas delivery network.
+
+
diff --git a/examples/Studio_V8_21_2_新手友好版交互预览.html b/examples/Studio_V8_21_2_新手友好版交互预览.html
new file mode 100644
index 0000000..8008118
--- /dev/null
+++ b/examples/Studio_V8_21_2_新手友好版交互预览.html
@@ -0,0 +1,37 @@
+SiteForge · 简洁可视化编辑器交互预览
+HESU Auto首页 / 页面设计
已载入演示更多 ▾
+只需 3 步① 选择或添加内容→② 修改右侧属性→③ 保存并发布
+
+
+
+
+
diff --git a/examples/evidence.template.json b/examples/evidence.template.json
new file mode 100644
index 0000000..0edce6b
--- /dev/null
+++ b/examples/evidence.template.json
@@ -0,0 +1,12 @@
+{
+ "isolated_pg": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "redis_ping": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "docker_health": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "backup_restore": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "browser_smoke": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "wordpress_url_parity": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "staging_deploy": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "rollback": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "production_approval": {"status": "pending", "operator": "", "date": "", "evidence": ""},
+ "art_design_pro": {"status": "pending", "operator": "", "date": "", "evidence": ""}
+}
diff --git a/examples/mobile-showcase/automotive/article.html b/examples/mobile-showcase/automotive/article.html
new file mode 100644
index 0000000..c140fbf
--- /dev/null
+++ b/examples/mobile-showcase/automotive/article.html
@@ -0,0 +1,13 @@
+企业动态与行业资讯跳转至正文
+
+
+
+LATEST NEWS / INSIGHTS企业动态与行业资讯
了解最新项目与服务进展
项目背景
这是一段用于检查手机端资讯排版的样例正文。适当的字距、行距和图片宽度能够提升移动阅读体验。
← 返回新闻
+
+
+LET'S CONNECT从这里开始下一步。
专业车辆采购与全球交付
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/automotive/assets/blocks.css b/examples/mobile-showcase/automotive/assets/blocks.css
new file mode 100644
index 0000000..9ce1279
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/blocks.css
@@ -0,0 +1,29 @@
+/* Portable SiteForge blocks, loaded with every theme (all styles scoped to sf-). */
+.sf-section{background:var(--sf-bg,transparent);color:var(--sf-fg,inherit);padding:var(--sf-pad,82px) 0;margin:0}.sf-section:nth-of-type(even){background:var(--sf-bg,#f4f6f9)}.sf-container{max-width:1200px;margin:auto;padding:0 32px}.sf-section h1,.sf-section h2,.sf-section h3,.sf-section p{color:inherit;line-height:1.3}.sf-section h1{font-size:clamp(38px,5vw,76px);font-weight:790;letter-spacing:-.052em}.sf-section h2{font-size:clamp(27px,3.2vw,44px);font-weight:760;letter-spacing:-.037em}.sf-kicker{display:block;font-size:12px;letter-spacing:.17em;text-transform:uppercase;font-weight:740;color:var(--accent,#3569d3);margin-bottom:18px}.sf-hero-grid,.sf-image-text{display:grid;grid-template-columns:1.08fr .92fr;gap:68px;align-items:center}.sf-hero-copy p{font-size:18px;line-height:1.75;max-width:520px;opacity:.75}.sf-hero-img,.sf-image-side{overflow:hidden;border-radius:9px;min-height:380px;background:linear-gradient(130deg,#d5e1ee,#eef3f9);display:grid;place-items:center}.sf-hero-img img,.sf-image-side img{width:100%;height:100%;max-height:580px;object-fit:cover}.sf-hero-abstract{font-size:110px;color:#a7bdd4}.sf-button{display:inline-flex;align-items:center;justify-content:center;gap:28px;background:var(--accent,#315ee2);color:white!important;padding:16px 26px;border-radius:6px;font-size:14px;text-decoration:none;font-weight:720;margin-top:24px}.sf-section-head{display:flex;align-items:end;justify-content:space-between;margin-bottom:32px;gap:24px}.sf-section-head h2{margin:0}.sf-section-head>a,.sf-link{color:var(--accent,#3569d3);font-weight:700;text-decoration:none}.sf-prose{font-size:16px;line-height:1.85;max-width:930px}.sf-prose p{line-height:1.85}.sf-features,.sf-collection,.sf-stats{display:grid;gap:20px}.sf-features{grid-template-columns:repeat(3,minmax(0,1fr))}.sf-feature{border:1px solid #e0e6eb;padding:32px 28px;border-radius:8px;background:#fff;color:#253247}.sf-feature>span{font-size:12px;font-weight:800;color:var(--accent,#346add)}.sf-feature h3{font-size:22px;margin:30px 0 8px}.sf-feature p{font-size:14px;line-height:1.7;color:#647181}.sf-stats{grid-template-columns:repeat(4,1fr);padding:40px 0;border-top:1px solid #b7c0cd66;border-bottom:1px solid #b7c0cd66}.sf-stats>div{display:flex;flex-direction:column;gap:8px}.sf-stats strong{font-size:clamp(28px,4vw,53px);letter-spacing:-.04em}.sf-stats span{font-size:12px;opacity:.65;letter-spacing:.04em}.sf-collection{grid-template-columns:repeat(var(--sf-cols,3),minmax(0,1fr))}.sf-item{display:block;text-decoration:none;color:#233044;background:#fff;border:1px solid #e8edf1;border-radius:8px;overflow:hidden;transition:transform .2s,box-shadow .2s}.sf-item:hover{transform:translateY(-4px);box-shadow:0 18px 38px #344d7018}.sf-item-img{height:210px;overflow:hidden;background:#e4eaf0}.sf-item-img img{height:100%;width:100%;object-fit:cover}.sf-placeholder{display:grid;place-items:center;width:100%;height:100%;min-height:190px;background:linear-gradient(130deg,#e2eaf2,#c9d7e6);font-size:72px;color:#8399b2;font-weight:800}.sf-large{min-height:360px}.sf-item-copy{padding:25px}.sf-item-copy small{text-transform:uppercase;letter-spacing:.13em;color:var(--accent,#376edc)}.sf-item-copy h3{font-size:22px;margin:12px 0 7px}.sf-item-copy p{font-size:14px;line-height:1.6;color:#7a8796;min-height:42px}.sf-item-copy span{font-size:12px;color:var(--accent,#376edc);font-weight:800}.sf-image-text>div>p{font-size:17px;line-height:1.8;max-width:520px;opacity:.7}.sf-reverse>div:first-child{order:2}.sf-gallery{display:grid;grid-template-columns:repeat(3,1fr);gap:15px}.sf-gallery img{width:100%;height:230px;object-fit:cover;border-radius:6px}.sf-faq{max-width:920px}.sf-faq details{border-bottom:1px solid #dce3eb;padding:22px 0}.sf-faq summary{font-size:17px;cursor:pointer;font-weight:700}.sf-faq p{color:#647181;line-height:1.75}.sf-cta-wrap{display:flex;justify-content:space-between;align-items:center;gap:24px}.sf-cta-wrap h2{margin:0}.sf-cta-wrap p{font-size:16px;opacity:.75}.sf-section a{cursor:pointer}.sf-muted{opacity:.65}
+@media(max-width:820px){.sf-container{padding:0 20px}.sf-section{padding:50px 0}.sf-hero-grid,.sf-image-text{grid-template-columns:1fr;gap:26px}.sf-reverse>div:first-child{order:initial}.sf-hero-img,.sf-image-side{min-height:250px}.sf-features,.sf-collection{grid-template-columns:repeat(2,minmax(0,1fr))}.sf-stats{grid-template-columns:repeat(2,1fr)}.sf-cta-wrap{align-items:flex-start;flex-direction:column}}
+@media(max-width:540px){.sf-features,.sf-collection,.sf-gallery{grid-template-columns:1fr}.sf-section-head{display:block}.sf-section-head>a{display:block;margin-top:13px}.sf-item-img{height:220px}.sf-hero-grid{gap:22px}.sf-hero-copy p{font-size:16px}.sf-stats{gap:28px}}
+
+.sf-attributes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 32px;border-top:1px solid #e5eaf0}.sf-attribute{display:flex;justify-content:space-between;align-items:center;gap:18px;border-bottom:1px solid #e5eaf0;padding:19px 10px;font-size:14px}.sf-attribute span{color:#748497}.sf-attribute strong{font-weight:720;text-align:right}@media(max-width:640px){.sf-attributes{grid-template-columns:1fr}}
+
+/* SiteForge responsive no-code grid container */
+.sf-layout-grid{display:grid;grid-template-columns:var(--sf-layout-cols,1fr 1fr);gap:var(--sf-layout-gap,24px);align-items:stretch}
+.sf-layout-column{min-width:0}.sf-layout-column>.sf-section{padding:0;margin:0 0 18px}.sf-layout-column .sf-container{width:100%;padding:0}
+.sf-layout-column>.sf-section:last-child{margin-bottom:0}
+@media(max-width:820px){.sf-layout-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}
+@media(max-width:540px){.sf-layout-grid{grid-template-columns:minmax(0,1fr)!important}}
+
+/* V8.6: accessible, script-free commercial design blocks */
+.sf-slides{display:flex;gap:18px;overflow-x:auto;scroll-snap-type:x mandatory;padding:6px 0 18px;scrollbar-width:thin}
+.sf-slide{flex:0 0 min(78%,720px);min-width:220px;margin:0;scroll-snap-align:start}
+.sf-slide img{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:10px}
+.sf-slide figcaption{padding:10px 2px;opacity:.7;font-size:14px}
+.sf-video{aspect-ratio:16/9;max-width:1000px;background:#ebf0f5;border-radius:10px;overflow:hidden}
+.sf-video iframe{width:100%;height:100%;border:0}
+.sf-tab{padding:19px 0;border-bottom:1px solid #dde4ec;max-width:960px}
+.sf-tab summary{font-size:18px;font-weight:700;cursor:pointer}.sf-tab p{max-width:850px;line-height:1.8}
+.sf-prices{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px}
+.sf-price{padding:28px;background:#fff;border:1px solid #dee5ef;border-radius:10px;color:#29394b}
+.sf-price strong{display:block;font-size:27px;margin:14px 0}.sf-price li{margin:11px 0}.sf-price ul{padding-left:19px}
+.sf-map-address{font-style:normal;font-size:18px;white-space:pre-line;line-height:1.8}
+@media (prefers-reduced-motion:reduce){.sf-slides{scroll-behavior:auto}.sf-item{transition:none}}
+@media(max-width:540px){.sf-slide{flex-basis:92%}}
diff --git a/examples/mobile-showcase/automotive/assets/commercial.css b/examples/mobile-showcase/automotive/assets/commercial.css
new file mode 100644
index 0000000..353bee4
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/commercial.css
@@ -0,0 +1,84 @@
+/* SiteForge commercial theme UX layer: accessibility, layout rhythm, media, responsive navigation. */
+:root{--sf-shadow:0 20px 55px rgba(17,33,55,.08);--sf-shadow-hover:0 26px 65px rgba(17,33,55,.15)}
+html{scroll-padding-top:104px}body{font-feature-settings:"kern";text-rendering:optimizeLegibility}
+a,button,input,select,textarea{-webkit-tap-highlight-color:transparent}
+a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--pro-accent);outline-offset:4px}
+.pro-wrap{width:100%;max-width:min(var(--pro-wide),100%)}.pro-header{isolation:isolate;box-shadow:0 1px 0 rgba(15,35,60,.035)}.pro-head-inner{gap:clamp(15px,2vw,29px)}.pro-logo{min-width:0;flex-shrink:0;gap:12px}.pro-brand-logo{max-width:155px;max-height:46px;width:auto;height:auto;object-fit:contain}.pro-logo-name{overflow-wrap:anywhere}.pro-navigation{gap:clamp(11px,1.6vw,24px)}.pro-header-cta{white-space:nowrap}
+.pro-mobile-toggle,.pro-catalog-toggle,.pro-nav-disclose{border:0;background:transparent;color:inherit;line-height:1.2}
+.pro-mobile-toggle{min-height:44px;min-width:44px;border:1px solid currentColor;border-radius:6px;font-size:19px}.pro-nav-group{position:relative;display:flex;align-items:center;gap:2px}
+.pro-nav-disclose{padding:9px;font-size:13px}.pro-nav-children{position:absolute;top:100%;left:0;min-width:215px;max-width:330px;display:grid;gap:2px;padding:13px;box-shadow:var(--sf-shadow);border-radius:10px;z-index:45;background:var(--pro-background);border:1px solid var(--pro-border);color:var(--pro-text)}
+.pro-nav-children[hidden],.pro-mega[hidden]{display:none!important}.pro-nav-children a{display:block;font-size:13px;white-space:normal;padding:10px;border-radius:5px}.pro-nav-children a:hover{background:var(--pro-surface)}
+.pro-catalog-toggle{padding:11px 3px;white-space:nowrap;font-size:12px;font-weight:700}.pro-mega{position:absolute;top:100%;left:50%;transform:translateX(-50%);width:min(100%,var(--pro-wide));box-shadow:var(--sf-shadow);z-index:41;display:grid;grid-template-columns:215px 1fr;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);border-radius:0 0 15px 15px;padding:29px;align-items:start}.pro-mega>span{font-size:12px;letter-spacing:.06em}.pro-mega>div{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px 22px;overflow:visible}.pro-mega>div a{white-space:normal;padding:10px 5px;border-bottom:1px solid var(--pro-border)}
+.pro-home-hero{overflow:hidden}.pro-home-hero .pro-home-grid{min-height:660px;gap:5%}.pro-home-hero .pro-hero-copy{position:relative;z-index:2}.pro-home-hero .pro-hero-copy h1{font-size:clamp(46px,5.7vw,92px);line-height:1.06;letter-spacing:-.064em;max-width:15ch}.pro-home-hero .pro-hero-copy h1 em{font-style:normal;display:block}.pro-home-hero .pro-hero-copy p{font-size:clamp(15px,1.3vw,19px);line-height:1.75;max-width:44ch}.pro-home-hero .pro-hero-art{min-width:0;min-height:425px;position:relative;isolation:isolate}.pro-home-hero .pro-hero-art>img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}.pro-hero-art:has(.pro-abstract-car),.pro-hero-art:has(.pro-technical),.pro-hero-art:has(.pro-corporate-shape){background-position:center;background-size:cover;background-repeat:no-repeat}.pro-abstract-car,.pro-technical,.pro-corporate-shape{position:relative;z-index:1;pointer-events:none}
+.pro-hero-actions{display:flex;flex-wrap:wrap;align-items:center;gap:15px}.pro-button,.pro-header-cta,.pro-outline-button{min-height:48px;transition:transform .24s ease,box-shadow .24s ease,opacity .24s ease}.pro-button:hover,.pro-header-cta:hover,.pro-outline-button:hover{transform:translateY(-2px);box-shadow:0 10px 22px rgba(0,0,0,.10)}
+.pro-section{padding-top:clamp(76px,7vw,112px)!important;padding-bottom:clamp(76px,7vw,112px)!important}.pro-section-head{gap:16px}.pro-section-head h2{font-size:clamp(32px,3.5vw,53px);max-width:22ch}.pro-section-head .pro-text-link{align-self:end}
+.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}.pro-card{min-width:0;display:flex;flex-direction:column;transition:transform .28s ease,box-shadow .28s ease;border:1px solid var(--pro-border);background:var(--pro-background);overflow:hidden}.pro-card:hover{transform:translateY(-6px);box-shadow:var(--sf-shadow-hover)}.pro-card-cover{aspect-ratio:1.42;overflow:hidden;background:var(--pro-surface);position:relative}.pro-card-cover .pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .45s ease}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-card-body{display:flex;flex-direction:column;flex:1}.pro-card-body h3{font-size:clamp(19px,1.6vw,24px);line-height:1.26}.pro-card-body p{font-size:14px}.pro-card-link{margin-top:auto;padding-top:14px;font-size:13px;font-weight:700}
+.pro-art{min-height:100%;width:100%;position:relative;overflow:hidden;display:grid;place-items:center;isolation:isolate}.pro-art-ring{width:60%;aspect-ratio:1;border:1px solid currentColor;opacity:.13;border-radius:50%;transform:rotate(20deg);box-shadow:0 0 0 23px #ffffff10,0 0 0 57px #ffffff07}.pro-art-center{font-size:clamp(38px,5vw,80px);position:absolute;font-weight:800;letter-spacing:-.065em;opacity:.65}.pro-art-label{position:absolute;bottom:19px;left:22px;font-size:10px;letter-spacing:.22em;opacity:.7}
+.pro-breadcrumb{display:flex;gap:11px;flex-wrap:wrap;font-size:12px;align-items:center}.pro-breadcrumb a{text-decoration:underline;text-underline-offset:4px}.pro-product-hero{align-items:start;gap:clamp(28px,5vw,80px)}.pro-product-media{aspect-ratio:1.12;background:var(--pro-surface);overflow:hidden}.pro-product-media>img,.pro-product-media>.pro-art{width:100%;height:100%;object-fit:contain}.pro-product-summary{padding:clamp(12px,3vw,50px) 0}.pro-product-summary h1{font-size:clamp(36px,4.3vw,72px)}.pro-detail-layout{border-top:1px solid var(--pro-border);padding-top:50px}.pro-specs>div{display:grid;grid-template-columns:minmax(100px,40%) 1fr;gap:10px;align-items:baseline;padding:15px 0;border-bottom:1px solid var(--pro-border)}.pro-specs strong{overflow-wrap:anywhere}.pro-gallery-thumbs button{flex-basis:96px;height:76px}.pro-gallery-lightbox{backdrop-filter:blur(6px)}
+.pro-filter-panel{display:flex;align-items:end;flex-wrap:wrap;gap:16px;background:var(--pro-surface);border:1px solid var(--pro-border);padding:20px;margin-bottom:25px;border-radius:var(--pro-radius)}.pro-filter-panel label{display:grid;gap:7px;min-width:180px;flex:1}.pro-filter-panel input,.pro-filter-panel select{width:100%;background:var(--pro-background);border:1px solid var(--pro-border);padding:12px 14px;color:var(--pro-text);border-radius:6px}.pro-filter-count{white-space:nowrap;font-size:13px;padding:12px 0}.pro-card[hidden]{display:none!important}.pro-filter-empty{padding:38px;border:1px dashed var(--pro-border);text-align:center;grid-column:1/-1}
+.pro-inquiry input,.pro-inquiry textarea{min-height:46px}.pro-inquiry textarea{min-height:150px}.pro-inquiry-wrap{gap:clamp(32px,7vw,110px)}.pro-inquiry .pro-button{border:0;justify-self:start}.pro-footer-links a{padding-block:5px;display:inline-block}
+.pro-end-cta{overflow:hidden}.pro-end-cta h2{font-size:clamp(30px,4vw,64px)}
+.pro-rich{line-height:1.85}.pro-rich img{border-radius:var(--pro-radius);margin-block:24px}.pro-rich table{width:100%;display:block;overflow:auto;border-collapse:collapse}.pro-rich td,.pro-rich th{border:1px solid var(--pro-border);padding:10px 14px}.pro-rich a{text-decoration:underline;text-underline-offset:3px}
+@media(min-width:1025px){.pro-navigation>.pro-nav-group:hover .pro-nav-children:not([hidden]){visibility:visible}}
+@media(max-width:1150px){.pro-head-inner{gap:15px}.pro-navigation{gap:12px}.pro-logo-name{font-size:15px}.pro-header-cta{padding:13px 16px}}
+@media(max-width:1024px){.pro-head-inner{min-height:72px;flex-wrap:wrap}.pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto}.pro-navigation{display:none;width:100%;flex-basis:100%;flex-direction:column;align-items:stretch;gap:0;order:5;margin:0;padding:8px 0 20px;border-top:1px solid var(--pro-border)}.pro-navigation.is-open{display:flex}.pro-navigation>a,.pro-nav-group>a{padding:14px 8px;font-size:15px}.pro-nav-group{display:flex;flex-wrap:wrap;justify-content:space-between}.pro-nav-children{position:static;flex-basis:100%;max-width:none;width:100%;box-shadow:none;border-radius:0;border-left:2px solid var(--pro-accent);margin:0 0 12px 8px}.pro-nav-disclose{min-width:44px;min-height:44px}.pro-catalog-toggle{margin-left:auto}.pro-mega{top:100%;display:block;padding:20px}.pro-mega>div{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:12px}.pro-home-hero .pro-home-grid{min-height:560px}.pro-home-hero .pro-hero-art{min-height:325px}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
+@media(max-width:700px){.pro-wrap{padding-left:20px;padding-right:20px}.pro-utility{display:none}.pro-head-inner{min-height:66px}.pro-logo-name{max-width:140px;font-size:14px}.pro-brand-logo{max-width:125px}.pro-header-cta{display:none}.pro-catalog-toggle{font-size:11px;padding-inline:7px;margin-left:0}.pro-home-hero .pro-home-grid{display:flex;flex-direction:column;gap:36px;padding-top:48px;padding-bottom:48px}.pro-home-hero .pro-hero-copy h1{font-size:clamp(43px,10vw,68px)}.pro-home-hero .pro-hero-copy p{font-size:15px}.pro-home-hero .pro-hero-art{width:100%;min-height:290px}.pro-home-hero .pro-hero-bottom{display:none}.pro-section{padding-top:68px!important;padding-bottom:68px!important}.pro-section-head{align-items:flex-start}.pro-grid{grid-template-columns:1fr;gap:18px}.pro-card-cover{aspect-ratio:1.45}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-inquiry-wrap{display:grid;grid-template-columns:1fr;gap:30px}.pro-product-summary{padding:0}.pro-specs>div{grid-template-columns:1fr 1fr}.pro-mega{top:100%;max-height:65vh;overflow-y:auto}.pro-mega>div{grid-template-columns:1fr}.pro-filter-panel{display:grid;grid-template-columns:1fr;gap:12px}.pro-filter-panel label{min-width:0}.pro-footer-top{display:grid;grid-template-columns:1fr;gap:27px}.pro-footer-bottom{gap:12px;flex-wrap:wrap}}
+@media(max-width:410px){.pro-logo-name{max-width:115px}.pro-logo-symbol{font-size:28px}.pro-catalog-toggle{font-size:10px}.pro-mobile-toggle{min-width:42px}}
+@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation-duration:.01ms!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}
+
+/* Accessible static catalogue pagination for large inventories. */
+.pro-pages-nav{display:flex;gap:1rem;justify-content:center;align-items:center;margin:2rem 0;flex-wrap:wrap}
+.pro-pages-nav a{padding:.7rem 1.1rem;border:1px solid currentColor;border-radius:.4rem;text-decoration:none}
+.pro-pages-nav a:focus-visible{outline:3px solid var(--accent,#2563eb);outline-offset:3px}
+.pro-pages-nav span{font-size:.875rem}
+
+/* P6 international sites: small, keyboard-operable language switch. */
+.sf-language-switch{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;margin:8px 12px;padding:4px;border:1px solid rgba(120,130,145,.22);border-radius:10px;background:rgba(255,255,255,.08)}
+.sf-language-switch a{font:inherit;font-size:12px;line-height:1.5;padding:4px 7px;border-radius:6px;color:inherit;text-decoration:none}
+.sf-language-switch a[aria-current="page"]{font-weight:700;text-decoration:underline;text-underline-offset:3px}
+.sf-language-switch a:focus-visible{outline:2px solid #2563eb;outline-offset:3px}
+@media(max-width:640px){.sf-language-switch{margin:6px 0;width:auto}}
+
+/* V8.21.3 — Four-theme unified mobile off-canvas navigation & optional tab bar.
+ Desktop remains unchanged. Phone / tablet use one accessible drawer. */
+.sf-mobile-tabs{display:none!important}
+.sf-drawer-header,.sf-mobile-drawer-backdrop,.sf-drawer-extra{display:none}
+@media(max-width:1024px){
+ .pro-head-inner{flex-wrap:nowrap!important;gap:12px!important}
+ .pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto!important;flex:0 0 44px;min-width:44px;min-height:44px;position:relative;z-index:3}
+ .pro-catalog-toggle{display:none!important}
+ .pro-navigation,.pro-navigation.is-open{display:flex!important;position:fixed!important;inset:0 0 0 auto!important;width:min(88vw,360px)!important;height:100dvh!important;height:100vh;max-height:100dvh!important;flex:unset!important;flex-direction:column!important;align-items:stretch!important;order:unset!important;margin:0!important;padding:0 0 calc(20px + env(safe-area-inset-bottom,0px))!important;overflow-x:hidden;overflow-y:auto!important;background:var(--pro-background,#fff)!important;color:var(--pro-text,#19283c)!important;border:0!important;border-left:1px solid var(--pro-border,#ddd)!important;box-shadow:-15px 0 45px #0002!important;z-index:1201!important;transform:translateX(105%);visibility:hidden;pointer-events:none;transition:transform .24s ease,visibility .24s ease!important;overscroll-behavior:contain}
+ .pro-navigation.is-open{transform:translateX(0);visibility:visible;pointer-events:auto}
+ .sf-drawer-header{display:flex!important;align-items:center;justify-content:space-between;gap:12px;padding:20px 18px;border-bottom:1px solid var(--pro-border,#dde3e9);font-size:17px;font-weight:750;position:sticky;top:0;background:var(--pro-background,#fff);z-index:2}
+ .sf-drawer-close{min-width:44px;min-height:44px;border:1px solid var(--pro-border,#dde3e9);color:inherit;background:transparent;border-radius:10px;font-size:24px;cursor:pointer}
+ .sf-mobile-drawer-backdrop{display:block;position:fixed;inset:0;z-index:1200;background:#0b1b31a3;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .24s ease,visibility .24s ease}
+ .sf-mobile-drawer-backdrop.is-open{opacity:1;visibility:visible;pointer-events:auto}
+ body.sf-drawer-open{overflow:hidden!important}
+ .pro-navigation>a,.pro-navigation .pro-nav-group>a,.pro-navigation .sf-drawer-extra>a{display:flex;align-items:center;min-height:50px;padding:13px 20px!important;font-size:15px!important;color:inherit!important;white-space:normal;border-bottom:1px solid var(--pro-border,#dde3e9)!important;text-decoration:none!important}
+ .pro-navigation .pro-nav-group{display:flex;position:relative;flex-wrap:wrap!important;width:100%;gap:0;align-items:center;justify-content:space-between}
+ .pro-navigation .pro-nav-group>a{flex:1;min-width:0}
+ .pro-navigation .pro-nav-disclose{margin-right:12px;min-width:44px;min-height:44px;border-radius:9px;background:var(--pro-surface,#f4f6f8)}
+ .pro-navigation .pro-nav-children{position:static!important;width:100%!important;max-width:100%!important;box-shadow:none!important;border-left:3px solid var(--pro-accent,#e77730);border-radius:0!important;margin:0 0 8px 14px!important;flex-basis:calc(100% - 14px)!important;background:var(--pro-surface,#f6f8fa)}
+ .pro-navigation .pro-nav-children a{padding:12px 16px!important;font-size:14px!important;color:inherit!important;min-height:44px}
+ .sf-drawer-extra{display:block;padding:12px 0 5px}.sf-drawer-extra strong{display:block;font-size:12px;letter-spacing:.06em;padding:12px 20px;opacity:.65}.sf-drawer-extra a{font-size:14px!important}
+ .sf-drawer-close:focus-visible,.pro-mobile-toggle:focus-visible,.pro-nav-disclose:focus-visible,.sf-mobile-tab:focus-visible{outline:3px solid var(--pro-accent,#ff8200);outline-offset:2px}
+}
+@media(max-width:767px){
+ .pro-header-cta{display:none!important}
+ .pro-head-inner .pro-logo{min-width:0;max-width:calc(100% - 65px)}
+ .pro-logo-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .pro-home-hero .pro-home-grid{min-height:0!important}
+ .pro-home-hero .pro-hero-art{min-height:230px}
+ .pro-card,.pro-card-cover,.pro-product-media{max-width:100%;min-width:0}
+ .pro-rich img,.pro-content img,.sf-canvas img{max-width:100%;height:auto}
+ .pro-rich table{max-width:100%;overflow:auto}
+ .pro-grid,.pro-footer-top{min-width:0}
+ .sf-mobile-tabs{display:grid!important;position:fixed;z-index:1100;bottom:0;left:0;right:0;grid-template-columns:repeat(var(--sf-mobile-tab-count,4),minmax(0,1fr));align-items:stretch;padding:5px 6px calc(5px + env(safe-area-inset-bottom,0px));min-height:63px;background:var(--pro-background,#fff);color:var(--pro-text,#263446);border-top:1px solid var(--pro-border,#dfe4eb);box-shadow:0 -6px 24px #13223914}
+ .sf-mobile-tab{appearance:none;border:0!important;background:transparent!important;color:inherit!important;text-decoration:none!important;padding:6px 2px;min-height:50px;min-width:0;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:2px;border-radius:9px;cursor:pointer;font-family:inherit}
+ .sf-tab-icon{font-size:23px;line-height:1.15;font-weight:500}
+ .sf-tab-label{font-size:11px;font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .sf-mobile-tab[aria-current=page]{color:var(--pro-accent,#e47730)!important;background:var(--pro-surface,#f5f6f8)!important}
+ body:has(> .sf-mobile-tabs){padding-bottom:calc(76px + env(safe-area-inset-bottom,0px))!important}
+}
+@media(min-width:768px){.sf-mobile-tabs{display:none!important}}
+@media(prefers-reduced-motion:reduce){.pro-navigation,.sf-mobile-drawer-backdrop{transition:none!important}}
diff --git a/examples/mobile-showcase/automotive/assets/custom.css b/examples/mobile-showcase/automotive/assets/custom.css
new file mode 100644
index 0000000..e18a2d4
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/custom.css
@@ -0,0 +1 @@
+body .pro-utility[hidden]{display:none!important}@media(max-width:1023px){.sf-canvas .sf-section{padding-block:64px}}@media(max-width:767px){.sf-canvas .sf-section{padding-block:40px}.pro-wrap,.sf-container{padding-left:20px;padding-right:20px}body .pro-utility{display:none!important}}
\ No newline at end of file
diff --git a/examples/mobile-showcase/automotive/assets/factory.css b/examples/mobile-showcase/automotive/assets/factory.css
new file mode 100644
index 0000000..154ffb3
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/factory.css
@@ -0,0 +1,27 @@
+/* SiteForge trusted global shell. Site themes remain visually independent. */
+:root{--sf-shell:#12243d;--sf-shell-accent:#dcae71;--sf-shell-soft:#edf2f8;--sf-shell-fg:#173049}
+html{scroll-behavior:smooth}body{overflow-x:hidden}body>.header,body>.utility,body>.top,body>header,body>footer{display:none!important}
+.sf-theme-industrial{--sf-shell:#114e79;--sf-shell-accent:#92d5ed;--sf-shell-soft:#ebf6fa;--sf-shell-fg:#0c4267}
+.sf-theme-corporate{--sf-shell:#232d62;--sf-shell-accent:#b8bdff;--sf-shell-soft:#f3f3fa;--sf-shell-fg:#333468}
+.sf-theme-minimal{--sf-shell:#1e1e1c;--sf-shell-accent:#ded6c5;--sf-shell-soft:#f6f4ef;--sf-shell-fg:#252520}
+.sf-site-head{position:relative;z-index:25;background:var(--sf-shell);color:#fff;font-family:system-ui,-apple-system,sans-serif}
+.sf-head-inner{max-width:1240px;margin:0 auto;min-height:78px;padding:0 28px;display:flex;align-items:center;gap:28px}
+.sf-logo{display:flex;align-items:center;gap:11px;font-size:18px;text-decoration:none!important;color:#fff!important;white-space:nowrap}
+.sf-logo-mark{width:36px;height:36px;display:grid;place-items:center;background:var(--sf-shell-accent);color:var(--sf-shell);font-size:22px;font-weight:900;border-radius:9px}
+.sf-primary-links{display:flex;gap:23px;margin-left:auto;align-items:center}
+.sf-primary-links a,.sf-mega a{color:#fff!important;text-decoration:none!important;font-size:13px;font-weight:650;white-space:nowrap}
+.sf-primary-links a:hover{color:var(--sf-shell-accent)!important}
+.sf-head-cta{font-size:13px;font-weight:750;text-decoration:none;background:var(--sf-shell-accent);color:var(--sf-shell)!important;padding:13px 18px;border-radius:4px;white-space:nowrap}
+.sf-menu-toggle{display:none;margin-left:auto;color:white;border:1px solid #ffffff55;background:none;border-radius:4px;padding:8px 12px;font-size:19px;cursor:pointer}
+.sf-mega{display:flex;gap:18px;max-width:1240px;margin:auto;padding:0 28px 9px;overflow-x:auto}
+.sf-mega a{display:block;min-width:120px;border-top:1px solid #ffffff2e;padding:12px 8px}.sf-mega small{display:block;color:#ffffff99;max-width:160px;overflow:hidden;text-overflow:ellipsis;font-size:10px;margin-top:6px}
+.sf-site-foot{background:var(--sf-shell);color:white!important;font:14px/1.7 system-ui,-apple-system,sans-serif;padding:72px 28px 26px}
+.sf-foot-grid{max-width:1200px;margin:auto;display:grid;grid-template-columns:2fr 1fr 1fr;gap:70px}.sf-foot-grid h2{font-size:25px;color:white;margin:0 0 17px}.sf-foot-grid h4{font-size:13px;color:var(--sf-shell-accent);letter-spacing:.07em;margin:0 0 17px}.sf-foot-grid p{color:#ffffffae;max-width:380px}.sf-foot-grid a{display:block;width:fit-content;color:#fff!important;text-decoration:none;margin-bottom:8px}.sf-foot-lower{border-top:1px solid #ffffff33;max-width:1200px;margin:45px auto 0;padding-top:20px;display:flex;justify-content:space-between;color:#ffffff9e;font-size:12px}
+.sf-filters{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin:0 0 30px}.sf-filters input,.sf-filters select{padding:11px 13px;border-radius:6px;border:1px solid #cfd9e4;background:#fff;color:#263a51;max-width:280px}.sf-filter-empty{padding:22px;color:#778598}
+.sf-search-page{min-height:76vh;max-width:1060px;padding:90px 24px;margin:auto;font-family:system-ui,sans-serif;color:var(--sf-shell-fg)}.sf-search-page h1{font-size:clamp(35px,5vw,62px);margin:20px 0}.sf-search-page>label{display:block;margin:18px 0 8px}.sf-search-page input{display:block;width:100%;padding:17px;font-size:18px;border:1px solid #b7c6d7;border-radius:8px}.sf-search-page #sf-search-count{margin:22px 0;color:#607589}.sf-search-result{padding:22px 0;border-bottom:1px solid #dae1ea}.sf-search-result a{color:var(--sf-shell-fg);font-size:21px;font-weight:750;text-decoration:none}.sf-search-result small{display:block;color:#7992a7}.sf-search-result p{margin:7px 0;color:#657487}
+@media(max-width:1050px){.sf-head-inner{gap:13px}.sf-primary-links{gap:12px}.sf-primary-links a{font-size:12px}.sf-head-cta{padding:10px}}
+@media(max-width:850px){.sf-head-inner{min-height:68px}.sf-menu-toggle{display:block}.sf-head-cta{display:none}.sf-primary-links{display:none;position:absolute;top:68px;left:0;right:0;margin:0;background:var(--sf-shell);padding:20px 28px;flex-direction:column;align-items:stretch;box-shadow:0 20px 24px #0002}.sf-primary-links.is-open{display:flex}.sf-mega{display:none}.sf-foot-grid{grid-template-columns:1fr 1fr;gap:35px}}
+@media(max-width:560px){.sf-foot-grid{grid-template-columns:1fr}.sf-foot-lower{display:block}.sf-site-foot{padding-top:48px}.sf-head-inner{padding:0 16px}}
+
+.sf-page-intro{padding:80px 28px;background:var(--sf-shell-soft);color:var(--sf-shell-fg);font-family:system-ui,sans-serif}.sf-page-intro>div{max-width:1200px;margin:auto}.sf-page-intro p{font-size:11px;font-weight:850;letter-spacing:.19em;text-transform:uppercase;color:var(--sf-shell-fg);opacity:.58}.sf-page-intro h1{font-size:clamp(38px,5vw,67px);letter-spacing:-.04em;line-height:1.08;margin:13px 0}.sf-page-intro span{font-size:16px;opacity:.72}
+.sf-item[hidden]{display:none!important}
diff --git a/examples/mobile-showcase/automotive/assets/pro-base.css b/examples/mobile-showcase/automotive/assets/pro-base.css
new file mode 100644
index 0000000..c2c4f61
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/pro-base.css
@@ -0,0 +1,21 @@
+/* SiteForge V3 professional theme primitives. Each commercial theme adds its own art direction. */
+:root{--pro-text:#19283a;--pro-muted:#687587;--pro-border:#dfe5ea;--pro-background:#fff;--pro-surface:#f5f7fa;--pro-accent:#365cb7;--pro-hero:#15263d;--pro-hero-fg:#fff;--pro-radius:12px;--pro-headings:system-ui,-apple-system,sans-serif;--pro-font:system-ui,-apple-system,sans-serif;--pro-wide:1270px}
+*,*:before,*:after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--pro-background);color:var(--pro-text);font-family:var(--pro-font);-webkit-font-smoothing:antialiased;overflow-x:hidden}button,input,select{font:inherit}button,a{touch-action:manipulation}button{cursor:pointer}a{text-decoration:none;color:inherit}a:hover{color:var(--pro-accent)}img{max-width:100%;display:block}h1,h2,h3,h4,p{margin-top:0}h1,h2,h3{font-family:var(--pro-headings);line-height:1.09}h1{letter-spacing:-.052em}h2{letter-spacing:-.042em}h3{letter-spacing:-.028em}p{line-height:1.72}.pro-wrap{max-width:var(--pro-wide);margin:0 auto;padding-left:32px;padding-right:32px}.pro-skip{position:absolute;left:-9999px;z-index:90;background:#fff;color:#000;padding:12px}.pro-skip:focus{left:10px;top:10px}.pro-eyebrow{font-weight:750;font-size:11px;letter-spacing:.17em;text-transform:uppercase;display:block;line-height:1.6;color:var(--pro-accent)}.pro-muted{opacity:.7}.pro-utility{min-height:30px;background:var(--pro-hero);color:#c5d2db;font-size:10px;letter-spacing:.16em}.pro-utility .pro-wrap{display:flex;justify-content:space-between;padding-block:8px}.pro-utility i{font-style:normal;color:var(--pro-accent)}.pro-header{background:var(--pro-background);color:var(--pro-text);border-bottom:1px solid var(--pro-border);position:relative;z-index:35}.pro-head-inner{min-height:84px;display:flex;align-items:center;gap:28px}.pro-logo{display:flex;align-items:center;gap:12px;min-width:170px}.pro-logo-symbol{font-size:32px;line-height:1;font-weight:800;letter-spacing:-.07em;color:var(--pro-accent)}.pro-logo-name{max-width:220px;font-weight:820;font-size:17px;line-height:1.1;letter-spacing:-.035em}.pro-logo-name small{display:block;font-weight:650;font-size:9px;letter-spacing:.19em;margin-top:7px;color:var(--pro-muted)}.pro-navigation{display:flex;align-items:center;gap:24px;margin-left:auto}.pro-navigation a{font-size:13px;font-weight:650;white-space:nowrap;transition:opacity .2s}.pro-navigation a:hover{opacity:.6}.pro-header-cta,.pro-button{display:inline-flex;align-items:center;justify-content:center;gap:22px;background:var(--pro-accent);color:#fff!important;border-radius:var(--pro-radius);font-size:13px;letter-spacing:.01em;font-weight:750;padding:16px 24px;white-space:nowrap;transition:transform .2s,background .2s}.pro-header-cta:hover,.pro-button:hover{transform:translateY(-2px)}.pro-mobile-toggle{display:none}.pro-mega{display:flex;align-items:center;gap:22px;min-height:37px;border-top:1px solid var(--pro-border);padding-block:10px;font-size:11px}.pro-mega>span{font-weight:750;color:var(--pro-muted);white-space:nowrap}.pro-mega>div{display:flex;gap:16px;overflow:auto;scrollbar-width:none}.pro-mega a{white-space:nowrap;font-weight:650}.pro-search-link{font-size:19px!important}.pro-home-hero{position:relative;overflow:hidden}.pro-home-grid{display:grid;grid-template-columns:1.02fr .98fr;align-items:center;gap:5%;min-height:580px;padding-top:85px;padding-bottom:85px}.pro-hero-copy{position:relative;z-index:2}.pro-hero-copy h1{font-size:clamp(48px,6.2vw,91px);line-height:1.03;margin:20px 0 25px;max-width:730px}.pro-hero-copy h1 em{font-style:normal;display:block;color:var(--pro-accent)}.pro-hero-copy p{font-size:17px;max-width:495px;color:var(--pro-muted)}.pro-hero-actions{display:flex;align-items:center;flex-wrap:wrap;gap:25px;margin-top:34px}.pro-outline-button{font-weight:700;border-bottom:1px solid currentColor;padding:12px 0}.pro-hero-art{width:100%;height:435px;position:relative;overflow:hidden;border-radius:var(--pro-radius)}.pro-hero-art>img{width:100%;height:100%;object-fit:cover}.pro-hero-bottom{display:flex;justify-content:space-between;font-size:10px;letter-spacing:.18em;padding-block:22px;border-top:1px solid #ffffff32}.pro-section{padding:106px 0}.pro-section-head{display:flex;align-items:end;justify-content:space-between;gap:25px;margin-bottom:34px}.pro-section-head h2{font-size:clamp(36px,4vw,57px);margin:12px 0 0}.pro-text-link{font-size:13px;font-weight:750;display:inline-flex;gap:18px;align-items:center;border-bottom:1px solid;padding-bottom:7px;color:var(--pro-accent)}.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}.pro-card{display:block;min-width:0;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);overflow:hidden;border-radius:var(--pro-radius);transition:transform .25s,box-shadow .25s}.pro-card:hover{transform:translateY(-6px);box-shadow:0 24px 45px #0f25431b}.pro-card-cover{width:100%;height:244px;overflow:hidden;background:var(--pro-surface)}.pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .5s}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-art{width:100%;height:100%;overflow:hidden;display:grid;place-items:center;position:relative;background:linear-gradient(130deg,#d8e1ed,#f3f6f9)}.pro-art-ring{width:55%;aspect-ratio:1;border:28px solid #ffffff5f;border-radius:50%;box-shadow:0 0 0 42px #ffffff30;transform:rotate(-25deg)}.pro-art-center{position:absolute;font-family:var(--pro-headings);font-weight:800;font-size:76px;letter-spacing:-.08em;color:#45647d90}.pro-art-label{position:absolute;bottom:16px;left:18px;letter-spacing:.16em;font-size:9px;color:#435773}.pro-card-body{padding:25px 25px 28px}.pro-card-meta{display:flex;justify-content:space-between;gap:20px;font-size:10px;font-weight:750;letter-spacing:.1em;text-transform:uppercase;color:var(--pro-accent)}.pro-card h3{font-size:23px;margin:14px 0 9px}.pro-card p{font-size:13px;color:var(--pro-muted);margin-bottom:24px;min-height:44px}.pro-card-link{font-size:12px;font-weight:800;display:flex;justify-content:space-between;color:var(--pro-text);padding-top:14px;border-top:1px solid var(--pro-border)}.pro-empty{grid-column:1/-1;padding:45px;border:1px dashed var(--pro-border);background:var(--pro-surface);color:var(--pro-muted)}.pro-statement{background:var(--pro-surface)}.pro-statement-layout{display:grid;grid-template-columns:1fr 2fr;gap:60px}.pro-statement h2{font-size:clamp(36px,4.5vw,63px);max-width:760px}.pro-statement p{max-width:520px;color:var(--pro-muted)}.pro-brand-pills{display:flex;flex-wrap:wrap;gap:12px}.pro-brand-pills a{padding:18px 28px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);font-weight:750}.pro-brand-pills a:hover{border-color:var(--pro-accent)}.pro-inner-hero{background:var(--pro-surface);padding:92px 0 65px;position:relative;overflow:hidden}.pro-inner-hero h1{max-width:870px;font-size:clamp(43px,6vw,85px);margin:18px 0}.pro-inner-hero p{max-width:650px;color:var(--pro-muted);font-size:17px}.pro-breadcrumb{font-size:12px;color:var(--pro-muted);margin-top:25px;display:flex;gap:12px;align-items:center;flex-wrap:wrap}.pro-breadcrumb strong{color:var(--pro-text)}.pro-list-section{padding:65px 0 110px}.pro-filter-panel{display:grid;grid-template-columns:minmax(220px,2fr) 1fr 1fr auto;align-items:end;gap:14px;margin-bottom:35px}.pro-filter-panel label>span{display:block;font-size:11px;margin-bottom:8px;color:var(--pro-muted);font-weight:750}.pro-filter-panel input,.pro-filter-panel select{display:block;width:100%;padding:14px 15px;border-radius:var(--pro-radius);border:1px solid var(--pro-border);background:var(--pro-background);color:var(--pro-text);outline-offset:2px}.pro-filter-count{font-size:12px;padding-bottom:17px;color:var(--pro-muted)}.pro-filter-scope .pro-card[hidden]{display:none}.pro-product-layout{padding-top:38px}.pro-product-layout .pro-breadcrumb{margin-bottom:38px}.pro-product-hero{display:grid;grid-template-columns:1.18fr .82fr;gap:6%;align-items:center}.pro-product-media{height:460px;overflow:hidden;background:var(--pro-surface);border-radius:var(--pro-radius)}.pro-product-media .pro-photo{height:100%}.pro-product-summary h1{font-size:clamp(45px,5vw,72px);margin:16px 0}.pro-product-summary p{font-size:16px;color:var(--pro-muted)}.pro-product-brand{font-weight:700;color:var(--pro-text)!important}.pro-detail-layout{border-top:1px solid var(--pro-border);display:grid;grid-template-columns:1fr 1fr;gap:8%;margin-top:80px;padding-top:55px}.pro-detail-layout h2{font-size:32px;margin-top:13px}.pro-specs{margin-top:25px}.pro-specs>div{display:grid;grid-template-columns:1fr 1fr;gap:22px;padding:16px 0;border-bottom:1px solid var(--pro-border)}.pro-specs span{color:var(--pro-muted)}.pro-rich{line-height:1.85;font-size:16px;overflow-wrap:anywhere}.pro-rich img{border-radius:var(--pro-radius);margin:25px 0}.pro-rich a{text-decoration:underline}.pro-rich h2{margin-top:30px}.pro-lead{font-size:20px;color:var(--pro-muted)}.pro-brand-hero-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:7%;align-items:center}.pro-brand-media{height:300px;overflow:hidden;border-radius:var(--pro-radius)}.pro-article-hero{text-align:center;padding:110px 0 75px;background:var(--pro-surface)}.pro-article-width{max-width:900px}.pro-article-hero h1{font-size:clamp(40px,5vw,75px);margin:15px 0 25px}.pro-article-hero p{color:var(--pro-muted);font-size:18px}.pro-article-hero time{font-size:12px;color:var(--pro-muted)}.pro-article-content{max-width:930px;padding-top:70px;padding-bottom:90px}.pro-article-cover{width:100%;max-height:530px;object-fit:cover;margin-bottom:40px;border-radius:var(--pro-radius)}.pro-article-content>.pro-text-link{margin-top:35px}.pro-case-detail{max-width:1020px}.pro-case-detail>img{width:100%;max-height:620px;object-fit:cover}.pro-case-meta{display:flex;gap:35px;margin:35px 0;color:var(--pro-muted);font-size:13px}.pro-case-meta strong{display:block;color:var(--pro-text);font-size:18px;margin-top:7px}.pro-info-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:12%}.pro-info-grid h2{font-size:clamp(31px,4.5vw,54px);margin-top:15px}.pro-contact-email{font-size:20px;color:var(--pro-accent);font-weight:750;word-break:break-word}.pro-info-photo{max-height:450px;object-fit:cover;margin-top:35px;border-radius:var(--pro-radius)}.pro-custom-content .sf-section{background:transparent}.pro-custom-content .sf-section:nth-of-type(even){background:var(--pro-surface)}.pro-end-cta{padding:76px 0;background:var(--pro-hero);color:#fff}.pro-end-cta .pro-wrap{display:flex;align-items:center;justify-content:space-between;gap:40px}.pro-end-cta h2{font-size:clamp(32px,4vw,53px);margin:13px 0}.pro-end-cta p{color:#c5c9d1}.pro-end-cta a{background:var(--pro-accent);color:#fff;font-weight:800;padding:19px 30px;border-radius:var(--pro-radius);white-space:nowrap}.pro-404{text-align:center;padding-block:125px}.pro-404>span{font-size:12px;letter-spacing:.3em}.pro-404 h1{font-size:clamp(150px,20vw,320px);margin:10px 0;letter-spacing:-.08em}.pro-404 p{font-size:18px;color:var(--pro-muted)}.pro-404 a{margin-top:22px}.pro-footer{background:#15212d;color:#fff;padding:75px 0 22px}.pro-footer-top{display:grid;grid-template-columns:1.45fr 1fr 1fr;gap:55px}.pro-footer h2{font-size:32px;margin:12px 0}.pro-footer p{max-width:350px;font-size:13px;color:#afbbc8}.pro-footer .pro-eyebrow{color:#b6c3d2}.pro-footer-links ul{margin:0;padding:0;list-style:none;display:grid;grid-template-columns:1fr 1fr;gap:15px}.pro-footer-links a{font-size:13px;color:#d6e0e8}.pro-footer-bottom{border-top:1px solid #ffffff29;margin-top:60px;padding-top:20px;font-size:10px;letter-spacing:.12em;color:#adb9c3;display:flex;justify-content:space-between}.pro-anchor{display:block;height:0}.pro-category-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.pro-category-tiles a{padding:35px 28px;min-height:155px;background:var(--pro-surface);display:flex;flex-direction:column;justify-content:space-between}.pro-category-tiles span{font-size:11px;color:var(--pro-muted)}.pro-category-tiles strong{font-size:24px}.pro-category-tiles b{align-self:end}.sf-site-head .sf-head-inner,.sf-site-head .sf-primary-links{display:none}
+@media(max-width:1100px){.pro-head-inner{gap:17px}.pro-navigation{gap:14px}.pro-navigation a{font-size:12px}.pro-home-grid{min-height:500px}.pro-hero-copy h1{font-size:60px}.pro-hero-art{height:350px}}
+@media(max-width:860px){.pro-wrap{padding-left:22px;padding-right:22px}.pro-utility{display:none}.pro-head-inner{min-height:70px;flex-wrap:wrap;gap:12px}.pro-mobile-toggle{display:block;margin-left:auto;border:1px solid var(--pro-border);padding:8px 12px;background:transparent;color:var(--pro-text);border-radius:5px}.pro-navigation{display:none;order:10;width:100%;flex-direction:column;align-items:stretch;gap:0;padding-bottom:12px}.pro-navigation.is-open{display:flex}.pro-navigation a{padding:11px 2px;border-bottom:1px solid var(--pro-border)}.pro-header-cta{display:none}.pro-mega{display:none}.pro-home-grid{grid-template-columns:1fr;gap:35px;padding-top:60px;padding-bottom:60px}.pro-hero-art{height:350px}.pro-hero-copy h1{font-size:clamp(45px,9vw,73px)}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-brand-hero-grid{grid-template-columns:1fr;gap:35px}.pro-product-media{height:380px}.pro-filter-panel{grid-template-columns:1fr 1fr}.pro-filter-panel label:first-child{grid-column:1/-1}.pro-end-cta .pro-wrap{align-items:start;flex-direction:column}.pro-section{padding:72px 0}.pro-footer-top{grid-template-columns:1fr 1fr}.pro-footer-top>div:first-child{grid-column:1/-1}.pro-category-tiles{grid-template-columns:repeat(2,1fr)}}
+@media(max-width:560px){.pro-wrap{padding-left:18px;padding-right:18px}.pro-logo-name{font-size:15px}.pro-hero-copy h1{font-size:43px}.pro-hero-art{height:255px}.pro-hero-bottom{display:none}.pro-home-grid{min-height:0}.pro-grid{grid-template-columns:1fr}.pro-section-head{align-items:start;flex-direction:column}.pro-section-head h2{font-size:37px}.pro-card-cover{height:230px}.pro-filter-panel{grid-template-columns:1fr}.pro-filter-panel label:first-child{grid-column:auto}.pro-filter-count{padding-bottom:4px}.pro-statement-layout,.pro-footer-top{grid-template-columns:1fr;gap:25px}.pro-footer-top>div:first-child{grid-column:auto}.pro-footer-bottom{flex-direction:column;gap:8px}.pro-inner-hero{padding:60px 0}.pro-product-media{height:270px}.pro-product-summary h1{font-size:43px}.pro-category-tiles{grid-template-columns:1fr}.pro-end-cta h2{font-size:38px}.pro-minimal-meta{display:block!important}}
+
+/* V2 compatibility stylesheet hides direct headers/footers; full themes restore their own. */
+body>header.sf-site-head.pro-header,body>footer.sf-site-foot.pro-footer{display:block!important}
+
+/* Accessible desktop mega-navigation and mobile disclosure */
+.pro-catalog-toggle{font-size:12px;font-weight:750;border:1px solid var(--pro-border);background:transparent;color:inherit;border-radius:var(--pro-radius);padding:10px 14px;white-space:nowrap}
+.pro-mega[hidden]{display:none!important}.pro-mega.is-open{display:flex}.pro-mega>div{padding-block:3px}
+@media(max-width:1060px){.pro-catalog-toggle{display:none}}
+@media(max-width:860px){.pro-mega.is-open{display:block!important}.pro-mega.is-open>div{display:flex;flex-wrap:wrap;gap:14px;padding:12px 0}.pro-mega>span{white-space:normal}}
+
+/* V4: responsive nested navigation, gallery and opt-in inquiry integration */
+.pro-nav-group{position:relative;display:flex;align-items:center;gap:3px}.pro-nav-disclose{border:0;background:none;color:inherit;font-size:12px;padding:8px}.pro-nav-children[hidden]{display:none!important}.pro-nav-children{position:absolute;left:0;top:100%;z-index:45;min-width:230px;max-width:360px;padding:10px;background:var(--pro-background);border:1px solid var(--pro-border);box-shadow:0 18px 35px #0002;border-radius:var(--pro-radius);display:flex;flex-direction:column}.pro-nav-children a{padding:10px 12px;white-space:nowrap}.pro-nav-children .pro-nav-leaf{padding-left:28px;color:var(--pro-muted)}
+.pro-product-gallery{min-width:0}.pro-gallery-thumbs{display:flex;gap:8px;margin-top:12px;overflow:auto;scrollbar-width:thin;padding-bottom:5px}.pro-gallery-thumbs button{flex:0 0 86px;height:67px;border:2px solid transparent;padding:3px;border-radius:8px;background:var(--pro-surface)}.pro-gallery-thumbs button:focus-visible,.pro-gallery-thumbs button[aria-pressed=true]{border-color:var(--pro-accent)}.pro-gallery-thumbs img{width:100%;height:100%;object-fit:cover;border-radius:4px}.pro-gallery-lightbox{position:fixed;inset:0;background:rgba(10,16,30,.92);z-index:1000;display:grid;place-items:center;padding:6vw}.pro-gallery-lightbox[hidden]{display:none}.pro-gallery-lightbox img{max-width:92vw;max-height:80vh;object-fit:contain}.pro-gallery-lightbox button{position:absolute;right:25px;top:25px;border:1px solid #fff8;background:#222;color:#fff;padding:10px 15px;border-radius:5px}
+.pro-inquiry-wrap{display:grid;grid-template-columns:1fr 1fr;gap:70px}.pro-inquiry{display:grid;gap:17px}.pro-inquiry label{display:grid;gap:7px;font-weight:650}.pro-inquiry input,.pro-inquiry textarea{padding:13px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);background:var(--pro-background);color:var(--pro-text);width:100%}.pro-inquiry textarea{min-height:140px}.pro-inquiry .pro-honeypot{position:absolute;left:-10000px;width:1px;height:1px}.pro-inquiry p{font-size:12px;color:var(--pro-muted)}
+@media(max-width:860px){.pro-nav-group{display:block}.pro-nav-children{position:static;max-width:none;width:100%;box-shadow:none;border:0;border-left:2px solid var(--pro-accent);padding-left:8px}.pro-nav-group>a{display:inline-block}.pro-inquiry-wrap{grid-template-columns:1fr;gap:20px}}
diff --git a/examples/mobile-showcase/automotive/assets/pro.css b/examples/mobile-showcase/automotive/assets/pro.css
new file mode 100644
index 0000000..9d84384
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/pro.css
@@ -0,0 +1,8 @@
+:root{--pro-text:#1c2a3b;--pro-muted:#718094;--pro-border:#e2e8ef;--pro-background:#fff;--pro-surface:#f2f5f9;--pro-accent:#bb8d50;--pro-hero:#0c1b2e;--pro-radius:5px;--pro-headings:'Arial Narrow',Arial,sans-serif;--pro-wide:1320px}
+.pro-header{background:#0e1e30;color:#fff;border-color:#26374b}.pro-logo-symbol{color:#e0ae6c}.pro-logo-symbol span{color:#fff}.pro-logo-name small{color:#b3bfcf}.pro-header-cta{background:#c59b63;color:#0f1a26!important}.pro-navigation a:hover{color:#dfb97d}.pro-mega{background:#13263b;border-color:#2a4058;color:#c2d0de}.pro-mega>span{color:#d5aa72}.pro-mobile-toggle{color:#fff}
+.pro-auto-hero{background:radial-gradient(circle at 68% 50%,#243b54 0%,#112539 34%,#081728 100%);color:#fff}.pro-auto-hero .pro-eyebrow{color:#d5a366}.pro-auto-hero .pro-hero-copy h1 em{color:#d2a16b}.pro-auto-hero .pro-hero-copy p{color:#b9c7d6}.pro-auto-hero .pro-button{color:#0d1b2d!important;background:#d8ad72}.pro-link-light{color:#d8e4ec;font-size:13px;font-weight:750}.pro-auto-hero .pro-hero-art{background:linear-gradient(160deg,#243e59,#0f2033);border:1px solid #ffffff25;box-shadow:0 50px 80px #0005;border-radius:3px;overflow:visible}.pro-abstract-car{position:absolute;inset:0;overflow:hidden;background:radial-gradient(ellipse at 50% 80%,#45648599,transparent 63%)}.pro-abstract-car:before{content:'';position:absolute;left:10%;right:9%;bottom:29%;height:29%;border:3px solid #d3dfefad;border-radius:53% 49% 10% 12%;transform:skewX(-9deg);box-shadow:0 23px 15px #05121f88,0 0 0 3px #182c41 inset}.pro-abstract-car:after{content:'';position:absolute;left:23%;right:23%;bottom:56%;height:17%;border:3px solid #d3dfef72;border-radius:85% 85% 0 0;transform:skewX(-20deg)}.pro-abstract-car>span{position:absolute;top:26px;right:22px;font-size:10px;letter-spacing:.2em;color:#ccd9e5}.auto-wheel{position:absolute;bottom:18%;width:16%;aspect-ratio:1;border-radius:50%;border:11px solid #6f8499;background:#111e2c;box-shadow:0 0 0 4px #111a25}.auto-wheel.a{left:19%}.auto-wheel.b{right:17%}.pro-hero-art>b{position:absolute;bottom:-31px;right:0;font-size:11px;letter-spacing:.2em;color:#bdcde0}.pro-featured{background:#f4f6f8}.pro-featured .pro-card{border:0;box-shadow:0 7px 30px #0f2e4710}.pro-featured .pro-card-cover{background:#d9e2eb}.pro-featured .pro-art{background:linear-gradient(140deg,#dce5ed,#a9baca)}.pro-featured .pro-art-ring{border-color:#eef3f8bb}.pro-statement{background:#12243b;color:#fff}.pro-statement .pro-eyebrow{color:#d3a975}.pro-statement p{color:#aebfd3}.pro-statement h2{font-size:clamp(42px,5vw,70px);max-width:700px}.pro-statement .pro-text-link{color:#dfba83}.pro-brand-section{background:white}.pro-brand-pills a{min-width:150px;text-align:center;background:#f5f7fa}.pro-inner-hero{background:linear-gradient(120deg,#101e31,#273b51);color:#fff}.pro-inner-hero .pro-eyebrow{color:#d0a46b}.pro-inner-hero p,.pro-inner-hero .pro-breadcrumb{color:#c9d1df}.pro-inner-hero .pro-breadcrumb strong{color:#fff}.pro-article-hero{background:#14243a;color:#fff}.pro-article-hero p{color:#bed0df}.pro-footer{background:#071421}.pro-footer-bottom{color:#98aebf}.pro-end-cta{background:#1a324b}.pro-end-cta a{background:#d4a56d;color:#132339!important}.pro-404 h1{color:#b8905c}
+
+/* SiteForge V5 commercial signature */
+.pro-auto-hero .pro-hero-art{background-image:url("./visual/hero.svg")!important;background-position:center;background-size:cover;}
+
+.pro-auto-hero{background:linear-gradient(117deg,#081729 0%,#102a43 100%)}.pro-auto-hero .pro-hero-art{border:1px solid #52667a55;border-radius:3px;box-shadow:0 30px 90px #0004;transform:translateX(0)}.pro-auto-hero .pro-hero-art .pro-abstract-car{opacity:0}.pro-hero-bottom{border-top:1px solid #ffffff1c}.pro-brand-pills a{transition:background .2s}.pro-brand-pills a:hover{background:#d6a974;color:#10233a}.pro-card{border-color:#e0e6ee;border-radius:5px}.pro-card-cover{background:linear-gradient(135deg,#edf1f6,#dbe4eb)}.pro-art{background:linear-gradient(130deg,#e0e8ee,#c4d0dc);color:#37536b}.pro-featured{background:#f8fafc}.pro-statement{background:#0d2134;color:white}.pro-statement p{color:#c6d0dd}.pro-statement .pro-text-link{color:#f0b870}.pro-section-head h2{font-weight:800}.pro-footer{background:#08182a;color:#e5e9ed}
diff --git a/examples/mobile-showcase/automotive/assets/pro.js b/examples/mobile-showcase/automotive/assets/pro.js
new file mode 100644
index 0000000..9cf100d
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/pro.js
@@ -0,0 +1,72 @@
+'use strict';(()=>{
+ const menu=document.querySelector('[data-pro-menu]'); const nav=document.querySelector('#pro-navigation');
+ if(menu&&nav){menu.addEventListener('click',()=>{const open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));nav.classList.toggle('is-open',open);menu.setAttribute('aria-label',open?'关闭导航':'打开导航');});}
+ // Drawer navigation. The same markup is used across the four professional themes.
+ const mobileNav=document.querySelector('#pro-navigation');
+ let drawerBackdrop=null,drawerClose=null,returnFocus=null;
+ if(menu&&mobileNav){
+ const originalParent=mobileNav.parentNode,originalNext=mobileNav.nextSibling;
+ const heading=document.createElement('div');heading.className='sf-drawer-header';
+ const title=document.createElement('strong');title.textContent='网站导航';
+ drawerClose=document.createElement('button');drawerClose.type='button';drawerClose.className='sf-drawer-close';drawerClose.textContent='×';drawerClose.setAttribute('aria-label','关闭网站导航');
+ heading.append(title,drawerClose);mobileNav.prepend(heading);
+ drawerBackdrop=document.createElement('div');drawerBackdrop.className='sf-mobile-drawer-backdrop';drawerBackdrop.hidden=false;document.body.append(drawerBackdrop);
+ function setDrawer(open,focusBack=true){
+ const isMobile=window.matchMedia('(max-width:1024px)').matches;
+ open=Boolean(open&&isMobile);
+ if(open){returnFocus=document.activeElement;document.body.append(mobileNav);}
+ else if(mobileNav.parentNode!==originalParent){if(originalNext&&originalNext.parentNode===originalParent)originalParent.insertBefore(mobileNav,originalNext);else originalParent.append(mobileNav);}
+ mobileNav.classList.toggle('is-open',open);drawerBackdrop.classList.toggle('is-open',open);
+ document.body.classList.toggle('sf-drawer-open',open);
+ menu.setAttribute('aria-expanded',String(open));menu.setAttribute('aria-label',open?'关闭导航':'打开导航');
+ mobileNav.setAttribute('aria-hidden',String(!open&&isMobile));
+ if('inert' in mobileNav)mobileNav.inert=!open&&isMobile;
+ if(open)drawerClose.focus();
+ else if(focusBack&&returnFocus instanceof HTMLElement){returnFocus.focus();returnFocus=null;}
+ }
+ // Replace the old inline-expansion listener: use capture to stop the old listener.
+ menu.addEventListener('click',e=>{if(!window.matchMedia('(max-width:1024px)').matches)return;e.stopImmediatePropagation();setDrawer(!mobileNav.classList.contains('is-open'));},true);
+ drawerClose.addEventListener('click',()=>setDrawer(false));
+ drawerBackdrop.addEventListener('click',()=>setDrawer(false));
+ document.querySelectorAll('[data-mobile-open-menu]').forEach(b=>b.addEventListener('click',()=>setDrawer(true)));
+ mobileNav.addEventListener('click',e=>{if(e.target.closest('a'))setDrawer(false,false)});
+ document.addEventListener('keydown',e=>{
+ if(e.key==='Escape'&&mobileNav.classList.contains('is-open')){e.preventDefault();setDrawer(false);}
+ if(e.key!=='Tab'||!mobileNav.classList.contains('is-open'))return;
+ const els=[...mobileNav.querySelectorAll('button:not([disabled]),a[href]')].filter(el=>el.getClientRects().length&&!el.closest('[hidden]'));
+ if(!els.length)return;const first=els[0],last=els[els.length-1];
+ if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
+ else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
+ });
+ window.matchMedia('(max-width:1024px)').addEventListener('change',()=>setDrawer(false,false));
+ if('inert' in mobileNav&&window.matchMedia('(max-width:1024px)').matches)mobileNav.inert=true;
+ // Do not strand catalogue links when the old mobile mega-button is hidden.
+ const megaLinks=document.querySelectorAll('[data-pro-mega] a');
+ if(megaLinks.length){const wrap=document.createElement('div');wrap.className='sf-drawer-extra';const h=document.createElement('strong');h.textContent='更多分类与系列';wrap.append(h);
+ [...megaLinks].slice(0,20).forEach(a=>{const clone=a.cloneNode(true);wrap.append(clone);});mobileNav.append(wrap);}
+ }
+ const tabbar=document.querySelector('[data-mobile-tabs]');
+ if(tabbar){tabbar.style.setProperty('--sf-mobile-tab-count',String(tabbar.children.length));}
+ const megaButton=document.querySelector('[data-pro-catalog]'), mega=document.querySelector('[data-pro-mega]');
+ if(megaButton&&mega){function setMega(open){mega.hidden=!open;mega.classList.toggle('is-open',open);megaButton.setAttribute('aria-expanded',String(open));}
+ megaButton.addEventListener('click',()=>setMega(mega.hidden));
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')setMega(false);});
+ document.addEventListener('click',e=>{if(!mega.hidden&&!mega.contains(e.target)&&!megaButton.contains(e.target))setMega(false);});
+ }
+ document.querySelectorAll('.pro-nav-disclose').forEach(button=>{button.addEventListener('click',()=>{let panel=button.parentElement.querySelector('.pro-nav-children');let visible=button.getAttribute('aria-expanded')==='true';button.setAttribute('aria-expanded',String(!visible));panel.hidden=visible;});});
+ document.querySelectorAll('.pro-gallery-thumbs').forEach(container=>{const main=container.parentElement.querySelector('.pro-product-media img');const buttons=[...container.querySelectorAll('[data-gallery-image]')];
+ buttons.forEach(b=>{b.addEventListener('click',()=>{buttons.forEach(x=>x.setAttribute('aria-pressed',String(x===b)));if(main){main.src=b.dataset.galleryImage;main.alt=b.querySelector('img')?.alt||'';}});});
+ if(!main)return;main.tabIndex=0;main.style.cursor='zoom-in';const open=()=>{const pane=document.createElement('div');pane.className='pro-gallery-lightbox';pane.setAttribute('role','dialog');pane.setAttribute('aria-label','产品大图预览');let img=document.createElement('img');img.src=main.src;img.alt=main.alt;const close=document.createElement('button');close.type='button';close.textContent='关闭 ×';pane.append(img,close);document.body.append(pane);const done=()=>{pane.remove();document.removeEventListener('keydown',esc);main.focus();};const esc=e=>{if(e.key==='Escape')done();};document.addEventListener('keydown',esc);close.addEventListener('click',done);pane.addEventListener('click',e=>{if(e.target===pane)done();});close.focus();};main.addEventListener('click',open);main.addEventListener('keydown',e=>{if(e.key==='Enter')open();});
+ });
+ document.querySelectorAll('.pro-filter-scope').forEach(scope=>{
+ const panel=scope.previousElementSibling;if(!panel||!panel.classList.contains('pro-filter-panel'))return;
+ const cards=Array.from(scope.querySelectorAll('.pro-card'));
+ const blank=document.createElement('div');blank.className='pro-filter-empty';blank.hidden=true;blank.textContent='没有找到符合条件的内容,请调整关键词或筛选条件。';scope.append(blank);
+ const q=panel.querySelector('[data-pro-filter-search]'),b=panel.querySelector('[data-pro-filter-brand]'),c=panel.querySelector('[data-pro-filter-category]'),count=panel.querySelector('[data-pro-filter-count]');
+ function options(sel,key){const vals=[...new Set(cards.map(x=>x.dataset[key]||'').filter(Boolean))].sort();vals.forEach(x=>sel.add(new Option(x,x)));if(!vals.length)sel.closest('label').hidden=true;}
+ options(b,'brand');options(c,'category');
+ function run(){const str=q.value.trim().toLocaleLowerCase();let visible=0;
+ cards.forEach(el=>{const hit=el.textContent.toLocaleLowerCase().includes(str)&&(!b.value||el.dataset.brand===b.value)&&(!c.value||el.dataset.category===c.value);el.hidden=!hit;visible+=Number(hit);});count.textContent=`共 ${visible} 条`;blank.hidden=visible!==0;}
+ [q,b,c].forEach(x=>x.addEventListener(x===q?'input':'change',run));run();
+ });
+})();
diff --git a/examples/mobile-showcase/automotive/assets/search-index.json b/examples/mobile-showcase/automotive/assets/search-index.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/search-index.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/examples/mobile-showcase/automotive/assets/site-runtime.js b/examples/mobile-showcase/automotive/assets/site-runtime.js
new file mode 100644
index 0000000..e4715e8
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/site-runtime.js
@@ -0,0 +1,31 @@
+'use strict';
+(()=>{
+ const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
+ const menu=$('.sf-menu-toggle');if(menu){menu.addEventListener('click',()=>{let open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));$('#sf-primary-links')?.classList.toggle('is-open',open);});}
+ // Filter actual static content cards, no API is needed at runtime.
+ $$('.sf-collection').forEach(grid=>{
+ const cards=$$('.sf-item',grid);
+ if(cards.length<2)return;
+ const toolbar=document.createElement('div');toolbar.className='sf-filters';
+ const search=document.createElement('input');search.type='search';search.placeholder='筛选当前列表';search.setAttribute('aria-label','筛选当前列表');
+ const status=document.createElement('span');status.setAttribute('aria-live','polite');status.style.fontSize='12px';
+ const brand=document.createElement('select');brand.setAttribute('aria-label','按品牌筛选');
+ const category=document.createElement('select');category.setAttribute('aria-label','按分类筛选');
+ function fill(el,field,all){el.append(new Option(all,''));const vals=[...new Set(cards.map(x=>x.dataset[field]||'').filter(Boolean))].sort();vals.forEach(v=>el.append(new Option(v,v)));if(!vals.length)el.hidden=true;}
+ fill(brand,'brand','全部品牌');fill(category,'category','全部分类');
+ toolbar.append(search,brand,category,status);grid.parentNode.insertBefore(toolbar,grid);
+ function apply(){let val=search.value.trim().toLocaleLowerCase();let visible=0;cards.forEach(card=>{let hit=card.textContent.toLocaleLowerCase().includes(val)&&(!brand.value||card.dataset.brand===brand.value)&&(!category.value||card.dataset.category===category.value);card.hidden=!hit;if(hit)visible++;});status.textContent=`共 ${visible} 项`;}
+ search.addEventListener('input',apply);brand.addEventListener('change',apply);category.addEventListener('change',apply);apply();
+ });
+ const input=$('#sf-search'),root=$('#sf-search-results'),count=$('#sf-search-count');
+ if(input&&root){
+ let data=[];const params=new URLSearchParams(location.search);input.value=params.get('q')||'';
+ let debounce=null;
+ const render=()=>{let q=input.value.trim().toLocaleLowerCase();let found=q?data.filter(d=>`${d.title} ${d.summary} ${d.brand} ${d.type}`.toLocaleLowerCase().includes(q)).slice(0,100):data.slice(0,30);
+ root.replaceChildren();count.textContent=`${found.length} 条结果${!q?'(显示前30条)':''}`;
+ found.forEach(d=>{let article=document.createElement('article'),a=document.createElement('a'),small=document.createElement('small'),p=document.createElement('p');article.className='sf-search-result';a.textContent=d.title;a.href=d.url.replace(/^\//,'');small.textContent=d.type;p.textContent=d.summary;article.append(a,small,p);root.append(article);});
+ };const base=document.querySelector('base')?.href||location.href;
+ fetch(new URL('search-index.json',base)).then(r=>{if(!r.ok)throw Error('index unavailable');return r.json();}).then(v=>{data=v;render()}).catch(()=>{count.textContent='搜索索引加载失败,请检查站点文件部署路径';});
+ input.addEventListener('input',()=>{clearTimeout(debounce);debounce=setTimeout(render,100)});
+ }
+})();
diff --git a/examples/mobile-showcase/automotive/assets/theme.css b/examples/mobile-showcase/automotive/assets/theme.css
new file mode 100644
index 0000000..2786b24
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/theme.css
@@ -0,0 +1 @@
+:root{--accent:#ecb75c;--ink:#f6f7fa;--body:#101318}*{box-sizing:border-box}body{margin:0;background:#f7f7f7;color:#1b232c;font:16px/1.65 Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}a{text-decoration:none;color:inherit}img{max-width:100%;object-fit:cover}.utility{height:32px;display:flex;justify-content:space-between;align-items:center;background:#090d13;color:#919da8;padding:0 5%;font-size:10px;letter-spacing:2px}.header{height:90px;background:#121922;color:#fff;display:flex;align-items:center;justify-content:space-between;padding:0 5%;gap:24px}.brand{font-size:22px;font-weight:780;display:flex;align-items:center;gap:14px;line-height:1.15}.brand small{display:block;font-size:9px;color:#9ba4b4;letter-spacing:2.5px;margin-top:5px}.mark{background:var(--accent);border-radius:3px;color:#111;padding:10px;font-size:19px}.header nav{display:flex;flex-wrap:wrap;gap:26px;font-size:12px;letter-spacing:1px}.header nav a:hover{color:var(--accent)}.nav-cta{border:1px solid #505a62;padding:12px 17px;color:#e6c893;font-size:11px;letter-spacing:1px}.hero{background:radial-gradient(circle at 80% 55%,#2d4359 0%,#141e2c 34%,#0b121b 75%);color:white;min-height:580px}.hero-inner{max-width:1450px;margin:auto;padding:70px 5% 50px;display:grid;grid-template-columns:51% 49%;align-items:center;min-height:520px}.eyebrow{font-size:11px;letter-spacing:3px;font-weight:700;color:#e9b962}.glow{display:inline-block;width:8px;height:8px;background:#efbf6e;border-radius:100%;margin-right:12px;box-shadow:0 0 16px #edc36d}.hero h1{font-size:clamp(54px,7.2vw,116px);font-weight:850;letter-spacing:-6px;line-height:.95;margin:29px 0}.hero p{font-size:17px;color:#b6c4d1;max-width:470px}.hero-actions{display:flex;align-items:center;gap:30px;margin-top:34px}.btn{display:inline-flex;align-items:center;justify-content:space-between;gap:32px;padding:17px 23px;background:#d5a962;color:#12181d;font-size:12px;font-weight:850;letter-spacing:.8px}.hero-hint{color:#89929b;font-size:12px}.hero-visual{height:330px;position:relative;display:flex;align-items:center;justify-content:center}.hero-visual img{max-height:360px;width:100%;object-fit:contain;filter:drop-shadow(0 16px 23px #0008)}.hero-no{position:absolute;right:0;bottom:-70px;color:#fff6;font-size:10px;letter-spacing:3px}.car-shape{height:220px;width:100%;position:relative;filter:drop-shadow(0 28px 16px #0007)}.car-body{position:absolute;inset:45px 0 24px;border-radius:90px 130px 36px 35px;background:linear-gradient(170deg,#c7d0d9,#60788a 37%,#142433 48%,#202d3d 70%);clip-path:polygon(0 70%,13% 47%,31% 38%,47% 6%,75% 6%,88% 39%,100% 56%,98% 95%,3% 95%)}.wheel{position:absolute;width:80px;height:80px;bottom:0;border-radius:100%;background:radial-gradient(circle,#71808b 0 25%,#101722 28% 50%,#354355 54% 61%,#0a0d13 64%)}.wheel-one{left:19%}.wheel-two{right:13%}.hero-bottom{border-top:1px solid #344052;padding:18px 5%;display:flex;justify-content:space-between;color:#9fa9b5;font-size:10px;letter-spacing:2px}.inner-hero{background:linear-gradient(110deg,#0d1925,#2a3f50);color:white;padding:100px 6% 90px}.inner-hero h1{font-size:clamp(40px,6vw,84px);line-height:1.1;letter-spacing:-2px;margin:16px 0}.inner-hero p{color:#c0ccd7}.container{max-width:1220px;margin:auto;padding:0 24px}.main{padding:75px 0 110px}.section-title{display:flex;align-items:end;gap:20px;justify-content:space-between;margin:30px 0 28px}.section-title span{font-size:11px;letter-spacing:2px;color:#9f7844}.section-title h2{font-size:37px;letter-spacing:-1.5px;flex:1;margin:0}.section-title a{font-size:12px;font-weight:700}.card-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}.model-card{background:#fff;transition:transform .2s,box-shadow .2s;min-width:0}.model-card:hover{transform:translateY(-4px);box-shadow:0 14px 35px #15273822}.card-visual{height:210px;background:linear-gradient(120deg,#dae2e8,#abbcc7);display:flex;align-items:center;justify-content:center;overflow:hidden}.card-visual img{height:100%;width:100%;object-fit:contain}.card-monogram{font-size:75px;font-weight:900;letter-spacing:-8px;color:#ffffff77}.card-copy{padding:24px}.card-copy span{color:#ae854c;font-size:10px;letter-spacing:2px}.card-copy h3{font-size:24px;margin:8px 0}.card-copy p{font-size:13px;color:#73808b}.card-copy b{font-size:11px;letter-spacing:1px;color:#a6793f}.detail-media{margin:40px auto;text-align:center;max-height:600px}.detail-media img{max-height:600px}.rich-content{margin-bottom:55px}.prose{max-width:860px}.rich-content p{color:#596574}.section{padding:45px 0}.section-heading h2{font-size:38px}.block-hero{padding:60px 0}.block-hero h1{font-size:52px}.feature-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.feature{padding:26px;background:white}.feature-mark{color:#b08756;font-size:12px}.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}.stat strong{display:block;font-size:35px}.stat span{color:#71808c}.cta{background:#19283a;color:white;padding:55px}.cta p{color:#b8c8d6}footer{background:#0d151f;color:white;padding:70px 5% 25px}.footer-brand{font-size:25px;font-weight:750}.footer-brand span{font-size:11px;font-weight:400;color:#aeb8c2}footer p{color:#8593a2}.footer-bottom{border-top:1px solid #30404c;padding-top:25px;margin-top:65px;display:flex;justify-content:space-between;font-size:10px;letter-spacing:1px;color:#8995a3}@media(max-width:860px){.utility{font-size:8px}.header{height:auto;padding:18px 5%;align-items:flex-start;flex-wrap:wrap}.header nav{order:3;width:100%;gap:18px}.nav-cta{display:none}.hero-inner{grid-template-columns:1fr;padding-top:55px}.hero h1{letter-spacing:-3px}.hero-visual{height:210px}.hero-no{bottom:0}.card-grid,.feature-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:560px){.card-grid,.feature-grid,.stats{grid-template-columns:1fr}.hero h1{font-size:56px}.hero-actions{flex-wrap:wrap}.section-title{align-items:start;flex-direction:column}.inner-hero{padding:75px 6%}.hero-bottom{font-size:8px}}
diff --git a/examples/mobile-showcase/automotive/assets/visual/hero.svg b/examples/mobile-showcase/automotive/assets/visual/hero.svg
new file mode 100644
index 0000000..4ba145a
--- /dev/null
+++ b/examples/mobile-showcase/automotive/assets/visual/hero.svg
@@ -0,0 +1 @@
+
diff --git a/examples/mobile-showcase/automotive/index.html b/examples/mobile-showcase/automotive/index.html
new file mode 100644
index 0000000..912369f
--- /dev/null
+++ b/examples/mobile-showcase/automotive/index.html
@@ -0,0 +1,24 @@
+汽车出口跳转至正文
+
+
+
+01 / THE WORLD IN MOTION汽车出口 MOVE BEYOND.
专业车辆采购与全球交付
DESIGN / TRUST / DELIVERYSCROLL TO DISCOVER ↓
+
+
+
+
+
+LET'S CONNECT从这里开始下一步。
专业车辆采购与全球交付
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/automotive/listing.html b/examples/mobile-showcase/automotive/listing.html
new file mode 100644
index 0000000..bae7222
--- /dev/null
+++ b/examples/mobile-showcase/automotive/listing.html
@@ -0,0 +1,22 @@
+全部产品跳转至正文
+
+
+
+DISCOVER / COLLECTION全部产品
分类查看产品与解决方案
+
+
+
+LET'S CONNECT从这里开始下一步。
专业车辆采购与全球交付
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/automotive/product.html b/examples/mobile-showcase/automotive/product.html
new file mode 100644
index 0000000..138d4e3
--- /dev/null
+++ b/examples/mobile-showcase/automotive/product.html
@@ -0,0 +1,17 @@
+标准系列产品跳转至正文
+
+
+
+
+
PRODUCT DETAILS / EXAMPLE标准系列产品
面向全球客户的精选示范产品
品牌 · EXAMPLE
咨询该产品 ↗
+
+
+
+LET'S CONNECT从这里开始下一步。
专业车辆采购与全球交付
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/corporate/article.html b/examples/mobile-showcase/corporate/article.html
new file mode 100644
index 0000000..0b35888
--- /dev/null
+++ b/examples/mobile-showcase/corporate/article.html
@@ -0,0 +1,13 @@
+企业动态与行业资讯跳转至正文
+
+
+
+LATEST NEWS / INSIGHTS企业动态与行业资讯
了解最新项目与服务进展
项目背景
这是一段用于检查手机端资讯排版的样例正文。适当的字距、行距和图片宽度能够提升移动阅读体验。
← 返回新闻
+
+
+LET'S CONNECT从这里开始下一步。
连接更可靠的全球业务
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/corporate/assets/blocks.css b/examples/mobile-showcase/corporate/assets/blocks.css
new file mode 100644
index 0000000..9ce1279
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/blocks.css
@@ -0,0 +1,29 @@
+/* Portable SiteForge blocks, loaded with every theme (all styles scoped to sf-). */
+.sf-section{background:var(--sf-bg,transparent);color:var(--sf-fg,inherit);padding:var(--sf-pad,82px) 0;margin:0}.sf-section:nth-of-type(even){background:var(--sf-bg,#f4f6f9)}.sf-container{max-width:1200px;margin:auto;padding:0 32px}.sf-section h1,.sf-section h2,.sf-section h3,.sf-section p{color:inherit;line-height:1.3}.sf-section h1{font-size:clamp(38px,5vw,76px);font-weight:790;letter-spacing:-.052em}.sf-section h2{font-size:clamp(27px,3.2vw,44px);font-weight:760;letter-spacing:-.037em}.sf-kicker{display:block;font-size:12px;letter-spacing:.17em;text-transform:uppercase;font-weight:740;color:var(--accent,#3569d3);margin-bottom:18px}.sf-hero-grid,.sf-image-text{display:grid;grid-template-columns:1.08fr .92fr;gap:68px;align-items:center}.sf-hero-copy p{font-size:18px;line-height:1.75;max-width:520px;opacity:.75}.sf-hero-img,.sf-image-side{overflow:hidden;border-radius:9px;min-height:380px;background:linear-gradient(130deg,#d5e1ee,#eef3f9);display:grid;place-items:center}.sf-hero-img img,.sf-image-side img{width:100%;height:100%;max-height:580px;object-fit:cover}.sf-hero-abstract{font-size:110px;color:#a7bdd4}.sf-button{display:inline-flex;align-items:center;justify-content:center;gap:28px;background:var(--accent,#315ee2);color:white!important;padding:16px 26px;border-radius:6px;font-size:14px;text-decoration:none;font-weight:720;margin-top:24px}.sf-section-head{display:flex;align-items:end;justify-content:space-between;margin-bottom:32px;gap:24px}.sf-section-head h2{margin:0}.sf-section-head>a,.sf-link{color:var(--accent,#3569d3);font-weight:700;text-decoration:none}.sf-prose{font-size:16px;line-height:1.85;max-width:930px}.sf-prose p{line-height:1.85}.sf-features,.sf-collection,.sf-stats{display:grid;gap:20px}.sf-features{grid-template-columns:repeat(3,minmax(0,1fr))}.sf-feature{border:1px solid #e0e6eb;padding:32px 28px;border-radius:8px;background:#fff;color:#253247}.sf-feature>span{font-size:12px;font-weight:800;color:var(--accent,#346add)}.sf-feature h3{font-size:22px;margin:30px 0 8px}.sf-feature p{font-size:14px;line-height:1.7;color:#647181}.sf-stats{grid-template-columns:repeat(4,1fr);padding:40px 0;border-top:1px solid #b7c0cd66;border-bottom:1px solid #b7c0cd66}.sf-stats>div{display:flex;flex-direction:column;gap:8px}.sf-stats strong{font-size:clamp(28px,4vw,53px);letter-spacing:-.04em}.sf-stats span{font-size:12px;opacity:.65;letter-spacing:.04em}.sf-collection{grid-template-columns:repeat(var(--sf-cols,3),minmax(0,1fr))}.sf-item{display:block;text-decoration:none;color:#233044;background:#fff;border:1px solid #e8edf1;border-radius:8px;overflow:hidden;transition:transform .2s,box-shadow .2s}.sf-item:hover{transform:translateY(-4px);box-shadow:0 18px 38px #344d7018}.sf-item-img{height:210px;overflow:hidden;background:#e4eaf0}.sf-item-img img{height:100%;width:100%;object-fit:cover}.sf-placeholder{display:grid;place-items:center;width:100%;height:100%;min-height:190px;background:linear-gradient(130deg,#e2eaf2,#c9d7e6);font-size:72px;color:#8399b2;font-weight:800}.sf-large{min-height:360px}.sf-item-copy{padding:25px}.sf-item-copy small{text-transform:uppercase;letter-spacing:.13em;color:var(--accent,#376edc)}.sf-item-copy h3{font-size:22px;margin:12px 0 7px}.sf-item-copy p{font-size:14px;line-height:1.6;color:#7a8796;min-height:42px}.sf-item-copy span{font-size:12px;color:var(--accent,#376edc);font-weight:800}.sf-image-text>div>p{font-size:17px;line-height:1.8;max-width:520px;opacity:.7}.sf-reverse>div:first-child{order:2}.sf-gallery{display:grid;grid-template-columns:repeat(3,1fr);gap:15px}.sf-gallery img{width:100%;height:230px;object-fit:cover;border-radius:6px}.sf-faq{max-width:920px}.sf-faq details{border-bottom:1px solid #dce3eb;padding:22px 0}.sf-faq summary{font-size:17px;cursor:pointer;font-weight:700}.sf-faq p{color:#647181;line-height:1.75}.sf-cta-wrap{display:flex;justify-content:space-between;align-items:center;gap:24px}.sf-cta-wrap h2{margin:0}.sf-cta-wrap p{font-size:16px;opacity:.75}.sf-section a{cursor:pointer}.sf-muted{opacity:.65}
+@media(max-width:820px){.sf-container{padding:0 20px}.sf-section{padding:50px 0}.sf-hero-grid,.sf-image-text{grid-template-columns:1fr;gap:26px}.sf-reverse>div:first-child{order:initial}.sf-hero-img,.sf-image-side{min-height:250px}.sf-features,.sf-collection{grid-template-columns:repeat(2,minmax(0,1fr))}.sf-stats{grid-template-columns:repeat(2,1fr)}.sf-cta-wrap{align-items:flex-start;flex-direction:column}}
+@media(max-width:540px){.sf-features,.sf-collection,.sf-gallery{grid-template-columns:1fr}.sf-section-head{display:block}.sf-section-head>a{display:block;margin-top:13px}.sf-item-img{height:220px}.sf-hero-grid{gap:22px}.sf-hero-copy p{font-size:16px}.sf-stats{gap:28px}}
+
+.sf-attributes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 32px;border-top:1px solid #e5eaf0}.sf-attribute{display:flex;justify-content:space-between;align-items:center;gap:18px;border-bottom:1px solid #e5eaf0;padding:19px 10px;font-size:14px}.sf-attribute span{color:#748497}.sf-attribute strong{font-weight:720;text-align:right}@media(max-width:640px){.sf-attributes{grid-template-columns:1fr}}
+
+/* SiteForge responsive no-code grid container */
+.sf-layout-grid{display:grid;grid-template-columns:var(--sf-layout-cols,1fr 1fr);gap:var(--sf-layout-gap,24px);align-items:stretch}
+.sf-layout-column{min-width:0}.sf-layout-column>.sf-section{padding:0;margin:0 0 18px}.sf-layout-column .sf-container{width:100%;padding:0}
+.sf-layout-column>.sf-section:last-child{margin-bottom:0}
+@media(max-width:820px){.sf-layout-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}
+@media(max-width:540px){.sf-layout-grid{grid-template-columns:minmax(0,1fr)!important}}
+
+/* V8.6: accessible, script-free commercial design blocks */
+.sf-slides{display:flex;gap:18px;overflow-x:auto;scroll-snap-type:x mandatory;padding:6px 0 18px;scrollbar-width:thin}
+.sf-slide{flex:0 0 min(78%,720px);min-width:220px;margin:0;scroll-snap-align:start}
+.sf-slide img{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:10px}
+.sf-slide figcaption{padding:10px 2px;opacity:.7;font-size:14px}
+.sf-video{aspect-ratio:16/9;max-width:1000px;background:#ebf0f5;border-radius:10px;overflow:hidden}
+.sf-video iframe{width:100%;height:100%;border:0}
+.sf-tab{padding:19px 0;border-bottom:1px solid #dde4ec;max-width:960px}
+.sf-tab summary{font-size:18px;font-weight:700;cursor:pointer}.sf-tab p{max-width:850px;line-height:1.8}
+.sf-prices{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px}
+.sf-price{padding:28px;background:#fff;border:1px solid #dee5ef;border-radius:10px;color:#29394b}
+.sf-price strong{display:block;font-size:27px;margin:14px 0}.sf-price li{margin:11px 0}.sf-price ul{padding-left:19px}
+.sf-map-address{font-style:normal;font-size:18px;white-space:pre-line;line-height:1.8}
+@media (prefers-reduced-motion:reduce){.sf-slides{scroll-behavior:auto}.sf-item{transition:none}}
+@media(max-width:540px){.sf-slide{flex-basis:92%}}
diff --git a/examples/mobile-showcase/corporate/assets/commercial.css b/examples/mobile-showcase/corporate/assets/commercial.css
new file mode 100644
index 0000000..353bee4
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/commercial.css
@@ -0,0 +1,84 @@
+/* SiteForge commercial theme UX layer: accessibility, layout rhythm, media, responsive navigation. */
+:root{--sf-shadow:0 20px 55px rgba(17,33,55,.08);--sf-shadow-hover:0 26px 65px rgba(17,33,55,.15)}
+html{scroll-padding-top:104px}body{font-feature-settings:"kern";text-rendering:optimizeLegibility}
+a,button,input,select,textarea{-webkit-tap-highlight-color:transparent}
+a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--pro-accent);outline-offset:4px}
+.pro-wrap{width:100%;max-width:min(var(--pro-wide),100%)}.pro-header{isolation:isolate;box-shadow:0 1px 0 rgba(15,35,60,.035)}.pro-head-inner{gap:clamp(15px,2vw,29px)}.pro-logo{min-width:0;flex-shrink:0;gap:12px}.pro-brand-logo{max-width:155px;max-height:46px;width:auto;height:auto;object-fit:contain}.pro-logo-name{overflow-wrap:anywhere}.pro-navigation{gap:clamp(11px,1.6vw,24px)}.pro-header-cta{white-space:nowrap}
+.pro-mobile-toggle,.pro-catalog-toggle,.pro-nav-disclose{border:0;background:transparent;color:inherit;line-height:1.2}
+.pro-mobile-toggle{min-height:44px;min-width:44px;border:1px solid currentColor;border-radius:6px;font-size:19px}.pro-nav-group{position:relative;display:flex;align-items:center;gap:2px}
+.pro-nav-disclose{padding:9px;font-size:13px}.pro-nav-children{position:absolute;top:100%;left:0;min-width:215px;max-width:330px;display:grid;gap:2px;padding:13px;box-shadow:var(--sf-shadow);border-radius:10px;z-index:45;background:var(--pro-background);border:1px solid var(--pro-border);color:var(--pro-text)}
+.pro-nav-children[hidden],.pro-mega[hidden]{display:none!important}.pro-nav-children a{display:block;font-size:13px;white-space:normal;padding:10px;border-radius:5px}.pro-nav-children a:hover{background:var(--pro-surface)}
+.pro-catalog-toggle{padding:11px 3px;white-space:nowrap;font-size:12px;font-weight:700}.pro-mega{position:absolute;top:100%;left:50%;transform:translateX(-50%);width:min(100%,var(--pro-wide));box-shadow:var(--sf-shadow);z-index:41;display:grid;grid-template-columns:215px 1fr;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);border-radius:0 0 15px 15px;padding:29px;align-items:start}.pro-mega>span{font-size:12px;letter-spacing:.06em}.pro-mega>div{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px 22px;overflow:visible}.pro-mega>div a{white-space:normal;padding:10px 5px;border-bottom:1px solid var(--pro-border)}
+.pro-home-hero{overflow:hidden}.pro-home-hero .pro-home-grid{min-height:660px;gap:5%}.pro-home-hero .pro-hero-copy{position:relative;z-index:2}.pro-home-hero .pro-hero-copy h1{font-size:clamp(46px,5.7vw,92px);line-height:1.06;letter-spacing:-.064em;max-width:15ch}.pro-home-hero .pro-hero-copy h1 em{font-style:normal;display:block}.pro-home-hero .pro-hero-copy p{font-size:clamp(15px,1.3vw,19px);line-height:1.75;max-width:44ch}.pro-home-hero .pro-hero-art{min-width:0;min-height:425px;position:relative;isolation:isolate}.pro-home-hero .pro-hero-art>img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}.pro-hero-art:has(.pro-abstract-car),.pro-hero-art:has(.pro-technical),.pro-hero-art:has(.pro-corporate-shape){background-position:center;background-size:cover;background-repeat:no-repeat}.pro-abstract-car,.pro-technical,.pro-corporate-shape{position:relative;z-index:1;pointer-events:none}
+.pro-hero-actions{display:flex;flex-wrap:wrap;align-items:center;gap:15px}.pro-button,.pro-header-cta,.pro-outline-button{min-height:48px;transition:transform .24s ease,box-shadow .24s ease,opacity .24s ease}.pro-button:hover,.pro-header-cta:hover,.pro-outline-button:hover{transform:translateY(-2px);box-shadow:0 10px 22px rgba(0,0,0,.10)}
+.pro-section{padding-top:clamp(76px,7vw,112px)!important;padding-bottom:clamp(76px,7vw,112px)!important}.pro-section-head{gap:16px}.pro-section-head h2{font-size:clamp(32px,3.5vw,53px);max-width:22ch}.pro-section-head .pro-text-link{align-self:end}
+.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}.pro-card{min-width:0;display:flex;flex-direction:column;transition:transform .28s ease,box-shadow .28s ease;border:1px solid var(--pro-border);background:var(--pro-background);overflow:hidden}.pro-card:hover{transform:translateY(-6px);box-shadow:var(--sf-shadow-hover)}.pro-card-cover{aspect-ratio:1.42;overflow:hidden;background:var(--pro-surface);position:relative}.pro-card-cover .pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .45s ease}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-card-body{display:flex;flex-direction:column;flex:1}.pro-card-body h3{font-size:clamp(19px,1.6vw,24px);line-height:1.26}.pro-card-body p{font-size:14px}.pro-card-link{margin-top:auto;padding-top:14px;font-size:13px;font-weight:700}
+.pro-art{min-height:100%;width:100%;position:relative;overflow:hidden;display:grid;place-items:center;isolation:isolate}.pro-art-ring{width:60%;aspect-ratio:1;border:1px solid currentColor;opacity:.13;border-radius:50%;transform:rotate(20deg);box-shadow:0 0 0 23px #ffffff10,0 0 0 57px #ffffff07}.pro-art-center{font-size:clamp(38px,5vw,80px);position:absolute;font-weight:800;letter-spacing:-.065em;opacity:.65}.pro-art-label{position:absolute;bottom:19px;left:22px;font-size:10px;letter-spacing:.22em;opacity:.7}
+.pro-breadcrumb{display:flex;gap:11px;flex-wrap:wrap;font-size:12px;align-items:center}.pro-breadcrumb a{text-decoration:underline;text-underline-offset:4px}.pro-product-hero{align-items:start;gap:clamp(28px,5vw,80px)}.pro-product-media{aspect-ratio:1.12;background:var(--pro-surface);overflow:hidden}.pro-product-media>img,.pro-product-media>.pro-art{width:100%;height:100%;object-fit:contain}.pro-product-summary{padding:clamp(12px,3vw,50px) 0}.pro-product-summary h1{font-size:clamp(36px,4.3vw,72px)}.pro-detail-layout{border-top:1px solid var(--pro-border);padding-top:50px}.pro-specs>div{display:grid;grid-template-columns:minmax(100px,40%) 1fr;gap:10px;align-items:baseline;padding:15px 0;border-bottom:1px solid var(--pro-border)}.pro-specs strong{overflow-wrap:anywhere}.pro-gallery-thumbs button{flex-basis:96px;height:76px}.pro-gallery-lightbox{backdrop-filter:blur(6px)}
+.pro-filter-panel{display:flex;align-items:end;flex-wrap:wrap;gap:16px;background:var(--pro-surface);border:1px solid var(--pro-border);padding:20px;margin-bottom:25px;border-radius:var(--pro-radius)}.pro-filter-panel label{display:grid;gap:7px;min-width:180px;flex:1}.pro-filter-panel input,.pro-filter-panel select{width:100%;background:var(--pro-background);border:1px solid var(--pro-border);padding:12px 14px;color:var(--pro-text);border-radius:6px}.pro-filter-count{white-space:nowrap;font-size:13px;padding:12px 0}.pro-card[hidden]{display:none!important}.pro-filter-empty{padding:38px;border:1px dashed var(--pro-border);text-align:center;grid-column:1/-1}
+.pro-inquiry input,.pro-inquiry textarea{min-height:46px}.pro-inquiry textarea{min-height:150px}.pro-inquiry-wrap{gap:clamp(32px,7vw,110px)}.pro-inquiry .pro-button{border:0;justify-self:start}.pro-footer-links a{padding-block:5px;display:inline-block}
+.pro-end-cta{overflow:hidden}.pro-end-cta h2{font-size:clamp(30px,4vw,64px)}
+.pro-rich{line-height:1.85}.pro-rich img{border-radius:var(--pro-radius);margin-block:24px}.pro-rich table{width:100%;display:block;overflow:auto;border-collapse:collapse}.pro-rich td,.pro-rich th{border:1px solid var(--pro-border);padding:10px 14px}.pro-rich a{text-decoration:underline;text-underline-offset:3px}
+@media(min-width:1025px){.pro-navigation>.pro-nav-group:hover .pro-nav-children:not([hidden]){visibility:visible}}
+@media(max-width:1150px){.pro-head-inner{gap:15px}.pro-navigation{gap:12px}.pro-logo-name{font-size:15px}.pro-header-cta{padding:13px 16px}}
+@media(max-width:1024px){.pro-head-inner{min-height:72px;flex-wrap:wrap}.pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto}.pro-navigation{display:none;width:100%;flex-basis:100%;flex-direction:column;align-items:stretch;gap:0;order:5;margin:0;padding:8px 0 20px;border-top:1px solid var(--pro-border)}.pro-navigation.is-open{display:flex}.pro-navigation>a,.pro-nav-group>a{padding:14px 8px;font-size:15px}.pro-nav-group{display:flex;flex-wrap:wrap;justify-content:space-between}.pro-nav-children{position:static;flex-basis:100%;max-width:none;width:100%;box-shadow:none;border-radius:0;border-left:2px solid var(--pro-accent);margin:0 0 12px 8px}.pro-nav-disclose{min-width:44px;min-height:44px}.pro-catalog-toggle{margin-left:auto}.pro-mega{top:100%;display:block;padding:20px}.pro-mega>div{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:12px}.pro-home-hero .pro-home-grid{min-height:560px}.pro-home-hero .pro-hero-art{min-height:325px}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
+@media(max-width:700px){.pro-wrap{padding-left:20px;padding-right:20px}.pro-utility{display:none}.pro-head-inner{min-height:66px}.pro-logo-name{max-width:140px;font-size:14px}.pro-brand-logo{max-width:125px}.pro-header-cta{display:none}.pro-catalog-toggle{font-size:11px;padding-inline:7px;margin-left:0}.pro-home-hero .pro-home-grid{display:flex;flex-direction:column;gap:36px;padding-top:48px;padding-bottom:48px}.pro-home-hero .pro-hero-copy h1{font-size:clamp(43px,10vw,68px)}.pro-home-hero .pro-hero-copy p{font-size:15px}.pro-home-hero .pro-hero-art{width:100%;min-height:290px}.pro-home-hero .pro-hero-bottom{display:none}.pro-section{padding-top:68px!important;padding-bottom:68px!important}.pro-section-head{align-items:flex-start}.pro-grid{grid-template-columns:1fr;gap:18px}.pro-card-cover{aspect-ratio:1.45}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-inquiry-wrap{display:grid;grid-template-columns:1fr;gap:30px}.pro-product-summary{padding:0}.pro-specs>div{grid-template-columns:1fr 1fr}.pro-mega{top:100%;max-height:65vh;overflow-y:auto}.pro-mega>div{grid-template-columns:1fr}.pro-filter-panel{display:grid;grid-template-columns:1fr;gap:12px}.pro-filter-panel label{min-width:0}.pro-footer-top{display:grid;grid-template-columns:1fr;gap:27px}.pro-footer-bottom{gap:12px;flex-wrap:wrap}}
+@media(max-width:410px){.pro-logo-name{max-width:115px}.pro-logo-symbol{font-size:28px}.pro-catalog-toggle{font-size:10px}.pro-mobile-toggle{min-width:42px}}
+@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation-duration:.01ms!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}
+
+/* Accessible static catalogue pagination for large inventories. */
+.pro-pages-nav{display:flex;gap:1rem;justify-content:center;align-items:center;margin:2rem 0;flex-wrap:wrap}
+.pro-pages-nav a{padding:.7rem 1.1rem;border:1px solid currentColor;border-radius:.4rem;text-decoration:none}
+.pro-pages-nav a:focus-visible{outline:3px solid var(--accent,#2563eb);outline-offset:3px}
+.pro-pages-nav span{font-size:.875rem}
+
+/* P6 international sites: small, keyboard-operable language switch. */
+.sf-language-switch{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;margin:8px 12px;padding:4px;border:1px solid rgba(120,130,145,.22);border-radius:10px;background:rgba(255,255,255,.08)}
+.sf-language-switch a{font:inherit;font-size:12px;line-height:1.5;padding:4px 7px;border-radius:6px;color:inherit;text-decoration:none}
+.sf-language-switch a[aria-current="page"]{font-weight:700;text-decoration:underline;text-underline-offset:3px}
+.sf-language-switch a:focus-visible{outline:2px solid #2563eb;outline-offset:3px}
+@media(max-width:640px){.sf-language-switch{margin:6px 0;width:auto}}
+
+/* V8.21.3 — Four-theme unified mobile off-canvas navigation & optional tab bar.
+ Desktop remains unchanged. Phone / tablet use one accessible drawer. */
+.sf-mobile-tabs{display:none!important}
+.sf-drawer-header,.sf-mobile-drawer-backdrop,.sf-drawer-extra{display:none}
+@media(max-width:1024px){
+ .pro-head-inner{flex-wrap:nowrap!important;gap:12px!important}
+ .pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto!important;flex:0 0 44px;min-width:44px;min-height:44px;position:relative;z-index:3}
+ .pro-catalog-toggle{display:none!important}
+ .pro-navigation,.pro-navigation.is-open{display:flex!important;position:fixed!important;inset:0 0 0 auto!important;width:min(88vw,360px)!important;height:100dvh!important;height:100vh;max-height:100dvh!important;flex:unset!important;flex-direction:column!important;align-items:stretch!important;order:unset!important;margin:0!important;padding:0 0 calc(20px + env(safe-area-inset-bottom,0px))!important;overflow-x:hidden;overflow-y:auto!important;background:var(--pro-background,#fff)!important;color:var(--pro-text,#19283c)!important;border:0!important;border-left:1px solid var(--pro-border,#ddd)!important;box-shadow:-15px 0 45px #0002!important;z-index:1201!important;transform:translateX(105%);visibility:hidden;pointer-events:none;transition:transform .24s ease,visibility .24s ease!important;overscroll-behavior:contain}
+ .pro-navigation.is-open{transform:translateX(0);visibility:visible;pointer-events:auto}
+ .sf-drawer-header{display:flex!important;align-items:center;justify-content:space-between;gap:12px;padding:20px 18px;border-bottom:1px solid var(--pro-border,#dde3e9);font-size:17px;font-weight:750;position:sticky;top:0;background:var(--pro-background,#fff);z-index:2}
+ .sf-drawer-close{min-width:44px;min-height:44px;border:1px solid var(--pro-border,#dde3e9);color:inherit;background:transparent;border-radius:10px;font-size:24px;cursor:pointer}
+ .sf-mobile-drawer-backdrop{display:block;position:fixed;inset:0;z-index:1200;background:#0b1b31a3;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .24s ease,visibility .24s ease}
+ .sf-mobile-drawer-backdrop.is-open{opacity:1;visibility:visible;pointer-events:auto}
+ body.sf-drawer-open{overflow:hidden!important}
+ .pro-navigation>a,.pro-navigation .pro-nav-group>a,.pro-navigation .sf-drawer-extra>a{display:flex;align-items:center;min-height:50px;padding:13px 20px!important;font-size:15px!important;color:inherit!important;white-space:normal;border-bottom:1px solid var(--pro-border,#dde3e9)!important;text-decoration:none!important}
+ .pro-navigation .pro-nav-group{display:flex;position:relative;flex-wrap:wrap!important;width:100%;gap:0;align-items:center;justify-content:space-between}
+ .pro-navigation .pro-nav-group>a{flex:1;min-width:0}
+ .pro-navigation .pro-nav-disclose{margin-right:12px;min-width:44px;min-height:44px;border-radius:9px;background:var(--pro-surface,#f4f6f8)}
+ .pro-navigation .pro-nav-children{position:static!important;width:100%!important;max-width:100%!important;box-shadow:none!important;border-left:3px solid var(--pro-accent,#e77730);border-radius:0!important;margin:0 0 8px 14px!important;flex-basis:calc(100% - 14px)!important;background:var(--pro-surface,#f6f8fa)}
+ .pro-navigation .pro-nav-children a{padding:12px 16px!important;font-size:14px!important;color:inherit!important;min-height:44px}
+ .sf-drawer-extra{display:block;padding:12px 0 5px}.sf-drawer-extra strong{display:block;font-size:12px;letter-spacing:.06em;padding:12px 20px;opacity:.65}.sf-drawer-extra a{font-size:14px!important}
+ .sf-drawer-close:focus-visible,.pro-mobile-toggle:focus-visible,.pro-nav-disclose:focus-visible,.sf-mobile-tab:focus-visible{outline:3px solid var(--pro-accent,#ff8200);outline-offset:2px}
+}
+@media(max-width:767px){
+ .pro-header-cta{display:none!important}
+ .pro-head-inner .pro-logo{min-width:0;max-width:calc(100% - 65px)}
+ .pro-logo-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .pro-home-hero .pro-home-grid{min-height:0!important}
+ .pro-home-hero .pro-hero-art{min-height:230px}
+ .pro-card,.pro-card-cover,.pro-product-media{max-width:100%;min-width:0}
+ .pro-rich img,.pro-content img,.sf-canvas img{max-width:100%;height:auto}
+ .pro-rich table{max-width:100%;overflow:auto}
+ .pro-grid,.pro-footer-top{min-width:0}
+ .sf-mobile-tabs{display:grid!important;position:fixed;z-index:1100;bottom:0;left:0;right:0;grid-template-columns:repeat(var(--sf-mobile-tab-count,4),minmax(0,1fr));align-items:stretch;padding:5px 6px calc(5px + env(safe-area-inset-bottom,0px));min-height:63px;background:var(--pro-background,#fff);color:var(--pro-text,#263446);border-top:1px solid var(--pro-border,#dfe4eb);box-shadow:0 -6px 24px #13223914}
+ .sf-mobile-tab{appearance:none;border:0!important;background:transparent!important;color:inherit!important;text-decoration:none!important;padding:6px 2px;min-height:50px;min-width:0;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:2px;border-radius:9px;cursor:pointer;font-family:inherit}
+ .sf-tab-icon{font-size:23px;line-height:1.15;font-weight:500}
+ .sf-tab-label{font-size:11px;font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .sf-mobile-tab[aria-current=page]{color:var(--pro-accent,#e47730)!important;background:var(--pro-surface,#f5f6f8)!important}
+ body:has(> .sf-mobile-tabs){padding-bottom:calc(76px + env(safe-area-inset-bottom,0px))!important}
+}
+@media(min-width:768px){.sf-mobile-tabs{display:none!important}}
+@media(prefers-reduced-motion:reduce){.pro-navigation,.sf-mobile-drawer-backdrop{transition:none!important}}
diff --git a/examples/mobile-showcase/corporate/assets/custom.css b/examples/mobile-showcase/corporate/assets/custom.css
new file mode 100644
index 0000000..e18a2d4
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/custom.css
@@ -0,0 +1 @@
+body .pro-utility[hidden]{display:none!important}@media(max-width:1023px){.sf-canvas .sf-section{padding-block:64px}}@media(max-width:767px){.sf-canvas .sf-section{padding-block:40px}.pro-wrap,.sf-container{padding-left:20px;padding-right:20px}body .pro-utility{display:none!important}}
\ No newline at end of file
diff --git a/examples/mobile-showcase/corporate/assets/factory.css b/examples/mobile-showcase/corporate/assets/factory.css
new file mode 100644
index 0000000..154ffb3
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/factory.css
@@ -0,0 +1,27 @@
+/* SiteForge trusted global shell. Site themes remain visually independent. */
+:root{--sf-shell:#12243d;--sf-shell-accent:#dcae71;--sf-shell-soft:#edf2f8;--sf-shell-fg:#173049}
+html{scroll-behavior:smooth}body{overflow-x:hidden}body>.header,body>.utility,body>.top,body>header,body>footer{display:none!important}
+.sf-theme-industrial{--sf-shell:#114e79;--sf-shell-accent:#92d5ed;--sf-shell-soft:#ebf6fa;--sf-shell-fg:#0c4267}
+.sf-theme-corporate{--sf-shell:#232d62;--sf-shell-accent:#b8bdff;--sf-shell-soft:#f3f3fa;--sf-shell-fg:#333468}
+.sf-theme-minimal{--sf-shell:#1e1e1c;--sf-shell-accent:#ded6c5;--sf-shell-soft:#f6f4ef;--sf-shell-fg:#252520}
+.sf-site-head{position:relative;z-index:25;background:var(--sf-shell);color:#fff;font-family:system-ui,-apple-system,sans-serif}
+.sf-head-inner{max-width:1240px;margin:0 auto;min-height:78px;padding:0 28px;display:flex;align-items:center;gap:28px}
+.sf-logo{display:flex;align-items:center;gap:11px;font-size:18px;text-decoration:none!important;color:#fff!important;white-space:nowrap}
+.sf-logo-mark{width:36px;height:36px;display:grid;place-items:center;background:var(--sf-shell-accent);color:var(--sf-shell);font-size:22px;font-weight:900;border-radius:9px}
+.sf-primary-links{display:flex;gap:23px;margin-left:auto;align-items:center}
+.sf-primary-links a,.sf-mega a{color:#fff!important;text-decoration:none!important;font-size:13px;font-weight:650;white-space:nowrap}
+.sf-primary-links a:hover{color:var(--sf-shell-accent)!important}
+.sf-head-cta{font-size:13px;font-weight:750;text-decoration:none;background:var(--sf-shell-accent);color:var(--sf-shell)!important;padding:13px 18px;border-radius:4px;white-space:nowrap}
+.sf-menu-toggle{display:none;margin-left:auto;color:white;border:1px solid #ffffff55;background:none;border-radius:4px;padding:8px 12px;font-size:19px;cursor:pointer}
+.sf-mega{display:flex;gap:18px;max-width:1240px;margin:auto;padding:0 28px 9px;overflow-x:auto}
+.sf-mega a{display:block;min-width:120px;border-top:1px solid #ffffff2e;padding:12px 8px}.sf-mega small{display:block;color:#ffffff99;max-width:160px;overflow:hidden;text-overflow:ellipsis;font-size:10px;margin-top:6px}
+.sf-site-foot{background:var(--sf-shell);color:white!important;font:14px/1.7 system-ui,-apple-system,sans-serif;padding:72px 28px 26px}
+.sf-foot-grid{max-width:1200px;margin:auto;display:grid;grid-template-columns:2fr 1fr 1fr;gap:70px}.sf-foot-grid h2{font-size:25px;color:white;margin:0 0 17px}.sf-foot-grid h4{font-size:13px;color:var(--sf-shell-accent);letter-spacing:.07em;margin:0 0 17px}.sf-foot-grid p{color:#ffffffae;max-width:380px}.sf-foot-grid a{display:block;width:fit-content;color:#fff!important;text-decoration:none;margin-bottom:8px}.sf-foot-lower{border-top:1px solid #ffffff33;max-width:1200px;margin:45px auto 0;padding-top:20px;display:flex;justify-content:space-between;color:#ffffff9e;font-size:12px}
+.sf-filters{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin:0 0 30px}.sf-filters input,.sf-filters select{padding:11px 13px;border-radius:6px;border:1px solid #cfd9e4;background:#fff;color:#263a51;max-width:280px}.sf-filter-empty{padding:22px;color:#778598}
+.sf-search-page{min-height:76vh;max-width:1060px;padding:90px 24px;margin:auto;font-family:system-ui,sans-serif;color:var(--sf-shell-fg)}.sf-search-page h1{font-size:clamp(35px,5vw,62px);margin:20px 0}.sf-search-page>label{display:block;margin:18px 0 8px}.sf-search-page input{display:block;width:100%;padding:17px;font-size:18px;border:1px solid #b7c6d7;border-radius:8px}.sf-search-page #sf-search-count{margin:22px 0;color:#607589}.sf-search-result{padding:22px 0;border-bottom:1px solid #dae1ea}.sf-search-result a{color:var(--sf-shell-fg);font-size:21px;font-weight:750;text-decoration:none}.sf-search-result small{display:block;color:#7992a7}.sf-search-result p{margin:7px 0;color:#657487}
+@media(max-width:1050px){.sf-head-inner{gap:13px}.sf-primary-links{gap:12px}.sf-primary-links a{font-size:12px}.sf-head-cta{padding:10px}}
+@media(max-width:850px){.sf-head-inner{min-height:68px}.sf-menu-toggle{display:block}.sf-head-cta{display:none}.sf-primary-links{display:none;position:absolute;top:68px;left:0;right:0;margin:0;background:var(--sf-shell);padding:20px 28px;flex-direction:column;align-items:stretch;box-shadow:0 20px 24px #0002}.sf-primary-links.is-open{display:flex}.sf-mega{display:none}.sf-foot-grid{grid-template-columns:1fr 1fr;gap:35px}}
+@media(max-width:560px){.sf-foot-grid{grid-template-columns:1fr}.sf-foot-lower{display:block}.sf-site-foot{padding-top:48px}.sf-head-inner{padding:0 16px}}
+
+.sf-page-intro{padding:80px 28px;background:var(--sf-shell-soft);color:var(--sf-shell-fg);font-family:system-ui,sans-serif}.sf-page-intro>div{max-width:1200px;margin:auto}.sf-page-intro p{font-size:11px;font-weight:850;letter-spacing:.19em;text-transform:uppercase;color:var(--sf-shell-fg);opacity:.58}.sf-page-intro h1{font-size:clamp(38px,5vw,67px);letter-spacing:-.04em;line-height:1.08;margin:13px 0}.sf-page-intro span{font-size:16px;opacity:.72}
+.sf-item[hidden]{display:none!important}
diff --git a/examples/mobile-showcase/corporate/assets/pro-base.css b/examples/mobile-showcase/corporate/assets/pro-base.css
new file mode 100644
index 0000000..c2c4f61
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/pro-base.css
@@ -0,0 +1,21 @@
+/* SiteForge V3 professional theme primitives. Each commercial theme adds its own art direction. */
+:root{--pro-text:#19283a;--pro-muted:#687587;--pro-border:#dfe5ea;--pro-background:#fff;--pro-surface:#f5f7fa;--pro-accent:#365cb7;--pro-hero:#15263d;--pro-hero-fg:#fff;--pro-radius:12px;--pro-headings:system-ui,-apple-system,sans-serif;--pro-font:system-ui,-apple-system,sans-serif;--pro-wide:1270px}
+*,*:before,*:after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--pro-background);color:var(--pro-text);font-family:var(--pro-font);-webkit-font-smoothing:antialiased;overflow-x:hidden}button,input,select{font:inherit}button,a{touch-action:manipulation}button{cursor:pointer}a{text-decoration:none;color:inherit}a:hover{color:var(--pro-accent)}img{max-width:100%;display:block}h1,h2,h3,h4,p{margin-top:0}h1,h2,h3{font-family:var(--pro-headings);line-height:1.09}h1{letter-spacing:-.052em}h2{letter-spacing:-.042em}h3{letter-spacing:-.028em}p{line-height:1.72}.pro-wrap{max-width:var(--pro-wide);margin:0 auto;padding-left:32px;padding-right:32px}.pro-skip{position:absolute;left:-9999px;z-index:90;background:#fff;color:#000;padding:12px}.pro-skip:focus{left:10px;top:10px}.pro-eyebrow{font-weight:750;font-size:11px;letter-spacing:.17em;text-transform:uppercase;display:block;line-height:1.6;color:var(--pro-accent)}.pro-muted{opacity:.7}.pro-utility{min-height:30px;background:var(--pro-hero);color:#c5d2db;font-size:10px;letter-spacing:.16em}.pro-utility .pro-wrap{display:flex;justify-content:space-between;padding-block:8px}.pro-utility i{font-style:normal;color:var(--pro-accent)}.pro-header{background:var(--pro-background);color:var(--pro-text);border-bottom:1px solid var(--pro-border);position:relative;z-index:35}.pro-head-inner{min-height:84px;display:flex;align-items:center;gap:28px}.pro-logo{display:flex;align-items:center;gap:12px;min-width:170px}.pro-logo-symbol{font-size:32px;line-height:1;font-weight:800;letter-spacing:-.07em;color:var(--pro-accent)}.pro-logo-name{max-width:220px;font-weight:820;font-size:17px;line-height:1.1;letter-spacing:-.035em}.pro-logo-name small{display:block;font-weight:650;font-size:9px;letter-spacing:.19em;margin-top:7px;color:var(--pro-muted)}.pro-navigation{display:flex;align-items:center;gap:24px;margin-left:auto}.pro-navigation a{font-size:13px;font-weight:650;white-space:nowrap;transition:opacity .2s}.pro-navigation a:hover{opacity:.6}.pro-header-cta,.pro-button{display:inline-flex;align-items:center;justify-content:center;gap:22px;background:var(--pro-accent);color:#fff!important;border-radius:var(--pro-radius);font-size:13px;letter-spacing:.01em;font-weight:750;padding:16px 24px;white-space:nowrap;transition:transform .2s,background .2s}.pro-header-cta:hover,.pro-button:hover{transform:translateY(-2px)}.pro-mobile-toggle{display:none}.pro-mega{display:flex;align-items:center;gap:22px;min-height:37px;border-top:1px solid var(--pro-border);padding-block:10px;font-size:11px}.pro-mega>span{font-weight:750;color:var(--pro-muted);white-space:nowrap}.pro-mega>div{display:flex;gap:16px;overflow:auto;scrollbar-width:none}.pro-mega a{white-space:nowrap;font-weight:650}.pro-search-link{font-size:19px!important}.pro-home-hero{position:relative;overflow:hidden}.pro-home-grid{display:grid;grid-template-columns:1.02fr .98fr;align-items:center;gap:5%;min-height:580px;padding-top:85px;padding-bottom:85px}.pro-hero-copy{position:relative;z-index:2}.pro-hero-copy h1{font-size:clamp(48px,6.2vw,91px);line-height:1.03;margin:20px 0 25px;max-width:730px}.pro-hero-copy h1 em{font-style:normal;display:block;color:var(--pro-accent)}.pro-hero-copy p{font-size:17px;max-width:495px;color:var(--pro-muted)}.pro-hero-actions{display:flex;align-items:center;flex-wrap:wrap;gap:25px;margin-top:34px}.pro-outline-button{font-weight:700;border-bottom:1px solid currentColor;padding:12px 0}.pro-hero-art{width:100%;height:435px;position:relative;overflow:hidden;border-radius:var(--pro-radius)}.pro-hero-art>img{width:100%;height:100%;object-fit:cover}.pro-hero-bottom{display:flex;justify-content:space-between;font-size:10px;letter-spacing:.18em;padding-block:22px;border-top:1px solid #ffffff32}.pro-section{padding:106px 0}.pro-section-head{display:flex;align-items:end;justify-content:space-between;gap:25px;margin-bottom:34px}.pro-section-head h2{font-size:clamp(36px,4vw,57px);margin:12px 0 0}.pro-text-link{font-size:13px;font-weight:750;display:inline-flex;gap:18px;align-items:center;border-bottom:1px solid;padding-bottom:7px;color:var(--pro-accent)}.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}.pro-card{display:block;min-width:0;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);overflow:hidden;border-radius:var(--pro-radius);transition:transform .25s,box-shadow .25s}.pro-card:hover{transform:translateY(-6px);box-shadow:0 24px 45px #0f25431b}.pro-card-cover{width:100%;height:244px;overflow:hidden;background:var(--pro-surface)}.pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .5s}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-art{width:100%;height:100%;overflow:hidden;display:grid;place-items:center;position:relative;background:linear-gradient(130deg,#d8e1ed,#f3f6f9)}.pro-art-ring{width:55%;aspect-ratio:1;border:28px solid #ffffff5f;border-radius:50%;box-shadow:0 0 0 42px #ffffff30;transform:rotate(-25deg)}.pro-art-center{position:absolute;font-family:var(--pro-headings);font-weight:800;font-size:76px;letter-spacing:-.08em;color:#45647d90}.pro-art-label{position:absolute;bottom:16px;left:18px;letter-spacing:.16em;font-size:9px;color:#435773}.pro-card-body{padding:25px 25px 28px}.pro-card-meta{display:flex;justify-content:space-between;gap:20px;font-size:10px;font-weight:750;letter-spacing:.1em;text-transform:uppercase;color:var(--pro-accent)}.pro-card h3{font-size:23px;margin:14px 0 9px}.pro-card p{font-size:13px;color:var(--pro-muted);margin-bottom:24px;min-height:44px}.pro-card-link{font-size:12px;font-weight:800;display:flex;justify-content:space-between;color:var(--pro-text);padding-top:14px;border-top:1px solid var(--pro-border)}.pro-empty{grid-column:1/-1;padding:45px;border:1px dashed var(--pro-border);background:var(--pro-surface);color:var(--pro-muted)}.pro-statement{background:var(--pro-surface)}.pro-statement-layout{display:grid;grid-template-columns:1fr 2fr;gap:60px}.pro-statement h2{font-size:clamp(36px,4.5vw,63px);max-width:760px}.pro-statement p{max-width:520px;color:var(--pro-muted)}.pro-brand-pills{display:flex;flex-wrap:wrap;gap:12px}.pro-brand-pills a{padding:18px 28px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);font-weight:750}.pro-brand-pills a:hover{border-color:var(--pro-accent)}.pro-inner-hero{background:var(--pro-surface);padding:92px 0 65px;position:relative;overflow:hidden}.pro-inner-hero h1{max-width:870px;font-size:clamp(43px,6vw,85px);margin:18px 0}.pro-inner-hero p{max-width:650px;color:var(--pro-muted);font-size:17px}.pro-breadcrumb{font-size:12px;color:var(--pro-muted);margin-top:25px;display:flex;gap:12px;align-items:center;flex-wrap:wrap}.pro-breadcrumb strong{color:var(--pro-text)}.pro-list-section{padding:65px 0 110px}.pro-filter-panel{display:grid;grid-template-columns:minmax(220px,2fr) 1fr 1fr auto;align-items:end;gap:14px;margin-bottom:35px}.pro-filter-panel label>span{display:block;font-size:11px;margin-bottom:8px;color:var(--pro-muted);font-weight:750}.pro-filter-panel input,.pro-filter-panel select{display:block;width:100%;padding:14px 15px;border-radius:var(--pro-radius);border:1px solid var(--pro-border);background:var(--pro-background);color:var(--pro-text);outline-offset:2px}.pro-filter-count{font-size:12px;padding-bottom:17px;color:var(--pro-muted)}.pro-filter-scope .pro-card[hidden]{display:none}.pro-product-layout{padding-top:38px}.pro-product-layout .pro-breadcrumb{margin-bottom:38px}.pro-product-hero{display:grid;grid-template-columns:1.18fr .82fr;gap:6%;align-items:center}.pro-product-media{height:460px;overflow:hidden;background:var(--pro-surface);border-radius:var(--pro-radius)}.pro-product-media .pro-photo{height:100%}.pro-product-summary h1{font-size:clamp(45px,5vw,72px);margin:16px 0}.pro-product-summary p{font-size:16px;color:var(--pro-muted)}.pro-product-brand{font-weight:700;color:var(--pro-text)!important}.pro-detail-layout{border-top:1px solid var(--pro-border);display:grid;grid-template-columns:1fr 1fr;gap:8%;margin-top:80px;padding-top:55px}.pro-detail-layout h2{font-size:32px;margin-top:13px}.pro-specs{margin-top:25px}.pro-specs>div{display:grid;grid-template-columns:1fr 1fr;gap:22px;padding:16px 0;border-bottom:1px solid var(--pro-border)}.pro-specs span{color:var(--pro-muted)}.pro-rich{line-height:1.85;font-size:16px;overflow-wrap:anywhere}.pro-rich img{border-radius:var(--pro-radius);margin:25px 0}.pro-rich a{text-decoration:underline}.pro-rich h2{margin-top:30px}.pro-lead{font-size:20px;color:var(--pro-muted)}.pro-brand-hero-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:7%;align-items:center}.pro-brand-media{height:300px;overflow:hidden;border-radius:var(--pro-radius)}.pro-article-hero{text-align:center;padding:110px 0 75px;background:var(--pro-surface)}.pro-article-width{max-width:900px}.pro-article-hero h1{font-size:clamp(40px,5vw,75px);margin:15px 0 25px}.pro-article-hero p{color:var(--pro-muted);font-size:18px}.pro-article-hero time{font-size:12px;color:var(--pro-muted)}.pro-article-content{max-width:930px;padding-top:70px;padding-bottom:90px}.pro-article-cover{width:100%;max-height:530px;object-fit:cover;margin-bottom:40px;border-radius:var(--pro-radius)}.pro-article-content>.pro-text-link{margin-top:35px}.pro-case-detail{max-width:1020px}.pro-case-detail>img{width:100%;max-height:620px;object-fit:cover}.pro-case-meta{display:flex;gap:35px;margin:35px 0;color:var(--pro-muted);font-size:13px}.pro-case-meta strong{display:block;color:var(--pro-text);font-size:18px;margin-top:7px}.pro-info-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:12%}.pro-info-grid h2{font-size:clamp(31px,4.5vw,54px);margin-top:15px}.pro-contact-email{font-size:20px;color:var(--pro-accent);font-weight:750;word-break:break-word}.pro-info-photo{max-height:450px;object-fit:cover;margin-top:35px;border-radius:var(--pro-radius)}.pro-custom-content .sf-section{background:transparent}.pro-custom-content .sf-section:nth-of-type(even){background:var(--pro-surface)}.pro-end-cta{padding:76px 0;background:var(--pro-hero);color:#fff}.pro-end-cta .pro-wrap{display:flex;align-items:center;justify-content:space-between;gap:40px}.pro-end-cta h2{font-size:clamp(32px,4vw,53px);margin:13px 0}.pro-end-cta p{color:#c5c9d1}.pro-end-cta a{background:var(--pro-accent);color:#fff;font-weight:800;padding:19px 30px;border-radius:var(--pro-radius);white-space:nowrap}.pro-404{text-align:center;padding-block:125px}.pro-404>span{font-size:12px;letter-spacing:.3em}.pro-404 h1{font-size:clamp(150px,20vw,320px);margin:10px 0;letter-spacing:-.08em}.pro-404 p{font-size:18px;color:var(--pro-muted)}.pro-404 a{margin-top:22px}.pro-footer{background:#15212d;color:#fff;padding:75px 0 22px}.pro-footer-top{display:grid;grid-template-columns:1.45fr 1fr 1fr;gap:55px}.pro-footer h2{font-size:32px;margin:12px 0}.pro-footer p{max-width:350px;font-size:13px;color:#afbbc8}.pro-footer .pro-eyebrow{color:#b6c3d2}.pro-footer-links ul{margin:0;padding:0;list-style:none;display:grid;grid-template-columns:1fr 1fr;gap:15px}.pro-footer-links a{font-size:13px;color:#d6e0e8}.pro-footer-bottom{border-top:1px solid #ffffff29;margin-top:60px;padding-top:20px;font-size:10px;letter-spacing:.12em;color:#adb9c3;display:flex;justify-content:space-between}.pro-anchor{display:block;height:0}.pro-category-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.pro-category-tiles a{padding:35px 28px;min-height:155px;background:var(--pro-surface);display:flex;flex-direction:column;justify-content:space-between}.pro-category-tiles span{font-size:11px;color:var(--pro-muted)}.pro-category-tiles strong{font-size:24px}.pro-category-tiles b{align-self:end}.sf-site-head .sf-head-inner,.sf-site-head .sf-primary-links{display:none}
+@media(max-width:1100px){.pro-head-inner{gap:17px}.pro-navigation{gap:14px}.pro-navigation a{font-size:12px}.pro-home-grid{min-height:500px}.pro-hero-copy h1{font-size:60px}.pro-hero-art{height:350px}}
+@media(max-width:860px){.pro-wrap{padding-left:22px;padding-right:22px}.pro-utility{display:none}.pro-head-inner{min-height:70px;flex-wrap:wrap;gap:12px}.pro-mobile-toggle{display:block;margin-left:auto;border:1px solid var(--pro-border);padding:8px 12px;background:transparent;color:var(--pro-text);border-radius:5px}.pro-navigation{display:none;order:10;width:100%;flex-direction:column;align-items:stretch;gap:0;padding-bottom:12px}.pro-navigation.is-open{display:flex}.pro-navigation a{padding:11px 2px;border-bottom:1px solid var(--pro-border)}.pro-header-cta{display:none}.pro-mega{display:none}.pro-home-grid{grid-template-columns:1fr;gap:35px;padding-top:60px;padding-bottom:60px}.pro-hero-art{height:350px}.pro-hero-copy h1{font-size:clamp(45px,9vw,73px)}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-brand-hero-grid{grid-template-columns:1fr;gap:35px}.pro-product-media{height:380px}.pro-filter-panel{grid-template-columns:1fr 1fr}.pro-filter-panel label:first-child{grid-column:1/-1}.pro-end-cta .pro-wrap{align-items:start;flex-direction:column}.pro-section{padding:72px 0}.pro-footer-top{grid-template-columns:1fr 1fr}.pro-footer-top>div:first-child{grid-column:1/-1}.pro-category-tiles{grid-template-columns:repeat(2,1fr)}}
+@media(max-width:560px){.pro-wrap{padding-left:18px;padding-right:18px}.pro-logo-name{font-size:15px}.pro-hero-copy h1{font-size:43px}.pro-hero-art{height:255px}.pro-hero-bottom{display:none}.pro-home-grid{min-height:0}.pro-grid{grid-template-columns:1fr}.pro-section-head{align-items:start;flex-direction:column}.pro-section-head h2{font-size:37px}.pro-card-cover{height:230px}.pro-filter-panel{grid-template-columns:1fr}.pro-filter-panel label:first-child{grid-column:auto}.pro-filter-count{padding-bottom:4px}.pro-statement-layout,.pro-footer-top{grid-template-columns:1fr;gap:25px}.pro-footer-top>div:first-child{grid-column:auto}.pro-footer-bottom{flex-direction:column;gap:8px}.pro-inner-hero{padding:60px 0}.pro-product-media{height:270px}.pro-product-summary h1{font-size:43px}.pro-category-tiles{grid-template-columns:1fr}.pro-end-cta h2{font-size:38px}.pro-minimal-meta{display:block!important}}
+
+/* V2 compatibility stylesheet hides direct headers/footers; full themes restore their own. */
+body>header.sf-site-head.pro-header,body>footer.sf-site-foot.pro-footer{display:block!important}
+
+/* Accessible desktop mega-navigation and mobile disclosure */
+.pro-catalog-toggle{font-size:12px;font-weight:750;border:1px solid var(--pro-border);background:transparent;color:inherit;border-radius:var(--pro-radius);padding:10px 14px;white-space:nowrap}
+.pro-mega[hidden]{display:none!important}.pro-mega.is-open{display:flex}.pro-mega>div{padding-block:3px}
+@media(max-width:1060px){.pro-catalog-toggle{display:none}}
+@media(max-width:860px){.pro-mega.is-open{display:block!important}.pro-mega.is-open>div{display:flex;flex-wrap:wrap;gap:14px;padding:12px 0}.pro-mega>span{white-space:normal}}
+
+/* V4: responsive nested navigation, gallery and opt-in inquiry integration */
+.pro-nav-group{position:relative;display:flex;align-items:center;gap:3px}.pro-nav-disclose{border:0;background:none;color:inherit;font-size:12px;padding:8px}.pro-nav-children[hidden]{display:none!important}.pro-nav-children{position:absolute;left:0;top:100%;z-index:45;min-width:230px;max-width:360px;padding:10px;background:var(--pro-background);border:1px solid var(--pro-border);box-shadow:0 18px 35px #0002;border-radius:var(--pro-radius);display:flex;flex-direction:column}.pro-nav-children a{padding:10px 12px;white-space:nowrap}.pro-nav-children .pro-nav-leaf{padding-left:28px;color:var(--pro-muted)}
+.pro-product-gallery{min-width:0}.pro-gallery-thumbs{display:flex;gap:8px;margin-top:12px;overflow:auto;scrollbar-width:thin;padding-bottom:5px}.pro-gallery-thumbs button{flex:0 0 86px;height:67px;border:2px solid transparent;padding:3px;border-radius:8px;background:var(--pro-surface)}.pro-gallery-thumbs button:focus-visible,.pro-gallery-thumbs button[aria-pressed=true]{border-color:var(--pro-accent)}.pro-gallery-thumbs img{width:100%;height:100%;object-fit:cover;border-radius:4px}.pro-gallery-lightbox{position:fixed;inset:0;background:rgba(10,16,30,.92);z-index:1000;display:grid;place-items:center;padding:6vw}.pro-gallery-lightbox[hidden]{display:none}.pro-gallery-lightbox img{max-width:92vw;max-height:80vh;object-fit:contain}.pro-gallery-lightbox button{position:absolute;right:25px;top:25px;border:1px solid #fff8;background:#222;color:#fff;padding:10px 15px;border-radius:5px}
+.pro-inquiry-wrap{display:grid;grid-template-columns:1fr 1fr;gap:70px}.pro-inquiry{display:grid;gap:17px}.pro-inquiry label{display:grid;gap:7px;font-weight:650}.pro-inquiry input,.pro-inquiry textarea{padding:13px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);background:var(--pro-background);color:var(--pro-text);width:100%}.pro-inquiry textarea{min-height:140px}.pro-inquiry .pro-honeypot{position:absolute;left:-10000px;width:1px;height:1px}.pro-inquiry p{font-size:12px;color:var(--pro-muted)}
+@media(max-width:860px){.pro-nav-group{display:block}.pro-nav-children{position:static;max-width:none;width:100%;box-shadow:none;border:0;border-left:2px solid var(--pro-accent);padding-left:8px}.pro-nav-group>a{display:inline-block}.pro-inquiry-wrap{grid-template-columns:1fr;gap:20px}}
diff --git a/examples/mobile-showcase/corporate/assets/pro.css b/examples/mobile-showcase/corporate/assets/pro.css
new file mode 100644
index 0000000..acb5e27
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/pro.css
@@ -0,0 +1,7 @@
+:root{--pro-text:#292d50;--pro-muted:#737793;--pro-border:#e1e2ed;--pro-background:#fff;--pro-surface:#f3f3f9;--pro-accent:#6054c5;--pro-hero:#272b61;--pro-radius:18px;--pro-headings:system-ui,Arial,sans-serif;--pro-wide:1250px}
+.pro-utility{background:#343771;color:#f6f6ff}.pro-header{background:#fff}.pro-logo-symbol{color:#655dc0;font-size:35px}.pro-logo-name{letter-spacing:-.07em}.pro-navigation a{font-weight:600}.pro-header-cta{background:#4e4aa4;border-radius:999px}.pro-corporate-hero{background:linear-gradient(135deg,#fafafe 0%,#f0f0fc 55%,#e9e8fa 100%)}.pro-corporate-hero .pro-home-grid{min-height:630px}.pro-corporate-hero .pro-hero-copy h1{font-weight:810}.pro-corporate-hero .pro-hero-copy h1 em{color:#6763c3}.pro-corporate-hero .pro-hero-art{border-radius:25px 160px 25px 160px;background:#dad9f7;box-shadow:25px 35px 0 #c3bde655}.pro-corporate-shape{position:absolute;inset:0;display:grid;place-items:center;background:radial-gradient(circle at 70% 40%,#8079dc 0%,#4d4aab 55%,#333576 100%)}.pro-corporate-shape:before{content:'';position:absolute;width:62%;aspect-ratio:1;border:65px solid #a19dfb77;border-radius:50%;left:-15%;top:-27%}.pro-corporate-shape:after{content:'';position:absolute;width:53%;aspect-ratio:1;border:50px solid #9994e955;border-radius:50%;right:-16%;bottom:-33%}.pro-corporate-shape span{z-index:1;color:#fff;font-size:clamp(26px,3vw,42px);font-weight:850;text-align:center;line-height:1.15;letter-spacing:-.06em}.pro-featured{background:#fff}.pro-featured .pro-card{border-color:#e6e5f0;box-shadow:0 18px 35px #30327a0e}.pro-card-cover{background:#e9eaf7}.pro-art-service{background:linear-gradient(120deg,#d5d2f8,#e9e8fa)}.pro-art-center{color:#716eb69c}.pro-statement{background:#f3f2fa;color:#2a2d50}.pro-statement h2{max-width:820px}.pro-statement .pro-text-link{color:#5e56bc}.pro-case-section{background:#f9f9fc}.pro-inner-hero{background:linear-gradient(140deg,#f3f3fc,#dfdff5)}.pro-inner-hero:after{content:'';position:absolute;right:-5%;bottom:-55%;width:400px;aspect-ratio:1;border:80px solid #8780e41a;border-radius:50%}.pro-article-hero{background:#eeedfa}.pro-footer{background:#242750}.pro-end-cta{background:#34366e}.pro-end-cta a{background:#fff;color:#383776!important}.pro-404 h1{color:#8a84d9}
+
+/* SiteForge V5 commercial signature */
+.pro-corporate-hero .pro-hero-art{background-image:url("./visual/hero.svg")!important;background-position:center;background-size:cover;}
+
+.pro-corporate-hero .pro-hero-art{background-color:#f5f4ff;border-radius:38px 120px 40px 120px;overflow:hidden;box-shadow:30px 35px 70px #565baa21}.pro-corporate-hero .pro-hero-art .pro-corporate-shape{opacity:0}.pro-card{border:1px solid #e5e2f2;border-radius:20px;box-shadow:0 10px 40px #3d3b8110}.pro-card-cover{border-radius:20px 20px 0 0;background:#edeafa}.pro-art{background:radial-gradient(circle at 75% 32%,#dad5f7,#eceafa 74%);color:#7772c1}.pro-featured{background:#fff}.pro-statement{background:linear-gradient(120deg,#f2f1fc,#e9e7fb)}.pro-brand-section{background:#f6f5fb}.pro-footer{background:#232658;color:#fff}.pro-header-cta{border-radius:100px}.pro-section-head h2{font-weight:760}
diff --git a/examples/mobile-showcase/corporate/assets/pro.js b/examples/mobile-showcase/corporate/assets/pro.js
new file mode 100644
index 0000000..9cf100d
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/pro.js
@@ -0,0 +1,72 @@
+'use strict';(()=>{
+ const menu=document.querySelector('[data-pro-menu]'); const nav=document.querySelector('#pro-navigation');
+ if(menu&&nav){menu.addEventListener('click',()=>{const open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));nav.classList.toggle('is-open',open);menu.setAttribute('aria-label',open?'关闭导航':'打开导航');});}
+ // Drawer navigation. The same markup is used across the four professional themes.
+ const mobileNav=document.querySelector('#pro-navigation');
+ let drawerBackdrop=null,drawerClose=null,returnFocus=null;
+ if(menu&&mobileNav){
+ const originalParent=mobileNav.parentNode,originalNext=mobileNav.nextSibling;
+ const heading=document.createElement('div');heading.className='sf-drawer-header';
+ const title=document.createElement('strong');title.textContent='网站导航';
+ drawerClose=document.createElement('button');drawerClose.type='button';drawerClose.className='sf-drawer-close';drawerClose.textContent='×';drawerClose.setAttribute('aria-label','关闭网站导航');
+ heading.append(title,drawerClose);mobileNav.prepend(heading);
+ drawerBackdrop=document.createElement('div');drawerBackdrop.className='sf-mobile-drawer-backdrop';drawerBackdrop.hidden=false;document.body.append(drawerBackdrop);
+ function setDrawer(open,focusBack=true){
+ const isMobile=window.matchMedia('(max-width:1024px)').matches;
+ open=Boolean(open&&isMobile);
+ if(open){returnFocus=document.activeElement;document.body.append(mobileNav);}
+ else if(mobileNav.parentNode!==originalParent){if(originalNext&&originalNext.parentNode===originalParent)originalParent.insertBefore(mobileNav,originalNext);else originalParent.append(mobileNav);}
+ mobileNav.classList.toggle('is-open',open);drawerBackdrop.classList.toggle('is-open',open);
+ document.body.classList.toggle('sf-drawer-open',open);
+ menu.setAttribute('aria-expanded',String(open));menu.setAttribute('aria-label',open?'关闭导航':'打开导航');
+ mobileNav.setAttribute('aria-hidden',String(!open&&isMobile));
+ if('inert' in mobileNav)mobileNav.inert=!open&&isMobile;
+ if(open)drawerClose.focus();
+ else if(focusBack&&returnFocus instanceof HTMLElement){returnFocus.focus();returnFocus=null;}
+ }
+ // Replace the old inline-expansion listener: use capture to stop the old listener.
+ menu.addEventListener('click',e=>{if(!window.matchMedia('(max-width:1024px)').matches)return;e.stopImmediatePropagation();setDrawer(!mobileNav.classList.contains('is-open'));},true);
+ drawerClose.addEventListener('click',()=>setDrawer(false));
+ drawerBackdrop.addEventListener('click',()=>setDrawer(false));
+ document.querySelectorAll('[data-mobile-open-menu]').forEach(b=>b.addEventListener('click',()=>setDrawer(true)));
+ mobileNav.addEventListener('click',e=>{if(e.target.closest('a'))setDrawer(false,false)});
+ document.addEventListener('keydown',e=>{
+ if(e.key==='Escape'&&mobileNav.classList.contains('is-open')){e.preventDefault();setDrawer(false);}
+ if(e.key!=='Tab'||!mobileNav.classList.contains('is-open'))return;
+ const els=[...mobileNav.querySelectorAll('button:not([disabled]),a[href]')].filter(el=>el.getClientRects().length&&!el.closest('[hidden]'));
+ if(!els.length)return;const first=els[0],last=els[els.length-1];
+ if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
+ else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
+ });
+ window.matchMedia('(max-width:1024px)').addEventListener('change',()=>setDrawer(false,false));
+ if('inert' in mobileNav&&window.matchMedia('(max-width:1024px)').matches)mobileNav.inert=true;
+ // Do not strand catalogue links when the old mobile mega-button is hidden.
+ const megaLinks=document.querySelectorAll('[data-pro-mega] a');
+ if(megaLinks.length){const wrap=document.createElement('div');wrap.className='sf-drawer-extra';const h=document.createElement('strong');h.textContent='更多分类与系列';wrap.append(h);
+ [...megaLinks].slice(0,20).forEach(a=>{const clone=a.cloneNode(true);wrap.append(clone);});mobileNav.append(wrap);}
+ }
+ const tabbar=document.querySelector('[data-mobile-tabs]');
+ if(tabbar){tabbar.style.setProperty('--sf-mobile-tab-count',String(tabbar.children.length));}
+ const megaButton=document.querySelector('[data-pro-catalog]'), mega=document.querySelector('[data-pro-mega]');
+ if(megaButton&&mega){function setMega(open){mega.hidden=!open;mega.classList.toggle('is-open',open);megaButton.setAttribute('aria-expanded',String(open));}
+ megaButton.addEventListener('click',()=>setMega(mega.hidden));
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')setMega(false);});
+ document.addEventListener('click',e=>{if(!mega.hidden&&!mega.contains(e.target)&&!megaButton.contains(e.target))setMega(false);});
+ }
+ document.querySelectorAll('.pro-nav-disclose').forEach(button=>{button.addEventListener('click',()=>{let panel=button.parentElement.querySelector('.pro-nav-children');let visible=button.getAttribute('aria-expanded')==='true';button.setAttribute('aria-expanded',String(!visible));panel.hidden=visible;});});
+ document.querySelectorAll('.pro-gallery-thumbs').forEach(container=>{const main=container.parentElement.querySelector('.pro-product-media img');const buttons=[...container.querySelectorAll('[data-gallery-image]')];
+ buttons.forEach(b=>{b.addEventListener('click',()=>{buttons.forEach(x=>x.setAttribute('aria-pressed',String(x===b)));if(main){main.src=b.dataset.galleryImage;main.alt=b.querySelector('img')?.alt||'';}});});
+ if(!main)return;main.tabIndex=0;main.style.cursor='zoom-in';const open=()=>{const pane=document.createElement('div');pane.className='pro-gallery-lightbox';pane.setAttribute('role','dialog');pane.setAttribute('aria-label','产品大图预览');let img=document.createElement('img');img.src=main.src;img.alt=main.alt;const close=document.createElement('button');close.type='button';close.textContent='关闭 ×';pane.append(img,close);document.body.append(pane);const done=()=>{pane.remove();document.removeEventListener('keydown',esc);main.focus();};const esc=e=>{if(e.key==='Escape')done();};document.addEventListener('keydown',esc);close.addEventListener('click',done);pane.addEventListener('click',e=>{if(e.target===pane)done();});close.focus();};main.addEventListener('click',open);main.addEventListener('keydown',e=>{if(e.key==='Enter')open();});
+ });
+ document.querySelectorAll('.pro-filter-scope').forEach(scope=>{
+ const panel=scope.previousElementSibling;if(!panel||!panel.classList.contains('pro-filter-panel'))return;
+ const cards=Array.from(scope.querySelectorAll('.pro-card'));
+ const blank=document.createElement('div');blank.className='pro-filter-empty';blank.hidden=true;blank.textContent='没有找到符合条件的内容,请调整关键词或筛选条件。';scope.append(blank);
+ const q=panel.querySelector('[data-pro-filter-search]'),b=panel.querySelector('[data-pro-filter-brand]'),c=panel.querySelector('[data-pro-filter-category]'),count=panel.querySelector('[data-pro-filter-count]');
+ function options(sel,key){const vals=[...new Set(cards.map(x=>x.dataset[key]||'').filter(Boolean))].sort();vals.forEach(x=>sel.add(new Option(x,x)));if(!vals.length)sel.closest('label').hidden=true;}
+ options(b,'brand');options(c,'category');
+ function run(){const str=q.value.trim().toLocaleLowerCase();let visible=0;
+ cards.forEach(el=>{const hit=el.textContent.toLocaleLowerCase().includes(str)&&(!b.value||el.dataset.brand===b.value)&&(!c.value||el.dataset.category===c.value);el.hidden=!hit;visible+=Number(hit);});count.textContent=`共 ${visible} 条`;blank.hidden=visible!==0;}
+ [q,b,c].forEach(x=>x.addEventListener(x===q?'input':'change',run));run();
+ });
+})();
diff --git a/examples/mobile-showcase/corporate/assets/search-index.json b/examples/mobile-showcase/corporate/assets/search-index.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/search-index.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/examples/mobile-showcase/corporate/assets/site-runtime.js b/examples/mobile-showcase/corporate/assets/site-runtime.js
new file mode 100644
index 0000000..e4715e8
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/site-runtime.js
@@ -0,0 +1,31 @@
+'use strict';
+(()=>{
+ const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
+ const menu=$('.sf-menu-toggle');if(menu){menu.addEventListener('click',()=>{let open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));$('#sf-primary-links')?.classList.toggle('is-open',open);});}
+ // Filter actual static content cards, no API is needed at runtime.
+ $$('.sf-collection').forEach(grid=>{
+ const cards=$$('.sf-item',grid);
+ if(cards.length<2)return;
+ const toolbar=document.createElement('div');toolbar.className='sf-filters';
+ const search=document.createElement('input');search.type='search';search.placeholder='筛选当前列表';search.setAttribute('aria-label','筛选当前列表');
+ const status=document.createElement('span');status.setAttribute('aria-live','polite');status.style.fontSize='12px';
+ const brand=document.createElement('select');brand.setAttribute('aria-label','按品牌筛选');
+ const category=document.createElement('select');category.setAttribute('aria-label','按分类筛选');
+ function fill(el,field,all){el.append(new Option(all,''));const vals=[...new Set(cards.map(x=>x.dataset[field]||'').filter(Boolean))].sort();vals.forEach(v=>el.append(new Option(v,v)));if(!vals.length)el.hidden=true;}
+ fill(brand,'brand','全部品牌');fill(category,'category','全部分类');
+ toolbar.append(search,brand,category,status);grid.parentNode.insertBefore(toolbar,grid);
+ function apply(){let val=search.value.trim().toLocaleLowerCase();let visible=0;cards.forEach(card=>{let hit=card.textContent.toLocaleLowerCase().includes(val)&&(!brand.value||card.dataset.brand===brand.value)&&(!category.value||card.dataset.category===category.value);card.hidden=!hit;if(hit)visible++;});status.textContent=`共 ${visible} 项`;}
+ search.addEventListener('input',apply);brand.addEventListener('change',apply);category.addEventListener('change',apply);apply();
+ });
+ const input=$('#sf-search'),root=$('#sf-search-results'),count=$('#sf-search-count');
+ if(input&&root){
+ let data=[];const params=new URLSearchParams(location.search);input.value=params.get('q')||'';
+ let debounce=null;
+ const render=()=>{let q=input.value.trim().toLocaleLowerCase();let found=q?data.filter(d=>`${d.title} ${d.summary} ${d.brand} ${d.type}`.toLocaleLowerCase().includes(q)).slice(0,100):data.slice(0,30);
+ root.replaceChildren();count.textContent=`${found.length} 条结果${!q?'(显示前30条)':''}`;
+ found.forEach(d=>{let article=document.createElement('article'),a=document.createElement('a'),small=document.createElement('small'),p=document.createElement('p');article.className='sf-search-result';a.textContent=d.title;a.href=d.url.replace(/^\//,'');small.textContent=d.type;p.textContent=d.summary;article.append(a,small,p);root.append(article);});
+ };const base=document.querySelector('base')?.href||location.href;
+ fetch(new URL('search-index.json',base)).then(r=>{if(!r.ok)throw Error('index unavailable');return r.json();}).then(v=>{data=v;render()}).catch(()=>{count.textContent='搜索索引加载失败,请检查站点文件部署路径';});
+ input.addEventListener('input',()=>{clearTimeout(debounce);debounce=setTimeout(render,100)});
+ }
+})();
diff --git a/examples/mobile-showcase/corporate/assets/theme.css b/examples/mobile-showcase/corporate/assets/theme.css
new file mode 100644
index 0000000..a081c46
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/theme.css
@@ -0,0 +1 @@
+:root{--ink:#152040;--blue:#283d8c;--purple:#6558b9;--light:#f6f7fb}*{box-sizing:border-box}body{margin:0;background:white;color:#172347;font-family:Inter,"Segoe UI",Arial,sans-serif;line-height:1.65}a{color:inherit;text-decoration:none}img{max-width:100%}.shell{width:min(1200px,89%);margin:auto}.announcement{height:35px;background:#262d70;color:white;display:flex;justify-content:center;align-items:center;gap:12px;font-size:10px;letter-spacing:1.3px}.announcement span{color:#b8d6fd}.header{height:94px;display:flex;align-items:center;justify-content:space-between;gap:20px}.logo{font-size:23px;font-weight:850;letter-spacing:-1px;display:flex;align-items:center;gap:12px}.logo-box{display:grid;place-items:center;width:43px;height:43px;color:#fff;font-size:25px;background:linear-gradient(135deg,#5b6ce0,#7c4dc3);border-radius:13px}.header nav{display:flex;gap:28px;font-size:13px;font-weight:600;flex-wrap:wrap}.header nav a:hover{color:#585cca}.nav-btn{background:#243475;color:#fff;padding:13px 20px;border-radius:8px;font-size:12px;font-weight:700}.nav-btn span{margin-left:18px}.hero{background:linear-gradient(120deg,#f5f7ff,#ebeeff 45%,#f8f6fc);overflow:hidden}.hero-grid{min-height:580px;display:grid;grid-template-columns:53% 47%;align-items:center}.hero-text{padding:70px 0}.pill{font-size:10px;letter-spacing:1.5px;color:#5549ac;background:#e5e5fa;border-radius:50px;display:inline-block;padding:8px 15px;font-weight:750}.hero h1{font-size:clamp(52px,6.2vw,87px);line-height:1.06;letter-spacing:-4px;margin:32px 0 20px;max-width:620px;color:#19265e}.hero p{font-size:18px;line-height:1.8;color:#69718f;max-width:520px}.actions{display:flex;gap:14px;flex-wrap:wrap;margin-top:34px}.primary,.secondary{padding:14px 24px;border-radius:8px;font-size:13px;font-weight:750}.primary{background:#38489e;color:#fff}.secondary{background:white;border:1px solid #d2d7ed}.trusted{font-size:10px;color:#9b9db6;letter-spacing:2px;margin-top:77px}.trusted span{padding:0 9px}.abstract-art{position:relative;height:100%;min-height:440px;display:grid;place-items:center}.abstract-art img{max-height:510px;object-fit:contain;filter:drop-shadow(0 25px 30px #33376620)}.orb{position:absolute;border-radius:50%;filter:drop-shadow(18px 15px 25px #5b69b125)}.orb-main{width:350px;height:350px;background:radial-gradient(circle at 30% 25%,#d9ddff,#8493e4 40%,#3d539e 70%,#1c3079);transform:rotate(-20deg)}.orb-inner{width:250px;height:250px;background:radial-gradient(circle at 30% 35%,#fff9,#a1aff366 38%,#5c68aa9c 65%);border:1px solid #ffffffc4;transform:translate(80px,60px)}.orb-small{width:93px;height:93px;background:linear-gradient(125deg,#c59fff,#6851a6);transform:translate(-155px,-155px)}.art-caption{position:relative;z-index:3;color:#fff;font-size:15px;line-height:1.4;letter-spacing:3px;text-align:center;font-weight:800;transform:translate(5px,20px)}.inner-hero{padding:85px 0;background:#f0f2ff}.inner-hero h1{font-size:clamp(40px,6vw,70px);letter-spacing:-2px;line-height:1.1;margin:20px 0}.inner-hero p{color:#69748c}.content{padding:60px 0 100px}.nudge{color:#6b6c95;font-size:10px;letter-spacing:2px;font-weight:750}.nudge span{color:#7b58d0;margin-right:7px}.section-header{margin:62px 0 25px;display:flex;justify-content:space-between;align-items:end}.section-header h2{font-size:40px;letter-spacing:-1.5px;margin:7px 0}.section-header>span{font-size:12px;color:#858eaa}.kicker{font-size:11px;color:#685dba;letter-spacing:2px}.tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}.tile{border:1px solid #eceef8;border-radius:15px;overflow:hidden;transition:box-shadow .2s,transform .2s}.tile:hover{box-shadow:0 18px 32px #323c7c18;transform:translateY(-4px)}.tile-image{height:195px;background:linear-gradient(130deg,#cbd3f5,#929de0);display:grid;place-items:center}.tile-image img{width:100%;height:100%;object-fit:contain}.tile-image>span{font-size:85px;color:#ffffffa8}.tile-info{padding:25px}.tile-info>span{font-size:10px;color:#7d6eba;letter-spacing:1.5px}.tile-info h3{font-size:23px;margin:9px 0}.tile-info p{font-size:13px;color:#7c859b;min-height:40px}.tile-info b{font-size:12px;color:#4e51a3}.rich-content{margin:30px 0}.section{padding:40px 0}.feature-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:15px}.feature{padding:27px;background:#f5f6fd;border-radius:10px}.feature-mark{color:#6e68c7}.stat strong{display:block;font-size:38px}.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}.cta{padding:50px;background:#252d73;color:white;border-radius:18px}.cta p{color:#c2c6ec}.big-cta{background:#293788;color:white;border-radius:20px;padding:60px;margin-top:90px;position:relative;overflow:hidden}.big-cta:after{content:'✦';position:absolute;right:-15px;top:-110px;font-size:410px;opacity:.10}.big-cta span{font-size:10px;letter-spacing:2px}.big-cta h2{font-size:45px;line-height:1.2;letter-spacing:-2px}.big-cta a{display:inline-block;background:white;color:#313e9a;padding:14px 20px;border-radius:9px;font-size:12px;font-weight:800}.detail-image{text-align:center;margin:30px 0}.detail-image img{max-height:520px}footer{background:#151c3c;color:white;padding:70px 0 30px}.footer-upper{display:flex;align-items:center;justify-content:space-between}.footer-upper h2{font-size:30px}.footer-upper p{color:#a6acce}.footer-line{border-top:1px solid #424866;padding-top:28px;margin-top:60px;display:flex;justify-content:space-between;color:#a7aed4;font-size:10px;letter-spacing:1.4px}@media(max-width:900px){.header{height:auto;padding:20px 0;flex-wrap:wrap}.header nav{order:3;width:100%}.hero-grid{grid-template-columns:1fr}.abstract-art{min-height:340px}.trusted{margin-top:40px}.tiles{grid-template-columns:repeat(2,1fr)}}@media(max-width:560px){.tiles,.feature-grid,.stats{grid-template-columns:1fr}.hero h1{font-size:54px}.footer-upper,.footer-line,.section-header{align-items:start;flex-direction:column}.announcement{padding:0 8px;font-size:8px}.big-cta h2{font-size:34px}}
diff --git a/examples/mobile-showcase/corporate/assets/visual/hero.svg b/examples/mobile-showcase/corporate/assets/visual/hero.svg
new file mode 100644
index 0000000..f89e148
--- /dev/null
+++ b/examples/mobile-showcase/corporate/assets/visual/hero.svg
@@ -0,0 +1 @@
+
diff --git a/examples/mobile-showcase/corporate/index.html b/examples/mobile-showcase/corporate/index.html
new file mode 100644
index 0000000..ff2e90b
--- /dev/null
+++ b/examples/mobile-showcase/corporate/index.html
@@ -0,0 +1,16 @@
+企业服务跳转至正文
+
+
+
+STRATEGY · PARTNERSHIP · DELIVERY企业服务 IDEAS INTO IMPACT.
连接更可靠的全球业务
+
+ABOUT THE COMPANY以专业连接价值,以合作创造可能。
连接更可靠的全球业务
了解我们 ↗
+
+
+
+LET'S CONNECT从这里开始下一步。
连接更可靠的全球业务
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/corporate/listing.html b/examples/mobile-showcase/corporate/listing.html
new file mode 100644
index 0000000..482f353
--- /dev/null
+++ b/examples/mobile-showcase/corporate/listing.html
@@ -0,0 +1,22 @@
+全部产品跳转至正文
+
+
+
+DISCOVER / COLLECTION全部产品
分类查看产品与解决方案
+
+
+
+LET'S CONNECT从这里开始下一步。
连接更可靠的全球业务
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/corporate/product.html b/examples/mobile-showcase/corporate/product.html
new file mode 100644
index 0000000..778d2b5
--- /dev/null
+++ b/examples/mobile-showcase/corporate/product.html
@@ -0,0 +1,17 @@
+标准系列产品跳转至正文
+
+
+
+
+
PRODUCT DETAILS / EXAMPLE标准系列产品
面向全球客户的精选示范产品
品牌 · EXAMPLE
咨询该产品 ↗
+
+
+
+LET'S CONNECT从这里开始下一步。
连接更可靠的全球业务
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/index.html b/examples/mobile-showcase/index.html
new file mode 100644
index 0000000..eb3e61a
--- /dev/null
+++ b/examples/mobile-showcase/index.html
@@ -0,0 +1,3 @@
+四套主题移动端实际模板预览 | SiteForgeSiteForge · 四套主题手机布局
V8.21.3 真实模板离线演示 · 右上角汉堡菜单打开抽屉,底部 Tabs 仅在手机宽度显示
\ No newline at end of file
diff --git a/examples/mobile-showcase/industrial/article.html b/examples/mobile-showcase/industrial/article.html
new file mode 100644
index 0000000..86fe13c
--- /dev/null
+++ b/examples/mobile-showcase/industrial/article.html
@@ -0,0 +1,13 @@
+企业动态与行业资讯跳转至正文
+
+
+
+LATEST NEWS / INSIGHTS企业动态与行业资讯
了解最新项目与服务进展
项目背景
这是一段用于检查手机端资讯排版的样例正文。适当的字距、行距和图片宽度能够提升移动阅读体验。
← 返回新闻
+
+
+LET'S CONNECT从这里开始下一步。
高性能工程材料与解决方案
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/industrial/assets/blocks.css b/examples/mobile-showcase/industrial/assets/blocks.css
new file mode 100644
index 0000000..9ce1279
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/blocks.css
@@ -0,0 +1,29 @@
+/* Portable SiteForge blocks, loaded with every theme (all styles scoped to sf-). */
+.sf-section{background:var(--sf-bg,transparent);color:var(--sf-fg,inherit);padding:var(--sf-pad,82px) 0;margin:0}.sf-section:nth-of-type(even){background:var(--sf-bg,#f4f6f9)}.sf-container{max-width:1200px;margin:auto;padding:0 32px}.sf-section h1,.sf-section h2,.sf-section h3,.sf-section p{color:inherit;line-height:1.3}.sf-section h1{font-size:clamp(38px,5vw,76px);font-weight:790;letter-spacing:-.052em}.sf-section h2{font-size:clamp(27px,3.2vw,44px);font-weight:760;letter-spacing:-.037em}.sf-kicker{display:block;font-size:12px;letter-spacing:.17em;text-transform:uppercase;font-weight:740;color:var(--accent,#3569d3);margin-bottom:18px}.sf-hero-grid,.sf-image-text{display:grid;grid-template-columns:1.08fr .92fr;gap:68px;align-items:center}.sf-hero-copy p{font-size:18px;line-height:1.75;max-width:520px;opacity:.75}.sf-hero-img,.sf-image-side{overflow:hidden;border-radius:9px;min-height:380px;background:linear-gradient(130deg,#d5e1ee,#eef3f9);display:grid;place-items:center}.sf-hero-img img,.sf-image-side img{width:100%;height:100%;max-height:580px;object-fit:cover}.sf-hero-abstract{font-size:110px;color:#a7bdd4}.sf-button{display:inline-flex;align-items:center;justify-content:center;gap:28px;background:var(--accent,#315ee2);color:white!important;padding:16px 26px;border-radius:6px;font-size:14px;text-decoration:none;font-weight:720;margin-top:24px}.sf-section-head{display:flex;align-items:end;justify-content:space-between;margin-bottom:32px;gap:24px}.sf-section-head h2{margin:0}.sf-section-head>a,.sf-link{color:var(--accent,#3569d3);font-weight:700;text-decoration:none}.sf-prose{font-size:16px;line-height:1.85;max-width:930px}.sf-prose p{line-height:1.85}.sf-features,.sf-collection,.sf-stats{display:grid;gap:20px}.sf-features{grid-template-columns:repeat(3,minmax(0,1fr))}.sf-feature{border:1px solid #e0e6eb;padding:32px 28px;border-radius:8px;background:#fff;color:#253247}.sf-feature>span{font-size:12px;font-weight:800;color:var(--accent,#346add)}.sf-feature h3{font-size:22px;margin:30px 0 8px}.sf-feature p{font-size:14px;line-height:1.7;color:#647181}.sf-stats{grid-template-columns:repeat(4,1fr);padding:40px 0;border-top:1px solid #b7c0cd66;border-bottom:1px solid #b7c0cd66}.sf-stats>div{display:flex;flex-direction:column;gap:8px}.sf-stats strong{font-size:clamp(28px,4vw,53px);letter-spacing:-.04em}.sf-stats span{font-size:12px;opacity:.65;letter-spacing:.04em}.sf-collection{grid-template-columns:repeat(var(--sf-cols,3),minmax(0,1fr))}.sf-item{display:block;text-decoration:none;color:#233044;background:#fff;border:1px solid #e8edf1;border-radius:8px;overflow:hidden;transition:transform .2s,box-shadow .2s}.sf-item:hover{transform:translateY(-4px);box-shadow:0 18px 38px #344d7018}.sf-item-img{height:210px;overflow:hidden;background:#e4eaf0}.sf-item-img img{height:100%;width:100%;object-fit:cover}.sf-placeholder{display:grid;place-items:center;width:100%;height:100%;min-height:190px;background:linear-gradient(130deg,#e2eaf2,#c9d7e6);font-size:72px;color:#8399b2;font-weight:800}.sf-large{min-height:360px}.sf-item-copy{padding:25px}.sf-item-copy small{text-transform:uppercase;letter-spacing:.13em;color:var(--accent,#376edc)}.sf-item-copy h3{font-size:22px;margin:12px 0 7px}.sf-item-copy p{font-size:14px;line-height:1.6;color:#7a8796;min-height:42px}.sf-item-copy span{font-size:12px;color:var(--accent,#376edc);font-weight:800}.sf-image-text>div>p{font-size:17px;line-height:1.8;max-width:520px;opacity:.7}.sf-reverse>div:first-child{order:2}.sf-gallery{display:grid;grid-template-columns:repeat(3,1fr);gap:15px}.sf-gallery img{width:100%;height:230px;object-fit:cover;border-radius:6px}.sf-faq{max-width:920px}.sf-faq details{border-bottom:1px solid #dce3eb;padding:22px 0}.sf-faq summary{font-size:17px;cursor:pointer;font-weight:700}.sf-faq p{color:#647181;line-height:1.75}.sf-cta-wrap{display:flex;justify-content:space-between;align-items:center;gap:24px}.sf-cta-wrap h2{margin:0}.sf-cta-wrap p{font-size:16px;opacity:.75}.sf-section a{cursor:pointer}.sf-muted{opacity:.65}
+@media(max-width:820px){.sf-container{padding:0 20px}.sf-section{padding:50px 0}.sf-hero-grid,.sf-image-text{grid-template-columns:1fr;gap:26px}.sf-reverse>div:first-child{order:initial}.sf-hero-img,.sf-image-side{min-height:250px}.sf-features,.sf-collection{grid-template-columns:repeat(2,minmax(0,1fr))}.sf-stats{grid-template-columns:repeat(2,1fr)}.sf-cta-wrap{align-items:flex-start;flex-direction:column}}
+@media(max-width:540px){.sf-features,.sf-collection,.sf-gallery{grid-template-columns:1fr}.sf-section-head{display:block}.sf-section-head>a{display:block;margin-top:13px}.sf-item-img{height:220px}.sf-hero-grid{gap:22px}.sf-hero-copy p{font-size:16px}.sf-stats{gap:28px}}
+
+.sf-attributes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 32px;border-top:1px solid #e5eaf0}.sf-attribute{display:flex;justify-content:space-between;align-items:center;gap:18px;border-bottom:1px solid #e5eaf0;padding:19px 10px;font-size:14px}.sf-attribute span{color:#748497}.sf-attribute strong{font-weight:720;text-align:right}@media(max-width:640px){.sf-attributes{grid-template-columns:1fr}}
+
+/* SiteForge responsive no-code grid container */
+.sf-layout-grid{display:grid;grid-template-columns:var(--sf-layout-cols,1fr 1fr);gap:var(--sf-layout-gap,24px);align-items:stretch}
+.sf-layout-column{min-width:0}.sf-layout-column>.sf-section{padding:0;margin:0 0 18px}.sf-layout-column .sf-container{width:100%;padding:0}
+.sf-layout-column>.sf-section:last-child{margin-bottom:0}
+@media(max-width:820px){.sf-layout-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}
+@media(max-width:540px){.sf-layout-grid{grid-template-columns:minmax(0,1fr)!important}}
+
+/* V8.6: accessible, script-free commercial design blocks */
+.sf-slides{display:flex;gap:18px;overflow-x:auto;scroll-snap-type:x mandatory;padding:6px 0 18px;scrollbar-width:thin}
+.sf-slide{flex:0 0 min(78%,720px);min-width:220px;margin:0;scroll-snap-align:start}
+.sf-slide img{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:10px}
+.sf-slide figcaption{padding:10px 2px;opacity:.7;font-size:14px}
+.sf-video{aspect-ratio:16/9;max-width:1000px;background:#ebf0f5;border-radius:10px;overflow:hidden}
+.sf-video iframe{width:100%;height:100%;border:0}
+.sf-tab{padding:19px 0;border-bottom:1px solid #dde4ec;max-width:960px}
+.sf-tab summary{font-size:18px;font-weight:700;cursor:pointer}.sf-tab p{max-width:850px;line-height:1.8}
+.sf-prices{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px}
+.sf-price{padding:28px;background:#fff;border:1px solid #dee5ef;border-radius:10px;color:#29394b}
+.sf-price strong{display:block;font-size:27px;margin:14px 0}.sf-price li{margin:11px 0}.sf-price ul{padding-left:19px}
+.sf-map-address{font-style:normal;font-size:18px;white-space:pre-line;line-height:1.8}
+@media (prefers-reduced-motion:reduce){.sf-slides{scroll-behavior:auto}.sf-item{transition:none}}
+@media(max-width:540px){.sf-slide{flex-basis:92%}}
diff --git a/examples/mobile-showcase/industrial/assets/commercial.css b/examples/mobile-showcase/industrial/assets/commercial.css
new file mode 100644
index 0000000..353bee4
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/commercial.css
@@ -0,0 +1,84 @@
+/* SiteForge commercial theme UX layer: accessibility, layout rhythm, media, responsive navigation. */
+:root{--sf-shadow:0 20px 55px rgba(17,33,55,.08);--sf-shadow-hover:0 26px 65px rgba(17,33,55,.15)}
+html{scroll-padding-top:104px}body{font-feature-settings:"kern";text-rendering:optimizeLegibility}
+a,button,input,select,textarea{-webkit-tap-highlight-color:transparent}
+a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--pro-accent);outline-offset:4px}
+.pro-wrap{width:100%;max-width:min(var(--pro-wide),100%)}.pro-header{isolation:isolate;box-shadow:0 1px 0 rgba(15,35,60,.035)}.pro-head-inner{gap:clamp(15px,2vw,29px)}.pro-logo{min-width:0;flex-shrink:0;gap:12px}.pro-brand-logo{max-width:155px;max-height:46px;width:auto;height:auto;object-fit:contain}.pro-logo-name{overflow-wrap:anywhere}.pro-navigation{gap:clamp(11px,1.6vw,24px)}.pro-header-cta{white-space:nowrap}
+.pro-mobile-toggle,.pro-catalog-toggle,.pro-nav-disclose{border:0;background:transparent;color:inherit;line-height:1.2}
+.pro-mobile-toggle{min-height:44px;min-width:44px;border:1px solid currentColor;border-radius:6px;font-size:19px}.pro-nav-group{position:relative;display:flex;align-items:center;gap:2px}
+.pro-nav-disclose{padding:9px;font-size:13px}.pro-nav-children{position:absolute;top:100%;left:0;min-width:215px;max-width:330px;display:grid;gap:2px;padding:13px;box-shadow:var(--sf-shadow);border-radius:10px;z-index:45;background:var(--pro-background);border:1px solid var(--pro-border);color:var(--pro-text)}
+.pro-nav-children[hidden],.pro-mega[hidden]{display:none!important}.pro-nav-children a{display:block;font-size:13px;white-space:normal;padding:10px;border-radius:5px}.pro-nav-children a:hover{background:var(--pro-surface)}
+.pro-catalog-toggle{padding:11px 3px;white-space:nowrap;font-size:12px;font-weight:700}.pro-mega{position:absolute;top:100%;left:50%;transform:translateX(-50%);width:min(100%,var(--pro-wide));box-shadow:var(--sf-shadow);z-index:41;display:grid;grid-template-columns:215px 1fr;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);border-radius:0 0 15px 15px;padding:29px;align-items:start}.pro-mega>span{font-size:12px;letter-spacing:.06em}.pro-mega>div{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px 22px;overflow:visible}.pro-mega>div a{white-space:normal;padding:10px 5px;border-bottom:1px solid var(--pro-border)}
+.pro-home-hero{overflow:hidden}.pro-home-hero .pro-home-grid{min-height:660px;gap:5%}.pro-home-hero .pro-hero-copy{position:relative;z-index:2}.pro-home-hero .pro-hero-copy h1{font-size:clamp(46px,5.7vw,92px);line-height:1.06;letter-spacing:-.064em;max-width:15ch}.pro-home-hero .pro-hero-copy h1 em{font-style:normal;display:block}.pro-home-hero .pro-hero-copy p{font-size:clamp(15px,1.3vw,19px);line-height:1.75;max-width:44ch}.pro-home-hero .pro-hero-art{min-width:0;min-height:425px;position:relative;isolation:isolate}.pro-home-hero .pro-hero-art>img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}.pro-hero-art:has(.pro-abstract-car),.pro-hero-art:has(.pro-technical),.pro-hero-art:has(.pro-corporate-shape){background-position:center;background-size:cover;background-repeat:no-repeat}.pro-abstract-car,.pro-technical,.pro-corporate-shape{position:relative;z-index:1;pointer-events:none}
+.pro-hero-actions{display:flex;flex-wrap:wrap;align-items:center;gap:15px}.pro-button,.pro-header-cta,.pro-outline-button{min-height:48px;transition:transform .24s ease,box-shadow .24s ease,opacity .24s ease}.pro-button:hover,.pro-header-cta:hover,.pro-outline-button:hover{transform:translateY(-2px);box-shadow:0 10px 22px rgba(0,0,0,.10)}
+.pro-section{padding-top:clamp(76px,7vw,112px)!important;padding-bottom:clamp(76px,7vw,112px)!important}.pro-section-head{gap:16px}.pro-section-head h2{font-size:clamp(32px,3.5vw,53px);max-width:22ch}.pro-section-head .pro-text-link{align-self:end}
+.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}.pro-card{min-width:0;display:flex;flex-direction:column;transition:transform .28s ease,box-shadow .28s ease;border:1px solid var(--pro-border);background:var(--pro-background);overflow:hidden}.pro-card:hover{transform:translateY(-6px);box-shadow:var(--sf-shadow-hover)}.pro-card-cover{aspect-ratio:1.42;overflow:hidden;background:var(--pro-surface);position:relative}.pro-card-cover .pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .45s ease}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-card-body{display:flex;flex-direction:column;flex:1}.pro-card-body h3{font-size:clamp(19px,1.6vw,24px);line-height:1.26}.pro-card-body p{font-size:14px}.pro-card-link{margin-top:auto;padding-top:14px;font-size:13px;font-weight:700}
+.pro-art{min-height:100%;width:100%;position:relative;overflow:hidden;display:grid;place-items:center;isolation:isolate}.pro-art-ring{width:60%;aspect-ratio:1;border:1px solid currentColor;opacity:.13;border-radius:50%;transform:rotate(20deg);box-shadow:0 0 0 23px #ffffff10,0 0 0 57px #ffffff07}.pro-art-center{font-size:clamp(38px,5vw,80px);position:absolute;font-weight:800;letter-spacing:-.065em;opacity:.65}.pro-art-label{position:absolute;bottom:19px;left:22px;font-size:10px;letter-spacing:.22em;opacity:.7}
+.pro-breadcrumb{display:flex;gap:11px;flex-wrap:wrap;font-size:12px;align-items:center}.pro-breadcrumb a{text-decoration:underline;text-underline-offset:4px}.pro-product-hero{align-items:start;gap:clamp(28px,5vw,80px)}.pro-product-media{aspect-ratio:1.12;background:var(--pro-surface);overflow:hidden}.pro-product-media>img,.pro-product-media>.pro-art{width:100%;height:100%;object-fit:contain}.pro-product-summary{padding:clamp(12px,3vw,50px) 0}.pro-product-summary h1{font-size:clamp(36px,4.3vw,72px)}.pro-detail-layout{border-top:1px solid var(--pro-border);padding-top:50px}.pro-specs>div{display:grid;grid-template-columns:minmax(100px,40%) 1fr;gap:10px;align-items:baseline;padding:15px 0;border-bottom:1px solid var(--pro-border)}.pro-specs strong{overflow-wrap:anywhere}.pro-gallery-thumbs button{flex-basis:96px;height:76px}.pro-gallery-lightbox{backdrop-filter:blur(6px)}
+.pro-filter-panel{display:flex;align-items:end;flex-wrap:wrap;gap:16px;background:var(--pro-surface);border:1px solid var(--pro-border);padding:20px;margin-bottom:25px;border-radius:var(--pro-radius)}.pro-filter-panel label{display:grid;gap:7px;min-width:180px;flex:1}.pro-filter-panel input,.pro-filter-panel select{width:100%;background:var(--pro-background);border:1px solid var(--pro-border);padding:12px 14px;color:var(--pro-text);border-radius:6px}.pro-filter-count{white-space:nowrap;font-size:13px;padding:12px 0}.pro-card[hidden]{display:none!important}.pro-filter-empty{padding:38px;border:1px dashed var(--pro-border);text-align:center;grid-column:1/-1}
+.pro-inquiry input,.pro-inquiry textarea{min-height:46px}.pro-inquiry textarea{min-height:150px}.pro-inquiry-wrap{gap:clamp(32px,7vw,110px)}.pro-inquiry .pro-button{border:0;justify-self:start}.pro-footer-links a{padding-block:5px;display:inline-block}
+.pro-end-cta{overflow:hidden}.pro-end-cta h2{font-size:clamp(30px,4vw,64px)}
+.pro-rich{line-height:1.85}.pro-rich img{border-radius:var(--pro-radius);margin-block:24px}.pro-rich table{width:100%;display:block;overflow:auto;border-collapse:collapse}.pro-rich td,.pro-rich th{border:1px solid var(--pro-border);padding:10px 14px}.pro-rich a{text-decoration:underline;text-underline-offset:3px}
+@media(min-width:1025px){.pro-navigation>.pro-nav-group:hover .pro-nav-children:not([hidden]){visibility:visible}}
+@media(max-width:1150px){.pro-head-inner{gap:15px}.pro-navigation{gap:12px}.pro-logo-name{font-size:15px}.pro-header-cta{padding:13px 16px}}
+@media(max-width:1024px){.pro-head-inner{min-height:72px;flex-wrap:wrap}.pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto}.pro-navigation{display:none;width:100%;flex-basis:100%;flex-direction:column;align-items:stretch;gap:0;order:5;margin:0;padding:8px 0 20px;border-top:1px solid var(--pro-border)}.pro-navigation.is-open{display:flex}.pro-navigation>a,.pro-nav-group>a{padding:14px 8px;font-size:15px}.pro-nav-group{display:flex;flex-wrap:wrap;justify-content:space-between}.pro-nav-children{position:static;flex-basis:100%;max-width:none;width:100%;box-shadow:none;border-radius:0;border-left:2px solid var(--pro-accent);margin:0 0 12px 8px}.pro-nav-disclose{min-width:44px;min-height:44px}.pro-catalog-toggle{margin-left:auto}.pro-mega{top:100%;display:block;padding:20px}.pro-mega>div{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:12px}.pro-home-hero .pro-home-grid{min-height:560px}.pro-home-hero .pro-hero-art{min-height:325px}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
+@media(max-width:700px){.pro-wrap{padding-left:20px;padding-right:20px}.pro-utility{display:none}.pro-head-inner{min-height:66px}.pro-logo-name{max-width:140px;font-size:14px}.pro-brand-logo{max-width:125px}.pro-header-cta{display:none}.pro-catalog-toggle{font-size:11px;padding-inline:7px;margin-left:0}.pro-home-hero .pro-home-grid{display:flex;flex-direction:column;gap:36px;padding-top:48px;padding-bottom:48px}.pro-home-hero .pro-hero-copy h1{font-size:clamp(43px,10vw,68px)}.pro-home-hero .pro-hero-copy p{font-size:15px}.pro-home-hero .pro-hero-art{width:100%;min-height:290px}.pro-home-hero .pro-hero-bottom{display:none}.pro-section{padding-top:68px!important;padding-bottom:68px!important}.pro-section-head{align-items:flex-start}.pro-grid{grid-template-columns:1fr;gap:18px}.pro-card-cover{aspect-ratio:1.45}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-inquiry-wrap{display:grid;grid-template-columns:1fr;gap:30px}.pro-product-summary{padding:0}.pro-specs>div{grid-template-columns:1fr 1fr}.pro-mega{top:100%;max-height:65vh;overflow-y:auto}.pro-mega>div{grid-template-columns:1fr}.pro-filter-panel{display:grid;grid-template-columns:1fr;gap:12px}.pro-filter-panel label{min-width:0}.pro-footer-top{display:grid;grid-template-columns:1fr;gap:27px}.pro-footer-bottom{gap:12px;flex-wrap:wrap}}
+@media(max-width:410px){.pro-logo-name{max-width:115px}.pro-logo-symbol{font-size:28px}.pro-catalog-toggle{font-size:10px}.pro-mobile-toggle{min-width:42px}}
+@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation-duration:.01ms!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}
+
+/* Accessible static catalogue pagination for large inventories. */
+.pro-pages-nav{display:flex;gap:1rem;justify-content:center;align-items:center;margin:2rem 0;flex-wrap:wrap}
+.pro-pages-nav a{padding:.7rem 1.1rem;border:1px solid currentColor;border-radius:.4rem;text-decoration:none}
+.pro-pages-nav a:focus-visible{outline:3px solid var(--accent,#2563eb);outline-offset:3px}
+.pro-pages-nav span{font-size:.875rem}
+
+/* P6 international sites: small, keyboard-operable language switch. */
+.sf-language-switch{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;margin:8px 12px;padding:4px;border:1px solid rgba(120,130,145,.22);border-radius:10px;background:rgba(255,255,255,.08)}
+.sf-language-switch a{font:inherit;font-size:12px;line-height:1.5;padding:4px 7px;border-radius:6px;color:inherit;text-decoration:none}
+.sf-language-switch a[aria-current="page"]{font-weight:700;text-decoration:underline;text-underline-offset:3px}
+.sf-language-switch a:focus-visible{outline:2px solid #2563eb;outline-offset:3px}
+@media(max-width:640px){.sf-language-switch{margin:6px 0;width:auto}}
+
+/* V8.21.3 — Four-theme unified mobile off-canvas navigation & optional tab bar.
+ Desktop remains unchanged. Phone / tablet use one accessible drawer. */
+.sf-mobile-tabs{display:none!important}
+.sf-drawer-header,.sf-mobile-drawer-backdrop,.sf-drawer-extra{display:none}
+@media(max-width:1024px){
+ .pro-head-inner{flex-wrap:nowrap!important;gap:12px!important}
+ .pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto!important;flex:0 0 44px;min-width:44px;min-height:44px;position:relative;z-index:3}
+ .pro-catalog-toggle{display:none!important}
+ .pro-navigation,.pro-navigation.is-open{display:flex!important;position:fixed!important;inset:0 0 0 auto!important;width:min(88vw,360px)!important;height:100dvh!important;height:100vh;max-height:100dvh!important;flex:unset!important;flex-direction:column!important;align-items:stretch!important;order:unset!important;margin:0!important;padding:0 0 calc(20px + env(safe-area-inset-bottom,0px))!important;overflow-x:hidden;overflow-y:auto!important;background:var(--pro-background,#fff)!important;color:var(--pro-text,#19283c)!important;border:0!important;border-left:1px solid var(--pro-border,#ddd)!important;box-shadow:-15px 0 45px #0002!important;z-index:1201!important;transform:translateX(105%);visibility:hidden;pointer-events:none;transition:transform .24s ease,visibility .24s ease!important;overscroll-behavior:contain}
+ .pro-navigation.is-open{transform:translateX(0);visibility:visible;pointer-events:auto}
+ .sf-drawer-header{display:flex!important;align-items:center;justify-content:space-between;gap:12px;padding:20px 18px;border-bottom:1px solid var(--pro-border,#dde3e9);font-size:17px;font-weight:750;position:sticky;top:0;background:var(--pro-background,#fff);z-index:2}
+ .sf-drawer-close{min-width:44px;min-height:44px;border:1px solid var(--pro-border,#dde3e9);color:inherit;background:transparent;border-radius:10px;font-size:24px;cursor:pointer}
+ .sf-mobile-drawer-backdrop{display:block;position:fixed;inset:0;z-index:1200;background:#0b1b31a3;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .24s ease,visibility .24s ease}
+ .sf-mobile-drawer-backdrop.is-open{opacity:1;visibility:visible;pointer-events:auto}
+ body.sf-drawer-open{overflow:hidden!important}
+ .pro-navigation>a,.pro-navigation .pro-nav-group>a,.pro-navigation .sf-drawer-extra>a{display:flex;align-items:center;min-height:50px;padding:13px 20px!important;font-size:15px!important;color:inherit!important;white-space:normal;border-bottom:1px solid var(--pro-border,#dde3e9)!important;text-decoration:none!important}
+ .pro-navigation .pro-nav-group{display:flex;position:relative;flex-wrap:wrap!important;width:100%;gap:0;align-items:center;justify-content:space-between}
+ .pro-navigation .pro-nav-group>a{flex:1;min-width:0}
+ .pro-navigation .pro-nav-disclose{margin-right:12px;min-width:44px;min-height:44px;border-radius:9px;background:var(--pro-surface,#f4f6f8)}
+ .pro-navigation .pro-nav-children{position:static!important;width:100%!important;max-width:100%!important;box-shadow:none!important;border-left:3px solid var(--pro-accent,#e77730);border-radius:0!important;margin:0 0 8px 14px!important;flex-basis:calc(100% - 14px)!important;background:var(--pro-surface,#f6f8fa)}
+ .pro-navigation .pro-nav-children a{padding:12px 16px!important;font-size:14px!important;color:inherit!important;min-height:44px}
+ .sf-drawer-extra{display:block;padding:12px 0 5px}.sf-drawer-extra strong{display:block;font-size:12px;letter-spacing:.06em;padding:12px 20px;opacity:.65}.sf-drawer-extra a{font-size:14px!important}
+ .sf-drawer-close:focus-visible,.pro-mobile-toggle:focus-visible,.pro-nav-disclose:focus-visible,.sf-mobile-tab:focus-visible{outline:3px solid var(--pro-accent,#ff8200);outline-offset:2px}
+}
+@media(max-width:767px){
+ .pro-header-cta{display:none!important}
+ .pro-head-inner .pro-logo{min-width:0;max-width:calc(100% - 65px)}
+ .pro-logo-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .pro-home-hero .pro-home-grid{min-height:0!important}
+ .pro-home-hero .pro-hero-art{min-height:230px}
+ .pro-card,.pro-card-cover,.pro-product-media{max-width:100%;min-width:0}
+ .pro-rich img,.pro-content img,.sf-canvas img{max-width:100%;height:auto}
+ .pro-rich table{max-width:100%;overflow:auto}
+ .pro-grid,.pro-footer-top{min-width:0}
+ .sf-mobile-tabs{display:grid!important;position:fixed;z-index:1100;bottom:0;left:0;right:0;grid-template-columns:repeat(var(--sf-mobile-tab-count,4),minmax(0,1fr));align-items:stretch;padding:5px 6px calc(5px + env(safe-area-inset-bottom,0px));min-height:63px;background:var(--pro-background,#fff);color:var(--pro-text,#263446);border-top:1px solid var(--pro-border,#dfe4eb);box-shadow:0 -6px 24px #13223914}
+ .sf-mobile-tab{appearance:none;border:0!important;background:transparent!important;color:inherit!important;text-decoration:none!important;padding:6px 2px;min-height:50px;min-width:0;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:2px;border-radius:9px;cursor:pointer;font-family:inherit}
+ .sf-tab-icon{font-size:23px;line-height:1.15;font-weight:500}
+ .sf-tab-label{font-size:11px;font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .sf-mobile-tab[aria-current=page]{color:var(--pro-accent,#e47730)!important;background:var(--pro-surface,#f5f6f8)!important}
+ body:has(> .sf-mobile-tabs){padding-bottom:calc(76px + env(safe-area-inset-bottom,0px))!important}
+}
+@media(min-width:768px){.sf-mobile-tabs{display:none!important}}
+@media(prefers-reduced-motion:reduce){.pro-navigation,.sf-mobile-drawer-backdrop{transition:none!important}}
diff --git a/examples/mobile-showcase/industrial/assets/custom.css b/examples/mobile-showcase/industrial/assets/custom.css
new file mode 100644
index 0000000..e18a2d4
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/custom.css
@@ -0,0 +1 @@
+body .pro-utility[hidden]{display:none!important}@media(max-width:1023px){.sf-canvas .sf-section{padding-block:64px}}@media(max-width:767px){.sf-canvas .sf-section{padding-block:40px}.pro-wrap,.sf-container{padding-left:20px;padding-right:20px}body .pro-utility{display:none!important}}
\ No newline at end of file
diff --git a/examples/mobile-showcase/industrial/assets/factory.css b/examples/mobile-showcase/industrial/assets/factory.css
new file mode 100644
index 0000000..154ffb3
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/factory.css
@@ -0,0 +1,27 @@
+/* SiteForge trusted global shell. Site themes remain visually independent. */
+:root{--sf-shell:#12243d;--sf-shell-accent:#dcae71;--sf-shell-soft:#edf2f8;--sf-shell-fg:#173049}
+html{scroll-behavior:smooth}body{overflow-x:hidden}body>.header,body>.utility,body>.top,body>header,body>footer{display:none!important}
+.sf-theme-industrial{--sf-shell:#114e79;--sf-shell-accent:#92d5ed;--sf-shell-soft:#ebf6fa;--sf-shell-fg:#0c4267}
+.sf-theme-corporate{--sf-shell:#232d62;--sf-shell-accent:#b8bdff;--sf-shell-soft:#f3f3fa;--sf-shell-fg:#333468}
+.sf-theme-minimal{--sf-shell:#1e1e1c;--sf-shell-accent:#ded6c5;--sf-shell-soft:#f6f4ef;--sf-shell-fg:#252520}
+.sf-site-head{position:relative;z-index:25;background:var(--sf-shell);color:#fff;font-family:system-ui,-apple-system,sans-serif}
+.sf-head-inner{max-width:1240px;margin:0 auto;min-height:78px;padding:0 28px;display:flex;align-items:center;gap:28px}
+.sf-logo{display:flex;align-items:center;gap:11px;font-size:18px;text-decoration:none!important;color:#fff!important;white-space:nowrap}
+.sf-logo-mark{width:36px;height:36px;display:grid;place-items:center;background:var(--sf-shell-accent);color:var(--sf-shell);font-size:22px;font-weight:900;border-radius:9px}
+.sf-primary-links{display:flex;gap:23px;margin-left:auto;align-items:center}
+.sf-primary-links a,.sf-mega a{color:#fff!important;text-decoration:none!important;font-size:13px;font-weight:650;white-space:nowrap}
+.sf-primary-links a:hover{color:var(--sf-shell-accent)!important}
+.sf-head-cta{font-size:13px;font-weight:750;text-decoration:none;background:var(--sf-shell-accent);color:var(--sf-shell)!important;padding:13px 18px;border-radius:4px;white-space:nowrap}
+.sf-menu-toggle{display:none;margin-left:auto;color:white;border:1px solid #ffffff55;background:none;border-radius:4px;padding:8px 12px;font-size:19px;cursor:pointer}
+.sf-mega{display:flex;gap:18px;max-width:1240px;margin:auto;padding:0 28px 9px;overflow-x:auto}
+.sf-mega a{display:block;min-width:120px;border-top:1px solid #ffffff2e;padding:12px 8px}.sf-mega small{display:block;color:#ffffff99;max-width:160px;overflow:hidden;text-overflow:ellipsis;font-size:10px;margin-top:6px}
+.sf-site-foot{background:var(--sf-shell);color:white!important;font:14px/1.7 system-ui,-apple-system,sans-serif;padding:72px 28px 26px}
+.sf-foot-grid{max-width:1200px;margin:auto;display:grid;grid-template-columns:2fr 1fr 1fr;gap:70px}.sf-foot-grid h2{font-size:25px;color:white;margin:0 0 17px}.sf-foot-grid h4{font-size:13px;color:var(--sf-shell-accent);letter-spacing:.07em;margin:0 0 17px}.sf-foot-grid p{color:#ffffffae;max-width:380px}.sf-foot-grid a{display:block;width:fit-content;color:#fff!important;text-decoration:none;margin-bottom:8px}.sf-foot-lower{border-top:1px solid #ffffff33;max-width:1200px;margin:45px auto 0;padding-top:20px;display:flex;justify-content:space-between;color:#ffffff9e;font-size:12px}
+.sf-filters{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin:0 0 30px}.sf-filters input,.sf-filters select{padding:11px 13px;border-radius:6px;border:1px solid #cfd9e4;background:#fff;color:#263a51;max-width:280px}.sf-filter-empty{padding:22px;color:#778598}
+.sf-search-page{min-height:76vh;max-width:1060px;padding:90px 24px;margin:auto;font-family:system-ui,sans-serif;color:var(--sf-shell-fg)}.sf-search-page h1{font-size:clamp(35px,5vw,62px);margin:20px 0}.sf-search-page>label{display:block;margin:18px 0 8px}.sf-search-page input{display:block;width:100%;padding:17px;font-size:18px;border:1px solid #b7c6d7;border-radius:8px}.sf-search-page #sf-search-count{margin:22px 0;color:#607589}.sf-search-result{padding:22px 0;border-bottom:1px solid #dae1ea}.sf-search-result a{color:var(--sf-shell-fg);font-size:21px;font-weight:750;text-decoration:none}.sf-search-result small{display:block;color:#7992a7}.sf-search-result p{margin:7px 0;color:#657487}
+@media(max-width:1050px){.sf-head-inner{gap:13px}.sf-primary-links{gap:12px}.sf-primary-links a{font-size:12px}.sf-head-cta{padding:10px}}
+@media(max-width:850px){.sf-head-inner{min-height:68px}.sf-menu-toggle{display:block}.sf-head-cta{display:none}.sf-primary-links{display:none;position:absolute;top:68px;left:0;right:0;margin:0;background:var(--sf-shell);padding:20px 28px;flex-direction:column;align-items:stretch;box-shadow:0 20px 24px #0002}.sf-primary-links.is-open{display:flex}.sf-mega{display:none}.sf-foot-grid{grid-template-columns:1fr 1fr;gap:35px}}
+@media(max-width:560px){.sf-foot-grid{grid-template-columns:1fr}.sf-foot-lower{display:block}.sf-site-foot{padding-top:48px}.sf-head-inner{padding:0 16px}}
+
+.sf-page-intro{padding:80px 28px;background:var(--sf-shell-soft);color:var(--sf-shell-fg);font-family:system-ui,sans-serif}.sf-page-intro>div{max-width:1200px;margin:auto}.sf-page-intro p{font-size:11px;font-weight:850;letter-spacing:.19em;text-transform:uppercase;color:var(--sf-shell-fg);opacity:.58}.sf-page-intro h1{font-size:clamp(38px,5vw,67px);letter-spacing:-.04em;line-height:1.08;margin:13px 0}.sf-page-intro span{font-size:16px;opacity:.72}
+.sf-item[hidden]{display:none!important}
diff --git a/examples/mobile-showcase/industrial/assets/pro-base.css b/examples/mobile-showcase/industrial/assets/pro-base.css
new file mode 100644
index 0000000..c2c4f61
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/pro-base.css
@@ -0,0 +1,21 @@
+/* SiteForge V3 professional theme primitives. Each commercial theme adds its own art direction. */
+:root{--pro-text:#19283a;--pro-muted:#687587;--pro-border:#dfe5ea;--pro-background:#fff;--pro-surface:#f5f7fa;--pro-accent:#365cb7;--pro-hero:#15263d;--pro-hero-fg:#fff;--pro-radius:12px;--pro-headings:system-ui,-apple-system,sans-serif;--pro-font:system-ui,-apple-system,sans-serif;--pro-wide:1270px}
+*,*:before,*:after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--pro-background);color:var(--pro-text);font-family:var(--pro-font);-webkit-font-smoothing:antialiased;overflow-x:hidden}button,input,select{font:inherit}button,a{touch-action:manipulation}button{cursor:pointer}a{text-decoration:none;color:inherit}a:hover{color:var(--pro-accent)}img{max-width:100%;display:block}h1,h2,h3,h4,p{margin-top:0}h1,h2,h3{font-family:var(--pro-headings);line-height:1.09}h1{letter-spacing:-.052em}h2{letter-spacing:-.042em}h3{letter-spacing:-.028em}p{line-height:1.72}.pro-wrap{max-width:var(--pro-wide);margin:0 auto;padding-left:32px;padding-right:32px}.pro-skip{position:absolute;left:-9999px;z-index:90;background:#fff;color:#000;padding:12px}.pro-skip:focus{left:10px;top:10px}.pro-eyebrow{font-weight:750;font-size:11px;letter-spacing:.17em;text-transform:uppercase;display:block;line-height:1.6;color:var(--pro-accent)}.pro-muted{opacity:.7}.pro-utility{min-height:30px;background:var(--pro-hero);color:#c5d2db;font-size:10px;letter-spacing:.16em}.pro-utility .pro-wrap{display:flex;justify-content:space-between;padding-block:8px}.pro-utility i{font-style:normal;color:var(--pro-accent)}.pro-header{background:var(--pro-background);color:var(--pro-text);border-bottom:1px solid var(--pro-border);position:relative;z-index:35}.pro-head-inner{min-height:84px;display:flex;align-items:center;gap:28px}.pro-logo{display:flex;align-items:center;gap:12px;min-width:170px}.pro-logo-symbol{font-size:32px;line-height:1;font-weight:800;letter-spacing:-.07em;color:var(--pro-accent)}.pro-logo-name{max-width:220px;font-weight:820;font-size:17px;line-height:1.1;letter-spacing:-.035em}.pro-logo-name small{display:block;font-weight:650;font-size:9px;letter-spacing:.19em;margin-top:7px;color:var(--pro-muted)}.pro-navigation{display:flex;align-items:center;gap:24px;margin-left:auto}.pro-navigation a{font-size:13px;font-weight:650;white-space:nowrap;transition:opacity .2s}.pro-navigation a:hover{opacity:.6}.pro-header-cta,.pro-button{display:inline-flex;align-items:center;justify-content:center;gap:22px;background:var(--pro-accent);color:#fff!important;border-radius:var(--pro-radius);font-size:13px;letter-spacing:.01em;font-weight:750;padding:16px 24px;white-space:nowrap;transition:transform .2s,background .2s}.pro-header-cta:hover,.pro-button:hover{transform:translateY(-2px)}.pro-mobile-toggle{display:none}.pro-mega{display:flex;align-items:center;gap:22px;min-height:37px;border-top:1px solid var(--pro-border);padding-block:10px;font-size:11px}.pro-mega>span{font-weight:750;color:var(--pro-muted);white-space:nowrap}.pro-mega>div{display:flex;gap:16px;overflow:auto;scrollbar-width:none}.pro-mega a{white-space:nowrap;font-weight:650}.pro-search-link{font-size:19px!important}.pro-home-hero{position:relative;overflow:hidden}.pro-home-grid{display:grid;grid-template-columns:1.02fr .98fr;align-items:center;gap:5%;min-height:580px;padding-top:85px;padding-bottom:85px}.pro-hero-copy{position:relative;z-index:2}.pro-hero-copy h1{font-size:clamp(48px,6.2vw,91px);line-height:1.03;margin:20px 0 25px;max-width:730px}.pro-hero-copy h1 em{font-style:normal;display:block;color:var(--pro-accent)}.pro-hero-copy p{font-size:17px;max-width:495px;color:var(--pro-muted)}.pro-hero-actions{display:flex;align-items:center;flex-wrap:wrap;gap:25px;margin-top:34px}.pro-outline-button{font-weight:700;border-bottom:1px solid currentColor;padding:12px 0}.pro-hero-art{width:100%;height:435px;position:relative;overflow:hidden;border-radius:var(--pro-radius)}.pro-hero-art>img{width:100%;height:100%;object-fit:cover}.pro-hero-bottom{display:flex;justify-content:space-between;font-size:10px;letter-spacing:.18em;padding-block:22px;border-top:1px solid #ffffff32}.pro-section{padding:106px 0}.pro-section-head{display:flex;align-items:end;justify-content:space-between;gap:25px;margin-bottom:34px}.pro-section-head h2{font-size:clamp(36px,4vw,57px);margin:12px 0 0}.pro-text-link{font-size:13px;font-weight:750;display:inline-flex;gap:18px;align-items:center;border-bottom:1px solid;padding-bottom:7px;color:var(--pro-accent)}.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}.pro-card{display:block;min-width:0;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);overflow:hidden;border-radius:var(--pro-radius);transition:transform .25s,box-shadow .25s}.pro-card:hover{transform:translateY(-6px);box-shadow:0 24px 45px #0f25431b}.pro-card-cover{width:100%;height:244px;overflow:hidden;background:var(--pro-surface)}.pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .5s}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-art{width:100%;height:100%;overflow:hidden;display:grid;place-items:center;position:relative;background:linear-gradient(130deg,#d8e1ed,#f3f6f9)}.pro-art-ring{width:55%;aspect-ratio:1;border:28px solid #ffffff5f;border-radius:50%;box-shadow:0 0 0 42px #ffffff30;transform:rotate(-25deg)}.pro-art-center{position:absolute;font-family:var(--pro-headings);font-weight:800;font-size:76px;letter-spacing:-.08em;color:#45647d90}.pro-art-label{position:absolute;bottom:16px;left:18px;letter-spacing:.16em;font-size:9px;color:#435773}.pro-card-body{padding:25px 25px 28px}.pro-card-meta{display:flex;justify-content:space-between;gap:20px;font-size:10px;font-weight:750;letter-spacing:.1em;text-transform:uppercase;color:var(--pro-accent)}.pro-card h3{font-size:23px;margin:14px 0 9px}.pro-card p{font-size:13px;color:var(--pro-muted);margin-bottom:24px;min-height:44px}.pro-card-link{font-size:12px;font-weight:800;display:flex;justify-content:space-between;color:var(--pro-text);padding-top:14px;border-top:1px solid var(--pro-border)}.pro-empty{grid-column:1/-1;padding:45px;border:1px dashed var(--pro-border);background:var(--pro-surface);color:var(--pro-muted)}.pro-statement{background:var(--pro-surface)}.pro-statement-layout{display:grid;grid-template-columns:1fr 2fr;gap:60px}.pro-statement h2{font-size:clamp(36px,4.5vw,63px);max-width:760px}.pro-statement p{max-width:520px;color:var(--pro-muted)}.pro-brand-pills{display:flex;flex-wrap:wrap;gap:12px}.pro-brand-pills a{padding:18px 28px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);font-weight:750}.pro-brand-pills a:hover{border-color:var(--pro-accent)}.pro-inner-hero{background:var(--pro-surface);padding:92px 0 65px;position:relative;overflow:hidden}.pro-inner-hero h1{max-width:870px;font-size:clamp(43px,6vw,85px);margin:18px 0}.pro-inner-hero p{max-width:650px;color:var(--pro-muted);font-size:17px}.pro-breadcrumb{font-size:12px;color:var(--pro-muted);margin-top:25px;display:flex;gap:12px;align-items:center;flex-wrap:wrap}.pro-breadcrumb strong{color:var(--pro-text)}.pro-list-section{padding:65px 0 110px}.pro-filter-panel{display:grid;grid-template-columns:minmax(220px,2fr) 1fr 1fr auto;align-items:end;gap:14px;margin-bottom:35px}.pro-filter-panel label>span{display:block;font-size:11px;margin-bottom:8px;color:var(--pro-muted);font-weight:750}.pro-filter-panel input,.pro-filter-panel select{display:block;width:100%;padding:14px 15px;border-radius:var(--pro-radius);border:1px solid var(--pro-border);background:var(--pro-background);color:var(--pro-text);outline-offset:2px}.pro-filter-count{font-size:12px;padding-bottom:17px;color:var(--pro-muted)}.pro-filter-scope .pro-card[hidden]{display:none}.pro-product-layout{padding-top:38px}.pro-product-layout .pro-breadcrumb{margin-bottom:38px}.pro-product-hero{display:grid;grid-template-columns:1.18fr .82fr;gap:6%;align-items:center}.pro-product-media{height:460px;overflow:hidden;background:var(--pro-surface);border-radius:var(--pro-radius)}.pro-product-media .pro-photo{height:100%}.pro-product-summary h1{font-size:clamp(45px,5vw,72px);margin:16px 0}.pro-product-summary p{font-size:16px;color:var(--pro-muted)}.pro-product-brand{font-weight:700;color:var(--pro-text)!important}.pro-detail-layout{border-top:1px solid var(--pro-border);display:grid;grid-template-columns:1fr 1fr;gap:8%;margin-top:80px;padding-top:55px}.pro-detail-layout h2{font-size:32px;margin-top:13px}.pro-specs{margin-top:25px}.pro-specs>div{display:grid;grid-template-columns:1fr 1fr;gap:22px;padding:16px 0;border-bottom:1px solid var(--pro-border)}.pro-specs span{color:var(--pro-muted)}.pro-rich{line-height:1.85;font-size:16px;overflow-wrap:anywhere}.pro-rich img{border-radius:var(--pro-radius);margin:25px 0}.pro-rich a{text-decoration:underline}.pro-rich h2{margin-top:30px}.pro-lead{font-size:20px;color:var(--pro-muted)}.pro-brand-hero-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:7%;align-items:center}.pro-brand-media{height:300px;overflow:hidden;border-radius:var(--pro-radius)}.pro-article-hero{text-align:center;padding:110px 0 75px;background:var(--pro-surface)}.pro-article-width{max-width:900px}.pro-article-hero h1{font-size:clamp(40px,5vw,75px);margin:15px 0 25px}.pro-article-hero p{color:var(--pro-muted);font-size:18px}.pro-article-hero time{font-size:12px;color:var(--pro-muted)}.pro-article-content{max-width:930px;padding-top:70px;padding-bottom:90px}.pro-article-cover{width:100%;max-height:530px;object-fit:cover;margin-bottom:40px;border-radius:var(--pro-radius)}.pro-article-content>.pro-text-link{margin-top:35px}.pro-case-detail{max-width:1020px}.pro-case-detail>img{width:100%;max-height:620px;object-fit:cover}.pro-case-meta{display:flex;gap:35px;margin:35px 0;color:var(--pro-muted);font-size:13px}.pro-case-meta strong{display:block;color:var(--pro-text);font-size:18px;margin-top:7px}.pro-info-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:12%}.pro-info-grid h2{font-size:clamp(31px,4.5vw,54px);margin-top:15px}.pro-contact-email{font-size:20px;color:var(--pro-accent);font-weight:750;word-break:break-word}.pro-info-photo{max-height:450px;object-fit:cover;margin-top:35px;border-radius:var(--pro-radius)}.pro-custom-content .sf-section{background:transparent}.pro-custom-content .sf-section:nth-of-type(even){background:var(--pro-surface)}.pro-end-cta{padding:76px 0;background:var(--pro-hero);color:#fff}.pro-end-cta .pro-wrap{display:flex;align-items:center;justify-content:space-between;gap:40px}.pro-end-cta h2{font-size:clamp(32px,4vw,53px);margin:13px 0}.pro-end-cta p{color:#c5c9d1}.pro-end-cta a{background:var(--pro-accent);color:#fff;font-weight:800;padding:19px 30px;border-radius:var(--pro-radius);white-space:nowrap}.pro-404{text-align:center;padding-block:125px}.pro-404>span{font-size:12px;letter-spacing:.3em}.pro-404 h1{font-size:clamp(150px,20vw,320px);margin:10px 0;letter-spacing:-.08em}.pro-404 p{font-size:18px;color:var(--pro-muted)}.pro-404 a{margin-top:22px}.pro-footer{background:#15212d;color:#fff;padding:75px 0 22px}.pro-footer-top{display:grid;grid-template-columns:1.45fr 1fr 1fr;gap:55px}.pro-footer h2{font-size:32px;margin:12px 0}.pro-footer p{max-width:350px;font-size:13px;color:#afbbc8}.pro-footer .pro-eyebrow{color:#b6c3d2}.pro-footer-links ul{margin:0;padding:0;list-style:none;display:grid;grid-template-columns:1fr 1fr;gap:15px}.pro-footer-links a{font-size:13px;color:#d6e0e8}.pro-footer-bottom{border-top:1px solid #ffffff29;margin-top:60px;padding-top:20px;font-size:10px;letter-spacing:.12em;color:#adb9c3;display:flex;justify-content:space-between}.pro-anchor{display:block;height:0}.pro-category-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.pro-category-tiles a{padding:35px 28px;min-height:155px;background:var(--pro-surface);display:flex;flex-direction:column;justify-content:space-between}.pro-category-tiles span{font-size:11px;color:var(--pro-muted)}.pro-category-tiles strong{font-size:24px}.pro-category-tiles b{align-self:end}.sf-site-head .sf-head-inner,.sf-site-head .sf-primary-links{display:none}
+@media(max-width:1100px){.pro-head-inner{gap:17px}.pro-navigation{gap:14px}.pro-navigation a{font-size:12px}.pro-home-grid{min-height:500px}.pro-hero-copy h1{font-size:60px}.pro-hero-art{height:350px}}
+@media(max-width:860px){.pro-wrap{padding-left:22px;padding-right:22px}.pro-utility{display:none}.pro-head-inner{min-height:70px;flex-wrap:wrap;gap:12px}.pro-mobile-toggle{display:block;margin-left:auto;border:1px solid var(--pro-border);padding:8px 12px;background:transparent;color:var(--pro-text);border-radius:5px}.pro-navigation{display:none;order:10;width:100%;flex-direction:column;align-items:stretch;gap:0;padding-bottom:12px}.pro-navigation.is-open{display:flex}.pro-navigation a{padding:11px 2px;border-bottom:1px solid var(--pro-border)}.pro-header-cta{display:none}.pro-mega{display:none}.pro-home-grid{grid-template-columns:1fr;gap:35px;padding-top:60px;padding-bottom:60px}.pro-hero-art{height:350px}.pro-hero-copy h1{font-size:clamp(45px,9vw,73px)}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-brand-hero-grid{grid-template-columns:1fr;gap:35px}.pro-product-media{height:380px}.pro-filter-panel{grid-template-columns:1fr 1fr}.pro-filter-panel label:first-child{grid-column:1/-1}.pro-end-cta .pro-wrap{align-items:start;flex-direction:column}.pro-section{padding:72px 0}.pro-footer-top{grid-template-columns:1fr 1fr}.pro-footer-top>div:first-child{grid-column:1/-1}.pro-category-tiles{grid-template-columns:repeat(2,1fr)}}
+@media(max-width:560px){.pro-wrap{padding-left:18px;padding-right:18px}.pro-logo-name{font-size:15px}.pro-hero-copy h1{font-size:43px}.pro-hero-art{height:255px}.pro-hero-bottom{display:none}.pro-home-grid{min-height:0}.pro-grid{grid-template-columns:1fr}.pro-section-head{align-items:start;flex-direction:column}.pro-section-head h2{font-size:37px}.pro-card-cover{height:230px}.pro-filter-panel{grid-template-columns:1fr}.pro-filter-panel label:first-child{grid-column:auto}.pro-filter-count{padding-bottom:4px}.pro-statement-layout,.pro-footer-top{grid-template-columns:1fr;gap:25px}.pro-footer-top>div:first-child{grid-column:auto}.pro-footer-bottom{flex-direction:column;gap:8px}.pro-inner-hero{padding:60px 0}.pro-product-media{height:270px}.pro-product-summary h1{font-size:43px}.pro-category-tiles{grid-template-columns:1fr}.pro-end-cta h2{font-size:38px}.pro-minimal-meta{display:block!important}}
+
+/* V2 compatibility stylesheet hides direct headers/footers; full themes restore their own. */
+body>header.sf-site-head.pro-header,body>footer.sf-site-foot.pro-footer{display:block!important}
+
+/* Accessible desktop mega-navigation and mobile disclosure */
+.pro-catalog-toggle{font-size:12px;font-weight:750;border:1px solid var(--pro-border);background:transparent;color:inherit;border-radius:var(--pro-radius);padding:10px 14px;white-space:nowrap}
+.pro-mega[hidden]{display:none!important}.pro-mega.is-open{display:flex}.pro-mega>div{padding-block:3px}
+@media(max-width:1060px){.pro-catalog-toggle{display:none}}
+@media(max-width:860px){.pro-mega.is-open{display:block!important}.pro-mega.is-open>div{display:flex;flex-wrap:wrap;gap:14px;padding:12px 0}.pro-mega>span{white-space:normal}}
+
+/* V4: responsive nested navigation, gallery and opt-in inquiry integration */
+.pro-nav-group{position:relative;display:flex;align-items:center;gap:3px}.pro-nav-disclose{border:0;background:none;color:inherit;font-size:12px;padding:8px}.pro-nav-children[hidden]{display:none!important}.pro-nav-children{position:absolute;left:0;top:100%;z-index:45;min-width:230px;max-width:360px;padding:10px;background:var(--pro-background);border:1px solid var(--pro-border);box-shadow:0 18px 35px #0002;border-radius:var(--pro-radius);display:flex;flex-direction:column}.pro-nav-children a{padding:10px 12px;white-space:nowrap}.pro-nav-children .pro-nav-leaf{padding-left:28px;color:var(--pro-muted)}
+.pro-product-gallery{min-width:0}.pro-gallery-thumbs{display:flex;gap:8px;margin-top:12px;overflow:auto;scrollbar-width:thin;padding-bottom:5px}.pro-gallery-thumbs button{flex:0 0 86px;height:67px;border:2px solid transparent;padding:3px;border-radius:8px;background:var(--pro-surface)}.pro-gallery-thumbs button:focus-visible,.pro-gallery-thumbs button[aria-pressed=true]{border-color:var(--pro-accent)}.pro-gallery-thumbs img{width:100%;height:100%;object-fit:cover;border-radius:4px}.pro-gallery-lightbox{position:fixed;inset:0;background:rgba(10,16,30,.92);z-index:1000;display:grid;place-items:center;padding:6vw}.pro-gallery-lightbox[hidden]{display:none}.pro-gallery-lightbox img{max-width:92vw;max-height:80vh;object-fit:contain}.pro-gallery-lightbox button{position:absolute;right:25px;top:25px;border:1px solid #fff8;background:#222;color:#fff;padding:10px 15px;border-radius:5px}
+.pro-inquiry-wrap{display:grid;grid-template-columns:1fr 1fr;gap:70px}.pro-inquiry{display:grid;gap:17px}.pro-inquiry label{display:grid;gap:7px;font-weight:650}.pro-inquiry input,.pro-inquiry textarea{padding:13px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);background:var(--pro-background);color:var(--pro-text);width:100%}.pro-inquiry textarea{min-height:140px}.pro-inquiry .pro-honeypot{position:absolute;left:-10000px;width:1px;height:1px}.pro-inquiry p{font-size:12px;color:var(--pro-muted)}
+@media(max-width:860px){.pro-nav-group{display:block}.pro-nav-children{position:static;max-width:none;width:100%;box-shadow:none;border:0;border-left:2px solid var(--pro-accent);padding-left:8px}.pro-nav-group>a{display:inline-block}.pro-inquiry-wrap{grid-template-columns:1fr;gap:20px}}
diff --git a/examples/mobile-showcase/industrial/assets/pro.css b/examples/mobile-showcase/industrial/assets/pro.css
new file mode 100644
index 0000000..a3b4f8f
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/pro.css
@@ -0,0 +1,7 @@
+:root{--pro-text:#173a57;--pro-muted:#6b8191;--pro-border:#d8e3eb;--pro-background:#fff;--pro-surface:#edf3f7;--pro-accent:#16629a;--pro-hero:#0e3959;--pro-radius:2px;--pro-headings:Arial,sans-serif;--pro-wide:1290px}
+.pro-header{background:#fff}.pro-logo-symbol{color:#14649b;font-size:38px}.pro-utility{background:#0d3a5a;color:#c6e2f1}.pro-header-cta{border-radius:2px;background:#0c5b94}.pro-industrial-hero{background:linear-gradient(90deg,#eff4f8 0%,#e6eef5 60%,#d7e5f0 100%);color:#123b59}.pro-industrial-hero .pro-hero-copy{padding-right:6%}.pro-industrial-hero .pro-hero-copy h1{font-size:clamp(45px,5.3vw,80px)}.pro-industrial-hero .pro-hero-copy h1 em{color:#186fa4}.pro-industrial-hero .pro-hero-art{border-radius:0;background:#0c466d}.pro-technical{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:3%;overflow:hidden;background:repeating-linear-gradient(0deg,transparent,transparent 39px,#ffffff0b 40px),repeating-linear-gradient(90deg,transparent,transparent 39px,#ffffff0b 40px)}.pro-technical>div{width:21%;height:55%;border-radius:60px;border:27px solid #6ba6d4;transform:skewY(-13deg) rotate(-5deg);box-shadow:18px 17px 0 #14324a,0 0 0 9px #bde2fb inset,12px 22px 50px #081c30}.pro-technical>div:nth-child(2){height:72%;width:27%;border-width:34px}.pro-technical strong{position:absolute;bottom:24px;left:28px;color:#aed1e8;font-size:13px;letter-spacing:.17em}.pro-outline-button{color:#0f5988}.pro-category-band{background:#fff}.pro-category-tiles a{background:#eaf2f7;border-top:4px solid #2176aa;min-height:185px;transition:background .2s}.pro-category-tiles a:hover{background:#dce9f2}.pro-category-tiles strong{font-size:21px}.pro-featured{background:#edf3f7}.pro-card{background:#fff;border-radius:2px}.pro-card-cover{background:#d8e6ef}.pro-art-industrial,.pro-art-product{background:linear-gradient(135deg,#aecadf,#e1f1fb)}.pro-art-center{color:#45789b99}.pro-statement{background:#103e5e;color:#fff}.pro-statement .pro-eyebrow{color:#8dc8eb}.pro-statement p{color:#d3e1e9}.pro-statement .pro-text-link{color:#9cdaff}.pro-inner-hero{background:linear-gradient(115deg,#edf4f9 0%,#d9e9f4 100%)}.pro-inner-hero:after{content:'+';position:absolute;right:11%;top:6%;font-size:350px;font-weight:100;color:#15699b10;line-height:1}.pro-article-hero{background:#dceaf4}.pro-article-hero p{color:#537088}.pro-footer{background:#092c45}.pro-end-cta{background:#0a4772}.pro-end-cta a{background:#fff;color:#144b75!important}.pro-404 h1{color:#156d9d}
+
+/* SiteForge V5 commercial signature */
+.pro-industrial-hero .pro-hero-art{background-image:url("./visual/hero.svg")!important;background-position:center;background-size:cover;}
+
+.pro-industrial-hero .pro-hero-art{border-radius:0;box-shadow:25px 25px 0 #ffffff9c}.pro-industrial-hero .pro-hero-art .pro-technical{opacity:0}.pro-category-tiles a{transition:transform .2s,background .2s}.pro-category-tiles a:hover{transform:translateY(-4px);background:#ddeef9}.pro-card{border-radius:0;border:1px solid #cddfe9}.pro-card-cover{background:linear-gradient(145deg,#dceaf0,#c3d9e7)}.pro-art{background:repeating-linear-gradient(0deg,#e2edf4,#e2edf4 24px,#d6e7ef 25px);color:#20577e}.pro-featured{background:#f4f8fb}.pro-statement{background:#103b57;color:#fff}.pro-statement p{color:#d4e5ed}.pro-statement .pro-text-link{color:#a8d8ee}.pro-footer{background:#0e3046;color:#eff5f9}
diff --git a/examples/mobile-showcase/industrial/assets/pro.js b/examples/mobile-showcase/industrial/assets/pro.js
new file mode 100644
index 0000000..9cf100d
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/pro.js
@@ -0,0 +1,72 @@
+'use strict';(()=>{
+ const menu=document.querySelector('[data-pro-menu]'); const nav=document.querySelector('#pro-navigation');
+ if(menu&&nav){menu.addEventListener('click',()=>{const open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));nav.classList.toggle('is-open',open);menu.setAttribute('aria-label',open?'关闭导航':'打开导航');});}
+ // Drawer navigation. The same markup is used across the four professional themes.
+ const mobileNav=document.querySelector('#pro-navigation');
+ let drawerBackdrop=null,drawerClose=null,returnFocus=null;
+ if(menu&&mobileNav){
+ const originalParent=mobileNav.parentNode,originalNext=mobileNav.nextSibling;
+ const heading=document.createElement('div');heading.className='sf-drawer-header';
+ const title=document.createElement('strong');title.textContent='网站导航';
+ drawerClose=document.createElement('button');drawerClose.type='button';drawerClose.className='sf-drawer-close';drawerClose.textContent='×';drawerClose.setAttribute('aria-label','关闭网站导航');
+ heading.append(title,drawerClose);mobileNav.prepend(heading);
+ drawerBackdrop=document.createElement('div');drawerBackdrop.className='sf-mobile-drawer-backdrop';drawerBackdrop.hidden=false;document.body.append(drawerBackdrop);
+ function setDrawer(open,focusBack=true){
+ const isMobile=window.matchMedia('(max-width:1024px)').matches;
+ open=Boolean(open&&isMobile);
+ if(open){returnFocus=document.activeElement;document.body.append(mobileNav);}
+ else if(mobileNav.parentNode!==originalParent){if(originalNext&&originalNext.parentNode===originalParent)originalParent.insertBefore(mobileNav,originalNext);else originalParent.append(mobileNav);}
+ mobileNav.classList.toggle('is-open',open);drawerBackdrop.classList.toggle('is-open',open);
+ document.body.classList.toggle('sf-drawer-open',open);
+ menu.setAttribute('aria-expanded',String(open));menu.setAttribute('aria-label',open?'关闭导航':'打开导航');
+ mobileNav.setAttribute('aria-hidden',String(!open&&isMobile));
+ if('inert' in mobileNav)mobileNav.inert=!open&&isMobile;
+ if(open)drawerClose.focus();
+ else if(focusBack&&returnFocus instanceof HTMLElement){returnFocus.focus();returnFocus=null;}
+ }
+ // Replace the old inline-expansion listener: use capture to stop the old listener.
+ menu.addEventListener('click',e=>{if(!window.matchMedia('(max-width:1024px)').matches)return;e.stopImmediatePropagation();setDrawer(!mobileNav.classList.contains('is-open'));},true);
+ drawerClose.addEventListener('click',()=>setDrawer(false));
+ drawerBackdrop.addEventListener('click',()=>setDrawer(false));
+ document.querySelectorAll('[data-mobile-open-menu]').forEach(b=>b.addEventListener('click',()=>setDrawer(true)));
+ mobileNav.addEventListener('click',e=>{if(e.target.closest('a'))setDrawer(false,false)});
+ document.addEventListener('keydown',e=>{
+ if(e.key==='Escape'&&mobileNav.classList.contains('is-open')){e.preventDefault();setDrawer(false);}
+ if(e.key!=='Tab'||!mobileNav.classList.contains('is-open'))return;
+ const els=[...mobileNav.querySelectorAll('button:not([disabled]),a[href]')].filter(el=>el.getClientRects().length&&!el.closest('[hidden]'));
+ if(!els.length)return;const first=els[0],last=els[els.length-1];
+ if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
+ else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
+ });
+ window.matchMedia('(max-width:1024px)').addEventListener('change',()=>setDrawer(false,false));
+ if('inert' in mobileNav&&window.matchMedia('(max-width:1024px)').matches)mobileNav.inert=true;
+ // Do not strand catalogue links when the old mobile mega-button is hidden.
+ const megaLinks=document.querySelectorAll('[data-pro-mega] a');
+ if(megaLinks.length){const wrap=document.createElement('div');wrap.className='sf-drawer-extra';const h=document.createElement('strong');h.textContent='更多分类与系列';wrap.append(h);
+ [...megaLinks].slice(0,20).forEach(a=>{const clone=a.cloneNode(true);wrap.append(clone);});mobileNav.append(wrap);}
+ }
+ const tabbar=document.querySelector('[data-mobile-tabs]');
+ if(tabbar){tabbar.style.setProperty('--sf-mobile-tab-count',String(tabbar.children.length));}
+ const megaButton=document.querySelector('[data-pro-catalog]'), mega=document.querySelector('[data-pro-mega]');
+ if(megaButton&&mega){function setMega(open){mega.hidden=!open;mega.classList.toggle('is-open',open);megaButton.setAttribute('aria-expanded',String(open));}
+ megaButton.addEventListener('click',()=>setMega(mega.hidden));
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')setMega(false);});
+ document.addEventListener('click',e=>{if(!mega.hidden&&!mega.contains(e.target)&&!megaButton.contains(e.target))setMega(false);});
+ }
+ document.querySelectorAll('.pro-nav-disclose').forEach(button=>{button.addEventListener('click',()=>{let panel=button.parentElement.querySelector('.pro-nav-children');let visible=button.getAttribute('aria-expanded')==='true';button.setAttribute('aria-expanded',String(!visible));panel.hidden=visible;});});
+ document.querySelectorAll('.pro-gallery-thumbs').forEach(container=>{const main=container.parentElement.querySelector('.pro-product-media img');const buttons=[...container.querySelectorAll('[data-gallery-image]')];
+ buttons.forEach(b=>{b.addEventListener('click',()=>{buttons.forEach(x=>x.setAttribute('aria-pressed',String(x===b)));if(main){main.src=b.dataset.galleryImage;main.alt=b.querySelector('img')?.alt||'';}});});
+ if(!main)return;main.tabIndex=0;main.style.cursor='zoom-in';const open=()=>{const pane=document.createElement('div');pane.className='pro-gallery-lightbox';pane.setAttribute('role','dialog');pane.setAttribute('aria-label','产品大图预览');let img=document.createElement('img');img.src=main.src;img.alt=main.alt;const close=document.createElement('button');close.type='button';close.textContent='关闭 ×';pane.append(img,close);document.body.append(pane);const done=()=>{pane.remove();document.removeEventListener('keydown',esc);main.focus();};const esc=e=>{if(e.key==='Escape')done();};document.addEventListener('keydown',esc);close.addEventListener('click',done);pane.addEventListener('click',e=>{if(e.target===pane)done();});close.focus();};main.addEventListener('click',open);main.addEventListener('keydown',e=>{if(e.key==='Enter')open();});
+ });
+ document.querySelectorAll('.pro-filter-scope').forEach(scope=>{
+ const panel=scope.previousElementSibling;if(!panel||!panel.classList.contains('pro-filter-panel'))return;
+ const cards=Array.from(scope.querySelectorAll('.pro-card'));
+ const blank=document.createElement('div');blank.className='pro-filter-empty';blank.hidden=true;blank.textContent='没有找到符合条件的内容,请调整关键词或筛选条件。';scope.append(blank);
+ const q=panel.querySelector('[data-pro-filter-search]'),b=panel.querySelector('[data-pro-filter-brand]'),c=panel.querySelector('[data-pro-filter-category]'),count=panel.querySelector('[data-pro-filter-count]');
+ function options(sel,key){const vals=[...new Set(cards.map(x=>x.dataset[key]||'').filter(Boolean))].sort();vals.forEach(x=>sel.add(new Option(x,x)));if(!vals.length)sel.closest('label').hidden=true;}
+ options(b,'brand');options(c,'category');
+ function run(){const str=q.value.trim().toLocaleLowerCase();let visible=0;
+ cards.forEach(el=>{const hit=el.textContent.toLocaleLowerCase().includes(str)&&(!b.value||el.dataset.brand===b.value)&&(!c.value||el.dataset.category===c.value);el.hidden=!hit;visible+=Number(hit);});count.textContent=`共 ${visible} 条`;blank.hidden=visible!==0;}
+ [q,b,c].forEach(x=>x.addEventListener(x===q?'input':'change',run));run();
+ });
+})();
diff --git a/examples/mobile-showcase/industrial/assets/search-index.json b/examples/mobile-showcase/industrial/assets/search-index.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/search-index.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/examples/mobile-showcase/industrial/assets/site-runtime.js b/examples/mobile-showcase/industrial/assets/site-runtime.js
new file mode 100644
index 0000000..e4715e8
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/site-runtime.js
@@ -0,0 +1,31 @@
+'use strict';
+(()=>{
+ const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
+ const menu=$('.sf-menu-toggle');if(menu){menu.addEventListener('click',()=>{let open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));$('#sf-primary-links')?.classList.toggle('is-open',open);});}
+ // Filter actual static content cards, no API is needed at runtime.
+ $$('.sf-collection').forEach(grid=>{
+ const cards=$$('.sf-item',grid);
+ if(cards.length<2)return;
+ const toolbar=document.createElement('div');toolbar.className='sf-filters';
+ const search=document.createElement('input');search.type='search';search.placeholder='筛选当前列表';search.setAttribute('aria-label','筛选当前列表');
+ const status=document.createElement('span');status.setAttribute('aria-live','polite');status.style.fontSize='12px';
+ const brand=document.createElement('select');brand.setAttribute('aria-label','按品牌筛选');
+ const category=document.createElement('select');category.setAttribute('aria-label','按分类筛选');
+ function fill(el,field,all){el.append(new Option(all,''));const vals=[...new Set(cards.map(x=>x.dataset[field]||'').filter(Boolean))].sort();vals.forEach(v=>el.append(new Option(v,v)));if(!vals.length)el.hidden=true;}
+ fill(brand,'brand','全部品牌');fill(category,'category','全部分类');
+ toolbar.append(search,brand,category,status);grid.parentNode.insertBefore(toolbar,grid);
+ function apply(){let val=search.value.trim().toLocaleLowerCase();let visible=0;cards.forEach(card=>{let hit=card.textContent.toLocaleLowerCase().includes(val)&&(!brand.value||card.dataset.brand===brand.value)&&(!category.value||card.dataset.category===category.value);card.hidden=!hit;if(hit)visible++;});status.textContent=`共 ${visible} 项`;}
+ search.addEventListener('input',apply);brand.addEventListener('change',apply);category.addEventListener('change',apply);apply();
+ });
+ const input=$('#sf-search'),root=$('#sf-search-results'),count=$('#sf-search-count');
+ if(input&&root){
+ let data=[];const params=new URLSearchParams(location.search);input.value=params.get('q')||'';
+ let debounce=null;
+ const render=()=>{let q=input.value.trim().toLocaleLowerCase();let found=q?data.filter(d=>`${d.title} ${d.summary} ${d.brand} ${d.type}`.toLocaleLowerCase().includes(q)).slice(0,100):data.slice(0,30);
+ root.replaceChildren();count.textContent=`${found.length} 条结果${!q?'(显示前30条)':''}`;
+ found.forEach(d=>{let article=document.createElement('article'),a=document.createElement('a'),small=document.createElement('small'),p=document.createElement('p');article.className='sf-search-result';a.textContent=d.title;a.href=d.url.replace(/^\//,'');small.textContent=d.type;p.textContent=d.summary;article.append(a,small,p);root.append(article);});
+ };const base=document.querySelector('base')?.href||location.href;
+ fetch(new URL('search-index.json',base)).then(r=>{if(!r.ok)throw Error('index unavailable');return r.json();}).then(v=>{data=v;render()}).catch(()=>{count.textContent='搜索索引加载失败,请检查站点文件部署路径';});
+ input.addEventListener('input',()=>{clearTimeout(debounce);debounce=setTimeout(render,100)});
+ }
+})();
diff --git a/examples/mobile-showcase/industrial/assets/theme.css b/examples/mobile-showcase/industrial/assets/theme.css
new file mode 100644
index 0000000..7bd34c1
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/theme.css
@@ -0,0 +1 @@
+:root{--blue:#0a3d5c;--sky:#daeaf2;--orange:#ef8f35;--text:#1d3441}*{box-sizing:border-box}body{margin:0;color:var(--text);background:#f8fafb;font-family:Inter,"Segoe UI",Arial,sans-serif;line-height:1.6}a{color:inherit;text-decoration:none}img{max-width:100%;object-fit:contain}.wrap{width:min(1300px,90%);margin:auto}.top{background:#153d56;color:#a7c5d5;font-size:10px;letter-spacing:2px}.top .wrap{display:flex;justify-content:space-between;padding:9px 0}header{background:white;border-bottom:1px solid #e0e8ea}.header-inner{min-height:94px;display:flex;justify-content:space-between;align-items:center;gap:20px}.brand{display:flex;align-items:center;gap:12px;font-size:21px;font-weight:800;letter-spacing:-.5px;line-height:1}.brand-icon{display:grid;place-items:center;width:43px;height:43px;background:var(--blue);color:white;font-size:26px}.brand small{display:block;font-size:10px;color:#718793;letter-spacing:2px;margin-top:9px;font-weight:500}nav{display:flex;gap:25px;font-size:13px;font-weight:650;flex-wrap:wrap}nav a:hover{color:#eb8725}.request{background:#e88c36;color:white;padding:15px 20px;font-size:11px;font-weight:800;white-space:nowrap;letter-spacing:.5px}.hero{background:linear-gradient(107deg,#073653 0%,#0e5270 51%,#14748c 100%);color:white;overflow:hidden}.hero-grid{display:grid;grid-template-columns:55% 45%;min-height:530px}.hero-text{padding:76px 25px 60px 0;position:relative;z-index:1}.kicker{font-size:11px;letter-spacing:2px;font-weight:750;color:#ce882e}.hero .kicker{color:#f5ac57}.kicker i{display:inline-block;width:22px;height:2px;background:#f5ac57;vertical-align:middle;margin-right:10px}.hero h1{font-size:clamp(48px,6vw,86px);letter-spacing:-3px;line-height:1.02;max-width:700px;margin:35px 0 26px}.hero p{max-width:470px;color:#c6e1e9;font-size:17px}.button{display:inline-flex;gap:55px;align-items:center;background:#ef9136;padding:18px 22px;font-size:12px;letter-spacing:1px;font-weight:800;margin-top:26px}.button b{font-size:18px}.hero-tags{display:flex;gap:22px;margin-top:60px;font-size:10px;color:#85bfcb;letter-spacing:1px}.hero-art{position:relative;display:grid;place-items:center;overflow:hidden;background:radial-gradient(circle at center,#2c94a6 2%,#11617b 45%,transparent 70%)}.hero-art:before,.hero-art:after{content:"";position:absolute;width:540px;height:540px;border:1px solid #ffffff25;transform:rotate(45deg)}.hero-art:after{width:380px;height:380px}.hero-art img{position:relative;z-index:3;width:100%;max-height:480px;filter:drop-shadow(0 20px 20px #002a3b55)}.hero-art>span{position:relative;z-index:3;color:#c6eaf0;font-size:25px;letter-spacing:8px;text-align:center;font-weight:300}.ring{position:absolute;width:430px;height:430px;border:28px solid #ffffff13;border-radius:50%;transform:rotateX(55deg) rotate(45deg)}.ring-2{width:290px;height:290px;border-color:#ffffff1d}.ring-3{width:150px;height:150px;border-color:#fba74b55}.subhero{padding:80px 0;background:linear-gradient(110deg,#073e5d,#0d6981);color:white}.subhero h1{font-size:clamp(40px,5.3vw,68px);line-height:1.1;margin:20px 0}.subhero p{color:#b8d6df}.main{padding:58px 0 100px}.metrics{display:grid;grid-template-columns:repeat(3,1fr);background:white;margin-top:-90px;position:relative;box-shadow:0 15px 45px #0b4f7515}.metrics>div{padding:25px 34px;display:flex;align-items:center;gap:18px;border-right:1px solid #eef3f5}.metrics strong{color:#dc7e2d;font-size:30px}.metrics span{letter-spacing:1px;font-size:11px;font-weight:800}.titlebar{margin:65px 0 28px;display:flex;justify-content:space-between;align-items:end}.titlebar h2{font-size:37px;margin:12px 0 0}.titlebar>a{color:#ba6d2f;font-size:12px;font-weight:800}.product-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:23px}.product{border:1px solid #e0e8ed;background:white}.product:hover{box-shadow:0 15px 35px #194b6518}.product-img{height:225px;display:flex;align-items:center;justify-content:center;background:linear-gradient(125deg,#e8f0f4,#cadce6);overflow:hidden}.product-img img{height:100%;width:100%;object-fit:contain}.technical-graphic{width:110px;height:110px;border:18px solid #96b8c8;border-radius:50%;position:relative;box-shadow:0 20px 0 #244d6120}.technical-graphic span{position:absolute;color:#e9fbff}.technical-graphic span:first-child{top:-56px;left:-50px}.technical-graphic span:nth-child(2){top:-56px;right:-50px}.technical-graphic span:nth-child(3){bottom:-55px;left:-45px}.technical-graphic span:nth-child(4){bottom:-55px;right:-45px}.product-info{padding:25px}.product-info>span{font-size:9px;color:#c17a38;letter-spacing:1.5px;font-weight:700}.product-info h3{font-size:21px;margin:8px 0}.product-info p{color:#6d8792;font-size:13px}.arrow{border-top:1px solid #e6eef1;margin-top:25px;padding-top:18px;display:flex;justify-content:space-between;font-size:11px;letter-spacing:1px;font-weight:800;color:#0c6788}.rich-content{margin:30px 0}.rich-content p{color:#617787}.section{padding:40px 0}.feature-grid,.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.feature{background:white;padding:25px;border-top:4px solid #f09c52}.stat strong{display:block;font-size:40px}.stat span{color:#78909c}.cta{padding:50px;background:#0e5675;color:white}.detail-image img{max-height:560px}footer{background:#0f334a;color:white;padding:65px 0 25px}.footer-cols{display:flex;justify-content:space-between;gap:40px}.footer-cols h2{font-size:26px}.footer-cols p{color:#96b1bf}.bottom{border-top:1px solid #36596d;margin-top:65px;padding-top:22px;display:flex;justify-content:space-between;letter-spacing:1px;font-size:10px;color:#9cb9c7}@media(max-width:940px){.header-inner{flex-wrap:wrap;padding:16px 0}nav{order:3;width:100%}.request{display:none}.hero-grid{grid-template-columns:1fr}.hero-text{padding-right:0}.hero-art{min-height:300px}.metrics{margin-top:0}}@media(max-width:660px){.product-grid,.metrics,.feature-grid,.stats{grid-template-columns:1fr}.hero h1{font-size:51px}.hero-tags{flex-wrap:wrap}.footer-cols,.bottom{flex-direction:column}.top span:last-child{display:none}.subhero{padding:60px 0}}
diff --git a/examples/mobile-showcase/industrial/assets/visual/hero.svg b/examples/mobile-showcase/industrial/assets/visual/hero.svg
new file mode 100644
index 0000000..5c363ca
--- /dev/null
+++ b/examples/mobile-showcase/industrial/assets/visual/hero.svg
@@ -0,0 +1 @@
+
diff --git a/examples/mobile-showcase/industrial/index.html b/examples/mobile-showcase/industrial/index.html
new file mode 100644
index 0000000..d2a92ca
--- /dev/null
+++ b/examples/mobile-showcase/industrial/index.html
@@ -0,0 +1,24 @@
+建筑材料跳转至正文
+
+
+
+ENGINEERED FOR WHAT'S NEXT / 001建筑材料 BUILT TO PERFORM.
高性能工程材料与解决方案
+
+
+
+
+
+LET'S CONNECT从这里开始下一步。
高性能工程材料与解决方案
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/industrial/listing.html b/examples/mobile-showcase/industrial/listing.html
new file mode 100644
index 0000000..a91fefa
--- /dev/null
+++ b/examples/mobile-showcase/industrial/listing.html
@@ -0,0 +1,22 @@
+全部产品跳转至正文
+
+
+
+DISCOVER / COLLECTION全部产品
分类查看产品与解决方案
+
+
+
+LET'S CONNECT从这里开始下一步。
高性能工程材料与解决方案
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/industrial/product.html b/examples/mobile-showcase/industrial/product.html
new file mode 100644
index 0000000..1cc203f
--- /dev/null
+++ b/examples/mobile-showcase/industrial/product.html
@@ -0,0 +1,17 @@
+标准系列产品跳转至正文
+
+
+
+
+
PRODUCT DETAILS / EXAMPLE标准系列产品
面向全球客户的精选示范产品
品牌 · EXAMPLE
咨询该产品 ↗
+
+
+
+LET'S CONNECT从这里开始下一步。
高性能工程材料与解决方案
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/minimal/article.html b/examples/mobile-showcase/minimal/article.html
new file mode 100644
index 0000000..c5a1cdb
--- /dev/null
+++ b/examples/mobile-showcase/minimal/article.html
@@ -0,0 +1,13 @@
+企业动态与行业资讯跳转至正文
+
+
+
+LATEST NEWS / INSIGHTS企业动态与行业资讯
了解最新项目与服务进展
项目背景
这是一段用于检查手机端资讯排版的样例正文。适当的字距、行距和图片宽度能够提升移动阅读体验。
← 返回新闻
+
+
+LET'S CONNECT从这里开始下一步。
简单而精致的产品体验
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/minimal/assets/blocks.css b/examples/mobile-showcase/minimal/assets/blocks.css
new file mode 100644
index 0000000..9ce1279
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/blocks.css
@@ -0,0 +1,29 @@
+/* Portable SiteForge blocks, loaded with every theme (all styles scoped to sf-). */
+.sf-section{background:var(--sf-bg,transparent);color:var(--sf-fg,inherit);padding:var(--sf-pad,82px) 0;margin:0}.sf-section:nth-of-type(even){background:var(--sf-bg,#f4f6f9)}.sf-container{max-width:1200px;margin:auto;padding:0 32px}.sf-section h1,.sf-section h2,.sf-section h3,.sf-section p{color:inherit;line-height:1.3}.sf-section h1{font-size:clamp(38px,5vw,76px);font-weight:790;letter-spacing:-.052em}.sf-section h2{font-size:clamp(27px,3.2vw,44px);font-weight:760;letter-spacing:-.037em}.sf-kicker{display:block;font-size:12px;letter-spacing:.17em;text-transform:uppercase;font-weight:740;color:var(--accent,#3569d3);margin-bottom:18px}.sf-hero-grid,.sf-image-text{display:grid;grid-template-columns:1.08fr .92fr;gap:68px;align-items:center}.sf-hero-copy p{font-size:18px;line-height:1.75;max-width:520px;opacity:.75}.sf-hero-img,.sf-image-side{overflow:hidden;border-radius:9px;min-height:380px;background:linear-gradient(130deg,#d5e1ee,#eef3f9);display:grid;place-items:center}.sf-hero-img img,.sf-image-side img{width:100%;height:100%;max-height:580px;object-fit:cover}.sf-hero-abstract{font-size:110px;color:#a7bdd4}.sf-button{display:inline-flex;align-items:center;justify-content:center;gap:28px;background:var(--accent,#315ee2);color:white!important;padding:16px 26px;border-radius:6px;font-size:14px;text-decoration:none;font-weight:720;margin-top:24px}.sf-section-head{display:flex;align-items:end;justify-content:space-between;margin-bottom:32px;gap:24px}.sf-section-head h2{margin:0}.sf-section-head>a,.sf-link{color:var(--accent,#3569d3);font-weight:700;text-decoration:none}.sf-prose{font-size:16px;line-height:1.85;max-width:930px}.sf-prose p{line-height:1.85}.sf-features,.sf-collection,.sf-stats{display:grid;gap:20px}.sf-features{grid-template-columns:repeat(3,minmax(0,1fr))}.sf-feature{border:1px solid #e0e6eb;padding:32px 28px;border-radius:8px;background:#fff;color:#253247}.sf-feature>span{font-size:12px;font-weight:800;color:var(--accent,#346add)}.sf-feature h3{font-size:22px;margin:30px 0 8px}.sf-feature p{font-size:14px;line-height:1.7;color:#647181}.sf-stats{grid-template-columns:repeat(4,1fr);padding:40px 0;border-top:1px solid #b7c0cd66;border-bottom:1px solid #b7c0cd66}.sf-stats>div{display:flex;flex-direction:column;gap:8px}.sf-stats strong{font-size:clamp(28px,4vw,53px);letter-spacing:-.04em}.sf-stats span{font-size:12px;opacity:.65;letter-spacing:.04em}.sf-collection{grid-template-columns:repeat(var(--sf-cols,3),minmax(0,1fr))}.sf-item{display:block;text-decoration:none;color:#233044;background:#fff;border:1px solid #e8edf1;border-radius:8px;overflow:hidden;transition:transform .2s,box-shadow .2s}.sf-item:hover{transform:translateY(-4px);box-shadow:0 18px 38px #344d7018}.sf-item-img{height:210px;overflow:hidden;background:#e4eaf0}.sf-item-img img{height:100%;width:100%;object-fit:cover}.sf-placeholder{display:grid;place-items:center;width:100%;height:100%;min-height:190px;background:linear-gradient(130deg,#e2eaf2,#c9d7e6);font-size:72px;color:#8399b2;font-weight:800}.sf-large{min-height:360px}.sf-item-copy{padding:25px}.sf-item-copy small{text-transform:uppercase;letter-spacing:.13em;color:var(--accent,#376edc)}.sf-item-copy h3{font-size:22px;margin:12px 0 7px}.sf-item-copy p{font-size:14px;line-height:1.6;color:#7a8796;min-height:42px}.sf-item-copy span{font-size:12px;color:var(--accent,#376edc);font-weight:800}.sf-image-text>div>p{font-size:17px;line-height:1.8;max-width:520px;opacity:.7}.sf-reverse>div:first-child{order:2}.sf-gallery{display:grid;grid-template-columns:repeat(3,1fr);gap:15px}.sf-gallery img{width:100%;height:230px;object-fit:cover;border-radius:6px}.sf-faq{max-width:920px}.sf-faq details{border-bottom:1px solid #dce3eb;padding:22px 0}.sf-faq summary{font-size:17px;cursor:pointer;font-weight:700}.sf-faq p{color:#647181;line-height:1.75}.sf-cta-wrap{display:flex;justify-content:space-between;align-items:center;gap:24px}.sf-cta-wrap h2{margin:0}.sf-cta-wrap p{font-size:16px;opacity:.75}.sf-section a{cursor:pointer}.sf-muted{opacity:.65}
+@media(max-width:820px){.sf-container{padding:0 20px}.sf-section{padding:50px 0}.sf-hero-grid,.sf-image-text{grid-template-columns:1fr;gap:26px}.sf-reverse>div:first-child{order:initial}.sf-hero-img,.sf-image-side{min-height:250px}.sf-features,.sf-collection{grid-template-columns:repeat(2,minmax(0,1fr))}.sf-stats{grid-template-columns:repeat(2,1fr)}.sf-cta-wrap{align-items:flex-start;flex-direction:column}}
+@media(max-width:540px){.sf-features,.sf-collection,.sf-gallery{grid-template-columns:1fr}.sf-section-head{display:block}.sf-section-head>a{display:block;margin-top:13px}.sf-item-img{height:220px}.sf-hero-grid{gap:22px}.sf-hero-copy p{font-size:16px}.sf-stats{gap:28px}}
+
+.sf-attributes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 32px;border-top:1px solid #e5eaf0}.sf-attribute{display:flex;justify-content:space-between;align-items:center;gap:18px;border-bottom:1px solid #e5eaf0;padding:19px 10px;font-size:14px}.sf-attribute span{color:#748497}.sf-attribute strong{font-weight:720;text-align:right}@media(max-width:640px){.sf-attributes{grid-template-columns:1fr}}
+
+/* SiteForge responsive no-code grid container */
+.sf-layout-grid{display:grid;grid-template-columns:var(--sf-layout-cols,1fr 1fr);gap:var(--sf-layout-gap,24px);align-items:stretch}
+.sf-layout-column{min-width:0}.sf-layout-column>.sf-section{padding:0;margin:0 0 18px}.sf-layout-column .sf-container{width:100%;padding:0}
+.sf-layout-column>.sf-section:last-child{margin-bottom:0}
+@media(max-width:820px){.sf-layout-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}
+@media(max-width:540px){.sf-layout-grid{grid-template-columns:minmax(0,1fr)!important}}
+
+/* V8.6: accessible, script-free commercial design blocks */
+.sf-slides{display:flex;gap:18px;overflow-x:auto;scroll-snap-type:x mandatory;padding:6px 0 18px;scrollbar-width:thin}
+.sf-slide{flex:0 0 min(78%,720px);min-width:220px;margin:0;scroll-snap-align:start}
+.sf-slide img{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:10px}
+.sf-slide figcaption{padding:10px 2px;opacity:.7;font-size:14px}
+.sf-video{aspect-ratio:16/9;max-width:1000px;background:#ebf0f5;border-radius:10px;overflow:hidden}
+.sf-video iframe{width:100%;height:100%;border:0}
+.sf-tab{padding:19px 0;border-bottom:1px solid #dde4ec;max-width:960px}
+.sf-tab summary{font-size:18px;font-weight:700;cursor:pointer}.sf-tab p{max-width:850px;line-height:1.8}
+.sf-prices{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px}
+.sf-price{padding:28px;background:#fff;border:1px solid #dee5ef;border-radius:10px;color:#29394b}
+.sf-price strong{display:block;font-size:27px;margin:14px 0}.sf-price li{margin:11px 0}.sf-price ul{padding-left:19px}
+.sf-map-address{font-style:normal;font-size:18px;white-space:pre-line;line-height:1.8}
+@media (prefers-reduced-motion:reduce){.sf-slides{scroll-behavior:auto}.sf-item{transition:none}}
+@media(max-width:540px){.sf-slide{flex-basis:92%}}
diff --git a/examples/mobile-showcase/minimal/assets/commercial.css b/examples/mobile-showcase/minimal/assets/commercial.css
new file mode 100644
index 0000000..353bee4
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/commercial.css
@@ -0,0 +1,84 @@
+/* SiteForge commercial theme UX layer: accessibility, layout rhythm, media, responsive navigation. */
+:root{--sf-shadow:0 20px 55px rgba(17,33,55,.08);--sf-shadow-hover:0 26px 65px rgba(17,33,55,.15)}
+html{scroll-padding-top:104px}body{font-feature-settings:"kern";text-rendering:optimizeLegibility}
+a,button,input,select,textarea{-webkit-tap-highlight-color:transparent}
+a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--pro-accent);outline-offset:4px}
+.pro-wrap{width:100%;max-width:min(var(--pro-wide),100%)}.pro-header{isolation:isolate;box-shadow:0 1px 0 rgba(15,35,60,.035)}.pro-head-inner{gap:clamp(15px,2vw,29px)}.pro-logo{min-width:0;flex-shrink:0;gap:12px}.pro-brand-logo{max-width:155px;max-height:46px;width:auto;height:auto;object-fit:contain}.pro-logo-name{overflow-wrap:anywhere}.pro-navigation{gap:clamp(11px,1.6vw,24px)}.pro-header-cta{white-space:nowrap}
+.pro-mobile-toggle,.pro-catalog-toggle,.pro-nav-disclose{border:0;background:transparent;color:inherit;line-height:1.2}
+.pro-mobile-toggle{min-height:44px;min-width:44px;border:1px solid currentColor;border-radius:6px;font-size:19px}.pro-nav-group{position:relative;display:flex;align-items:center;gap:2px}
+.pro-nav-disclose{padding:9px;font-size:13px}.pro-nav-children{position:absolute;top:100%;left:0;min-width:215px;max-width:330px;display:grid;gap:2px;padding:13px;box-shadow:var(--sf-shadow);border-radius:10px;z-index:45;background:var(--pro-background);border:1px solid var(--pro-border);color:var(--pro-text)}
+.pro-nav-children[hidden],.pro-mega[hidden]{display:none!important}.pro-nav-children a{display:block;font-size:13px;white-space:normal;padding:10px;border-radius:5px}.pro-nav-children a:hover{background:var(--pro-surface)}
+.pro-catalog-toggle{padding:11px 3px;white-space:nowrap;font-size:12px;font-weight:700}.pro-mega{position:absolute;top:100%;left:50%;transform:translateX(-50%);width:min(100%,var(--pro-wide));box-shadow:var(--sf-shadow);z-index:41;display:grid;grid-template-columns:215px 1fr;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);border-radius:0 0 15px 15px;padding:29px;align-items:start}.pro-mega>span{font-size:12px;letter-spacing:.06em}.pro-mega>div{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px 22px;overflow:visible}.pro-mega>div a{white-space:normal;padding:10px 5px;border-bottom:1px solid var(--pro-border)}
+.pro-home-hero{overflow:hidden}.pro-home-hero .pro-home-grid{min-height:660px;gap:5%}.pro-home-hero .pro-hero-copy{position:relative;z-index:2}.pro-home-hero .pro-hero-copy h1{font-size:clamp(46px,5.7vw,92px);line-height:1.06;letter-spacing:-.064em;max-width:15ch}.pro-home-hero .pro-hero-copy h1 em{font-style:normal;display:block}.pro-home-hero .pro-hero-copy p{font-size:clamp(15px,1.3vw,19px);line-height:1.75;max-width:44ch}.pro-home-hero .pro-hero-art{min-width:0;min-height:425px;position:relative;isolation:isolate}.pro-home-hero .pro-hero-art>img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}.pro-hero-art:has(.pro-abstract-car),.pro-hero-art:has(.pro-technical),.pro-hero-art:has(.pro-corporate-shape){background-position:center;background-size:cover;background-repeat:no-repeat}.pro-abstract-car,.pro-technical,.pro-corporate-shape{position:relative;z-index:1;pointer-events:none}
+.pro-hero-actions{display:flex;flex-wrap:wrap;align-items:center;gap:15px}.pro-button,.pro-header-cta,.pro-outline-button{min-height:48px;transition:transform .24s ease,box-shadow .24s ease,opacity .24s ease}.pro-button:hover,.pro-header-cta:hover,.pro-outline-button:hover{transform:translateY(-2px);box-shadow:0 10px 22px rgba(0,0,0,.10)}
+.pro-section{padding-top:clamp(76px,7vw,112px)!important;padding-bottom:clamp(76px,7vw,112px)!important}.pro-section-head{gap:16px}.pro-section-head h2{font-size:clamp(32px,3.5vw,53px);max-width:22ch}.pro-section-head .pro-text-link{align-self:end}
+.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}.pro-card{min-width:0;display:flex;flex-direction:column;transition:transform .28s ease,box-shadow .28s ease;border:1px solid var(--pro-border);background:var(--pro-background);overflow:hidden}.pro-card:hover{transform:translateY(-6px);box-shadow:var(--sf-shadow-hover)}.pro-card-cover{aspect-ratio:1.42;overflow:hidden;background:var(--pro-surface);position:relative}.pro-card-cover .pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .45s ease}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-card-body{display:flex;flex-direction:column;flex:1}.pro-card-body h3{font-size:clamp(19px,1.6vw,24px);line-height:1.26}.pro-card-body p{font-size:14px}.pro-card-link{margin-top:auto;padding-top:14px;font-size:13px;font-weight:700}
+.pro-art{min-height:100%;width:100%;position:relative;overflow:hidden;display:grid;place-items:center;isolation:isolate}.pro-art-ring{width:60%;aspect-ratio:1;border:1px solid currentColor;opacity:.13;border-radius:50%;transform:rotate(20deg);box-shadow:0 0 0 23px #ffffff10,0 0 0 57px #ffffff07}.pro-art-center{font-size:clamp(38px,5vw,80px);position:absolute;font-weight:800;letter-spacing:-.065em;opacity:.65}.pro-art-label{position:absolute;bottom:19px;left:22px;font-size:10px;letter-spacing:.22em;opacity:.7}
+.pro-breadcrumb{display:flex;gap:11px;flex-wrap:wrap;font-size:12px;align-items:center}.pro-breadcrumb a{text-decoration:underline;text-underline-offset:4px}.pro-product-hero{align-items:start;gap:clamp(28px,5vw,80px)}.pro-product-media{aspect-ratio:1.12;background:var(--pro-surface);overflow:hidden}.pro-product-media>img,.pro-product-media>.pro-art{width:100%;height:100%;object-fit:contain}.pro-product-summary{padding:clamp(12px,3vw,50px) 0}.pro-product-summary h1{font-size:clamp(36px,4.3vw,72px)}.pro-detail-layout{border-top:1px solid var(--pro-border);padding-top:50px}.pro-specs>div{display:grid;grid-template-columns:minmax(100px,40%) 1fr;gap:10px;align-items:baseline;padding:15px 0;border-bottom:1px solid var(--pro-border)}.pro-specs strong{overflow-wrap:anywhere}.pro-gallery-thumbs button{flex-basis:96px;height:76px}.pro-gallery-lightbox{backdrop-filter:blur(6px)}
+.pro-filter-panel{display:flex;align-items:end;flex-wrap:wrap;gap:16px;background:var(--pro-surface);border:1px solid var(--pro-border);padding:20px;margin-bottom:25px;border-radius:var(--pro-radius)}.pro-filter-panel label{display:grid;gap:7px;min-width:180px;flex:1}.pro-filter-panel input,.pro-filter-panel select{width:100%;background:var(--pro-background);border:1px solid var(--pro-border);padding:12px 14px;color:var(--pro-text);border-radius:6px}.pro-filter-count{white-space:nowrap;font-size:13px;padding:12px 0}.pro-card[hidden]{display:none!important}.pro-filter-empty{padding:38px;border:1px dashed var(--pro-border);text-align:center;grid-column:1/-1}
+.pro-inquiry input,.pro-inquiry textarea{min-height:46px}.pro-inquiry textarea{min-height:150px}.pro-inquiry-wrap{gap:clamp(32px,7vw,110px)}.pro-inquiry .pro-button{border:0;justify-self:start}.pro-footer-links a{padding-block:5px;display:inline-block}
+.pro-end-cta{overflow:hidden}.pro-end-cta h2{font-size:clamp(30px,4vw,64px)}
+.pro-rich{line-height:1.85}.pro-rich img{border-radius:var(--pro-radius);margin-block:24px}.pro-rich table{width:100%;display:block;overflow:auto;border-collapse:collapse}.pro-rich td,.pro-rich th{border:1px solid var(--pro-border);padding:10px 14px}.pro-rich a{text-decoration:underline;text-underline-offset:3px}
+@media(min-width:1025px){.pro-navigation>.pro-nav-group:hover .pro-nav-children:not([hidden]){visibility:visible}}
+@media(max-width:1150px){.pro-head-inner{gap:15px}.pro-navigation{gap:12px}.pro-logo-name{font-size:15px}.pro-header-cta{padding:13px 16px}}
+@media(max-width:1024px){.pro-head-inner{min-height:72px;flex-wrap:wrap}.pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto}.pro-navigation{display:none;width:100%;flex-basis:100%;flex-direction:column;align-items:stretch;gap:0;order:5;margin:0;padding:8px 0 20px;border-top:1px solid var(--pro-border)}.pro-navigation.is-open{display:flex}.pro-navigation>a,.pro-nav-group>a{padding:14px 8px;font-size:15px}.pro-nav-group{display:flex;flex-wrap:wrap;justify-content:space-between}.pro-nav-children{position:static;flex-basis:100%;max-width:none;width:100%;box-shadow:none;border-radius:0;border-left:2px solid var(--pro-accent);margin:0 0 12px 8px}.pro-nav-disclose{min-width:44px;min-height:44px}.pro-catalog-toggle{margin-left:auto}.pro-mega{top:100%;display:block;padding:20px}.pro-mega>div{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:12px}.pro-home-hero .pro-home-grid{min-height:560px}.pro-home-hero .pro-hero-art{min-height:325px}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
+@media(max-width:700px){.pro-wrap{padding-left:20px;padding-right:20px}.pro-utility{display:none}.pro-head-inner{min-height:66px}.pro-logo-name{max-width:140px;font-size:14px}.pro-brand-logo{max-width:125px}.pro-header-cta{display:none}.pro-catalog-toggle{font-size:11px;padding-inline:7px;margin-left:0}.pro-home-hero .pro-home-grid{display:flex;flex-direction:column;gap:36px;padding-top:48px;padding-bottom:48px}.pro-home-hero .pro-hero-copy h1{font-size:clamp(43px,10vw,68px)}.pro-home-hero .pro-hero-copy p{font-size:15px}.pro-home-hero .pro-hero-art{width:100%;min-height:290px}.pro-home-hero .pro-hero-bottom{display:none}.pro-section{padding-top:68px!important;padding-bottom:68px!important}.pro-section-head{align-items:flex-start}.pro-grid{grid-template-columns:1fr;gap:18px}.pro-card-cover{aspect-ratio:1.45}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-inquiry-wrap{display:grid;grid-template-columns:1fr;gap:30px}.pro-product-summary{padding:0}.pro-specs>div{grid-template-columns:1fr 1fr}.pro-mega{top:100%;max-height:65vh;overflow-y:auto}.pro-mega>div{grid-template-columns:1fr}.pro-filter-panel{display:grid;grid-template-columns:1fr;gap:12px}.pro-filter-panel label{min-width:0}.pro-footer-top{display:grid;grid-template-columns:1fr;gap:27px}.pro-footer-bottom{gap:12px;flex-wrap:wrap}}
+@media(max-width:410px){.pro-logo-name{max-width:115px}.pro-logo-symbol{font-size:28px}.pro-catalog-toggle{font-size:10px}.pro-mobile-toggle{min-width:42px}}
+@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation-duration:.01ms!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}
+
+/* Accessible static catalogue pagination for large inventories. */
+.pro-pages-nav{display:flex;gap:1rem;justify-content:center;align-items:center;margin:2rem 0;flex-wrap:wrap}
+.pro-pages-nav a{padding:.7rem 1.1rem;border:1px solid currentColor;border-radius:.4rem;text-decoration:none}
+.pro-pages-nav a:focus-visible{outline:3px solid var(--accent,#2563eb);outline-offset:3px}
+.pro-pages-nav span{font-size:.875rem}
+
+/* P6 international sites: small, keyboard-operable language switch. */
+.sf-language-switch{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;margin:8px 12px;padding:4px;border:1px solid rgba(120,130,145,.22);border-radius:10px;background:rgba(255,255,255,.08)}
+.sf-language-switch a{font:inherit;font-size:12px;line-height:1.5;padding:4px 7px;border-radius:6px;color:inherit;text-decoration:none}
+.sf-language-switch a[aria-current="page"]{font-weight:700;text-decoration:underline;text-underline-offset:3px}
+.sf-language-switch a:focus-visible{outline:2px solid #2563eb;outline-offset:3px}
+@media(max-width:640px){.sf-language-switch{margin:6px 0;width:auto}}
+
+/* V8.21.3 — Four-theme unified mobile off-canvas navigation & optional tab bar.
+ Desktop remains unchanged. Phone / tablet use one accessible drawer. */
+.sf-mobile-tabs{display:none!important}
+.sf-drawer-header,.sf-mobile-drawer-backdrop,.sf-drawer-extra{display:none}
+@media(max-width:1024px){
+ .pro-head-inner{flex-wrap:nowrap!important;gap:12px!important}
+ .pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto!important;flex:0 0 44px;min-width:44px;min-height:44px;position:relative;z-index:3}
+ .pro-catalog-toggle{display:none!important}
+ .pro-navigation,.pro-navigation.is-open{display:flex!important;position:fixed!important;inset:0 0 0 auto!important;width:min(88vw,360px)!important;height:100dvh!important;height:100vh;max-height:100dvh!important;flex:unset!important;flex-direction:column!important;align-items:stretch!important;order:unset!important;margin:0!important;padding:0 0 calc(20px + env(safe-area-inset-bottom,0px))!important;overflow-x:hidden;overflow-y:auto!important;background:var(--pro-background,#fff)!important;color:var(--pro-text,#19283c)!important;border:0!important;border-left:1px solid var(--pro-border,#ddd)!important;box-shadow:-15px 0 45px #0002!important;z-index:1201!important;transform:translateX(105%);visibility:hidden;pointer-events:none;transition:transform .24s ease,visibility .24s ease!important;overscroll-behavior:contain}
+ .pro-navigation.is-open{transform:translateX(0);visibility:visible;pointer-events:auto}
+ .sf-drawer-header{display:flex!important;align-items:center;justify-content:space-between;gap:12px;padding:20px 18px;border-bottom:1px solid var(--pro-border,#dde3e9);font-size:17px;font-weight:750;position:sticky;top:0;background:var(--pro-background,#fff);z-index:2}
+ .sf-drawer-close{min-width:44px;min-height:44px;border:1px solid var(--pro-border,#dde3e9);color:inherit;background:transparent;border-radius:10px;font-size:24px;cursor:pointer}
+ .sf-mobile-drawer-backdrop{display:block;position:fixed;inset:0;z-index:1200;background:#0b1b31a3;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .24s ease,visibility .24s ease}
+ .sf-mobile-drawer-backdrop.is-open{opacity:1;visibility:visible;pointer-events:auto}
+ body.sf-drawer-open{overflow:hidden!important}
+ .pro-navigation>a,.pro-navigation .pro-nav-group>a,.pro-navigation .sf-drawer-extra>a{display:flex;align-items:center;min-height:50px;padding:13px 20px!important;font-size:15px!important;color:inherit!important;white-space:normal;border-bottom:1px solid var(--pro-border,#dde3e9)!important;text-decoration:none!important}
+ .pro-navigation .pro-nav-group{display:flex;position:relative;flex-wrap:wrap!important;width:100%;gap:0;align-items:center;justify-content:space-between}
+ .pro-navigation .pro-nav-group>a{flex:1;min-width:0}
+ .pro-navigation .pro-nav-disclose{margin-right:12px;min-width:44px;min-height:44px;border-radius:9px;background:var(--pro-surface,#f4f6f8)}
+ .pro-navigation .pro-nav-children{position:static!important;width:100%!important;max-width:100%!important;box-shadow:none!important;border-left:3px solid var(--pro-accent,#e77730);border-radius:0!important;margin:0 0 8px 14px!important;flex-basis:calc(100% - 14px)!important;background:var(--pro-surface,#f6f8fa)}
+ .pro-navigation .pro-nav-children a{padding:12px 16px!important;font-size:14px!important;color:inherit!important;min-height:44px}
+ .sf-drawer-extra{display:block;padding:12px 0 5px}.sf-drawer-extra strong{display:block;font-size:12px;letter-spacing:.06em;padding:12px 20px;opacity:.65}.sf-drawer-extra a{font-size:14px!important}
+ .sf-drawer-close:focus-visible,.pro-mobile-toggle:focus-visible,.pro-nav-disclose:focus-visible,.sf-mobile-tab:focus-visible{outline:3px solid var(--pro-accent,#ff8200);outline-offset:2px}
+}
+@media(max-width:767px){
+ .pro-header-cta{display:none!important}
+ .pro-head-inner .pro-logo{min-width:0;max-width:calc(100% - 65px)}
+ .pro-logo-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .pro-home-hero .pro-home-grid{min-height:0!important}
+ .pro-home-hero .pro-hero-art{min-height:230px}
+ .pro-card,.pro-card-cover,.pro-product-media{max-width:100%;min-width:0}
+ .pro-rich img,.pro-content img,.sf-canvas img{max-width:100%;height:auto}
+ .pro-rich table{max-width:100%;overflow:auto}
+ .pro-grid,.pro-footer-top{min-width:0}
+ .sf-mobile-tabs{display:grid!important;position:fixed;z-index:1100;bottom:0;left:0;right:0;grid-template-columns:repeat(var(--sf-mobile-tab-count,4),minmax(0,1fr));align-items:stretch;padding:5px 6px calc(5px + env(safe-area-inset-bottom,0px));min-height:63px;background:var(--pro-background,#fff);color:var(--pro-text,#263446);border-top:1px solid var(--pro-border,#dfe4eb);box-shadow:0 -6px 24px #13223914}
+ .sf-mobile-tab{appearance:none;border:0!important;background:transparent!important;color:inherit!important;text-decoration:none!important;padding:6px 2px;min-height:50px;min-width:0;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:2px;border-radius:9px;cursor:pointer;font-family:inherit}
+ .sf-tab-icon{font-size:23px;line-height:1.15;font-weight:500}
+ .sf-tab-label{font-size:11px;font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .sf-mobile-tab[aria-current=page]{color:var(--pro-accent,#e47730)!important;background:var(--pro-surface,#f5f6f8)!important}
+ body:has(> .sf-mobile-tabs){padding-bottom:calc(76px + env(safe-area-inset-bottom,0px))!important}
+}
+@media(min-width:768px){.sf-mobile-tabs{display:none!important}}
+@media(prefers-reduced-motion:reduce){.pro-navigation,.sf-mobile-drawer-backdrop{transition:none!important}}
diff --git a/examples/mobile-showcase/minimal/assets/custom.css b/examples/mobile-showcase/minimal/assets/custom.css
new file mode 100644
index 0000000..e18a2d4
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/custom.css
@@ -0,0 +1 @@
+body .pro-utility[hidden]{display:none!important}@media(max-width:1023px){.sf-canvas .sf-section{padding-block:64px}}@media(max-width:767px){.sf-canvas .sf-section{padding-block:40px}.pro-wrap,.sf-container{padding-left:20px;padding-right:20px}body .pro-utility{display:none!important}}
\ No newline at end of file
diff --git a/examples/mobile-showcase/minimal/assets/factory.css b/examples/mobile-showcase/minimal/assets/factory.css
new file mode 100644
index 0000000..154ffb3
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/factory.css
@@ -0,0 +1,27 @@
+/* SiteForge trusted global shell. Site themes remain visually independent. */
+:root{--sf-shell:#12243d;--sf-shell-accent:#dcae71;--sf-shell-soft:#edf2f8;--sf-shell-fg:#173049}
+html{scroll-behavior:smooth}body{overflow-x:hidden}body>.header,body>.utility,body>.top,body>header,body>footer{display:none!important}
+.sf-theme-industrial{--sf-shell:#114e79;--sf-shell-accent:#92d5ed;--sf-shell-soft:#ebf6fa;--sf-shell-fg:#0c4267}
+.sf-theme-corporate{--sf-shell:#232d62;--sf-shell-accent:#b8bdff;--sf-shell-soft:#f3f3fa;--sf-shell-fg:#333468}
+.sf-theme-minimal{--sf-shell:#1e1e1c;--sf-shell-accent:#ded6c5;--sf-shell-soft:#f6f4ef;--sf-shell-fg:#252520}
+.sf-site-head{position:relative;z-index:25;background:var(--sf-shell);color:#fff;font-family:system-ui,-apple-system,sans-serif}
+.sf-head-inner{max-width:1240px;margin:0 auto;min-height:78px;padding:0 28px;display:flex;align-items:center;gap:28px}
+.sf-logo{display:flex;align-items:center;gap:11px;font-size:18px;text-decoration:none!important;color:#fff!important;white-space:nowrap}
+.sf-logo-mark{width:36px;height:36px;display:grid;place-items:center;background:var(--sf-shell-accent);color:var(--sf-shell);font-size:22px;font-weight:900;border-radius:9px}
+.sf-primary-links{display:flex;gap:23px;margin-left:auto;align-items:center}
+.sf-primary-links a,.sf-mega a{color:#fff!important;text-decoration:none!important;font-size:13px;font-weight:650;white-space:nowrap}
+.sf-primary-links a:hover{color:var(--sf-shell-accent)!important}
+.sf-head-cta{font-size:13px;font-weight:750;text-decoration:none;background:var(--sf-shell-accent);color:var(--sf-shell)!important;padding:13px 18px;border-radius:4px;white-space:nowrap}
+.sf-menu-toggle{display:none;margin-left:auto;color:white;border:1px solid #ffffff55;background:none;border-radius:4px;padding:8px 12px;font-size:19px;cursor:pointer}
+.sf-mega{display:flex;gap:18px;max-width:1240px;margin:auto;padding:0 28px 9px;overflow-x:auto}
+.sf-mega a{display:block;min-width:120px;border-top:1px solid #ffffff2e;padding:12px 8px}.sf-mega small{display:block;color:#ffffff99;max-width:160px;overflow:hidden;text-overflow:ellipsis;font-size:10px;margin-top:6px}
+.sf-site-foot{background:var(--sf-shell);color:white!important;font:14px/1.7 system-ui,-apple-system,sans-serif;padding:72px 28px 26px}
+.sf-foot-grid{max-width:1200px;margin:auto;display:grid;grid-template-columns:2fr 1fr 1fr;gap:70px}.sf-foot-grid h2{font-size:25px;color:white;margin:0 0 17px}.sf-foot-grid h4{font-size:13px;color:var(--sf-shell-accent);letter-spacing:.07em;margin:0 0 17px}.sf-foot-grid p{color:#ffffffae;max-width:380px}.sf-foot-grid a{display:block;width:fit-content;color:#fff!important;text-decoration:none;margin-bottom:8px}.sf-foot-lower{border-top:1px solid #ffffff33;max-width:1200px;margin:45px auto 0;padding-top:20px;display:flex;justify-content:space-between;color:#ffffff9e;font-size:12px}
+.sf-filters{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin:0 0 30px}.sf-filters input,.sf-filters select{padding:11px 13px;border-radius:6px;border:1px solid #cfd9e4;background:#fff;color:#263a51;max-width:280px}.sf-filter-empty{padding:22px;color:#778598}
+.sf-search-page{min-height:76vh;max-width:1060px;padding:90px 24px;margin:auto;font-family:system-ui,sans-serif;color:var(--sf-shell-fg)}.sf-search-page h1{font-size:clamp(35px,5vw,62px);margin:20px 0}.sf-search-page>label{display:block;margin:18px 0 8px}.sf-search-page input{display:block;width:100%;padding:17px;font-size:18px;border:1px solid #b7c6d7;border-radius:8px}.sf-search-page #sf-search-count{margin:22px 0;color:#607589}.sf-search-result{padding:22px 0;border-bottom:1px solid #dae1ea}.sf-search-result a{color:var(--sf-shell-fg);font-size:21px;font-weight:750;text-decoration:none}.sf-search-result small{display:block;color:#7992a7}.sf-search-result p{margin:7px 0;color:#657487}
+@media(max-width:1050px){.sf-head-inner{gap:13px}.sf-primary-links{gap:12px}.sf-primary-links a{font-size:12px}.sf-head-cta{padding:10px}}
+@media(max-width:850px){.sf-head-inner{min-height:68px}.sf-menu-toggle{display:block}.sf-head-cta{display:none}.sf-primary-links{display:none;position:absolute;top:68px;left:0;right:0;margin:0;background:var(--sf-shell);padding:20px 28px;flex-direction:column;align-items:stretch;box-shadow:0 20px 24px #0002}.sf-primary-links.is-open{display:flex}.sf-mega{display:none}.sf-foot-grid{grid-template-columns:1fr 1fr;gap:35px}}
+@media(max-width:560px){.sf-foot-grid{grid-template-columns:1fr}.sf-foot-lower{display:block}.sf-site-foot{padding-top:48px}.sf-head-inner{padding:0 16px}}
+
+.sf-page-intro{padding:80px 28px;background:var(--sf-shell-soft);color:var(--sf-shell-fg);font-family:system-ui,sans-serif}.sf-page-intro>div{max-width:1200px;margin:auto}.sf-page-intro p{font-size:11px;font-weight:850;letter-spacing:.19em;text-transform:uppercase;color:var(--sf-shell-fg);opacity:.58}.sf-page-intro h1{font-size:clamp(38px,5vw,67px);letter-spacing:-.04em;line-height:1.08;margin:13px 0}.sf-page-intro span{font-size:16px;opacity:.72}
+.sf-item[hidden]{display:none!important}
diff --git a/examples/mobile-showcase/minimal/assets/pro-base.css b/examples/mobile-showcase/minimal/assets/pro-base.css
new file mode 100644
index 0000000..c2c4f61
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/pro-base.css
@@ -0,0 +1,21 @@
+/* SiteForge V3 professional theme primitives. Each commercial theme adds its own art direction. */
+:root{--pro-text:#19283a;--pro-muted:#687587;--pro-border:#dfe5ea;--pro-background:#fff;--pro-surface:#f5f7fa;--pro-accent:#365cb7;--pro-hero:#15263d;--pro-hero-fg:#fff;--pro-radius:12px;--pro-headings:system-ui,-apple-system,sans-serif;--pro-font:system-ui,-apple-system,sans-serif;--pro-wide:1270px}
+*,*:before,*:after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--pro-background);color:var(--pro-text);font-family:var(--pro-font);-webkit-font-smoothing:antialiased;overflow-x:hidden}button,input,select{font:inherit}button,a{touch-action:manipulation}button{cursor:pointer}a{text-decoration:none;color:inherit}a:hover{color:var(--pro-accent)}img{max-width:100%;display:block}h1,h2,h3,h4,p{margin-top:0}h1,h2,h3{font-family:var(--pro-headings);line-height:1.09}h1{letter-spacing:-.052em}h2{letter-spacing:-.042em}h3{letter-spacing:-.028em}p{line-height:1.72}.pro-wrap{max-width:var(--pro-wide);margin:0 auto;padding-left:32px;padding-right:32px}.pro-skip{position:absolute;left:-9999px;z-index:90;background:#fff;color:#000;padding:12px}.pro-skip:focus{left:10px;top:10px}.pro-eyebrow{font-weight:750;font-size:11px;letter-spacing:.17em;text-transform:uppercase;display:block;line-height:1.6;color:var(--pro-accent)}.pro-muted{opacity:.7}.pro-utility{min-height:30px;background:var(--pro-hero);color:#c5d2db;font-size:10px;letter-spacing:.16em}.pro-utility .pro-wrap{display:flex;justify-content:space-between;padding-block:8px}.pro-utility i{font-style:normal;color:var(--pro-accent)}.pro-header{background:var(--pro-background);color:var(--pro-text);border-bottom:1px solid var(--pro-border);position:relative;z-index:35}.pro-head-inner{min-height:84px;display:flex;align-items:center;gap:28px}.pro-logo{display:flex;align-items:center;gap:12px;min-width:170px}.pro-logo-symbol{font-size:32px;line-height:1;font-weight:800;letter-spacing:-.07em;color:var(--pro-accent)}.pro-logo-name{max-width:220px;font-weight:820;font-size:17px;line-height:1.1;letter-spacing:-.035em}.pro-logo-name small{display:block;font-weight:650;font-size:9px;letter-spacing:.19em;margin-top:7px;color:var(--pro-muted)}.pro-navigation{display:flex;align-items:center;gap:24px;margin-left:auto}.pro-navigation a{font-size:13px;font-weight:650;white-space:nowrap;transition:opacity .2s}.pro-navigation a:hover{opacity:.6}.pro-header-cta,.pro-button{display:inline-flex;align-items:center;justify-content:center;gap:22px;background:var(--pro-accent);color:#fff!important;border-radius:var(--pro-radius);font-size:13px;letter-spacing:.01em;font-weight:750;padding:16px 24px;white-space:nowrap;transition:transform .2s,background .2s}.pro-header-cta:hover,.pro-button:hover{transform:translateY(-2px)}.pro-mobile-toggle{display:none}.pro-mega{display:flex;align-items:center;gap:22px;min-height:37px;border-top:1px solid var(--pro-border);padding-block:10px;font-size:11px}.pro-mega>span{font-weight:750;color:var(--pro-muted);white-space:nowrap}.pro-mega>div{display:flex;gap:16px;overflow:auto;scrollbar-width:none}.pro-mega a{white-space:nowrap;font-weight:650}.pro-search-link{font-size:19px!important}.pro-home-hero{position:relative;overflow:hidden}.pro-home-grid{display:grid;grid-template-columns:1.02fr .98fr;align-items:center;gap:5%;min-height:580px;padding-top:85px;padding-bottom:85px}.pro-hero-copy{position:relative;z-index:2}.pro-hero-copy h1{font-size:clamp(48px,6.2vw,91px);line-height:1.03;margin:20px 0 25px;max-width:730px}.pro-hero-copy h1 em{font-style:normal;display:block;color:var(--pro-accent)}.pro-hero-copy p{font-size:17px;max-width:495px;color:var(--pro-muted)}.pro-hero-actions{display:flex;align-items:center;flex-wrap:wrap;gap:25px;margin-top:34px}.pro-outline-button{font-weight:700;border-bottom:1px solid currentColor;padding:12px 0}.pro-hero-art{width:100%;height:435px;position:relative;overflow:hidden;border-radius:var(--pro-radius)}.pro-hero-art>img{width:100%;height:100%;object-fit:cover}.pro-hero-bottom{display:flex;justify-content:space-between;font-size:10px;letter-spacing:.18em;padding-block:22px;border-top:1px solid #ffffff32}.pro-section{padding:106px 0}.pro-section-head{display:flex;align-items:end;justify-content:space-between;gap:25px;margin-bottom:34px}.pro-section-head h2{font-size:clamp(36px,4vw,57px);margin:12px 0 0}.pro-text-link{font-size:13px;font-weight:750;display:inline-flex;gap:18px;align-items:center;border-bottom:1px solid;padding-bottom:7px;color:var(--pro-accent)}.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}.pro-card{display:block;min-width:0;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);overflow:hidden;border-radius:var(--pro-radius);transition:transform .25s,box-shadow .25s}.pro-card:hover{transform:translateY(-6px);box-shadow:0 24px 45px #0f25431b}.pro-card-cover{width:100%;height:244px;overflow:hidden;background:var(--pro-surface)}.pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .5s}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-art{width:100%;height:100%;overflow:hidden;display:grid;place-items:center;position:relative;background:linear-gradient(130deg,#d8e1ed,#f3f6f9)}.pro-art-ring{width:55%;aspect-ratio:1;border:28px solid #ffffff5f;border-radius:50%;box-shadow:0 0 0 42px #ffffff30;transform:rotate(-25deg)}.pro-art-center{position:absolute;font-family:var(--pro-headings);font-weight:800;font-size:76px;letter-spacing:-.08em;color:#45647d90}.pro-art-label{position:absolute;bottom:16px;left:18px;letter-spacing:.16em;font-size:9px;color:#435773}.pro-card-body{padding:25px 25px 28px}.pro-card-meta{display:flex;justify-content:space-between;gap:20px;font-size:10px;font-weight:750;letter-spacing:.1em;text-transform:uppercase;color:var(--pro-accent)}.pro-card h3{font-size:23px;margin:14px 0 9px}.pro-card p{font-size:13px;color:var(--pro-muted);margin-bottom:24px;min-height:44px}.pro-card-link{font-size:12px;font-weight:800;display:flex;justify-content:space-between;color:var(--pro-text);padding-top:14px;border-top:1px solid var(--pro-border)}.pro-empty{grid-column:1/-1;padding:45px;border:1px dashed var(--pro-border);background:var(--pro-surface);color:var(--pro-muted)}.pro-statement{background:var(--pro-surface)}.pro-statement-layout{display:grid;grid-template-columns:1fr 2fr;gap:60px}.pro-statement h2{font-size:clamp(36px,4.5vw,63px);max-width:760px}.pro-statement p{max-width:520px;color:var(--pro-muted)}.pro-brand-pills{display:flex;flex-wrap:wrap;gap:12px}.pro-brand-pills a{padding:18px 28px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);font-weight:750}.pro-brand-pills a:hover{border-color:var(--pro-accent)}.pro-inner-hero{background:var(--pro-surface);padding:92px 0 65px;position:relative;overflow:hidden}.pro-inner-hero h1{max-width:870px;font-size:clamp(43px,6vw,85px);margin:18px 0}.pro-inner-hero p{max-width:650px;color:var(--pro-muted);font-size:17px}.pro-breadcrumb{font-size:12px;color:var(--pro-muted);margin-top:25px;display:flex;gap:12px;align-items:center;flex-wrap:wrap}.pro-breadcrumb strong{color:var(--pro-text)}.pro-list-section{padding:65px 0 110px}.pro-filter-panel{display:grid;grid-template-columns:minmax(220px,2fr) 1fr 1fr auto;align-items:end;gap:14px;margin-bottom:35px}.pro-filter-panel label>span{display:block;font-size:11px;margin-bottom:8px;color:var(--pro-muted);font-weight:750}.pro-filter-panel input,.pro-filter-panel select{display:block;width:100%;padding:14px 15px;border-radius:var(--pro-radius);border:1px solid var(--pro-border);background:var(--pro-background);color:var(--pro-text);outline-offset:2px}.pro-filter-count{font-size:12px;padding-bottom:17px;color:var(--pro-muted)}.pro-filter-scope .pro-card[hidden]{display:none}.pro-product-layout{padding-top:38px}.pro-product-layout .pro-breadcrumb{margin-bottom:38px}.pro-product-hero{display:grid;grid-template-columns:1.18fr .82fr;gap:6%;align-items:center}.pro-product-media{height:460px;overflow:hidden;background:var(--pro-surface);border-radius:var(--pro-radius)}.pro-product-media .pro-photo{height:100%}.pro-product-summary h1{font-size:clamp(45px,5vw,72px);margin:16px 0}.pro-product-summary p{font-size:16px;color:var(--pro-muted)}.pro-product-brand{font-weight:700;color:var(--pro-text)!important}.pro-detail-layout{border-top:1px solid var(--pro-border);display:grid;grid-template-columns:1fr 1fr;gap:8%;margin-top:80px;padding-top:55px}.pro-detail-layout h2{font-size:32px;margin-top:13px}.pro-specs{margin-top:25px}.pro-specs>div{display:grid;grid-template-columns:1fr 1fr;gap:22px;padding:16px 0;border-bottom:1px solid var(--pro-border)}.pro-specs span{color:var(--pro-muted)}.pro-rich{line-height:1.85;font-size:16px;overflow-wrap:anywhere}.pro-rich img{border-radius:var(--pro-radius);margin:25px 0}.pro-rich a{text-decoration:underline}.pro-rich h2{margin-top:30px}.pro-lead{font-size:20px;color:var(--pro-muted)}.pro-brand-hero-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:7%;align-items:center}.pro-brand-media{height:300px;overflow:hidden;border-radius:var(--pro-radius)}.pro-article-hero{text-align:center;padding:110px 0 75px;background:var(--pro-surface)}.pro-article-width{max-width:900px}.pro-article-hero h1{font-size:clamp(40px,5vw,75px);margin:15px 0 25px}.pro-article-hero p{color:var(--pro-muted);font-size:18px}.pro-article-hero time{font-size:12px;color:var(--pro-muted)}.pro-article-content{max-width:930px;padding-top:70px;padding-bottom:90px}.pro-article-cover{width:100%;max-height:530px;object-fit:cover;margin-bottom:40px;border-radius:var(--pro-radius)}.pro-article-content>.pro-text-link{margin-top:35px}.pro-case-detail{max-width:1020px}.pro-case-detail>img{width:100%;max-height:620px;object-fit:cover}.pro-case-meta{display:flex;gap:35px;margin:35px 0;color:var(--pro-muted);font-size:13px}.pro-case-meta strong{display:block;color:var(--pro-text);font-size:18px;margin-top:7px}.pro-info-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:12%}.pro-info-grid h2{font-size:clamp(31px,4.5vw,54px);margin-top:15px}.pro-contact-email{font-size:20px;color:var(--pro-accent);font-weight:750;word-break:break-word}.pro-info-photo{max-height:450px;object-fit:cover;margin-top:35px;border-radius:var(--pro-radius)}.pro-custom-content .sf-section{background:transparent}.pro-custom-content .sf-section:nth-of-type(even){background:var(--pro-surface)}.pro-end-cta{padding:76px 0;background:var(--pro-hero);color:#fff}.pro-end-cta .pro-wrap{display:flex;align-items:center;justify-content:space-between;gap:40px}.pro-end-cta h2{font-size:clamp(32px,4vw,53px);margin:13px 0}.pro-end-cta p{color:#c5c9d1}.pro-end-cta a{background:var(--pro-accent);color:#fff;font-weight:800;padding:19px 30px;border-radius:var(--pro-radius);white-space:nowrap}.pro-404{text-align:center;padding-block:125px}.pro-404>span{font-size:12px;letter-spacing:.3em}.pro-404 h1{font-size:clamp(150px,20vw,320px);margin:10px 0;letter-spacing:-.08em}.pro-404 p{font-size:18px;color:var(--pro-muted)}.pro-404 a{margin-top:22px}.pro-footer{background:#15212d;color:#fff;padding:75px 0 22px}.pro-footer-top{display:grid;grid-template-columns:1.45fr 1fr 1fr;gap:55px}.pro-footer h2{font-size:32px;margin:12px 0}.pro-footer p{max-width:350px;font-size:13px;color:#afbbc8}.pro-footer .pro-eyebrow{color:#b6c3d2}.pro-footer-links ul{margin:0;padding:0;list-style:none;display:grid;grid-template-columns:1fr 1fr;gap:15px}.pro-footer-links a{font-size:13px;color:#d6e0e8}.pro-footer-bottom{border-top:1px solid #ffffff29;margin-top:60px;padding-top:20px;font-size:10px;letter-spacing:.12em;color:#adb9c3;display:flex;justify-content:space-between}.pro-anchor{display:block;height:0}.pro-category-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.pro-category-tiles a{padding:35px 28px;min-height:155px;background:var(--pro-surface);display:flex;flex-direction:column;justify-content:space-between}.pro-category-tiles span{font-size:11px;color:var(--pro-muted)}.pro-category-tiles strong{font-size:24px}.pro-category-tiles b{align-self:end}.sf-site-head .sf-head-inner,.sf-site-head .sf-primary-links{display:none}
+@media(max-width:1100px){.pro-head-inner{gap:17px}.pro-navigation{gap:14px}.pro-navigation a{font-size:12px}.pro-home-grid{min-height:500px}.pro-hero-copy h1{font-size:60px}.pro-hero-art{height:350px}}
+@media(max-width:860px){.pro-wrap{padding-left:22px;padding-right:22px}.pro-utility{display:none}.pro-head-inner{min-height:70px;flex-wrap:wrap;gap:12px}.pro-mobile-toggle{display:block;margin-left:auto;border:1px solid var(--pro-border);padding:8px 12px;background:transparent;color:var(--pro-text);border-radius:5px}.pro-navigation{display:none;order:10;width:100%;flex-direction:column;align-items:stretch;gap:0;padding-bottom:12px}.pro-navigation.is-open{display:flex}.pro-navigation a{padding:11px 2px;border-bottom:1px solid var(--pro-border)}.pro-header-cta{display:none}.pro-mega{display:none}.pro-home-grid{grid-template-columns:1fr;gap:35px;padding-top:60px;padding-bottom:60px}.pro-hero-art{height:350px}.pro-hero-copy h1{font-size:clamp(45px,9vw,73px)}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-brand-hero-grid{grid-template-columns:1fr;gap:35px}.pro-product-media{height:380px}.pro-filter-panel{grid-template-columns:1fr 1fr}.pro-filter-panel label:first-child{grid-column:1/-1}.pro-end-cta .pro-wrap{align-items:start;flex-direction:column}.pro-section{padding:72px 0}.pro-footer-top{grid-template-columns:1fr 1fr}.pro-footer-top>div:first-child{grid-column:1/-1}.pro-category-tiles{grid-template-columns:repeat(2,1fr)}}
+@media(max-width:560px){.pro-wrap{padding-left:18px;padding-right:18px}.pro-logo-name{font-size:15px}.pro-hero-copy h1{font-size:43px}.pro-hero-art{height:255px}.pro-hero-bottom{display:none}.pro-home-grid{min-height:0}.pro-grid{grid-template-columns:1fr}.pro-section-head{align-items:start;flex-direction:column}.pro-section-head h2{font-size:37px}.pro-card-cover{height:230px}.pro-filter-panel{grid-template-columns:1fr}.pro-filter-panel label:first-child{grid-column:auto}.pro-filter-count{padding-bottom:4px}.pro-statement-layout,.pro-footer-top{grid-template-columns:1fr;gap:25px}.pro-footer-top>div:first-child{grid-column:auto}.pro-footer-bottom{flex-direction:column;gap:8px}.pro-inner-hero{padding:60px 0}.pro-product-media{height:270px}.pro-product-summary h1{font-size:43px}.pro-category-tiles{grid-template-columns:1fr}.pro-end-cta h2{font-size:38px}.pro-minimal-meta{display:block!important}}
+
+/* V2 compatibility stylesheet hides direct headers/footers; full themes restore their own. */
+body>header.sf-site-head.pro-header,body>footer.sf-site-foot.pro-footer{display:block!important}
+
+/* Accessible desktop mega-navigation and mobile disclosure */
+.pro-catalog-toggle{font-size:12px;font-weight:750;border:1px solid var(--pro-border);background:transparent;color:inherit;border-radius:var(--pro-radius);padding:10px 14px;white-space:nowrap}
+.pro-mega[hidden]{display:none!important}.pro-mega.is-open{display:flex}.pro-mega>div{padding-block:3px}
+@media(max-width:1060px){.pro-catalog-toggle{display:none}}
+@media(max-width:860px){.pro-mega.is-open{display:block!important}.pro-mega.is-open>div{display:flex;flex-wrap:wrap;gap:14px;padding:12px 0}.pro-mega>span{white-space:normal}}
+
+/* V4: responsive nested navigation, gallery and opt-in inquiry integration */
+.pro-nav-group{position:relative;display:flex;align-items:center;gap:3px}.pro-nav-disclose{border:0;background:none;color:inherit;font-size:12px;padding:8px}.pro-nav-children[hidden]{display:none!important}.pro-nav-children{position:absolute;left:0;top:100%;z-index:45;min-width:230px;max-width:360px;padding:10px;background:var(--pro-background);border:1px solid var(--pro-border);box-shadow:0 18px 35px #0002;border-radius:var(--pro-radius);display:flex;flex-direction:column}.pro-nav-children a{padding:10px 12px;white-space:nowrap}.pro-nav-children .pro-nav-leaf{padding-left:28px;color:var(--pro-muted)}
+.pro-product-gallery{min-width:0}.pro-gallery-thumbs{display:flex;gap:8px;margin-top:12px;overflow:auto;scrollbar-width:thin;padding-bottom:5px}.pro-gallery-thumbs button{flex:0 0 86px;height:67px;border:2px solid transparent;padding:3px;border-radius:8px;background:var(--pro-surface)}.pro-gallery-thumbs button:focus-visible,.pro-gallery-thumbs button[aria-pressed=true]{border-color:var(--pro-accent)}.pro-gallery-thumbs img{width:100%;height:100%;object-fit:cover;border-radius:4px}.pro-gallery-lightbox{position:fixed;inset:0;background:rgba(10,16,30,.92);z-index:1000;display:grid;place-items:center;padding:6vw}.pro-gallery-lightbox[hidden]{display:none}.pro-gallery-lightbox img{max-width:92vw;max-height:80vh;object-fit:contain}.pro-gallery-lightbox button{position:absolute;right:25px;top:25px;border:1px solid #fff8;background:#222;color:#fff;padding:10px 15px;border-radius:5px}
+.pro-inquiry-wrap{display:grid;grid-template-columns:1fr 1fr;gap:70px}.pro-inquiry{display:grid;gap:17px}.pro-inquiry label{display:grid;gap:7px;font-weight:650}.pro-inquiry input,.pro-inquiry textarea{padding:13px;border:1px solid var(--pro-border);border-radius:var(--pro-radius);background:var(--pro-background);color:var(--pro-text);width:100%}.pro-inquiry textarea{min-height:140px}.pro-inquiry .pro-honeypot{position:absolute;left:-10000px;width:1px;height:1px}.pro-inquiry p{font-size:12px;color:var(--pro-muted)}
+@media(max-width:860px){.pro-nav-group{display:block}.pro-nav-children{position:static;max-width:none;width:100%;box-shadow:none;border:0;border-left:2px solid var(--pro-accent);padding-left:8px}.pro-nav-group>a{display:inline-block}.pro-inquiry-wrap{grid-template-columns:1fr;gap:20px}}
diff --git a/examples/mobile-showcase/minimal/assets/pro.css b/examples/mobile-showcase/minimal/assets/pro.css
new file mode 100644
index 0000000..e9ec81c
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/pro.css
@@ -0,0 +1,8 @@
+:root{--pro-text:#272722;--pro-muted:#797972;--pro-border:#d9d8d0;--pro-background:#fafaf7;--pro-surface:#f0f0eb;--pro-accent:#282923;--pro-hero:#272722;--pro-radius:0;--pro-headings:Georgia,Times New Roman,serif;--pro-font:Arial,sans-serif;--pro-wide:1380px}
+.pro-header{background:#fafaf7;color:#292920}.pro-head-inner{min-height:96px}.pro-logo{min-width:200px}.pro-logo-symbol{font-family:Georgia,serif;color:#272722;font-size:40px;font-weight:400}.pro-logo-name{font-family:Georgia,serif;font-size:23px;font-weight:500}.pro-logo-name small{font-family:Arial,sans-serif;font-size:9px}.pro-navigation a{font-size:12px;font-weight:550}.pro-header-cta{border-radius:0;background:transparent;color:#262621!important;border-bottom:1px solid #222;padding:10px 0}.pro-minimal-hero{background:#f4f3ec;color:#252521;border-bottom:1px solid #d7d7cc}.pro-minimal-hero>.pro-wrap{padding-top:68px;padding-bottom:90px}.pro-minimal-meta{display:flex;justify-content:space-between;color:#727267;font-size:10px;letter-spacing:.16em}.pro-minimal-hero h1{font-weight:400;font-family:Georgia,serif;letter-spacing:-.072em;font-size:clamp(65px,10vw,153px);line-height:1.03;margin:100px 0 85px}.pro-minimal-hero h1 em{font-weight:400;font-style:italic;color:#8f8e82}.pro-minimal-bottom{display:flex;justify-content:space-between;align-items:end;gap:40px;border-top:1px solid #c9c8bf;padding-top:28px}.pro-minimal-bottom p{font-size:17px;max-width:450px;margin:0}.pro-minimal-bottom a{font-weight:750;font-size:11px;letter-spacing:.11em;white-space:nowrap}.pro-minimal-bottom a span{padding-left:25px}.pro-section-head h2{font-weight:400;font-size:clamp(40px,5vw,70px)}.pro-grid{gap:34px 20px}.pro-card{border:0;background:transparent;border-radius:0}.pro-card:hover{box-shadow:none}.pro-card-cover{height:310px;background:#eae9e1}.pro-card-body{padding:23px 0}.pro-card h3{font-family:Georgia,serif;font-size:27px;font-weight:400}.pro-art{background:linear-gradient(145deg,#deddd2,#ebeae5)}.pro-art-ring{border-color:#ffffff8a}.pro-art-center{color:#aba89b}.pro-card-link{border-color:#c7c6bf}.pro-statement{background:#e9e7dc}.pro-statement h2{font-family:Georgia,serif;font-weight:400;font-size:clamp(43px,6vw,83px)}.pro-statement .pro-text-link{color:#292a22}.pro-inner-hero{background:#f0efe8;border-bottom:1px solid #deddd3}.pro-inner-hero h1{font-family:Georgia,serif;font-weight:400;font-size:clamp(60px,9vw,117px)}.pro-article-hero{background:#eeede5}.pro-article-hero h1{font-family:Georgia,serif;font-weight:400}.pro-article-content{max-width:830px}.pro-product-summary h1{font-family:Georgia,serif;font-weight:400}.pro-footer{background:#242520}.pro-footer h2{font-family:Georgia,serif;font-size:40px;font-weight:400}.pro-end-cta{background:#282822}.pro-end-cta h2{font-family:Georgia,serif;font-weight:400}.pro-end-cta a{background:#fff;color:#242520!important}.pro-404 h1{font-family:Georgia,serif;font-weight:400;color:#8f8e82}.pro-dot{color:#a8a595}
+
+/* SiteForge V5 commercial signature */
+.pro-minimal-hero .pro-hero-art{background-image:url("./visual/hero.svg")!important;background-position:center;background-size:cover;}
+
+.pro-minimal-hero{background:#efeee8!important}.pro-minimal-hero .pro-wrap{background-image:url("./visual/hero.svg");background-repeat:no-repeat;background-size:48% auto;background-position:right 56%}.pro-minimal-hero h1{max-width:930px;position:relative;text-shadow:0 1px #ffffff70}.pro-card{border:0;border-bottom:1px solid #c8c7c0;background:transparent;border-radius:0}.pro-card-cover{background:#e7e5dc}.pro-art{background:linear-gradient(155deg,#e8e7e0,#c8c9bd);color:#585d55}.pro-card-body{padding-left:0;padding-right:0}.pro-featured{background:#faf9f5}.pro-section-head h2{font-weight:400}.pro-statement{border-block:1px solid #dddcd2;background:#f1efe7}.pro-footer{background:#22251e;color:#f6f4ec}.pro-button,.pro-header-cta{border-radius:0}
+@media(max-width:700px){.pro-minimal-hero .pro-wrap{background-size:80% auto;background-position:center bottom;padding-bottom:250px}}
diff --git a/examples/mobile-showcase/minimal/assets/pro.js b/examples/mobile-showcase/minimal/assets/pro.js
new file mode 100644
index 0000000..9cf100d
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/pro.js
@@ -0,0 +1,72 @@
+'use strict';(()=>{
+ const menu=document.querySelector('[data-pro-menu]'); const nav=document.querySelector('#pro-navigation');
+ if(menu&&nav){menu.addEventListener('click',()=>{const open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));nav.classList.toggle('is-open',open);menu.setAttribute('aria-label',open?'关闭导航':'打开导航');});}
+ // Drawer navigation. The same markup is used across the four professional themes.
+ const mobileNav=document.querySelector('#pro-navigation');
+ let drawerBackdrop=null,drawerClose=null,returnFocus=null;
+ if(menu&&mobileNav){
+ const originalParent=mobileNav.parentNode,originalNext=mobileNav.nextSibling;
+ const heading=document.createElement('div');heading.className='sf-drawer-header';
+ const title=document.createElement('strong');title.textContent='网站导航';
+ drawerClose=document.createElement('button');drawerClose.type='button';drawerClose.className='sf-drawer-close';drawerClose.textContent='×';drawerClose.setAttribute('aria-label','关闭网站导航');
+ heading.append(title,drawerClose);mobileNav.prepend(heading);
+ drawerBackdrop=document.createElement('div');drawerBackdrop.className='sf-mobile-drawer-backdrop';drawerBackdrop.hidden=false;document.body.append(drawerBackdrop);
+ function setDrawer(open,focusBack=true){
+ const isMobile=window.matchMedia('(max-width:1024px)').matches;
+ open=Boolean(open&&isMobile);
+ if(open){returnFocus=document.activeElement;document.body.append(mobileNav);}
+ else if(mobileNav.parentNode!==originalParent){if(originalNext&&originalNext.parentNode===originalParent)originalParent.insertBefore(mobileNav,originalNext);else originalParent.append(mobileNav);}
+ mobileNav.classList.toggle('is-open',open);drawerBackdrop.classList.toggle('is-open',open);
+ document.body.classList.toggle('sf-drawer-open',open);
+ menu.setAttribute('aria-expanded',String(open));menu.setAttribute('aria-label',open?'关闭导航':'打开导航');
+ mobileNav.setAttribute('aria-hidden',String(!open&&isMobile));
+ if('inert' in mobileNav)mobileNav.inert=!open&&isMobile;
+ if(open)drawerClose.focus();
+ else if(focusBack&&returnFocus instanceof HTMLElement){returnFocus.focus();returnFocus=null;}
+ }
+ // Replace the old inline-expansion listener: use capture to stop the old listener.
+ menu.addEventListener('click',e=>{if(!window.matchMedia('(max-width:1024px)').matches)return;e.stopImmediatePropagation();setDrawer(!mobileNav.classList.contains('is-open'));},true);
+ drawerClose.addEventListener('click',()=>setDrawer(false));
+ drawerBackdrop.addEventListener('click',()=>setDrawer(false));
+ document.querySelectorAll('[data-mobile-open-menu]').forEach(b=>b.addEventListener('click',()=>setDrawer(true)));
+ mobileNav.addEventListener('click',e=>{if(e.target.closest('a'))setDrawer(false,false)});
+ document.addEventListener('keydown',e=>{
+ if(e.key==='Escape'&&mobileNav.classList.contains('is-open')){e.preventDefault();setDrawer(false);}
+ if(e.key!=='Tab'||!mobileNav.classList.contains('is-open'))return;
+ const els=[...mobileNav.querySelectorAll('button:not([disabled]),a[href]')].filter(el=>el.getClientRects().length&&!el.closest('[hidden]'));
+ if(!els.length)return;const first=els[0],last=els[els.length-1];
+ if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
+ else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
+ });
+ window.matchMedia('(max-width:1024px)').addEventListener('change',()=>setDrawer(false,false));
+ if('inert' in mobileNav&&window.matchMedia('(max-width:1024px)').matches)mobileNav.inert=true;
+ // Do not strand catalogue links when the old mobile mega-button is hidden.
+ const megaLinks=document.querySelectorAll('[data-pro-mega] a');
+ if(megaLinks.length){const wrap=document.createElement('div');wrap.className='sf-drawer-extra';const h=document.createElement('strong');h.textContent='更多分类与系列';wrap.append(h);
+ [...megaLinks].slice(0,20).forEach(a=>{const clone=a.cloneNode(true);wrap.append(clone);});mobileNav.append(wrap);}
+ }
+ const tabbar=document.querySelector('[data-mobile-tabs]');
+ if(tabbar){tabbar.style.setProperty('--sf-mobile-tab-count',String(tabbar.children.length));}
+ const megaButton=document.querySelector('[data-pro-catalog]'), mega=document.querySelector('[data-pro-mega]');
+ if(megaButton&&mega){function setMega(open){mega.hidden=!open;mega.classList.toggle('is-open',open);megaButton.setAttribute('aria-expanded',String(open));}
+ megaButton.addEventListener('click',()=>setMega(mega.hidden));
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')setMega(false);});
+ document.addEventListener('click',e=>{if(!mega.hidden&&!mega.contains(e.target)&&!megaButton.contains(e.target))setMega(false);});
+ }
+ document.querySelectorAll('.pro-nav-disclose').forEach(button=>{button.addEventListener('click',()=>{let panel=button.parentElement.querySelector('.pro-nav-children');let visible=button.getAttribute('aria-expanded')==='true';button.setAttribute('aria-expanded',String(!visible));panel.hidden=visible;});});
+ document.querySelectorAll('.pro-gallery-thumbs').forEach(container=>{const main=container.parentElement.querySelector('.pro-product-media img');const buttons=[...container.querySelectorAll('[data-gallery-image]')];
+ buttons.forEach(b=>{b.addEventListener('click',()=>{buttons.forEach(x=>x.setAttribute('aria-pressed',String(x===b)));if(main){main.src=b.dataset.galleryImage;main.alt=b.querySelector('img')?.alt||'';}});});
+ if(!main)return;main.tabIndex=0;main.style.cursor='zoom-in';const open=()=>{const pane=document.createElement('div');pane.className='pro-gallery-lightbox';pane.setAttribute('role','dialog');pane.setAttribute('aria-label','产品大图预览');let img=document.createElement('img');img.src=main.src;img.alt=main.alt;const close=document.createElement('button');close.type='button';close.textContent='关闭 ×';pane.append(img,close);document.body.append(pane);const done=()=>{pane.remove();document.removeEventListener('keydown',esc);main.focus();};const esc=e=>{if(e.key==='Escape')done();};document.addEventListener('keydown',esc);close.addEventListener('click',done);pane.addEventListener('click',e=>{if(e.target===pane)done();});close.focus();};main.addEventListener('click',open);main.addEventListener('keydown',e=>{if(e.key==='Enter')open();});
+ });
+ document.querySelectorAll('.pro-filter-scope').forEach(scope=>{
+ const panel=scope.previousElementSibling;if(!panel||!panel.classList.contains('pro-filter-panel'))return;
+ const cards=Array.from(scope.querySelectorAll('.pro-card'));
+ const blank=document.createElement('div');blank.className='pro-filter-empty';blank.hidden=true;blank.textContent='没有找到符合条件的内容,请调整关键词或筛选条件。';scope.append(blank);
+ const q=panel.querySelector('[data-pro-filter-search]'),b=panel.querySelector('[data-pro-filter-brand]'),c=panel.querySelector('[data-pro-filter-category]'),count=panel.querySelector('[data-pro-filter-count]');
+ function options(sel,key){const vals=[...new Set(cards.map(x=>x.dataset[key]||'').filter(Boolean))].sort();vals.forEach(x=>sel.add(new Option(x,x)));if(!vals.length)sel.closest('label').hidden=true;}
+ options(b,'brand');options(c,'category');
+ function run(){const str=q.value.trim().toLocaleLowerCase();let visible=0;
+ cards.forEach(el=>{const hit=el.textContent.toLocaleLowerCase().includes(str)&&(!b.value||el.dataset.brand===b.value)&&(!c.value||el.dataset.category===c.value);el.hidden=!hit;visible+=Number(hit);});count.textContent=`共 ${visible} 条`;blank.hidden=visible!==0;}
+ [q,b,c].forEach(x=>x.addEventListener(x===q?'input':'change',run));run();
+ });
+})();
diff --git a/examples/mobile-showcase/minimal/assets/search-index.json b/examples/mobile-showcase/minimal/assets/search-index.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/search-index.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/examples/mobile-showcase/minimal/assets/site-runtime.js b/examples/mobile-showcase/minimal/assets/site-runtime.js
new file mode 100644
index 0000000..e4715e8
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/site-runtime.js
@@ -0,0 +1,31 @@
+'use strict';
+(()=>{
+ const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
+ const menu=$('.sf-menu-toggle');if(menu){menu.addEventListener('click',()=>{let open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));$('#sf-primary-links')?.classList.toggle('is-open',open);});}
+ // Filter actual static content cards, no API is needed at runtime.
+ $$('.sf-collection').forEach(grid=>{
+ const cards=$$('.sf-item',grid);
+ if(cards.length<2)return;
+ const toolbar=document.createElement('div');toolbar.className='sf-filters';
+ const search=document.createElement('input');search.type='search';search.placeholder='筛选当前列表';search.setAttribute('aria-label','筛选当前列表');
+ const status=document.createElement('span');status.setAttribute('aria-live','polite');status.style.fontSize='12px';
+ const brand=document.createElement('select');brand.setAttribute('aria-label','按品牌筛选');
+ const category=document.createElement('select');category.setAttribute('aria-label','按分类筛选');
+ function fill(el,field,all){el.append(new Option(all,''));const vals=[...new Set(cards.map(x=>x.dataset[field]||'').filter(Boolean))].sort();vals.forEach(v=>el.append(new Option(v,v)));if(!vals.length)el.hidden=true;}
+ fill(brand,'brand','全部品牌');fill(category,'category','全部分类');
+ toolbar.append(search,brand,category,status);grid.parentNode.insertBefore(toolbar,grid);
+ function apply(){let val=search.value.trim().toLocaleLowerCase();let visible=0;cards.forEach(card=>{let hit=card.textContent.toLocaleLowerCase().includes(val)&&(!brand.value||card.dataset.brand===brand.value)&&(!category.value||card.dataset.category===category.value);card.hidden=!hit;if(hit)visible++;});status.textContent=`共 ${visible} 项`;}
+ search.addEventListener('input',apply);brand.addEventListener('change',apply);category.addEventListener('change',apply);apply();
+ });
+ const input=$('#sf-search'),root=$('#sf-search-results'),count=$('#sf-search-count');
+ if(input&&root){
+ let data=[];const params=new URLSearchParams(location.search);input.value=params.get('q')||'';
+ let debounce=null;
+ const render=()=>{let q=input.value.trim().toLocaleLowerCase();let found=q?data.filter(d=>`${d.title} ${d.summary} ${d.brand} ${d.type}`.toLocaleLowerCase().includes(q)).slice(0,100):data.slice(0,30);
+ root.replaceChildren();count.textContent=`${found.length} 条结果${!q?'(显示前30条)':''}`;
+ found.forEach(d=>{let article=document.createElement('article'),a=document.createElement('a'),small=document.createElement('small'),p=document.createElement('p');article.className='sf-search-result';a.textContent=d.title;a.href=d.url.replace(/^\//,'');small.textContent=d.type;p.textContent=d.summary;article.append(a,small,p);root.append(article);});
+ };const base=document.querySelector('base')?.href||location.href;
+ fetch(new URL('search-index.json',base)).then(r=>{if(!r.ok)throw Error('index unavailable');return r.json();}).then(v=>{data=v;render()}).catch(()=>{count.textContent='搜索索引加载失败,请检查站点文件部署路径';});
+ input.addEventListener('input',()=>{clearTimeout(debounce);debounce=setTimeout(render,100)});
+ }
+})();
diff --git a/examples/mobile-showcase/minimal/assets/theme.css b/examples/mobile-showcase/minimal/assets/theme.css
new file mode 100644
index 0000000..1547baa
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/theme.css
@@ -0,0 +1 @@
+*{box-sizing:border-box}body{margin:0;color:#171717;background:#faf9f6;font:16px/1.6 Arial,Helvetica,sans-serif}a{color:inherit;text-decoration:none}.container{width:min(1260px,90%);margin:auto}.header{height:110px;display:flex;align-items:center;justify-content:space-between;gap:20px;border-bottom:1px solid #cccac4}.brand{font-family:Georgia,"Times New Roman",serif;font-size:30px;font-weight:bold;letter-spacing:-1px}.brand span{font-size:12px;vertical-align:top}.header nav{display:flex;gap:33px;font-size:12px;flex-wrap:wrap}.header nav a:hover{text-decoration:underline}.contact{font-size:12px;font-weight:bold;border-bottom:1px solid black;padding-bottom:5px}.hero{padding:48px 0 0}.topline{display:flex;justify-content:space-between;font-size:10px;letter-spacing:2.3px;font-weight:bold}.hero h1,.inner-hero h1{font-family:Georgia,"Times New Roman",serif;font-size:clamp(65px,9.1vw,155px);line-height:1;letter-spacing:-8px;font-weight:400;margin:85px 0 40px}.hero-bottom{display:flex;justify-content:space-between;align-items:end;margin:35px 0 64px;gap:25px}.hero-bottom p{font-size:18px;max-width:490px;margin:0}.hero-bottom a{font-size:12px;letter-spacing:1.5px;border-bottom:1px solid black;padding-bottom:8px}.hero-image{height:425px;background:#deddd4;overflow:hidden;position:relative;display:flex;align-items:center;justify-content:center}.hero-image img{height:100%;width:100%;object-fit:cover}.graphic{background:#cec8bc}.shape{position:absolute;border-radius:50%;background:linear-gradient(125deg,#ede9e1,#98918a);box-shadow:25px 30px 40px #59544c22}.shape.a{height:550px;width:550px;left:5%;top:-230px}.shape.b{width:280px;height:280px;right:15%;bottom:-120px;background:#989e97}.shape.c{width:160px;height:160px;right:40%;top:0;background:#ece9e3}.graphic>span{position:relative;font-size:25px;letter-spacing:7px;font-weight:500;line-height:1.5}.inner-hero{padding:80px 0;border-bottom:1px solid #d3d0ca}.inner-hero h1{font-size:clamp(48px,8vw,115px);margin:40px 0}.inner-hero p{font-size:18px;color:#777;max-width:660px}.content{padding:70px 0 130px}.list-head{display:grid;grid-template-columns:1fr 2fr;align-items:center;margin:30px 0 40px;border-bottom:1px solid black}.list-head span{font-size:10px;letter-spacing:2px}.list-head h2{font-family:Georgia,serif;font-weight:400;font-size:54px;letter-spacing:-2px}.list{display:grid;grid-template-columns:repeat(2,1fr);gap:80px 35px}.item{display:block}.item:hover h3{text-decoration:underline}.thumb{height:290px;background:#e3e1d8;display:grid;place-items:center}.thumb img{width:100%;height:100%;object-fit:contain}.thumb span{font-family:Georgia,serif;font-size:140px;color:#c0bcb2}.info{padding:25px 0}.info>span{font-size:10px;letter-spacing:1.5px}.info h3{font-family:Georgia,serif;font-size:36px;font-weight:400;margin:12px 0}.info p{color:#666;max-width:400px}.info b{font-size:11px;letter-spacing:1px}.rich-content{margin:25px 0}.section{padding:45px 0}.block-hero{padding:50px 0}.block-hero h1{font-family:Georgia,serif;font-size:60px;font-weight:400}.feature-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:30px}.feature{border-top:1px solid #8f8a84;padding-top:20px}.feature-mark{font-size:12px}.stats{display:grid;grid-template-columns:repeat(4,1fr)}.stat strong{font-family:Georgia,serif;display:block;font-size:47px;font-weight:400}.stat span{font-size:11px;letter-spacing:1px}.cta{padding:55px;background:#e4e0d8}.cta h2{font-family:Georgia,serif;font-weight:400;font-size:44px}.btn{display:inline-block;background:#222;color:white;padding:13px 20px;font-size:12px}.detail-img img{max-width:100%;max-height:550px}footer{border-top:1px solid #b9b5ae;padding:70px 0 25px}.footer-lead{font-family:Georgia,serif;font-size:clamp(45px,6vw,90px);line-height:1.05;letter-spacing:-4px}.footer-lead span{color:#909084}.footer-bottom{display:flex;justify-content:space-between;border-top:1px solid #d5d1c9;margin-top:90px;padding-top:25px;font-size:11px;color:#777}@media(max-width:850px){.header{height:auto;padding:26px 0;flex-wrap:wrap}.header nav{order:3;width:100%}.hero h1{letter-spacing:-4px;margin-top:60px}.hero-image{height:320px}.list{gap:35px}.list-head{grid-template-columns:1fr}.footer-lead{letter-spacing:-2px}}@media(max-width:560px){.list,.feature-grid,.stats{grid-template-columns:1fr}.hero h1{font-size:67px}.hero-bottom,.topline,.footer-bottom{flex-direction:column;align-items:start;gap:15px}.hero-image{height:240px}.list-head h2{font-size:40px}.inner-hero{padding:50px 0}}
diff --git a/examples/mobile-showcase/minimal/assets/visual/hero.svg b/examples/mobile-showcase/minimal/assets/visual/hero.svg
new file mode 100644
index 0000000..f43879d
--- /dev/null
+++ b/examples/mobile-showcase/minimal/assets/visual/hero.svg
@@ -0,0 +1 @@
+
diff --git a/examples/mobile-showcase/minimal/index.html b/examples/mobile-showcase/minimal/index.html
new file mode 100644
index 0000000..08b160c
--- /dev/null
+++ b/examples/mobile-showcase/minimal/index.html
@@ -0,0 +1,23 @@
+精选产品跳转至正文
+
+
+
+INDEPENDENT / CREATIVE / CONSIDEREDNO. 001 — ESTABLISH A PRESENCE
精选产品
MAKE IT MATTER.
+
+
+
+
+LET'S CONNECT从这里开始下一步。
简单而精致的产品体验
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/minimal/listing.html b/examples/mobile-showcase/minimal/listing.html
new file mode 100644
index 0000000..6bb15f3
--- /dev/null
+++ b/examples/mobile-showcase/minimal/listing.html
@@ -0,0 +1,22 @@
+全部产品跳转至正文
+
+
+
+DISCOVER / COLLECTION全部产品
分类查看产品与解决方案
+
+
+
+LET'S CONNECT从这里开始下一步。
简单而精致的产品体验
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/mobile-showcase/minimal/product.html b/examples/mobile-showcase/minimal/product.html
new file mode 100644
index 0000000..4a596f1
--- /dev/null
+++ b/examples/mobile-showcase/minimal/product.html
@@ -0,0 +1,17 @@
+标准系列产品跳转至正文
+
+
+
+
+
PRODUCT DETAILS / EXAMPLE标准系列产品
面向全球客户的精选示范产品
品牌 · EXAMPLE
咨询该产品 ↗
+
+
+
+LET'S CONNECT从这里开始下一步。
简单而精致的产品体验
联系我们 ↗
+
+
+
+
\ No newline at end of file
diff --git a/examples/old_urls.example.txt b/examples/old_urls.example.txt
new file mode 100644
index 0000000..c941329
--- /dev/null
+++ b/examples/old_urls.example.txt
@@ -0,0 +1,6 @@
+# 从旧站 sitemap / WordPress 页面导出的相对路径,保留大小写和尾斜杠
+/
+/about/
+/products/
+/pickup-truck/
+/contact/
diff --git a/fixtures/v820/wordPress-complex-sample.xml b/fixtures/v820/wordPress-complex-sample.xml
new file mode 100644
index 0000000..fcb3776
--- /dev/null
+++ b/fixtures/v820/wordPress-complex-sample.xml
@@ -0,0 +1,13 @@
+
+
+- Abouthttps://old.example.org/about/101pagepublishAbout us
]]>
+_featuresfield_parent
+features2
+_features_0_namefield_name
+features_0_nameLogistics
+_features_1_namefield_name
+features_1_nameOverseas delivery
+
+- Contacthttps://old.example.org/contact/102pagepublish
+- Legacy dynamichttps://old.example.org/?p=103103postpublish
+
diff --git a/integrations/art-design-pro/README.md b/integrations/art-design-pro/README.md
index a62db44..bcb5a54 100644
--- a/integrations/art-design-pro/README.md
+++ b/integrations/art-design-pro/README.md
@@ -1,28 +1,14 @@
-# Art Design Pro 官方工程对接(可执行安装器)
+# SiteForge V8.19 → Art Design Pro 集成模块
-SiteForge V2 随包提供三个 Vue 3 + Element Plus 业务页面:`WebsiteFactory.vue`、`ThemeCenter.vue`、`EnterpriseCenter.vue`。
+当前交付包含 9 个 Vue 业务页面(含新工作台和内容管理)、共享请求/会话工具 `_shared.ts`、正式单一导航树安装器,以及 FastAPI 授权菜单 `/api/console/bootstrap`。**未包含 Art Design Pro 官方完整源代码及依赖**,故不能直接执行 Art 的正式 `pnpm build`。
-> **重要:** 由于本次代码执行环境无法访问 GitHub 与 npm,无法直接打包或编译 Art Design Pro 官方完整仓库。以下对接脚本已实现源码复制和路由注册逻辑,但尚未针对官方实际项目执行 `pnpm build`。默认可运行的 `/factory-studio` Web 管理台不依赖这些文件。
+正式接入说明、所需反向代理和权限边界见 [`docs/V8_19_ART_UNIFIED_BACKEND.md`](../../docs/V8_19_ART_UNIFIED_BACKEND.md)。所有后台写操作由 SiteForge FastAPI 服务端会话、CSRF、站点角色权限控制;Art 演示登录/菜单不能代替服务端授权。官方项目从 按适用许可证获取。
-## 在联网设备对接
+快速验证:
-1. 依官方文档克隆 `https://github.com/Daymychen/art-design-pro`,使用符合要求的 Node 版本和 pnpm。
-2. 在 SiteForge 根目录执行:
-
- ```bash
- python scripts/install_into_art_design_pro.py /path/to/art-design-pro
- python scripts/install_into_art_design_pro.py /path/to/art-design-pro --apply
- ```
-
-3. 使用同域反向代理:
-
- - Art 前端 `/` 为网站工厂管理 UI
- - `/api` → SiteForge FastAPI
- - `/editor`、`/preview`、`/ui`、`/local-media`、`/login`、`/factory-studio` → SiteForge FastAPI
-
-4. 在 Art 工程目录执行 `pnpm install && pnpm build`,访问 `/siteforge/factory`、`/siteforge/themes`、`/siteforge/enterprise`。
-5. SiteForge API 仍使用自己的服务端 Session、CSRF、站点级 RBAC;前端的隐藏菜单不构成授权。`/api/auth/me` 必须在同一 Cookie 域名可访问。正式发布需统一登录体验,并补齐 Art 当前版本的路由/权限约定。
-
-安装脚本不会覆盖已有配置而不备份:原 `asyncRoutes.ts` 会先保存 `.ts.siteforge-backup`,同名 Vue 文件另存 `.vue.siteforge-backup`。重复执行不会重复插入路由。
-
-官方路由结构和本项目的适配规则以 [Art Design Pro 官方路由文档](https://www.artd.pro/docs/zh/guide/essentials/route.html) 为准。
+```bash
+python scripts/install_into_art_design_pro.py /path/to/art-design-pro
+python scripts/install_into_art_design_pro.py /path/to/art-design-pro --apply
+python scripts/verify_art_design_pro_v819.py /path/to/art-design-pro
+# 官方源码依赖安装后,才可额外执行 --build
+```
diff --git a/integrations/art-design-pro/src/views/siteforge/ConsoleHome.vue b/integrations/art-design-pro/src/views/siteforge/ConsoleHome.vue
new file mode 100644
index 0000000..0a7817f
--- /dev/null
+++ b/integrations/art-design-pro/src/views/siteforge/ConsoleHome.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+ 可管理网站{{ data.sites.length }}
+ 当前角色{{ data.capabilities.role }}
+ 平台版本{{ data.version }}
+
+
+ 当前网站{{ data.user }}
+
+ 切换网站后其他 SiteForge 页面将记住这次选择。权限在后端再次检查。
+
+
+
开始工作
+
{{ item.title }}{{ descriptions[item.key]||'查看当前模块' }}
进入模块 →
+
+
+
+
+
diff --git a/integrations/art-design-pro/src/views/siteforge/ContentCenter.vue b/integrations/art-design-pro/src/views/siteforge/ContentCenter.vue
new file mode 100644
index 0000000..6352816
--- /dev/null
+++ b/integrations/art-design-pro/src/views/siteforge/ContentCenter.vue
@@ -0,0 +1,40 @@
+
+
+
+
+ 查询
+
+ 编辑设计页面删除
+
+
+
+
+
+
+
+
+
+ 取消保存内容
+
+
+
+
+
diff --git a/integrations/art-design-pro/src/views/siteforge/EnterpriseCenter.vue b/integrations/art-design-pro/src/views/siteforge/EnterpriseCenter.vue
index facef24..68af114 100644
--- a/integrations/art-design-pro/src/views/siteforge/EnterpriseCenter.vue
+++ b/integrations/art-design-pro/src/views/siteforge/EnterpriseCenter.vue
@@ -11,21 +11,23 @@
diff --git a/integrations/art-design-pro/src/views/siteforge/ReleaseCenter.vue b/integrations/art-design-pro/src/views/siteforge/ReleaseCenter.vue
index 3e3836e..ab90511 100644
--- a/integrations/art-design-pro/src/views/siteforge/ReleaseCenter.vue
+++ b/integrations/art-design-pro/src/views/siteforge/ReleaseCenter.vue
@@ -4,15 +4,17 @@
审批请求审核通过
diff --git a/integrations/art-design-pro/src/views/siteforge/ThemeCenter.vue b/integrations/art-design-pro/src/views/siteforge/ThemeCenter.vue
index e437650..16acd8f 100644
--- a/integrations/art-design-pro/src/views/siteforge/ThemeCenter.vue
+++ b/integrations/art-design-pro/src/views/siteforge/ThemeCenter.vue
@@ -10,23 +10,21 @@
diff --git a/integrations/art-design-pro/src/views/siteforge/VisualEditor.vue b/integrations/art-design-pro/src/views/siteforge/VisualEditor.vue
index c0a6574..3580601 100644
--- a/integrations/art-design-pro/src/views/siteforge/VisualEditor.vue
+++ b/integrations/art-design-pro/src/views/siteforge/VisualEditor.vue
@@ -13,15 +13,17 @@
SiteForge Enterprise · 8.21.3-dev.1四套主题移动端与抽屉导航验收
真实源码集成版 · 自动化测试与离线 Chromium 实测 · 2026-10-23
Python 全量回归
346
41 个测试模块 · 0 失败
四套内置主题
4/4
抽屉菜单和可选底部 Tabs 通过
手机布局组合
48
4 主题 × 4 页面类型 × 3 宽度
主题及布局检查
| 主题 | 页面类型 | 手机宽度 | 水平溢出 | 结果 |
|---|
| 汽车主题 | 4 类页面 | 375 / 390 / 430px | 0px | 通过 |
| 建材主题 | 4 类页面 | 375 / 390 / 430px | 0px | 通过 |
| 企业主题 | 4 类页面 | 375 / 390 / 430px | 0px | 通过 |
| 简约主题 | 4 类页面 | 375 / 390 / 430px | 0px | 通过 |
交互逻辑

- 手机右侧抽屉导航,多级目录可展开;支持遮罩、关闭按钮、Esc、焦点返回与滚动锁定。
- 手机底部 Tabs 默认关闭;可以从后台添加 1–5 个入口,并编辑文字、图标、顺序、链接。
- 可配置站内页面、HTTPS(WhatsApp 等)、拨号、邮箱,或“打开导航菜单”操作。
- 桌面版不显示手机底栏;已有导航和页脚保留。
- 配置先保存草稿,发布后在下一次静态构建中生效,不直接更新线上站点。
打开四套主题离线互动预览 · 分批回归原始报告
验收限制
Chromium 通过 page.set_content 对真实 HTML、CSS、JavaScript 模板离线渲染,以适配运行环境对 file:// 和 localhost 导航的限制;图片使用示例数据。并非真实线上网站、CDN、iOS Safari 或 Android 真机验收。
本次覆盖四套内置专业主题的四种代表性页面,不代表所有页面、所有设备或额外安装的第三方主题均已逐一审核。Art Design Pro 官方工程和 PostgreSQL/Redis 生产验收仍需独立完成。
技术文档:docs/V8_21_3_MOBILE_THEMES.md;原始浏览器日志:reports/mobile-v8213-browser.json。
\ No newline at end of file
diff --git a/reports/V8_21_3_移动端验收总览.md b/reports/V8_21_3_移动端验收总览.md
new file mode 100644
index 0000000..d204c15
--- /dev/null
+++ b/reports/V8_21_3_移动端验收总览.md
@@ -0,0 +1,10 @@
+# SiteForge V8.21.3 移动端四套主题验收报告
+
+- 版本:8.21.3-dev.1
+- 全量回归:346 项通过 / 41 模块,11 个隔离批次,0 失败
+- Chromium:汽车、建材、企业、简约四主题抽屉和 Tabs 实测通过,0 JS 错误
+- 布局:4 套主题 × 4 页面类型 × 3 手机宽度 = 48 组合,0px 横向溢出
+- HTTP/Worker:登录、CSRF、建站、构建、ZIP、预览通过
+- 真实 iOS/Android、域名部署、第三方主题、正式 Art/PG/Redis 未验收
+
+详见 docs/V8_21_3_MOBILE_THEMES.md 和 reports/v8213-final-regression.json。
diff --git a/reports/mobile-v8213-automotive.png b/reports/mobile-v8213-automotive.png
new file mode 100644
index 0000000000000000000000000000000000000000..9ef17a1988fcc3ee884c87a5ad0d70807bb775a3
GIT binary patch
literal 261037
zcmcG#bzGED+ci3bG)PIqAdM1=3exQ`gh+Rnbc0eur!X?Kl+xXebVvwDcXu=700W%S
z=Xt;HJ?H=P@67$%d*9c!?{%%UuDv5Zyay5D(cl39079^wj4A+t7773m_~W3XegWCN
zas&W~0bm(P^-pOBzS#cyweYJQpQG(1?_X5wflAop3LkOlr3ix)|JX7HNH9JPCbc2O
z;hrFtIu<>*q@|@?sEGC!z4EKOu)YF+qfV%IJSJL$v}&Z8{JhG@p&t6;chlmzf0}22
zEnS$PTbMAHI6ClSLcVlD-ag7Y7p88Lt7)V6;dw%i>hoIF`9R%5mVZxCOvtkq{(Y?O
zyXG@6>Y3cSN42WpXSJxuxC;{GRsRlVFIlP%`!^K6&Hrbphary%6YLDP1LeBIdX0Jh
z(v@2~d`gX&JIk@`2d~u-t>sV}0hJs2denFiEtlKN4>XuRk-gsQY>I|8pKyILOO^H$
zG3s``5goUs2KDz^(~AUeZ2wJi**2vvs;l0(0j*!g7q@G^sd7!_xAy6&eN!ZF@!{|L
zJV-00PY3%0TNw&3Z}lEj+YIkA>8gMe-~X+9?m=A*IjZ3J6Z;|e@-+20-RHORcdSww
z$0XgtJLNY$R?qKo?iK8RBO)+v9SPrjeE9W2by-Xl-*CLemc90w``$#r-$g%s^J=~H
zJ)7lJ67{v6u6r}_-F}yKcOYuGb?fvDc9W%ecQVO-+_wIBx1J;BFizm!eFl0NKwv)*
z?)mQLl}P-5a#434r1wyIi$r|)y<(_48yRUnMPw7%e`ve^R=ZCmXzb_}?bYxd@pFn>
z?^(;wpE1wg*(4Pd&F$#ded>qtzVi{SfGi6;{Jg1zw49v8jj&}ZaFT7-ki`gHH*}RZ
zt!lcb5U;J=UCC$8(;oXZSdX)D{LSE;t)3TH=sIms9o+OVERIC1_>PDj=I_O2raR@_
zNSA+pSmzxW7`U8q$G(jy(pO%3u*xrniqSgnz^yc2JBYZQ)Ez@kL+O8z;#ajo8yIs%f
ztuHiZN<^%p3#yVV?j?G)UKiJGO&!OeA1oYIzWhEDix9}~2V`Ki^6cl^v0HsqW^0%0
zc3?GEN5Vi?grvZtvr(9beduA;9Wo@!ujZIx_zmQKU)m@JDKt7FvN>~eu`i0;cvaqd
zkx7|>n0F8+vLCks6-XBAS1G!0Lj2ZVifdNA*V%m%iHeD?gPt3B&Nt~0Dt`Jk8-mP+
zh+H2$nZ5_7S2TGZ9_XL_j&fv)G=&Tc9tL0R3oKlns6FyN8Y?vGd_7sJRWBKI6S35y
zz|JaQ&oe6hcN5lX+Oihf?qhA!kCi%x^$tBBNPW#^RAJXu(qr3}UcWUC9u!Myn0V3G
zQ%md6xV_B#(_^w{`*%jY@eGeJa$}`k?*|?$>wxSwvcz-5;o%x_=I85OEIyR_rc^Ee
zH{Zf@%`$a;Sj+xQ*JD)(sX;n6{@A;}AL?aS%^W8<3E6N9_V(Z^55i7)UrJLTE_`M0
zJZ6}lSWh<^2OFxZz4$FKNAI|}ztAb>|Bed7@v_lLvx$ncv1@7|<
z=BL`)E_hL2{pEvX^F?TJD8T~TV8Zsp-VK~Azx`_2Mi(|GQ0}?8+3eYm
zrbEj7%s&;9L2qoQ1T)JGt2O&W328Y#w~IBG>$RCIU~cjC@aVKy$FX4_33COATWq*)
z^F8GM&1qMu<1xb?4s3IvH=4HN@fd3O&`XDRYF_6xPad}l@1KDO=lP4Le^}hvH*3;W
z+lFa@c#0U3a&)0(>5p$m#Uin2ZiiU5L1$aM>ZkGRM3BwW2NAa`C8cLHxAkhUkBc`K
zhjnw*k=CWU{GLnoHJQ>eILicoXHJ?o5f{a;tI!;WO*CT^r`NZM_)eTJkXUPO3SDdu
zat;E0Ar9F$lMrpc>qk~og=*WY>=|YyOAkkh@#^+Wi>m_u>x&O`Go5(N+WtdB^?R
z`VQi=4c2B^6Qz0%_=LV|iAeWXoSDrB73nt^|MDkY`z$f%-kFIS6Ip>)D;1@#u^{lU4E4-H{=`zFFQwkj&eTif}*^-kOq4H`T;MUkE9
zSE8iNEAV_H#87VA7vCq+ovy-%uwqBM1rD!gnvYk0x1@q%81mmerl?I1B`VT0Y;|6L
z_Yd84?Q2hD>mfq9$CA=>f0Wg^5pH8uVN2n|!pO%@iktx$FcS*3Qz+7r_D+K05ULfN|j(Qk9g
z0|VVhce(}Q$I+$8yS6ripIJAK0CKzRU+;1J93X=^1((CvnNp~V`ygPC@3W>U{6HtM
zeq%9BH-EeG!~PD)ZDimMmnH)#-Yu>EE2omtk^>N5^xCti?BZ$1*3-)VUkf68wXygD
z_NLI@)Hh|`^VYtpZ=U&PJN&J_PaJBmn-Ai)$}~fEzlwkHS?dzqFRpmO*?D`CJ}3FF
z*z5k#yfrF+>DhlHE-G->1bNA3XIzi>FG#A-Cv3bj=By+JonLA3=pT{J0HH?m4|5fU
zyMx!L9Mv^kiX(&_*pDZzKbBh-J@XZEo{euT&Kov*<=!#JM!1hWLWrGLr9@5H&pQ?`
z8@R8=NMrG6Hy~1ymKt_rZomH}5BJ4~kQ2`ZSe^Ye+n(~kqjJC6lcwc6=QdH#kOjwf
zO?`K`;ISWK_N2U5;_rg*uI3(AjvFkjQ8A?xHq%HNrr!x7SPY$iBz&*#1N
zr5_6xD3>o)Z~yk&KlBuTI33&wG!*sLha5U?52t%yYqY6A7R_dktTN$eQBOhwrJ;+?
z+1AkU%~`4E!=_^wD5gZsaNE&y@nRv0w-vsnw^udD+w$q8J)plOKh5vm?($pCTGi~*
zyz|R3DWl-?rR0I-EFlzUu^2RuZLLW2K9au(`Hw5uG$?@%b(5;#TQ*t
zpWzu10e{4EF8dXW*-e)%K1njrH_S)I{sJ2P{M3KFeQzr9aDSOtM5v*Z%73`PdG(~e
z`JiSTmiq<$_Tov>)!yWK%65iY?qP-WW3jD*?1?bj=~A`yw)27K@@*HF!a%8qeJo4Ga!eO!X;4(wYr_-Nr`V*$_c8|nbyxflG
ztWkl3d|rWn$>$Hz7enrJf#cdluhOa?F3w-=QjQGP6YdKX4DeYlG;DPJm?tdLs$a7A
zTxc}u3RM=nD>>F|sb4nPnSrmzWloi9T}7>SdCuD>O)r14Ef({A7ekZagBEAA*yM5e
z=SPc_Vo{*P5k6R#h`wsEEz?(Xl4SC8TwY_)UITntUBe#d{Z^c^xcJ2e*%KMn1c!qe
zN6k`0dq_^?1Q}FB1kw?xP~JfLm-a^2=)P_6ePbVTqjpl|Ovd(@#)E@=e1gRKkH5VJ
zIy;2^wNv}FS?@Dhhb6lQcj8>T(1~MiQmH4B$X=4C18@1&Eq3yM(bv2547@b^#Ww?c
zpeYz)9P-U7N#^yuMMm*zfajcV53vd9K0O3I9tCnDE*H*t%kpCKu
z$fH;G9=r$beADHz%zf>%n$;6oGV7S>i}hHO)pq{+j3UpC@3dHjR={55>^zfj!9!10
zs@7przEbKu5H+)g`fIU`??Ga>Yxp{8td9K+xox7tS+SHVZY5o@5X)xwb;K(XE*aJPp#^0
zrVUD$)LAc*-Yb;ar)uI;@PyLT{8Kupzs%ZFUK^*jo*3Dnw1B83Ql>!iKT_$T@N!3O
z1julczXwV7*!Y26fGa`xAMtdjTuW)_ZFSgmp=IR3gIqamGD>fAn>|fh`t;c5&)$UN
z)W7^4LJC^W?%Bf_8m>&l=kjgV^E8U5Ssr-KucpZ%HG?tIJfg>6ii(Psk@q7Xsx`fE
zQHXj7sV-cf2miX4i4{Jas|!7@<`mO|gPI4x*%hW5C@l!sZt?+i?_c*5gJXLtPdZmP3oKb
z_}k;DskF6%ND>+K4Oh?QMwmLUlP&l4D?aV2qw~l>wd}fa1nx+++?9+&apMn``09(4
z{!9H0WAY~{oYWv57Z+JWayx`CI8f1O|1+mt78U)em!XscW1e2h#ur7tUxU(GVLu%#
z_`YMpa^F#2L*ltPTq*mJi>eQU_IwWzYkJvpAF-VXJT?RG2%;&~v*Tt#A&tjb7gGil|)HQ12P(?V$N?#{M)7ONQz3Zv%pG3=av2$%SC;Ya!futXeA
zc?)Ix@8{vUF3#X&v>2OW!SjIP#C)uhA>>9F5lk@Il|2uac2cFU9P(~VXR*icVk+k{
z9xw28Fv?H}!al)t>_t+Jmp--M{Lxj)>Wk}DB@^_M589{;DwO=eK}uzFV{U)I+EhVG
z_VN*HY;DJ(a3^a|s*EI+^<+Q_NZOh?I(S9iDTLE*c;CJ$2dFj{K!a^HO
z{;}B!&L|n8h`l0*)_3Y1EA5JV;5S@C(Q8vuK_wd)TwXz15@T4K{#29HJglw{M*>q@
zF5W+u`Ow80cgiN43`4|6#$BHQhYJd3w{0Hwje&j;(6j}r(yMq7v*MOa2V%Mq19KVp
zBaTiuZ!ANhzX=P@ijEHfOs(bQzP&8@bRYio(Vq*-x@X)s!G0PEXvwMLd{n=o-wI#zJA3)(%$F+pfTatlkYj;NH@Q!yqStSlsnJiCiq
zO#XKkCVC(8XL*HJ^CPd|AOc>589beRJlK?bP6?-meA(w-Za?c!vmA8^-;)Zed9{Pm
zClIV&JZftKxk>@13&;?WmD?)XMV3-wB6~J-Ua(_;10L0C#&i*Vj%-um$8w5oWgG95
zYfPLZ7HNG_r+7?)=Q=4HPuHRTtB;bzvq;|>qN3FaX|>FXvkOcU&k~xk?<43p_ZP;n
zEv#X{T-e0iRrR;x{dy)&>=S>%3ST)Nb`1wUVl
zR70D|YIahEA>CpJe!nv3V^hs8T>E89XiRC~J{C
zq}$MjLwFA(3J?tmA$;qPrW{CoC<`Ti2b*_$jFY^P^~78{H&6@A4&ilo#M=q!tZ&kX
z;VPylq=~~kwB(}G+%ROEIq97=E~>|rWd>PuPeCh3j>KEAG|$-fbr_wGOe3IRt69#N
z^#lD0nVFFMt1s?BI0Ro1;d2hT~7F&%a4jW+q=m>RD~8&3PB8F=z*|yO>;HRhSznV6Ow2kqu7QQa+aw&
ze!o*b@nfKjO1!vftXi~Ztp6FPa~+D;7PXOOw+{BF5;Nr-O8O;_8ZwgjiInp(Q_4rQ
zOa-PcJXlIYg!E`?*dXx97HkHKj6uK&sGi+u$aXnZVPz_u{3Mbv;mA^%*Tq3n>&c0g
zeNDJYOth^0ozc~-DI0B+%ZFNrtku*~WpUd;%~!oaX^Yx<(=NpbEDyF>J-qGUL=d|~+GY+|fOlUc9
z5Qz_q2oD%%S6uJ)%k^Djwv0|zYREU(t_!$RmwQz8=R!2oFi^#snLP%2F6Hba)6j?d
zq)2#h|JY=KPGYB?zJkQ-$7q8wv-)7gR}@mNQbc0?p&224b=&cS>_g
zta)ejjD>1GHGhJuUQb0c;&xyKndXN&FLkfM@Pmc&UvqNGMY6Xf0US4!M-mclv5W>4xc-60p~G1
zYG5H1Hx!LL1yPA}j?9&C61C>-TQY~m90WJiYRGBM*ggWKUMcn}%)BypDu^80Y@
z^iPu|X)C0J-;uEdIW<~_+8ZAYzJ54^$1@@vfp+SXenN2k2C{mG$b!B26UFh?nU%9P
zsh4EekR%CZEi%})oE4%5*VL5$bBdq}z^G^JZyt7)
zJSQ-^T>&2HJ&(N0HM1IY=Il)*l*RIU&MZ#6_!>eNv*Je(w4lXnjQ}``c&d&2qNH$=
z#r#W*UKry&i*|nxh|a?G=Xdsfo%9fKCKdCfsMYLl4+(2Nbi(flIX<6Ym5n*uv&CpS
z-bl=9N+x~MUhymzNs95=hK`bALRnYbnf`2sO{n5=NCS)EMVyXQjoWQpl)zN