hanseol-dev 5.0.3-dev.4 → 5.0.3-dev.41
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.
- package/dist/agents/office/powerpoint-create-agent.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-agent.js +251 -331
- package/dist/agents/office/powerpoint-create-agent.js.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.d.ts +3 -2
- package/dist/agents/office/powerpoint-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.js +305 -247
- package/dist/agents/office/powerpoint-create-prompts.js.map +1 -1
- package/dist/agents/office/word-create-agent.js +4 -4
- package/dist/agents/office/word-create-agent.js.map +1 -1
- package/dist/agents/office/word-create-prompts.d.ts +3 -3
- package/dist/agents/office/word-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/word-create-prompts.js +103 -42
- package/dist/agents/office/word-create-prompts.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/tools/office/powerpoint-client.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-client.js +4 -0
- package/dist/tools/office/powerpoint-client.js.map +1 -1
- package/dist/tools/office/word-client.d.ts +15 -0
- package/dist/tools/office/word-client.d.ts.map +1 -1
- package/dist/tools/office/word-client.js +228 -5
- package/dist/tools/office/word-client.js.map +1 -1
- package/dist/tools/office/word-tools/section-builders.d.ts +2 -0
- package/dist/tools/office/word-tools/section-builders.d.ts.map +1 -1
- package/dist/tools/office/word-tools/section-builders.js +189 -34
- package/dist/tools/office/word-tools/section-builders.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const PPT_DESIGN_PROMPT = `You are an elite presentation design consultant AND structure planner. Output ONLY valid JSON — no markdown, no explanation, no code fences.
|
|
2
2
|
|
|
3
|
-
Given the user's instruction, produce a JSON object
|
|
3
|
+
Given the user's instruction, produce a JSON object with a visual design system and a detailed slide plan.
|
|
4
4
|
|
|
5
5
|
{
|
|
6
6
|
"design": {
|
|
@@ -22,170 +22,102 @@ Given the user's instruction, produce a JSON object that combines a cohesive vis
|
|
|
22
22
|
]
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
═══ DESIGN SYSTEM
|
|
26
|
-
•
|
|
27
|
-
•
|
|
28
|
-
•
|
|
29
|
-
•
|
|
30
|
-
•
|
|
31
|
-
•
|
|
32
|
-
•
|
|
33
|
-
•
|
|
34
|
-
• design.mood: One of modern-minimal, bold-energetic, corporate-elegant, warm-friendly, academic-clean
|
|
35
|
-
• design.design_notes: 1-2 sentence visual approach description
|
|
36
|
-
|
|
37
|
-
• slides[].type: One of "title", "content", "closing"
|
|
38
|
-
• slides[].title: Slide title in user's language (max 60 chars)
|
|
39
|
-
• slides[].content_direction: The ACTUAL TEXT AND DATA to display on this slide (6-10 sentences).
|
|
25
|
+
═══ DESIGN SYSTEM ═══
|
|
26
|
+
• primary_color: Deep main color for headers and key elements
|
|
27
|
+
• accent_color: Vibrant contrast color for highlights and CTAs
|
|
28
|
+
• background_color: Page background (near white, near black, or subtle tint)
|
|
29
|
+
• text_color: Main text color (must contrast with background)
|
|
30
|
+
• accent_light: Light tint for subtle section backgrounds
|
|
31
|
+
• gradient_end: Paired gradient color for primary
|
|
32
|
+
• font_title / font_body: System fonts only (Segoe UI, Arial, Georgia, Calibri, Malgun Gothic)
|
|
33
|
+
• mood: One of modern-minimal, bold-energetic, corporate-elegant, warm-friendly, academic-clean
|
|
40
34
|
|
|
41
35
|
═══ COLOR PALETTE — CREATIVE PSYCHOLOGY ═══
|
|
42
|
-
⚠
|
|
43
|
-
|
|
44
|
-
•
|
|
45
|
-
•
|
|
46
|
-
•
|
|
47
|
-
• Education/Training
|
|
48
|
-
• Marketing/Creative
|
|
49
|
-
• Environment
|
|
50
|
-
•
|
|
36
|
+
⚠ EVERY presentation must have a UNIQUE palette matching the topic's emotional tone.
|
|
37
|
+
• Medical/Health → blues + greens (#0B5394, #27AE60) — trust, healing
|
|
38
|
+
• Tech/AI/Startup → purples + cyans (#6C63FF, #00BCD4) — innovation, future
|
|
39
|
+
• Finance/Investment → navy + gold (#1A237E, #F4A300) — authority, wealth
|
|
40
|
+
• Travel/Lifestyle → warm coral + sky blue (#E8634A, #4A90D9) — adventure, excitement
|
|
41
|
+
• Education/Training → teal + orange (#00796B, #FF7043) — growth, curiosity
|
|
42
|
+
• Marketing/Creative → coral + purple (#FF6B6B, #7C3AED) — boldness, passion
|
|
43
|
+
• Environment → deep green + amber (#1B5E20, #FFA000) — nature, urgency
|
|
44
|
+
• Food/Restaurant → warm brown + red (#5D4037, #E53935) — appetite, warmth
|
|
45
|
+
• Fashion/Beauty → rose + charcoal (#E91E63, #37474F) — elegance, style
|
|
46
|
+
• Real Estate → slate blue + gold (#455A64, #FFB300) — trust, luxury
|
|
51
47
|
⚠ Choose colors that MATCH the specific topic. Generic blue = LAZY.
|
|
52
48
|
⚠ Ensure accent_color has HIGH contrast with primary_color.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
• modern-minimal: Clean lines, subtle shadows, generous whitespace, thin borders, smooth radius (12-16px)
|
|
57
|
-
• bold-energetic: Strong gradients, deep shadows, thick accent bars, high contrast, punchy colors
|
|
58
|
-
• corporate-elegant: Refined spacing, subtle gradients, structured grids, sophisticated feel
|
|
59
|
-
• warm-friendly: Rounded corners (20px+), soft shadows, inviting spacing, gentle transitions
|
|
60
|
-
• academic-clean: Tight grids, minimal decoration, clear hierarchy, data-first design
|
|
61
|
-
|
|
62
|
-
═══ SLIDE STRUCTURE RULES ═══
|
|
63
|
-
⚠ First slide MUST be type "title"
|
|
64
|
-
⚠ Last slide MUST be type "closing" with "감사합니다"/"Thank You" as title
|
|
49
|
+
|
|
50
|
+
═══ SLIDE STRUCTURE ═══
|
|
51
|
+
⚠ First slide MUST be type "title". Last slide MUST be type "closing".
|
|
65
52
|
⚠ Minimum 10, maximum 13 slides total. Aim for 10-12.
|
|
66
|
-
⚠
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
•
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
• title: "감사합니다" (Korean) or "Thank You" (English)
|
|
80
|
-
• content_direction: Same company/topic name as title slide
|
|
81
|
-
• Do NOT include visual/layout instructions — closing uses a fixed premium template
|
|
82
|
-
|
|
83
|
-
═══ TEMPLATES ═══
|
|
84
|
-
PITCH DECK (startup/사업계획서/피치덱): 12 slides
|
|
53
|
+
⚠ Current year: \${new Date().getFullYear()}.
|
|
54
|
+
|
|
55
|
+
═══ TITLE & CLOSING FORMAT ═══
|
|
56
|
+
⚠ Title slide:
|
|
57
|
+
• title: Company/topic name ONLY — max 3-4 words (rendered at 96px)
|
|
58
|
+
✓ "드림투어" ✓ "MediAI" ✗ "MediAI - AI 의료 혁신" (too long)
|
|
59
|
+
• content_direction: The actual subtitle/tagline TEXT (1-2 lines, under 120 chars)
|
|
60
|
+
⚠ Closing slide:
|
|
61
|
+
• title: "감사합니다" (Korean) / "Thank You" (English)
|
|
62
|
+
• content_direction: Company/topic name
|
|
63
|
+
|
|
64
|
+
═══ TOPIC-SPECIFIC TEMPLATES ═══
|
|
65
|
+
PITCH DECK (사업계획서/피치덱): 12 slides
|
|
85
66
|
title → problem → solution → market size → product(1) → product(2) → business model → competition → traction → team → financials → closing
|
|
86
67
|
|
|
87
|
-
|
|
68
|
+
MARKETING PROPOSAL (마케팅/제안서): 12 slides
|
|
69
|
+
title → company intro → market analysis → target audience → product/service showcase(1) → product/service showcase(2) → marketing strategy → promotions/pricing → SNS/partnerships → performance KPIs → expected results → closing
|
|
70
|
+
|
|
71
|
+
BUSINESS REPORT (보고서/실적): 10 slides
|
|
88
72
|
title → executive summary → key metrics → analysis → comparison → spotlight → breakdown → action plan → recommendations → closing
|
|
89
73
|
|
|
90
|
-
TRAINING (
|
|
74
|
+
TRAINING (교육/세미나): 10 slides
|
|
91
75
|
title → objectives → core concepts → key data → process → details → case study → summary → key takeaway → closing
|
|
92
76
|
|
|
93
|
-
PRODUCT LAUNCH (
|
|
77
|
+
PRODUCT LAUNCH (제품/출시): 11 slides
|
|
94
78
|
title → market need → overview → key feature → features → metrics → use cases → pricing → timeline → CTA → closing
|
|
95
79
|
|
|
96
|
-
GENERAL
|
|
80
|
+
GENERAL: 10 slides
|
|
97
81
|
title → overview → key points → data → detail → spotlight → comparison → process → summary → closing
|
|
98
82
|
|
|
99
|
-
═══ THE #1
|
|
100
|
-
⚠⚠⚠
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
✓ "
|
|
109
|
-
✓ "
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
• "제품/플랫폼" → "핵심 기능" (3 card grid with feature descriptions)
|
|
125
|
-
• "마케팅/GTM 전략" → "마케팅 채널 전략" (3 cards) + "성과 지표" (metrics or table)
|
|
126
|
-
• "로드맵" → "단기 로드맵" (3 milestone cards) + "장기 비전" (3 milestone cards)
|
|
127
|
-
• "팀 소개" (4+ members) → "창업진 소개" (2-3 people) + "핵심 팀원" (2-3 people)
|
|
128
|
-
|
|
129
|
-
═══ CONTENT DENSITY — FILL THE SLIDE ═══
|
|
130
|
-
⚠⚠⚠ Each slide is 1920×1080px. Content MUST fill 85-95% of vertical space. EMPTY slides = FAILURE.
|
|
131
|
-
⚠ content_direction MUST contain 6-10 sentences of ACTUAL DATA and details.
|
|
132
|
-
⚠ Each item: title + 3-5 detailed bullets with specific numbers, names, and descriptions.
|
|
133
|
-
⚠ Tables: 4-5 data rows × 3-4 columns for rich comparison. Fill EVERY cell with real data. Column headers MUST be descriptive — NEVER generic "Column 1".
|
|
134
|
-
⚠ Timeline/Roadmap: 3 milestones (MAX 3) with dates, descriptions, and key metrics.
|
|
135
|
-
⚠ Process/Flow: 3-4 steps (MAX 4) — each step with short title + concise description.
|
|
136
|
-
⚠ Data/metrics: 3-4 big numbers with trend indicators AND supporting context text below each.
|
|
137
|
-
⚠ Cards: 3-4 cards, each with a title + 3-4 bullet points + a summary sentence.
|
|
138
|
-
⚠ Donut/Pie charts: include 4-5 segments with labels + a summary box below the chart.
|
|
139
|
-
⚠ The more SPECIFIC DATA you include in content_direction, the better the slide will look.
|
|
140
|
-
|
|
141
|
-
═══ content_direction QUALITY RULES ═══
|
|
142
|
-
⚠ content_direction = REAL CONTENT the viewer will read. NOT layout instructions.
|
|
143
|
-
Include: specific numbers, names, descriptions, bullet point text, table data.
|
|
144
|
-
MUST be DETAILED — 6-10 sentences with specific data. Sparse directions produce empty slides.
|
|
145
|
-
Each item/card/row MUST include: title + 3-4 supporting details with specific data.
|
|
146
|
-
Optionally end with ONE short layout hint (e.g., "Layout: 3-column cards" or "Layout: comparison table").
|
|
147
|
-
|
|
148
|
-
✓ GOOD: "총매출 1,250억원(전년비 15%↑), 영업이익 180억원(14.4%), 순이익 95억원(7.6%). 사업부별: 클라우드 45%(562억), AI솔루션 30%(375억), 컨설팅 25%(312억). 클라우드가 전년비 32% 성장하며 최대 성장 동력. Layout: bar chart"
|
|
149
|
-
✓ GOOD: "3 core services: (1) 클라우드 인프라 — 하이브리드 클라우드 구축, AWS/Azure 호환, 99.9% SLA 보장, 50+ 기업 고객 (2) AI 솔루션 — 자연어처리/영상분석, 진단 정확도 98.2%, 월 100만건 처리 (3) 컨설팅 — 디지털 전환 자문, 평균 ROI 250%, 12개월 효과. Layout: 3-column cards"
|
|
150
|
-
✗ BAD: "클라우드, AI, 컨설팅 3개 서비스" ← Too sparse!
|
|
151
|
-
✗ BAD: "왼쪽에 개요 텍스트, 오른쪽에 카드" ← Layout instruction, NOT content!
|
|
152
|
-
✗ BAD: "#accent_light 배경, #primary 글씨" ← CSS instruction, NOT content!
|
|
83
|
+
═══ content_direction = THE #1 PRIORITY ═══
|
|
84
|
+
⚠⚠⚠ content_direction is the REAL DATA AND TEXT that will appear on the slide.
|
|
85
|
+
⚠ Each content_direction MUST be 6-10 sentences of SPECIFIC DATA:
|
|
86
|
+
- Include: numbers, percentages, names, descriptions, dates, comparisons
|
|
87
|
+
- The MORE specific data you provide, the better the slide will look
|
|
88
|
+
- Each item/section: title + 3-4 supporting details with real numbers
|
|
89
|
+
⚠ content_direction MUST describe ONE focused topic per slide.
|
|
90
|
+
If a topic has 4+ sub-items, SPLIT into 2 slides.
|
|
91
|
+
⚠ NEVER include layout/CSS instructions in content_direction. Just the DATA.
|
|
92
|
+
✓ GOOD: "한국인 유럽 여행객은 연평균 31% 성장. 2022년 180만 명, 2023년 240만 명, 2024년 310만 명. 2027년 시장 규모 7.2조 원 전망. 코로나19 이후 회복세, 유로화 약세, 직항 노선 확대가 수요 증가를 견인하고 있다."
|
|
93
|
+
✓ GOOD: "베스트셀러 1번 상품: 드림 유로피안 클래식 - 프랑스·스위스·이탈리아 3국 10일 코스. 1인 349만원(세금 포함). 4성급 호텔 9박, 매일 조식+7회 식사 포함. 주요 일정: Day 1-3 파리(에펠탑, 루브르, 센 강 유람선), Day 4-5 스위스(융프라우요하 등반, 열차), Day 6-9 이탈리아(로마 콜로세움, 피렌체, 베네치아), Day 10 귀국."
|
|
94
|
+
✗ BAD: "3개 서비스 소개. Layout: cards" ← Too sparse, has layout hint!
|
|
95
|
+
✗ BAD: "왼쪽에 텍스트, 오른쪽에 차트" ← Layout instruction!
|
|
96
|
+
|
|
97
|
+
═══ SLIDE CONTENT VARIETY ═══
|
|
98
|
+
⚠ Each slide's content should naturally suggest a DIFFERENT visual treatment.
|
|
99
|
+
Vary what you write about across slides:
|
|
100
|
+
- Some slides: key metrics/numbers (naturally displayed as large metric spotlights)
|
|
101
|
+
- Some slides: comparison data (naturally shown as tables or side-by-side)
|
|
102
|
+
- Some slides: step-by-step processes (naturally shown as flows)
|
|
103
|
+
- Some slides: category breakdowns (naturally shown as charts or card grids)
|
|
104
|
+
- Some slides: timeline/roadmap items (naturally shown as milestone sequences)
|
|
105
|
+
- Some slides: detailed feature descriptions (naturally shown as rich cards)
|
|
106
|
+
⚠ Don't make every slide a list of items. Mix data-heavy slides with narrative slides.
|
|
107
|
+
⚠ AVOID having 3+ consecutive slides with the same content structure (e.g., all lists of 3-4 items).
|
|
153
108
|
|
|
154
109
|
═══ OVERVIEW / AGENDA SLIDES ═══
|
|
155
|
-
⚠ Overview
|
|
156
|
-
|
|
157
|
-
If 10+ sections, group related topics: "시장 및 경쟁" instead of separate "시장 분석" + "경쟁 분석".
|
|
158
|
-
|
|
159
|
-
═══ LAYOUT VARIETY — MANDATORY ═══
|
|
160
|
-
⚠⚠⚠ You MUST vary the visual approach. No more than 2 slides with the same layout hint.
|
|
161
|
-
Available layouts (use at least 5 different types across the presentation):
|
|
162
|
-
- "2×2 grid": 4 cards in 2 rows × 2 columns (MAX 2 slides)
|
|
163
|
-
- "bar chart": CSS vertical/horizontal bars showing data trends
|
|
164
|
-
- "donut chart": conic-gradient pie/donut with metric labels
|
|
165
|
-
- "comparison table": styled table with 3-4 rows, colored headers
|
|
166
|
-
- "process flow": horizontal step boxes connected by arrows (→)
|
|
167
|
-
- "big numbers": 2-3 large metric spotlights (72-96px) with trend indicators
|
|
168
|
-
- "2-column split": left panel + right content
|
|
169
|
-
- "timeline": 3-4 milestone steps horizontal with dates
|
|
170
|
-
- "progress bars": horizontal bars showing completion percentages
|
|
171
|
-
- "hero stat": one large central metric with supporting context
|
|
172
|
-
⚠ Each content_direction MUST end with a "Layout:" hint specifying the visual type.
|
|
173
|
-
⚠ If your plan has 3+ slides all using "cards/grid" layout, REWRITE to use charts, tables, flows instead.
|
|
174
|
-
⚠ A 10-slide deck MUST have: at least 2 chart/data-viz slides, at least 1 table slide, at least 1 process/flow slide.
|
|
175
|
-
⚠ NEVER plan a presentation where every slide is cards — that is LAZY and BORING.
|
|
110
|
+
⚠ Overview slides: MAXIMUM 5 items, numbered list with short titles + 1-line descriptions.
|
|
111
|
+
If 10+ sections, group related topics.
|
|
176
112
|
|
|
177
113
|
═══ HARD RULES ═══
|
|
178
|
-
⚠ First slide MUST be type "title"
|
|
179
|
-
⚠ Last slide MUST be type "closing" with "감사합니다"/"Thank You" as title
|
|
180
114
|
⚠ ALL titles and content_direction MUST be in the SAME language as the user's instruction
|
|
181
|
-
⚠ content_direction
|
|
182
|
-
⚠
|
|
183
|
-
⚠
|
|
184
|
-
⚠
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Output ONLY the JSON object. No preamble, no markdown fences, no explanation.`;
|
|
115
|
+
⚠ content_direction with NO real data = FAILURE
|
|
116
|
+
⚠ HARD MAXIMUM: 13 slides. Slides beyond 13 are DISCARDED.
|
|
117
|
+
⚠ NEVER use "스크린샷", "screenshot", "이미지", "사진", "placeholder" in content_direction
|
|
118
|
+
⚠ Do NOT create a separate "연락처" slide — closing handles this.
|
|
119
|
+
|
|
120
|
+
Output ONLY the JSON object.`;
|
|
189
121
|
export function extractLayoutHint(contentDirection) {
|
|
190
122
|
const match = contentDirection.match(/Layout:\s*(.+?)$/im);
|
|
191
123
|
if (!match)
|
|
@@ -324,8 +256,8 @@ ${layoutCss}
|
|
|
324
256
|
• Complete HTML: <!DOCTYPE html> through </html>. ALL styling in <style>.
|
|
325
257
|
• NO <img>, NO external resources, NO JavaScript, NO external fonts.
|
|
326
258
|
• System fonts ONLY. CSS for visuals: gradients, shapes, shadows, borders.
|
|
327
|
-
• Title:
|
|
328
|
-
• Content fills 85-95% of 1080px height. Empty space = FAILURE.
|
|
259
|
+
• Title: 42-48px bold. Body: 26-32px. MINIMUM any text: 24px. If text needs to shrink below 24px, remove content instead.
|
|
260
|
+
• Content fills 85-95% of 1080px height. Empty space = FAILURE. MAX ~1000 chars of visible text.
|
|
329
261
|
• NEVER use justify-content:center on .content (creates dead space). Use stretch/space-evenly.
|
|
330
262
|
• Use gradients, box-shadow (0 4px 20px rgba(0,0,0,0.06)), border-radius (12-20px).
|
|
331
263
|
• Generate REAL professional content. No placeholders. Specific numbers and data.
|
|
@@ -334,27 +266,119 @@ ${layoutCss}
|
|
|
334
266
|
|
|
335
267
|
Output the complete HTML now.`;
|
|
336
268
|
}
|
|
337
|
-
export function
|
|
269
|
+
export function buildFreeHtmlPrompt(title, contentDirection, design, slideIndex, totalSlides, language) {
|
|
270
|
+
const langRule = language === 'ko'
|
|
271
|
+
? 'ALL visible text MUST be in Korean. Write naturally in Korean. Never use Chinese characters (漢字).'
|
|
272
|
+
: 'ALL visible text MUST be in English.';
|
|
273
|
+
const variants = [
|
|
274
|
+
`Background: ${design.background_color}. Elements use white background with box-shadow.`,
|
|
275
|
+
`Top accent bar (height:4px, linear-gradient(90deg, ${design.accent_color}, ${design.primary_color})). Background: ${design.background_color}.`,
|
|
276
|
+
`Subtle gradient background: linear-gradient(150deg, ${design.background_color} 0%, ${design.accent_light}40 100%). Stronger shadows.`,
|
|
277
|
+
];
|
|
278
|
+
const styleGuide = variants[slideIndex % variants.length];
|
|
279
|
+
return `You are a world-class presentation designer. Create ONE slide as a complete HTML page.
|
|
280
|
+
Output ONLY the complete HTML (<!DOCTYPE html> to </html>). No explanation, no markdown fences.
|
|
281
|
+
|
|
282
|
+
═══ SLIDE ${slideIndex + 1} OF ${totalSlides} ═══
|
|
283
|
+
Title: "${title}"
|
|
284
|
+
|
|
285
|
+
═══ CONTENT TO VISUALIZE ═══
|
|
286
|
+
${contentDirection}
|
|
287
|
+
|
|
288
|
+
═══ DESIGN SYSTEM ═══
|
|
289
|
+
Primary: ${design.primary_color} | Accent: ${design.accent_color} | BG: ${design.background_color}
|
|
290
|
+
Text: ${design.text_color} | Light: ${design.accent_light} | Gradient: ${design.gradient_end}
|
|
291
|
+
Title Font: ${design.font_title} | Body Font: ${design.font_body} | Mood: ${design.mood}
|
|
292
|
+
|
|
293
|
+
═══ STYLE FOR THIS SLIDE ═══
|
|
294
|
+
${styleGuide}
|
|
295
|
+
|
|
296
|
+
═══ YOUR CREATIVE MISSION ═══
|
|
297
|
+
Design the BEST possible visual layout for THIS specific content.
|
|
298
|
+
Think like a professional designer: what visual structure best communicates this information?
|
|
299
|
+
|
|
300
|
+
You have FULL CREATIVE FREEDOM. Choose the most appropriate visual approach:
|
|
301
|
+
• Card grids (2×2, 1×3, 1×4) with icons, stats, bullet details
|
|
302
|
+
• Styled data tables (<table>) with colored headers and alternating rows
|
|
303
|
+
• CSS bar charts: vertical bars using flex-end alignment + height percentages
|
|
304
|
+
• Donut/pie charts: conic-gradient on border-radius:50% elements
|
|
305
|
+
• Horizontal progress bars with labeled tracks and percentage fills
|
|
306
|
+
• Step-by-step process flows with numbered circles + arrow (→) connectors
|
|
307
|
+
• Timeline layouts with dated milestone cards in a horizontal row
|
|
308
|
+
• Dashboard panels: 2-3 large metric spotlights (80-100px numbers)
|
|
309
|
+
• 2-column split: left summary/data + right detailed content or bullets
|
|
310
|
+
• Feature showcases with emoji/icon badges
|
|
311
|
+
• Itinerary/schedule: day-by-day breakdown with locations and activities
|
|
312
|
+
• Ranking/leaderboard: ordered items with visual indicators
|
|
313
|
+
• Comparison matrices: side-by-side analysis with visual scoring
|
|
314
|
+
• SWOT or quadrant grids for strategic analysis
|
|
315
|
+
• Package/pricing comparison: side-by-side product cards with highlights
|
|
316
|
+
• Any OTHER CSS-only visual that serves the content perfectly
|
|
317
|
+
|
|
318
|
+
⚠ Choose the layout that BEST fits THIS content. A travel itinerary needs a schedule layout, not a pie chart. Financial data needs charts/tables, not icon grids. Be creative and appropriate.
|
|
319
|
+
|
|
320
|
+
═══ MANDATORY CSS BOILERPLATE (copy into <style>) ═══
|
|
321
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
322
|
+
html, body { width:1920px; height:1080px; overflow:hidden; font-family:"${design.font_body}","${design.font_title}","Segoe UI","Malgun Gothic",Arial,sans-serif; word-break:keep-all; overflow-wrap:break-word; }
|
|
323
|
+
body { display:flex; flex-direction:column; padding:60px 80px; height:1080px; background:${design.background_color}; color:${design.text_color}; font-size:26px; }
|
|
324
|
+
|
|
325
|
+
═══ HTML STRUCTURE ═══
|
|
326
|
+
body has EXACTLY 2 direct children:
|
|
327
|
+
1. .slide-title (flex:0 0 auto) — h1 with title + accent bar
|
|
328
|
+
.slide-title h1 { font-size:48px; font-weight:700; color:${design.primary_color}; font-family:"${design.font_title}","Segoe UI",sans-serif; }
|
|
329
|
+
Below h1: <div style="width:80px;height:3px;background:${design.accent_color};border-radius:2px;margin-top:12px"></div>
|
|
330
|
+
2. .content (flex:1) — stretches to fill ALL remaining vertical space
|
|
331
|
+
⚠ .content class name is REQUIRED.
|
|
332
|
+
⚠ ALL layout elements MUST be DIRECT children of .content — no wrapper divs.
|
|
333
|
+
|
|
334
|
+
═══ DESIGN RULES ═══
|
|
335
|
+
• ${langRule}
|
|
336
|
+
• Complete HTML: <!DOCTYPE html> through </html>. ALL styling in <style>.
|
|
337
|
+
• NO <img>, NO external resources, NO JavaScript, NO external fonts.
|
|
338
|
+
• System fonts ONLY. CSS for visuals: gradients, shapes, shadows, borders.
|
|
339
|
+
• Title: 42-48px bold. Body: 26-32px. MINIMUM any visible text: 24px (except page numbers at 12px).
|
|
340
|
+
• ⚠ If you need to shrink text below 24px, you have TOO MUCH content. Remove sections or shorten text instead.
|
|
341
|
+
• ⚠ ABSOLUTELY NO OVERFLOW — content MUST fit within 1080px total height. If you include too much content, the bottom will be CLIPPED and invisible. It is better to have 15% empty space than 1px of clipping. Limit yourself to 4-5 major content elements max.
|
|
342
|
+
• The available height for .content is approximately 900px (1080 - 60px top padding - 60px bottom padding - ~60px title). Design within this constraint.
|
|
343
|
+
• ⚠ MAXIMUM visible text: ~1000 characters. More than this WILL cause overflow. Be concise — short labels, brief descriptions.
|
|
344
|
+
• NEVER use position:absolute for layout (ok for page numbers). Use flexbox/grid.
|
|
345
|
+
• NEVER use justify-content:center on .content — it creates dead space. Use stretch/space-evenly.
|
|
346
|
+
• Use gradients, box-shadow (0 4px 20px rgba(0,0,0,0.06)), border-radius (12-20px).
|
|
347
|
+
• Cards/elements: white (#fff) background with subtle shadow on light slides.
|
|
348
|
+
• Table headers: dark background (${design.primary_color}) with white text.
|
|
349
|
+
• Generate REAL professional content from the direction. No placeholders.
|
|
350
|
+
• ⚠ NEVER use bracket placeholders like [Team Name], [Email], [YYYY], [Author]. Instead, INVENT realistic fictional content for ALL fields — names, emails, dates, numbers, etc.
|
|
351
|
+
• If user specified a year, USE THAT YEAR. Default to ${new Date().getFullYear()} only when no year given.
|
|
352
|
+
• Page number: bottom-right "${slideIndex + 1}" (12px, opacity 0.4, position:absolute ok for this).
|
|
353
|
+
|
|
354
|
+
Output the complete HTML now.`;
|
|
355
|
+
}
|
|
356
|
+
export function validateSlideHtml(html, _layoutType) {
|
|
338
357
|
if (!html.includes('<!DOCTYPE') && !html.includes('<!doctype')) {
|
|
339
358
|
return { pass: false, feedback: 'Missing <!DOCTYPE html> declaration. Start with <!DOCTYPE html><html>.' };
|
|
340
359
|
}
|
|
341
360
|
if (!html.includes('<html') || !html.includes('</html>')) {
|
|
342
361
|
return { pass: false, feedback: 'Missing <html> or </html> tags. Output must be a complete HTML document.' };
|
|
343
362
|
}
|
|
344
|
-
const layoutFeedback = checkLayoutCompliance(html, layoutType);
|
|
345
|
-
if (layoutFeedback) {
|
|
346
|
-
return { pass: false, feedback: layoutFeedback };
|
|
347
|
-
}
|
|
348
363
|
if (/<img\s/i.test(html)) {
|
|
349
364
|
return { pass: false, feedback: 'Forbidden: <img> tags are not allowed. Use CSS gradients, shapes, and backgrounds instead.' };
|
|
350
365
|
}
|
|
351
|
-
if (
|
|
352
|
-
return { pass: false, feedback: 'Forbidden:
|
|
366
|
+
if (/<script[\s>]/i.test(html)) {
|
|
367
|
+
return { pass: false, feedback: 'Forbidden: <script> tags are not allowed. This is a static slide — no JavaScript.' };
|
|
368
|
+
}
|
|
369
|
+
const absCount = (html.match(/position\s*:\s*absolute/gi) || []).length;
|
|
370
|
+
if (absCount > 6) {
|
|
371
|
+
return { pass: false, feedback: `Too many position:absolute (${absCount}). Use flexbox/grid for main layout.` };
|
|
372
|
+
}
|
|
373
|
+
const externalUrls = html.match(/url\(\s*['"]?https?:\/\//gi) || [];
|
|
374
|
+
if (externalUrls.length > 0) {
|
|
375
|
+
return { pass: false, feedback: 'Forbidden: External URLs detected in CSS url(). No external resources allowed.' };
|
|
353
376
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
377
|
+
const scaleMatches = html.match(/transform\s*:[^;]*scale\(\s*([\d.]+)/gi) || [];
|
|
378
|
+
for (const m of scaleMatches) {
|
|
379
|
+
const val = parseFloat(m.replace(/.*scale\(\s*/i, ''));
|
|
380
|
+
if (val > 0 && val < 0.9) {
|
|
381
|
+
return { pass: false, feedback: `Forbidden: transform:scale(${val}) shrinks content. Use full 1920×1080 layout without scaling.` };
|
|
358
382
|
}
|
|
359
383
|
}
|
|
360
384
|
const placeholderPatterns = [
|
|
@@ -364,33 +388,40 @@ export function validateSlideHtml(html, layoutType) {
|
|
|
364
388
|
/Display value \(e\.g\./i,
|
|
365
389
|
/1-2 sentence key insight/i,
|
|
366
390
|
/Category name/i,
|
|
367
|
-
/Another detail/i,
|
|
368
|
-
/Third point/i,
|
|
369
|
-
/Fourth point/i,
|
|
370
|
-
/Brief context/i,
|
|
371
391
|
/Segment name/i,
|
|
392
|
+
/Lorem ipsum/i,
|
|
393
|
+
/\[placeholder\]/i,
|
|
394
|
+
/\[내용\]/i,
|
|
395
|
+
/\[.{2,20}을\s*입력/i,
|
|
396
|
+
/\[YYYY/i,
|
|
397
|
+
/\[이메일/i,
|
|
398
|
+
/\[작성자/i,
|
|
399
|
+
/\[직급/i,
|
|
400
|
+
/\[NNNN\]/i,
|
|
401
|
+
/\[MM월/i,
|
|
402
|
+
/\[담당자\s*명\]/i,
|
|
372
403
|
];
|
|
373
404
|
for (const pattern of placeholderPatterns) {
|
|
374
405
|
if (pattern.test(html)) {
|
|
375
|
-
return { pass: false, feedback: `Placeholder text detected: "${pattern.source}". Generate REAL content
|
|
406
|
+
return { pass: false, feedback: `Placeholder text detected: "${pattern.source}". Generate REAL content.` };
|
|
376
407
|
}
|
|
377
408
|
}
|
|
378
409
|
const textElements = (html.match(/<(p|li|td|th|span|div|h[1-6])[^>]*>[^<]{2,}/gi) || []).length;
|
|
379
410
|
if (textElements < 5) {
|
|
380
|
-
return { pass: false, feedback: `Low content density: only ${textElements} text elements
|
|
411
|
+
return { pass: false, feedback: `Low content density: only ${textElements} text elements. Need at least 5.` };
|
|
381
412
|
}
|
|
382
413
|
const visibleText = html.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
383
414
|
.replace(/<[^>]+>/g, ' ')
|
|
384
415
|
.replace(/\s+/g, ' ')
|
|
385
416
|
.trim();
|
|
386
|
-
if (visibleText.length >
|
|
387
|
-
return { pass: false, feedback: `Content overflow risk: ${visibleText.length} chars of visible text. Reduce to under
|
|
417
|
+
if (visibleText.length > 1350) {
|
|
418
|
+
return { pass: false, feedback: `Content overflow risk: ${visibleText.length} chars of visible text. Reduce to under 1350 chars. Remove 1-2 sections or shorten text to prevent bottom clipping.` };
|
|
388
419
|
}
|
|
389
420
|
const smallFonts = html.match(/font-size\s*:\s*(\d+)px/gi) || [];
|
|
390
421
|
for (const match of smallFonts) {
|
|
391
422
|
const size = parseInt(match.replace(/[^0-9]/g, ''), 10);
|
|
392
|
-
if (size > 0 && size <
|
|
393
|
-
return { pass: false, feedback: `Font too small: found font-size:${size}px. Minimum allowed is
|
|
423
|
+
if (size > 0 && size < 22 && size > 13) {
|
|
424
|
+
return { pass: false, feedback: `Font too small: found font-size:${size}px. Minimum allowed is 22px (except 12-13px for page numbers). Increase font size or reduce content.` };
|
|
394
425
|
}
|
|
395
426
|
}
|
|
396
427
|
return { pass: true, feedback: '' };
|
|
@@ -480,7 +511,7 @@ MANDATORY STRUCTURE (body has EXACTLY 2 direct children):
|
|
|
480
511
|
${getLayoutSpecificCss(layoutType, design)}
|
|
481
512
|
|
|
482
513
|
═══ DESIGN RULES ═══
|
|
483
|
-
1. Title:
|
|
514
|
+
1. Title: 42-48px bold. Body: 26-32px. MINIMUM any visible text: 24px. NEVER use font-size below 24px. If content doesn't fit at 24px, REDUCE ITEM COUNT instead of shrinking text. Card descriptions: 26-30px. Card titles: 32-40px. Labels/captions: 24px minimum. body { font-size: 26px; } is MANDATORY. MAX visible text ~1000 characters.
|
|
484
515
|
2. Use gradients, box-shadow (0 4px 20px rgba(0,0,0,0.06)), border-radius (12-20px)
|
|
485
516
|
3. Follow the REQUIRED LAYOUT specified above exactly. Do NOT substitute a different layout type.
|
|
486
517
|
4. LAYOUT LIMITS: MAX 4 cards (2×2 grid), MAX 2 cards per row. Tables: 5-8 rows for rich data. Timeline: 4-5 steps.
|
|
@@ -531,64 +562,64 @@ export function buildContentFillJsonPrompt(slideTitle, contentDirection, layoutT
|
|
|
531
562
|
const schemas = {
|
|
532
563
|
cards: `{
|
|
533
564
|
"cards": [
|
|
534
|
-
{ "icon": "single emoji", "title": "
|
|
565
|
+
{ "icon": "single emoji", "title": "Short card title (2-5 words)", "bullets": ["Detail with data", "Another detail", "Third point"], "stat": "Key metric with number" }
|
|
535
566
|
]
|
|
536
567
|
}
|
|
537
|
-
RULES: 3-4 cards. Each: icon + title +
|
|
568
|
+
RULES: 3-4 cards. Each: icon + title + 3 bullets (MAX 3, keep each under 25 chars) + stat.`,
|
|
538
569
|
bar_chart: `{
|
|
539
570
|
"bars": [
|
|
540
|
-
{ "label": "Category name", "value": "
|
|
571
|
+
{ "label": "Category name (under 15 chars)", "value": "Number with unit", "height": 85 }
|
|
541
572
|
],
|
|
542
573
|
"insight": "1-2 sentence key insight about the data"
|
|
543
574
|
}
|
|
544
|
-
RULES: 4-
|
|
575
|
+
RULES: 4-5 bars (MAX 5). height: 10-90 (tallest=85, others proportional). Values must include units.`,
|
|
545
576
|
donut_chart: `{
|
|
546
577
|
"segments": [
|
|
547
|
-
{ "label": "Segment name", "value": "
|
|
578
|
+
{ "label": "Segment name", "value": "Number with unit", "percent": 45 }
|
|
548
579
|
],
|
|
549
|
-
"centerText": "
|
|
580
|
+
"centerText": "Total or summary label",
|
|
550
581
|
"summary": "1-2 sentence summary"
|
|
551
582
|
}
|
|
552
583
|
RULES: 3-5 segments. Percents MUST sum to exactly 100.`,
|
|
553
584
|
table: `{
|
|
554
|
-
"headers": ["
|
|
555
|
-
"rows": [["data", "data", "data", "data"]],
|
|
585
|
+
"headers": ["Descriptive column name", "Another column", "Third column", "Fourth column"],
|
|
586
|
+
"rows": [["real data", "real data", "real data", "real data"]],
|
|
556
587
|
"highlightRow": 0,
|
|
557
588
|
"summary": "1-2 sentence summary"
|
|
558
589
|
}
|
|
559
|
-
RULES: 3-4 columns. 4-5 rows. ALL cells real data. highlightRow: 0-indexed or null.
|
|
560
|
-
⚠ headers MUST be meaningful column names
|
|
590
|
+
RULES: 3-4 columns. 4-5 rows. ALL cells must contain real data. highlightRow: 0-indexed or null.
|
|
591
|
+
⚠ headers MUST be meaningful column names — NEVER use generic "Column 1", "Column 2".`,
|
|
561
592
|
process_flow: `{
|
|
562
593
|
"steps": [
|
|
563
|
-
{ "title": "Step name (2-4 words)", "desc": "2-3 sentence description with details", "detail": "Duration or metric" }
|
|
594
|
+
{ "title": "Step name (2-4 words)", "desc": "2-3 sentence description with specific details", "detail": "Duration or key metric" }
|
|
564
595
|
]
|
|
565
596
|
}
|
|
566
597
|
RULES: 3-4 steps (MAX 4). Each: title + detailed description + time/metric.`,
|
|
567
598
|
big_numbers: `{
|
|
568
599
|
"metrics": [
|
|
569
|
-
{ "value": "
|
|
600
|
+
{ "value": "Number only", "unit": "Unit text", "label": "Metric name", "desc": "1-2 sentence context", "trend": "▲ or ▼ + percentage", "positive": true }
|
|
570
601
|
]
|
|
571
602
|
}
|
|
572
|
-
RULES: 2-3 metrics. value=number only, unit=separate. trend: ▲/▼ + percentage.`,
|
|
603
|
+
RULES: 2-3 metrics. value=number only, unit=separate field. trend: ▲/▼ + percentage.`,
|
|
573
604
|
timeline: `{
|
|
574
605
|
"milestones": [
|
|
575
|
-
{ "date": "
|
|
606
|
+
{ "date": "Date or period", "title": "Milestone name", "desc": "2-3 sentence description", "kpi": "Target metric" }
|
|
576
607
|
]
|
|
577
608
|
}
|
|
578
609
|
RULES: 3 milestones (MAX 3). Each with date, description, and KPI target.`,
|
|
579
610
|
progress_bars: `{
|
|
580
611
|
"bars": [
|
|
581
|
-
{ "label": "Category name", "value": "Display
|
|
612
|
+
{ "label": "Category name", "value": "Display value with unit", "percent": 75, "detail": "Brief context" }
|
|
582
613
|
]
|
|
583
614
|
}
|
|
584
615
|
RULES: 4-6 bars. percent: 5-100. Include context detail for each.`,
|
|
585
616
|
hero_stat: `{
|
|
586
|
-
"number": "
|
|
587
|
-
"unit": "
|
|
588
|
-
"label": "
|
|
617
|
+
"number": "The big number",
|
|
618
|
+
"unit": "Unit or symbol",
|
|
619
|
+
"label": "What this number measures",
|
|
589
620
|
"context": "2-3 sentence context explaining significance",
|
|
590
621
|
"supporting": [
|
|
591
|
-
{ "value": "
|
|
622
|
+
{ "value": "Number with unit", "label": "Supporting metric name" }
|
|
592
623
|
]
|
|
593
624
|
}
|
|
594
625
|
RULES: 1 hero number + context + 2-3 supporting metrics.`,
|
|
@@ -600,7 +631,7 @@ RULES: 1 hero number + context + 2-3 supporting metrics.`,
|
|
|
600
631
|
"rightTitle": "Right column heading",
|
|
601
632
|
"rightBullets": ["Detailed bullet point with data"]
|
|
602
633
|
}
|
|
603
|
-
RULES: Left: 3-4 key-value items. Right: 3-4 detailed bullets. Keep each bullet under 30 chars.`,
|
|
634
|
+
RULES: Left: 3-4 key-value items with REAL data. Right: 3-4 detailed bullets with REAL data. Keep each bullet under 30 chars.`,
|
|
604
635
|
};
|
|
605
636
|
return `Extract content from the direction below and output ONLY valid JSON.
|
|
606
637
|
Do NOT output markdown fences, explanations, or anything besides the JSON object.
|
|
@@ -646,6 +677,19 @@ export function parseContentFillJson(raw, layoutType) {
|
|
|
646
677
|
}
|
|
647
678
|
if (!parsed)
|
|
648
679
|
return null;
|
|
680
|
+
const jsonStr = JSON.stringify(parsed);
|
|
681
|
+
const ellipsisMatches = jsonStr.match(/"\.\.\."|\u2026/g) || [];
|
|
682
|
+
const totalStringValues = jsonStr.match(/"[^"]+"/g) || [];
|
|
683
|
+
if (ellipsisMatches.length > 0 && totalStringValues.length > 0) {
|
|
684
|
+
const ellipsisRatio = ellipsisMatches.length / totalStringValues.length;
|
|
685
|
+
if (ellipsisRatio > 0.3) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const stringLiteralMatches = jsonStr.match(/"string"/g) || [];
|
|
690
|
+
if (stringLiteralMatches.length >= 2) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
649
693
|
switch (layoutType) {
|
|
650
694
|
case 'cards':
|
|
651
695
|
if (!Array.isArray(parsed['cards']) || parsed['cards'].length === 0)
|
|
@@ -686,8 +730,15 @@ export function parseContentFillJson(raw, layoutType) {
|
|
|
686
730
|
case 'two_col_split':
|
|
687
731
|
if (!parsed['leftTitle'] && !parsed['rightTitle'])
|
|
688
732
|
return null;
|
|
733
|
+
if (!Array.isArray(parsed['leftItems']) || parsed['leftItems'].length === 0)
|
|
734
|
+
return null;
|
|
735
|
+
if (!Array.isArray(parsed['rightBullets']) || parsed['rightBullets'].length === 0)
|
|
736
|
+
return null;
|
|
689
737
|
break;
|
|
690
738
|
}
|
|
739
|
+
const allText = JSON.stringify(parsed).replace(/[{}\[\]",:]/g, '').trim();
|
|
740
|
+
if (allText.length < 50)
|
|
741
|
+
return null;
|
|
691
742
|
return parsed;
|
|
692
743
|
}
|
|
693
744
|
function getCardTextColor(design) {
|
|
@@ -738,21 +789,21 @@ function buildCardsContent(design, data) {
|
|
|
738
789
|
const cols = n <= 3 ? `repeat(${n},1fr)` : '1fr 1fr';
|
|
739
790
|
const is2x2 = n === 4;
|
|
740
791
|
const gridExtra = is2x2 ? 'grid-template-rows:1fr 1fr' : 'grid-template-rows:1fr';
|
|
741
|
-
const cardPad = is2x2 ? '28px 24px' : '36px
|
|
742
|
-
const cardGap = is2x2 ? '10px' : '
|
|
743
|
-
const h2Size = is2x2 ? '32px' : '
|
|
744
|
-
const liSize = is2x2 ? '26px' : '
|
|
745
|
-
const liMargin = is2x2 ? '8px' : '
|
|
792
|
+
const cardPad = is2x2 ? '28px 24px' : '48px 36px';
|
|
793
|
+
const cardGap = is2x2 ? '10px' : '16px';
|
|
794
|
+
const h2Size = is2x2 ? '32px' : '40px';
|
|
795
|
+
const liSize = is2x2 ? '26px' : '30px';
|
|
796
|
+
const liMargin = is2x2 ? '8px' : '14px';
|
|
746
797
|
const cardText = getCardTextColor(design);
|
|
747
798
|
const css = `.content{flex:1;display:grid;grid-template-columns:${cols};${gridExtra};gap:24px}
|
|
748
799
|
.card{background:#fff;border-radius:16px;padding:${cardPad};box-shadow:0 4px 20px rgba(0,0,0,0.06);display:flex;flex-direction:column;gap:${cardGap};overflow:hidden;color:${cardText}}
|
|
749
800
|
.card-icon{font-size:36px;line-height:1}
|
|
750
801
|
.card h2{font-size:${h2Size};font-weight:700;color:${design.primary_color}}
|
|
751
|
-
.card ul{list-style:none;flex:1}
|
|
802
|
+
.card ul{list-style:none;flex:1;display:flex;flex-direction:column;justify-content:center}
|
|
752
803
|
.card li{margin-bottom:${liMargin};padding-left:22px;position:relative;font-size:${liSize};line-height:1.4}
|
|
753
804
|
.card li::before{content:"•";color:${design.accent_color};position:absolute;left:0;font-weight:bold}
|
|
754
805
|
.card-stat{margin-top:auto;padding-top:12px;border-top:2px solid ${design.accent_light};font-size:24px;font-weight:600;color:${design.accent_color}}`;
|
|
755
|
-
const maxBullets =
|
|
806
|
+
const maxBullets = 3;
|
|
756
807
|
const cards = (data.cards || []).slice(0, 4).map(c => `
|
|
757
808
|
<div class="card">
|
|
758
809
|
<div class="card-icon">${c.icon || '📌'}</div>
|
|
@@ -773,7 +824,7 @@ function buildBarChartContent(design, data) {
|
|
|
773
824
|
.bar-label{font-size:24px;font-weight:600;color:${design.text_color};text-align:center;min-width:120px;word-break:keep-all;padding:12px 0;flex-shrink:0}
|
|
774
825
|
.insight-row{padding:20px 60px;background:${design.accent_light};border-radius:12px;margin-top:16px;font-size:26px;color:${cardText};line-height:1.5;flex-shrink:0}`;
|
|
775
826
|
const maxH = Math.max(...(data.bars || []).map(b => b.height || 50), 1);
|
|
776
|
-
const bars = (data.bars || []).slice(0,
|
|
827
|
+
const bars = (data.bars || []).slice(0, 5).map(b => {
|
|
777
828
|
const normalized = Math.max(8, Math.round(((b.height || 50) / maxH) * 85));
|
|
778
829
|
return `
|
|
779
830
|
<div class="bar-group">
|
|
@@ -802,16 +853,16 @@ function buildDonutChartContent(design, data) {
|
|
|
802
853
|
cumPct += pct;
|
|
803
854
|
return `${segColors[i % segColors.length]} ${start.toFixed(1)}% ${cumPct.toFixed(1)}%`;
|
|
804
855
|
}).join(',');
|
|
805
|
-
const css = `.content{flex:1;display:grid;grid-template-columns:1.2fr 1fr;gap:40px;align-items:center;align-content:center}
|
|
856
|
+
const css = `.content{flex:1;display:grid;grid-template-columns:1.2fr 1fr;gap:40px;align-items:center;align-content:center;padding:0}
|
|
806
857
|
.donut-wrap{display:flex;justify-content:center;align-items:center}
|
|
807
|
-
.donut{width:
|
|
808
|
-
.donut-hole{width:
|
|
809
|
-
.donut-center{font-size:
|
|
810
|
-
.legend{display:flex;flex-direction:column;gap:
|
|
811
|
-
.legend-item{display:flex;align-items:center;gap:16px;font-size:28px;padding:
|
|
858
|
+
.donut{width:600px;height:600px;border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 32px rgba(0,0,0,0.08)}
|
|
859
|
+
.donut-hole{width:290px;height:290px;border-radius:50%;background:${design.background_color};display:flex;align-items:center;justify-content:center;flex-direction:column;gap:4px}
|
|
860
|
+
.donut-center{font-size:34px;font-weight:800;color:${design.primary_color};text-align:center;line-height:1.3}
|
|
861
|
+
.legend{display:flex;flex-direction:column;gap:24px}
|
|
862
|
+
.legend-item{display:flex;align-items:center;gap:16px;font-size:28px;padding:14px 18px;background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,0.04);color:${getCardTextColor(design)}}
|
|
812
863
|
.legend-dot{width:24px;height:24px;border-radius:50%;flex-shrink:0}
|
|
813
864
|
.legend-value{font-weight:700;color:${design.primary_color};margin-left:auto;white-space:nowrap}
|
|
814
|
-
.chart-summary{
|
|
865
|
+
.chart-summary{padding:16px 20px;background:${design.accent_light};border-radius:12px;font-size:24px;color:${getCardTextColor(design)};line-height:1.5;margin-top:8px}`;
|
|
815
866
|
const legendHtml = segs.map((s, i) => `
|
|
816
867
|
<div class="legend-item">
|
|
817
868
|
<div class="legend-dot" style="background:${segColors[i % segColors.length]}"></div>
|
|
@@ -826,26 +877,27 @@ function buildDonutChartContent(design, data) {
|
|
|
826
877
|
<div class="donut-hole"><div class="donut-center">${escapeHtmlTemplate(data.centerText || '')}</div></div>
|
|
827
878
|
</div>
|
|
828
879
|
</div>
|
|
829
|
-
<div class="legend">${legendHtml}
|
|
830
|
-
|
|
880
|
+
<div class="legend">${legendHtml}
|
|
881
|
+
${data.summary ? `<div class="chart-summary">${escapeHtmlTemplate(data.summary)}</div>` : ''}
|
|
882
|
+
</div>
|
|
831
883
|
</div>`,
|
|
832
884
|
};
|
|
833
885
|
}
|
|
834
886
|
function buildTableContent(design, data) {
|
|
835
887
|
const cardText = getCardTextColor(design);
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
.data-table
|
|
839
|
-
.data-table
|
|
888
|
+
const maxCols = Math.min((data.headers || []).length, 4);
|
|
889
|
+
const css = `.content{flex:1;display:flex;flex-direction:column;justify-content:center}
|
|
890
|
+
.data-table{width:100%;border-collapse:collapse;background:#fff;box-shadow:0 4px 12px rgba(0,0,0,0.05);border-radius:12px;overflow:hidden;table-layout:fixed}
|
|
891
|
+
.data-table th{background:${design.primary_color};color:#fff;font-size:28px;text-align:left;padding:22px 28px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
892
|
+
.data-table td{font-size:28px;color:${cardText};padding:22px 28px;border-bottom:1px solid ${design.accent_light};overflow:hidden;word-break:break-word}
|
|
840
893
|
.data-table tr:last-child td{border-bottom:none}
|
|
841
894
|
.data-table tr:nth-child(even){background:${design.accent_light}20}
|
|
842
895
|
.data-table tr.highlight td{background:${design.accent_color}15;font-weight:600}
|
|
843
|
-
.table-summary{margin-top:
|
|
844
|
-
const headers = (data.headers || []).slice(0,
|
|
845
|
-
const maxCols = Math.min((data.headers || []).length, 4);
|
|
896
|
+
.table-summary{margin-top:20px;padding:16px 24px;background:${design.accent_light};border-radius:12px;font-size:26px;color:${cardText};line-height:1.5}`;
|
|
897
|
+
const headers = (data.headers || []).slice(0, maxCols).map(h => `<th>${escapeHtmlTemplate(String(h || '').slice(0, 25))}</th>`).join('');
|
|
846
898
|
const rows = (data.rows || []).slice(0, 5).map((row, ri) => {
|
|
847
899
|
const cls = ri === data.highlightRow ? ' class="highlight"' : '';
|
|
848
|
-
const cells = (row || []).slice(0, maxCols).map(c => `<td>${escapeHtmlTemplate(String(c || ''))}</td>`).join('');
|
|
900
|
+
const cells = (row || []).slice(0, maxCols).map(c => `<td>${escapeHtmlTemplate(String(c || '').slice(0, 50))}</td>`).join('');
|
|
849
901
|
return `<tr${cls}>${cells}</tr>`;
|
|
850
902
|
}).join('\n');
|
|
851
903
|
return {
|
|
@@ -863,19 +915,21 @@ function buildProcessFlowContent(design, data) {
|
|
|
863
915
|
const descSz = n <= 3 ? '28px' : '26px';
|
|
864
916
|
const detailSz = n <= 3 ? '28px' : '26px';
|
|
865
917
|
const css = `.content{flex:1;display:flex;align-items:stretch;gap:0}
|
|
866
|
-
.step{flex:1;display:flex;flex-direction:column;align-items:center;padding:36px 24px;background:#fff;border-radius:16px;text-align:center;gap:16px;box-shadow:0 4px 20px rgba(0,0,0,0.06)}
|
|
918
|
+
.step{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:36px 24px;background:#fff;border-radius:16px;text-align:center;gap:16px;box-shadow:0 4px 20px rgba(0,0,0,0.06)}
|
|
867
919
|
.step-num{width:52px;height:52px;border-radius:50%;background:linear-gradient(135deg,${design.primary_color},${design.gradient_end});color:#fff;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:700;flex-shrink:0}
|
|
868
920
|
.step-title{font-size:${titleSz};font-weight:700;color:${design.primary_color}}
|
|
869
|
-
.step-desc{font-size:${descSz};color:${cardText};line-height:1.5
|
|
870
|
-
.step-detail{font-size:${detailSz};color:${design.accent_color};font-weight:600;padding-top:14px;border-top:2px solid ${design.accent_color}40
|
|
921
|
+
.step-desc{font-size:${descSz};color:${cardText};line-height:1.5}
|
|
922
|
+
.step-detail{font-size:${detailSz};color:${design.accent_color};font-weight:600;padding-top:14px;border-top:2px solid ${design.accent_color}40}
|
|
871
923
|
.arrow{width:48px;display:flex;align-items:center;justify-content:center;font-size:40px;color:${design.accent_color};flex-shrink:0}`;
|
|
872
924
|
const steps = (data.steps || []).slice(0, 4);
|
|
925
|
+
const maxDescLen = n <= 3 ? 120 : 80;
|
|
873
926
|
const stepsHtml = steps.map((s, i) => {
|
|
927
|
+
const desc = (s.desc || '').slice(0, maxDescLen);
|
|
874
928
|
const stepDiv = `
|
|
875
929
|
<div class="step">
|
|
876
930
|
<div class="step-num">${i + 1}</div>
|
|
877
931
|
<div class="step-title">${escapeHtmlTemplate(s.title || '')}</div>
|
|
878
|
-
<div class="step-desc">${escapeHtmlTemplate(
|
|
932
|
+
<div class="step-desc">${escapeHtmlTemplate(desc)}</div>
|
|
879
933
|
${s.detail ? `<div class="step-detail">${escapeHtmlTemplate(s.detail)}</div>` : ''}
|
|
880
934
|
</div>`;
|
|
881
935
|
return i < steps.length - 1 ? stepDiv + '\n <div class="arrow">→</div>' : stepDiv;
|
|
@@ -884,13 +938,13 @@ function buildProcessFlowContent(design, data) {
|
|
|
884
938
|
}
|
|
885
939
|
function buildBigNumbersContent(design, data) {
|
|
886
940
|
const cardText = getCardTextColor(design);
|
|
887
|
-
const css = `.content{flex:1;display:flex;gap:40px;align-items:
|
|
888
|
-
.metric-card{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;padding:
|
|
889
|
-
.metric-value{font-size:
|
|
890
|
-
.metric-unit{font-size:
|
|
891
|
-
.metric-label{font-size:
|
|
892
|
-
.metric-desc{font-size:26px;color:${cardText}aa;line-height:1.5}
|
|
893
|
-
.metric-trend{font-size:28px;font-weight:600
|
|
941
|
+
const css = `.content{flex:1;display:flex;gap:40px;align-items:center}
|
|
942
|
+
.metric-card{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;padding:56px 36px;border-radius:20px;background:#fff;box-shadow:0 6px 28px rgba(0,0,0,0.08);text-align:center;gap:20px;border-top:4px solid ${design.accent_color}}
|
|
943
|
+
.metric-value{font-size:100px;font-weight:800;color:${design.primary_color};line-height:1}
|
|
944
|
+
.metric-unit{font-size:36px;font-weight:600;color:${design.accent_color}}
|
|
945
|
+
.metric-label{font-size:32px;font-weight:600;color:${cardText}}
|
|
946
|
+
.metric-desc{font-size:26px;color:${cardText}aa;line-height:1.5;max-width:90%}
|
|
947
|
+
.metric-trend{font-size:28px;font-weight:600}`;
|
|
894
948
|
const metrics = (data.metrics || []).slice(0, 3).map(m => `
|
|
895
949
|
<div class="metric-card">
|
|
896
950
|
<div class="metric-value">${escapeHtmlTemplate(m.value || '')}</div>
|
|
@@ -902,16 +956,16 @@ function buildBigNumbersContent(design, data) {
|
|
|
902
956
|
return { css, html: `<div class="content">${metrics}\n</div>` };
|
|
903
957
|
}
|
|
904
958
|
function buildTimelineContent(design, data) {
|
|
905
|
-
const titleSz = '
|
|
906
|
-
const descSz = '
|
|
907
|
-
const kpiSz = '
|
|
908
|
-
const pad = '40px
|
|
909
|
-
const css = `.content{flex:1;display:flex;align-items:stretch;gap:
|
|
910
|
-
.milestone{flex:1;display:flex;flex-direction:column;padding:${pad};border-radius:16px;background:#fff;box-shadow:0 4px 20px rgba(0,0,0,0.06);gap:
|
|
911
|
-
.ms-date{display:inline-block;padding:8px
|
|
959
|
+
const titleSz = '40px';
|
|
960
|
+
const descSz = '32px';
|
|
961
|
+
const kpiSz = '30px';
|
|
962
|
+
const pad = '44px 40px';
|
|
963
|
+
const css = `.content{flex:1;display:flex;align-items:stretch;gap:24px}
|
|
964
|
+
.milestone{flex:1;display:flex;flex-direction:column;justify-content:center;padding:${pad};border-radius:16px;background:#fff;box-shadow:0 4px 20px rgba(0,0,0,0.06);gap:18px}
|
|
965
|
+
.ms-date{display:inline-block;padding:8px 18px;border-radius:8px;background:${design.primary_color};color:#fff;font-size:26px;font-weight:700;align-self:flex-start}
|
|
912
966
|
.ms-title{font-size:${titleSz};font-weight:700;color:${design.primary_color}}
|
|
913
|
-
.ms-desc{font-size:${descSz};color:${getCardTextColor(design)};line-height:1.55
|
|
914
|
-
.ms-kpi{font-size:${kpiSz};font-weight:600;color:${design.accent_color};padding-top:16px;border-top:2px solid ${design.accent_light}
|
|
967
|
+
.ms-desc{font-size:${descSz};color:${getCardTextColor(design)};line-height:1.55}
|
|
968
|
+
.ms-kpi{font-size:${kpiSz};font-weight:600;color:${design.accent_color};padding-top:16px;border-top:2px solid ${design.accent_light}}`;
|
|
915
969
|
const milestones = (data.milestones || []).slice(0, 3).map((m) => `
|
|
916
970
|
<div class="milestone">
|
|
917
971
|
<div class="ms-date">${escapeHtmlTemplate(m.date || '')}</div>
|
|
@@ -922,14 +976,18 @@ function buildTimelineContent(design, data) {
|
|
|
922
976
|
return { css, html: `<div class="content">${milestones}\n</div>` };
|
|
923
977
|
}
|
|
924
978
|
function buildProgressBarsContent(design, data) {
|
|
925
|
-
const
|
|
979
|
+
const barCount = Math.min((data.bars || []).length, 6);
|
|
980
|
+
const barGap = barCount >= 5 ? '28px' : '44px';
|
|
981
|
+
const barHeight = barCount >= 5 ? '44px' : '56px';
|
|
982
|
+
const barRadius = barCount >= 5 ? '22px' : '28px';
|
|
983
|
+
const css = `.content{flex:1;display:flex;flex-direction:column;justify-content:center;gap:${barGap};padding:20px 0}
|
|
926
984
|
.bar-item{display:flex;flex-direction:column;gap:10px}
|
|
927
985
|
.bar-header{display:flex;justify-content:space-between;align-items:baseline}
|
|
928
|
-
.bar-label{font-size:
|
|
929
|
-
.bar-val{font-size:
|
|
930
|
-
.bar-track{width:100%;height
|
|
931
|
-
.bar-fill{height:100%;border-radius
|
|
932
|
-
.bar-detail{font-size:
|
|
986
|
+
.bar-label{font-size:32px;font-weight:600;color:${design.text_color}}
|
|
987
|
+
.bar-val{font-size:32px;font-weight:700;color:${design.primary_color}}
|
|
988
|
+
.bar-track{width:100%;height:${barHeight};background:${design.accent_light};border-radius:${barRadius};overflow:hidden}
|
|
989
|
+
.bar-fill{height:100%;border-radius:${barRadius};background:linear-gradient(90deg,${design.primary_color},${design.accent_color})}
|
|
990
|
+
.bar-detail{font-size:26px;color:${design.text_color}88;margin-top:-2px}`;
|
|
933
991
|
const bars = (data.bars || []).slice(0, 6).map(b => `
|
|
934
992
|
<div class="bar-item">
|
|
935
993
|
<div class="bar-header"><span class="bar-label">${escapeHtmlTemplate(b.label || '')}</span><span class="bar-val">${escapeHtmlTemplate(b.value || '')}</span></div>
|
|
@@ -968,11 +1026,11 @@ function buildTwoColSplitContent(design, data) {
|
|
|
968
1026
|
const css = `.content{flex:1;display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:stretch}
|
|
969
1027
|
.col{display:flex;flex-direction:column;gap:14px;justify-content:space-evenly}
|
|
970
1028
|
.col h2{font-size:36px;font-weight:700;color:${design.primary_color};margin-bottom:12px;padding-bottom:12px;border-bottom:3px solid ${design.accent_color}}
|
|
971
|
-
.kv-item{display:flex;justify-content:space-between;align-items:center;padding:
|
|
1029
|
+
.kv-item{display:flex;justify-content:space-between;align-items:center;padding:28px 28px;background:#fff;border-radius:14px;box-shadow:0 2px 12px rgba(0,0,0,0.04);font-size:30px;color:${getCardTextColor(design)}}
|
|
972
1030
|
.kv-label{color:${getCardTextColor(design)};font-weight:500}
|
|
973
|
-
.kv-value{color:${design.primary_color};font-weight:700;font-size:
|
|
974
|
-
.col ul{list-style:none;display:flex;flex-direction:column;gap:
|
|
975
|
-
.col li{padding:
|
|
1031
|
+
.kv-value{color:${design.primary_color};font-weight:700;font-size:30px}
|
|
1032
|
+
.col ul{list-style:none;display:flex;flex-direction:column;gap:12px}
|
|
1033
|
+
.col li{padding:20px 20px 20px 34px;position:relative;font-size:28px;line-height:1.5;background:#fff;border-radius:12px;box-shadow:0 1px 8px rgba(0,0,0,0.03);color:${getCardTextColor(design)}}
|
|
976
1034
|
.col li::before{content:"•";color:${design.accent_color};position:absolute;left:12px;font-weight:bold}`;
|
|
977
1035
|
const leftItems = (data.leftItems || []).slice(0, 4).map(item => `
|
|
978
1036
|
<div class="kv-item"><span class="kv-label">${escapeHtmlTemplate(item.label || '')}</span><span class="kv-value">${escapeHtmlTemplate(item.value || '')}</span></div>`).join('');
|
|
@@ -1018,9 +1076,9 @@ CSS:
|
|
|
1018
1076
|
.card { display:flex; flex-direction:column; justify-content:flex-start; gap:12px; padding:32px 28px; border-radius:16px; background:#fff; box-shadow:0 4px 20px rgba(0,0,0,0.06); overflow:hidden; }
|
|
1019
1077
|
|
|
1020
1078
|
⚠ grid-template-rows:1fr 1fr is CRITICAL — it forces cards to stretch and fill vertical space. Without it, cards collapse to content height leaving huge empty bottom.
|
|
1021
|
-
• MAX 4 cards (2×2). Each: icon/badge + title (32-40px) +
|
|
1079
|
+
• MAX 4 cards (2×2). Each: icon/badge + title (32-40px) + 3 bullets (26px, MAX 3 per card, each under 25 chars) + stat/metric at bottom.
|
|
1022
1080
|
• If only 3 items: grid-template-columns:1fr 1fr 1fr; grid-template-rows:1fr; (all 3 in one row)
|
|
1023
|
-
• MINIMUM CARD CONTENT: title +
|
|
1081
|
+
• MINIMUM CARD CONTENT: title + 3 bullet points with numbers/data + bottom metric. A card with only 1 bullet = FAILURE.
|
|
1024
1082
|
• Card bottom stat: use margin-top:auto to push it to card bottom, creating visual anchor.
|
|
1025
1083
|
• ⚠ NEVER have empty grid cells. If you have 3 items, use 3 columns. If 4, use 2×2.`,
|
|
1026
1084
|
bar_chart: `═══ REQUIRED LAYOUT: BAR CHART ═══
|
|
@@ -1183,7 +1241,7 @@ CRITICAL PATTERN — your .content MUST be a vertical stack of .bar-item divs:
|
|
|
1183
1241
|
|
|
1184
1242
|
⚠ justify-content:space-evenly distributes bars across the full height — NO empty bottom.
|
|
1185
1243
|
⚠ Each bar: header row (label + percentage) + track div containing fill div with style="width:XX%".
|
|
1186
|
-
⚠ 4-
|
|
1244
|
+
⚠ 4-5 bars (MAX 5). Bar height:44px. Use gradient fills. Labels under 15 chars. The structure above is non-negotiable.
|
|
1187
1245
|
⚠⚠⚠ .bar-item divs MUST be DIRECT children of .content. Do NOT wrap them in a container div.`,
|
|
1188
1246
|
hero_stat: `═══ REQUIRED LAYOUT: HERO STAT ═══
|
|
1189
1247
|
⚠⚠⚠ You MUST create a SINGLE LARGE CENTRAL METRIC. Cards/tables = FAILURE.
|