feat: trust_bar + applications_band blocks; homepage now block-driven
- blueprints: register trust_bar (icon/title/text items) and applications_band (2x2 dark-band tiles) block types; render with escaped items, ordinal icon fallback, 8-item cap; auto-exposed in editor via /api/editor/blocks and grouped under business in the studio palette - blocks.css: platform base layer for both blocks (app/static + trade-pro Layer A kept verbatim in sync) + trade-pro bridge vocabulary (icon-circle, hairline dividers, navy->brand gradient band, glass tiles, 900/620 breakpoints, reduced-motion) without touching canvas hooks - tests: test_trade_pro_home_block_driven covers the full studio-draft PUT -> publish -> build chain; 8 block types on the homepage, template markers absent under layout_mode=replace, clean build report - scripts/setup_home_blocks.py: production write path for site 483295c29ea0 (login -> draft -> publish -> build -> verify); real catalogue counts feed the stats block; run after the Docker image picks up the new block types Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,8 @@ BLOCKS = {
|
||||
'pricing':{'name':'服务方案','icon':'$','fields':['title','items'],'defaults':{'title':'服务方案','items':[{'name':'标准方案','price':'联系我们','features':['专业咨询','售后服务'],'href':'/contact/'}]}},
|
||||
'map':{'name':'位置导航','icon':'⌖','fields':['title','address','url'],'defaults':{'title':'欢迎来访','address':'请填写实际办公地址','url':''}},
|
||||
'form_link':{'name':'询盘表单入口','icon':'✉','fields':['title','description','button','href'],'defaults':{'title':'提交询盘','description':'欢迎联系我们获取报价和资料','button':'联系销售','href':'/contact/'}},
|
||||
'trust_bar':{'name':'信任条','icon':'✓','fields':['title','items'],'defaults':{'title':'','items':[{'icon':'✓','title':'Certified quality','text':'Inspected before every shipment'},{'icon':'◎','title':'Global export','text':'Delivering to worldwide markets'},{'icon':'▤','title':'Full documentation','text':'Specs and export files on hand'},{'icon':'✉','title':'Responsive service','text':'Replies within one business day'}]}},
|
||||
'applications_band':{'name':'应用展示带','icon':'⊞','fields':['title','subtitle','items'],'defaults':{'title':'Built for real-world projects','subtitle':'APPLICATIONS','items':[{'icon':'◉','title':'Water supply','text':'Municipal piping and treatment networks.'},{'icon':'◍','title':'Drainage','text':'Gravity and pressure sewer systems.'},{'icon':'✦','title':'Gas','text':'PE pipelines for fuel gas distribution.'},{'icon':'◆','title':'Mining','text':'Abrasion-resistant slurry and vent lines.'}]}},
|
||||
}
|
||||
# Property contracts shared with the no-code editor. Unknown executable types
|
||||
# are not supported; all values are still verified by validate_blocks.
|
||||
@@ -308,6 +310,17 @@ def render_blocks(blocks, site, page, entries):
|
||||
visible_items=sum(1 for item in items[:12] if isinstance(item,dict))
|
||||
feature_cols=visible_items if 1<=visible_items<=4 else 3
|
||||
parts.append(start+f'<div class="sf-section-head"><h2>{title}</h2></div><div class="sf-features" style="--sf-feature-cols:{feature_cols}">{cards}</div>'+end)
|
||||
elif typ=='trust_bar':
|
||||
# Horizontal trust strip; item icons are free-text glyphs, empty ones
|
||||
# fall back to ordinal markers like the features block.
|
||||
items=block.get('items') if isinstance(block.get('items'),list) else []
|
||||
cells=''.join(f'<div class="sf-trust-item"><span class="sf-trust-icon" aria-hidden="true">{escape(str(item.get("icon") or (i+1)))}</span><strong>{escape(str(item.get("title","")))}</strong><span class="sf-trust-text">{escape(str(item.get("text","")))}</span></div>' for i,item in enumerate(items[:8]) if isinstance(item,dict))
|
||||
head=f'<div class="sf-section-head"><h2>{title}</h2></div>' if title else ''
|
||||
parts.append(start+head+f'<div class="sf-trust-bar">{cells}</div>'+end)
|
||||
elif typ=='applications_band':
|
||||
items=block.get('items') if isinstance(block.get('items'),list) else []
|
||||
tiles=''.join(f'<div class="sf-app-tile"><span class="sf-app-icon" aria-hidden="true">{escape(str(item.get("icon") or (i+1)))}</span><h3>{escape(str(item.get("title","")))}</h3><p>{escape(str(item.get("text","")))}</p></div>' for i,item in enumerate(items[:8]) if isinstance(item,dict))
|
||||
parts.append(start+f'<div class="sf-section-head"><div><span class="sf-kicker">{subtitle}</span><h2>{title}</h2></div></div><div class="sf-applications-band">{tiles}</div>'+end)
|
||||
elif typ=='stats':
|
||||
items=block.get('items') if isinstance(block.get('items'),list) else []
|
||||
cards=''.join(f'<div><strong>{escape(str(item.get("value","")))}</strong><span>{escape(str(item.get("label","")))}</span></div>' for item in items[:12] if isinstance(item,dict))
|
||||
|
||||
@@ -69,3 +69,23 @@
|
||||
@media(prefers-reduced-motion:reduce){.sf-hero-track{scroll-behavior:auto}}
|
||||
|
||||
.sf-hero-panel .sf-hero-copy h2{font-size:clamp(38px,5vw,76px);font-weight:790;letter-spacing:-.052em}
|
||||
|
||||
/* Trust bar: horizontal credibility strip under the hero (icon + title + note). */
|
||||
.sf-section.sf-trust_bar{padding:38px 0}
|
||||
.sf-trust-bar{display:grid;grid-template-columns:repeat(var(--sf-trust-cols,4),minmax(0,1fr))}
|
||||
.sf-trust-item{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;padding:10px 20px;border-inline-end:1px solid #dce3eb}
|
||||
.sf-trust-item:last-child{border-inline-end:0}
|
||||
.sf-trust-icon{display:grid;place-items:center;width:46px;height:46px;border-radius:50%;background:#eef2f8;color:var(--accent,#315ee2);font-size:18px;font-weight:800;margin-bottom:6px}
|
||||
.sf-trust-item strong{font-size:15px}
|
||||
.sf-trust-text{font-size:13px;opacity:.68;line-height:1.55}
|
||||
@media(max-width:820px){.sf-trust-bar{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:24px}.sf-trust-item:nth-child(2n){border-inline-end:0}}
|
||||
@media(max-width:540px){.sf-trust-bar{grid-template-columns:minmax(0,1fr)}.sf-trust-item{border-inline-end:0}}
|
||||
|
||||
/* Applications band: dark gradient strip with a 2x2 tile grid. */
|
||||
.sf-section.sf-applications_band{background:linear-gradient(115deg,#101828,#1d3a6e 78%);color:#f5f7fa}
|
||||
.sf-applications-band{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}
|
||||
.sf-app-tile{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.14);border-radius:10px;padding:26px 24px}
|
||||
.sf-app-icon{display:grid;place-items:center;width:44px;height:44px;border-radius:50%;background:rgba(255,255,255,.12);font-size:17px;font-weight:800;margin-bottom:12px}
|
||||
.sf-app-tile h3{font-size:20px;margin:0 0 6px}
|
||||
.sf-app-tile p{font-size:14px;line-height:1.65;opacity:.75;margin:0}
|
||||
@media(max-width:540px){.sf-applications-band{grid-template-columns:minmax(0,1fr)}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
const pages=new Set(['home','listing','product','article','about','contact']);
|
||||
const categories={
|
||||
visual:new Set(['hero','banner','carousel','gallery','image','image_text','image-grid','video','slider']),
|
||||
business:new Set(['collection','products','product_list','brand_wall','brands','features','stats','testimonials','faq','contact','form','cta','map']),
|
||||
business:new Set(['collection','products','product_list','brand_wall','brands','features','stats','testimonials','faq','contact','form','cta','map','trust_bar','applications_band']),
|
||||
advanced:new Set(['layout','columns','tabs','accordion','html-embed','embed','spacer','divider'])
|
||||
};
|
||||
function categoryOf(key){for(const [name,ids] of Object.entries(categories))if(ids.has(key))return name;return 'basic'}
|
||||
|
||||
Reference in New Issue
Block a user