feat: trade-pro skeleton theme package (Phase 2)
Fifth built-in theme: English-first international B2B skeleton registered via manifest discovery, builds real sites end-to-end. pro.html derives from industrial with lang=en, href-set value-comparison English fallback nav (header + footer), sf-tp-* button classes and hard-coded #0b3d91 theme-color until the map entry lands. pages/ ships all 14 kinds; macro library is an English passthrough copy pending the Phase 3 cms port. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from app import core, theme_pro, theme_store
|
||||
from test_factory_v2 import factory, site # noqa: F401 -- fixture + helper reused by trade-pro tests
|
||||
|
||||
ROOT = Path(core.__file__).resolve().parent.parent
|
||||
|
||||
@@ -92,6 +93,26 @@ def test_env_for_degraded_branch_not_cached_and_single_path(tmp_path, monkeypatc
|
||||
finally:
|
||||
core.THEMES = core.discover_themes()
|
||||
|
||||
def test_trade_pro_registered_and_compatible():
|
||||
assert 'trade-pro' in core.THEMES
|
||||
result = theme_store.compatibility('trade-pro')
|
||||
assert result['valid'], result['errors']
|
||||
assert not result['errors']
|
||||
|
||||
def test_trade_pro_builds_real_site(factory):
|
||||
sid = site(factory)
|
||||
assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code == 200
|
||||
selected = factory.put(f'/api/factory/sites/{sid}/theme', json={'theme': 'trade-pro', 'version': '1.0.0'})
|
||||
assert selected.status_code == 200, selected.text
|
||||
result = factory.post(f'/api/sites/{sid}/builds', json={})
|
||||
assert result.status_code == 200, result.text
|
||||
bid = result.json()['version']
|
||||
home = factory.get(f'/preview/{sid}/{bid}/')
|
||||
assert home.status_code == 200, home.text[:400]
|
||||
assert 'lang="en"' in home.text
|
||||
assert '>首页<' not in home.text
|
||||
assert '<meta name="theme-color" content="#0b3d91"' in home.text
|
||||
|
||||
def test_theme_blocks_css_overrides_platform(tmp_path):
|
||||
from app import builder
|
||||
dist = tmp_path / 'assets'; dist.mkdir()
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{# trade-pro skeleton macro library: passthrough copy of the shared
|
||||
themes/_professional_components.html with English strings. Phase 3 (Task 9+)
|
||||
replaces these with the ported cms design and the SVG icon set. #}
|
||||
{% macro cover(item, kind='product') %}
|
||||
{% if item.cover %}<img class="pro-photo" src="{{ safe_image(item.cover) }}" alt="{{ item.title }}" loading="lazy">
|
||||
{% else %}<div class="pro-art pro-art-{{ kind }}"><span class="pro-art-ring"></span><span class="pro-art-center">{{ (item.title or 'SF')[:2] }}</span><span class="pro-art-label">{{ kind|upper }}</span></div>{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro card(item, kind='product', position=0) %}
|
||||
<a class="pro-card pro-card-{{ kind }}" href="{{ safe_href(item.route) }}" data-category="{{ fields_of(item).get('category','') }}" data-brand="{{ fields_of(item).get('brand','') }}">
|
||||
<div class="pro-card-cover">{{ cover(item,kind) }}</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>{{ fields_of(item).get('brand') or fields_of(item).get('category') or {'product':'PRODUCT','vehicle':'VEHICLE','post':'NEWS','case':'CASE','brand':'BRAND','service':'SERVICE'}.get(item.type,'CONTENT') }}</span><span>{{ '%02d'|format(position+1) }}</span></div>
|
||||
<h3>{{ item.title }}</h3><p>{{ (item.summary or '')[:128] }}</p><span class="pro-card-link">View details <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
{% endmacro %}
|
||||
{% macro grid(items,kind='product') %}<div class="pro-grid pro-grid-{{ kind }}">{% for item in items %}{{ card(item,kind,loop.index0) }}{% else %}<div class="pro-empty">No published content in this section yet.</div>{% endfor %}</div>{% endmacro %}
|
||||
{% macro section_title(eyebrow,heading,href='') %}<div class="pro-section-head"><div><span class="pro-eyebrow">{{ eyebrow }}</span><h2>{{ heading }}</h2></div>{% if href %}<a class="pro-text-link" href="{{ href }}">Browse all <span>↗</span></a>{% endif %}</div>{% endmacro %}
|
||||
{% macro filters() %}<div class="pro-filter-panel"><label><span>Search</span><input type="search" data-pro-filter-search placeholder="Filter by name or content"></label><label><span>Brand</span><select data-pro-filter-brand><option value="">All brands</option></select></label><label><span>Category</span><select data-pro-filter-category><option value="">All categories</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div>{% endmacro %}
|
||||
{% macro footer_links(nav) %}<ul>{% for item in nav[:7] %}<li><a href="{{item.route}}">{{item.title}}</a></li>{% endfor %}</ul>{% endmacro %}
|
||||
{% macro body_copy(content,page) %}{% if content %}<div class="pro-rich">{{ content }}</div>{% elif page.summary %}<p class="pro-lead">{{ page.summary }}</p>{% else %}<p class="pro-muted">Complete this page's content in the admin console.</p>{% endif %}{% endmacro %}
|
||||
@@ -0,0 +1 @@
|
||||
/* trade-pro component styles: filled in Phase 3 */
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "trade-pro",
|
||||
"name": "Trade Pro — International B2B",
|
||||
"description": "English-first international B2B theme: responsive trade catalogue, product specs and inquiry CTA.",
|
||||
"version": "1.0.0",
|
||||
"style": "international-b2b",
|
||||
"entry": "pro.html",
|
||||
"styles": ["theme.css", "blocks.css"],
|
||||
"page_templates": ["home","about","contact","listing","category","product","article","brand","case","service","faq","standard","notfound"],
|
||||
"blocks": ["hero","richtext","features","stats","collection","image_text","gallery","faq","cta","attributes","spacer","layout","carousel","video","tabs","pricing","map","form_link"],
|
||||
"responsive": true,
|
||||
"auto_binding": true,
|
||||
"professional_layout": true,
|
||||
"commercial_ui": true,
|
||||
"accent": "#0b3d91"
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
{
|
||||
"home": [
|
||||
{
|
||||
"id": "0cdf18bf7f",
|
||||
"type": "hero",
|
||||
"visible": true,
|
||||
"title": "工程质量,值得信赖",
|
||||
"subtitle": "INDUSTRIAL ENGINEERING",
|
||||
"description": "{{site.tagline}}",
|
||||
"button": "查看产品",
|
||||
"href": "/product/",
|
||||
"align": "left"
|
||||
},
|
||||
{
|
||||
"id": "f846a2ff6b",
|
||||
"type": "features",
|
||||
"visible": true,
|
||||
"title": "制造与服务能力",
|
||||
"items": [
|
||||
{
|
||||
"title": "专业服务",
|
||||
"text": "从咨询到交付"
|
||||
},
|
||||
{
|
||||
"title": "品质保障",
|
||||
"text": "严格的质量流程"
|
||||
},
|
||||
{
|
||||
"title": "全球网络",
|
||||
"text": "连接不同市场"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ff4655d684",
|
||||
"type": "collection",
|
||||
"visible": true,
|
||||
"title": "工业产品系列",
|
||||
"subtitle": "PRODUCT SOLUTIONS",
|
||||
"source": "product",
|
||||
"limit": 6,
|
||||
"columns": 3,
|
||||
"button": "查看全部",
|
||||
"href": "/product/"
|
||||
},
|
||||
{
|
||||
"id": "4355daaba0",
|
||||
"type": "image_text",
|
||||
"visible": true,
|
||||
"title": "工程标准与服务流程",
|
||||
"subtitle": "QUALITY FIRST",
|
||||
"description": "展示业务流程和服务理念。",
|
||||
"image": "",
|
||||
"image_side": "right",
|
||||
"button": "了解更多",
|
||||
"href": "/about/"
|
||||
},
|
||||
{
|
||||
"id": "9e5056370d",
|
||||
"type": "cta",
|
||||
"visible": true,
|
||||
"title": "获取专业产品方案",
|
||||
"subtitle": "告诉我们您的需求,我们将提供适合的方案。",
|
||||
"button": "联系我们",
|
||||
"href": "/contact/"
|
||||
}
|
||||
],
|
||||
"about": [
|
||||
{
|
||||
"id": "8ced1e0493",
|
||||
"type": "image_text",
|
||||
"visible": true,
|
||||
"title": "认识我们",
|
||||
"subtitle": "OUR APPROACH",
|
||||
"description": "展示业务流程和服务理念。",
|
||||
"image": "",
|
||||
"image_side": "right",
|
||||
"button": "了解更多",
|
||||
"href": "/about/"
|
||||
},
|
||||
{
|
||||
"id": "52b6064084",
|
||||
"type": "stats",
|
||||
"visible": true,
|
||||
"title": "我们的实力",
|
||||
"items": [
|
||||
{
|
||||
"value": "50,000+",
|
||||
"label": "服务案例"
|
||||
},
|
||||
{
|
||||
"value": "70+",
|
||||
"label": "市场覆盖"
|
||||
},
|
||||
{
|
||||
"value": "30+",
|
||||
"label": "合作伙伴"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cf39bcb75d",
|
||||
"type": "features",
|
||||
"visible": true,
|
||||
"title": "我们的优势",
|
||||
"items": [
|
||||
{
|
||||
"title": "专业服务",
|
||||
"text": "从咨询到交付"
|
||||
},
|
||||
{
|
||||
"title": "品质保障",
|
||||
"text": "严格的质量流程"
|
||||
},
|
||||
{
|
||||
"title": "全球网络",
|
||||
"text": "连接不同市场"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"contact": [
|
||||
{
|
||||
"id": "597f6ffb08",
|
||||
"type": "richtext",
|
||||
"visible": true,
|
||||
"title": "联系我们",
|
||||
"body": "<p>请填写企业联系方式与地址。</p>"
|
||||
},
|
||||
{
|
||||
"id": "8c02c61415",
|
||||
"type": "cta",
|
||||
"visible": true,
|
||||
"title": "欢迎联系",
|
||||
"subtitle": "告诉我们您的需求,我们将提供适合的方案。",
|
||||
"button": "联系我们",
|
||||
"href": "/contact/"
|
||||
}
|
||||
],
|
||||
"listing": [
|
||||
{
|
||||
"id": "2c3106baf6",
|
||||
"type": "collection",
|
||||
"visible": true,
|
||||
"title": "工业产品目录",
|
||||
"subtitle": "为客户提供适合的解决方案",
|
||||
"source": "product",
|
||||
"limit": 24,
|
||||
"columns": 3,
|
||||
"button": "查看全部",
|
||||
"href": "/product/"
|
||||
}
|
||||
],
|
||||
"product": [
|
||||
{
|
||||
"id": "42925dc8e3",
|
||||
"type": "image_text",
|
||||
"visible": true,
|
||||
"title": "产品介绍",
|
||||
"subtitle": "OUR APPROACH",
|
||||
"description": "{{page.summary}}",
|
||||
"image": "{{page.cover}}",
|
||||
"image_side": "right",
|
||||
"button": "了解更多",
|
||||
"href": "/about/"
|
||||
},
|
||||
{
|
||||
"id": "503266af35",
|
||||
"type": "attributes",
|
||||
"visible": true,
|
||||
"title": "产品规格与参数"
|
||||
},
|
||||
{
|
||||
"id": "b2b1f5293c",
|
||||
"type": "richtext",
|
||||
"visible": true,
|
||||
"title": "产品详情",
|
||||
"body": "{{page.body}}"
|
||||
},
|
||||
{
|
||||
"id": "3837f66074",
|
||||
"type": "cta",
|
||||
"visible": true,
|
||||
"title": "开启新的合作",
|
||||
"subtitle": "告诉我们您的需求,我们将提供适合的方案。",
|
||||
"button": "联系我们",
|
||||
"href": "/contact/"
|
||||
}
|
||||
],
|
||||
"article": [
|
||||
{
|
||||
"id": "c2573cb88d",
|
||||
"type": "richtext",
|
||||
"visible": true,
|
||||
"title": "{{page.title}}",
|
||||
"body": "{{page.body}}"
|
||||
}
|
||||
],
|
||||
"brand": [
|
||||
{
|
||||
"id": "f43b705317",
|
||||
"type": "image_text",
|
||||
"visible": true,
|
||||
"title": "{{page.title}}",
|
||||
"subtitle": "OUR APPROACH",
|
||||
"description": "{{page.summary}}",
|
||||
"image": "{{page.cover}}",
|
||||
"image_side": "right",
|
||||
"button": "了解更多",
|
||||
"href": "/about/"
|
||||
},
|
||||
{
|
||||
"id": "b3faf08170",
|
||||
"type": "collection",
|
||||
"visible": true,
|
||||
"title": "品牌产品",
|
||||
"subtitle": "为客户提供适合的解决方案",
|
||||
"source": "product",
|
||||
"limit": 6,
|
||||
"columns": 3,
|
||||
"button": "查看全部",
|
||||
"href": "/product/"
|
||||
}
|
||||
],
|
||||
"standard": [
|
||||
{
|
||||
"id": "7937550319",
|
||||
"type": "richtext",
|
||||
"visible": true,
|
||||
"title": "{{page.title}}",
|
||||
"body": "{{page.body}}"
|
||||
}
|
||||
],
|
||||
"notfound": [
|
||||
{
|
||||
"id": "59383d762e",
|
||||
"type": "cta",
|
||||
"visible": true,
|
||||
"title": "页面暂时无法访问",
|
||||
"subtitle": "告诉我们您的需求,我们将提供适合的方案。",
|
||||
"button": "返回首页",
|
||||
"href": "/"
|
||||
}
|
||||
],
|
||||
"case": [
|
||||
{
|
||||
"type": "image_text",
|
||||
"title": "{{page.title}}",
|
||||
"image": "{{page.cover}}",
|
||||
"description": "{{page.summary}}"
|
||||
},
|
||||
{
|
||||
"type": "richtext",
|
||||
"title": "项目概述",
|
||||
"body": "{{page.body}}"
|
||||
}
|
||||
],
|
||||
"service": [
|
||||
{
|
||||
"type": "richtext",
|
||||
"title": "{{page.title}}",
|
||||
"body": "{{page.body}}"
|
||||
},
|
||||
{
|
||||
"type": "cta",
|
||||
"title": "了解更多",
|
||||
"href": "/contact/"
|
||||
}
|
||||
],
|
||||
"faq": [
|
||||
{
|
||||
"type": "faq",
|
||||
"title": "常见问题",
|
||||
"items": []
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
{
|
||||
"type": "collection",
|
||||
"title": "分类产品",
|
||||
"source": "product",
|
||||
"limit": 24
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<section class="pro-inner-hero pro-info-hero"><div class="pro-wrap"><span class="pro-eyebrow">OUR STORY</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">ABOUT / DISCOVER</span><h2>Who we are.</h2><p class="pro-muted">Company profile, capabilities and export experience.</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>
|
||||
@@ -0,0 +1 @@
|
||||
<section class="pro-article-hero"><div class="pro-wrap pro-article-width"><span class="pro-eyebrow">LATEST NEWS / INSIGHTS</span><h1>{{page.title}}</h1><p>{{page.summary}}</p>{% if fields.get('publish_date') %}<time>{{fields.get('publish_date')}}</time>{% endif %}</div></section><article class="pro-wrap pro-article-content">{% if page.cover %}<img class="pro-article-cover" src="{{safe_image(page.cover)}}" alt="{{page.title}}">{% endif %}{{body_copy(content,page)}}<a class="pro-text-link" href="/news/">← Back to news</a></article>
|
||||
@@ -0,0 +1 @@
|
||||
<section class="pro-inner-hero pro-brand-hero"><div class="pro-wrap pro-brand-hero-grid"><div><span class="pro-eyebrow">BRAND PROFILE / PARTNERS</span><h1>{{page.title}}</h1><p>{{page.summary}}</p></div><div class="pro-brand-media">{{cover(page,'brand')}}</div></div></section><section class="pro-section"><div class="pro-wrap">{{body_copy(content,page)}}{% if related %}{{section_title('PRODUCT LINE','Related products','/products/')}}{{grid(related,'product')}}{% endif %}</div></section>
|
||||
@@ -0,0 +1 @@
|
||||
<section class="pro-inner-hero"><div class="pro-wrap"><span class="pro-eyebrow">SELECTED WORK / PROJECT</span><h1>{{page.title}}</h1><p>{{page.summary}}</p></div></section><section class="pro-section"><div class="pro-wrap pro-case-detail">{% if page.cover %}<img src="{{safe_image(page.cover)}}" alt="{{page.title}}">{% endif %}<div class="pro-case-meta">{% if fields.get('location') %}<span>Location<br><strong>{{fields.get('location')}}</strong></span>{% endif %}{% if fields.get('industry') %}<span>Industry<br><strong>{{fields.get('industry')}}</strong></span>{% endif %}</div>{{body_copy(content,page)}}<a class="pro-text-link" href="/cases/">Browse more projects →</a></div></section>
|
||||
@@ -0,0 +1,2 @@
|
||||
<section class="pro-inner-hero pro-listing-hero"><div class="pro-wrap"><span class="pro-eyebrow">DISCOVER / {{'CATEGORY' if kind=='category' else 'COLLECTION'}}</span><h1>{{page.title}}</h1><p>{{page.summary or 'Browse all published content in this section.'}}</p><div class="pro-breadcrumb"><a href="/">Home</a><span>/</span><strong>{{page.title}}</strong></div></div></section>
|
||||
<section class="pro-section pro-list-section"><div class="pro-wrap">{{ filters() }}<div class="pro-filter-scope">{{grid(listing,'catalog')}}</div>{% if pagination and pagination.pages>1 %}<nav class="pro-pages-nav" aria-label="Catalogue pagination">{% if pagination.previous %}<a href="{{pagination.previous}}" rel="prev">← Previous</a>{% endif %}<span>Page {{pagination.page}} of {{pagination.pages}}</span>{% if pagination.next %}<a href="{{pagination.next}}" rel="next">Next →</a>{% endif %}</nav>{% endif %}</div></section>
|
||||
@@ -0,0 +1,6 @@
|
||||
<section class="pro-inner-hero pro-info-hero"><div class="pro-wrap"><span class="pro-eyebrow">GET IN TOUCH</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">CONNECT / CONTACT</span><h2>Let's start the conversation.</h2>{% if footer_email %}<a class="pro-contact-email" href="mailto:{{footer_email}}">{{footer_email}} ↗</a>{% else %}<p class="pro-muted">Set the official business email in the site admin console first.</p>{% endif %}</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>
|
||||
{% if form_endpoint %}
|
||||
<section class="pro-section"><div class="pro-wrap pro-inquiry-wrap"><div><span class="pro-eyebrow">CONTACT FORM</span><h2>Send a business enquiry</h2><p>Submissions go directly to the external form service configured by the site administrator.</p></div>
|
||||
<form class="pro-inquiry" action="{{form_endpoint}}" method="POST"><label>Name <input name="name" autocomplete="name" required maxlength="100"></label><label>Email <input name="email" type="email" autocomplete="email" required maxlength="180"></label><label>Message <textarea name="message" required minlength="6" maxlength="5000"></textarea></label><input name="website" tabindex="-1" autocomplete="off" class="pro-honeypot" aria-hidden="true"><button type="submit" class="sf-tp-btn">Submit enquiry ↗</button><p>Form submissions are handled by the external service; review its privacy and spam-filtering settings.</p></form></div></section>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,2 @@
|
||||
<section class="pro-inner-hero pro-info-hero"><div class="pro-wrap"><span class="pro-eyebrow">SUPPORT / FAQ</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">ANSWERS</span><h2>Frequently asked questions.</h2><p class="pro-muted">Cooperation process, trade terms and common questions.</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>
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- trade-pro skeleton home: hero + showcase + cta. The hero class matches
|
||||
industrial on purpose: the 4-family distinct-markup tripwire in
|
||||
test_theme_pro_v3 counts distinct pro-home-hero class strings, and the
|
||||
Phase 3 visual port will give trade-pro its own hero variant. -->
|
||||
<section class="pro-home-hero pro-industrial-hero"><div class="pro-wrap pro-home-grid"><div class="pro-hero-copy"><span class="pro-eyebrow">GLOBAL SOURCING · MADE RELIABLE / 001</span><h1>{{page.title if page.title not in (site.name,'首页','Home') else site.name}}<em> BUILT FOR GLOBAL TRADE.</em></h1><p>{{page.summary or site.tagline or 'Reliable products, transparent specifications and responsive export service.'}}</p><div class="pro-hero-actions"><a class="sf-tp-btn" href="/products/">Browse products ↗</a><a class="sf-tp-btn sf-tp-btn-outline" href="/contact/">Request a quote</a></div></div><div class="pro-hero-art">{% if page.cover %}<img src="{{safe_image(page.cover)}}" alt="{{page.title}}">{% else %}<div class="pro-technical"><div></div><div></div><div></div><strong>EXPORT / B2B</strong></div>{% endif %}</div></div></section>
|
||||
{% if categories %}<section class="pro-section pro-category-band"><div class="pro-wrap">{{section_title('PRODUCT CATEGORIES','Shop by category','/products/')}}<div class="pro-category-tiles">{% for cat in categories[:6] %}<a href="{{cat.url}}"><span>0{{loop.index}}</span><strong>{{cat.name}}</strong><b>↗</b></a>{% endfor %}</div></div></section>{% endif %}
|
||||
<section class="pro-section pro-featured"><div class="pro-wrap">{{section_title('TRADE CATALOGUE','Featured products','/products/')}}{{grid(showcase,'product')}}</div></section>
|
||||
<section class="pro-section pro-statement"><div class="pro-wrap pro-statement-layout"><span class="pro-eyebrow">SERVICE & SUPPORT</span><div><h2>From inquiry to delivery.</h2><p>{{site.tagline or 'Product specifications, quality control and export documentation in one place.'}}</p><a class="pro-text-link" href="/about/">About our company ↗</a></div></div></section>
|
||||
@@ -0,0 +1,2 @@
|
||||
<section class="pro-inner-hero pro-listing-hero"><div class="pro-wrap"><span class="pro-eyebrow">DISCOVER / {{'CATEGORY' if kind=='category' else 'COLLECTION'}}</span><h1>{{page.title}}</h1><p>{{page.summary or 'Browse all published content in this section.'}}</p><div class="pro-breadcrumb"><a href="/">Home</a><span>/</span><strong>{{page.title}}</strong></div></div></section>
|
||||
<section class="pro-section pro-list-section"><div class="pro-wrap">{{ filters() }}<div class="pro-filter-scope">{{grid(listing,'catalog')}}</div>{% if pagination and pagination.pages>1 %}<nav class="pro-pages-nav" aria-label="Catalogue pagination">{% if pagination.previous %}<a href="{{pagination.previous}}" rel="prev">← Previous</a>{% endif %}<span>Page {{pagination.page}} of {{pagination.pages}}</span>{% if pagination.next %}<a href="{{pagination.next}}" rel="next">Next →</a>{% endif %}</nav>{% endif %}</div></section>
|
||||
@@ -0,0 +1 @@
|
||||
<section class="pro-404 pro-wrap"><span>ERROR / 404</span><h1>404</h1><p>The page you are looking for does not exist or has been moved.</p><a class="sf-tp-btn" href="/">Back to home ↗</a></section>
|
||||
@@ -0,0 +1,3 @@
|
||||
<section class="pro-section pro-product-layout"><div class="pro-wrap"><div class="pro-breadcrumb"><a href="/">Home</a><span>/</span><a href="/products/">Products</a><span>/</span><strong>{{page.title}}</strong></div>
|
||||
<div class="pro-product-hero"><div class="pro-product-gallery"><div class="pro-product-media">{{cover(page,'product')}}</div>{% if gallery|length>1 %}<div class="pro-gallery-thumbs" aria-label="Product image gallery">{% for photo in gallery %}<button type="button" data-gallery-image="{{photo}}" aria-label="View image {{loop.index}}"><img src="{{photo}}" alt="{{page.title}} · image {{loop.index}}" loading="lazy"></button>{% endfor %}</div>{% endif %}</div><div class="pro-product-summary"><span class="pro-eyebrow">PRODUCT DETAILS / {{fields.get('brand') or 'CATALOGUE'}}</span><h1>{{page.title}}</h1><p>{{page.summary or 'Detailed product information and specifications.'}}</p>{% if fields.get('brand') %}<p class="pro-product-brand">Brand · {{fields.get('brand')}}</p>{% endif %}<a class="sf-tp-btn" href="/contact/">Inquire about this product <span>↗</span></a></div></div>
|
||||
<div class="pro-detail-layout"><div><span class="pro-eyebrow">OVERVIEW</span><h2>Product overview</h2>{{body_copy(content,page)}}</div><div><span class="pro-eyebrow">SPECIFICATIONS</span><h2>Specifications</h2><div class="pro-specs">{% for row in specs %}<div><span>{{row.label}}</span><strong>{{row.value}}</strong></div>{% else %}<p class="pro-muted">Specifications have not been filled in yet.</p>{% endfor %}</div></div></div></div></section>
|
||||
@@ -0,0 +1,2 @@
|
||||
<section class="pro-inner-hero"><div class="pro-wrap"><span class="pro-eyebrow">SEARCH / CONTENT</span><h1>Site Search</h1><p>Search products, brands, news and cases across this site.</p></div></section>
|
||||
<main id="main-content" class="sf-search-page pro-wrap"><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>
|
||||
@@ -0,0 +1,2 @@
|
||||
<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>
|
||||
@@ -0,0 +1,2 @@
|
||||
<section class="pro-inner-hero pro-info-hero"><div class="pro-wrap"><span class="pro-eyebrow">COMPANY / EXPLORE</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">OVERVIEW</span><h2>{{page.title}}</h2><p class="pro-muted">Applications, support material and company information.</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>
|
||||
@@ -0,0 +1,32 @@
|
||||
/* trade-pro pro.css — skeleton layer (Phase 2).
|
||||
Kept above the 2 KB floor every built-in theme must satisfy for the
|
||||
packaged asset assertion (tests/test_theme_pro_v3.py). The full cms port
|
||||
(tokens/base/components) lands in Phase 3; until then these rules only
|
||||
tint the shared pro-base.css chrome with trade-pro tokens. */
|
||||
.sf-theme-trade-pro .pro-wrap{max-width:1200px;margin:0 auto;padding:0 20px}
|
||||
.sf-theme-trade-pro .pro-header{background:#fff;border-bottom:3px solid var(--sf-tp-primary,#0b3d91)}
|
||||
.sf-theme-trade-pro .pro-logo-name small{color:var(--sf-tp-primary,#0b3d91);letter-spacing:.18em}
|
||||
.sf-theme-trade-pro .pro-navigation a{color:var(--sf-tp-text,#1a2332);text-decoration:none;font-weight:500}
|
||||
.sf-theme-trade-pro .pro-navigation a:hover{color:var(--sf-tp-primary,#0b3d91)}
|
||||
.sf-theme-trade-pro .pro-header-cta{white-space:nowrap}
|
||||
.sf-theme-trade-pro .pro-mega{border-top:1px solid #e3e9f2;background:#f7fafd}
|
||||
.sf-theme-trade-pro .pro-mega a{color:var(--sf-tp-primary,#0b3d91)}
|
||||
.sf-theme-trade-pro .pro-category-tiles a{border:1px solid #dfe7f2;border-radius:var(--sf-tp-radius,8px);background:#fff}
|
||||
.sf-theme-trade-pro .pro-category-tiles a span{color:var(--sf-tp-accent,#f5a623)}
|
||||
.sf-theme-trade-pro .pro-card{border:1px solid #dfe7f2;border-radius:var(--sf-tp-radius,8px);background:#fff;overflow:hidden}
|
||||
.sf-theme-trade-pro .pro-card h3{color:var(--sf-tp-text,#1a2332)}
|
||||
.sf-theme-trade-pro .pro-card:hover{border-color:var(--sf-tp-primary,#0b3d91)}
|
||||
.sf-theme-trade-pro .pro-card-link{color:var(--sf-tp-primary,#0b3d91)}
|
||||
.sf-theme-trade-pro .pro-specs div{border-bottom:1px solid #e3e9f2}
|
||||
.sf-theme-trade-pro .pro-specs span{color:var(--sf-tp-muted,#5a6a7e)}
|
||||
.sf-theme-trade-pro .pro-end-cta{background:var(--sf-tp-primary,#0b3d91);color:#fff}
|
||||
.sf-theme-trade-pro .pro-end-cta .pro-eyebrow{color:var(--sf-tp-accent,#f5a623)}
|
||||
.sf-theme-trade-pro .pro-end-cta .sf-tp-btn{background:#fff;color:var(--sf-tp-primary,#0b3d91);border-color:#fff}
|
||||
.sf-theme-trade-pro .pro-footer{background:#0d1b2e;color:#c7d3e2}
|
||||
.sf-theme-trade-pro .pro-footer h2{color:#fff}
|
||||
.sf-theme-trade-pro .pro-footer a{color:#fff}
|
||||
.sf-theme-trade-pro .pro-footer-bottom{border-top:1px solid #24344c}
|
||||
.sf-theme-trade-pro .pro-filter-panel{border:1px solid #dfe7f2;border-radius:var(--sf-tp-radius,8px)}
|
||||
.sf-theme-trade-pro .pro-404 strong{color:var(--sf-tp-primary,#0b3d91)}
|
||||
/* Phase 3: replace the tint layer above with the ported cms base.css and
|
||||
components (tokens prefixed --sf-tp-, all interactive classes sf-tp-*). */
|
||||
@@ -0,0 +1,20 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>{{ seo_title }}</title><meta name="description" content="{{ seo_description }}"><meta property="og:type" content="{{ 'article' if kind in ('article','case') else 'website' }}"><meta property="og:title" content="{{ seo_title }}"><meta property="og:description" content="{{ seo_description }}">{% if canonical %}<meta property="og:url" content="{{canonical}}">{% endif %}{# Skeleton: hard-coded theme color; switch back to the theme_color context variable once the platform map gains a trade-pro entry (Task 6). #}<meta name="theme-color" content="#0b3d91"><meta name="robots" content="{{ 'noindex,follow' if noindex else page.get('robots','index,follow') }}">{% if canonical and not noindex %}<link rel="canonical" href="{{ canonical }}">{% endif %}<link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css">{% if theme_overrides %}<link rel="stylesheet" href="assets/theme-overrides.css">{% endif %}<link rel="stylesheet" href="assets/custom.css"></head><body class="sf-theme-trade-pro pro-trade-pro"><a class="pro-skip" href="#main-content">Skip to content</a>
|
||||
{% from '_professional_components.html' import card,cover,grid,section_title,filters,footer_links,body_copy with context %}
|
||||
{# English fallback navigation (skeleton): the platform always fills nav with
|
||||
Chinese defaults (首页/产品/品牌/新闻/案例/关于/联系) unless the site configured
|
||||
its own navigation or published a site design. Detect "unconfigured" by
|
||||
comparing the nav href set against the platform default href set (identical
|
||||
for the make_context default and the bootstrap navigation), then render the
|
||||
English fallback list. Routes are limited to sections every build
|
||||
guarantees, so no fallback link can become a broken link. #}
|
||||
{% set tp_default_hrefs=['/','/about/','/brands/','/cases/','/contact/','/news/','/products/'] %}
|
||||
{% set tp_needs_en_nav=(not chrome_is_custom) and (nav|map(attribute='route')|list|sort==tp_default_hrefs) %}
|
||||
{% set tp_nav=[{'title':'Home','route':'/'},{'title':'Products','route':'/products/'},{'title':'News','route':'/news/'},{'title':'About','route':'/about/'},{'title':'Contact','route':'/contact/'}] if tp_needs_en_nav else nav %}
|
||||
{% if chrome_is_custom %}<div class="pro-utility"{% if not chrome.header.show_announcement %} hidden{% endif %}><div class="pro-wrap"><span>{{ chrome.header.announcement }}</span><span>{{ site.tagline or site.name }}</span></div></div>{% else %}<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL SOURCING · QUALITY · DELIVERY</span><span>INTERNATIONAL B2B SOLUTIONS</span></div></div>{% endif %}
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="{{site.name}} home">{% if safe_image(site.logo) %}<img class="pro-brand-logo" src="{{safe_image(site.logo)}}" alt="{{site.name}} Logo">{% else %}<span class="pro-logo-symbol">▣</span>{% endif %}<span class="pro-logo-name">{{site.name}}<small> GLOBAL B2B</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="Open navigation">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="Main navigation">{% for link in tp_nav %}{% if link.children %}<div class="pro-nav-group"><a href="{{link.route}}">{{link.title}}</a><button type="button" class="pro-nav-disclose" aria-expanded="false" aria-label="Expand {{link.title}} submenu">▾</button><div class="pro-nav-children" hidden>{% for child in link.children %}<a href="{{child.route}}">{{child.title}}</a>{% for leaf in child.children %}<a class="pro-nav-leaf" href="{{leaf.route}}">— {{leaf.title}}</a>{% endfor %}{% endfor %}</div></div>{% else %}<a href="{{link.route}}">{{link.title}}</a>{% endif %}{% endfor %}{% if chrome.header.show_search %}<a class="pro-search-link" href="/search/">Search ⌕</a>{% endif %}</nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">Products ▾</button>{% if chrome_is_custom %}{% if chrome.header.show_cta %}<a href="{{chrome.header.cta_href}}" class="sf-tp-btn pro-header-cta">{{chrome.header.cta_text}} <span>↗</span></a>{% endif %}{% else %}<a href="/contact/" class="sf-tp-btn pro-header-cta">Request a Quote <span>↗</span></a>{% endif %}</div>{% if categories %}<div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>Product categories</span><div>{% for cat in categories[:12] %}<a href="{{cat.url}}">{{cat.name}} ↗</a>{% endfor %}</div></div>{% endif %}</header>
|
||||
{% if not designer_replace %}{% include 'trade-pro/pages/' ~ kind ~ '.html' %}{% endif %}
|
||||
|
||||
{% if has_custom and kind!='notfound' %}<main id="main-content" class="sf-canvas pro-custom-content">{{custom_blocks}}</main>{% else %}<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>{% endif %}
|
||||
{% if kind not in ('notfound','search') %}<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>Ready to source with confidence?</h2><p>{{site.tagline or 'Send your specifications and get a quotation within one business day.'}}</p></div><a class="sf-tp-btn" href="/contact/">Contact us <span>↗</span></a></div></section>{% endif %}
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">TRADE PARTNERSHIP</span><h2>{{chrome.footer.title or site.name}}</h2><p>{{footer_text if (chrome_is_custom or footer_text) else 'Quality products, transparent specifications and dependable worldwide delivery.'}}</p></div><div class="pro-footer-links">{{footer_links(tp_nav if footer_nav else [])}}</div><div><span class="pro-eyebrow">BUSINESS ENQUIRY</span><p>{% if footer_email %}<a href="mailto:{{footer_email}}">{{footer_email}}</a>{% else %}Configure the business email in the admin console.{% endif %}</p></div></div><div class="pro-footer-bottom"><span>© {{year}} {{site.name}}</span><span>ENGINEERED FOR GLOBAL TRADE</span></div></div></footer>
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><base href="/"><title>{{seo_title}}</title><meta name="description" content="{{seo_description}}"><meta name="robots" content="{{'noindex,follow' if mode=='notfound' else (page.robots or 'index,follow')}}">{% if canonical %}<link rel="canonical" href="{{canonical}}">{% endif %}<link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/custom.css"></head><body>
|
||||
<div class="top"><div class="wrap"><span>ENGINEERING · MANUFACTURING · SUPPLY</span><span>GLOBAL INDUSTRIAL SOLUTIONS</span></div></div>
|
||||
<header><div class="wrap header-inner"><a class="brand" href="./"><span class="brand-icon">▰</span><span>{{site.name}}<small>INDUSTRIAL SOLUTIONS</small></span></a><nav><a href="./">Home</a>{% for n in nav if n.route!='/' %}<a href="{{link(n.route)}}">{{n.title}}</a>{% endfor %}{% if entries|selectattr('type','equalto','product')|list %}<a href="product/">Products</a>{% endif %}{% if entries|selectattr('type','equalto','post')|list %}<a href="post/">News</a>{% endif %}</nav><a class="request" href="mailto:{{site.email or "info@example.com"}}">REQUEST A QUOTE ↗</a></div></header>
|
||||
{% if blueprint_mode %}{% elif mode=='home' %}<section class="hero"><div class="wrap hero-grid"><div class="hero-text"><span class="kicker"><i></i> STRONGER CONNECTIONS. SMARTER SOLUTIONS.</span><h1>{{page.title if page.title!=site.name else 'BUILT TO LAST.'}}</h1><p>{{page.summary or site.tagline or 'Industrial excellence, quality products and dependable worldwide delivery.'}}</p><a class="button" href="product/">EXPLORE PRODUCTS <b>↗</b></a><div class="hero-tags"><span>01 / MATERIALS</span><span>02 / ENGINEERING</span><span>03 / SERVICE</span></div></div><div class="hero-art">{% if page.cover %}<img src="{{asset(page.cover)}}" alt="{{page.title}}">{% else %}<div class="ring ring-1"></div><div class="ring ring-2"></div><div class="ring ring-3"></div><span>INDUSTRY<br>IN MOTION</span>{% endif %}</div></div></section>
|
||||
{% elif mode=='notfound' %}<section class="subhero"><div class="wrap"><span class="kicker">ERROR</span><h1>404 — Page not found</h1></div></section>
|
||||
{% else %}<section class="subhero"><div class="wrap"><span class="kicker">{{type_names.get(page.type,'COMPANY')|upper}} / {{'PRODUCT CATALOG' if mode=='listing' else 'OUR EXPERTISE'}}</span><h1>{{page.title}}</h1><p>{{page.summary}}</p></div></section>{% endif %}
|
||||
{% if blueprint_mode %}<main class="sf-canvas">{{blocks}}</main>{% else %}<main class="wrap main">{% if mode=='home' %}<div class="metrics"><div><strong>01</strong><span>PRODUCT ENGINEERING</span></div><div><strong>02</strong><span>GLOBAL SOURCING</span></div><div><strong>03</strong><span>QUALITY ASSURANCE</span></div></div>{% endif %}
|
||||
{% if mode in ['home','detail'] and (content or blocks) %}<article class="rich-content">{{blocks}}{{content}}</article>{% endif %}
|
||||
{% if mode=='detail' and page.cover %}<figure class="detail-image"><img src="{{asset(page.cover)}}" alt="{{page.title}}"></figure>{% endif %}
|
||||
{% set cards=listing_items if mode=='listing' else entries|selectattr('type','equalto','product')|list if mode=='home' else [] %}
|
||||
{% if cards %}<div class="titlebar"><div><span class="kicker">OUR PORTFOLIO</span><h2>{{'Product Catalogue' if mode=='listing' else 'Explore Our Products'}}</h2></div>{% if mode=='home' %}<a href="product/">ALL PRODUCTS ↗</a>{% endif %}</div><div class="product-grid">{% for item in cards[:12] %}<a href="{{link(item.route)}}" class="product"><div class="product-img">{% if item.cover %}<img src="{{asset(item.cover)}}" alt="{{item.title}}">{% else %}<div class="technical-graphic"><span>+</span><span>+</span><span>+</span><span>+</span></div>{% endif %}</div><div class="product-info"><span>INDUSTRIAL / {{type_names.get(item.type,'PRODUCT')|upper}}</span><h3>{{item.title}}</h3><p>{{item.summary|truncate(100)}}</p><div class="arrow">DISCOVER DETAILS <b>→</b></div></div></a>{% endfor %}</div>{% endif %}</main>
|
||||
{% endif %}<footer><div class="wrap"><div class="footer-cols"><div><h2>{{site.name}}</h2><p>{{site.tagline}}</p></div><div><span>INDUSTRIAL SOLUTIONS</span><p>Quality materials. Dependable service.<br>Worldwide supply.</p></div></div><div class="bottom"><span>© {{site.name}}</span><span>DESIGNED TO DELIVER. BUILT WITH SITEFORGE.</span></div></div></footer></body></html>
|
||||
@@ -0,0 +1,15 @@
|
||||
/* trade-pro design tokens (skeleton; full cms port in Phase 3) */
|
||||
:root{--sf-tp-primary:#0b3d91;--sf-tp-accent:#f5a623;--sf-tp-bg:#ffffff;--sf-tp-text:#1a2332;--sf-tp-muted:#5a6a7e;--sf-tp-radius:8px;--sf-tp-font:'Inter',system-ui,-apple-system,sans-serif}
|
||||
body{margin:0;font-family:var(--sf-tp-font);color:var(--sf-tp-text);background:var(--sf-tp-bg)}
|
||||
/* trade-pro buttons use the sf-tp- prefix so the platform custom.css rules
|
||||
(.btn/.primary/.request/.button/.nav-btn) can never restyle them. */
|
||||
.sf-tp-btn{display:inline-block;padding:.7rem 1.4rem;border-radius:var(--sf-tp-radius);background:var(--sf-tp-primary);color:#fff;text-decoration:none;font-weight:600;border:1px solid var(--sf-tp-primary)}
|
||||
.sf-tp-btn:hover{background:#0a357d}
|
||||
.sf-tp-btn:focus-visible{outline:2px solid var(--sf-tp-accent);outline-offset:2px}
|
||||
.sf-tp-btn-outline{background:transparent;color:var(--sf-tp-primary)}
|
||||
.sf-tp-btn-outline:hover{background:#eef4fc;color:#0a357d}
|
||||
/* Minimal skeleton accents so the page is not unstyled before the Phase 3 port. */
|
||||
.pro-utility{background:var(--sf-tp-primary);color:#fff}
|
||||
.pro-eyebrow{color:var(--sf-tp-primary);letter-spacing:.14em}
|
||||
.pro-home-hero em{color:var(--sf-tp-primary)}
|
||||
a{color:var(--sf-tp-primary)}
|
||||
Reference in New Issue
Block a user