MonorailCssService Pennington.MonorailCss
Generates CSS stylesheets using MonorailCSS with collected utility classes.
Constructors
.ctor
#public class MonorailCssService(MonorailCssOptions options, CssClassCollector cssClassCollector)
{
/// <summary>
/// Processes collected CSS classes and returns the generated stylesheet.
/// </summary>
public string GetStyleSheet()
{
// we are only scanning razor files, not the generated files. if you use
// code like bg-{color}-400 in the razor as a variable, that's not going to be detected.
var cssClassValues = cssClassCollector.GetClasses();
var cssFramework = GetCssFramework();
var styleSheet = cssFramework.Process(cssClassValues);
return $"""
{ContentVisibilityRules}
{options.ExtraStyles}
{styleSheet}
""";
}
// Paired content-visibility classes consumed by the llms.txt and search pipelines.
// .humans-only has no browser-side effect — it's a marker the extractor honors.
// .robots-only hides from browsers via display:none; the markup is still emitted,
// so automated extraction keeps it.
private const string ContentVisibilityRules = """
/* Pennington content-visibility markers. */
/* .humans-only — visible in the browser; stripped from llms.txt extraction. */
/* .robots-only — hidden in the browser; kept in llms.txt extraction. */
.robots-only { display: none; }
""";
private CssFramework GetCssFramework()
{
var theme = Theme.CreateWithDefaults();
theme = options.ColorScheme.ApplyToTheme(theme);
var cssFrameworkSettings = new CssFrameworkSettings()
{
Theme = theme,
Applies = ImmutableDictionary<string, string>.Empty
.AddRange(CodeBlockApplies())
.AddRange(TabApplies())
.AddRange(MarkdownAlertApplies())
.AddRange(HljsApplies(options.SyntaxTheme))
.AddRange(SearchModalApplies()),
ProseCustomization = GetCustomProseSettings()
};
var optionsCustomCssFrameworkSettings = options.CustomCssFrameworkSettings(cssFrameworkSettings);
return new CssFramework(optionsCustomCssFrameworkSettings);
}
private static ProseCustomization GetCustomProseSettings()
{
var proseCustomization = new ProseCustomization
{
Customization = theme =>
{
// Helper to get color values from theme
string GetColorValue(string color, string shade) =>
theme.ResolveValue(shade, [$"--color-{color}"]) ?? "#000000";
// Helper to add opacity to color (simplified - you might need a more robust implementation)
string WithOpacity(string color, string opacity) =>
$"color-mix(in srgb, {color} {opacity}, transparent)";
return new Dictionary<string, ProseElementRules>
{
["DEFAULT"] = new()
{
Rules = new List<ProseElementRule>
{
new() { Selector = "a", Declarations = new List<ProseDeclaration> {
new() { Property = "font-weight", Value = "700" },
new() { Property = "text-decoration", Value = "none" }
}.ToImmutableList()
},
new()
{
Selector = "a:not(:has(> code))",
Declarations = new List<ProseDeclaration>
{
new() { Property = "border-bottom-width", Value = "1px" },
new() { Property = "border-bottom-color", Value = WithOpacity(GetColorValue("primary", "500"), "75%")
}
}.ToImmutableList()
},
new()
{
Selector = "blockquote",
Declarations = new List<ProseDeclaration>
{
new() { Property = "border-left-width", Value = "4px" },
new() { Property = "padding-left", Value = "1rem" },
new() { Property = "border-color", Value = GetColorValue("primary", "700") }
}.ToImmutableList()
},
new()
{
Selector = "pre",
Declarations = new List<ProseDeclaration>
{
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "200"), "50%") },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklch(87.1% .006 286.286)" },
new() { Property = "border-radius", Value = "0.4rem" }
}.ToImmutableList()
},
new()
{
Selector = "code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-weight", Value = "400" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "padding", Value = "3px 8px" },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklch(87.1% .006 286.286)" },
new() { Property = "border-radius", Value = "0.4rem" },
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "200"), "50%") },
new() { Property = "color", Value = GetColorValue("base", "700") },
new() { Property = "word-break", Value = "break-word" }
}.ToImmutableList()
}
}.ToImmutableList()
},
["base"] = new()
{
Rules = new List<ProseElementRule>
{
new()
{
Selector = "pre > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "inherit" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "0.8em" }
}.ToImmutableList()
}
}.ToImmutableList()
},
["sm"] = new()
{
Rules = new List<ProseElementRule>
{
new()
{
Selector = "pre > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "inherit" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "0.8em" }
}.ToImmutableList()
}
}.ToImmutableList()
},
["invert"] = new()
{
Rules = new List<ProseElementRule>
{
new()
{
Selector = "pre",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-weight", Value = "300" },
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "800"), "75%") },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklab(100% 0 5.96046e-8/.1)" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "800"), "75%") },
new() { Property = "color", Value = GetColorValue("base", "200") },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklab(100% 0 5.96046e-8/.1)" }
}.ToImmutableList()
}
}.ToImmutableList()
}
}.ToImmutableDictionary();
}
};
return proseCustomization;
}
private static ImmutableDictionary<string, string> CodeBlockApplies()
{
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
{
".code-highlight-wrapper .standalone-code-container",
"bg-white/50 border border-base-300/75 shadow-xs rounded rounded-xl overflow-x-auto dark:bg-black/20 dark:border-base-700/50"
},
{
".code-highlight-wrapper pre ",
"py-2 md:py-3 px-4 md:px-8 overflow-x-auto font-mono text-xs md:text-sm leading-relaxed w-full dark:scheme-dark"
},
{
".code-highlight-wrapper .standalone-code-highlight pre",
"text-base-900/90 dark:text-base-100/90"
},
{
".code-highlight-wrapper pre code",
"font-mono inline-block min-w-full"
},
// Code transformation line containers
{
".code-highlight-wrapper .line",
"inline-block transition-all duration-300 lg:py-[1px] px-8 -mx-8 w-[calc(100%+4rem)] relative"
},
{
".code-highlight-wrapper pre.has-focused .line",
"blur-[0.05rem] opacity-75"
},
{
".code-highlight-wrapper pre.has-focused:hover .line",
"blur-[0] opacity-100"
},
// Line highlighting
{
".code-highlight-wrapper .line.highlight",
"bg-primary-700/20 dark:bg-primary-500/20"
},
// Diff notation
{
".code-highlight-wrapper .line.diff-add",
"bg-emerald-600/20 dark:bg-emerald-900/20 before:font-bold before:content-['+'] before:hidden md:before:block before:text-sm before:absolute before:left-1 before:green:text-green-500 before:text-green-700"
},
{
".code-highlight-wrapper .line.diff-remove",
"bg-red-600/20 dark:bg-red-900/20 before:font-bold before:content-['-'] before:hidden md:before:block before:text-sm before:absolute before:left-1 before:dark:text-red-500 before:text-red-700"
},
{
".code-highlight-wrapper .line.diff-remove > *",
"opacity-50 contrast-50"
},
// Focus and blur
{
".code-highlight-wrapper pre.has-focused .line.focused",
"blur-[0] opacity-100"
},
// Error and warning states
{
".code-highlight-wrapper .line.error",
"bg-red-300/50 dark:bg-red-500/20"
},
{
".code-highlight-wrapper .line.warning",
"bg-amber-300/50 dark:bg-amber-400/20"
},
// Word highlighting
{
".code-highlight-wrapper .word-highlight",
"border border-primary-600 dark:border-primary-300/25 rounded px-0.5 py-0 bg-primary-100/25 dark:bg-primary-500/10"
},
{
".code-highlight-wrapper .word-highlight-with-message",
"border border-b border-primary-600 dark:border-primary-300/25 rounded px-1 py-1 bg-primary-100/25 dark:bg-primary-500/10 relative "
},
{
".code-highlight-wrapper .word-highlight-wrapper",
"relative inline-block"
},
{
".code-highlight-wrapper .word-highlight-message",
"font-sans font-semilight tracking-loose absolute top-full left-0 mt-3 px-2 py-1 text-xs text-base-800 bg-base-200/25 dark:bg-primary-700/20 dark:text-primary-200 rounded border border-primary-500/50 whitespace-nowrap z-10 select-none pointer-events-none"
},
{
".code-highlight-wrapper .word-highlight-message::selection",
"bg-transparent"
},
{
".code-highlight-wrapper .line:has(.word-highlight-wrapper)",
"mb-12"
},
});
}
private static ImmutableDictionary<string, string> TabApplies()
{
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
{
".tab-container",
"flex flex-col bg-base-100 border border-base-300/75 shadow-xs rounded rounded-xl overflow-x-auto dark:bg-base-950/25 dark:border-base-700/50"
},
{
".tab-list",
"flex flex-row flex-wrap px-4 pt-1 bg-base-200/90 gap-x-2 lg:gap-x-4 dark:bg-base-800/50"
},
{
".tab-button",
"whitespace-nowrap border-b border-transparent py-2 text-xs text-base-900/90 font-medium transition-colors hover:text-accent-500 disabled:pointer-events-none disabled:opacity-50 data-[selected=true]:text-accent-700 data-[selected=true]:border-accent-700 dark:text-base-100/90 dark:hover:text-accent-300 dark:data-[selected=true]:text-accent-400 dark:data-[selected=true]:border-accent-400"
},
{
".tab-panel",
"hidden data-[selected=true]:block py-3 "
},
});
}
private static ImmutableDictionary<string, string> MarkdownAlertApplies()
{
const string alertFormatString =
"fill-{0}-700 dark:fill-{0}-500 bg-{0}-100/75 border-{0}-500/20 dark:border-{0}-500/30 dark:bg-{0}-900/25 text-{0}-800 dark:text-{0}-200";
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
// Markdig Alert Styles
{ ".markdown-alert", "my-6 px-4 flex flex-row gap-2.5 rounded-2xl border text-sm items-center" },
{ ".markdown-alert a", "underline" },
{ ".markdown-alert-note", string.Format(alertFormatString, "emerald") },
{ ".markdown-alert-tip", string.Format(alertFormatString, "blue") },
{ ".markdown-alert-caution", string.Format(alertFormatString, "amber") },
{ ".markdown-alert-warning", string.Format(alertFormatString, "rose") },
{ ".markdown-alert-important", string.Format(alertFormatString, "sky") },
{ ".markdown-alert-title", "text-[0px]" },
{ ".markdown-alert svg", "h-4 w-4 mt-0.5" },
});
}
private static ImmutableDictionary<string, string> HljsApplies(SyntaxTheme syntax)
{
string Token(ColorName c) => $"text-{c.Value}-800 dark:text-{c.Value}-300";
string Soft(ColorName c) => $"text-{c.Value}-700 dark:text-{c.Value}-300";
var keyword = Token(syntax.Keyword);
var @string = Token(syntax.String);
var variable = Token(syntax.Variable);
var function = Token(syntax.Function);
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
// Base highlight.js styles
{ ".hljs", "text-base-900 dark:text-base-200" },
// Comments
{ ".hljs-comment", $"text-{syntax.Comment.Value}-600 italic dark:text-{syntax.Comment.Value}-400" },
{ ".hljs-quote", $"text-{syntax.Comment.Value}-800/50 italic dark:text-{syntax.Comment.Value}-300" },
// Keywords and control flow
{ ".hljs-keyword", keyword },
{ ".hljs-selector-tag", Soft(syntax.Keyword) },
{ ".hljs-literal", keyword },
{ ".hljs-type", "text-base-700 dark:text-base-300" },
// Strings and characters
{ ".hljs-string", @string },
{ ".hljs-number", @string },
{ ".hljs-regexp", @string },
// Functions and methods
{ ".hljs-function", function },
{ ".hljs-title", function },
{ ".hljs-params", function },
// Variables and identifiers
{ ".hljs-variable", variable },
{ ".hljs-name", variable },
{ ".hljs-attr", variable },
{ ".hljs-symbol", variable },
// Operators and punctuation
{ ".hljs-operator", "text-base-800 dark:text-base-300" },
{ ".hljs-punctuation", "text-base-800 dark:text-base-300" },
// Special elements
{ ".hljs-built_in", Soft(syntax.Function) },
{ ".hljs-class", keyword },
{ ".hljs-meta", "text-base-800 dark:text-base-300" },
{ ".hljs-tag", keyword },
{ ".hljs-attribute", variable },
{ ".hljs-addition", "text-green-800 dark:text-green-300" },
{ ".hljs-deletion", "text-red-800 dark:text-red-300" },
{ ".hljs-link", "text-blue-800 dark:text-blue-300" },
});
}
private static ImmutableDictionary<string, string> SearchModalApplies()
{
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
// Modal backdrop and container
{ ".search-modal-backdrop", "fixed inset-0 bg-base-950/50 backdrop-blur z-50 p-4 md:p-16" },
{
".search-modal-content",
" top-16 mx-auto w-full mt-8 max-w-2xl bg-base-100 dark:bg-base-900 rounded-lg shadow-xl border border-base-200 dark:border-base-700"
},
// Modal header and input
{ ".search-modal-header", "p-4 border-b border-base-200 dark:border-base-700" },
{ ".search-modal-input-container", "relative" },
{
".search-modal-input",
"w-full px-4 py-2 pl-10 bg-base-50 dark:bg-base-800 border border-base-300 dark:border-base-600 rounded-md text-base-900 dark:text-base-100 placeholder-base-500 dark:placeholder-base-400 focus:outline-none focus:ring-1 focus:ring-primary-500/50 focus:border-primary-500"
},
{ ".search-modal-icon", "absolute left-3 top-2.5 h-4 w-4 text-base-400 dark:text-base-500" },
// Results container
{ ".search-modal-results", "max-h-96 overflow-y-auto px-4 dark:scheme-dark" },
// Status messages
{ ".search-modal-placeholder", "text-center text-base-600 dark:text-base-400 py-4" },
{ ".search-modal-loading", "text-center text-base-600 dark:text-base-400 py-4" },
{ ".search-modal-no-results", "text-center text-base-600 dark:text-base-400 py-4" },
{ ".search-modal-error", "text-center text-red-600 dark:text-red-400 py-4" },
// Search result items
{ ".search-result-item", "border-b border-base-200 dark:border-base-800 py-4 last:border-b-0" },
{
".search-result-link",
"block hover:bg-base-50 dark:hover:bg-base-800 rounded-md p-2 -m-2 transition-colors"
},
{ ".search-result-header", "flex items-start justify-between mb-1" },
{ ".search-result-title", "text-sm font-medium text-primary-700 dark:text-primary-400 flex-1" },
{ ".search-result-score", "text-xs text-base-500 dark:text-base-500 ml-2" },
{ ".search-result-description", "text-sm text-base-600 dark:text-base-400 mb-2" },
{ ".search-result-snippet", "text-xs text-base-700 dark:text-base-500" },
{ ".search-result-url", "text-xs text-base-500 dark:text-base-500 mt-2" },
// Search highlighting
{ ".search-result-title .search-highlight", "text-primary-500 dark:text-primary-100 bg-inherit" },
{ ".search-highlight", "text-base-500 dark:text-base-50 bg-inherit" },
});
}
}Generates CSS stylesheets using MonorailCSS with collected utility classes.
Parameters
optionsMonorailCssOptions- MonorailCSS configuration options.
cssClassCollectorCssClassCollector- Collector containing discovered CSS class names.
Methods
GetStyleSheet
#public string GetStyleSheet();Processes collected CSS classes and returns the generated stylesheet.
Returns
stringPennington.MonorailCss.MonorailCssService
namespace Pennington.MonorailCss;
/// Generates CSS stylesheets using MonorailCSS with collected utility classes.
public class MonorailCssService
{
/// Generates CSS stylesheets using MonorailCSS with collected utility classes.
public class MonorailCssService(MonorailCssOptions options, CssClassCollector cssClassCollector)
{
/// <summary>
/// Processes collected CSS classes and returns the generated stylesheet.
/// </summary>
public string GetStyleSheet()
{
// we are only scanning razor files, not the generated files. if you use
// code like bg-{color}-400 in the razor as a variable, that's not going to be detected.
var cssClassValues = cssClassCollector.GetClasses();
var cssFramework = GetCssFramework();
var styleSheet = cssFramework.Process(cssClassValues);
return $"""
{ContentVisibilityRules}
{options.ExtraStyles}
{styleSheet}
""";
}
// Paired content-visibility classes consumed by the llms.txt and search pipelines.
// .humans-only has no browser-side effect — it's a marker the extractor honors.
// .robots-only hides from browsers via display:none; the markup is still emitted,
// so automated extraction keeps it.
private const string ContentVisibilityRules = """
/* Pennington content-visibility markers. */
/* .humans-only — visible in the browser; stripped from llms.txt extraction. */
/* .robots-only — hidden in the browser; kept in llms.txt extraction. */
.robots-only { display: none; }
""";
private CssFramework GetCssFramework()
{
var theme = Theme.CreateWithDefaults();
theme = options.ColorScheme.ApplyToTheme(theme);
var cssFrameworkSettings = new CssFrameworkSettings()
{
Theme = theme,
Applies = ImmutableDictionary<string, string>.Empty
.AddRange(CodeBlockApplies())
.AddRange(TabApplies())
.AddRange(MarkdownAlertApplies())
.AddRange(HljsApplies(options.SyntaxTheme))
.AddRange(SearchModalApplies()),
ProseCustomization = GetCustomProseSettings()
};
var optionsCustomCssFrameworkSettings = options.CustomCssFrameworkSettings(cssFrameworkSettings);
return new CssFramework(optionsCustomCssFrameworkSettings);
}
private static ProseCustomization GetCustomProseSettings()
{
var proseCustomization = new ProseCustomization
{
Customization = theme =>
{
// Helper to get color values from theme
string GetColorValue(string color, string shade) =>
theme.ResolveValue(shade, [$"--color-{color}"]) ?? "#000000";
// Helper to add opacity to color (simplified - you might need a more robust implementation)
string WithOpacity(string color, string opacity) =>
$"color-mix(in srgb, {color} {opacity}, transparent)";
return new Dictionary<string, ProseElementRules>
{
["DEFAULT"] = new()
{
Rules = new List<ProseElementRule>
{
new() { Selector = "a", Declarations = new List<ProseDeclaration> {
new() { Property = "font-weight", Value = "700" },
new() { Property = "text-decoration", Value = "none" }
}.ToImmutableList()
},
new()
{
Selector = "a:not(:has(> code))",
Declarations = new List<ProseDeclaration>
{
new() { Property = "border-bottom-width", Value = "1px" },
new() { Property = "border-bottom-color", Value = WithOpacity(GetColorValue("primary", "500"), "75%")
}
}.ToImmutableList()
},
new()
{
Selector = "blockquote",
Declarations = new List<ProseDeclaration>
{
new() { Property = "border-left-width", Value = "4px" },
new() { Property = "padding-left", Value = "1rem" },
new() { Property = "border-color", Value = GetColorValue("primary", "700") }
}.ToImmutableList()
},
new()
{
Selector = "pre",
Declarations = new List<ProseDeclaration>
{
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "200"), "50%") },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklch(87.1% .006 286.286)" },
new() { Property = "border-radius", Value = "0.4rem" }
}.ToImmutableList()
},
new()
{
Selector = "code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-weight", Value = "400" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "padding", Value = "3px 8px" },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklch(87.1% .006 286.286)" },
new() { Property = "border-radius", Value = "0.4rem" },
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "200"), "50%") },
new() { Property = "color", Value = GetColorValue("base", "700") },
new() { Property = "word-break", Value = "break-word" }
}.ToImmutableList()
}
}.ToImmutableList()
},
["base"] = new()
{
Rules = new List<ProseElementRule>
{
new()
{
Selector = "pre > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "inherit" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "0.8em" }
}.ToImmutableList()
}
}.ToImmutableList()
},
["sm"] = new()
{
Rules = new List<ProseElementRule>
{
new()
{
Selector = "pre > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "inherit" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-size", Value = "0.8em" }
}.ToImmutableList()
}
}.ToImmutableList()
},
["invert"] = new()
{
Rules = new List<ProseElementRule>
{
new()
{
Selector = "pre",
Declarations = new List<ProseDeclaration>
{
new() { Property = "font-weight", Value = "300" },
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "800"), "75%") },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklab(100% 0 5.96046e-8/.1)" }
}.ToImmutableList()
},
new()
{
Selector = ":not(pre) > code",
Declarations = new List<ProseDeclaration>
{
new() { Property = "background-color", Value = WithOpacity(GetColorValue("base", "800"), "75%") },
new() { Property = "color", Value = GetColorValue("base", "200") },
new() { Property = "box-shadow", Value = "inset 0 0 0 1px oklab(100% 0 5.96046e-8/.1)" }
}.ToImmutableList()
}
}.ToImmutableList()
}
}.ToImmutableDictionary();
}
};
return proseCustomization;
}
private static ImmutableDictionary<string, string> CodeBlockApplies()
{
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
{
".code-highlight-wrapper .standalone-code-container",
"bg-white/50 border border-base-300/75 shadow-xs rounded rounded-xl overflow-x-auto dark:bg-black/20 dark:border-base-700/50"
},
{
".code-highlight-wrapper pre ",
"py-2 md:py-3 px-4 md:px-8 overflow-x-auto font-mono text-xs md:text-sm leading-relaxed w-full dark:scheme-dark"
},
{
".code-highlight-wrapper .standalone-code-highlight pre",
"text-base-900/90 dark:text-base-100/90"
},
{
".code-highlight-wrapper pre code",
"font-mono inline-block min-w-full"
},
// Code transformation line containers
{
".code-highlight-wrapper .line",
"inline-block transition-all duration-300 lg:py-[1px] px-8 -mx-8 w-[calc(100%+4rem)] relative"
},
{
".code-highlight-wrapper pre.has-focused .line",
"blur-[0.05rem] opacity-75"
},
{
".code-highlight-wrapper pre.has-focused:hover .line",
"blur-[0] opacity-100"
},
// Line highlighting
{
".code-highlight-wrapper .line.highlight",
"bg-primary-700/20 dark:bg-primary-500/20"
},
// Diff notation
{
".code-highlight-wrapper .line.diff-add",
"bg-emerald-600/20 dark:bg-emerald-900/20 before:font-bold before:content-['+'] before:hidden md:before:block before:text-sm before:absolute before:left-1 before:green:text-green-500 before:text-green-700"
},
{
".code-highlight-wrapper .line.diff-remove",
"bg-red-600/20 dark:bg-red-900/20 before:font-bold before:content-['-'] before:hidden md:before:block before:text-sm before:absolute before:left-1 before:dark:text-red-500 before:text-red-700"
},
{
".code-highlight-wrapper .line.diff-remove > *",
"opacity-50 contrast-50"
},
// Focus and blur
{
".code-highlight-wrapper pre.has-focused .line.focused",
"blur-[0] opacity-100"
},
// Error and warning states
{
".code-highlight-wrapper .line.error",
"bg-red-300/50 dark:bg-red-500/20"
},
{
".code-highlight-wrapper .line.warning",
"bg-amber-300/50 dark:bg-amber-400/20"
},
// Word highlighting
{
".code-highlight-wrapper .word-highlight",
"border border-primary-600 dark:border-primary-300/25 rounded px-0.5 py-0 bg-primary-100/25 dark:bg-primary-500/10"
},
{
".code-highlight-wrapper .word-highlight-with-message",
"border border-b border-primary-600 dark:border-primary-300/25 rounded px-1 py-1 bg-primary-100/25 dark:bg-primary-500/10 relative "
},
{
".code-highlight-wrapper .word-highlight-wrapper",
"relative inline-block"
},
{
".code-highlight-wrapper .word-highlight-message",
"font-sans font-semilight tracking-loose absolute top-full left-0 mt-3 px-2 py-1 text-xs text-base-800 bg-base-200/25 dark:bg-primary-700/20 dark:text-primary-200 rounded border border-primary-500/50 whitespace-nowrap z-10 select-none pointer-events-none"
},
{
".code-highlight-wrapper .word-highlight-message::selection",
"bg-transparent"
},
{
".code-highlight-wrapper .line:has(.word-highlight-wrapper)",
"mb-12"
},
});
}
private static ImmutableDictionary<string, string> TabApplies()
{
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
{
".tab-container",
"flex flex-col bg-base-100 border border-base-300/75 shadow-xs rounded rounded-xl overflow-x-auto dark:bg-base-950/25 dark:border-base-700/50"
},
{
".tab-list",
"flex flex-row flex-wrap px-4 pt-1 bg-base-200/90 gap-x-2 lg:gap-x-4 dark:bg-base-800/50"
},
{
".tab-button",
"whitespace-nowrap border-b border-transparent py-2 text-xs text-base-900/90 font-medium transition-colors hover:text-accent-500 disabled:pointer-events-none disabled:opacity-50 data-[selected=true]:text-accent-700 data-[selected=true]:border-accent-700 dark:text-base-100/90 dark:hover:text-accent-300 dark:data-[selected=true]:text-accent-400 dark:data-[selected=true]:border-accent-400"
},
{
".tab-panel",
"hidden data-[selected=true]:block py-3 "
},
});
}
private static ImmutableDictionary<string, string> MarkdownAlertApplies()
{
const string alertFormatString =
"fill-{0}-700 dark:fill-{0}-500 bg-{0}-100/75 border-{0}-500/20 dark:border-{0}-500/30 dark:bg-{0}-900/25 text-{0}-800 dark:text-{0}-200";
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
// Markdig Alert Styles
{ ".markdown-alert", "my-6 px-4 flex flex-row gap-2.5 rounded-2xl border text-sm items-center" },
{ ".markdown-alert a", "underline" },
{ ".markdown-alert-note", string.Format(alertFormatString, "emerald") },
{ ".markdown-alert-tip", string.Format(alertFormatString, "blue") },
{ ".markdown-alert-caution", string.Format(alertFormatString, "amber") },
{ ".markdown-alert-warning", string.Format(alertFormatString, "rose") },
{ ".markdown-alert-important", string.Format(alertFormatString, "sky") },
{ ".markdown-alert-title", "text-[0px]" },
{ ".markdown-alert svg", "h-4 w-4 mt-0.5" },
});
}
private static ImmutableDictionary<string, string> HljsApplies(SyntaxTheme syntax)
{
string Token(ColorName c) => $"text-{c.Value}-800 dark:text-{c.Value}-300";
string Soft(ColorName c) => $"text-{c.Value}-700 dark:text-{c.Value}-300";
var keyword = Token(syntax.Keyword);
var @string = Token(syntax.String);
var variable = Token(syntax.Variable);
var function = Token(syntax.Function);
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
// Base highlight.js styles
{ ".hljs", "text-base-900 dark:text-base-200" },
// Comments
{ ".hljs-comment", $"text-{syntax.Comment.Value}-600 italic dark:text-{syntax.Comment.Value}-400" },
{ ".hljs-quote", $"text-{syntax.Comment.Value}-800/50 italic dark:text-{syntax.Comment.Value}-300" },
// Keywords and control flow
{ ".hljs-keyword", keyword },
{ ".hljs-selector-tag", Soft(syntax.Keyword) },
{ ".hljs-literal", keyword },
{ ".hljs-type", "text-base-700 dark:text-base-300" },
// Strings and characters
{ ".hljs-string", @string },
{ ".hljs-number", @string },
{ ".hljs-regexp", @string },
// Functions and methods
{ ".hljs-function", function },
{ ".hljs-title", function },
{ ".hljs-params", function },
// Variables and identifiers
{ ".hljs-variable", variable },
{ ".hljs-name", variable },
{ ".hljs-attr", variable },
{ ".hljs-symbol", variable },
// Operators and punctuation
{ ".hljs-operator", "text-base-800 dark:text-base-300" },
{ ".hljs-punctuation", "text-base-800 dark:text-base-300" },
// Special elements
{ ".hljs-built_in", Soft(syntax.Function) },
{ ".hljs-class", keyword },
{ ".hljs-meta", "text-base-800 dark:text-base-300" },
{ ".hljs-tag", keyword },
{ ".hljs-attribute", variable },
{ ".hljs-addition", "text-green-800 dark:text-green-300" },
{ ".hljs-deletion", "text-red-800 dark:text-red-300" },
{ ".hljs-link", "text-blue-800 dark:text-blue-300" },
});
}
private static ImmutableDictionary<string, string> SearchModalApplies()
{
return ImmutableDictionary.Create<string, string>()
.AddRange(new Dictionary<string, string>
{
// Modal backdrop and container
{ ".search-modal-backdrop", "fixed inset-0 bg-base-950/50 backdrop-blur z-50 p-4 md:p-16" },
{
".search-modal-content",
" top-16 mx-auto w-full mt-8 max-w-2xl bg-base-100 dark:bg-base-900 rounded-lg shadow-xl border border-base-200 dark:border-base-700"
},
// Modal header and input
{ ".search-modal-header", "p-4 border-b border-base-200 dark:border-base-700" },
{ ".search-modal-input-container", "relative" },
{
".search-modal-input",
"w-full px-4 py-2 pl-10 bg-base-50 dark:bg-base-800 border border-base-300 dark:border-base-600 rounded-md text-base-900 dark:text-base-100 placeholder-base-500 dark:placeholder-base-400 focus:outline-none focus:ring-1 focus:ring-primary-500/50 focus:border-primary-500"
},
{ ".search-modal-icon", "absolute left-3 top-2.5 h-4 w-4 text-base-400 dark:text-base-500" },
// Results container
{ ".search-modal-results", "max-h-96 overflow-y-auto px-4 dark:scheme-dark" },
// Status messages
{ ".search-modal-placeholder", "text-center text-base-600 dark:text-base-400 py-4" },
{ ".search-modal-loading", "text-center text-base-600 dark:text-base-400 py-4" },
{ ".search-modal-no-results", "text-center text-base-600 dark:text-base-400 py-4" },
{ ".search-modal-error", "text-center text-red-600 dark:text-red-400 py-4" },
// Search result items
{ ".search-result-item", "border-b border-base-200 dark:border-base-800 py-4 last:border-b-0" },
{
".search-result-link",
"block hover:bg-base-50 dark:hover:bg-base-800 rounded-md p-2 -m-2 transition-colors"
},
{ ".search-result-header", "flex items-start justify-between mb-1" },
{ ".search-result-title", "text-sm font-medium text-primary-700 dark:text-primary-400 flex-1" },
{ ".search-result-score", "text-xs text-base-500 dark:text-base-500 ml-2" },
{ ".search-result-description", "text-sm text-base-600 dark:text-base-400 mb-2" },
{ ".search-result-snippet", "text-xs text-base-700 dark:text-base-500" },
{ ".search-result-url", "text-xs text-base-500 dark:text-base-500 mt-2" },
// Search highlighting
{ ".search-result-title .search-highlight", "text-primary-500 dark:text-primary-100 bg-inherit" },
{ ".search-highlight", "text-base-500 dark:text-base-50 bg-inherit" },
});
}
}
/// Processes collected CSS classes and returns the generated stylesheet.
public string GetStyleSheet();
}