Files
AX-Copilot-Codex/src/AxCopilot/Services/Agent/TemplateService.cs
lacvet 16e136107c HTML 보고서 레거시 양식 호환을 보강한다
- TemplateService 공통 CSS에 h4/dl/matrix/comparison/board_report/metrics/roadmap 레거시 블록 호환 스타일을 추가해 raw body 기반 HTML 보고서의 뒤쪽 섹션도 동일한 폰트 크기와 카드 양식을 유지하도록 수정

- roadmap 내부 timeline/owner 배지가 전역 timeline 스타일과 충돌하던 문제를 override로 분리하고 다크 모드·모바일 레이아웃까지 함께 정리

- HtmlSkillLegacyBodyCompatibilityTests를 추가하고 dotnet build 및 HtmlSkillLegacyBodyCompatibilityTests|HtmlSkillConsultingSectionsTests 통과로 검증
2026-04-15 22:47:39 +09:00

1143 lines
73 KiB
C#
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Markdig;
namespace AxCopilot.Services.Agent;
/// <summary>
/// 문서 디자인 템플릿 서비스.
/// 테마 무드(현대적, 전문가, 창의적 등)에 따라 CSS 스타일을 제공합니다.
/// HtmlSkill, DocxSkill 등에서 호출하여 문서 생성 시 적용합니다.
/// </summary>
public static class TemplateService
{
/// <summary>사용 가능한 테마 무드 목록.</summary>
public static readonly TemplateMood[] AvailableMoods =
[
new("modern", "현대적", "🔷", "깔끔한 라인, 넓은 여백, 미니멀한 색상 — 테크 기업 스타일"),
new("professional", "전문가", "📊", "신뢰감 있는 네이비 톤, 데이터 중심 레이아웃 — 비즈니스 보고서"),
new("creative", "아이디어", "🎨", "생동감 있는 그라데이션, 카드 레이아웃 — 브레인스토밍·기획서"),
new("minimal", "미니멀", "◻️", "극도로 절제된 흑백, 타이포그래피 중심 — 학술·논문 스타일"),
new("elegant", "우아한", "✨", "세리프 서체, 골드 포인트, 격식 있는 레이아웃 — 공식 문서"),
new("dark", "다크 모드", "🌙", "어두운 배경, 고대비 텍스트 — 개발자·야간 리딩"),
new("colorful", "컬러풀", "🌈", "밝고 활기찬 멀티 컬러, 둥근 모서리 — 프레젠테이션·요약"),
new("corporate", "기업 공식", "🏢", "보수적인 레이아웃, 로고 영역, 페이지 번호 — 사내 공식 보고서"),
new("magazine", "매거진", "📰", "멀티 컬럼, 큰 히어로 헤더, 인용 강조 — 뉴스레터·매거진"),
new("dashboard", "대시보드", "📈", "KPI 카드, 차트 영역, 그리드 레이아웃 — 데이터 대시보드"),
new("seminar", "세미나", "🎓", "다크/라이트 테마 전환, 그라데이션 히어로, 파이프라인 다이어그램 — 기술 세미나·발표 자료"),
new("seminar-toc", "세미나 (사이드 목차)", "📑", "좌측 고정 사이드바 목차 + 세미나 스타일 — 긴 기술 문서·레퍼런스"),
];
/// <summary>테마 전환 + 플로팅 TOC JS 스크립트 (HTML에 삽입용).</summary>
public const string ThemeToggleScript = """
<script>
function axToggleTheme(){
var h=document.documentElement,t=h.getAttribute('data-theme')==='dark'?'light':'dark';
h.setAttribute('data-theme',t);
var b=document.querySelector('.ax-theme-toggle');
if(b) b.innerHTML=t==='dark'?'&#127769;':'&#9728;&#65039;';
try{localStorage.setItem('ax-doc-theme',t);}catch(e){}
}
(function(){
try{var s=localStorage.getItem('ax-doc-theme');
if(s){document.documentElement.setAttribute('data-theme',s);
var b=document.querySelector('.ax-theme-toggle');
if(b) b.innerHTML=s==='dark'?'&#127769;':'&#9728;&#65039;';}}catch(e){}
var fab=document.getElementById('axFabToc');
if(fab){window.addEventListener('scroll',function(){
fab.classList.toggle('visible',window.scrollY>300);
});}
})();
</script>
""";
// ── 커스텀 무드 저장소 ──
private static readonly Dictionary<string, Models.CustomMoodEntry> _customMoods = new(StringComparer.OrdinalIgnoreCase);
/// <summary>커스텀 무드를 로드합니다 (앱 시작 시 SettingsService에서 호출).</summary>
public static void LoadCustomMoods(IEnumerable<Models.CustomMoodEntry> entries)
{
_customMoods.Clear();
foreach (var e in entries)
if (!string.IsNullOrWhiteSpace(e.Key))
_customMoods[e.Key] = e;
}
/// <summary>내장 + 커스텀 무드를 합친 전체 목록.</summary>
public static IReadOnlyList<TemplateMood> AllMoods
{
get
{
var list = new List<TemplateMood>(AvailableMoods);
foreach (var cm in _customMoods.Values)
list.Add(new TemplateMood(cm.Key, cm.Label, cm.Icon, cm.Description));
return list;
}
}
/// <summary>무드 키로 CSS 스타일을 반환합니다. 없으면 modern 기본값. 공통 CSS가 자동 첨부됩니다.</summary>
public static string GetCss(string moodKey)
{
// 커스텀 무드 우선 확인
if (_customMoods.TryGetValue(moodKey, out var custom))
return custom.Css + "\n" + CssShared;
var moodCss = moodKey switch
{
"modern" => CssModern,
"professional" => CssProfessional,
"creative" => CssCreative,
"minimal" => CssMinimal,
"elegant" => CssElegant,
"dark" => CssDark,
"colorful" => CssColorful,
"corporate" => CssCorporate,
"magazine" => CssMagazine,
"dashboard" => CssDashboard,
"seminar" => CssSeminar,
"seminar-toc" => CssSeminarSidebar,
_ => CssModern,
};
return moodCss + "\n" + CssShared;
}
/// <summary>무드 키로 TemplateMood를 반환합니다.</summary>
public static TemplateMood? GetMood(string key)
{
var builtin = Array.Find(AvailableMoods, m => m.Key == key);
if (builtin != null) return builtin;
return _customMoods.TryGetValue(key, out var cm)
? new TemplateMood(cm.Key, cm.Label, cm.Icon, cm.Description)
: null;
}
/// <summary>LLM 시스템 프롬프트에 삽입할 무드 목록 설명.</summary>
public static string GetMoodListForPrompt()
{
var sb = new System.Text.StringBuilder();
sb.AppendLine("Available document design moods (pass as 'mood' parameter to html_create):");
foreach (var m in AllMoods)
sb.AppendLine($" - \"{m.Key}\": {m.Label} — {m.Description}");
return sb.ToString();
}
/// <summary>Markdown 텍스트를 무드 CSS가 적용된 HTML 문서로 변환합니다.</summary>
public static string RenderMarkdownToHtml(string markdown, string moodKey = "modern")
{
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var bodyHtml = Markdown.ToHtml(markdown, pipeline);
var css = GetCss(moodKey);
var defaultTheme = moodKey is "dark" or "seminar" or "seminar-toc" or "dashboard" ? "dark" : "light";
return $"""
<!DOCTYPE html>
<html lang="ko" data-theme="{defaultTheme}">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>{css}</style>
</head>
<body>
<button class="ax-theme-toggle" onclick="axToggleTheme()" title="테마 전환">&#127769;</button>
<div class="container">
{bodyHtml}
</div>
{ThemeToggleScript}
</body>
</html>
""";
}
/// <summary>
/// HTML 본문에서 워크스페이스 하위 파일/폴더 경로를 파란색으로 강조합니다.
/// 코드 블록(&lt;code&gt;, &lt;pre&gt;) 내부의 경로 텍스트를 감지하여 파란색 스타일을 적용합니다.
/// </summary>
public static string HighlightFilePaths(string html, string? workFolder)
{
if (string.IsNullOrEmpty(workFolder) || string.IsNullOrEmpty(html))
return html;
// <code> 태그 내부의 텍스트 중 파일/폴더 경로 패턴을 감지하여 파란색으로 감싸기
// 패턴: 슬래시/백슬래시를 포함한 경로 또는 확장자가 있는 파일명
var pathPattern = new System.Text.RegularExpressions.Regex(
@"(?<![""'=/>])(\b[A-Za-z]:\\[^\s<>""]+|" + // 절대 경로: C:\folder\file.ext
@"\.{0,2}/[^\s<>""]+(?:\.[a-zA-Z]{1,10})?|" + // 상대 경로: ./src/file.cs, ../util.py
@"\b[\w\-]+(?:/[\w\-\.]+)+(?:\.[a-zA-Z]{1,10})?|" + // 폴더/파일: src/utils/helper.cs
@"\b[\w\-]+\.(?:cs|py|js|ts|tsx|jsx|json|xml|html|htm|css|md|txt|yml|yaml|toml|sh|bat|ps1|csproj|sln|docx|xlsx|pptx|pdf|csv)\b)", // 파일명.확장자
System.Text.RegularExpressions.RegexOptions.Compiled);
// <pre> 및 <code> 블록 외부의 텍스트에서만 치환
// 간단 접근: 모든 텍스트 노드에서 경로를 감지
return pathPattern.Replace(html, match =>
{
var path = match.Value;
// 이미 HTML 태그 내부이거나 href/src 속성값이면 건너뛰기
var prefix = html[..match.Index];
if (prefix.Length > 0)
{
var lastLt = prefix.LastIndexOf('<');
var lastGt = prefix.LastIndexOf('>');
if (lastLt > lastGt) return path; // 태그 속성 내부
}
return $"<span style=\"color:#3B82F6;font-weight:500;\">{path}</span>";
});
}
/// <summary>무드의 주요 색상을 반환합니다 (갤러리 미리보기용).</summary>
public static MoodColors GetMoodColors(string moodKey)
{
return moodKey switch
{
"modern" => new("#f5f5f7", "#ffffff", "#1d1d1f", "#6e6e73", "#0066cc", "#e5e5e7"),
"professional" => new("#f0f2f5", "#ffffff", "#1a365d", "#4a5568", "#2c5282", "#e2e8f0"),
"creative" => new("#faf5ff", "#ffffff", "#2d3748", "#718096", "#7c3aed", "#e9d5ff"),
"minimal" => new("#fafafa", "#ffffff", "#111111", "#555555", "#333333", "#e0e0e0"),
"elegant" => new("#fefdf8", "#fffef9", "#2c1810", "#6b5c4f", "#b8860b", "#e8e0d4"),
"dark" => new("#0d1117", "#161b22", "#e6edf3", "#8b949e", "#58a6ff", "#30363d"),
"colorful" => new("#f0f9ff", "#ffffff", "#1e293b", "#64748b", "#3b82f6", "#e0f2fe"),
"corporate" => new("#f3f4f6", "#ffffff", "#1f2937", "#6b7280", "#1e40af", "#e5e7eb"),
"magazine" => new("#f9fafb", "#ffffff", "#111827", "#6b7280", "#dc2626", "#f3f4f6"),
"dashboard" => new("#0f172a", "#1e293b", "#f1f5f9", "#94a3b8", "#3b82f6", "#334155"),
"seminar" => new("#0f1117", "#161822", "#e2e8f0", "#8892a8", "#6C8EEF", "#2a2d3e"),
"seminar-toc" => new("#0f1117", "#161822", "#e2e8f0", "#8892a8", "#6C8EEF", "#2a2d3e"),
_ => new("#f5f5f7", "#ffffff", "#1d1d1f", "#6e6e73", "#0066cc", "#e5e5e7"),
};
}
/// <summary>무드 갤러리용 색상 정보.</summary>
public record MoodColors(string Background, string CardBg, string PrimaryText, string SecondaryText, string Accent, string Border);
// ════════════════════════════════════════════════════════════════════
// CSS 템플릿 정의
// ════════════════════════════════════════════════════════════════════
#region Modern
private const string CssModern = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Inter', 'Segoe UI', 'Noto Sans KR', sans-serif;
background: #f5f5f7; color: #1d1d1f; line-height: 1.75; padding: 48px 24px; }
.container { max-width: 1080px; margin: 0 auto; background: #fff;
border-radius: 16px; padding: 56px 52px;
box-shadow: 0 4px 24px rgba(0,0,0,0.06); }
h1 { font-size: 28px; font-weight: 700; letter-spacing: -0.5px; color: #1d1d1f; margin-bottom: 4px; }
h2 { font-size: 20px; font-weight: 600; margin: 36px 0 14px; color: #1d1d1f;
padding-bottom: 8px; border-bottom: 2px solid #e5e5ea; }
h3 { font-size: 16px; font-weight: 600; margin: 24px 0 10px; color: #0071e3; }
.meta { font-size: 12px; color: #86868b; margin-bottom: 28px; letter-spacing: 0.3px; }
p { margin: 10px 0; font-size: 14.5px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 13.5px;
border-radius: 10px; overflow: hidden; }
th { background: #f5f5f7; text-align: left; padding: 12px 14px; font-weight: 600;
color: #1d1d1f; border-bottom: 2px solid #d2d2d7; }
td { padding: 10px 14px; border-bottom: 1px solid #f0f0f2; }
tr:hover td { background: #f9f9fb; }
ul, ol { margin: 10px 0 10px 28px; font-size: 14.5px; }
li { margin: 5px 0; }
code { background: #f5f5f7; padding: 2px 8px; border-radius: 6px; font-size: 13px;
font-family: 'SF Mono', Consolas, monospace; color: #e3116c; }
pre { background: #1d1d1f; color: #f5f5f7; padding: 20px; border-radius: 12px;
overflow-x: auto; font-size: 13px; margin: 16px 0; line-height: 1.6; }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { border-left: 3px solid #0071e3; padding: 12px 20px; margin: 16px 0;
background: #f0f7ff; color: #1d1d1f; border-radius: 0 8px 8px 0; font-size: 14px; }
.highlight { background: linear-gradient(120deg, #e0f0ff 0%, #f0e0ff 100%);
padding: 16px 20px; border-radius: 10px; margin: 16px 0; }
.badge { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 11px;
font-weight: 600; background: #0071e3; color: #fff; margin: 2px 4px 2px 0; }
""";
#endregion
#region Professional
private const string CssProfessional = """
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Segoe UI', 'Noto Sans KR', Arial, sans-serif;
background: #eef1f5; color: #1e293b; line-height: 1.7; padding: 40px 20px; }
.container { max-width: 1080px; margin: 0 auto; background: #fff;
border-radius: 8px; padding: 48px;
box-shadow: 0 1px 8px rgba(0,0,0,0.08);
border-top: 4px solid #1e3a5f; }
h1 { font-size: 26px; font-weight: 700; color: #1e3a5f; margin-bottom: 4px; }
h2 { font-size: 18px; font-weight: 600; margin: 32px 0 12px; color: #1e3a5f;
border-bottom: 2px solid #c8d6e5; padding-bottom: 6px; }
h3 { font-size: 15px; font-weight: 600; margin: 22px 0 8px; color: #2c5282; }
.meta { font-size: 12px; color: #94a3b8; margin-bottom: 24px; border-bottom: 1px solid #e2e8f0;
padding-bottom: 12px; }
p { margin: 8px 0; font-size: 14px; }
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13.5px;
border: 1px solid #e2e8f0; }
th { background: #1e3a5f; color: #fff; text-align: left; padding: 10px 14px;
font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.5px; }
td { padding: 9px 14px; border-bottom: 1px solid #e2e8f0; }
tr:nth-child(even) td { background: #f8fafc; }
tr:hover td { background: #eef2ff; }
ul, ol { margin: 8px 0 8px 24px; }
li { margin: 4px 0; font-size: 14px; }
code { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; font-size: 12.5px;
font-family: Consolas, monospace; color: #1e3a5f; }
pre { background: #0f172a; color: #e2e8f0; padding: 18px; border-radius: 6px;
overflow-x: auto; font-size: 12.5px; margin: 14px 0; }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { border-left: 4px solid #1e3a5f; padding: 10px 18px; margin: 14px 0;
background: #f0f4f8; color: #334155; }
.callout { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 6px;
padding: 14px 18px; margin: 14px 0; font-size: 13.5px; }
.callout strong { color: #1e40af; }
""";
#endregion
#region Creative
private const string CssCreative = """
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Poppins', 'Segoe UI', 'Noto Sans KR', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh; color: #2d3748; line-height: 1.75; padding: 48px 24px; }
.container { max-width: 1080px; margin: 0 auto; background: rgba(255,255,255,0.95);
backdrop-filter: blur(20px); border-radius: 20px; padding: 52px;
box-shadow: 0 20px 60px rgba(0,0,0,0.15); }
h1 { font-size: 30px; font-weight: 700;
background: linear-gradient(135deg, #667eea, #e040fb);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
margin-bottom: 4px; }
h2 { font-size: 20px; font-weight: 600; margin: 36px 0 14px; color: #553c9a;
position: relative; padding-left: 16px; }
h2::before { content: ''; position: absolute; left: 0; top: 4px; width: 4px; height: 22px;
background: linear-gradient(180deg, #667eea, #e040fb); border-radius: 4px; }
h3 { font-size: 16px; font-weight: 600; margin: 22px 0 10px; color: #7c3aed; }
.meta { font-size: 12px; color: #a0aec0; margin-bottom: 28px; }
p { margin: 10px 0; font-size: 14.5px; }
table { width: 100%; border-collapse: separate; border-spacing: 0; margin: 18px 0;
font-size: 13.5px; border-radius: 12px; overflow: hidden;
box-shadow: 0 4px 12px rgba(102,126,234,0.1); }
th { background: linear-gradient(135deg, #667eea, #764ba2); color: #fff;
text-align: left; padding: 12px 14px; font-weight: 600; }
td { padding: 10px 14px; border-bottom: 1px solid #f0e7fe; }
tr:hover td { background: #faf5ff; }
ul, ol { margin: 10px 0 10px 28px; font-size: 14.5px; }
li { margin: 5px 0; }
li::marker { color: #7c3aed; }
code { background: #f5f3ff; padding: 2px 8px; border-radius: 6px; font-size: 13px;
font-family: 'Fira Code', Consolas, monospace; color: #7c3aed; }
pre { background: #1a1a2e; color: #e0d4f5; padding: 20px; border-radius: 14px;
overflow-x: auto; font-size: 13px; margin: 16px 0;
border: 1px solid rgba(124,58,237,0.2); }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { border-left: 4px solid #7c3aed; padding: 14px 20px; margin: 16px 0;
background: linear-gradient(135deg, #f5f3ff, #faf5ff);
border-radius: 0 12px 12px 0; font-style: italic; }
.card { background: #fff; border: 1px solid #e9d8fd; border-radius: 14px;
padding: 20px; margin: 14px 0; box-shadow: 0 2px 8px rgba(124,58,237,0.08); }
.tag { display: inline-block; padding: 3px 12px; border-radius: 20px; font-size: 11px;
font-weight: 500; background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff; margin: 2px 4px 2px 0; }
""";
#endregion
#region Minimal
private const string CssMinimal = """
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Georgia', 'Batang', serif;
background: #fff; color: #222; line-height: 1.85; padding: 60px 24px; }
.container { max-width: 720px; margin: 0 auto; padding: 0; }
h1 { font-size: 32px; font-weight: 400; color: #000; margin-bottom: 4px;
letter-spacing: -0.5px; }
h2 { font-size: 20px; font-weight: 400; margin: 40px 0 14px; color: #000;
border-bottom: 1px solid #ddd; padding-bottom: 8px; }
h3 { font-size: 16px; font-weight: 600; margin: 28px 0 10px; color: #333; }
.meta { font-size: 12px; color: #999; margin-bottom: 36px; font-style: italic; }
p { margin: 12px 0; font-size: 15px; text-align: justify; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 14px; }
th { text-align: left; padding: 8px 0; font-weight: 600; border-bottom: 2px solid #000;
font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: #555; }
td { padding: 8px 0; border-bottom: 1px solid #eee; }
tr:hover td { background: #fafafa; }
ul, ol { margin: 12px 0 12px 20px; font-size: 15px; }
li { margin: 6px 0; }
code { background: #f7f7f7; padding: 2px 6px; border-radius: 2px; font-size: 13px;
font-family: 'Courier New', monospace; }
pre { background: #f7f7f7; color: #333; padding: 18px; margin: 16px 0;
overflow-x: auto; font-size: 13px; border: 1px solid #e5e5e5; }
pre code { background: transparent; padding: 0; }
blockquote { border-left: 3px solid #000; padding: 8px 20px; margin: 16px 0;
color: #555; font-style: italic; }
hr { border: none; border-top: 1px solid #ddd; margin: 32px 0; }
""";
#endregion
#region Elegant
private const string CssElegant = """
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&family=Source+Sans+3:wght@300;400;600&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Source Sans 3', 'Noto Sans KR', sans-serif;
background: #faf8f5; color: #3d3929; line-height: 1.75; padding: 48px 24px; }
.container { max-width: 1080px; margin: 0 auto; background: #fff;
border-radius: 4px; padding: 56px 52px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
border: 1px solid #e8e4dd; }
h1 { font-family: 'Playfair Display', Georgia, serif; font-size: 30px;
font-weight: 700; color: #2c2416; margin-bottom: 6px; letter-spacing: -0.3px; }
h2 { font-family: 'Playfair Display', Georgia, serif; font-size: 20px;
font-weight: 600; margin: 36px 0 14px; color: #2c2416;
border-bottom: 1px solid #d4c9b8; padding-bottom: 8px; }
h3 { font-size: 15px; font-weight: 600; margin: 24px 0 10px; color: #8b7a5e; }
.meta { font-size: 12px; color: #b0a48e; margin-bottom: 28px; letter-spacing: 0.5px;
text-transform: uppercase; }
p { margin: 10px 0; font-size: 14.5px; }
table { width: 100%; border-collapse: collapse; margin: 18px 0; font-size: 13.5px; }
th { background: #f8f5f0; text-align: left; padding: 10px 14px; font-weight: 600;
color: #5a4d38; border-bottom: 2px solid #d4c9b8; font-size: 12.5px;
letter-spacing: 0.5px; }
td { padding: 9px 14px; border-bottom: 1px solid #f0ece5; }
tr:hover td { background: #fdfcfa; }
ul, ol { margin: 10px 0 10px 26px; font-size: 14.5px; }
li { margin: 5px 0; }
code { background: #f8f5f0; padding: 2px 7px; border-radius: 3px; font-size: 12.5px;
font-family: 'Courier New', monospace; color: #8b6914; }
pre { background: #2c2416; color: #e8e0d0; padding: 18px; border-radius: 4px;
overflow-x: auto; font-size: 12.5px; margin: 16px 0; }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { border-left: 3px solid #c9a96e; padding: 12px 20px; margin: 16px 0;
background: #fdf9f0; color: #5a4d38; font-style: italic;
font-family: 'Playfair Display', Georgia, serif; }
.ornament { text-align: center; color: #c9a96e; font-size: 18px; margin: 24px 0; letter-spacing: 8px; }
""";
#endregion
#region Dark
private const string CssDark = """
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Inter:wght@300;400;500;600;700&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Inter', 'Segoe UI', 'Noto Sans KR', sans-serif;
background: #0d1117; color: #e6edf3; line-height: 1.75; padding: 48px 24px; }
.container { max-width: 1080px; margin: 0 auto; background: #161b22;
border-radius: 12px; padding: 52px;
border: 1px solid #30363d;
box-shadow: 0 8px 32px rgba(0,0,0,0.3); }
h1 { font-size: 28px; font-weight: 700; color: #f0f6fc; margin-bottom: 4px; }
h2 { font-size: 20px; font-weight: 600; margin: 36px 0 14px; color: #f0f6fc;
border-bottom: 1px solid #30363d; padding-bottom: 8px; }
h3 { font-size: 16px; font-weight: 600; margin: 24px 0 10px; color: #58a6ff; }
.meta { font-size: 12px; color: #8b949e; margin-bottom: 28px; }
p { margin: 10px 0; font-size: 14.5px; color: #c9d1d9; }
table { width: 100%; border-collapse: collapse; margin: 18px 0; font-size: 13.5px;
border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
th { background: #21262d; text-align: left; padding: 10px 14px; font-weight: 600;
color: #f0f6fc; border-bottom: 1px solid #30363d; }
td { padding: 9px 14px; border-bottom: 1px solid #21262d; color: #c9d1d9; }
tr:hover td { background: #1c2128; }
ul, ol { margin: 10px 0 10px 28px; font-size: 14.5px; color: #c9d1d9; }
li { margin: 5px 0; }
code { background: #1c2128; padding: 2px 8px; border-radius: 6px; font-size: 13px;
font-family: 'JetBrains Mono', Consolas, monospace; color: #79c0ff; }
pre { background: #0d1117; color: #c9d1d9; padding: 20px; border-radius: 8px;
overflow-x: auto; font-size: 13px; margin: 16px 0;
border: 1px solid #30363d; }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { border-left: 3px solid #58a6ff; padding: 12px 20px; margin: 16px 0;
background: #161b22; color: #8b949e;
border-radius: 0 8px 8px 0; }
a { color: #58a6ff; text-decoration: none; }
a:hover { text-decoration: underline; }
.label { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px;
font-weight: 500; border: 1px solid #30363d; color: #8b949e; margin: 2px 4px 2px 0; }
""";
#endregion
#region Colorful
private const string CssColorful = """
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700;800&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Nunito', 'Segoe UI', 'Noto Sans KR', sans-serif;
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 50%, #ffecd2 100%);
min-height: 100vh; color: #2d3436; line-height: 1.75; padding: 48px 24px; }
.container { max-width: 1080px; margin: 0 auto; background: #fff;
border-radius: 20px; padding: 52px;
box-shadow: 0 12px 40px rgba(0,0,0,0.08); }
h1 { font-size: 30px; font-weight: 800; color: #e17055; margin-bottom: 4px; }
h2 { font-size: 20px; font-weight: 700; margin: 34px 0 14px; color: #6c5ce7;
padding: 6px 14px; background: #f8f0ff; border-radius: 8px; display: inline-block; }
h3 { font-size: 16px; font-weight: 700; margin: 22px 0 10px; color: #00b894; }
.meta { font-size: 12px; color: #b2bec3; margin-bottom: 28px; }
p { margin: 10px 0; font-size: 14.5px; }
table { width: 100%; border-collapse: separate; border-spacing: 0; margin: 18px 0;
font-size: 13.5px; border-radius: 14px; overflow: hidden;
box-shadow: 0 2px 8px rgba(108,92,231,0.1); }
th { background: linear-gradient(135deg, #a29bfe, #6c5ce7); color: #fff;
text-align: left; padding: 12px 14px; font-weight: 700; }
td { padding: 10px 14px; border-bottom: 1px solid #f0f0f0; }
tr:hover td { background: #faf0ff; }
ul, ol { margin: 10px 0 10px 28px; font-size: 14.5px; }
li { margin: 5px 0; }
li::marker { color: #e17055; font-weight: 700; }
code { background: #fff3e0; padding: 2px 8px; border-radius: 6px; font-size: 13px;
font-family: Consolas, monospace; color: #e17055; }
pre { background: #2d3436; color: #dfe6e9; padding: 20px; border-radius: 14px;
overflow-x: auto; font-size: 13px; margin: 16px 0; }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { border-left: 4px solid #fdcb6e; padding: 14px 20px; margin: 16px 0;
background: #fffbf0; border-radius: 0 12px 12px 0; color: #636e72; }
.chip { display: inline-block; padding: 4px 14px; border-radius: 20px; font-size: 12px;
font-weight: 700; color: #fff; margin: 3px 4px 3px 0; }
.chip-red { background: #e17055; } .chip-blue { background: #74b9ff; }
.chip-green { background: #00b894; } .chip-purple { background: #6c5ce7; }
.chip-yellow { background: #fdcb6e; color: #2d3436; }
""";
#endregion
#region Corporate
private const string CssCorporate = """
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Segoe UI', 'Noto Sans KR', Arial, sans-serif;
background: #f2f2f2; color: #333; line-height: 1.65; padding: 32px 20px; }
.container { max-width: 1080px; margin: 0 auto; background: #fff; padding: 0;
box-shadow: 0 1px 4px rgba(0,0,0,0.1); }
.header-bar { background: #003366; color: #fff; padding: 28px 40px 20px;
border-bottom: 3px solid #ff6600; }
.header-bar h1 { font-size: 22px; font-weight: 700; color: #fff; margin-bottom: 2px; }
.header-bar .meta { color: rgba(255,255,255,0.7); margin-bottom: 0; font-size: 12px; }
.body-content { padding: 36px 40px 40px; }
.cover-page { margin: -36px -40px 40px -40px !important; border-radius: 0 !important;
width: auto !important; box-sizing: border-box !important;
left: 0; right: 0; }
h1 { font-size: 22px; font-weight: 700; color: #003366; margin-bottom: 4px; }
h2 { font-size: 17px; font-weight: 600; margin: 28px 0 10px; color: #003366;
border-left: 4px solid #ff6600; padding-left: 12px; }
h3 { font-size: 14.5px; font-weight: 600; margin: 20px 0 8px; color: #004488; }
.meta { font-size: 11.5px; color: #999; margin-bottom: 20px; }
p { margin: 8px 0; font-size: 13.5px; }
table { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: 12.5px;
border: 1px solid #ddd; }
th { background: #003366; color: #fff; text-align: left; padding: 8px 12px;
font-weight: 600; font-size: 11.5px; }
td { padding: 7px 12px; border: 1px solid #e0e0e0; }
tr:nth-child(even) td { background: #f9f9f9; }
ul, ol { margin: 8px 0 8px 24px; font-size: 13.5px; }
li { margin: 3px 0; }
code { background: #f4f4f4; padding: 1px 5px; border-radius: 3px; font-size: 12px;
font-family: Consolas, monospace; }
pre { background: #f4f4f4; color: #333; padding: 14px; border-radius: 4px;
overflow-x: auto; font-size: 12px; margin: 12px 0; border: 1px solid #ddd; }
pre code { background: transparent; padding: 0; }
blockquote { border-left: 4px solid #ff6600; padding: 10px 16px; margin: 12px 0;
background: #fff8f0; color: #555; }
.footer { text-align: center; font-size: 10.5px; color: #aaa; margin-top: 32px;
padding-top: 12px; border-top: 1px solid #eee; }
.stamp { display: inline-block; border: 2px solid #003366; color: #003366; padding: 4px 16px;
border-radius: 4px; font-size: 11px; font-weight: 700; text-transform: uppercase;
letter-spacing: 1px; }
""";
#endregion
#region Magazine
private const string CssMagazine = """
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700;900&family=Open+Sans:wght@300;400;600;700&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Open Sans', 'Noto Sans KR', sans-serif;
background: #f0ece3; color: #2c2c2c; line-height: 1.7; padding: 48px 24px; }
.container { max-width: 1080px; margin: 0 auto; background: #fff;
border-radius: 2px; padding: 0; overflow: hidden;
box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
.hero { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
padding: 48px 44px 36px; color: #fff; }
.hero h1 { font-family: 'Merriweather', Georgia, serif; font-size: 32px; font-weight: 900;
line-height: 1.3; margin-bottom: 8px; }
.hero .meta { color: rgba(255,255,255,0.6); margin-bottom: 0; font-size: 13px; }
.content { padding: 40px 44px 44px; }
h1 { font-family: 'Merriweather', Georgia, serif; font-size: 28px; font-weight: 900;
color: #1a1a2e; margin-bottom: 4px; }
h2 { font-family: 'Merriweather', Georgia, serif; font-size: 20px; font-weight: 700;
margin: 36px 0 14px; color: #1a1a2e; }
h3 { font-size: 15px; font-weight: 700; margin: 24px 0 10px; color: #e94560;
text-transform: uppercase; letter-spacing: 1px; font-size: 12px; }
.meta { font-size: 12px; color: #999; margin-bottom: 24px; }
p { margin: 10px 0; font-size: 15px; }
p:first-of-type::first-letter { font-family: 'Merriweather', Georgia, serif;
font-size: 48px; float: left; line-height: 1; padding-right: 8px; color: #e94560;
font-weight: 900; }
table { width: 100%; border-collapse: collapse; margin: 18px 0; font-size: 13.5px; }
th { background: #1a1a2e; color: #fff; text-align: left; padding: 10px 14px;
font-weight: 600; }
td { padding: 9px 14px; border-bottom: 1px solid #eee; }
tr:hover td { background: #fafafa; }
ul, ol { margin: 10px 0 10px 28px; font-size: 14.5px; }
li { margin: 5px 0; }
code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 12.5px;
font-family: 'Courier New', monospace; }
pre { background: #1a1a2e; color: #e0e0e0; padding: 18px; border-radius: 4px;
overflow-x: auto; font-size: 12.5px; margin: 16px 0; }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { font-family: 'Merriweather', Georgia, serif; font-size: 18px;
font-style: italic; color: #555; border: none; padding: 20px 0; margin: 24px 0;
text-align: center; position: relative; }
blockquote::before { content: '\201C'; font-size: 60px; color: #e94560;
position: absolute; top: -10px; left: 50%; transform: translateX(-50%);
opacity: 0.3; }
.pullquote { font-size: 20px; font-family: 'Merriweather', Georgia, serif;
font-weight: 700; color: #e94560; border-top: 3px solid #e94560;
border-bottom: 3px solid #e94560; padding: 16px 0; margin: 24px 0;
text-align: center; }
""";
#endregion
#region Dashboard
private const string CssDashboard = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: 'Inter', 'Segoe UI', 'Noto Sans KR', sans-serif;
background: #f0f2f5; color: #1a1a2e; line-height: 1.6; padding: 32px 24px; }
.container { max-width: 1000px; margin: 0 auto; padding: 0; background: transparent; }
h1 { font-size: 26px; font-weight: 700; color: #1a1a2e; margin-bottom: 4px; }
h2 { font-size: 17px; font-weight: 600; margin: 28px 0 14px; color: #1a1a2e; }
h3 { font-size: 14px; font-weight: 600; margin: 18px 0 8px; color: #6c7893; }
.meta { font-size: 12px; color: #8c95a6; margin-bottom: 24px; }
p { margin: 8px 0; font-size: 13.5px; color: #4a5568; }
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px; margin: 20px 0; }
.kpi-card { background: #fff; border-radius: 12px; padding: 20px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.kpi-card .kpi-label { font-size: 12px; color: #8c95a6; font-weight: 500;
text-transform: uppercase; letter-spacing: 0.5px; }
.kpi-card .kpi-value { font-size: 28px; font-weight: 700; color: #1a1a2e; margin: 4px 0; }
.kpi-card .kpi-change { font-size: 12px; font-weight: 600; }
.kpi-up { color: #10b981; } .kpi-down { color: #ef4444; }
.chart-area { background: #fff; border-radius: 12px; padding: 24px; margin: 16px 0;
box-shadow: 0 1px 4px rgba(0,0,0,0.06); min-height: 200px; }
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px;
background: #fff; border-radius: 10px; overflow: hidden;
box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
th { background: #f7f8fa; text-align: left; padding: 10px 14px; font-weight: 600;
color: #6c7893; font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.5px;
border-bottom: 1px solid #edf0f4; }
td { padding: 10px 14px; border-bottom: 1px solid #f3f4f6; }
tr:hover td { background: #f9fafb; }
ul, ol { margin: 8px 0 8px 24px; font-size: 13.5px; }
li { margin: 4px 0; }
code { background: #f1f3f5; padding: 2px 7px; border-radius: 5px; font-size: 12px;
font-family: 'JetBrains Mono', Consolas, monospace; }
pre { background: #1a1a2e; color: #c9d1d9; padding: 18px; border-radius: 10px;
overflow-x: auto; font-size: 12px; margin: 14px 0; }
pre code { background: transparent; color: inherit; padding: 0; }
blockquote { border-left: 3px solid #4b5efc; padding: 10px 16px; margin: 14px 0;
background: #f0f0ff; border-radius: 0 8px 8px 0; font-size: 13px; }
.status-badge { display: inline-block; padding: 2px 10px; border-radius: 12px;
font-size: 11px; font-weight: 600; }
.status-ok { background: #d1fae5; color: #065f46; }
.status-warn { background: #fef3c7; color: #92400e; }
.status-err { background: #fee2e2; color: #991b1b; }
""";
#endregion
#region Seminar
private const string CssSeminar = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
:root {
--accent: #6C8EEF; --accent2: #A78BFA; --green: #34D399; --amber: #FBBF24;
--red: #F87171; --cyan: #22D3EE; --transition: 0.3s ease;
}
[data-theme="dark"] {
--bg: #0f1117; --bg2: #0b0d13; --surface: #161822; --surface2: #1c1f2e; --surface3: #22253a;
--border: #2a2d3e; --text: #e2e8f0; --text-dim: #8892a8; --text-inv: #1a1a2e;
--hero-grad1: #161822; --hero-grad2: #0f1117; --hero-glow: rgba(108,142,239,0.12);
--shadow: rgba(0,0,0,0.3); --code-bg: rgba(108,142,239,0.1); --code-border: rgba(108,142,239,0.15);
}
[data-theme="light"] {
--bg: #f8fafc; --bg2: #f1f5f9; --surface: #ffffff; --surface2: #f1f5f9; --surface3: #e8ecf2;
--border: #e2e8f0; --text: #1e293b; --text-dim: #64748b; --text-inv: #ffffff;
--hero-grad1: #eef2ff; --hero-grad2: #f8fafc; --hero-glow: rgba(108,142,239,0.08);
--shadow: rgba(0,0,0,0.06); --code-bg: rgba(108,142,239,0.06); --code-border: rgba(108,142,239,0.12);
--accent: #4f6fd9; --accent2: #8b6fc0; --green: #16a34a; --amber: #d97706; --red: #dc2626; --cyan: #0891b2;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { font-family: 'Inter', 'Segoe UI', 'Noto Sans KR', sans-serif;
background: var(--bg); color: var(--text); line-height: 1.75; padding: 0;
-webkit-font-smoothing: antialiased; transition: background var(--transition), color var(--transition); }
.container { max-width: 980px; margin: 0 auto; padding: 0 28px; background: transparent; }
/* ── Hero ── */
.cover-page, .header-bar { position: relative; padding: 60px 40px 48px; text-align: center; overflow: hidden;
background: linear-gradient(180deg, var(--hero-grad1) 0%, var(--hero-grad2) 100%);
border-radius: 0; margin: 0 -28px 32px; transition: background var(--transition); }
.cover-page::before, .header-bar::before { content: ''; position: absolute; top: -120px; left: 50%;
transform: translateX(-50%); width: 600px; height: 600px;
background: radial-gradient(circle, var(--hero-glow) 0%, transparent 70%); pointer-events: none; }
.cover-page h1, .header-bar h1 { font-size: 36px; font-weight: 800; letter-spacing: -0.5px;
background: linear-gradient(135deg, var(--text), var(--accent), var(--accent2));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 8px; color: var(--text); }
.cover-page .cover-subtitle { font-size: 16px; color: var(--text-dim); }
.cover-page .cover-meta, .meta { font-size: 12px; color: var(--text-dim); margin-bottom: 24px; }
.header-bar .meta { margin-bottom: 0; margin-top: 8px; }
/* ── Headings ── */
h1 { font-size: 28px; font-weight: 800; color: var(--text); margin-bottom: 4px; }
h2 { font-size: 20px; font-weight: 700; margin: 40px 0 16px; color: var(--text);
padding-bottom: 12px; border-bottom: 2px solid var(--border);
display: flex; align-items: center; gap: 12px; transition: border-color var(--transition); }
h3 { font-size: 16px; font-weight: 700; color: var(--accent); margin: 28px 0 10px; }
h4 { font-size: 14px; font-weight: 600; color: var(--text); margin: 16px 0 8px;
border-left: 3px solid var(--accent); padding-left: 10px; }
/* ── Text ── */
p { margin: 10px 0; font-size: 14px; }
ul, ol { margin: 8px 0 12px 20px; font-size: 14px; }
li { margin: 4px 0; }
li::marker { color: var(--text-dim); }
strong { font-weight: 700; }
em { color: var(--accent); font-style: normal; font-weight: 600; }
/* ── Code ── */
code { font-family: 'Cascadia Code', 'Fira Code', Consolas, monospace;
background: var(--code-bg); border: 1px solid var(--code-border);
border-radius: 4px; padding: 1px 6px; font-size: 12.5px; color: var(--cyan); }
pre { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
padding: 18px; overflow-x: auto; font-size: 12.5px; margin: 14px 0; line-height: 1.55;
transition: all var(--transition); }
pre code { background: transparent; border: none; padding: 0; color: var(--text-dim); }
/* ── Cards ── */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
padding: 20px 24px; margin-bottom: 16px; transition: all var(--transition); }
.card:hover { box-shadow: 0 4px 16px var(--shadow); }
.card-header { font-size: 14px; font-weight: 700; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
/* ── Tables ── */
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
th { background: var(--surface2); padding: 10px 14px; text-align: left; font-weight: 700;
font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim);
border-bottom: 2px solid var(--border); transition: all var(--transition); }
td { padding: 10px 14px; border-bottom: 1px solid var(--border); vertical-align: top;
transition: all var(--transition); }
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--surface2); }
/* ── Badges ── */
.badge { font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 4px;
text-transform: uppercase; letter-spacing: 0.5px; display: inline-block; margin: 2px 4px 2px 0; }
.badge-green { background: rgba(52,211,153,0.15); color: var(--green); }
.badge-amber, .badge-yellow { background: rgba(251,191,36,0.15); color: var(--amber); }
.badge-blue { background: rgba(108,142,239,0.15); color: var(--accent); }
.badge-purple { background: rgba(167,139,250,0.15); color: var(--accent2); }
.badge-red { background: rgba(248,113,113,0.15); color: var(--red); }
.badge-cyan { background: rgba(34,211,238,0.15); color: var(--cyan); }
/* ── Info boxes ── */
.callout { border-radius: 10px; padding: 16px 20px; margin: 16px 0; font-size: 13.5px;
display: flex; gap: 12px; align-items: flex-start; border-left: 4px solid;
transition: all var(--transition); }
.callout::before { font-size: 16px; flex-shrink: 0; margin-top: 1px; }
.callout-info { background: rgba(108,142,239,0.07); border-color: var(--accent); }
.callout-info::before { content: '💡'; }
.callout-tip { background: rgba(52,211,153,0.07); border-color: var(--green); }
.callout-tip::before { content: '✅'; }
.callout-warning { background: rgba(251,191,36,0.07); border-color: var(--amber); }
.callout-warning::before { content: ''; }
.callout-danger { background: rgba(248,113,113,0.07); border-color: var(--red); }
.callout-danger::before { content: '🚨'; }
.callout-note { background: rgba(167,139,250,0.07); border-color: var(--accent2); }
.callout-note::before { content: '📝'; }
/* ── Flow diagram ── */
.flow { display: flex; align-items: center; gap: 0; margin: 20px 0; flex-wrap: wrap; justify-content: center; }
.flow-step { background: var(--surface2); border: 1px solid var(--border); border-radius: 10px;
padding: 12px 18px; text-align: center; min-width: 120px; transition: all var(--transition); }
.flow-step .num { font-size: 10px; font-weight: 700; color: var(--accent); text-transform: uppercase;
letter-spacing: 1px; margin-bottom: 4px; }
.flow-step .label { font-size: 13px; font-weight: 600; }
.flow-step .desc { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
.flow-arrow { color: var(--text-dim); font-size: 18px; margin: 0 6px; flex-shrink: 0; }
/* ── Pipeline ── */
.pipeline { margin: 20px 0; }
.pipeline-stage { display: flex; align-items: flex-start; gap: 16px; padding: 14px 0;
border-left: 2px solid var(--border); margin-left: 14px; padding-left: 24px; position: relative; }
.pipeline-stage::before { content: ''; position: absolute; left: -7px; top: 18px;
width: 12px; height: 12px; border-radius: 50%;
background: var(--surface); border: 2px solid var(--accent); transition: all var(--transition); }
.pipeline-stage:last-child { border-left-color: transparent; }
.pipeline-stage .stage-num { font-size: 10px; font-weight: 700; color: var(--accent);
text-transform: uppercase; letter-spacing: 1px; min-width: 60px; padding-top: 2px; }
.pipeline-stage .stage-body { flex: 1; }
.pipeline-stage .stage-title { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
.pipeline-stage .stage-desc { font-size: 12.5px; color: var(--text-dim); }
/* ── Stat cards ── */
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
padding: 18px 14px; text-align: center; transition: all var(--transition); }
.stat-card .number { font-size: 30px; font-weight: 800; color: var(--accent); }
.stat-card .label { font-size: 11px; color: var(--text-dim); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
/* ── Grids ── */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 16px 0; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin: 16px 0; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 16px 0; }
/* ── Profile cards ── */
.profile-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 16px 0; }
.profile-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
padding: 18px 20px; transition: all var(--transition); }
.profile-card:hover { box-shadow: 0 4px 16px var(--shadow); }
.profile-card .name { font-size: 14px; font-weight: 700; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; }
.profile-card .desc { font-size: 12px; color: var(--text-dim); margin-bottom: 10px; }
.profile-card .props { font-size: 12px; }
.profile-card .props dt { color: var(--text-dim); float: left; width: 110px; }
.profile-card .props dd { margin-bottom: 3px; }
/* ── Arch diagram ── */
.arch-diagram { background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
padding: 24px 28px; margin: 20px 0; font-family: 'Cascadia Code', monospace;
font-size: 11.5px; line-height: 1.55; overflow-x: auto; white-space: pre;
color: var(--text-dim); transition: all var(--transition); }
.arch-diagram .hl { color: var(--accent); font-weight: 600; }
.arch-diagram .g { color: var(--green); }
.arch-diagram .a { color: var(--amber); }
.arch-diagram .r { color: var(--red); }
.arch-diagram .p { color: var(--accent2); }
.arch-diagram .c { color: var(--cyan); }
/* ── Tags ── */
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
.tag { display: inline-block; padding: 3px 10px; border-radius: 6px; font-size: 11.5px;
font-weight: 500; background: var(--surface2); border: 1px solid var(--border);
color: var(--text-dim); transition: all var(--transition); }
.tag.accent { border-color: rgba(108,142,239,0.3); color: var(--accent); background: rgba(108,142,239,0.06); }
/* ── Blockquote ── */
blockquote { border-left: 3px solid var(--accent); padding: 12px 20px; margin: 16px 0;
background: rgba(108,142,239,0.05); border-radius: 0 10px 10px 0; font-size: 14px;
color: var(--text-dim); transition: all var(--transition); }
/* ── Separator ── */
.divider, hr { border: none; height: 1px;
background: linear-gradient(90deg, transparent, var(--border), transparent); margin: 40px 0; }
/* ── Responsive ── */
@media (max-width: 720px) {
.grid-2, .profile-grid { grid-template-columns: 1fr; }
.grid-3, .grid-4 { grid-template-columns: 1fr 1fr; }
.flow { flex-direction: column; }
.flow-arrow { transform: rotate(90deg); }
.cover-page h1, .header-bar h1 { font-size: 24px; }
.arch-diagram { font-size: 10px; padding: 16px; }
}
""";
private const string CssSeminarSidebar = CssSeminar + """
/* ═══════ Sidebar TOC Layout Override ═══════ */
.page-wrapper { display: flex; min-height: 100vh; }
.container { max-width: 980px; padding: 0 28px; margin-left: 280px; margin-right: auto; }
@supports (margin-left: max(0px, 0px)) {
.container { margin-left: max(280px, calc((100vw + 280px - 980px) / 2)); }
}
.toc {
position: fixed; top: 0; left: 0; width: 280px; height: 100vh;
overflow-y: auto; background: var(--surface); border-right: 1px solid var(--border);
padding: 24px 16px; z-index: 100; box-shadow: 2px 0 16px var(--shadow);
transition: all var(--transition);
}
.toc h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 2px;
color: var(--text-dim); margin-bottom: 14px; font-weight: 700; padding: 0 8px; }
.toc-grid { display: flex; flex-direction: column; gap: 2px; }
.toc a { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--text);
padding: 6px 8px; border-radius: 6px; font-size: 12.5px; transition: all 0.15s; }
.toc a:hover { background: var(--surface2); color: var(--accent); }
.toc a.active { background: rgba(108,142,239,0.1); color: var(--accent); font-weight: 600; }
.toc-num { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
border-radius: 5px; font-size: 10px; font-weight: 700;
background: rgba(108,142,239,0.1); color: var(--accent); flex-shrink: 0; }
.toc-sub { padding-left: 28px; font-size: 11.5px; color: var(--text-dim); }
.toc-sub:hover { color: var(--accent); }
.toc-divider { height: 1px; background: var(--border); margin: 6px 0; }
.toc::-webkit-scrollbar { width: 4px; }
.toc::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
@media (max-width: 960px) {
.toc { position: static; width: 100%; height: auto; max-height: none;
border-right: none; border-bottom: 1px solid var(--border);
box-shadow: 0 4px 16px var(--shadow); }
.container { margin-left: 0; }
.page-wrapper { flex-direction: column; }
}
""";
#endregion
// ════════════════════════════════════════════════════════════════════
// 공통 CSS 컴포넌트 (모든 무드에 자동 첨부)
// ════════════════════════════════════════════════════════════════════
#region Shared
private const string CssShared = """
/* ── 범용 다크 모드 (CSS 변수 미사용 무드용) ── */
[data-theme="dark"] body { background: #0f172a; color: #e2e8f0; }
[data-theme="dark"] .container { background: #1e293b; color: #e2e8f0; border-color: #334155; }
[data-theme="dark"] h1, [data-theme="dark"] h2, [data-theme="dark"] h3 { color: #e2e8f0; }
[data-theme="dark"] p, [data-theme="dark"] li { color: #cbd5e1; }
[data-theme="dark"] th { background: #334155; color: #e2e8f0; border-color: #475569; }
[data-theme="dark"] td { border-color: #334155; color: #cbd5e1; }
[data-theme="dark"] tr:hover td { background: #1e293b; }
[data-theme="dark"] tr:nth-child(even) td { background: #1e293b; }
[data-theme="dark"] code { background: rgba(99,102,241,0.15); color: #a5b4fc; border-color: rgba(99,102,241,0.2); }
[data-theme="dark"] pre { background: #0f172a; color: #e2e8f0; }
[data-theme="dark"] blockquote { background: rgba(99,102,241,0.08); color: #cbd5e1; }
[data-theme="dark"] .card { background: #1e293b; border-color: #334155; color: #e2e8f0; }
[data-theme="dark"] nav.toc { background: #1e293b; border-color: #334155; }
[data-theme="dark"] nav.toc a { color: #818cf8; }
[data-theme="dark"] .callout { background: rgba(99,102,241,0.08); border-color: #6366f1; color: #cbd5e1; }
[data-theme="dark"] .callout-info { background: rgba(59,130,246,0.1); border-color: #3b82f6; }
[data-theme="dark"] .callout-warning { background: rgba(245,158,11,0.1); border-color: #f59e0b; }
[data-theme="dark"] .callout-tip { background: rgba(34,197,94,0.1); border-color: #22c55e; }
[data-theme="dark"] .callout-danger { background: rgba(239,68,68,0.1); border-color: #ef4444; }
[data-theme="dark"] .highlight, [data-theme="dark"] .highlight-box { background: rgba(99,102,241,0.1); }
[data-theme="dark"] .cover-page { background: linear-gradient(135deg, #312e81 0%, #4c1d95 100%); }
[data-theme="dark"] .header-bar { border-color: #334155; }
[data-theme="dark"] .meta { color: #94a3b8; }
[data-theme="dark"] .chart-bar .bar-track { background: #334155; }
[data-theme="dark"] .divider, [data-theme="dark"] .divider-thick { border-color: #334155; }
[data-theme="dark"] hr { background: #334155; }
[data-theme="dark"] .kpi-card, [data-theme="dark"] .chart-area { background: #1e293b; border-color: #334155; color: #e2e8f0; }
[data-theme="dark"] .kpi-card .kpi-value { color: #e2e8f0; }
[data-theme="dark"] .kpi-card .kpi-label { color: #94a3b8; }
[data-theme="dark"] .badge-blue { background: rgba(59,130,246,0.15); color: #60a5fa; }
[data-theme="dark"] .badge-green { background: rgba(34,197,94,0.15); color: #4ade80; }
[data-theme="dark"] .badge-red { background: rgba(239,68,68,0.15); color: #f87171; }
[data-theme="dark"] .badge-yellow { background: rgba(245,158,11,0.15); color: #fbbf24; }
[data-theme="dark"] .badge-purple { background: rgba(139,92,246,0.15); color: #a78bfa; }
[data-theme="dark"] .badge-gray { background: rgba(107,114,128,0.15); color: #9ca3af; }
[data-theme="light"] body { } /* light 기본값 — 각 무드 CSS가 우선 */
/* ── 테마 토글 버튼 ── */
.ax-theme-toggle { position: fixed; top: 16px; right: 16px; z-index: 1000;
width: 40px; height: 40px; border-radius: 50%; border: 1px solid #d1d5db;
background: #fff; color: #374151; font-size: 18px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s ease; }
.ax-theme-toggle:hover { transform: scale(1.1); box-shadow: 0 4px 16px rgba(0,0,0,0.15); }
[data-theme="dark"] .ax-theme-toggle { background: #1e293b; color: #e2e8f0; border-color: #334155; }
/* ── 플로팅 TOC 버튼 ── */
.ax-fab-toc { position: fixed; bottom: 24px; right: 24px; z-index: 999;
width: 44px; height: 44px; border-radius: 12px; border: 1px solid #d1d5db;
background: #fff; color: #4b5efc; font-size: 20px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 16px rgba(0,0,0,0.1); transition: all 0.3s ease;
opacity: 0; pointer-events: none; transform: translateY(12px); }
.ax-fab-toc.visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
.ax-fab-toc:hover { background: #4b5efc; color: #fff; transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(75,94,252,0.3); }
[data-theme="dark"] .ax-fab-toc { background: #1e293b; border-color: #334155; }
[data-theme="dark"] .ax-fab-toc:hover { background: #4b5efc; }
/* ── 목차 (TOC) ── */
nav.toc { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 10px;
padding: 20px 28px; margin: 24px 0 32px; }
nav.toc h2 { font-size: 15px; font-weight: 700; margin: 0 0 12px; padding: 0; border: none;
color: inherit; display: block; background: none; }
nav.toc ul { list-style: none; margin: 0; padding: 0; }
nav.toc li { margin: 4px 0; }
nav.toc li.toc-h3 { padding-left: 18px; }
nav.toc a { text-decoration: none; color: #4b5efc; font-size: 13.5px; }
nav.toc a:hover { text-decoration: underline; }
/* ── 커버 페이지 ── */
.cover-page { text-align: center; padding: 80px 40px 60px; margin: -56px -52px 40px;
border-radius: 16px 16px 0 0; position: relative; overflow: hidden;
background: linear-gradient(135deg, #4b5efc 0%, #7c3aed 100%); color: #fff; }
.cover-page h1 { font-size: 36px; font-weight: 800; margin-bottom: 12px; color: #fff;
-webkit-text-fill-color: #fff; }
.cover-page .cover-subtitle { font-size: 18px; opacity: 0.9; margin-bottom: 24px; }
.cover-page .cover-meta { font-size: 13px; opacity: 0.7; }
.cover-page .cover-divider { width: 60px; height: 3px; background: rgba(255,255,255,0.5);
margin: 20px auto; border-radius: 2px; }
/* ── 콜아웃 (callout) ── */
.callout { border-radius: 8px; padding: 16px 20px; margin: 16px 0; font-size: 14px;
border-left: 4px solid; display: flex; gap: 10px; align-items: flex-start; }
.callout::before { font-size: 16px; flex-shrink: 0; margin-top: 1px; }
.callout-info { background: #eff6ff; border-color: #3b82f6; color: #1e40af; }
.callout-info::before { content: ''; }
.callout-warning { background: #fffbeb; border-color: #f59e0b; color: #92400e; }
.callout-warning::before { content: ''; }
.callout-tip { background: #f0fdf4; border-color: #22c55e; color: #166534; }
.callout-tip::before { content: '💡'; }
.callout-danger { background: #fef2f2; border-color: #ef4444; color: #991b1b; }
.callout-danger::before { content: '🚨'; }
.callout-note { background: #f5f3ff; border-color: #8b5cf6; color: #5b21b6; }
.callout-note::before { content: '📝'; }
/* ── 배지 (badge) — 공통 ── */
.badge, .tag, .chip { display: inline-block; padding: 3px 10px; border-radius: 20px;
font-size: 11px; font-weight: 600; margin: 2px 4px 2px 0; }
.badge-blue { background: #dbeafe; color: #1e40af; }
.badge-green { background: #d1fae5; color: #065f46; }
.badge-red { background: #fee2e2; color: #991b1b; }
.badge-yellow { background: #fef3c7; color: #92400e; }
.badge-purple { background: #ede9fe; color: #5b21b6; }
.badge-gray { background: #f3f4f6; color: #374151; }
.badge-orange { background: #ffedd5; color: #9a3412; }
/* ── 하이라이트 박스 ── */
.highlight-box { background: linear-gradient(120deg, #e0f0ff 0%, #f0e0ff 100%);
padding: 16px 20px; border-radius: 10px; margin: 16px 0; }
/* ── CSS 차트 (bar/horizontal) ── */
.chart-bar { margin: 20px 0; }
.chart-bar .bar-item { display: flex; align-items: center; margin: 6px 0; gap: 10px; }
.chart-bar .bar-label { min-width: 100px; font-size: 13px; text-align: right; flex-shrink: 0; }
.chart-bar .bar-track { flex: 1; background: #e5e7eb; border-radius: 6px; height: 22px;
overflow: hidden; }
.chart-bar .bar-fill { height: 100%; border-radius: 6px; display: flex; align-items: center;
padding: 0 8px; font-size: 11px; font-weight: 600; color: #fff;
transition: width 0.3s ease; min-width: fit-content; }
.bar-fill.blue { background: #3b82f6; } .bar-fill.green { background: #22c55e; }
.bar-fill.red { background: #ef4444; } .bar-fill.yellow { background: #f59e0b; }
.bar-fill.purple { background: #8b5cf6; } .bar-fill.orange { background: #f97316; }
/* ── CSS 도넛 차트 ── */
.chart-donut { width: 160px; height: 160px; border-radius: 50%; margin: 20px auto;
background: conic-gradient(var(--seg1-color, #3b82f6) 0% var(--seg1, 0%),
var(--seg2-color, #22c55e) var(--seg1, 0%) var(--seg2, 0%),
var(--seg3-color, #f59e0b) var(--seg2, 0%) var(--seg3, 0%),
var(--seg4-color, #ef4444) var(--seg3, 0%) var(--seg4, 0%),
#e5e7eb var(--seg4, 0%) 100%);
display: flex; align-items: center; justify-content: center; position: relative; }
.chart-donut::after { content: ''; width: 100px; height: 100px; background: #fff;
border-radius: 50%; position: absolute; }
.chart-donut .donut-label { position: absolute; z-index: 1; font-size: 18px; font-weight: 700; }
/* ── 진행률 바 ── */
.progress { background: #e5e7eb; border-radius: 8px; height: 10px; margin: 8px 0;
overflow: hidden; }
.progress-fill { height: 100%; border-radius: 8px; background: #3b82f6; }
/* ── 타임라인 ── */
.timeline { position: relative; padding-left: 28px; margin: 20px 0; }
.timeline::before { content: ''; position: absolute; left: 8px; top: 0; bottom: 0;
width: 2px; background: #e5e7eb; }
.timeline-item { position: relative; margin: 16px 0; }
.timeline-item::before { content: ''; position: absolute; left: -24px; top: 5px;
width: 12px; height: 12px; border-radius: 50%; background: #4b5efc;
border: 2px solid #fff; box-shadow: 0 0 0 2px #4b5efc; }
.timeline-item .timeline-date { font-size: 12px; color: #6b7280; font-weight: 600; }
.timeline-item .timeline-content { font-size: 14px; margin-top: 4px; }
/* ── 섹션 자동 번호 ── */
body { counter-reset: section; }
h2.numbered { counter-increment: section; counter-reset: subsection; }
h2.numbered::before { content: counter(section) '. '; }
h3.numbered { counter-increment: subsection; }
h3.numbered::before { content: counter(section) '-' counter(subsection) '. '; }
/* ── 그리드 레이아웃 ── */
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin: 16px 0; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 16px 0; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 16px 0; }
/* ── 카드 공통 ── */
.card { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px;
padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
.card-header { font-size: 15px; font-weight: 700; margin-bottom: 8px; }
/* ── 레거시 보고서 블록 호환 스타일 ── */
h4 { font-size: 13.5px; font-weight: 700; margin: 16px 0 8px; color: #1f4b7a; }
h5 { font-size: 12.5px; font-weight: 700; margin: 12px 0 6px; color: #334155; }
h6 { font-size: 12px; font-weight: 700; margin: 10px 0 6px; color: #475569; }
dl { margin: 14px 0; }
dt { font-size: 13px; font-weight: 700; color: #003366; margin-top: 10px; }
dd { margin: 4px 0 10px; padding-left: 12px; font-size: 13.5px; color: #4b5563; }
.matrix, .comparison, .decision_matrix, .board_report, .roadmap { margin: 18px 0 24px; }
.matrix { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.matrix > h3 { grid-column: 1 / -1; }
.matrix .quadrant { border: 1px solid #e5e7eb; border-radius: 12px; padding: 14px 16px;
background: linear-gradient(180deg, #fff, #f8fafc); min-height: 168px; }
.matrix .quadrant h4 { margin-top: 0; }
.comparison { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
.comparison .comparison-item { border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px 18px;
background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.decision_matrix { margin-top: 16px; }
.board_report { border: 1px solid #dbe4f0; border-radius: 16px; padding: 18px 20px;
background: linear-gradient(180deg, #f8fbff, #fff);
box-shadow: 0 10px 26px rgba(15,23,42,.05); }
.board_report > h3 { margin-top: 0; }
.board_report .decision-summary { padding: .9rem 1rem; border-radius: 12px; background: #eef2ff;
color: #312e81; margin: .8rem 0 1rem; }
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: .85rem;
margin: 1rem 0 1.25rem; }
.metric-item { border: 1px solid #dbe4f0; border-radius: 14px; padding: .9rem 1rem; background: #fff;
box-shadow: 0 8px 24px rgba(15,23,42,.05); display: flex; flex-direction: column; gap: .25rem; }
.metric-label { font-size: .78rem; font-weight: 700; color: #64748b; text-transform: uppercase;
letter-spacing: .05em; }
.metric-value { font-size: 1.35rem; font-weight: 800; color: #0f172a; }
.metric-note { font-size: .82rem; color: #475569; line-height: 1.55; }
.risks, .next-steps { margin-top: 1rem; padding: .95rem 1rem; border-radius: 14px; background: #fff;
border: 1px solid #e5e7eb; }
.risks h4, .next-steps h4 { margin-top: 0; }
.roadmap { display: grid; gap: .9rem; }
.roadmap .phase { display: grid; grid-template-columns: 160px 1fr; gap: 1rem; align-items: start;
border: 1px solid #e5e7eb; border-radius: 14px; padding: 1rem 1.1rem;
background: linear-gradient(180deg, #fff, #f8fafc); }
.roadmap .phase h4 { margin: 0 0 .35rem; }
.roadmap .phase p { margin: 0; }
.roadmap .phase .timeline,
.roadmap .phase .owner { position: static; padding-left: 0; margin: 0; display: inline-flex;
align-items: center; width: auto; line-height: 1.2; }
.roadmap .phase .timeline::before,
.roadmap .phase .owner::before { content: none; }
.roadmap .phase .timeline { margin: 0 0 .35rem; padding: .28rem .62rem; border-radius: 999px;
background: #eef2ff; color: #4338ca; font-size: .82rem; font-weight: 700; }
.roadmap .phase .owner { padding: .28rem .62rem; border-radius: 999px; background: #f3f4f6;
color: #475569; font-size: .82rem; font-weight: 600; }
/* ── 구분선 ── */
.divider { border: none; border-top: 1px solid #e5e7eb; margin: 32px 0; }
.divider-thick { border: none; border-top: 3px solid #e5e7eb; margin: 40px 0; }
@media (max-width: 720px) {
.matrix { grid-template-columns: 1fr; }
.roadmap .phase { grid-template-columns: 1fr; }
}
/* ── 인쇄/PDF 최적화 ── */
@media print {
.ax-theme-toggle, .ax-fab-toc { display: none !important; }
body { background: #fff !important; padding: 0 !important; }
.container { box-shadow: none !important; border: none !important;
max-width: none !important; padding: 20px !important; }
.cover-page { break-after: page; }
h2, h3 { break-after: avoid; }
table, figure, .chart-bar, .callout { break-inside: avoid; }
nav.toc { break-after: page; }
a { color: inherit !important; text-decoration: none !important; }
a[href]::after { content: ' (' attr(href) ')'; font-size: 10px; color: #999; }
.no-print { display: none !important; }
}
[data-theme="dark"] h4,
[data-theme="dark"] h5,
[data-theme="dark"] h6 { color: #e2e8f0; }
[data-theme="dark"] dt { color: #cbd5e1; }
[data-theme="dark"] dd { color: #94a3b8; }
[data-theme="dark"] .matrix .quadrant,
[data-theme="dark"] .comparison .comparison-item,
[data-theme="dark"] .board_report,
[data-theme="dark"] .metric-item,
[data-theme="dark"] .risks,
[data-theme="dark"] .next-steps,
[data-theme="dark"] .roadmap .phase {
background: #1e293b;
border-color: #334155;
color: #e2e8f0;
}
[data-theme="dark"] .board_report .decision-summary { background: rgba(99,102,241,0.14); color: #c7d2fe; }
[data-theme="dark"] .metric-label { color: #94a3b8; }
[data-theme="dark"] .metric-value { color: #f8fafc; }
[data-theme="dark"] .metric-note { color: #cbd5e1; }
[data-theme="dark"] .roadmap .phase .timeline { background: rgba(99,102,241,0.18); color: #c7d2fe; }
[data-theme="dark"] .roadmap .phase .owner { background: rgba(148,163,184,0.18); color: #cbd5e1; }
""";
#endregion
}
/// <summary>테마 무드 정의.</summary>
public record TemplateMood(string Key, string Label, string Icon, string Description);