diff --git a/components/registry.js b/components/registry.js new file mode 100644 index 0000000..97b3436 --- /dev/null +++ b/components/registry.js @@ -0,0 +1,14 @@ +// 封面组件注册表 v1.0 · 外部AI的菜单 +// 读 HLDP-SPEC-v2.0.md → 读本文件 → 选组件 → 组合 → POST /api/compose +// 国作登字-2026-A-00037559 + +export const COMPONENTS = [ + { id:"canvas", name:"画布", desc:"底板,宽高底色", params:{ width:{type:"number",default:1080}, height:{type:"number",default:1440}, bg:{type:"color",default:"#FDF8F3"} } }, + { id:"card", name:"卡片容器", desc:"白色圆角大卡片", params:{ radius:{type:"number",default:60}, padding:{type:"string",default:"60px 70px 50px"}, shadow:{type:"boolean",default:true} } }, + { id:"decor", name:"装饰圆", desc:"半透明渐变圆点缀角落", params:{ side:{type:"enum",values:["top-right","bottom-left","both"],default:"both"}, color:{type:"enum",values:["orange","blue"],default:"orange"} } }, + { id:"badge", name:"标签徽章", desc:"顶行小标签", params:{ text:{type:"string",required:true}, style:{type:"enum",values:["accent","normal"],default:"accent"} } }, + { id:"title", name:"大标题", desc:"多行多色标题", params:{ lines:{type:"array",required:true,desc:"每行文字"}, colors:{type:"array",desc:"dark/green/orange"}, size:{type:"number",default:68} } }, + { id:"pills", name:"标签组", desc:"一排药丸标签", params:{ items:{type:"array",required:true}, zeros:{type:"array",desc:"强调项索引"} } }, + { id:"toolCards", name:"工具卡片组", desc:"一排工具卡(图标+名+描述+价格)", params:{ items:{type:"array",required:true,desc:"[[emoji,name,desc,price],...]"} } }, + { id:"bigText", name:"大字强调区", desc:"底部渐变强调区", params:{ main:{type:"string",required:true}, sub:{type:"string"} } } +] diff --git a/components/render.js b/components/render.js new file mode 100644 index 0000000..7ccdb65 --- /dev/null +++ b/components/render.js @@ -0,0 +1,33 @@ +const FONT = "'Noto Sans CJK SC','WenQuanYi Micro Hei',sans-serif" +export function renderComponents(config) { + const { canvas:cv={}, stack=[] } = config + const W=cv.width||1080, H=cv.height||1440, bg=cv.bg||'#FDF8F3' + let css=`*{margin:0;padding:0;box-sizing:border-box}body{width:${W}px;height:${H}px;overflow:hidden;font-family:${FONT};background:${bg};display:flex;justify-content:center;align-items:center}` + css+=`.card{border-radius:60px;box-shadow:0 20px 80px rgba(0,0,0,.06);background:#fff;display:flex;flex-direction:column;width:960px;height:1320px;padding:60px 70px 50px;position:relative}` + css+=`.deco{position:absolute;border-radius:50%}.deco-tr{top:-100px;right:-80px;width:320px;height:320px;background:radial-gradient(circle,rgba(224,123,57,.08),transparent 70%)}.deco-bl{bottom:200px;left:-60px;width:180px;height:180px;background:radial-gradient(circle,rgba(74,144,164,.06),transparent 70%)}` + css+=`.badge{display:inline-flex;padding:10px 24px;border-radius:24px;font-size:20px;font-weight:800}.badge-a{background:rgba(224,123,57,.12);color:#2D5016;border:2px solid rgba(224,123,57,.25)}.badge-n{background:#FDF8F3;color:#2D5016;border:2px solid rgba(224,123,57,.12)}` + css+=`.th1{font-size:68px;font-weight:900;line-height:1.15;letter-spacing:2px;margin-bottom:16px}.tl{display:block}.td{color:#1A1A1A}.tg{color:#2D5016}.to{color:#E07B39}` + css+=`.pw{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:40px}.pi{display:inline-block;padding:8px 20px;border-radius:24px;font-size:20px;font-weight:700}.pz{background:#2D5016;color:#fff;font-size:24px}.pn{background:rgba(224,123,57,.08);color:#E07B39}` + css+=`.tg{display:flex;gap:16px;margin-bottom:24px}.tc{flex:1;background:#FDF8F3;border-radius:20px;padding:24px 16px;text-align:center}.tci{font-size:36px;margin-bottom:10px}.tcn{font-size:18px;font-weight:700;color:#1A1A1A;margin-bottom:4px}.tcd{font-size:14px;color:#8B8680;margin-top:4px}.tcp{font-size:22px;font-weight:900;color:#E07B39;margin-top:8px}` + css+=`.bs{text-align:center;padding:28px;background:linear-gradient(135deg,rgba(224,123,57,.06),rgba(45,80,22,.06));border-radius:24px;margin-top:auto}.bm{font-size:56px;font-weight:900;color:#E07B39;line-height:1}.bsb{font-size:18px;color:#8B8680;margin-top:8px}` + css+=`.tr{display:flex;gap:12px;margin-bottom:30px;align-items:center}` + + let body='',inCard=false + for(const c of stack){ + const p=c.params||{} + switch(c.component){ + case'card':body+='
';inCard=true;break + case'decor': + if(p.side==='top-right'||p.side==='both')body+='
' + if(p.side==='bottom-left'||p.side==='both')body+='
';break + case'badge':body+=`${p.text||''}`;break + case'topRow':body+='
';break + case'title':{const l=p.lines||[],co=p.colors||l.map(()=>'d');body+='
';l.forEach((x,i)=>body+=`${x}`);body+='
';break} + case'pills':{const it=p.items||[],zz=p.zeros||[];body+='
';it.forEach((t,i)=>body+=`${t}`);body+='
';break} + case'toolCards':{const tc=p.items||[];body+='
';tc.forEach(t=>body+=`
${t[0]||''}
${t[1]||''}
${t[2]||''}
${t[3]||''}
`);body+='
';break} + case'bigText':body+=`
${p.main||''}
`;if(p.sub)body+=`
${p.sub}
`;body+='
';break + } + } + if(inCard)body+='
' + return`${body}` +}