namespace JobTrackerApi.Models; // A theme is DATA, not code. The renderer (ThemedCvRenderer) has one render path; every visual // difference between themes is expressed by the fields below. Adding a theme = adding one CvTheme to // CvThemeCatalog — no renderer changes. This is the core Phase 4 architectural rule // ("Do NOT hardcode HTML templates"). docs/architecture/cv-theme-engine.md. public sealed class CvTheme { public string Id { get; init; } = string.Empty; public string Name { get; init; } = string.Empty; public string Category { get; init; } = string.Empty; public string Description { get; init; } = string.Empty; // single | sidebar-left | sidebar-right | header-band public string Layout { get; init; } = "single"; public double SidebarWidthMm { get; init; } = 62; // Palette public string Accent { get; init; } = "#2563eb"; public string Ink { get; init; } = "#111827"; public string Muted { get; init; } = "#4b5563"; public string Line { get; init; } = "#d1d5db"; public string Paper { get; init; } = "#ffffff"; public string SidebarBg { get; init; } = "#f3f4f6"; public string SidebarInk { get; init; } = "#111827"; public string? HeadingColor { get; init; } // null => accent // Typography public string HeadingFont { get; init; } = "Georgia, 'Times New Roman', serif"; public string BodyFont { get; init; } = "Arial, Helvetica, sans-serif"; public double NameSizePt { get; init; } = 24; public double HeadingSizePt { get; init; } = 12; public double BodySizePt { get; init; } = 10; public double LineHeight { get; init; } = 1.42; // Spacing (mm) public double PageMarginMm { get; init; } = 16; public double SectionGapMm { get; init; } = 6; public double EntryGapMm { get; init; } = 4.5; // plain | band | centered | kicker public string HeaderStyle { get; init; } = "plain"; // caps-rule | underline | plain | bar public string HeadingStyle { get; init; } = "caps-rule"; // none | square | rounded | circle public string PhotoShape { get; init; } = "rounded"; public bool DefaultIcons { get; init; } // ATS-friendly = single-column, no sidebar tables/graphics that trip naive resume parsers. Set on // the single-column themes; surfaced in GET /api/cv/themes so the picker can badge it. public bool AtsFriendly { get; init; } public bool Premium { get; init; } // Which section keys render in the sidebar for two-column layouts (ignored for single/header-band). public List SidebarSections { get; init; } = new() { "contact", "skills", "languages" }; } public static class CvThemeCatalog { // 8 professional themes, all data. To add one: append here. public static readonly IReadOnlyList Themes = new List { new() { Id = "modern", Name = "Modern", Category = "Modern Professional", Description = "Clean SaaS-style layout with a confident accent header.", Layout = "header-band", HeaderStyle = "band", HeadingStyle = "caps-rule", Accent = "#2563eb", HeadingFont = "'Segoe UI', Roboto, Arial, sans-serif", BodyFont = "'Segoe UI', Roboto, Arial, sans-serif", PhotoShape = "rounded", DefaultIcons = true, AtsFriendly = true, }, new() { Id = "minimal", Name = "Minimal", Category = "Minimal", Description = "Quiet, spacious, typography-first. Nothing competes with the content.", Layout = "single", HeaderStyle = "plain", HeadingStyle = "plain", Accent = "#111827", HeadingColor = "#111827", Muted = "#6b7280", HeadingFont = "'Helvetica Neue', Arial, sans-serif", BodyFont = "'Helvetica Neue', Arial, sans-serif", SectionGapMm = 7, EntryGapMm = 5, PhotoShape = "none", AtsFriendly = true, }, new() { Premium = true, Id = "executive", Name = "Executive", Category = "Executive", Description = "High-contrast, serif, leadership-weighted. For senior and client-facing roles.", Layout = "single", HeaderStyle = "centered", HeadingStyle = "underline", Accent = "#7c2d12", Ink = "#1c1917", Muted = "#57534e", Line = "#1c1917", HeadingFont = "Georgia, 'Times New Roman', serif", BodyFont = "Georgia, 'Times New Roman', serif", NameSizePt = 27, PhotoShape = "square", AtsFriendly = true, }, new() { Premium = true, Id = "technical", Name = "Technical", Category = "Technical", Description = "Dense two-column layout tuned for engineering CVs — skills and projects up front.", Layout = "sidebar-left", HeaderStyle = "band", HeadingStyle = "bar", Accent = "#0f4c5c", Ink = "#102a43", Muted = "#486581", SidebarBg = "#0f4c5c", SidebarInk = "#ffffff", HeadingFont = "'Roboto', Arial, sans-serif", BodyFont = "'Roboto', Arial, sans-serif", BodySizePt = 9.5, LineHeight = 1.36, DefaultIcons = true, SidebarSections = new() { "contact", "skills", "languages", "certifications" }, }, new() { Id = "ats-classic", Name = "ATS Classic", Category = "ATS Professional", Description = "Single column, no graphics, maximum parser compatibility. The safe default.", Layout = "single", HeaderStyle = "plain", HeadingStyle = "caps-rule", Accent = "#334155", Ink = "#111827", Muted = "#374151", HeadingFont = "Arial, Helvetica, sans-serif", BodyFont = "Arial, Helvetica, sans-serif", PhotoShape = "none", AtsFriendly = true, }, new() { Premium = true, Id = "nordic", Name = "Nordic", Category = "Modern Professional", Description = "Calm cool-blue sidebar, generous whitespace, Scandinavian restraint.", Layout = "sidebar-right", HeaderStyle = "plain", HeadingStyle = "caps-rule", Accent = "#3b6ea5", Ink = "#1f2937", Muted = "#4b5563", SidebarBg = "#eef3f8", SidebarInk = "#1f2937", HeadingFont = "'Segoe UI', Arial, sans-serif", BodyFont = "'Segoe UI', Arial, sans-serif", PhotoShape = "circle", DefaultIcons = true, }, new() { Premium = true, Id = "elegant", Name = "Elegant", Category = "Creative", Description = "Editorial serif headings over sans body, premium spacing and a plum accent.", Layout = "single", HeaderStyle = "kicker", HeadingStyle = "underline", Accent = "#7c3aed", Ink = "#1f2937", Muted = "#4b5563", HeadingFont = "Georgia, 'Times New Roman', serif", BodyFont = "'Segoe UI', Arial, sans-serif", NameSizePt = 26, SectionGapMm = 7, PhotoShape = "circle", AtsFriendly = true, }, new() { Premium = true, Id = "creative", Name = "Creative", Category = "Creative", Description = "Bold accent sidebar and photo-forward header for design and product roles.", Layout = "sidebar-left", HeaderStyle = "band", HeadingStyle = "bar", Accent = "#db2777", Ink = "#18181b", Muted = "#52525b", SidebarBg = "#be185d", SidebarInk = "#ffffff", HeadingFont = "'Poppins', 'Segoe UI', Arial, sans-serif", BodyFont = "'Segoe UI', Arial, sans-serif", PhotoShape = "circle", DefaultIcons = true, SidebarSections = new() { "contact", "skills", "languages", "interests" }, }, }; public static CvTheme Resolve(string? id) { var key = (id ?? string.Empty).Trim().ToLowerInvariant(); return Themes.FirstOrDefault(t => t.Id == key) ?? Themes[0]; } public static bool CanUse(string? id, bool premiumThemes) => Exists(id) && (premiumThemes || !Resolve(id).Premium); public static bool Exists(string? id) { var key = (id ?? string.Empty).Trim().ToLowerInvariant(); return Themes.Any(t => t.Id == key); } }