/* ===================================================================== * Afflix Thumbnail · T01_A (가성비 비교 · BEST 3) * --------------------------------------------------------------------- * 디팀 v1.0.0 · 2026-05-07 * SSOT: v12/thumb-slots.json (templates.T01_A) * 개발팀장 흡수 가이드: * - 이 컴포넌트는 600×600 기준으로 그리고, 1200 retina는 size=1200 prop * - 모든 시각 규격은 SSOT.style_rules 와 1:1 일치 * - validators는 SSOT.templates.T01_A.validators 동일 — 검증은 발행 전 * - HTML→PNG 렌더는 자동화 영역 (puppeteer/playwright/satori 등) * 입력 슬롯: * props.slots = { * post_id, template_id, char_id, theme_color, * title_main, title_sub, badge_text, highlight_price, * product_image_1, product_image_2, product_image_3, * product_label_1, product_label_2, product_label_3, brand_mark * } * ===================================================================== */ const { useMemo: uMt } = React; /* SSOT 와 1:1 — 사이즈별 px 스케일 */ const T01A_STYLE = { 600: { title_main: 56, title_sub: 22, badge: 18, price: 30, label: 17, brand: 14, pad: 24, gap: 14, card_radius: 16, badge_radius: 6, price_radius: 8, header_h: 188, products_h: 296, footer_h: 92, big_w: 256, small_w: 160, image_pad: 12, }, 1200: { title_main: 112, title_sub: 44, badge: 36, price: 60, label: 34, brand: 28, pad: 48, gap: 28, card_radius: 32, badge_radius: 12, price_radius: 16, header_h: 376, products_h: 592, footer_h: 184, big_w: 512, small_w: 320, image_pad: 24, }, }; /* 폴백 룰 — SSOT.fallback_rules */ function _truncate(text, max, useEllipsis = true) { if (!text) return ''; if (text.length <= max) return text; return useEllipsis ? text.slice(0, max - 1) + '…' : text.slice(0, max); } /* 색상 darken (theme_color → theme_color_700) — 자동 그라디언트용 */ function _darken(hex, amt = 0.18) { const m = /^#([0-9A-Fa-f]{6})$/.exec(hex || ''); if (!m) return hex; const n = parseInt(m[1], 16); let r = (n >> 16) & 0xff, g = (n >> 8) & 0xff, b = n & 0xff; r = Math.max(0, Math.round(r * (1 - amt))); g = Math.max(0, Math.round(g * (1 - amt))); b = Math.max(0, Math.round(b * (1 - amt))); return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); } /* SSOT validators 와 동일 — 발행 전 검증 가능 */ window.validateThumbT01A = function(slots) { const errs = []; if (!slots) return ['slots_missing']; if (!slots.title_main) errs.push('title_main_required'); else if (slots.title_main.length > 22) errs.push('title_main_overflow'); if (slots.title_sub && slots.title_sub.length > 18) errs.push('title_sub_overflow'); if (slots.badge_text && slots.badge_text.length > 6) errs.push('badge_overflow'); if (!slots.highlight_price) errs.push('highlight_price_required'); else if (slots.highlight_price.length > 10) errs.push('highlight_price_overflow'); if (!slots.product_image_1) errs.push('product_image_1_missing'); if (!slots.product_image_2) errs.push('product_image_2_missing'); if (!slots.product_image_3) errs.push('product_image_3_missing'); if (!slots.theme_color || !/^#[0-9A-Fa-f]{6}$/.test(slots.theme_color)) errs.push('theme_color_invalid'); if (!slots.char_id || !/^C\d{2}$/.test(slots.char_id)) errs.push('char_id_invalid'); return errs; }; /* ============== T01_A 컴포넌트 ============== */ window.ThumbT01A = function ThumbT01A({ slots, size = 600 }) { const s = T01A_STYLE[size] || T01A_STYLE[600]; const v = useMemo_t(() => slots || {}, [slots]); /* 폴백 적용 */ const titleMain = _truncate(v.title_main || '', 22); const titleSub = _truncate(v.title_sub || '', 18); const badgeText = (v.badge_text && v.badge_text.length <= 6) ? v.badge_text : ''; const price = _truncate(v.highlight_price || '', 10); const themeColor = (/^#[0-9A-Fa-f]{6}$/.test(v.theme_color || '')) ? v.theme_color : '#D7263D'; const themeDark = _darken(themeColor, 0.22); const brandMark = v.brand_mark || 'AFFLIX'; const lbl1 = v.product_label_1 || 'BEST 1'; const lbl2 = v.product_label_2 || 'BEST 2'; const lbl3 = v.product_label_3 || 'BEST 3'; return (