- TemplateService 공통 CSS에 h4/dl/matrix/comparison/board_report/metrics/roadmap 레거시 블록 호환 스타일을 추가해 raw body 기반 HTML 보고서의 뒤쪽 섹션도 동일한 폰트 크기와 카드 양식을 유지하도록 수정 - roadmap 내부 timeline/owner 배지가 전역 timeline 스타일과 충돌하던 문제를 override로 분리하고 다크 모드·모바일 레이아웃까지 함께 정리 - HtmlSkillLegacyBodyCompatibilityTests를 추가하고 dotnet build 및 HtmlSkillLegacyBodyCompatibilityTests|HtmlSkillConsultingSectionsTests 통과로 검증
1143 lines
73 KiB
C#
1143 lines
73 KiB
C#
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'?'🌙':'☀️';
|
||
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'?'🌙':'☀️';}}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="테마 전환">🌙</button>
|
||
<div class="container">
|
||
{bodyHtml}
|
||
</div>
|
||
{ThemeToggleScript}
|
||
</body>
|
||
</html>
|
||
""";
|
||
}
|
||
|
||
/// <summary>
|
||
/// HTML 본문에서 워크스페이스 하위 파일/폴더 경로를 파란색으로 강조합니다.
|
||
/// 코드 블록(<code>, <pre>) 내부의 경로 텍스트를 감지하여 파란색 스타일을 적용합니다.
|
||
/// </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);
|