312 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 小红书风格小组件库 v1.0
// 每个widget = 核心HTML模板 + 3~5个随机变体(variant) + CSS变量可覆写
// 国作登字-2026-A-00037559
// 设计原则: 每个widget是独立片段的HTML可随机组合不打架
export const WIDGETS = {
// ═══════════════════════════════════════
// 装饰类 Widgets
// ═══════════════════════════════════════
'dot-grid': {
name: '圆点矩阵',
category: 'decor',
variants: [
{ id: 'warm', css: { size: '6px', color: '#E07B39', opacity: '0.25', gap: '18px', cols: 8, rows: 3 } },
{ id: 'cool', css: { size: '4px', color: '#4A90A4', opacity: '0.2', gap: '22px', cols: 10, rows: 2 } },
{ id: 'mixed', css: { size: '5px', colors: ['#E07B39','#4A90A4','#5B8C5A'], opacity: '0.3', gap: '16px', cols: 6, rows: 4 } },
{ id: 'large', css: { size: '8px', color: '#2D5016', opacity: '0.12', gap: '24px', cols: 5, rows: 2 } },
],
render(v) {
const c = v.css, colors = c.colors || [c.color]
let dots = ''
const total = (c.cols || 6) * (c.rows || 3)
for (let i = 0; i < total; i++) {
const clr = colors[i % colors.length]
const r = Math.floor(i / c.cols), col = i % c.cols
const ox = (Math.random() - 0.5) * 4, oy = (Math.random() - 0.5) * 4
dots += `<circle cx="${col * parseInt(c.gap) + 20 + ox}" cy="${r * parseInt(c.gap) + 14 + oy}" r="${c.size}" fill="${clr}" opacity="${c.opacity}"/>`
}
return `<svg width="${c.cols * parseInt(c.gap) + 16}" height="${c.rows * parseInt(c.gap) + 16}" style="display:block">${dots}</svg>`
}
},
'gradient-stripe': {
name: '渐变装饰条',
category: 'decor',
variants: [
{ id: 'warm-sunset', css: { colors: ['#E07B39','#F5A623','#FFD700'], height: '6px', width: '80%', radius: '3px' } },
{ id: 'cool-ocean', css: { colors: ['#4A90A4','#5B8C5A','#7BC47B'], height: '4px', width: '60%', radius: '2px' } },
{ id: 'rose-gold', css: { colors: ['#E8435E','#C9A96E','#F5D0C5'], height: '5px', width: '70%', radius: '3px' } },
{ id: 'forest', css: { colors: ['#2D5016','#5B8C5A','#A8D5A2'], height: '8px', width: '50%', radius: '4px' } },
],
render(v) {
const c = v.css
const grad = c.colors.map((clr, i) => `${clr} ${(i / (c.colors.length - 1)) * 100}%`).join(', ')
return `<div style="width:${c.width};height:${c.height};border-radius:${c.radius};background:linear-gradient(90deg,${grad});margin:12px auto"></div>`
}
},
'sticker-badge': {
name: '贴纸角标',
category: 'decor',
variants: [
{ id: 'hot', text: 'HOT', bg: '#E8435E', color: '#FFF', rotate: '-8deg' },
{ id: 'new', text: 'NEW', bg: '#E07B39', color: '#FFF', rotate: '5deg' },
{ id: 'free', text: 'FREE', bg: '#5B8C5A', color: '#FFF', rotate: '-3deg' },
{ id: 'top', text: 'TOP', bg: '#4A90A4', color: '#FFF', rotate: '10deg' },
{ id: 'hand', text: '手写', bg: '#F5E6C8', color: '#8B4513', rotate: '-12deg' },
],
render(v) {
return `<div style="display:inline-block;background:${v.bg};color:${v.color};padding:6px 16px;border-radius:6px;font-size:18px;font-weight:800;transform:rotate(${v.rotate});box-shadow:2px 2px 0 rgba(0,0,0,.1);letter-spacing:2px">${v.text}</div>`
}
},
'hand-drawn-circle': {
name: '手绘圈线',
category: 'decor',
variants: [
{ id: 'orange', stroke: '#E07B39', width: '3px', dash: 'none', radius: '60px' },
{ id: 'green', stroke: '#5B8C5A', width: '2px', dash: '8,4', radius: '50px' },
{ id: 'blue', stroke: '#4A90A4', width: '2.5px', dash: '12,6', radius: '45px' },
{ id: 'pink', stroke: '#E8435E', width: '3px', dash: '6,3', radius: '55px' },
],
render(v) {
const d = parseInt(v.radius) * 2
return `<svg width="${d + 10}" height="${d + 10}" style="display:block"><circle cx="${d/2 + 5}" cy="${d/2 + 5}" r="${v.radius}" fill="none" stroke="${v.stroke}" stroke-width="${v.width}" ${v.dash !== 'none' ? `stroke-dasharray="${v.dash}"` : ''} opacity="0.6"/></svg>`
}
},
// ═══════════════════════════════════════
// 信息类 Widgets
// ═══════════════════════════════════════
'photo-frame': {
name: '照片框',
category: 'info',
description: '带白边/阴影的照片展示框,支持随机占位色块',
variants: [
{ id: 'polaroid', style: 'polaroid', borderColor: '#FFF', borderWidth: '12px 12px 40px 12px', rotate: '-2deg' },
{ id: 'rounded', style: 'rounded', borderColor: '#FFF', borderRadius: '16px', shadow: true },
{ id: 'film', style: 'film', borderColor: '#1A1A1A', borderWidth: '6px 6px 24px 6px', rotate: '1deg' },
{ id: 'clean', style: 'clean', borderColor: 'transparent', borderRadius: '20px' },
],
render(v) {
const w = v.width || 280, h = v.height || 210
const placeholderColors = ['#F5D0C5','#A8D5A2','#B5D4F4','#F5E6C8','#F4C0D1']
const bg = placeholderColors[Math.floor(Math.random() * placeholderColors.length)]
let style = `width:${w}px;height:${h}px;background:${bg}`
if (v.style === 'polaroid') style += `;border:${v.borderWidth} solid ${v.borderColor};transform:rotate(${v.rotate})`
else if (v.style === 'film') style += `;border:${v.borderWidth} solid ${v.borderColor};transform:rotate(${v.rotate})`
else if (v.style === 'rounded') style += `;border:4px solid ${v.borderColor};border-radius:${v.borderRadius}`
else style += `;border-radius:${v.borderRadius};overflow:hidden`
if (v.shadow) style += ';box-shadow:0 8px 30px rgba(0,0,0,.1)'
if (v.caption) {
return `<div style="display:inline-block"><div style="${style};position:relative">${v.emoji ? `<span style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:48px">${v.emoji}</span>` : ''}</div><div style="text-align:center;font-size:14px;color:#8B8680;margin-top:8px">${v.caption}</div></div>`
}
return `<div style="${style};position:relative">${v.emoji ? `<span style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:48px">${v.emoji}</span>` : ''}</div>`
}
},
'info-tag-row': {
name: '信息标签行',
category: 'info',
description: '一排小红书风格的信息标签:价格/难度/时长/评分等',
variants: [
{ id: 'price', items: [{ icon: '💰', text: '均价', value: '¥28' }, { icon: '⭐', text: '评分', value: '4.8' }, { icon: '⏱', text: '耗时', value: '30min' }] },
{ id: 'recipe', items: [{ icon: '👨‍🍳', text: '难度', value: '简单' }, { icon: '⏱', text: '时间', value: '20min' }, { icon: '🍽', text: '份量', value: '2人' }] },
{ id: 'travel', items: [{ icon: '📍', text: '目的地', value: '杭州' }, { icon: '💰', text: '预算', value: '¥500' }, { icon: '📅', text: '天数', value: '3天' }] },
{ id: 'study', items: [{ icon: '📚', text: '方法', value: '番茄钟' }, { icon: '⏱', text: '时长', value: '25min' }, { icon: '📈', text: '效果', value: '显著' }] },
],
render(v) {
const items = (v.items || v.variant?.items || []).map(i =>
`<div style="text-align:center;padding:10px 18px;background:#FDF8F3;border-radius:16px">
<div style="font-size:14px;color:#8B8680;margin-bottom:2px">${i.icon} ${i.text}</div>
<div style="font-size:22px;font-weight:800;color:#2D5016">${i.value}</div>
</div>`
).join('')
return `<div style="display:flex;gap:12px;justify-content:center">${items}</div>`
}
},
'bullet-list': {
name: '要点清单',
category: 'info',
description: '小红书风格的bullet point列表支持emoji前缀',
variants: [
{ id: 'check', bullet: '✅', color: '#5B8C5A', items: ['要点一','要点二','要点三'] },
{ id: 'star', bullet: '✦', color: '#E07B39', items: ['关键点A','关键点B','关键点C'] },
{ id: 'number', bullet: 'N', color: '#4A90A4', numbered: true, items: ['第一步','第二步','第三步'] },
{ id: 'heart', bullet: '♡', color: '#E8435E', items: ['喜欢的原因','喜欢的原因','喜欢的原因'] },
],
render(v) {
const items = (v.items || v.variant?.items || []).map((item, i) => {
const prefix = v.numbered ? `${String(i + 1).padStart(2, '0')}.` : v.bullet
return `<div style="display:flex;align-items:flex-start;gap:10px;margin-bottom:10px">
<span style="color:${v.color};font-weight:700;font-size:18px;flex-shrink:0;min-width:28px">${prefix}</span>
<span style="font-size:18px;color:#1A1A1A;line-height:1.6">${item}</span>
</div>`
}).join('')
return `<div style="padding:8px 0">${items}</div>`
}
},
// ═══════════════════════════════════════
// 排版类 Widgets
// ═══════════════════════════════════════
'split-layout': {
name: '左右分栏',
category: 'layout',
description: '左图右文或左文右图分栏,小红书经典排版',
variants: [
{ id: 'image-left', direction: 'row', ratio: '45:55', gap: '16px' },
{ id: 'image-right', direction: 'row-reverse', ratio: '45:55', gap: '16px' },
{ id: 'equal', direction: 'row', ratio: '50:50', gap: '20px' },
{ id: 'stack-top', direction: 'column', ratio: 'auto' },
],
render(v) {
const [l, r] = (v.ratio || '50:50').split(':').map(Number)
const dir = v.direction === 'column' ? 'column' : 'row'
return {
wrapper: true,
style: `display:flex;flex-direction:${dir};gap:${v.gap || '16px'};align-items:center`,
leftStyle: dir === 'column' ? 'width:100%' : `flex:0 0 ${l}%`,
rightStyle: dir === 'column' ? 'width:100%' : `flex:1`,
}
}
},
'card-grid': {
name: '卡片网格',
category: 'layout',
description: '小红书同款2x2或3列卡片网格',
variants: [
{ id: '2x2', cols: 2, gap: '12px', cardHeight: '100px' },
{ id: '1x3', cols: 3, gap: '10px', cardHeight: '140px' },
{ id: '1x2-wide', cols: 2, gap: '16px', cardHeight: '180px' },
],
render(v) {
return {
wrapper: true,
style: `display:grid;grid-template-columns:repeat(${v.cols},1fr);gap:${v.gap}`,
cardStyle: `height:${v.cardHeight};background:#FDF8F3;border-radius:16px;display:flex;align-items:center;justify-content:center`,
}
}
},
// ═══════════════════════════════════════
// 情感类 Widgets
// ═══════════════════════════════════════
'sticky-note': {
name: '便签纸',
category: 'emotion',
variants: [
{ id: 'yellow', bg: '#FFF9C4', shadow: '#E6D88A', rotate: '-3deg', pin: '#F5A623' },
{ id: 'pink', bg: '#FCE4EC', shadow: '#F0D0D8', rotate: '2deg', pin: '#E8435E' },
{ id: 'green', bg: '#E8F5E9', shadow: '#C8E6C9', rotate: '-1deg', pin: '#5B8C5A' },
{ id: 'blue', bg: '#E3F2FD', shadow: '#BBDEFB', rotate: '4deg', pin: '#4A90A4' },
],
render(v) {
return `<div style="position:relative;display:inline-block">
<div style="position:absolute;top:-8px;left:50%;transform:translateX(-50%);width:12px;height:12px;border-radius:50%;background:${v.pin};box-shadow:0 2px 4px rgba(0,0,0,.2);z-index:1"></div>
<div style="background:${v.bg};padding:20px 24px;border-radius:4px;transform:rotate(${v.rotate});box-shadow:2px 3px 6px ${v.shadow};max-width:200px;line-height:1.7">
<span style="font-size:16px;color:#4A3728;line-height:1.8">${v.text || '便签内容'}</span>
</div>
</div>`
}
},
'highlight-box': {
name: '高亮强调框',
category: 'emotion',
description: '小红书金句高亮框,荧光笔标注风格',
variants: [
{ id: 'yellow-marker', bg: '#FFF3CD', border: '#FFE69C', text: '#856404', icon: '💡' },
{ id: 'pink-marker', bg: '#FCE4EC', border: '#F48FB1', text: '#880E4F', icon: '❤️' },
{ id: 'green-marker', bg: '#E8F5E9', border: '#A5D6A7', text: '#1B5E20', icon: '🌟' },
{ id: 'blue-marker', bg: '#E3F2FD', border: '#90CAF9', text: '#0D47A1', icon: '📌' },
],
render(v) {
return `<div style="background:${v.bg};border-left:4px solid ${v.border};border-radius:4px 12px 12px 4px;padding:14px 20px;margin:8px 0">
<span style="font-size:15px;color:${v.text};line-height:1.7">${v.icon ? v.icon + ' ' : ''}${v.text || '金句内容'}</span>
</div>`
}
},
'mood-indicator': {
name: '心情标识',
category: 'emotion',
variants: [
{ id: 'happy', mood: '😊 开心', bg: '#FFF9C4', color: '#F5A623' },
{ id: 'calm', mood: '😌 平静', bg: '#E8F5E9', color: '#5B8C5A' },
{ id: 'excited', mood: '🤩 激动', bg: '#FCE4EC', color: '#E8435E' },
{ id: 'focused', mood: '🧘 专注', bg: '#E3F2FD', color: '#4A90A4' },
],
render(v) {
return `<div style="display:inline-flex;align-items:center;gap:8px;padding:8px 16px;background:${v.bg};border-radius:20px">
<span style="font-size:16px;color:${v.color};font-weight:700">${v.mood}</span>
</div>`
}
},
// ═══════════════════════════════════════
// 特殊效果 Widgets
// ═══════════════════════════════════════
'scribble-underline': {
name: '手写划线下划线',
category: 'decor',
variants: [
{ id: 'orange-wavy', color: '#E07B39', style: 'wavy', offset: '4px', width: '3px' },
{ id: 'green-straight', color: '#5B8C5A', style: 'solid', offset: '2px', width: '4px' },
{ id: 'pink-dashed', color: '#E8435E', style: 'dashed', offset: '6px', width: '2px' },
],
render(v) {
return {
cssClass: `text-decoration:underline;text-decoration-color:${v.color};text-decoration-thickness:${v.width};text-underline-offset:${v.offset};${v.style !== 'solid' ? `text-decoration-style:${v.style}` : ''}`,
inline: true,
}
}
},
'corner-fold': {
name: '折角效果',
category: 'decor',
variants: [
{ id: 'top-right', corner: 'top-right', color: '#FDF8F3', size: '30px' },
{ id: 'top-left', corner: 'top-left', color: '#E8F0E0', size: '24px' },
],
render(v) {
return `<div style="position:absolute;${v.corner === 'top-right' ? 'top:0;right:0' : 'top:0;left:0'};width:0;height:0;border-style:solid;border-width:${v.size};border-color:${v.color} ${v.color} transparent transparent;z-index:2"></div>`
}
},
}
// 随机选择一个widget的变体
export function randomVariant(widgetId) {
const w = WIDGETS[widgetId]
if (!w || !w.variants) return null
const idx = Math.floor(Math.random() * w.variants.length)
return w.variants[idx]
}
// 列举所有widget
export function listWidgets() {
return Object.entries(WIDGETS).map(([id, w]) => ({
id,
name: w.name,
category: w.category,
description: w.description || '',
variantCount: (w.variants || []).length,
}))
}
// 按类别筛选
export function widgetsByCategory(category) {
return listWidgets().filter(w => w.category === category)
}