diff --git a/tests/test_theme_registry.py b/tests/test_theme_registry.py index 1ca5cf9..2712018 100644 --- a/tests/test_theme_registry.py +++ b/tests/test_theme_registry.py @@ -798,6 +798,182 @@ def test_trade_pro_standard_layout_structure(factory): assert ssoup.select_one('.sf-tp-page-hero-lead') is not None # bootstrap 摘要在导语位 +def test_trade_pro_variant_pages_structure(factory): + # Task 11:brand/service/faq/search/notfound 五个次要页面精修(布局移植 + # 收官)。设计参照:上游四主题 1.3.0/1.4.0 对应页面的信息架构 + # (automotive 1.4.0 brand 页最完整:横幅 + 面包屑 + 锚点按钮 + 车型 + # 网格 + 品牌故事;其余为通用信息页骨架)+ 已建立的 sf-tp 设计语言 + # (page-hero/面包屑/卡片网格/排版层/空态)。 + # 数据事实(make_context):brand kind 由 related 提供同品牌产品 + # (fields.brand == 页题);service/faq 仅页面条目上下文(正文富文本, + # sanitize 剥离 details/summary——theme.css 的折叠样式只服务块系统注入 + # 的 custom_blocks,模板正文只能走排版层);search 为构建器生成的 + # noindex 静态页(site-runtime.js + search-index.json 的 ?q= 客户端检索, + # 上游四主题同款机制,#sf-search/#sf-search-results/#sf-search-count + # 三钩子是平台 JS 契约);notfound 走 mode='notfound'(noindex meta)。 + import re + sid = site(factory) + assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code == 200 + # ── 数据夹具:显式品牌(正文/封面)+ 2 同品牌产品 + 1 异品牌产品 + + # 服务条目(正文/封面)+ FAQ 页(page_template 显式指定)── + made = factory.post(f'/api/sites/{sid}/entries', json={ + 'type': 'brand', 'title': 'Quanta', 'route': '/brands/quanta/', + 'cover': 'https://imgs.example/quanta-hall.jpg', + 'summary': 'German engineering brand for flow control systems.', + 'body': '

Quanta manufactures flow control systems for export markets.

Heritage

Founded as an industrial valve maker.

'}) + assert made.status_code in (200, 201), made.text + batch = [ + {'type': 'product', 'title': 'Quanta Valve V1', 'route': '/product/quanta-valve-v1/', + 'summary': 'Control valve from Quanta.', 'meta': {'fields': {'brand': 'Quanta', 'category': 'Valves'}}}, + {'type': 'product', 'title': 'Quanta Pump P2', 'route': '/product/quanta-pump-p2/', + 'summary': 'Centrifugal pump from Quanta.', 'meta': {'fields': {'brand': 'Quanta'}}}, + {'type': 'product', 'title': 'Rival Actuator A9', 'route': '/product/rival-actuator/', + 'summary': 'Actuator from another brand.', 'meta': {'fields': {'brand': 'Rival'}}}, + {'type': 'service', 'title': 'Export Documentation Service', 'route': '/services/export-docs/', + 'cover': 'https://imgs.example/docs-desk.jpg', + 'summary': 'Certificates of origin, packing lists and customs paperwork.', + 'body': '

We prepare the complete document set for every shipment.

What is included

Commercial invoice, packing list and certificate of origin.

'}, + {'type': 'page', 'title': 'Shipping FAQ', 'route': '/faq-shipping/', + 'summary': 'Frequently asked questions about shipping and documents.', + 'body': '

Answers about lead times and paperwork.

How long is the lead time?

Standard orders ship in 15 working days.

', + 'meta': {'page_template': 'faq'}}, + ] + for row in batch: + r = factory.post(f'/api/sites/{sid}/entries', json=row) + assert r.status_code in (200, 201), r.text + assert factory.put(f'/api/factory/sites/{sid}/theme', json={'theme': 'trade-pro', 'version': '1.0.0'}).status_code == 200 + bid = factory.post(f'/api/sites/{sid}/builds', json={}).json()['version'] + + # ── v3 契约抽验:五种页面 200 / 唯一 h1 / pro-header + pro-footer ── + variant_urls = ['/brands/quanta/', '/services/export-docs/', '/faq-shipping/', '/search/', '/404.html'] + pages = {} + for url in variant_urls: + r = factory.get(f'/preview/{sid}/{bid}{url}') + assert r.status_code == 200, (url, r.text[:200]) + soup = BeautifulSoup(r.text, 'html.parser') + assert len(soup.find_all('h1')) == 1, url + assert soup.select_one('.pro-header') and soup.select_one('.pro-footer'), url + # 交互箭头一律 SVG(Task 9 契约) + assert not re.search(r'[←→↗↘⌕▾]', ''.join(a.get_text() for a in soup.find_all('a'))), url + pages[url] = soup + + # ── brand:hero(面包屑 Home › Brands › 品名 + 封面底图 + 锚点按钮)+ + # 同品牌产品网格(related,异品牌不混入)+ 品牌介绍(正文存在才渲染)── + bsoup = pages['/brands/quanta/'] + hero = bsoup.select_one('section.sf-tp-page-hero.sf-tp-brand-hero') + assert hero is not None + bg = hero.select_one('.sf-tp-page-hero-bg') + assert bg is not None and 'quanta-hall.jpg' in bg.get('src', '') + assert hero.select_one('.sf-tp-breadcrumb a[href$="/"]') + assert hero.select_one('.sf-tp-breadcrumb a[href$="/brands/"]') + assert hero.select_one('.sf-tp-breadcrumb-current').get_text(strip=True) == 'Quanta' + assert hero.select_one('.sf-tp-page-hero-copy h1').get_text(strip=True) == 'Quanta' + # hero 锚点按钮(automotive 1.4.0 同款信息架构):有产品才渲染 + assert hero.select_one('.sf-tp-page-hero-copy a.sf-tp-btn[href="#tp-brand-products"]') is not None + # 产品网格:related(fields.brand == 页题),异品牌产品不出现在页面上 + # (groups 无稳定顺序——断言与插入顺序解耦,只验集合内容) + grid = bsoup.select_one('.sf-tp-brand-products') + assert grid is not None and grid.get('id') == 'tp-brand-products' + cards = grid.select('.sf-tp-card-grid .sf-tp-card') + assert len(cards) == 2 + card_titles = {c.select_one('h3').get_text(strip=True) for c in cards} + assert card_titles == {'Quanta Valve V1', 'Quanta Pump P2'} + assert 'Rival Actuator' not in grid.get_text() + assert cards[0].select_one('.sf-tp-card-media') and cards[0].select_one('a.sf-tp-more svg') + assert grid.select_one('.sf-tp-section-head a.sf-tp-more[href$="/products/"] svg') + # 品牌介绍:body 存在才渲染(排版层双挂);空品牌不虚构介绍 + profile = bsoup.select_one('.sf-tp-brand-profile .sf-tp-article-body .pro-rich') + assert profile is not None and 'Heritage' in profile.get_text() + + # ── service:hero(面包屑 Home › Services › 标题)+ 正文排版层 + CTA 侧卡 ── + ssoup = pages['/services/export-docs/'] + shero = ssoup.select_one('section.sf-tp-page-hero.sf-tp-service-hero') + assert shero is not None + sbg = shero.select_one('.sf-tp-page-hero-bg') + assert sbg is not None and 'docs-desk.jpg' in sbg.get('src', '') + assert shero.select_one('.sf-tp-breadcrumb a[href$="/services/"]') + assert shero.select_one('.sf-tp-breadcrumb-current').get_text(strip=True) == 'Export Documentation Service' + body = ssoup.select_one('.sf-tp-service-body.sf-tp-article-body .pro-rich') + assert body is not None and 'What is included' in body.get_text() + aside = ssoup.select_one('.sf-tp-service-aside') + assert aside is not None + assert aside.select_one('a.sf-tp-btn[href$="/contact/"]') is not None # CTA + assert aside.select_one('a[href$="/services/"]') is not None # 返回服务列表 + + # ── faq:hero + 正文排版层(平台无 faq 内容类型,正文富文本承担)+ + # 引导联系卡 ── + fsoup = pages['/faq-shipping/'] + fhero = fsoup.select_one('section.sf-tp-page-hero.sf-tp-faq-hero') + assert fhero is not None + assert fhero.select_one('.sf-tp-breadcrumb a[href$="/faq/"]') + fbody = fsoup.select_one('.sf-tp-faq-body.sf-tp-article-body .pro-rich') + assert fbody is not None and 'How long is the lead time?' in fbody.get_text() + guide = fsoup.select_one('.sf-tp-faq-guide') + assert guide is not None and guide.select_one('a.sf-tp-btn[href$="/contact/"]') + + # ── search:与上游四主题同款平台机制(site-runtime.js ?q= 客户端检索 + # search-index.json),三钩子契约保留 + noindex + sf-tp 化的搜索面板 ── + psoup = pages['/search/'] + assert 'noindex' in str(psoup.find('meta', attrs={'name': 'robots'})) + assert psoup.select_one('main#main-content.sf-search-page') + assert psoup.select_one('label[for="sf-search"]') + inp = psoup.select_one('input#sf-search[type="search"]') + assert inp is not None and inp.get('autofocus') is not None + assert psoup.select_one('#sf-search-results') is not None + assert psoup.select_one('#sf-search-count[aria-live="polite"]') is not None + assert psoup.find('h1').get_text(strip=True) == 'Site Search' + # 机制资源随构建分发:site-runtime.js 页面引用 + 检索索引含真实内容 + # (write_assets 把 search-index.json 写在 dist 根,site-runtime.js 以 + # 相对解析——检索 URL 契约) + sr = factory.get(f'/preview/{sid}/{bid}/search/') + assert 'site-runtime.js' in sr.text + idx = factory.get(f'/preview/{sid}/{bid}/search-index.json') + assert idx.status_code == 200 and 'Quanta Valve V1' in idx.text + # search/notfound 无结尾 CTA 带(pro.html 既有分支) + assert 'pro-end-cta' not in sr.text + + # ── notfound:品牌化 404(大标题 + 英文文案 + 双按钮 + SVG 图标)── + nsoup = pages['/404.html'] + assert 'noindex' in str(nsoup.find('meta', attrs={'name': 'robots'})) + nsec = nsoup.select_one('.sf-tp-notfound') + assert nsec is not None + assert nsec.find('h1').get_text(strip=True) == '404' + assert nsec.select_one('.icon-circle svg') is not None # icon 宏渲染 + assert nsec.select_one('a.sf-tp-btn[href$="/"]') is not None # Back to home + assert nsec.select_one('a[href$="/products/"]') is not None # Browse products + assert 'pro-end-cta' not in str(nsoup) + + # 本构建链接全通(新增面包屑/CTA/锚点不产生断链) + report = factory.get(f'/api/sites/{sid}/builds/{bid}/report').json() + assert not report['errors'] and not report['broken_links'] and not report['missing_local_images'] + + # ── 空数据形态:空品牌(无产品/无正文)只显空态不虚构介绍;空服务/ + # 空 FAQ 正文整段隐藏、CTA/引导保留 ── + sid2 = site(factory) + for row in [ + {'type': 'brand', 'title': 'Orphan', 'route': '/brands/orphan/'}, + {'type': 'service', 'title': 'Placeholder Service', 'route': '/services/placeholder/'}, + {'type': 'page', 'title': 'Bare FAQ', 'route': '/faq-bare/', 'meta': {'page_template': 'faq'}}, + ]: + r = factory.post(f'/api/sites/{sid2}/entries', json=row) + assert r.status_code in (200, 201), r.text + assert factory.put(f'/api/factory/sites/{sid2}/theme', json={'theme': 'trade-pro', 'version': '1.0.0'}).status_code == 200 + bid2 = factory.post(f'/api/sites/{sid2}/builds', json={}).json()['version'] + for url, absent, present in [ + ('/brands/orphan/', ['.sf-tp-brand-products', '.sf-tp-brand-profile', '.sf-tp-card'], ['.sf-tp-listing-empty']), + ('/services/placeholder/', ['.sf-tp-service-body'], ['.sf-tp-service-aside']), + ('/faq-bare/', ['.sf-tp-faq-body'], ['.sf-tp-faq-guide']), + ]: + r = factory.get(f'/preview/{sid2}/{bid2}{url}') + assert r.status_code == 200, (url, r.text[:200]) + soup = BeautifulSoup(r.text, 'html.parser') + assert len(soup.find_all('h1')) == 1, url + for sel in absent: + assert soup.select_one(sel) is None, (url, sel) + for sel in present: + assert soup.select_one(sel) is not None, (url, sel) + + def test_trade_pro_chrome_layout(factory): # Task 10j:cms 页眉/页脚 chrome 视觉还原 + 手机端抽屉导航。 # 设计源 D:/project/cms/runtimes/astro-site-runtime/src/components/ diff --git a/themes/trade-pro/pages/brand.html b/themes/trade-pro/pages/brand.html index 9705456..d4105cc 100644 --- a/themes/trade-pro/pages/brand.html +++ b/themes/trade-pro/pages/brand.html @@ -1 +1,85 @@ -
BRAND PROFILE / PARTNERS

{{page.title}}

{{page.summary}}

{{cover(page,'brand')}}
{{body_copy(content,page)}}{% if related %}{{section_title('PRODUCT LINE','Related products','/products/')}}{{grid(related,'product')}}{% endif %}
+{# trade-pro brand detail — 品牌详情精修(Task 11,次要页面收官)。 + 设计参照(本轮无 cms 源对应):上游 themes/automotive/versions/1.4.0/ + pages/brand.html 的信息架构——品牌横幅(面包屑 + 眉题/h1/导语 + 页内 + 锚点按钮 + 品牌图)→ 车型/产品网格(section heading + "全部"动作位)→ + 品牌故事(眉题 + h2 + 正文);industrial 1.3.0 为同构薄形态。本模板以 + sf-tp 设计语言重组:sf-tp-page-hero(10c 内页 hero:封面底图 + brand-soft + 罩层)+ sf-tp-section-head/card-grid(10a 卡片系)+ sf-tp-article-body + 排版层双挂(10d)。 + 数据事实(make_context 对 brand kind):related = fields.brand 与页题 + (或 fields.brand 字段)一致的产品/车型集合——品牌页的产品网格只信它; + 派生品牌页(builder 从产品 brand 字段派生 /brands//)body/cover + 恒空、summary 是平台占位串「相关品牌及产品」——导语按 authored 判定 + 排除占位串(home.html 排除「首页」同款决策),空品牌不虚构介绍: + 无产品给数据无关的英文导语 + 空态卡,绝不编造品牌故事。 + 面包屑 Brands 链接 /brands/(required_sections 恒保证该路由存在); + hero 锚点按钮锚到 #tp-brand-products(构建器豁免 # 锚点,10h 同款)。 #} +{% set tp_brand_summary = page.summary if page.summary and page.summary != '相关品牌及产品' else '' %} +{# 导语优先级:authored 摘要 > 数据派生计数(Jinja 三元与 or 的优先级易误读, + 显式分行赋值避免歧义)> 空品牌通用引导 #} +{% if tp_brand_summary %}{% set tp_brand_lead = tp_brand_summary %} +{% elif related %}{% set tp_brand_lead = ('One product from ' if related|length == 1 else related|length ~ ' products from ') ~ page.title ~ ' — specifications and export documentation included.' %} +{% else %}{% set tp_brand_lead = 'Products assigned to this brand in the admin console appear here automatically.' %}{% endif %} +
+ {% if safe_image(page.cover) %}{% endif %} + +
+ +
+ BRAND PROFILE / PARTNERS +

{{ page.title }}

+

{{ tp_brand_lead }}

+ {% if related %}View brand products {{ icon('arrow-right') }}{% endif %} +
+
+
+{% if related %} +
+
+
+
+ PRODUCT LINE +

{{ page.title }} products

+
+ All products {{ icon('arrow-right', cls='sf-tp-icon--sm') }} +
+
+ {% for tp_item in related[:8] %} + + {% endfor %} +
+
+
+{% else %} +
+
+
+

No products listed under this brand yet

+

Products assigned to this brand appear here automatically once published.

+ Browse all products {{ icon('arrow-right') }} +
+
+
+{% endif %} +{% if content %} +
+
+
+
+ BRAND PROFILE +

About {{ page.title }}

+
+
+
{{ body_copy(content, page) }}
+
+
+{% endif %} diff --git a/themes/trade-pro/pages/faq.html b/themes/trade-pro/pages/faq.html index 6db86e9..c8e6db5 100644 --- a/themes/trade-pro/pages/faq.html +++ b/themes/trade-pro/pages/faq.html @@ -1,2 +1,56 @@ -
SUPPORT / FAQ

{{page.title}}

{{page.summary}}

-
ANSWERS

Frequently asked questions.

Cooperation process, trade terms and common questions.

{{body_copy(content,page)}}{% if page.cover %}{{page.title}}{% endif %}
+{# trade-pro faq — FAQ 页精修(Task 11)。 + 设计参照(本轮无 cms 源对应):上游 industrial/automotive 1.3.0/1.4.0 的 + faq 页与 service 共用通用信息页薄形态;本模板以 sf-tp 设计语言定 FAQ + 专属口径:sf-tp-page-hero(10c)→ 问题正文(sf-tp-article-body 排版层 + 双挂,10d)→ 深色引导联系卡(找不到答案 → /contact/,10d 侧栏 CTA + 同款深底白按钮语汇)。 + 数据事实(make_context 对 faq kind):平台无 faq 内容类型——FAQ 页由 + 普通页面条目承担(meta.page_template='faq' 显式指定,或 /faq 前缀路由 + 推断),内容 = 单一富文本 body。sanitize 的 ALLOWED_TAGS 不含 + details/summary(正文富文本带不进折叠结构);theme.css 的 details/ + summary 折叠样式(Task 8)服务的是块系统注入的 custom_blocks(编辑器 + faq 块原生 details 渲染在 sf-canvas 区),与本模板正交共存——模板 + 正文只能走排版层,问题/答案的分节由正文自身 h2/h3 承担(10g/10h 同 + 决策)。body 为空 → 正文区块整段隐藏换空态卡;引导联系卡恒渲染 + (真实动作位)。面包屑/链接指向 /faq/(required_sections 恒保证; + 当前页恰为 /faq/ 时自指隐藏)。 #} +
+ {% if safe_image(page.cover) %}{% endif %} + +
+ +
+ SUPPORT / FAQ +

{{ page.title }}

+ {% if page.summary %}

{{ page.summary }}

{% endif %} +
+
+
+
+
+ {% if content %} +
+
+ ANSWERS +

Frequently asked questions

+
+
+
{{ body_copy(content, page) }}
+ {% else %} +
+

No answers published here yet

+

Questions added to this page in the admin console appear here automatically.

+
+ {% endif %} +
+
+ STILL NEED HELP? +

Can't find your answer?

+

Send the question directly — replies within one business day.

+
+ Contact us {{ icon('arrow-right') }} +
+
+
diff --git a/themes/trade-pro/pages/notfound.html b/themes/trade-pro/pages/notfound.html index cf49837..f07242b 100644 --- a/themes/trade-pro/pages/notfound.html +++ b/themes/trade-pro/pages/notfound.html @@ -1 +1,22 @@ -
ERROR / 404

404

The page you are looking for does not exist or has been moved.

Back to home {{ icon('arrow-right') }}
+{# trade-pro notfound — 品牌化 404(Task 11)。 + 设计参照(本轮无 cms 源对应):上游 industrial/automotive 1.3.0/1.4.0 + 的 404 为「眉题 + 巨号 404 + 一句文案 + 返回首页」的居中薄形态;本模板 + 保留该信息架构并以 sf-tp 深色带语汇品牌化(10a 应用带的藏青渐变 + 光晕 + 底、反色文案、白底按钮反色),双动作位:返回首页 + 浏览产品目录。 + 渲染流:构建器以 mode='notfound' 渲染(noindex meta),pro.html 对 + notfound 不渲染结尾 CTA 带;h1 唯一(「404」大标题即页题)。图标走 + Task 9 icon 宏(search:找不到页面的语义),无裸箭头字符。 #} +
+
+
+ + ERROR / 404 +

404

+

The page you are looking for does not exist or has been moved.

+ +
+
+
diff --git a/themes/trade-pro/pages/search.html b/themes/trade-pro/pages/search.html index 56a6217..db8f944 100644 --- a/themes/trade-pro/pages/search.html +++ b/themes/trade-pro/pages/search.html @@ -1,2 +1,31 @@ -
SEARCH / CONTENT

Site Search

Search products, brands, news and cases across this site.

-
+{# trade-pro search — 站内搜索页精修(Task 11)。 + 机制决策:采用与上游四主题完全相同的平台静态检索机制(V8.31 起全主题 + 共享,非 automotive 专属)——构建器为每个构建打包 search-index.json + (全站条目标题/摘要/类型/品牌),site-runtime.js 在客户端完成 ?q= 深链 + 读取 + 关键词过滤 + 结果渲染,无任何服务端 API。JS 寻址的是 ID 钩子 + (#sf-search 输入 / #sf-search-results 容器 / #sf-search-count 计数, + factory.css 的 .sf-search-page 提供基线观感)——三钩子与 .sf-search-page + 类名必须原样保留(平台契约),否则检索退化为一页死输入框。 + 本模板的精修是观感层:hero 换 sf-tp-page-hero(10c 内页 hero 同款浅 + 品牌横幅,搜索是工具页无层级——不带面包屑,与上游一致),搜索面板由 + pro.css 的 Task 11 作用域规则把 factory.css 的 system-ui 基线覆写为 + cms 令牌观感(含 site-runtime.js 注入的 .sf-search-result 结果卡)。 + 构建器生成本页时 kind='search'(route 推断先于显式模板),robots 为 + noindex,follow;pro.html 对 search 页不渲染结尾 CTA 带。页面为英文 + (构建器的中文页题「站内搜索」仅进 ,不进正文)。 #} +<section class="sf-tp-page-hero sf-tp-search-hero"> + <div class="sf-tp-page-hero-overlay" aria-hidden="true"></div> + <div class="wrap"> + <div class="sf-tp-page-hero-copy" data-reveal> + <span class="eyebrow">SEARCH / CONTENT</span> + <h1>Site Search</h1> + <p class="sf-tp-page-hero-lead">Search products, brands, news and cases across this site.</p> + </div> + </div> +</section> +<main id="main-content" class="sf-search-page sf-tp-search-panel"> + <label for="sf-search">Enter a keyword</label> + <input type="search" id="sf-search" placeholder="Search products, brands, news, cases..." autofocus> + <div id="sf-search-count" aria-live="polite"></div> + <div id="sf-search-results"></div> +</main> diff --git a/themes/trade-pro/pages/service.html b/themes/trade-pro/pages/service.html index 2f57ad8..e4af976 100644 --- a/themes/trade-pro/pages/service.html +++ b/themes/trade-pro/pages/service.html @@ -1,2 +1,47 @@ -<section class="pro-inner-hero pro-info-hero"><div class="pro-wrap"><span class="pro-eyebrow">OUR SERVICES</span><h1>{{page.title}}</h1><p>{{page.summary}}</p></div></section> -<section class="pro-section pro-info-section"><div class="pro-wrap pro-info-grid"><div><span class="pro-eyebrow">SERVICE / SUPPORT</span><h2>{{page.title}}</h2><p class="pro-muted">Working process, service scope and support commitments.</p></div><div class="pro-info-copy">{{body_copy(content,page)}}{% if page.cover %}<img class="pro-info-photo" src="{{safe_image(page.cover)}}" alt="{{page.title}}">{% endif %}</div></div></section> +{# trade-pro service detail — 服务详情精修(Task 11)。 + 设计参照(本轮无 cms 源对应):上游 industrial/automotive 1.3.0/1.4.0 的 + service 页为通用信息页薄形态(hero + 双栏正文 + 封面图);本模板以 + sf-tp 设计语言重组为「详情页」口径:sf-tp-page-hero(10c:封面底图 + + 面包屑)→ detail 双栏(10g 案例页同构 1.2fr/0.8fr:文案列 = 眉题 + h2 + + 正文排版层;侧列 = sticky CTA 卡 + 返回服务列表)。 + 数据事实(make_context 对 service kind):仅页面条目上下文(title/ + summary/body/cover),无服务专属集合;正文 = 单一富文本 body,小节结构 + 由正文自身 h2 承担(10g/10h 同决策),sf-tp-article-body 排版层双挂 + (10d)。body 为空 → 正文区块整段隐藏,双栏退化为居中单列(CTA 卡 + 保留——真实动作位,非虚构内容)。 + 面包屑/返回链接指向 /services/(required_sections 恒保证该路由存在; + 当前页恰为 /services/ 时自指链接隐藏,面包屑降级为 Home › 标题)。 #} +<section class="sf-tp-page-hero sf-tp-service-hero"> + {% if safe_image(page.cover) %}<img class="sf-tp-page-hero-bg" src="{{ safe_image(page.cover) }}" alt="" width="1600" height="900" fetchpriority="high" decoding="async">{% endif %} + <div class="sf-tp-page-hero-overlay" aria-hidden="true"></div> + <div class="wrap"> + <nav class="sf-tp-breadcrumb" aria-label="Breadcrumb" data-reveal> + <a href="/">Home</a>{% if route != '/services/' %}<a href="/services/">Services</a>{% endif %}<span class="sf-tp-breadcrumb-current">{{ page.title }}</span> + </nav> + <div class="sf-tp-page-hero-copy" data-reveal style="--reveal-i: 1"> + <span class="eyebrow">SERVICE / SUPPORT</span> + <h1>{{ page.title }}</h1> + {% if page.summary %}<p class="sf-tp-page-hero-lead">{{ page.summary }}</p>{% endif %} + </div> + </div> +</section> +<section class="section sf-tp-service"> + <div class="wrap sf-tp-service-grid{{ '' if content else ' sf-tp-service-grid--solo' }}"> + {% if content %} + <div class="sf-tp-service-copy" data-reveal> + <span class="eyebrow">SERVICE DETAIL</span> + <h2>{{ page.title }}</h2> + <div class="sf-tp-service-body sf-tp-article-body" data-reveal style="--reveal-i: 1">{{ body_copy(content, page) }}</div> + </div> + {% endif %} + <aside class="sf-tp-service-aside" data-reveal style="--reveal-i: 1"> + <div class="sf-tp-service-cta"> + <span class="eyebrow">NEXT STEP</span> + <h3>Request this service</h3> + <p>Send your requirements and the team replies within one business day.</p> + <a class="sf-tp-btn" href="/contact/">Contact us {{ icon('arrow-right') }}</a> + </div> + {% if route != '/services/' %}<a class="sf-tp-service-back" href="/services/">{{ icon('arrow-left', cls='sf-tp-icon--sm') }} All services</a>{% endif %} + </aside> + </div> +</section> diff --git a/themes/trade-pro/pro.css b/themes/trade-pro/pro.css index f1b37bf..805f8ea 100644 --- a/themes/trade-pro/pro.css +++ b/themes/trade-pro/pro.css @@ -745,3 +745,85 @@ body.sf-theme-trade-pro{overflow-x:hidden;overflow-x:clip} .sf-theme-trade-pro .pro-footer-top{grid-template-columns:1fr;gap:var(--sf-tp-space-8)} .sf-theme-trade-pro .pro-footer-bottom{display:block} } + +/* ── Task 11:brand/service/faq/search/notfound 次要页面精修 ── + 设计参照:上游四主题 1.3.0/1.4.0 对应页的信息架构(automotive 1.4.0 + brand 页最完整:横幅 + 面包屑 + 锚点按钮 + 车型网格 + 品牌故事;其余为 + 通用信息页骨架)+ 已建立的 sf-tp 设计语言。共享组件零重复声明: + brand 的 hero/卡片网格/正文分别由 10c page-hero、10a card-grid、 + 10d article-body 既有规则(含 10a/10c 的 900/767/620 折叠块)覆盖—— + brand 模板无新增视觉规则;本块只补 service 双栏/CTA 卡、faq 引导卡、 + search 面板与结果卡、notfound 深色带。级联:全部为 Task 11 新类名的 + 首次声明,位于 10a-10j 既有块之后,无同特异性竞争;search 段按 + .sf-theme-trade-pro 作用域(0,2,0+)覆写 factory.css 的 .sf-search-page + 基线(0,1,0)与 site-runtime.js 注入的 .sf-search-result,机制钩子 + (ID 与类名)不在样式层触碰。 */ + +/* service 双栏(10g 案例页 1.2fr/0.8fr 同构):文案列 + sticky CTA 侧列; + 正文为空时 --solo 单列,CTA 卡居中收窄 */ +.sf-theme-trade-pro .sf-tp-service-grid{display:grid;grid-template-columns:1.2fr 0.8fr;gap:calc(var(--sf-tp-space-12) + var(--sf-tp-space-4));align-items:start} +.sf-theme-trade-pro .sf-tp-service-grid--solo{grid-template-columns:minmax(0,1fr)} +.sf-theme-trade-pro .sf-tp-service-grid--solo .sf-tp-service-aside{width:100%;max-width:560px;margin-inline:auto} +.sf-theme-trade-pro .sf-tp-service-copy h2{margin-block:var(--sf-tp-space-2) var(--sf-tp-space-6)} +.sf-theme-trade-pro .sf-tp-service-aside{position:sticky;top:calc(var(--sf-tp-space-16) + var(--sf-tp-space-4));display:grid;gap:var(--sf-tp-space-4);justify-items:start} +.sf-theme-trade-pro .sf-tp-service-cta{display:grid;gap:var(--sf-tp-space-3);padding:var(--sf-tp-card-padding-md);background:var(--sf-tp-color-surface-muted);border-radius:var(--sf-tp-card-border-radius)} +.sf-theme-trade-pro .sf-tp-service-cta h3{margin:0;font-size:var(--sf-tp-font-size-xl)} +.sf-theme-trade-pro .sf-tp-service-cta p{margin:0;color:var(--sf-tp-color-text-muted);font-size:var(--sf-tp-font-size-sm)} +.sf-theme-trade-pro .sf-tp-service-cta .sf-tp-btn{justify-self:start} +.sf-theme-trade-pro .sf-tp-service-back{display:inline-flex;align-items:center;gap:var(--sf-tp-space-2);font-size:var(--sf-tp-font-size-sm);font-weight:var(--sf-tp-font-weight-semibold);color:var(--sf-tp-color-text-link)} +.sf-theme-trade-pro .sf-tp-service-back:hover{color:var(--sf-tp-color-text-link-hover)} + +/* faq 引导联系卡(10d 侧栏 CTA 深底白按钮语汇):正文之后横排收束 */ +.sf-theme-trade-pro .sf-tp-faq-guide{display:flex;align-items:center;justify-content:space-between;gap:var(--sf-tp-space-6);flex-wrap:wrap;margin-block-start:var(--sf-tp-space-10);padding:var(--sf-tp-card-padding-md);background:var(--sf-tp-color-surface-inverse);border-radius:var(--sf-tp-card-border-radius);color:var(--sf-tp-color-text-inverse)} +.sf-theme-trade-pro .sf-tp-faq-guide .eyebrow{color:var(--sf-tp-color-text-inverse-muted)} +.sf-theme-trade-pro .sf-tp-faq-guide h3{margin-block-end:var(--sf-tp-space-2);color:var(--sf-tp-color-text-inverse)} +.sf-theme-trade-pro .sf-tp-faq-guide p{margin:0;color:color-mix(in srgb,var(--sf-tp-color-text-inverse) 82%,transparent);font-size:var(--sf-tp-font-size-sm)} +.sf-theme-trade-pro .sf-tp-faq-guide .sf-tp-btn{background:var(--sf-tp-color-surface-default);border-color:var(--sf-tp-color-surface-default);color:var(--sf-tp-color-text-brand)} +.sf-theme-trade-pro .sf-tp-faq-guide .sf-tp-btn:hover{background:var(--sf-tp-color-brand-primary);border-color:var(--sf-tp-color-brand-primary);color:var(--sf-tp-color-text-on-brand)} +/* 空态卡内的按钮(brand 空品牌/browse 产品):与文案拉开间距 */ +.sf-theme-trade-pro .sf-tp-listing-empty .sf-tp-btn{margin-block-start:var(--sf-tp-space-4)} + +/* search 面板:factory.css 的 .sf-search-page system-ui 基线 → cms 令牌 + 观感(含 site-runtime.js 注入的 .sf-search-result 结果卡样式); + #sf-search/#sf-search-results/#sf-search-count 三钩子为平台 JS 契约 */ +.sf-theme-trade-pro .sf-search-page{min-height:auto;width:min(880px,calc(100% - 32px));margin-inline:auto;padding-block:calc(var(--sf-tp-space-10) + var(--sf-tp-space-4)) calc(var(--sf-tp-space-16) + var(--sf-tp-space-8));font-family:var(--sf-tp-font-family-sans);color:var(--sf-tp-color-text-primary)} +.sf-theme-trade-pro .sf-search-page>label{display:block;margin:0 0 var(--sf-tp-space-3);font-size:var(--sf-tp-font-size-xs);font-weight:var(--sf-tp-font-weight-semibold);letter-spacing:var(--sf-tp-letter-spacing-wide);text-transform:uppercase;color:var(--sf-tp-color-text-muted)} +.sf-theme-trade-pro .sf-search-page input[type="search"]{display:block;width:100%;padding:var(--sf-tp-space-4) calc(var(--sf-tp-space-4) + var(--sf-tp-space-1));font-size:var(--sf-tp-font-size-lg);font-family:inherit;border:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-default);border-radius:var(--sf-tp-radius-sm);background:var(--sf-tp-color-surface-default);color:var(--sf-tp-color-text-primary)} +.sf-theme-trade-pro .sf-search-page #sf-search-count{margin:var(--sf-tp-space-5) 0 var(--sf-tp-space-3);color:var(--sf-tp-color-text-muted);font-size:var(--sf-tp-font-size-sm)} +.sf-theme-trade-pro .sf-search-result{padding:var(--sf-tp-space-5) 0;border-block-end:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-subtle)} +.sf-theme-trade-pro .sf-search-result a{color:var(--sf-tp-color-text-primary);font-size:var(--sf-tp-font-size-xl);font-weight:var(--sf-tp-font-weight-bold);text-decoration:none} +.sf-theme-trade-pro .sf-search-result a:hover{color:var(--sf-tp-color-text-brand)} +.sf-theme-trade-pro .sf-search-result small{display:block;margin-block-start:var(--sf-tp-space-1);color:var(--sf-tp-color-text-brand);font-size:var(--sf-tp-font-size-xs);letter-spacing:var(--sf-tp-letter-spacing-wide);text-transform:uppercase} +.sf-theme-trade-pro .sf-search-result p{margin-block-start:var(--sf-tp-space-2);color:var(--sf-tp-color-text-muted);font-size:var(--sf-tp-font-size-sm)} + +/* notfound 深色带(10a 应用带语汇:藏青渐变 + 光晕 + 反色文案): + 居中单列,巨号 404 为唯一 h1;双按钮(实底 + 深底上的白底反色 ghost) */ +.sf-theme-trade-pro .sf-tp-notfound{position:relative;overflow:hidden;padding-block:calc(var(--sf-tp-space-16) + var(--sf-tp-space-8)) calc(var(--sf-tp-space-16) + var(--sf-tp-space-4));color:var(--sf-tp-color-text-inverse);background:linear-gradient(115deg,var(--sf-tp-color-surface-inverse),var(--sf-tp-color-brand-primary-deep) 78%)} +.sf-theme-trade-pro .sf-tp-notfound::before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 15% 20%,color-mix(in srgb,var(--sf-tp-color-brand-primary) 24%,transparent),transparent 42%);pointer-events:none} +.sf-theme-trade-pro .sf-tp-notfound .wrap{position:relative;z-index:var(--sf-tp-z-raised)} +.sf-theme-trade-pro .sf-tp-notfound-inner{display:grid;justify-items:center;max-width:560px;margin-inline:auto;text-align:center} +.sf-theme-trade-pro .sf-tp-notfound-inner .eyebrow{color:var(--sf-tp-color-text-inverse-muted)} +.sf-theme-trade-pro .sf-tp-notfound-inner h1{margin-block:var(--sf-tp-space-2) var(--sf-tp-space-3);font-size:clamp(120px,22vw,220px);line-height:.9;letter-spacing:var(--sf-tp-letter-spacing-tight);color:var(--sf-tp-color-text-inverse)} +.sf-theme-trade-pro .sf-tp-notfound-icon{background:color-mix(in srgb,var(--sf-tp-color-neutral-0) 12%,transparent);color:var(--sf-tp-color-text-inverse)} +.sf-theme-trade-pro .sf-tp-notfound-lead{color:color-mix(in srgb,var(--sf-tp-color-text-inverse) 88%,transparent);font-size:var(--sf-tp-font-size-lg);margin-block-end:var(--sf-tp-space-8)} +.sf-theme-trade-pro .sf-tp-notfound-actions{display:flex;gap:var(--sf-tp-space-4);flex-wrap:wrap;justify-content:center} +.sf-theme-trade-pro .sf-tp-notfound .sf-tp-btn--ghost{background:var(--sf-tp-color-surface-default);border-color:var(--sf-tp-color-surface-default);color:var(--sf-tp-color-text-brand)} + +/* ── Task 11 响应式(新类名折叠收本块;hero 竖版/卡片栅格/正文紧凑由 + 10c/10a/10d 既有媒体块经共用类名覆盖——10c 教训:同特异性按源顺序 + 取胜,本块只声明 Task 11 新类名)── */ +@media (max-width: 900px){ + .sf-theme-trade-pro .sf-tp-service-grid{grid-template-columns:1fr;gap:var(--sf-tp-space-8)} + .sf-theme-trade-pro .sf-tp-service-aside{position:static} +} +@media (max-width: 620px){ + .sf-theme-trade-pro .sf-tp-service-cta{padding:16px} + .sf-theme-trade-pro .sf-tp-faq-guide{display:block} + .sf-theme-trade-pro .sf-tp-faq-guide .sf-tp-btn{margin-block-start:12px} + .sf-theme-trade-pro .sf-search-page{padding-block:28px 72px} + .sf-theme-trade-pro .sf-search-page input[type="search"]{padding:13px 16px;font-size:var(--sf-tp-font-size-md)} + .sf-theme-trade-pro .sf-search-result a{font-size:var(--sf-tp-font-size-lg)} + .sf-theme-trade-pro .sf-tp-notfound{padding-block:64px 56px} + .sf-theme-trade-pro .sf-tp-notfound-inner h1{font-size:clamp(96px,26vw,140px)} + .sf-theme-trade-pro .sf-tp-notfound-actions{display:grid;grid-template-columns:1fr;justify-items:stretch} +}