adyou 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -76,17 +76,16 @@ const isHangul = (s) => /[가-힣]/.test(s);
|
|
|
76
76
|
const KO_ADVERB_FOR_MODIFIER = /[가-힣](에|에서|로|으로|부터|께|에게|한테|와|과|서|처럼|같이|보다)$/;
|
|
77
77
|
/** 자주 쓰는 2음절 관형형 용언(광고 문구) — 명사와 겹치지 않는 것만 */
|
|
78
78
|
const KO_MODIFIER_2 = new Set(['구운', '만든', '고른', '담은', '키운', '빚은', '캐낸', '뽑은', '볶은', '우린', '지은', '튀긴', '삶은', '말린', '재운', '절인', '쪄낸', '끓인', '내린', '갈아낸', '짜낸', '따낸', '건진', '골라낸', '추린', '모은', '익힌', '갓난', '길러낸', '빻은', '깎은', '썰은', '다진', '무친', '데친', '졸인', '지진', '부친', '섞은', '띄운', '숙성한', '엄선한', '수확한', '재배한', '직접', '갓']);
|
|
79
|
+
/** 1음절 관형형 용언(광고 문구에서 부사어 뒤에 오는 것) — 명사와 겹치는 글자(문·손·길·말·물·눈)는 넣지 않는다 */
|
|
80
|
+
const KO_MODIFIER_1 = new Set(['딴', '산', '쓴', '든', '본', '준', '큰', '낼', '쓸']);
|
|
79
81
|
/** 짧은 관형형 용언인가 — 1음절은 받침 ㄴ·ㄹ(딴·산·쓴·든·본·준·큰·낼·쓸 …)이고 관형사·수사·부사 결속 표에 없는 것 · 2음절은 표에 있는 것 */
|
|
80
82
|
function isShortModifier(w) {
|
|
81
83
|
const chars = [...w.replace(/[^가-힣]/g, '')];
|
|
82
84
|
if (!chars.length || chars.length !== [...w].length)
|
|
83
85
|
return false;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const fin = (w.codePointAt(0) - 0xac00) % 28;
|
|
88
|
-
return fin === 4 || fin === 8;
|
|
89
|
-
}
|
|
86
|
+
// 🔴 받침 ㄴ·ㄹ 이면 다 관형형이라고 보면 「문 앞까지」의 「문」·「손」·「길」 같은 명사가 앞 어절에 붙는다(2026-10-03 「하우스에서 문 / 앞까지 하루」) → 실제로 쓰는 1음절 관형형만 표로
|
|
87
|
+
if (chars.length === 1)
|
|
88
|
+
return KO_MODIFIER_1.has(w);
|
|
90
89
|
if (chars.length === 2)
|
|
91
90
|
return KO_MODIFIER_2.has(w);
|
|
92
91
|
return false;
|
|
@@ -226,7 +226,18 @@ export function tree(size, concept, brief, bg, logo, ctaText) {
|
|
|
226
226
|
// 🔴 글 위주(배경 이미지 없음) 소재가 허전하다는 사장님 지적(2026-09-30 「이렇게 허전하면 텍스트 광고를 누가 하겠니」) → 글 위주 포스터는 장식 도형·혜택 뱃지·증거 칩으로 채운다
|
|
227
227
|
const rich = !bg && !banner && !short && !narrow && !googleLight && Math.min(w, hh) >= 600;
|
|
228
228
|
const richProofN = rich && !widePoster ? (hh / w > 1.5 ? 3 : 2) : 3;
|
|
229
|
-
|
|
229
|
+
// 🔴 같은 사실을 뱃지·알약·본문에 되풀이하지 않는다(2026-10-03 「무료배송」이 뱃지·알약·본문에 세 번) — 헤드라인 > 뱃지 > 알약 > 본문 순으로 한 번만 보여 준다
|
|
230
|
+
const flat = (t) => String(t || '').replace(/[\s·,.]/g, '').toLowerCase();
|
|
231
|
+
const headFlat = flat(head), bodyFlat = flat(body);
|
|
232
|
+
const badgePool = [brief.offer, ...(brief.usp || []), ...(brief.proofPoints || [])].map((x) => String(x || '').replace(/\s+/g, ' ').trim()).filter((x) => x && wlen(x) <= 18 && !policyIssues(x).length);
|
|
233
|
+
const badge = rich && !widePoster ? (badgePool.find((x) => /%|할인|무료|혜택|증정|off|free/i.test(x) && !headFlat.includes(flat(x))) || '') : '';
|
|
234
|
+
const badgeFlat = flat(badge);
|
|
235
|
+
const grams = (t) => { const a = []; for (let i = 0; i + 1 < t.length; i++)
|
|
236
|
+
a.push(t.slice(i, i + 2)); return a; };
|
|
237
|
+
const seenText = headFlat + '|' + bodyFlat + '|' + badgeFlat;
|
|
238
|
+
const novel = (x) => { const f = flat(x); if (!f || headFlat.includes(f) || bodyFlat.includes(f) || (badgeFlat && (badgeFlat.includes(f) || f.includes(badgeFlat))))
|
|
239
|
+
return false; const g = grams(f).filter((q) => !/\d/.test(q)); return !(g.length >= 4 && g.filter((q) => seenText.includes(q)).length / g.length >= 0.5); };
|
|
240
|
+
let proofs = widePoster || rich ? [...(brief.proofPoints || []), ...(brief.usp || [])].map((x) => String(x || '').replace(/\s+/g, ' ').trim()).filter((x) => x && wlen(x) <= 36 && !policyIssues(x).length).filter((x, i, a) => a.indexOf(x) === i).filter(novel).slice(0, richProofN) : [];
|
|
230
241
|
const cjk = /[\u3000-\u9fff\uac00-\ud7af]/.test(body);
|
|
231
242
|
let bodyText = banner ? cutWords(body, 70) : widePoster ? cutWords(body, cjk ? 60 : 110) : googleLight ? cutWords(body, 48) : body;
|
|
232
243
|
if (widePoster) {
|
|
@@ -257,8 +268,23 @@ export function tree(size, concept, brief, bg, logo, ctaText) {
|
|
|
257
268
|
if (total() > avail)
|
|
258
269
|
proofs = [];
|
|
259
270
|
}
|
|
260
|
-
|
|
261
|
-
|
|
271
|
+
if (rich && !widePoster && proofs.length) {
|
|
272
|
+
const colW = w - pad * 2;
|
|
273
|
+
const lns = (t, fs, cap = 9) => Math.min(cap, Math.max(1, breakLines(t, fs, colW).length));
|
|
274
|
+
const chipRows = () => { let rows = 0, x = 0; for (const t of proofs) {
|
|
275
|
+
const cw = textW(t, chipFs) + chipFs * 2.4;
|
|
276
|
+
if (!rows || x + cw > colW) {
|
|
277
|
+
rows++;
|
|
278
|
+
x = cw;
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
x += cw + chipFs * 0.45;
|
|
282
|
+
} return rows; };
|
|
283
|
+
const total = () => headFs * 0.9 + (badge ? bodyFs * 1.7 : 0) + lns(head, headFs) * headFs * 1.12 + lns(bodyText, bodyFs, 4) * bodyFs * 1.45 + chipRows() * chipFs * 1.9 + ctaFs * 2.2 + headFs * 0.3 + pad * 0.5 * 4;
|
|
284
|
+
const avail = hh - pad * 2;
|
|
285
|
+
while (proofs.length && total() > avail)
|
|
286
|
+
proofs = proofs.slice(0, -1);
|
|
287
|
+
}
|
|
262
288
|
const chips = proofs.length ? h('div', { display: 'flex', flexWrap: 'wrap', gap: Math.round(chipFs * 0.45) }, proofs.map((t) => h('div', { display: 'flex', alignItems: 'center', gap: Math.round(chipFs * 0.4), fontSize: chipFs, fontWeight: 700, color: fg, backgroundColor: dark ? 'rgba(255,255,255,.16)' : 'rgba(255,255,255,.78)', borderRadius: 999, padding: `${Math.round(chipFs * 0.28)}px ${Math.round(chipFs * 0.7)}px`, whiteSpace: 'nowrap' }, [h('div', { width: Math.round(chipFs * 0.42), height: Math.round(chipFs * 0.42), borderRadius: 999, backgroundColor: primary }), h('div', { whiteSpace: 'nowrap' }, t)]))) : null;
|
|
263
289
|
const children = [];
|
|
264
290
|
if (bg)
|
|
@@ -270,8 +296,8 @@ export function tree(size, concept, brief, bg, logo, ctaText) {
|
|
|
270
296
|
children.push(h('div', { position: 'absolute', top: 0, left: 0, width: w, height: hh, backgroundImage: `radial-gradient(circle at 82% 16%, ${tint(0.34)} 0%, ${tint(0)} 52%)` }, ''));
|
|
271
297
|
children.push(h('div', { position: 'absolute', right: -Math.round(base * 0.2), top: -Math.round(base * 0.16), width: Math.round(base * 0.85), height: Math.round(base * 0.85), borderRadius: 9999, backgroundColor: tint(0.16) }, ''));
|
|
272
298
|
children.push(h('div', { position: 'absolute', left: -Math.round(base * 0.28), bottom: -Math.round(base * 0.3), width: Math.round(base * 0.8), height: Math.round(base * 0.8), borderRadius: 9999, backgroundColor: tint(0.12) }, ''));
|
|
273
|
-
children.push(h('div', { position: 'absolute', right: Math.round(base * 0.
|
|
274
|
-
children.push(h('div', { position: 'absolute', right: Math.round(base * 0.
|
|
299
|
+
children.push(h('div', { position: 'absolute', right: Math.round(base * 0.07), top: Math.round(base * 0.2), width: Math.round(base * 0.15), height: Math.round(base * 0.15), borderRadius: 9999, border: `${Math.max(2, Math.round(base * 0.004))}px solid ${tint(0.4)}` }, ''));
|
|
300
|
+
children.push(h('div', { position: 'absolute', right: Math.round(base * 0.095), top: Math.round(base * 0.225), width: Math.round(base * 0.1), height: Math.round(base * 0.1), borderRadius: 9999, backgroundColor: tint(0.22) }, ''));
|
|
275
301
|
}
|
|
276
302
|
const brandRow = h('div', { display: 'flex', alignItems: 'center', gap: Math.round(pad * 0.3) }, [
|
|
277
303
|
logo ? h('img', { height: short ? Math.round(hh * 0.5) : Math.round(headFs * 0.9), borderRadius: 6 }, undefined, { src: logo }) : h('div', { fontSize: Math.round(headFs * 0.42), fontWeight: 800, color: fg, letterSpacing: -0.5 }, brief.company),
|
|
@@ -289,7 +315,7 @@ export function tree(size, concept, brief, bg, logo, ctaText) {
|
|
|
289
315
|
children.push(h('div', { position: 'absolute', top: 0, left: 0, width: w, height: hh, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', padding: pad }, [
|
|
290
316
|
h('div', { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, [brandRow, domain && !banner ? h('div', { fontSize: Math.round(headFs * 0.3), fontWeight: 500, color: sub }, domain) : h('div', {}, '')]),
|
|
291
317
|
h('div', { display: 'flex', flexDirection: 'column', gap: Math.round(pad * (banner ? 0.35 : 0.5)), maxWidth: isWide ? Math.round(w * 0.72) : w - pad * 2 }, [
|
|
292
|
-
...(rich && !isWide ? [h('div', { display: 'flex', alignItems: 'center', gap: Math.round(pad * 0.3) }, [h('div', { width: Math.round(base * 0.075), height: Math.max(6, Math.round(base * 0.008)), borderRadius: 9, backgroundColor: primary }, ''), ...(badge ? [h('div', { display: 'flex', fontSize: Math.round(bodyFs * 0.82), fontWeight: 700, color: onColor(primary), backgroundColor: primary, borderRadius: 999, padding: `${Math.round(bodyFs * 0.25)}px ${Math.round(bodyFs * 0.7)}px` }, badge)] : [])])] : []),
|
|
318
|
+
...(rich && !isWide ? [h('div', { display: 'flex', alignItems: 'center', gap: Math.round(pad * 0.3) }, [...(badge ? [] : [h('div', { width: Math.round(base * 0.075), height: Math.max(6, Math.round(base * 0.008)), borderRadius: 9, backgroundColor: primary }, '')]), ...(badge ? [h('div', { display: 'flex', fontSize: Math.round(bodyFs * 0.82), fontWeight: 700, color: onColor(primary), backgroundColor: primary, borderRadius: 999, padding: `${Math.round(bodyFs * 0.25)}px ${Math.round(bodyFs * 0.7)}px` }, badge)] : [])])] : []),
|
|
293
319
|
linesEl(head, { fontSize: headFs, fontWeight: 800, color: fg, lineHeight: 1.12, letterSpacing: -1.5 }, isWide ? Math.round(w * 0.72) : w - pad * 2),
|
|
294
320
|
// 본문도 같은 규칙으로 균형 있게(「… 분들께 / 추천해요.」 한 어절 고아 방지 · 최대 4줄 · 배너는 폭이 좁아 렌더러 줄바꿈)
|
|
295
321
|
(banner && bodyFs < 14) || bodyFs === 0 ? h('div', {}, '') : banner ? wordsEl(bodyText, { fontSize: bodyFs, fontWeight: 500, color: sub, lineHeight: 1.45 }) : linesEl(bodyText, { fontSize: bodyFs, fontWeight: 500, color: sub, lineHeight: 1.45 }, isWide ? Math.round(w * 0.72) : w - pad * 2, 'left', 4),
|
package/package.json
CHANGED