Spaces:
Running
Running
Download aries/src/badge.js from Ethscriptions/POLS: direct link, hf CLI and curl.
- Browser
- Download file 9.96 kB
-
https://huggingface.co/spaces/Ethscriptions/POLS/resolve/main/aries/src/badge.js
- Command line
-
hf download hf://spaces/Ethscriptions/POLS/aries/src/badge.js
-
curl -L -o badge.js https://huggingface.co/spaces/Ethscriptions/POLS/resolve/main/aries/src/badge.js
9.96 kB
| // ♈ 白羊座 · 先锋荣耀铭牌(视频第 9 幕 + 单独导出卡片共用) | |
| import { PI2, clamp01, mix, span, E, hash, rgba, GOLD, SERIF, SANS, DISPLAY, bloom, star, gold, say, trace, ramHead, laurel, ariesGlyph, marsGlyph, rr } from './kit.js'; | |
| function plateShape() { | |
| const p = new Path2D(); | |
| p.moveTo(-300, -380); p.bezierCurveTo(-150, -440, 150, -440, 300, -380); | |
| p.lineTo(370, -310); p.lineTo(370, 420); p.lineTo(300, 500); p.lineTo(-300, 500); p.lineTo(-370, 420); p.lineTo(-370, -310); p.closePath(); | |
| return p; | |
| } | |
| function innerShape() { | |
| const p = new Path2D(); | |
| p.moveTo(-282, -352); p.bezierCurveTo(-140, -405, 140, -405, 282, -352); | |
| p.lineTo(342, -296); p.lineTo(342, 408); p.lineTo(286, 472); p.lineTo(-286, 472); p.lineTo(-342, 408); p.lineTo(-342, -296); p.closePath(); | |
| return p; | |
| } | |
| const STARS = [[-150, 60, 2.4], [-40, 10, 3.6], [60, 30, 3], [110, 70, 2.2], [210, -10, 2.6]]; | |
| /** | |
| * o: { t, forge(0-1), engrave(0-1), ribbon(0-1), name(0-1), shine(0-1 扫光进度), holder, birthday } | |
| */ | |
| export function drawBadge(ctx, cx, cy, size, o = {}) { | |
| const s = size / 1000, t = o.t ?? 0; | |
| const forge = o.forge ?? 1; if (forge <= 0) return; | |
| const cool = span(forge, .45, 1); // 熔融 → 冷却成金 | |
| const body = span(forge, .25, .8); | |
| ctx.save(); ctx.translate(cx, cy); ctx.scale(s, s); | |
| const plate = plateShape(), inner = innerShape(); | |
| // 背后光晕 | |
| ctx.save(); ctx.scale(1 / s, 1 / s); bloom(ctx, 0, 0, size * .9, [255, 150, 70], .55 * body); ctx.restore(); | |
| // —— 板体 —— | |
| if (body > 0) { | |
| ctx.save(); ctx.globalAlpha *= body; | |
| ctx.shadowColor = 'rgba(0,0,0,.6)'; ctx.shadowBlur = 40 * s; ctx.shadowOffsetY = 18 * s; | |
| ctx.fillStyle = cool < 1 ? gold(ctx, -370, -440, 370, 500, 1) : gold(ctx, -370, -440, 370, 500); | |
| ctx.fill(plate); ctx.shadowColor = 'transparent'; | |
| if (cool > 0 && cool < 1) { ctx.globalAlpha = body * cool; ctx.fillStyle = gold(ctx, -370, -440, 370, 500); ctx.fill(plate); ctx.globalAlpha = body; } | |
| // 内层:深红珐琅 | |
| const en = ctx.createLinearGradient(0, -400, 0, 480); en.addColorStop(0, '#5a0d0d'); en.addColorStop(.5, '#2a0507'); en.addColorStop(1, '#420909'); | |
| ctx.fillStyle = en; ctx.fill(inner); | |
| // 珐琅纹理:放射细线 | |
| ctx.save(); ctx.clip(inner); ctx.globalAlpha *= .12; ctx.strokeStyle = '#ffcf8a'; ctx.lineWidth = 1.2; | |
| for (let i = 0; i < 72; i++) { const a = i / 72 * PI2; ctx.beginPath(); ctx.moveTo(0, -300); ctx.lineTo(Math.cos(a) * 1200, -300 + Math.sin(a) * 1200); ctx.stroke(); } | |
| ctx.restore(); | |
| // 内金边 + 回纹 | |
| ctx.lineWidth = 6; ctx.strokeStyle = gold(ctx, -340, -400, 340, 470); ctx.stroke(inner); | |
| ctx.save(); ctx.globalAlpha *= .8; ctx.lineWidth = 2; const k = 330; ctx.beginPath(); | |
| for (let y = -250; y < 380; y += 26) { for (const d of [-1, 1]) { ctx.moveTo(d * k, y); ctx.lineTo(d * (k - 10), y); ctx.lineTo(d * (k - 10), y + 13); ctx.lineTo(d * k, y + 13); } } | |
| ctx.stroke(); ctx.restore(); | |
| // 铆钉 | |
| for (const [x, y] of [[-330, 450], [330, 450], [-330, -270], [330, -270]]) { ctx.fillStyle = gold(ctx, x - 12, y - 12, x + 12, y + 12); ctx.beginPath(); ctx.arc(x, y, 11, 0, PI2); ctx.fill(); ctx.fillStyle = 'rgba(255,255,255,.6)'; ctx.beginPath(); ctx.arc(x - 3, y - 3, 3, 0, PI2); ctx.fill(); } | |
| ctx.restore(); | |
| } | |
| // 熔融描边 | |
| trace(ctx, plate, 3200, span(forge, 0, .5), { width: 8, color: cool > .5 ? '#ffe3a0' : '#ffb14a', glow: 'rgba(255,140,40,1)', blur: 30 * s }); | |
| // —— 星图暗纹 —— | |
| const eng = o.engrave ?? 1; | |
| if (body > .5) { | |
| ctx.save(); ctx.globalAlpha *= .35 * cool; ctx.strokeStyle = '#ffcf8a'; ctx.lineWidth = 2; ctx.setLineDash([4, 6]); | |
| ctx.beginPath(); STARS.forEach(([x, y], i) => i ? ctx.lineTo(x, y + 120) : ctx.moveTo(x, y + 120)); ctx.stroke(); ctx.restore(); | |
| STARS.forEach(([x, y, r]) => { ctx.save(); ctx.scale(1, 1); star(ctx, x, y + 120, r, [255, 220, 150], .5 * cool, .3); ctx.restore(); }); | |
| } | |
| // —— 顶部徽章 —— | |
| const mk = span(forge, .35, .95); | |
| if (mk > 0) { | |
| const my = -300; | |
| ctx.save(); ctx.globalAlpha *= mk; | |
| ctx.shadowColor = 'rgba(0,0,0,.6)'; ctx.shadowBlur = 30 * s; | |
| ctx.fillStyle = gold(ctx, -170, my - 170, 170, my + 170, cool < 1 ? 1 : 0); ctx.beginPath(); ctx.arc(0, my, 168, 0, PI2); ctx.fill(); ctx.shadowColor = 'transparent'; | |
| const rg = ctx.createRadialGradient(0, my - 40, 10, 0, my, 140); rg.addColorStop(0, '#7a1712'); rg.addColorStop(1, '#2a0506'); ctx.fillStyle = rg; ctx.beginPath(); ctx.arc(0, my, 140, 0, PI2); ctx.fill(); | |
| ctx.strokeStyle = gold(ctx, -140, my - 140, 140, my + 140); ctx.lineWidth = 4; ctx.stroke(); | |
| // 齿边 | |
| ctx.fillStyle = gold(ctx, -170, my - 170, 170, my + 170); for (let i = 0; i < 48; i++) { const a = i / 48 * PI2; ctx.beginPath(); ctx.arc(Math.cos(a) * 158, my + Math.sin(a) * 158, 4, 0, PI2); ctx.fill(); } | |
| ctx.restore(); | |
| laurel(ctx, 0, my + 20, 1.25, -1, span(forge, .55, 1)); laurel(ctx, 0, my + 20, 1.25, 1, span(forge, .55, 1)); | |
| ramHead(ctx, 0, my + 12, .62, { k: span(forge, .4, 1), molten: cool < .6, eye: .9 }); | |
| } | |
| // —— 缎带 —— | |
| const rb = o.ribbon ?? 1; | |
| if (rb > 0) { | |
| const w = 860 * E.out3(rb), y = -88, h = 96; | |
| ctx.save(); | |
| for (const d of [-1, 1]) { ctx.fillStyle = '#7d1310'; ctx.beginPath(); const ex = d * w / 2; ctx.moveTo(ex - d * 20, y - h / 2 + 22); ctx.lineTo(ex + d * 70, y - h / 2 + 22); ctx.lineTo(ex + d * 44, y + 14); ctx.lineTo(ex + d * 70, y + h / 2 + 22); ctx.lineTo(ex - d * 20, y + h / 2 + 22); ctx.closePath(); ctx.fill(); } | |
| const g = ctx.createLinearGradient(0, y - h / 2, 0, y + h / 2); g.addColorStop(0, '#e8443a'); g.addColorStop(.5, '#b8221b'); g.addColorStop(1, '#7d1310'); | |
| ctx.shadowColor = 'rgba(0,0,0,.5)'; ctx.shadowBlur = 18 * s; ctx.fillStyle = g; ctx.beginPath(); | |
| ctx.moveTo(-w / 2, y - h / 2); ctx.quadraticCurveTo(0, y - h / 2 - 24, w / 2, y - h / 2); ctx.lineTo(w / 2, y + h / 2); ctx.quadraticCurveTo(0, y + h / 2 - 24, -w / 2, y + h / 2); ctx.closePath(); ctx.fill(); | |
| ctx.shadowColor = 'transparent'; ctx.strokeStyle = gold(ctx, -w / 2, y, w / 2, y); ctx.lineWidth = 3; ctx.stroke(); | |
| ctx.restore(); | |
| say(ctx, '白羊座 · ARIES', 0, y - 8, { size: 56, metal: true, alpha: span(rb, .5, 1), track: 6, shadow: 'rgba(60,0,0,.8)', blur: 6 }); | |
| } | |
| // —— 刻字信息 —— | |
| if (eng > 0) { | |
| const rows = [['守护星', '火星 ♂'], ['元素', '火 · 开创'], ['宫位', '黄道第一宫'], o.birthday ? ['生日', o.birthday] : ['星座季', '3.21 — 4.19']]; | |
| rows.forEach(([k, v], i) => { | |
| const kk = span(eng, i * .16, i * .16 + .35); if (kk <= 0) return; | |
| const col = i % 2, row = Math.floor(i / 2); const x = col ? 170 : -170, y = 30 + row * 108; | |
| ctx.save(); ctx.globalAlpha *= kk; rr(ctx, x - 150, y - 42, 300, 84, 12); ctx.fillStyle = 'rgba(0,0,0,.28)'; ctx.fill(); ctx.strokeStyle = 'rgba(255,207,138,.55)'; ctx.lineWidth = 1.5; ctx.stroke(); ctx.restore(); | |
| say(ctx, k, x, y - 18, { size: 22, fill: '#ffcf9a', alpha: kk, font: SANS, weight: 600, track: 4 }); | |
| say(ctx, v, x, y + 16, { size: 34, metal: true, alpha: kk, font: SERIF, weight: 800 }); | |
| }); | |
| const kw = span(eng, .7, 1); | |
| if (kw > 0) { say(ctx, '勇 气 · 热 情 · 开 创', 0, 262, { size: 34, fill: '#ffe3b0', alpha: kw, font: SERIF, weight: 700, shadow: 'rgba(255,120,40,.9)', blur: 18 }); } | |
| } | |
| // —— 先锋之证 + 署名 —— | |
| const nm = o.name ?? 1; | |
| if (nm > 0) { | |
| say(ctx, '先 锋 之 证', 0, 340, { size: 70, metal: true, alpha: span(nm, 0, .5), weight: 900, shadow: 'rgba(255,120,40,.8)', blur: 26 }); | |
| const holder = (o.holder || '').trim(); | |
| ctx.save(); ctx.globalAlpha *= span(nm, .3, .8); ctx.strokeStyle = 'rgba(255,207,138,.6)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(-210, 386); ctx.lineTo(210, 386); ctx.stroke(); ctx.restore(); | |
| say(ctx, holder ? `授予 · ${holder}` : '授予 · 每一位白羊座', 0, 418, { size: holder ? 36 : 32, fill: '#fff1d6', alpha: span(nm, .3, .9), font: SERIF, weight: 700, track: 2 }); | |
| say(ctx, 'VANGUARD OF THE ZODIAC · No.001', 0, 460, { size: 17, fill: 'rgba(255,214,160,.8)', alpha: span(nm, .5, 1), font: DISPLAY, weight: 600, track: 3 }); | |
| } | |
| // —— 扫光 —— | |
| const sh = o.shine; | |
| if (sh !== undefined && sh > 0 && sh < 1 && body >= 1) { | |
| ctx.save(); ctx.clip(plate); ctx.globalCompositeOperation = 'lighter'; | |
| const x = mix(-700, 700, sh); const g = ctx.createLinearGradient(x - 160, -500, x + 160, 500); | |
| g.addColorStop(0, 'rgba(255,240,200,0)'); g.addColorStop(.5, 'rgba(255,240,200,.45)'); g.addColorStop(1, 'rgba(255,240,200,0)'); | |
| ctx.fillStyle = g; ctx.fillRect(-400, -520, 800, 1040); ctx.restore(); | |
| } | |
| // 闪烁星芒 | |
| if (body >= 1) for (let i = 0; i < 5; i++) { const ph = (t * .5 + i * .23) % 1; const x = [-330, 300, -200, 250, 0][i], y = [-300, -200, 450, 380, -470][i]; ctx.save(); ctx.scale(1, 1); star(ctx, x, y, 3.5, [255, 240, 200], Math.sin(ph * Math.PI) * .9, 1.2); ctx.restore(); } | |
| ctx.restore(); | |
| } | |
| // 单独的铭牌卡片(壁纸 9:16 / 头像 1:1),用于页面下载 | |
| export function drawBadgeCard(ctx, W, H, opts, paintBg) { | |
| paintBg(ctx, W, H); | |
| const S = Math.min(W, H) / 1080; | |
| const P = H > W * 1.2; | |
| const size = P ? Math.min(W * 1.12, H * .66) : H * .84; | |
| drawBadge(ctx, W / 2, P ? H * .47 : H * .5, size, { t: 2.3, forge: 1, engrave: 1, ribbon: 1, name: 1, holder: opts.name, birthday: opts.birthday }); | |
| if (P) { | |
| say(ctx, '♈ 白羊座 · 春天从这里出发', W / 2, H * .085, { size: 40 * S, fill: '#ffe2b0', font: SERIF, weight: 700, track: 4 * S }); | |
| say(ctx, '愿你永远保有第一次出发时的那份热血', W / 2, H * .9, { size: 34 * S, fill: '#fff1d6', font: SERIF, weight: 600, track: 2 * S }); | |
| say(ctx, '星座文化 · 一份属于白羊座的荣耀', W / 2, H * .94, { size: 24 * S, fill: 'rgba(255,214,160,.8)', font: SANS, weight: 400 }); | |
| } | |
| } | |