adyou 0.6.6 → 0.6.8

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.
@@ -152,7 +152,39 @@ export function tree(size, concept, brief, bg, logo, ctaText) {
152
152
  ctaFs = Math.round(hh * 0.058);
153
153
  }
154
154
  const chipFs = Math.round(hh * 0.044);
155
- const proofs = widePoster ? [...(brief.proofPoints || []), ...(brief.usp || [])].map((x) => cutWords(String(x || '').replace(/\s+/g, ' ').trim(), 18)).filter((x) => x && wlen(x) <= 36).filter((x, i, a) => a.indexOf(x) === i).slice(0, 3) : [];
155
+ let proofs = widePoster ? [...(brief.proofPoints || []), ...(brief.usp || [])].map((x) => { const t = String(x || '').replace(/\s+/g, ' ').trim(); return cutWords(t, /[\u3000-\u9fff\uac00-\ud7af]/.test(t) ? 18 : 34); }).filter((x) => x && wlen(x) <= 36).filter((x, i, a) => a.indexOf(x) === i).slice(0, 3) : [];
156
+ const cjk = /[\u3000-\u9fff\uac00-\ud7af]/.test(body);
157
+ let bodyText = banner ? cutWords(body, 70) : widePoster ? cutWords(body, cjk ? 60 : 110) : googleLight ? cutWords(body, 48) : body;
158
+ if (widePoster) {
159
+ // 세로 예산 — 헤드라인이 두 줄이면 CTA 가 바닥 밖으로 밀린다(힐스테이트 1200×628 실사고 · 2026-09-17) → 줄 수를 어림해 넘치면 헤드라인 축소 → 알약 줄이기 → 본문 한 줄 순으로 맞춘다
160
+ const colW = Math.round(w * 0.72);
161
+ const gap = Math.round(pad * 0.5);
162
+ // 글자 폭 어림(Pretendard 실측 · 에어서울 97px 헤드라인 780px): 한글·전각 0.86em · 숫자·라틴 0.55em · 공백 0.3em
163
+ const textW = (t, fs) => [...t].reduce((n, ch) => n + (ch === ' ' ? 0.3 : (ch.codePointAt(0) || 0) > 0x2e7f ? 0.86 : 0.55), 0) * fs;
164
+ const lines = (t, fs) => Math.max(1, Math.ceil(textW(t, fs) / colW));
165
+ const chipRows = () => { let rows = 0, x = 0; for (const t of proofs) {
166
+ const cw = textW(t, chipFs) + chipFs * 2.4;
167
+ if (!rows || x + cw > colW) {
168
+ rows++;
169
+ x = cw;
170
+ }
171
+ else
172
+ x += cw + chipFs * 0.45;
173
+ } return rows; };
174
+ // 실측 보정(에어서울 1200×628 · 헤드라인 97 + 본문 2줄 + 알약 2줄 + CTA ≈ 563px 로 들어감) — 로고 행 ≈ headFs×0.5 · 사이 간격 4곳
175
+ const total = () => headFs * (logo ? 0.95 : 0.5) + lines(head, headFs) * headFs * 1.12 + lines(bodyText, bodyFs) * bodyFs * 1.45 + (proofs.length ? chipRows() * chipFs * 1.9 : 0) + ctaFs * 2 + headFs * 0.28 * 1.4 + gap * 4;
176
+ const avail = hh - pad * 0.9;
177
+ if (total() > avail && chipRows() > 1)
178
+ proofs = proofs.slice(0, 2);
179
+ if (total() > avail && chipRows() > 1)
180
+ proofs = proofs.slice(0, 1);
181
+ if (total() > avail && lines(head, headFs) > 1)
182
+ headFs = Math.round(hh * 0.11);
183
+ if (total() > avail && lines(bodyText, bodyFs) > 1)
184
+ bodyText = cutWords(body, cjk ? 30 : 55);
185
+ if (total() > avail)
186
+ proofs = [];
187
+ }
156
188
  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;
157
189
  const children = [];
158
190
  if (bg)
@@ -175,7 +207,7 @@ export function tree(size, concept, brief, bg, logo, ctaText) {
175
207
  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', {}, '')]),
176
208
  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 }, [
177
209
  wordsEl(head, { fontSize: headFs, fontWeight: 800, color: fg, lineHeight: 1.12, letterSpacing: -1.5 }),
178
- (banner && bodyFs < 14) || bodyFs === 0 ? h('div', {}, '') : wordsEl(banner ? cutWords(body, 70) : widePoster ? cutWords(body, 60) : googleLight ? cutWords(body, 48) : body, { fontSize: bodyFs, fontWeight: 500, color: sub, lineHeight: 1.45 }),
210
+ (banner && bodyFs < 14) || bodyFs === 0 ? h('div', {}, '') : wordsEl(bodyText, { fontSize: bodyFs, fontWeight: 500, color: sub, lineHeight: 1.45 }),
179
211
  ...(chips ? [chips] : []),
180
212
  h('div', { display: 'flex', marginTop: Math.round(pad * 0.3) }, [ctaPill]),
181
213
  ]),
@@ -85,7 +85,10 @@ export async function generateStoryboards(brief, concept, o) {
85
85
  continue;
86
86
  }
87
87
  const clips = p.clips.map((pc, i) => { const src = b.clips[i] || b.clips[b.clips.length - 1]; return { sec: pc.sec, kind: pc.kind, prompt: (src?.prompt || '').trim(), speech: src?.speech || undefined, continues: i > 0 ? !!src?.continues : false }; });
88
- boards.push({ id: `${concept.key}_${p.format}`, format: p.format, genre: p.format === 'ugc_selfie' ? 'ugc' : (b.genre && genres.includes(b.genre) ? b.genre : genres[0]) || 'photoreal', hook: fitHook(b.hook), captions: b.captions.map((c) => c.trim()).filter(Boolean).slice(0, 3), voice: b.voice || undefined, music: b.music || tone.music, cta, durationSec: o.durationSec, clips, lang, cast: b.cast || undefined });
88
+ const caps = b.captions.map((c) => c.trim()).filter(Boolean).slice(0, 3);
89
+ // 🔴 장면 포맷의 voice 를 모델이 비우면 내레이션 없는 「음악만」 영상이 된다(힐스테이트 글자 훅 · 2026-09-17) → 자막 두 줄로 내레이션을 만든다
90
+ const voice = b.voice?.trim() || (p.format !== 'ugc_selfie' ? [caps[0], caps[1]].filter(Boolean).join(', ').replace(/[.!?。!?]?$/, '.') : undefined);
91
+ boards.push({ id: `${concept.key}_${p.format}`, format: p.format, genre: p.format === 'ugc_selfie' ? 'ugc' : (b.genre && genres.includes(b.genre) ? b.genre : genres[0]) || 'photoreal', hook: fitHook(b.hook), captions: caps, voice, music: b.music || tone.music, cta, durationSec: o.durationSec, clips, lang, cast: b.cast || undefined });
89
92
  }
90
93
  return boards;
91
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adyou",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "ADYou — 대행사 없이, 당신이 직접. 사이트 주소 하나로 광고 소재·매체 등록·자동 운영·보고까지: AI 광고 자율주행 CLI + MCP 서버(Meta·Google · 생성은 항상 PAUSED · 승인 뒤 시작)",
5
5
  "type": "module",
6
6
  "license": "MIT",