chore(brand): 스토어 그래픽 에셋 생성기 + 산출물
- brand/gen-store-assets.mjs: 1024 마스터에서 스토어 이미지 생성(sharp) - brand/logo-1024.png: 돈돼지 마스터 아트(1024x1024) 백업 - brand/store/: icon-512, feature-1024x500(텍스트 유/무) — Play Console 업로드용 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
// Play Console 스토어 그래픽 생성기.
|
||||||
|
// 입력: brand/logo-1024.png (사용자가 저장한 1024x1024 돈돼지 마스터 아트)
|
||||||
|
// 출력: brand/store/icon-512.png (스토어 고해상도 아이콘 512x512, 불투명)
|
||||||
|
// brand/store/feature-1024x500.png (피처 그래픽 — 텍스트 없음, 항상 안정)
|
||||||
|
// brand/store/feature-1024x500-text.png(피처 그래픽 — 한글 텍스트 포함, best-effort)
|
||||||
|
//
|
||||||
|
// 실행: node brand/gen-store-assets.mjs [입력경로]
|
||||||
|
import sharp from 'sharp'
|
||||||
|
import { mkdirSync, existsSync } from 'node:fs'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { dirname, join, resolve } from 'node:path'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const SRC = resolve(process.argv[2] || join(__dirname, 'logo-1024.png'))
|
||||||
|
const OUT = join(__dirname, 'store')
|
||||||
|
|
||||||
|
// 브랜드 색 (아이콘 배경의 크림/옐로 계열)
|
||||||
|
const CREAM = '#FBF7E9'
|
||||||
|
const YELLOW = '#F2C94C'
|
||||||
|
const BROWN = '#B8860B'
|
||||||
|
|
||||||
|
if (!existsSync(SRC)) {
|
||||||
|
console.error(`\n[!] 마스터 아트가 없습니다: ${SRC}`)
|
||||||
|
console.error(` 1024x1024 돈돼지 PNG 를 위 경로에 저장한 뒤 다시 실행하세요.\n`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
mkdirSync(OUT, { recursive: true })
|
||||||
|
|
||||||
|
const log = (f) => console.log(' ✓', f)
|
||||||
|
|
||||||
|
// 1) 스토어 아이콘 512x512 — 불투명 크림 배경 위에 마스터를 합성(투명 PNG 대비)
|
||||||
|
async function icon512() {
|
||||||
|
const logo = await sharp(SRC).resize(512, 512, { fit: 'contain', background: CREAM }).png().toBuffer()
|
||||||
|
const out = join(OUT, 'icon-512.png')
|
||||||
|
await sharp({ create: { width: 512, height: 512, channels: 4, background: CREAM } })
|
||||||
|
.composite([{ input: logo }])
|
||||||
|
.flatten({ background: CREAM })
|
||||||
|
.png()
|
||||||
|
.toFile(out)
|
||||||
|
log('store/icon-512.png (512x512, 불투명)')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 공통: 1024x500 배경 — 피그가 놓일 우측(크림)에서 좌측(옐로 살짝)으로.
|
||||||
|
// 중심을 피그 쪽 크림으로 두어 마스터의 크림 배경 사각이 자연스럽게 녹도록 한다.
|
||||||
|
function bgSvg(cx = '80%') {
|
||||||
|
return Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="500">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="g" cx="${cx}" cy="48%" r="95%">
|
||||||
|
<stop offset="0%" stop-color="${CREAM}"/>
|
||||||
|
<stop offset="62%" stop-color="${CREAM}"/>
|
||||||
|
<stop offset="100%" stop-color="${YELLOW}" stop-opacity="0.55"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="1024" height="500" fill="url(#g)"/>
|
||||||
|
</svg>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 피처 그래픽 (텍스트 없음) — 가운데 큼직하게, 박스 없이 녹아들게
|
||||||
|
async function featurePlain() {
|
||||||
|
const bg = await sharp(bgSvg('50%')).png().toBuffer()
|
||||||
|
const pig = await sharp(SRC).resize(420, 420, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer()
|
||||||
|
const out = join(OUT, 'feature-1024x500.png')
|
||||||
|
await sharp(bg).composite([{ input: pig, top: 40, left: 302 }]).png().toFile(out)
|
||||||
|
log('store/feature-1024x500.png (텍스트 없음)')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 피처 그래픽 (한글 텍스트) — 좌측 텍스트 / 우측 피그, 겹침 없도록 간격 조정.
|
||||||
|
async function featureWithText() {
|
||||||
|
const bg = await sharp(bgSvg('82%')).png().toBuffer()
|
||||||
|
const pig = await sharp(SRC).resize(380, 380, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer()
|
||||||
|
const text = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="500">
|
||||||
|
<style>
|
||||||
|
.t { font-family: 'Malgun Gothic','맑은 고딕',sans-serif; fill:${BROWN}; }
|
||||||
|
.title { font-size: 80px; font-weight: 800; }
|
||||||
|
.sub { font-size: 29px; font-weight: 500; fill:#8a6d10; }
|
||||||
|
</style>
|
||||||
|
<text x="64" y="232" class="t title">돈돼지 가계부</text>
|
||||||
|
<text x="68" y="290" class="t sub">슬림하게 관리하는</text>
|
||||||
|
<text x="68" y="332" class="t sub">가계부 · 자산 · 예산</text>
|
||||||
|
</svg>`)
|
||||||
|
const out = join(OUT, 'feature-1024x500-text.png')
|
||||||
|
await sharp(bg)
|
||||||
|
.composite([{ input: pig, top: 60, left: 630 }, { input: text }])
|
||||||
|
.png()
|
||||||
|
.toFile(out)
|
||||||
|
log('store/feature-1024x500-text.png (한글 텍스트, 폰트 렌더 확인 필요)')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n[스토어 에셋 생성] 입력: ${SRC}\n`)
|
||||||
|
await icon512()
|
||||||
|
await featurePlain()
|
||||||
|
await featureWithText()
|
||||||
|
console.log(`\n완료 → ${OUT}\n`)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 908 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
Reference in New Issue
Block a user