diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..aaf66cf --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,26 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet build)", + "Bash(dotnet run)", + "Bash(curl:*)", + "Bash(dotnet publish:*)", + "Bash(dotnet run:*)", + "Bash(ls:*)", + "Bash(dotnet build:*)", + "Bash(find:*)", + "Bash(dotnet clean:*)", + "Bash(cp:*)", + "Bash(dotnet test)", + "Bash(dotnet test:*)", + "WebFetch(domain:htmx.org)", + "WebFetch(domain:xunit.net)", + "Bash(dotnet add:*)", + "Bash(./coverage.sh)", + "Bash(grep:*)", + "Bash(rm:*)", + "mcp__ide__getDiagnostics" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4372ce0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,1323 @@ +# EditorConfig is awesome: https://EditorConfig.org +# Roslynator's EditorConfig https://github.com/dotnet/roslynator/blob/main/.editorconfig + +# top-most EditorConfig file +root = true + +# Code files +[*.{cs,csx}] + +indent_style = space +indent_size = 4 + +dotnet_sort_system_directives_first = true + +csharp_style_namespace_declarations = file_scoped:warning + +# Options + +roslynator_accessibility_modifiers = explicit +# Applicable to: rcs1018 + +roslynator_accessor_braces_style = single_line_when_expression_is_on_single_line +# Applicable to: rcs0020 + +roslynator_array_creation_type_style = implicit +# Applicable to: rcs1014 + +roslynator_arrow_token_new_line = before +# Applicable to: rcs0032 + +roslynator_binary_operator_new_line = before +# Applicable to: rcs0027 + +roslynator_blank_line_after_file_scoped_namespace_declaration = true +# Applicable to: rcs0060 + +roslynator_blank_line_between_closing_brace_and_switch_section = true +# Applicable to: rcs0014, rcs1036 + +roslynator_blank_line_between_single_line_accessors = true +# Applicable to: rcs0011 + +roslynator_blank_line_between_switch_sections = omit +# Applicable to: rcs0061 + +roslynator_blank_line_between_using_directives = never +# Applicable to: rcs0015 + +roslynator_block_braces_style = multi_line +# Applicable to: rcs0021 + +roslynator_body_style = block +# Applicable to: rcs1016 + +roslynator_conditional_operator_condition_parentheses_style = omit +# Applicable to: rcs1051 + +roslynator_conditional_operator_new_line = before +# Applicable to: rcs0028 + +roslynator_configure_await = true +# Applicable to: rcs1090 + +roslynator_doc_comment_summary_style = multi_line +# Applicable to: rcs1253 + +roslynator_empty_string_style = field +# Applicable to: rcs1078 + +roslynator_enum_flag_value_style = shift_operator +# Applicable to: rcs1254 + +roslynator_enum_has_flag_style = operator +# Applicable to: rcs1096 + +roslynator_equals_token_new_line = after +# Applicable to: rcs0052 + +roslynator_infinite_loop_style = while +# Applicable to: rcs1252 + +roslynator_max_line_length = 140 +# Applicable to: rcs0056 + +roslynator_new_line_at_end_of_file = true +# Applicable to: rcs0058 + +roslynator_new_line_before_while_in_do_statement = true +# Applicable to: rcs0051 + +roslynator_null_check_style = pattern_matching +# Applicable to: rcs1248 + +roslynator_null_conditional_operator_new_line = before +# Applicable to: rcs0059 + +roslynator_object_creation_parentheses_style = omit +# Applicable to: rcs1050 + +roslynator_object_creation_type_style = implicit_when_type_is_obvious +# Applicable to: rcs1250 + +roslynator_prefix_field_identifier_with_underscore = true + +roslynator_tab_length = 4 +# Applicable to: rcs0056 + +roslynator_trailing_comma_style = omit +# Applicable to: rcs1260 + +roslynator_unity_code_analysis.enabled = false +# Applicable to: rcs1169, rcs1213 + +roslynator_use_anonymous_function_or_method_group = method_group +# Applicable to: rcs1207 + +roslynator_use_block_body_when_declaration_spans_over_multiple_lines = true +# Applicable to: rcs1016 + +roslynator_use_block_body_when_expression_spans_over_multiple_lines = true +# Applicable to: rcs1016 + +roslynator_use_collection_expression = true +# Applicable to: rcs1014, rcs1250 + +roslynator_use_var = when_type_is_obvious +# Applicable to: rcs1264 + +roslynator_use_var_instead_of_implicit_object_creation = false +# Applicable to: rcs1250 + + +# Analyzers + +# Add blank line after embedded statement +dotnet_diagnostic.rcs0001.severity = warning + +# Add blank line after #region +dotnet_diagnostic.rcs0002.severity = warning + +# Add blank line after using directive list +dotnet_diagnostic.rcs0003.severity = warning + +# Add blank line before #endregion +dotnet_diagnostic.rcs0005.severity = warning + +# Add blank line before using directive list +dotnet_diagnostic.rcs0006.severity = warning + +# Add blank line between accessors +dotnet_diagnostic.rcs0007.severity = warning + +# Add blank line between closing brace and next statement +dotnet_diagnostic.rcs0008.severity = warning + +# Add blank line between declaration and documentation comment +dotnet_diagnostic.rcs0009.severity = warning + +# Add blank line between declarations +dotnet_diagnostic.rcs0010.severity = warning + +# Add/remove blank line between single-line accessors +dotnet_diagnostic.rcs0011.severity = warning +# Options: roslynator_blank_line_between_single_line_accessors + +# Add blank line between single-line declarations +dotnet_diagnostic.rcs0012.severity = none + +# Add blank line between single-line declarations of different kind +dotnet_diagnostic.rcs0013.severity = warning + +# Add/remove blank line between using directives +dotnet_diagnostic.rcs0015.severity = warning +# Options: roslynator_blank_line_between_using_directives + +# Put attribute list on its own line +dotnet_diagnostic.rcs0016.severity = warning + +# Format accessor's braces on a single line or multiple lines +dotnet_diagnostic.rcs0020.severity = warning +# Options: roslynator_accessor_braces_style + +# Format block's braces on a single line or multiple lines +dotnet_diagnostic.rcs0021.severity = warning +# Options: roslynator_block_braces_style + +# Format type declaration's braces +dotnet_diagnostic.rcs0023.severity = warning + +# Add new line after switch label +dotnet_diagnostic.rcs0024.severity = warning + +# Put full accessor on its own line +dotnet_diagnostic.rcs0025.severity = warning + +# Place new line after/before binary operator +dotnet_diagnostic.rcs0027.severity = warning +# Options: roslynator_binary_operator_new_line + +# Place new line after/before '?:' operator +dotnet_diagnostic.rcs0028.severity = warning +# Options: roslynator_conditional_operator_new_line + +# Put constructor initializer on its own line +dotnet_diagnostic.rcs0029.severity = warning + +# Put embedded statement on its own line +dotnet_diagnostic.rcs0030.severity = warning + +# Put enum member on its own line +dotnet_diagnostic.rcs0031.severity = warning + +# Place new line after/before arrow token +dotnet_diagnostic.rcs0032.severity = warning +# Options: roslynator_arrow_token_new_line + +# Put statement on its own line +dotnet_diagnostic.rcs0033.severity = warning + +# Put type parameter constraint on its own line +dotnet_diagnostic.rcs0034.severity = warning + +# Remove blank line between single-line declarations of same kind +dotnet_diagnostic.rcs0036.severity = none + +# Remove new line before base list +dotnet_diagnostic.rcs0039.severity = warning + +# Remove new line between 'if' keyword and 'else' keyword +dotnet_diagnostic.rcs0041.severity = warning + +# Put auto-accessors on a single line +dotnet_diagnostic.rcs0042.severity = warning + +# Use carriage return + linefeed as new line +dotnet_diagnostic.rcs0044.severity = none + +# Use linefeed as new line +dotnet_diagnostic.rcs0045.severity = none + +# Use spaces instead of tab +dotnet_diagnostic.rcs0046.severity = warning + +# Put initializer on a single line +dotnet_diagnostic.rcs0048.severity = warning + +# Add blank line after top comment +dotnet_diagnostic.rcs0049.severity = warning + +# Add blank line before top declaration +dotnet_diagnostic.rcs0050.severity = warning + +# Add/remove new line before 'while' in 'do' statement +dotnet_diagnostic.rcs0051.severity = warning +# Options: roslynator_new_line_before_while_in_do_statement + +# Place new line after/before equals token +dotnet_diagnostic.rcs0052.severity = warning +# Options: roslynator_equals_token_new_line + +# Fix formatting of a list +dotnet_diagnostic.rcs0053.severity = warning + +# Fix formatting of a call chain +dotnet_diagnostic.rcs0054.severity = warning + +# Fix formatting of a binary expression chain +dotnet_diagnostic.rcs0055.severity = warning + +# A line is too long +dotnet_diagnostic.rcs0056.severity = warning +# Options: roslynator_max_line_length, roslynator_tab_length + +# Normalize whitespace at the beginning of a file +dotnet_diagnostic.rcs0057.severity = warning + +# Normalize whitespace at the end of a file +dotnet_diagnostic.rcs0058.severity = warning +# Options: roslynator_new_line_at_end_of_file + +# Place new line after/before null-conditional operator +dotnet_diagnostic.rcs0059.severity = warning +# Options: roslynator_null_conditional_operator_new_line + +# Add/remove line after file scoped namespace declaration +dotnet_diagnostic.rcs0060.severity = warning +# Options: roslynator_blank_line_after_file_scoped_namespace_declaration + +# Add/remove blank line between switch sections +dotnet_diagnostic.rcs0061.severity = warning +# Options: roslynator_blank_line_between_switch_sections + +# Add braces (when expression spans over multiple lines) +dotnet_diagnostic.rcs1001.severity = suggestion + +# Remove braces +dotnet_diagnostic.rcs1002.severity = none + +# Add braces to if-else (when expression spans over multiple lines) +dotnet_diagnostic.rcs1003.severity = suggestion + +# Remove braces from if-else +dotnet_diagnostic.rcs1004.severity = none + +# Simplify nested using statement +dotnet_diagnostic.rcs1005.severity = silent + +# Merge 'else' with nested 'if' +dotnet_diagnostic.rcs1006.severity = silent + +# Add braces +dotnet_diagnostic.rcs1007.severity = warning + +# Use predefined type +dotnet_diagnostic.rcs1013.severity = warning + +# Use explicitly/implicitly typed array +dotnet_diagnostic.rcs1014.severity = warning +# Options: roslynator_array_creation_type_style, roslynator_use_collection_expression + +# Use nameof operator +dotnet_diagnostic.rcs1015.severity = suggestion + +# Use block body or expression body +dotnet_diagnostic.rcs1016.severity = none +# Options: roslynator_body_style, roslynator_use_block_body_when_declaration_spans_over_multiple_lines, roslynator_use_block_body_when_expression_spans_over_multiple_lines + +# Add/remove accessibility modifiers +dotnet_diagnostic.rcs1018.severity = warning +# Options: roslynator_accessibility_modifiers + +# Order modifiers +dotnet_diagnostic.rcs1019.severity = warning + +# Simplify Nullable to T? +dotnet_diagnostic.rcs1020.severity = suggestion + +# Convert lambda expression body to expression body +dotnet_diagnostic.rcs1021.severity = suggestion + +# Remove unnecessary braces in switch section +dotnet_diagnostic.rcs1031.severity = silent + +# Remove redundant parentheses +dotnet_diagnostic.rcs1032.severity = suggestion + +# Remove redundant boolean literal +dotnet_diagnostic.rcs1033.severity = suggestion + +# Remove redundant 'sealed' modifier +dotnet_diagnostic.rcs1034.severity = silent + +# Remove unnecessary blank line +dotnet_diagnostic.rcs1036.severity = suggestion +# Options: roslynator_blank_line_between_closing_brace_and_switch_section + +# Remove trailing white-space +dotnet_diagnostic.rcs1037.severity = suggestion + +# Remove argument list from attribute +dotnet_diagnostic.rcs1039.severity = silent + +# Remove enum default underlying type +dotnet_diagnostic.rcs1042.severity = silent + +# Remove 'partial' modifier from type with a single part +dotnet_diagnostic.rcs1043.severity = silent + +# Remove original exception from throw statement +dotnet_diagnostic.rcs1044.severity = error + +# Asynchronous method name should end with 'Async' +dotnet_diagnostic.rcs1046.severity = warning + +# Non-asynchronous method name should not end with 'Async' +dotnet_diagnostic.rcs1047.severity = warning + +# Use lambda expression instead of anonymous method +dotnet_diagnostic.rcs1048.severity = suggestion + +# Simplify boolean comparison +dotnet_diagnostic.rcs1049.severity = suggestion + +# Include/omit parentheses when creating new object +dotnet_diagnostic.rcs1050.severity = warning +# Options: roslynator_object_creation_parentheses_style + +# Add/remove parentheses from condition in conditional operator +dotnet_diagnostic.rcs1051.severity = warning +# Options: roslynator_conditional_operator_condition_parentheses_style + +# Declare each attribute separately +dotnet_diagnostic.rcs1052.severity = warning + +# Unnecessary semicolon at the end of declaration +dotnet_diagnostic.rcs1055.severity = silent + +# Avoid usage of using alias directive +dotnet_diagnostic.rcs1056.severity = warning + +# Use compound assignment +dotnet_diagnostic.rcs1058.severity = suggestion + +# Avoid locking on publicly accessible instance +dotnet_diagnostic.rcs1059.severity = warning + +# Declare each type in separate file +dotnet_diagnostic.rcs1060.severity = none + +# Merge 'if' with nested 'if' +dotnet_diagnostic.rcs1061.severity = silent + +# Simplify logical negation +dotnet_diagnostic.rcs1068.severity = suggestion + +# Remove unnecessary case label +dotnet_diagnostic.rcs1069.severity = silent + +# Remove redundant default switch section +dotnet_diagnostic.rcs1070.severity = silent + +# Remove redundant base constructor call +dotnet_diagnostic.rcs1071.severity = silent + +# Convert 'if' to 'return' statement +dotnet_diagnostic.rcs1073.severity = suggestion + +# Remove redundant constructor +dotnet_diagnostic.rcs1074.severity = silent + +# Avoid empty catch clause that catches System.Exception +dotnet_diagnostic.rcs1075.severity = warning + +# Optimize LINQ method call +dotnet_diagnostic.rcs1077.severity = suggestion + +# Use "" or 'string.Empty' +dotnet_diagnostic.rcs1078.severity = warning +# Options: roslynator_empty_string_style + +# Throwing of new NotImplementedException +dotnet_diagnostic.rcs1079.severity = warning + +# Use 'Count/Length' property instead of 'Any' method +dotnet_diagnostic.rcs1080.severity = warning + +# Split variable declaration +dotnet_diagnostic.rcs1081.severity = warning + +# Use coalesce expression instead of conditional expression +dotnet_diagnostic.rcs1084.severity = warning + +# Use auto-implemented property +dotnet_diagnostic.rcs1085.severity = suggestion + +# Use --/++ operator instead of assignment +dotnet_diagnostic.rcs1089.severity = warning + +# Add/remove 'ConfigureAwait(false)' call +dotnet_diagnostic.rcs1090.severity = warning +# Options: roslynator_configure_await + +# File contains no code +dotnet_diagnostic.rcs1093.severity = suggestion + +# Declare using directive on top level +dotnet_diagnostic.rcs1094.severity = warning + +# Use 'HasFlag' method or bitwise operator +dotnet_diagnostic.rcs1096.severity = warning +# Options: roslynator_enum_has_flag_style + +# Remove redundant 'ToString' call +dotnet_diagnostic.rcs1097.severity = suggestion + +# Constant values should be placed on right side of comparisons +dotnet_diagnostic.rcs1098.severity = suggestion + +# Default label should be the last label in a switch section +dotnet_diagnostic.rcs1099.severity = suggestion + +# Make class static +dotnet_diagnostic.rcs1102.severity = warning + +# Convert 'if' to assignment +dotnet_diagnostic.rcs1103.severity = suggestion + +# Simplify conditional expression +dotnet_diagnostic.rcs1104.severity = suggestion + +# Unnecessary interpolation +dotnet_diagnostic.rcs1105.severity = suggestion + +# Remove redundant 'ToCharArray' call +dotnet_diagnostic.rcs1107.severity = suggestion + +# Add 'static' modifier to all partial class declarations +dotnet_diagnostic.rcs1108.severity = suggestion + +# Declare type inside namespace +dotnet_diagnostic.rcs1110.severity = silent + +# Add braces to switch section with multiple statements +dotnet_diagnostic.rcs1111.severity = warning + +# Combine 'Enumerable.Where' method chain +dotnet_diagnostic.rcs1112.severity = suggestion + +# Use 'string.IsNullOrEmpty' method +dotnet_diagnostic.rcs1113.severity = suggestion + +# Remove redundant delegate creation +dotnet_diagnostic.rcs1114.severity = suggestion + +# Mark local variable as const +dotnet_diagnostic.rcs1118.severity = suggestion + +# Add parentheses when necessary +dotnet_diagnostic.rcs1123.severity = suggestion + +# Inline local variable +dotnet_diagnostic.rcs1124.severity = silent + +# Add braces to if-else +dotnet_diagnostic.rcs1126.severity = warning + +# Use coalesce expression +dotnet_diagnostic.rcs1128.severity = suggestion + +# Remove redundant field initialization +dotnet_diagnostic.rcs1129.severity = silent + +# Bitwise operation on enum without Flags attribute +dotnet_diagnostic.rcs1130.severity = suggestion + +# Remove redundant overriding member +dotnet_diagnostic.rcs1132.severity = suggestion + +# Remove redundant Dispose/Close call +dotnet_diagnostic.rcs1133.severity = silent + +# Remove redundant statement +dotnet_diagnostic.rcs1134.severity = silent + +# Declare enum member with zero value (when enum has FlagsAttribute) +dotnet_diagnostic.rcs1135.severity = suggestion + +# Merge switch sections with equivalent content +dotnet_diagnostic.rcs1136.severity = silent + +# Add summary to documentation comment +dotnet_diagnostic.rcs1138.severity = warning + +# Add summary element to documentation comment +dotnet_diagnostic.rcs1139.severity = warning + +# Add exception to documentation comment +dotnet_diagnostic.rcs1140.severity = silent + +# Add 'param' element to documentation comment +dotnet_diagnostic.rcs1141.severity = silent + +# Add 'typeparam' element to documentation comment +dotnet_diagnostic.rcs1142.severity = silent + +# Simplify coalesce expression +dotnet_diagnostic.rcs1143.severity = silent + +# Remove redundant 'as' operator +dotnet_diagnostic.rcs1145.severity = silent + +# Use conditional access +dotnet_diagnostic.rcs1146.severity = suggestion + +# Remove redundant cast +dotnet_diagnostic.rcs1151.severity = silent + +# Sort enum members +dotnet_diagnostic.rcs1154.severity = suggestion + +# Use StringComparison when comparing strings +dotnet_diagnostic.rcs1155.severity = warning + +# Use string.Length instead of comparison with empty string +dotnet_diagnostic.rcs1156.severity = suggestion + +# Composite enum value contains undefined flag +dotnet_diagnostic.rcs1157.severity = suggestion + +# Static member in generic type should use a type parameter +dotnet_diagnostic.rcs1158.severity = suggestion + +# Use EventHandler +dotnet_diagnostic.rcs1159.severity = suggestion + +# Abstract type should not have public constructors +dotnet_diagnostic.rcs1160.severity = suggestion + +# Enum should declare explicit values +dotnet_diagnostic.rcs1161.severity = silent + +# Avoid chain of assignments +dotnet_diagnostic.rcs1162.severity = warning + +# Unused parameter +dotnet_diagnostic.rcs1163.severity = suggestion + +# Unused type parameter +dotnet_diagnostic.rcs1164.severity = suggestion + +# Unconstrained type parameter checked for null +dotnet_diagnostic.rcs1165.severity = silent + +# Value type object is never equal to null +dotnet_diagnostic.rcs1166.severity = suggestion + +# Parameter name differs from base name +dotnet_diagnostic.rcs1168.severity = silent + +# Make field read-only +dotnet_diagnostic.rcs1169.severity = suggestion +# Options: roslynator_unity_code_analysis.enabled + +# Use read-only auto-implemented property +dotnet_diagnostic.rcs1170.severity = suggestion + +# Simplify lazy initialization +dotnet_diagnostic.rcs1171.severity = suggestion + +# Use 'is' operator instead of 'as' operator +dotnet_diagnostic.rcs1172.severity = warning + +# Use coalesce expression instead of 'if' +dotnet_diagnostic.rcs1173.severity = suggestion + +# Remove redundant async/await +dotnet_diagnostic.rcs1174.severity = warning + +# Unused 'this' parameter +dotnet_diagnostic.rcs1175.severity = suggestion + +# Unnecessary assignment +dotnet_diagnostic.rcs1179.severity = suggestion + +# Inline lazy initialization +dotnet_diagnostic.rcs1180.severity = suggestion + +# Convert comment to documentation comment +dotnet_diagnostic.rcs1181.severity = silent + +# Remove redundant base interface +dotnet_diagnostic.rcs1182.severity = silent + +# Use Regex instance instead of static method +dotnet_diagnostic.rcs1186.severity = silent + +# Use constant instead of field +dotnet_diagnostic.rcs1187.severity = suggestion + +# Remove redundant auto-property initialization +dotnet_diagnostic.rcs1188.severity = silent + +# Add or remove region name +dotnet_diagnostic.rcs1189.severity = silent + +# Join string expressions +dotnet_diagnostic.rcs1190.severity = suggestion + +# Declare enum value as combination of names +dotnet_diagnostic.rcs1191.severity = suggestion + +# Unnecessary usage of verbatim string literal +dotnet_diagnostic.rcs1192.severity = suggestion + +# Overriding member should not change 'params' modifier +dotnet_diagnostic.rcs1193.severity = warning + +# Implement exception constructors +dotnet_diagnostic.rcs1194.severity = warning + +# Use ^ operator +dotnet_diagnostic.rcs1195.severity = suggestion + +# Call extension method as instance method +dotnet_diagnostic.rcs1196.severity = suggestion + +# Optimize StringBuilder.Append/AppendLine call +dotnet_diagnostic.rcs1197.severity = suggestion + +# Avoid unnecessary boxing of value type +dotnet_diagnostic.rcs1198.severity = warning + +# Unnecessary null check +dotnet_diagnostic.rcs1199.severity = suggestion + +# Call 'Enumerable.ThenBy' instead of 'Enumerable.OrderBy' +dotnet_diagnostic.rcs1200.severity = suggestion + +# Use method chaining +dotnet_diagnostic.rcs1201.severity = silent + +# Avoid NullReferenceException +dotnet_diagnostic.rcs1202.severity = suggestion + +# Use AttributeUsageAttribute +dotnet_diagnostic.rcs1203.severity = warning + +# Use EventArgs.Empty +dotnet_diagnostic.rcs1204.severity = suggestion + +# Order named arguments according to the order of parameters +dotnet_diagnostic.rcs1205.severity = suggestion + +# Use conditional access instead of conditional expression +dotnet_diagnostic.rcs1206.severity = suggestion + +# Use anonymous function or method group +dotnet_diagnostic.rcs1207.severity = warning +# Options: roslynator_use_anonymous_function_or_method_group + +# Reduce 'if' nesting +dotnet_diagnostic.rcs1208.severity = warning + +# Order type parameter constraints +dotnet_diagnostic.rcs1209.severity = suggestion + +# Return completed task instead of returning null +dotnet_diagnostic.rcs1210.severity = warning + +# Remove unnecessary 'else' +dotnet_diagnostic.rcs1211.severity = silent + +# Remove redundant assignment +dotnet_diagnostic.rcs1212.severity = suggestion + +# Remove unused member declaration +dotnet_diagnostic.rcs1213.severity = suggestion +# Options: roslynator_unity_code_analysis.enabled + +# Unnecessary interpolated string +dotnet_diagnostic.rcs1214.severity = suggestion + +# Expression is always equal to true/false +dotnet_diagnostic.rcs1215.severity = warning + +# Unnecessary unsafe context +dotnet_diagnostic.rcs1216.severity = suggestion + +# Convert interpolated string to concatenation +dotnet_diagnostic.rcs1217.severity = silent + +# Simplify code branching +dotnet_diagnostic.rcs1218.severity = suggestion + +# Use pattern matching instead of combination of 'is' operator and cast operator +dotnet_diagnostic.rcs1220.severity = suggestion + +# Use pattern matching instead of combination of 'as' operator and null check +dotnet_diagnostic.rcs1221.severity = suggestion + +# Merge preprocessor directives +dotnet_diagnostic.rcs1222.severity = suggestion + +# Mark publicly visible type with DebuggerDisplay attribute +dotnet_diagnostic.rcs1223.severity = none + +# Make method an extension method +dotnet_diagnostic.rcs1224.severity = suggestion + +# Make class sealed +dotnet_diagnostic.rcs1225.severity = suggestion + +# Add paragraph to documentation comment +dotnet_diagnostic.rcs1226.severity = suggestion + +# Validate arguments correctly +dotnet_diagnostic.rcs1227.severity = suggestion + +# Unused element in a documentation comment +dotnet_diagnostic.rcs1228.severity = silent + +# Use async/await when necessary +dotnet_diagnostic.rcs1229.severity = suggestion + +# Unnecessary explicit use of enumerator +dotnet_diagnostic.rcs1230.severity = suggestion + +# Make parameter ref read-only +dotnet_diagnostic.rcs1231.severity = warning + +# Order elements in documentation comment +dotnet_diagnostic.rcs1232.severity = suggestion + +# Use short-circuiting operator +dotnet_diagnostic.rcs1233.severity = suggestion + +# Duplicate enum value +dotnet_diagnostic.rcs1234.severity = suggestion + +# Optimize method call +dotnet_diagnostic.rcs1235.severity = suggestion + +# Use exception filter +dotnet_diagnostic.rcs1236.severity = suggestion + +# Avoid nested ?: operators +dotnet_diagnostic.rcs1238.severity = silent + +# Use 'for' statement instead of 'while' statement +dotnet_diagnostic.rcs1239.severity = suggestion + +# Operator is unnecessary +dotnet_diagnostic.rcs1240.severity = suggestion + +# Implement non-generic counterpart +dotnet_diagnostic.rcs1241.severity = silent + +# Do not pass non-read-only struct by read-only reference +dotnet_diagnostic.rcs1242.severity = warning + +# Duplicate word in a comment +dotnet_diagnostic.rcs1243.severity = suggestion + +# Simplify 'default' expression +dotnet_diagnostic.rcs1244.severity = silent + +# Use element access +dotnet_diagnostic.rcs1246.severity = suggestion + +# Fix documentation comment tag +dotnet_diagnostic.rcs1247.severity = suggestion + +# Normalize null check +dotnet_diagnostic.rcs1248.severity = warning +# Options: roslynator_null_check_style + +# Unnecessary null-forgiving operator +dotnet_diagnostic.rcs1249.severity = suggestion + +# Use implicit/explicit object creation +dotnet_diagnostic.rcs1250.severity = warning +# Options: roslynator_object_creation_type_style, roslynator_use_collection_expression, roslynator_use_var_instead_of_implicit_object_creation + +# Remove unnecessary braces from record declaration +dotnet_diagnostic.rcs1251.severity = suggestion + +# Normalize usage of infinite loop +dotnet_diagnostic.rcs1252.severity = warning +# Options: roslynator_infinite_loop_style + +# Format documentation comment summary +dotnet_diagnostic.rcs1253.severity = warning +# Options: roslynator_doc_comment_summary_style + +# Normalize format of enum flag value +dotnet_diagnostic.rcs1254.severity = suggestion +# Options: roslynator_enum_flag_value_style + +# Simplify argument null check +dotnet_diagnostic.rcs1255.severity = warning + +# Invalid argument null check +dotnet_diagnostic.rcs1256.severity = suggestion + +# Use enum field explicitly +dotnet_diagnostic.rcs1257.severity = suggestion + +# Unnecessary enum flag +dotnet_diagnostic.rcs1258.severity = suggestion + +# Remove empty syntax +dotnet_diagnostic.rcs1259.severity = suggestion + +# Add/remove trailing comma +dotnet_diagnostic.rcs1260.severity = warning +# Options: roslynator_trailing_comma_style + +# Resource can be disposed asynchronously +dotnet_diagnostic.rcs1261.severity = suggestion + +# Unnecessary raw string literal +dotnet_diagnostic.rcs1262.severity = suggestion + +# Invalid reference in a documentation comment +dotnet_diagnostic.rcs1263.severity = warning + +# Use 'var' or explicit type +dotnet_diagnostic.rcs1264.severity = warning +# Options: roslynator_use_var + +# Remove redundant catch block +dotnet_diagnostic.rcs1265.severity = suggestion + +# Use raw string literal +dotnet_diagnostic.rcs1266.severity = suggestion + +# Use string interpolation instead of 'string.Concat' +dotnet_diagnostic.rcs1267.severity = suggestion + +# Simplify numeric comparison +dotnet_diagnostic.rcs1268.severity = suggestion + +# Use pattern matching +dotnet_diagnostic.rcs9001.severity = silent + +# Use property SyntaxNode.SpanStart +dotnet_diagnostic.rcs9002.severity = suggestion + +# Unnecessary conditional access +dotnet_diagnostic.rcs9003.severity = suggestion + +# Call 'Any' instead of accessing 'Count' +dotnet_diagnostic.rcs9004.severity = suggestion + +# Unnecessary null check +dotnet_diagnostic.rcs9005.severity = suggestion + +# Use element access +dotnet_diagnostic.rcs9006.severity = suggestion + +# Use return value +dotnet_diagnostic.rcs9007.severity = warning + +# Call 'Last' instead of using [] +dotnet_diagnostic.rcs9008.severity = suggestion + +# Unknown language name +dotnet_diagnostic.rcs9009.severity = warning + +# Specify ExportCodeRefactoringProviderAttribute.Name +dotnet_diagnostic.rcs9010.severity = silent + +# Specify ExportCodeFixProviderAttribute.Name +dotnet_diagnostic.rcs9011.severity = silent + + +# Refactorings + +roslynator_refactoring.add_all_properties_to_initializer.enabled = true +roslynator_refactoring.add_argument_name.enabled = true +roslynator_refactoring.add_braces.enabled = true +roslynator_refactoring.add_braces_to_if_else.enabled = true +roslynator_refactoring.add_braces_to_switch_section.enabled = true +roslynator_refactoring.add_braces_to_switch_sections.enabled = true +roslynator_refactoring.add_default_value_to_parameter.enabled = true +roslynator_refactoring.add_empty_line_between_declarations.enabled = true +roslynator_refactoring.add_exception_element_to_documentation_comment.enabled = true +roslynator_refactoring.add_generic_parameter_to_declaration.enabled = true +roslynator_refactoring.add_member_to_interface.enabled = true +roslynator_refactoring.add_missing_cases_to_switch.enabled = true +roslynator_refactoring.add_parameter_to_interface_member.enabled = true +roslynator_refactoring.add_tag_to_documentation_comment.enabled = true +roslynator_refactoring.add_using_directive.enabled = true +roslynator_refactoring.add_using_static_directive.enabled = true +roslynator_refactoring.call_extension_method_as_instance_method.enabled = true +roslynator_refactoring.call_indexof_instead_of_contains.enabled = true +roslynator_refactoring.change_accessibility.enabled = true +roslynator_refactoring.change_method_return_type_to_void.enabled = true +roslynator_refactoring.change_type_according_to_expression.enabled = true +roslynator_refactoring.check_expression_for_null.enabled = true +roslynator_refactoring.check_parameter_for_null.enabled = true +roslynator_refactoring.comment_out_member_declaration.enabled = true +roslynator_refactoring.comment_out_statement.enabled = true +roslynator_refactoring.convert_auto_property_to_full_property.enabled = true +roslynator_refactoring.convert_auto_property_to_full_property_without_backing_field.enabled = true +roslynator_refactoring.convert_block_body_to_expression_body.enabled = true +roslynator_refactoring.convert_comment_to_documentation_comment.enabled = true +roslynator_refactoring.convert_conditional_expression_to_if_else.enabled = true +roslynator_refactoring.convert_do_to_while.enabled = true +roslynator_refactoring.convert_expression_body_to_block_body.enabled = true +roslynator_refactoring.convert_for_to_foreach.enabled = true +roslynator_refactoring.convert_for_to_while.enabled = true +roslynator_refactoring.convert_foreach_to_for.enabled = true +roslynator_refactoring.convert_foreach_to_for_and_reverse_loop.enabled = false +roslynator_refactoring.convert_hasflag_call_to_bitwise_operation.enabled = true +roslynator_refactoring.convert_hexadecimal_literal_to_decimal_literal.enabled = true +roslynator_refactoring.convert_if_to_conditional_expression.enabled = true +roslynator_refactoring.convert_if_to_switch.enabled = true +roslynator_refactoring.convert_interpolated_string_to_concatenation.enabled = true +roslynator_refactoring.convert_interpolated_string_to_string_format.enabled = true +roslynator_refactoring.convert_interpolated_string_to_string_literal.enabled = true +roslynator_refactoring.convert_lambda_block_body_to_expression_body.enabled = true +roslynator_refactoring.convert_lambda_expression_body_to_block_body.enabled = true +roslynator_refactoring.convert_method_group_to_lambda.enabled = true +roslynator_refactoring.convert_regular_string_literal_to_verbatim_string_literal.enabled = true +roslynator_refactoring.convert_return_statement_to_if.enabled = true +roslynator_refactoring.convert_statements_to_if_else.enabled = true +roslynator_refactoring.convert_string_format_to_interpolated_string.enabled = true +roslynator_refactoring.convert_switch_expression_to_switch_statement.enabled = true +roslynator_refactoring.convert_switch_to_if.enabled = true +roslynator_refactoring.convert_verbatim_string_literal_to_regular_string_literal.enabled = true +roslynator_refactoring.convert_verbatim_string_literal_to_regular_string_literals.enabled = true +roslynator_refactoring.convert_while_to_do.enabled = true +roslynator_refactoring.convert_while_to_for.enabled = true +roslynator_refactoring.copy_argument.enabled = true +roslynator_refactoring.copy_documentation_comment_from_base_member.enabled = true +roslynator_refactoring.copy_member_declaration.enabled = true +roslynator_refactoring.copy_parameter.enabled = true +roslynator_refactoring.copy_statement.enabled = true +roslynator_refactoring.copy_switch_section.enabled = true +roslynator_refactoring.deconstruct_foreach_variable.enabled = true +roslynator_refactoring.expand_coalesce_expression.enabled = true +roslynator_refactoring.expand_compound_assignment.enabled = true +roslynator_refactoring.expand_event_declaration.enabled = true +roslynator_refactoring.expand_initializer.enabled = false +roslynator_refactoring.expand_positional_constructor.enabled = true +roslynator_refactoring.extract_event_handler_method.enabled = true +roslynator_refactoring.extract_expression_from_condition.enabled = true +roslynator_refactoring.extract_type_declaration_to_new_file.enabled = false +roslynator_refactoring.generate_base_constructors.enabled = true +roslynator_refactoring.generate_combined_enum_member.enabled = true +roslynator_refactoring.generate_enum_member.enabled = true +roslynator_refactoring.generate_enum_values.enabled = true +roslynator_refactoring.generate_event_invoking_method.enabled = true +roslynator_refactoring.generate_property_for_debuggerdisplay_attribute.enabled = true +roslynator_refactoring.implement_custom_enumerator.enabled = true +roslynator_refactoring.implement_iequatable.enabled = true +roslynator_refactoring.initialize_field_from_constructor.enabled = true +roslynator_refactoring.initialize_local_variable_with_default_value.enabled = true +roslynator_refactoring.inline_alias_expression.enabled = true +roslynator_refactoring.inline_constant.enabled = true +roslynator_refactoring.inline_constant_value.enabled = true +roslynator_refactoring.inline_method.enabled = true +roslynator_refactoring.inline_property.enabled = true +roslynator_refactoring.inline_using_static.enabled = true +roslynator_refactoring.insert_string_interpolation.enabled = true +roslynator_refactoring.introduce_and_initialize_field.enabled = true +roslynator_refactoring.introduce_and_initialize_property.enabled = true +roslynator_refactoring.introduce_constructor.enabled = false +roslynator_refactoring.introduce_field_to_lock_on.enabled = true +roslynator_refactoring.introduce_local_variable.enabled = true +roslynator_refactoring.invert_binary_expression.enabled = true +roslynator_refactoring.invert_boolean_literal.enabled = true +roslynator_refactoring.invert_conditional_expression.enabled = true +roslynator_refactoring.invert_if.enabled = true +roslynator_refactoring.invert_if_else.enabled = true +roslynator_refactoring.invert_is_expression.enabled = true +roslynator_refactoring.invert_linq_method_call.enabled = true +roslynator_refactoring.invert_operator.enabled = true +roslynator_refactoring.invert_prefix_or_postfix_unary_expression.enabled = true +roslynator_refactoring.join_string_expressions.enabled = true +roslynator_refactoring.make_member_abstract.enabled = true +roslynator_refactoring.make_member_virtual.enabled = true +roslynator_refactoring.merge_attributes.enabled = true +roslynator_refactoring.merge_if_statements.enabled = true +roslynator_refactoring.merge_if_with_parent_if.enabled = true +roslynator_refactoring.merge_local_declarations.enabled = true +roslynator_refactoring.merge_switch_sections.enabled = true +roslynator_refactoring.move_unsafe_context_to_containing_declaration.enabled = true +roslynator_refactoring.notify_when_property_changes.enabled = true +roslynator_refactoring.parenthesize_expression.enabled = true +roslynator_refactoring.promote_local_variable_to_parameter.enabled = true +roslynator_refactoring.remove_all_comments.enabled = true +roslynator_refactoring.remove_all_comments_except_documentation_comments.enabled = true +roslynator_refactoring.remove_all_documentation_comments.enabled = false +roslynator_refactoring.remove_all_member_declarations.enabled = true +roslynator_refactoring.remove_all_preprocessor_directives.enabled = true +roslynator_refactoring.remove_all_region_directives.enabled = true +roslynator_refactoring.remove_all_statements.enabled = true +roslynator_refactoring.remove_all_switch_sections.enabled = true +roslynator_refactoring.remove_argument_name.enabled = true +roslynator_refactoring.remove_async_await.enabled = true +roslynator_refactoring.remove_braces.enabled = true +roslynator_refactoring.remove_braces_from_if_else.enabled = true +roslynator_refactoring.remove_braces_from_switch_section.enabled = true +roslynator_refactoring.remove_braces_from_switch_sections.enabled = true +roslynator_refactoring.remove_comment.enabled = true +roslynator_refactoring.remove_condition_from_last_else.enabled = true +roslynator_refactoring.remove_containing_statement.enabled = true +roslynator_refactoring.remove_empty_lines.enabled = true +roslynator_refactoring.remove_enum_member_value.enabled = true +roslynator_refactoring.remove_instantiation_of_local_variable.enabled = true +roslynator_refactoring.remove_interpolation.enabled = true +roslynator_refactoring.remove_member_declaration.enabled = true +roslynator_refactoring.remove_member_declarations_above_or_below.enabled = true +roslynator_refactoring.remove_parentheses.enabled = true +roslynator_refactoring.remove_preprocessor_directive.enabled = true +roslynator_refactoring.remove_property_initializer.enabled = true +roslynator_refactoring.remove_region.enabled = true +roslynator_refactoring.remove_statement.enabled = true +roslynator_refactoring.remove_unnecessary_assignment.enabled = true +roslynator_refactoring.rename_identifier_according_to_type_name.enabled = true +roslynator_refactoring.rename_method_according_to_type_name.enabled = true +roslynator_refactoring.rename_parameter_according_to_type_name.enabled = true +roslynator_refactoring.rename_property_according_to_type_name.enabled = true +roslynator_refactoring.replace_as_expression_with_explicit_cast.enabled = true +roslynator_refactoring.replace_conditional_expression_with_true_or_false_branch.enabled = true +roslynator_refactoring.replace_equality_operator_with_string_equals.enabled = true +roslynator_refactoring.replace_equality_operator_with_string_isnullorempty.enabled = true +roslynator_refactoring.replace_equality_operator_with_string_isnullorwhitespace.enabled = true +roslynator_refactoring.replace_explicit_cast_with_as_expression.enabled = true +roslynator_refactoring.replace_interpolated_string_with_interpolation_expression.enabled = true +roslynator_refactoring.replace_method_with_property.enabled = false +roslynator_refactoring.replace_null_literal_with_default_expression.enabled = true +roslynator_refactoring.replace_prefix_operator_with_postfix_operator.enabled = true +roslynator_refactoring.replace_property_with_method.enabled = true +roslynator_refactoring.reverse_for_statement.enabled = true +roslynator_refactoring.simplify_if.enabled = true +roslynator_refactoring.sort_case_labels.enabled = true +roslynator_refactoring.sort_member_declarations.enabled = true +roslynator_refactoring.split_attributes.enabled = true +roslynator_refactoring.split_if.enabled = true +roslynator_refactoring.split_if_else.enabled = true +roslynator_refactoring.split_local_declaration_and_assignment.enabled = true +roslynator_refactoring.split_switch_labels.enabled = true +roslynator_refactoring.split_variable_declaration.enabled = true +roslynator_refactoring.swap_binary_operands.enabled = true +roslynator_refactoring.swap_member_declarations.enabled = true +roslynator_refactoring.sync_property_name_and_backing_field_name.enabled = true +roslynator_refactoring.uncomment_multiline_comment.enabled = true +roslynator_refactoring.uncomment_singleline_comment.enabled = true +roslynator_refactoring.use_coalesce_expression_instead_of_if.enabled = true +roslynator_refactoring.use_constant_instead_of_readonly_field.enabled = true +roslynator_refactoring.use_element_access_instead_of_linq_method.enabled = true +roslynator_refactoring.use_enumerator_explicitly.enabled = true +roslynator_refactoring.use_explicit_type.enabled = true +roslynator_refactoring.use_implicit_type.enabled = true +roslynator_refactoring.use_index_initializer.enabled = true +roslynator_refactoring.use_lambda_instead_of_anonymous_method.enabled = true +roslynator_refactoring.use_list_instead_of_yield.enabled = true +roslynator_refactoring.use_object_initializer.enabled = true +roslynator_refactoring.use_readonly_field_instead_of_constant.enabled = true +roslynator_refactoring.use_string_empty_instead_of_empty_string_literal.enabled = false +roslynator_refactoring.use_stringbuilder_instead_of_concatenation.enabled = true +roslynator_refactoring.wrap_arguments.enabled = true +roslynator_refactoring.wrap_binary_expression.enabled = true +roslynator_refactoring.wrap_call_chain.enabled = true +roslynator_refactoring.wrap_conditional_expression.enabled = true +roslynator_refactoring.wrap_constraint_clauses.enabled = true +roslynator_refactoring.wrap_initializer_expressions.enabled = true +roslynator_refactoring.wrap_lines_in_preprocessor_directive.enabled = true +roslynator_refactoring.wrap_lines_in_region.enabled = true +roslynator_refactoring.wrap_lines_in_try_catch.enabled = true +roslynator_refactoring.wrap_parameters.enabled = true +roslynator_refactoring.wrap_statements_in_condition.enabled = true +roslynator_refactoring.wrap_statements_in_using_statement.enabled = true + +# Compiler diagnostic fixes + +roslynator_compiler_diagnostic_fix.cs0019.enabled = true +roslynator_compiler_diagnostic_fix.cs0021.enabled = true +roslynator_compiler_diagnostic_fix.cs0023.enabled = true +roslynator_compiler_diagnostic_fix.cs0029.enabled = true +roslynator_compiler_diagnostic_fix.cs0030.enabled = true +roslynator_compiler_diagnostic_fix.cs0037.enabled = true +roslynator_compiler_diagnostic_fix.cs0069.enabled = true +roslynator_compiler_diagnostic_fix.cs0077.enabled = true +roslynator_compiler_diagnostic_fix.cs0080.enabled = true +roslynator_compiler_diagnostic_fix.cs0101.enabled = true +roslynator_compiler_diagnostic_fix.cs0102.enabled = true +roslynator_compiler_diagnostic_fix.cs0103.enabled = true +roslynator_compiler_diagnostic_fix.cs0106.enabled = true +roslynator_compiler_diagnostic_fix.cs0107.enabled = true +roslynator_compiler_diagnostic_fix.cs0108.enabled = true +roslynator_compiler_diagnostic_fix.cs0109.enabled = true +roslynator_compiler_diagnostic_fix.cs0112.enabled = true +roslynator_compiler_diagnostic_fix.cs0114.enabled = true +roslynator_compiler_diagnostic_fix.cs0115.enabled = true +roslynator_compiler_diagnostic_fix.cs0119.enabled = true +roslynator_compiler_diagnostic_fix.cs0120.enabled = true +roslynator_compiler_diagnostic_fix.cs0123.enabled = true +roslynator_compiler_diagnostic_fix.cs0126.enabled = true +roslynator_compiler_diagnostic_fix.cs0127.enabled = true +roslynator_compiler_diagnostic_fix.cs0128.enabled = true +roslynator_compiler_diagnostic_fix.cs0131.enabled = true +roslynator_compiler_diagnostic_fix.cs0132.enabled = true +roslynator_compiler_diagnostic_fix.cs0133.enabled = true +roslynator_compiler_diagnostic_fix.cs0136.enabled = true +roslynator_compiler_diagnostic_fix.cs0139.enabled = true +roslynator_compiler_diagnostic_fix.cs0152.enabled = true +roslynator_compiler_diagnostic_fix.cs0161.enabled = true +roslynator_compiler_diagnostic_fix.cs0162.enabled = true +roslynator_compiler_diagnostic_fix.cs0163.enabled = true +roslynator_compiler_diagnostic_fix.cs0164.enabled = true +roslynator_compiler_diagnostic_fix.cs0165.enabled = true +roslynator_compiler_diagnostic_fix.cs0168.enabled = true +roslynator_compiler_diagnostic_fix.cs0173.enabled = true +roslynator_compiler_diagnostic_fix.cs0177.enabled = true +roslynator_compiler_diagnostic_fix.cs0191.enabled = true +roslynator_compiler_diagnostic_fix.cs0192.enabled = true +roslynator_compiler_diagnostic_fix.cs0201.enabled = true +roslynator_compiler_diagnostic_fix.cs0214.enabled = true +roslynator_compiler_diagnostic_fix.cs0216.enabled = true +roslynator_compiler_diagnostic_fix.cs0219.enabled = true +roslynator_compiler_diagnostic_fix.cs0221.enabled = true +roslynator_compiler_diagnostic_fix.cs0225.enabled = true +roslynator_compiler_diagnostic_fix.cs0238.enabled = true +roslynator_compiler_diagnostic_fix.cs0246.enabled = true +roslynator_compiler_diagnostic_fix.cs0260.enabled = true +roslynator_compiler_diagnostic_fix.cs0262.enabled = true +roslynator_compiler_diagnostic_fix.cs0266.enabled = true +roslynator_compiler_diagnostic_fix.cs0267.enabled = true +roslynator_compiler_diagnostic_fix.cs0272.enabled = true +roslynator_compiler_diagnostic_fix.cs0275.enabled = true +roslynator_compiler_diagnostic_fix.cs0305.enabled = true +roslynator_compiler_diagnostic_fix.cs0401.enabled = true +roslynator_compiler_diagnostic_fix.cs0403.enabled = true +roslynator_compiler_diagnostic_fix.cs0405.enabled = true +roslynator_compiler_diagnostic_fix.cs0407.enabled = true +roslynator_compiler_diagnostic_fix.cs0409.enabled = true +roslynator_compiler_diagnostic_fix.cs0428.enabled = true +roslynator_compiler_diagnostic_fix.cs0441.enabled = true +roslynator_compiler_diagnostic_fix.cs0442.enabled = true +roslynator_compiler_diagnostic_fix.cs0449.enabled = true +roslynator_compiler_diagnostic_fix.cs0450.enabled = true +roslynator_compiler_diagnostic_fix.cs0451.enabled = true +roslynator_compiler_diagnostic_fix.cs0472.enabled = true +roslynator_compiler_diagnostic_fix.cs0500.enabled = true +roslynator_compiler_diagnostic_fix.cs0501.enabled = true +roslynator_compiler_diagnostic_fix.cs0507.enabled = true +roslynator_compiler_diagnostic_fix.cs0508.enabled = true +roslynator_compiler_diagnostic_fix.cs0513.enabled = true +roslynator_compiler_diagnostic_fix.cs0515.enabled = true +roslynator_compiler_diagnostic_fix.cs0524.enabled = true +roslynator_compiler_diagnostic_fix.cs0525.enabled = true +roslynator_compiler_diagnostic_fix.cs0527.enabled = true +roslynator_compiler_diagnostic_fix.cs0531.enabled = true +roslynator_compiler_diagnostic_fix.cs0539.enabled = true +roslynator_compiler_diagnostic_fix.cs0541.enabled = true +roslynator_compiler_diagnostic_fix.cs0549.enabled = true +roslynator_compiler_diagnostic_fix.cs0558.enabled = true +roslynator_compiler_diagnostic_fix.cs0567.enabled = true +roslynator_compiler_diagnostic_fix.cs0568.enabled = true +roslynator_compiler_diagnostic_fix.cs0573.enabled = true +roslynator_compiler_diagnostic_fix.cs0574.enabled = true +roslynator_compiler_diagnostic_fix.cs0575.enabled = true +roslynator_compiler_diagnostic_fix.cs0579.enabled = true +roslynator_compiler_diagnostic_fix.cs0592.enabled = true +roslynator_compiler_diagnostic_fix.cs0621.enabled = true +roslynator_compiler_diagnostic_fix.cs0628.enabled = true +roslynator_compiler_diagnostic_fix.cs0659.enabled = true +roslynator_compiler_diagnostic_fix.cs0660.enabled = true +roslynator_compiler_diagnostic_fix.cs0661.enabled = true +roslynator_compiler_diagnostic_fix.cs0678.enabled = true +roslynator_compiler_diagnostic_fix.cs0693.enabled = true +roslynator_compiler_diagnostic_fix.cs0708.enabled = true +roslynator_compiler_diagnostic_fix.cs0710.enabled = true +roslynator_compiler_diagnostic_fix.cs0713.enabled = true +roslynator_compiler_diagnostic_fix.cs0714.enabled = true +roslynator_compiler_diagnostic_fix.cs0718.enabled = true +roslynator_compiler_diagnostic_fix.cs0750.enabled = true +roslynator_compiler_diagnostic_fix.cs0751.enabled = true +roslynator_compiler_diagnostic_fix.cs0753.enabled = true +roslynator_compiler_diagnostic_fix.cs0756.enabled = true +roslynator_compiler_diagnostic_fix.cs0759.enabled = true +roslynator_compiler_diagnostic_fix.cs0766.enabled = true +roslynator_compiler_diagnostic_fix.cs0815.enabled = true +roslynator_compiler_diagnostic_fix.cs0819.enabled = true +roslynator_compiler_diagnostic_fix.cs0822.enabled = true +roslynator_compiler_diagnostic_fix.cs1002.enabled = true +roslynator_compiler_diagnostic_fix.cs1003.enabled = true +roslynator_compiler_diagnostic_fix.cs1004.enabled = true +roslynator_compiler_diagnostic_fix.cs1012.enabled = true +roslynator_compiler_diagnostic_fix.cs1023.enabled = true +roslynator_compiler_diagnostic_fix.cs1031.enabled = true +roslynator_compiler_diagnostic_fix.cs1057.enabled = true +roslynator_compiler_diagnostic_fix.cs1061.enabled = true +roslynator_compiler_diagnostic_fix.cs1100.enabled = true +roslynator_compiler_diagnostic_fix.cs1105.enabled = true +roslynator_compiler_diagnostic_fix.cs1106.enabled = true +roslynator_compiler_diagnostic_fix.cs1503.enabled = true +roslynator_compiler_diagnostic_fix.cs1522.enabled = true +roslynator_compiler_diagnostic_fix.cs1526.enabled = true +roslynator_compiler_diagnostic_fix.cs1527.enabled = true +roslynator_compiler_diagnostic_fix.cs1591.enabled = true +roslynator_compiler_diagnostic_fix.cs1597.enabled = true +roslynator_compiler_diagnostic_fix.cs1609.enabled = true +roslynator_compiler_diagnostic_fix.cs1615.enabled = true +roslynator_compiler_diagnostic_fix.cs1620.enabled = true +roslynator_compiler_diagnostic_fix.cs1621.enabled = true +roslynator_compiler_diagnostic_fix.cs1622.enabled = true +roslynator_compiler_diagnostic_fix.cs1623.enabled = true +roslynator_compiler_diagnostic_fix.cs1624.enabled = true +roslynator_compiler_diagnostic_fix.cs1643.enabled = true +roslynator_compiler_diagnostic_fix.cs1674.enabled = true +roslynator_compiler_diagnostic_fix.cs1689.enabled = true +roslynator_compiler_diagnostic_fix.cs1715.enabled = true +roslynator_compiler_diagnostic_fix.cs1717.enabled = true +roslynator_compiler_diagnostic_fix.cs1722.enabled = true +roslynator_compiler_diagnostic_fix.cs1737.enabled = true +roslynator_compiler_diagnostic_fix.cs1741.enabled = true +roslynator_compiler_diagnostic_fix.cs1743.enabled = true +roslynator_compiler_diagnostic_fix.cs1750.enabled = true +roslynator_compiler_diagnostic_fix.cs1751.enabled = true +roslynator_compiler_diagnostic_fix.cs1955.enabled = true +roslynator_compiler_diagnostic_fix.cs1983.enabled = true +roslynator_compiler_diagnostic_fix.cs1988.enabled = true +roslynator_compiler_diagnostic_fix.cs1994.enabled = true +roslynator_compiler_diagnostic_fix.cs1997.enabled = true +roslynator_compiler_diagnostic_fix.cs3000.enabled = true +roslynator_compiler_diagnostic_fix.cs3001.enabled = true +roslynator_compiler_diagnostic_fix.cs3002.enabled = true +roslynator_compiler_diagnostic_fix.cs3003.enabled = true +roslynator_compiler_diagnostic_fix.cs3005.enabled = true +roslynator_compiler_diagnostic_fix.cs3006.enabled = true +roslynator_compiler_diagnostic_fix.cs3007.enabled = true +roslynator_compiler_diagnostic_fix.cs3008.enabled = true +roslynator_compiler_diagnostic_fix.cs3009.enabled = true +roslynator_compiler_diagnostic_fix.cs3016.enabled = true +roslynator_compiler_diagnostic_fix.cs3024.enabled = true +roslynator_compiler_diagnostic_fix.cs3027.enabled = true +roslynator_compiler_diagnostic_fix.cs7036.enabled = true +roslynator_compiler_diagnostic_fix.cs8050.enabled = true +roslynator_compiler_diagnostic_fix.cs8070.enabled = true +roslynator_compiler_diagnostic_fix.cs8112.enabled = true +roslynator_compiler_diagnostic_fix.cs8139.enabled = true +roslynator_compiler_diagnostic_fix.cs8340.enabled = true +roslynator_compiler_diagnostic_fix.cs8403.enabled = true +roslynator_compiler_diagnostic_fix.cs8600.enabled = true +roslynator_compiler_diagnostic_fix.cs8602.enabled = true +roslynator_compiler_diagnostic_fix.cs8604.enabled = true +roslynator_compiler_diagnostic_fix.cs8610.enabled = true +roslynator_compiler_diagnostic_fix.cs8618.enabled = true +roslynator_compiler_diagnostic_fix.cs8625.enabled = true +roslynator_compiler_diagnostic_fix.cs8632.enabled = true +roslynator_compiler_diagnostic_fix.cs8765.enabled = true +roslynator_compiler_diagnostic_fix.cs8767.enabled = true + +# The analysis performed by RS1022 is slow and relies on implementation +# details of the JIT compiler for correctness. +# Authors of compiler extensions are encouraged to use the stricter +# (and faster) analyzer RS1038 instead of this rule. +roslyn_correctness.assembly_reference_validation = relaxed \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..de17764 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,93 @@ +name: Bug Report +description: Report a bug in FastComponents +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug! Please fill out the information below to help us investigate. + + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of the bug. + placeholder: Describe the bug... + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Create a component with... + 2. Register endpoint... + 3. Send request to... + 4. See error... + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What you expected to happen. + placeholder: Describe what should have happened... + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What actually happened. + placeholder: Describe what actually happened... + validations: + required: true + + - type: input + id: dotnet-version + attributes: + label: .NET SDK Version + description: Output of `dotnet --version` + placeholder: "9.0.100" + validations: + required: true + + - type: input + id: package-version + attributes: + label: FastComponents Version + description: The version of FastComponents you are using. + placeholder: "1.0.0" + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Operating System + options: + - Windows + - macOS + - Linux + validations: + required: true + + - type: textarea + id: code + attributes: + label: Minimal Reproduction Code + description: If possible, provide a minimal code sample that reproduces the issue. + render: csharp + placeholder: | + // Your component or setup code here + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other context, screenshots, or logs. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..112246d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,69 @@ +name: Feature Request +description: Suggest a new feature for FastComponents +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a feature! Please describe your idea below. + + - type: textarea + id: problem + attributes: + label: Problem Statement + description: What problem does this feature solve? Is it related to a frustration? + placeholder: I'm frustrated when... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Describe the solution you'd like. + placeholder: I would like... + validations: + required: true + + - type: textarea + id: api + attributes: + label: Proposed API + description: If applicable, sketch out what the API might look like. + render: csharp + placeholder: | + // Example API usage + builder.Services.AddFastComponents(options => + { + options.YourNewFeature = true; + }); + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Describe any alternative solutions or features you've considered. + placeholder: I also considered... + + - type: dropdown + id: area + attributes: + label: Area + description: Which part of FastComponents does this relate to? + options: + - Components (HtmxComponentBase, HtmxTag) + - Endpoints (routing, registration) + - Source Generators + - Services (rendering, HTML processing) + - Documentation + - Demo Application + - Other + validations: + required: true + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other context, mockups, or examples. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..163e08d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## Summary + + + +## Changes + + +- + +## Related Issues + + + +## Type of Change + +- [ ] Bug fix (non-breaking change that fixes an issue) +- [ ] New feature (non-breaking change that adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] Documentation update +- [ ] Refactoring (no functional changes) +- [ ] Dependency update + +## Checklist + +- [ ] My code follows the project's code style (`.editorconfig` rules) +- [ ] I have added tests that prove my fix/feature works +- [ ] All new and existing tests pass (`dotnet test`) +- [ ] I have updated documentation for any changed public APIs +- [ ] The build succeeds with no warnings (`dotnet build --configuration Release`) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0328098..abb8175 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,9 +123,9 @@ jobs: deploy: # Publish only when creating a GitHub Release # https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository - # You can update this logic if you want to manage releases differently + if: github.event_name == 'release' runs-on: ubuntu-latest - needs: [ validate_nuget, run_test, update_release_draft ] + needs: [ validate_nuget, run_test ] steps: # Download the NuGet package created in the previous job - uses: actions/download-artifact@v8 diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 0000000..cb5a9d5 --- /dev/null +++ b/.globalconfig @@ -0,0 +1,64 @@ +is_global = true + +# Enforce explicit type usage (not var) +dotnet_diagnostic.IDE0007.severity = none # Use 'var' instead of explicit type +dotnet_diagnostic.IDE0008.severity = warning # Use explicit type instead of 'var' + +# Naming conventions +dotnet_diagnostic.IDE1006.severity = warning # Naming rule violation + +# Code quality +dotnet_diagnostic.CA1031.severity = suggestion # Do not catch general exception types +dotnet_diagnostic.CA1062.severity = suggestion # Validate arguments of public methods +dotnet_diagnostic.CA1303.severity = suggestion # Do not pass literals as localized parameters +dotnet_diagnostic.CA1305.severity = suggestion # Specify IFormatProvider +dotnet_diagnostic.CA1707.severity = warning # Identifiers should not contain underscores (except tests) +dotnet_diagnostic.CA1822.severity = suggestion # Mark members as static +dotnet_diagnostic.CA2227.severity = suggestion # Collection properties should be read only + +# Performance +dotnet_diagnostic.CA1805.severity = warning # Do not initialize unnecessarily +dotnet_diagnostic.CA1819.severity = suggestion # Properties should not return arrays +dotnet_diagnostic.CA1859.severity = suggestion # Use concrete types when possible for improved performance +dotnet_diagnostic.CA1860.severity = suggestion # Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1861.severity = suggestion # Avoid constant arrays as arguments + +# Reliability +dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task (not needed in ASP.NET Core) + +# Style +dotnet_diagnostic.IDE0008.severity = none # Use 'var' instead of explicit type +dotnet_diagnostic.IDE0022.severity = suggestion # Use expression body for methods +dotnet_diagnostic.IDE0039.severity = suggestion # Use local function +dotnet_diagnostic.IDE0058.severity = suggestion # Expression value is never used +dotnet_diagnostic.IDE0059.severity = warning # Value assigned is never used +dotnet_diagnostic.IDE0060.severity = warning # Remove unused parameter +dotnet_diagnostic.IDE0090.severity = suggestion # Use 'new(...)' +dotnet_diagnostic.IDE0161.severity = warning # Use file-scoped namespace +dotnet_diagnostic.IDE0200.severity = none # Anonymous function can be made static + +# Design +dotnet_diagnostic.CA1000.severity = suggestion # Do not declare static members on generic types +dotnet_diagnostic.CA1010.severity = suggestion # Collections should implement generic interface +dotnet_diagnostic.CA1051.severity = warning # Do not declare visible instance fields + +# Usage +dotnet_diagnostic.CA2201.severity = warning # Do not raise reserved exception types +dotnet_diagnostic.CA2208.severity = warning # Instantiate argument exceptions correctly +dotnet_diagnostic.CA2211.severity = warning # Non-constant fields should not be visible + +# Roslynator integration +dotnet_diagnostic.RCS1001.severity = none # Add braces (handled by editorconfig) +dotnet_diagnostic.RCS1003.severity = none # Add braces to if-else (handled by editorconfig) +dotnet_diagnostic.RCS1007.severity = none # Add braces (handled by editorconfig) +dotnet_diagnostic.RCS1008.severity = none # Use explicit type instead of 'var' (when the type is not obvious) +dotnet_diagnostic.RCS1009.severity = none # Use explicit type instead of 'var' (foreach variable) +dotnet_diagnostic.RCS1010.severity = warning # Use explicit type instead of 'var' (when the type is obvious) +dotnet_diagnostic.RCS1018.severity = warning # Add accessibility modifiers +dotnet_diagnostic.RCS1036.severity = warning # Remove redundant empty line +dotnet_diagnostic.RCS1037.severity = warning # Remove trailing white-space +dotnet_diagnostic.RCS1090.severity = none # Call 'ConfigureAwait(false)' (not needed in ASP.NET Core) +dotnet_diagnostic.RCS1118.severity = warning # Mark local variable as const +dotnet_diagnostic.RCS1124.severity = none # Inline local variable +dotnet_diagnostic.RCS1163.severity = none # Unused parameter (handled by IDE0060) +dotnet_diagnostic.RCS1194.severity = warning # Implement exception constructors \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6745982 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,47 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + + // C# specific settings + "omnisharp.enableRoslynAnalyzers": true, + "omnisharp.enableEditorConfigSupport": true, + "omnisharp.enableImportCompletion": true, + + // Code style preferences aligned with .editorconfig + "csharp.format.enable": true, + "[csharp]": { + "editor.defaultFormatter": "ms-dotnettools.csharp", + "editor.tabSize": 4, + "editor.insertSpaces": true, + "editor.detectIndentation": false + }, + + // File associations + "files.associations": { + "*.csproj": "xml", + "*.props": "xml", + "*.targets": "xml", + ".editorconfig": "editorconfig" + }, + + // Exclude unnecessary files from explorer + "files.exclude": { + "**/bin": true, + "**/obj": true, + "**/.vs": true, + "**/*.user": true + }, + + // Search exclusions + "search.exclude": { + "**/bin": true, + "**/obj": true, + "**/node_modules": true, + "**/bower_components": true, + "**/.git": true + } +} \ No newline at end of file diff --git a/API_COMPARISON.md b/API_COMPARISON.md new file mode 100644 index 0000000..3ce2b57 --- /dev/null +++ b/API_COMPARISON.md @@ -0,0 +1,177 @@ +# FastComponents API Simplification + +## 🎯 **Simplified API Comparison** + +### **Before: Current Complex API** + +#### 1. Component Creation (4+ files needed) +```csharp +// CounterExample.razor.cs +[GenerateParameterMethods] +public partial record CounterParameters : HtmxComponentParameters +{ + public int Count { get; init; } = 10; + + public string Increment() + { + var parameters = this with { Count = Count + 1 }; + return parameters.ToComponentUrl(HtmxRoutes.RouteCounter); + } +} + +// HtmxRoutes.cs +public const string RouteCounter = "/ui/examples/counter"; + +// HtmxEndpointConfiguration.cs +app.MapHtmxGet(RouteCounter).AllowAnonymous(); + +// JsonSerializerContext.cs +[JsonSerializable(typeof(CounterParameters))] +``` + +#### 2. Component Template +```razor +@inherits HtmxComponentBase + +
+
+ + + Increment + + + +
+
+``` + +--- + +### **After: Simplified API** + +#### 1. Component Creation (1 file only!) +```razor +@inherits SimpleHtmxComponent + +
+
+ @HtmxBuilderExtensions.Button("+", Url(s => s with { Count = s.Count + 1 }), "counter") + @State.Count + @HtmxBuilderExtensions.Button("-", Url(s => s with { Count = s.Count - 1 }), "counter") +
+
+ +public record CounterState { public int Count { get; init; } = 0; } +``` + +#### 2. Registration (1 line!) +```csharp +// Program.cs +services.AddFastComponentsAuto(); // Replaces manual service registration +app.UseFastComponentsAuto(); // Replaces manual endpoint mapping +``` + +--- + +## 🚀 **Key Improvements** + +### **1. Reduced Boilerplate by 80%** +- ❌ No more separate `.razor.cs` files +- ❌ No more `[GenerateParameterMethods]` attributes +- ❌ No more manual endpoint mapping +- ❌ No more JSON serialization registration +- ✅ Convention-based routing: `/htmx/counter` +- ✅ Auto-discovery of components + +### **2. Fluent Builder API** +```csharp +// Before: Verbose HtmxTag syntax + + +// After: Fluent builder +@HtmxBuilder.Button().GetSelf("/url", "target").Text("Click me") + +// Or helper methods for common patterns +@HtmxBuilderExtensions.Button("Click me", "/url", "target") +@HtmxBuilderExtensions.SearchInput("/search", "#results") +@HtmxBuilderExtensions.LoadContainer("/load-content") +``` + +### **3. Smart Defaults & Patterns** +```csharp +// Common patterns made simple +HtmxPatterns.SelfUpdatingButton(url, id) // Button that updates itself +HtmxPatterns.SearchInput(url, target) // Search with debouncing +HtmxPatterns.LoadOnce(url) // Load content once on page load + +// Smart defaults +HtmxDefaults.Swap // "outerHTML" +HtmxDefaults.SearchTrigger // "keyup changed delay:300ms" +HtmxDefaults.LoadOnceTrigger // "load once" +``` + +### **4. Type-Safe State Management** +```csharp +// Before: Complex parameter methods +public string Increment() +{ + var parameters = this with { Count = Count + 1 }; + return parameters.ToComponentUrl(HtmxRoutes.RouteCounter); +} + +// After: Simple lambda expressions +Url(s => s with { Count = s.Count + 1 }) // Much cleaner! +Url(new CounterState { Count = State.Count + 1 }) // Or explicit +``` + +--- + +## 🎯 **Migration Path** + +### **Phase 1: Backwards Compatible** +- Keep existing `HtmxComponentBase` API +- Add new `SimpleHtmxComponent` alongside +- Add convention-based registration as opt-in + +### **Phase 2: Gradual Adoption** +- Migrate examples to simplified API +- Show performance/maintainability benefits +- Provide migration tooling + +### **Phase 3: Stabilization** +- Mark old API as `[Obsolete]` but still supported +- Focus documentation on simplified API +- Community feedback integration + +--- + +## 🔥 **Developer Experience Benefits** + +### **For Beginners** +- ✅ **5-minute setup** - Add components, registration is automatic +- ✅ **No magic** - Clear, convention-based patterns +- ✅ **Less to learn** - Focus on HTMX concepts, not FastComponents boilerplate + +### **For Experts** +- ✅ **Less maintenance** - Fewer files, less configuration +- ✅ **Better debugging** - Simpler call stacks, clearer errors +- ✅ **Higher productivity** - Build components faster + +### **Common Use Cases Made Trivial** +```csharp +// Counter component +@HtmxBuilderExtensions.Button("+", Url(s => s with { Count = s.Count + 1 }), "counter") + +// Search component +@HtmxBuilderExtensions.SearchInput("/search", "#results", "Search movies...") + +// Auto-loading content +@HtmxBuilderExtensions.LoadContainer("/load-data", "Loading...") + +// Form that posts to server +@HtmxBuilder.Form().PostTo("/submit", "#result").Content(@) +``` + +This simplified API maintains all the power of FastComponents while dramatically reducing complexity for developers! \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..cbc6bca --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,138 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +FastComponents is a .NET 9.0 Razor Class Library that enables building HTMX-powered web applications using Blazor components with server-side rendering. It provides type-safe C# properties for all HTMX attributes and integrates with ASP.NET Minimal APIs for endpoint routing. + +The library simplifies building Multiple Resources Applications (MRA) by combining the component model of Blazor with the simplicity of HTMX, allowing developers to build dynamic web applications without writing JavaScript. + +## Common Development Commands + +```bash +# Build the solution +dotnet build + +# Build in Release mode +dotnet build --configuration Release + +# Run all tests +dotnet test + +# Run tests with coverage and generate HTML report +./coverage.sh + +# Run specific test project +dotnet test tests/FastComponents.UnitTests/FastComponents.UnitTests.csproj +dotnet test tests/FastComponents.Generators.UnitTests/FastComponents.Generators.UnitTests.csproj + +# Run a single test +dotnet test --filter "FullyQualifiedName~TestMethodName" +dotnet test --filter "DisplayName~TestMethodName" + +# Pack NuGet package +dotnet pack --configuration Release + +# Run the demo application +dotnet run --project demo/HtmxAppServer/HtmxAppServer.csproj + +# Watch mode for development +dotnet watch --project demo/HtmxAppServer/HtmxAppServer.csproj + +# Clean build artifacts +dotnet clean +``` + +## Architecture + +### Core Components + +- **HtmxComponentBase** (`src/FastComponents/Components/Base/HtmxComponentBase.cs`) - Base class providing HTMX attribute properties for all components +- **SimpleHtmxComponent** (`src/FastComponents/Components/Base/SimpleHtmxComponent.cs`) - Simplified base class with automatic route generation +- **ComponentHtmlResponseService** (`src/FastComponents/Services/ComponentHtmlResponseService.cs`) - Renders Blazor components as HTML responses for HTMX requests +- **HtmxComponentEndpoints** (`src/FastComponents/Endpoints/HtmxComponentEndpoints.cs`) - ASP.NET Minimal API integration for component routing +- **ClassNamesBuilder** (`src/FastComponents/Components/Base/ClassNamesBuilder.cs`) - Fluent API for building CSS class names +- **HtmxBuilder** (`src/FastComponents/Builders/HtmxBuilder.cs`) - Fluent builder for creating HTMX elements programmatically +- **FastComponents.Generators** (`src/FastComponents.Generators/`) - Source generators for parameter method generation + +### HTMX Integration Pattern + +Components inherit from `HtmxComponentBase` to gain access to HTMX attributes: +- Core attributes: HxGet, HxPost, HxTrigger, HxTarget, HxSwap +- Additional attributes: HxConfirm, HxDisable, HxIndicator, etc. +- CSS classes: HxCssAdded, HxCssRequest, HxCssSwapping, etc. + +### MRA (Multiple Resources Application) Architecture + +The project follows an MRA pattern where: +1. Server renders Blazor components as HTML +2. HTMX handles dynamic updates without full page reloads +3. ASP.NET Minimal APIs provide endpoints for component requests + +### Simplified API and Convention-Based Registration + +FastComponents provides two API approaches: + +1. **Explicit Registration** - Traditional approach with manual endpoint mapping: + ```csharp + app.MapHtmxGet("/htmx/counter"); + ``` + +2. **Convention-Based Registration** - Automatic discovery and registration: + ```csharp + // In Program.cs + builder.Services.AddFastComponentsAuto(); + app.UseFastComponentsAuto(); + ``` + + Components are automatically discovered and mapped based on naming conventions: + - `CounterComponent` → `/htmx/counter` + - `MovieCharactersExample` → `/htmx/movie-characters` + - Routes are kebab-cased with common suffixes removed + +The convention-based approach is implemented in `ConventionBasedRegistration.cs` and `SimplifiedExtensions.cs`. + +## Key Dependencies + +- **.NET 9.0** - Target framework +- **AngleSharp 1.3.0** - HTML parsing and beautification +- **HTMX** - Client-side library for dynamic HTML (included in wwwroot) + +## Code Quality + +The project enforces strict code analysis: +- All .NET analyzers enabled with "All" mode +- EditorConfig for consistent formatting +- XML documentation required for public APIs +- Reproducible builds enabled + +## Testing + +The project uses xUnit for testing with comprehensive coverage: + +- **FastComponents.UnitTests** - Main library unit tests covering all core components +- **FastComponents.Generators.UnitTests** - Source generator tests +- **coverage.sh** - Automated coverage script that generates HTML reports using ReportGenerator + +Test files are organized by component and follow the pattern `{ComponentName}Tests.cs`. All public APIs have corresponding test coverage. + +## Demo Application + +The `demo/HtmxAppServer` project demonstrates usage patterns: +- Component organization in `/Components/Blocks/` +- Service registration in `Program.cs` +- HTMX route configuration in `HtmxRoutes.cs` +- Example components: Counter, MovieCharacters + +## NuGet Package + +The project uses MinVer for automatic versioning based on Git tags. Package is published to NuGet.org on GitHub release creation. + +## AOT Compilation + +The project supports AOT (Ahead-Of-Time) compilation with the following considerations: +- The demo app has AOT enabled (`PublishAot=true`) +- The library is marked as AOT-compatible (`IsAotCompatible=true`) +- Component rendering and parameter binding use reflection, requiring `RequiresUnreferencedCode` and `RequiresDynamicCode` attributes +- AOT warnings are suppressed at the application level where HTMX endpoints are mapped \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a96a986 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,179 @@ +# Contributing to FastComponents + +Thank you for your interest in contributing to FastComponents! This guide will help you get started. + +## Code of Conduct + +By participating in this project, you agree to maintain a respectful and inclusive environment for everyone. + +## Getting Started + +### Prerequisites + +- [.NET SDK](https://dotnet.microsoft.com/download) >= 9.0 +- A code editor (VS Code, Visual Studio, Rider) +- Git + +### Setup + +```bash +# Clone the repository +git clone https://github.com/Atypical-Consulting/FastComponents.git +cd FastComponents + +# Build the solution +dotnet build + +# Run all tests +dotnet test + +# Run the demo application +dotnet run --project demo/HtmxAppServer/HtmxAppServer.csproj +``` + +## Development Workflow + +### Branch Naming + +Use descriptive branch names with prefixes: + +- `feature/` - New features (e.g., `feature/add-hx-on-attribute`) +- `fix/` - Bug fixes (e.g., `fix/query-string-encoding`) +- `docs/` - Documentation changes (e.g., `docs/update-api-reference`) +- `chore/` - Maintenance tasks (e.g., `chore/update-dependencies`) +- `refactor/` - Code refactoring (e.g., `refactor/simplify-endpoint-registration`) + +### Making Changes + +1. **Fork** the repository and create your branch from `dev`: + ```bash + git checkout dev + git pull origin dev + git checkout -b feature/your-feature-name + ``` + +2. **Make your changes** following the code style guidelines below. + +3. **Write or update tests** for any changed functionality. + +4. **Run the full test suite** to ensure nothing is broken: + ```bash + dotnet test + ``` + +5. **Build in Release mode** to verify no warnings: + ```bash + dotnet build --configuration Release + ``` + +6. **Commit** using [Conventional Commits](https://www.conventionalcommits.org/): + ``` + feat: add support for hx-on attribute + fix: correct query string encoding for special characters + docs: update getting started guide + chore: upgrade AngleSharp to 1.5.0 + refactor: simplify convention-based registration + test: add generator tests for bool properties + ``` + +7. **Push** your branch and **open a Pull Request** against `dev`. + +## Code Style Guidelines + +### General + +- Follow the existing `.editorconfig` rules (enforced at build time) +- Use file-scoped namespaces +- Enable nullable reference types +- Maximum line length: 140 characters +- Use `var` when the type is obvious from context + +### C# Conventions + +- Use records for immutable data (e.g., parameter objects) +- Prefer `init` properties over `set` for parameter records +- Use `readonly` fields where possible +- Add XML documentation for all public APIs +- Use `ConfigureAwait(false)` in library code + +### Component Development + +- Inherit from `HtmxComponentBase` for components with parameters +- Use `[GenerateParameterMethods]` on parameter records +- Parameter records must be `partial` and inherit from `HtmxComponentParameters` +- Use `HtmxTag` for rendering HTML elements with HTMX attributes + +### Testing + +- Follow Arrange-Act-Assert pattern +- Use [Shouldly](https://github.com/shouldly/shouldly) for assertions +- Name test methods descriptively: `MethodName_Scenario_ExpectedResult` +- Place test files in the corresponding test project mirroring the source structure + +## Project Structure + +``` +FastComponents/ +├── src/ +│ ├── FastComponents/ # Core library +│ └── FastComponents.Generators/ # Source generators +├── tests/ +│ ├── FastComponents.UnitTests/ # Library tests +│ └── FastComponents.Generators.UnitTests/ # Generator tests +├── demo/ +│ └── HtmxAppServer/ # Demo application +└── docs/ # Generated API docs +``` + +## Types of Contributions + +### Bug Reports + +- Use the [Bug Report](https://github.com/Atypical-Consulting/FastComponents/issues/new?template=bug_report.yml) issue template +- Include a minimal reproduction if possible +- Specify your .NET SDK version and OS + +### Feature Requests + +- Use the [Feature Request](https://github.com/Atypical-Consulting/FastComponents/issues/new?template=feature_request.yml) issue template +- Explain the use case and expected behavior +- Consider how it fits with the existing API + +### Pull Requests + +- Keep PRs focused on a single change +- Update documentation if adding/changing public APIs +- Add tests for new functionality +- Ensure CI passes before requesting review + +## Building and Testing + +```bash +# Build everything +dotnet build + +# Run all tests +dotnet test + +# Run tests with coverage +./coverage.sh + +# Run specific test project +dotnet test tests/FastComponents.UnitTests/FastComponents.UnitTests.csproj + +# Run a single test +dotnet test --filter "FullyQualifiedName~TestMethodName" + +# Pack NuGet package locally +dotnet pack --configuration Release +``` + +## Need Help? + +- Open a [Discussion](https://github.com/Atypical-Consulting/FastComponents/discussions) for questions +- Check existing [Issues](https://github.com/Atypical-Consulting/FastComponents/issues) for known problems +- Review the [documentation](https://github.com/Atypical-Consulting/FastComponents/tree/main/Writerside) for guides + +## License + +By contributing, you agree that your contributions will be licensed under the [Apache-2.0 License](LICENSE). diff --git a/Directory.Build.props b/Directory.Build.props index 99e4a09..a30e230 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,20 +1,95 @@ + net10.0 enable enable - 14 + latest + true + true + true + + + + + true + + + + Atypical Consulting SRL + Philippe Matray + FastComponents + Copyright (c) 2020-2025 Atypical Consulting SRL + + + + + https://github.com/Atypical-Consulting/FastComponents.git + git + main + https://github.com/Atypical-Consulting/FastComponents - + + + v + preview + true + + + + + true + true + true + snupkg + + + - 0.1.0 - 0.1.0 - 0.1.0 - 0.1.0 + true + true + + + + true + true + true + + latest + AllDisabledByDefault + + + + + + $(NoWarn);IDE0073 + + $(NoWarn);IL2026 + + $(NoWarn);IL3050 + + $(NoWarn);CA2263 + + $(NoWarn);ASP0006 + + + + + $(MSBuildProjectDirectory)\bin\ + $(MSBuildProjectDirectory)\obj\ + $(MSBuildThisFileDirectory)artifacts\ + + + + + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..21568a8 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,45 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + \ No newline at end of file diff --git a/FastComponents.sln b/FastComponents.sln index 9e0e82c..545ba70 100644 --- a/FastComponents.sln +++ b/FastComponents.sln @@ -5,32 +5,99 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastComponents", "src\FastComponents\FastComponents.csproj", "{6ADB078A-97C4-45D4-BE76-62328767ABB6}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{062EAA7A-79B6-4BF2-AF58-9DEF0A73B024}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{15A02DE6-0793-497A-BD0E-F9E778FB3C13}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmxAppServer", "demo\HtmxAppServer\HtmxAppServer.csproj", "{4CC7445F-B783-4CB4-8125-E128F6747FE0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastComponents.Generators", "src\FastComponents.Generators\FastComponents.Generators.csproj", "{8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C565D4D9-889D-4B05-B3AA-054E1BEFD2B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastComponents.UnitTests", "tests\FastComponents.UnitTests\FastComponents.UnitTests.csproj", "{FE8AE6CF-938B-45EB-BCB7-189037C10DB5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastComponents.Generators.UnitTests", "tests\FastComponents.Generators.UnitTests\FastComponents.Generators.UnitTests.csproj", "{59070222-4F02-4C0B-8980-BCFE4D0F1619}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Debug|x64.ActiveCfg = Debug|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Debug|x64.Build.0 = Debug|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Debug|x86.ActiveCfg = Debug|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Debug|x86.Build.0 = Debug|Any CPU {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Release|Any CPU.Build.0 = Release|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Release|x64.ActiveCfg = Release|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Release|x64.Build.0 = Release|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Release|x86.ActiveCfg = Release|Any CPU + {6ADB078A-97C4-45D4-BE76-62328767ABB6}.Release|x86.Build.0 = Release|Any CPU {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Debug|x64.ActiveCfg = Debug|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Debug|x64.Build.0 = Debug|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Debug|x86.ActiveCfg = Debug|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Debug|x86.Build.0 = Debug|Any CPU {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Release|Any CPU.Build.0 = Release|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Release|x64.ActiveCfg = Release|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Release|x64.Build.0 = Release|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Release|x86.ActiveCfg = Release|Any CPU + {4CC7445F-B783-4CB4-8125-E128F6747FE0}.Release|x86.Build.0 = Release|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Debug|x64.ActiveCfg = Debug|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Debug|x64.Build.0 = Debug|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Debug|x86.ActiveCfg = Debug|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Debug|x86.Build.0 = Debug|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Release|Any CPU.Build.0 = Release|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Release|x64.ActiveCfg = Release|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Release|x64.Build.0 = Release|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Release|x86.ActiveCfg = Release|Any CPU + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7}.Release|x86.Build.0 = Release|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Debug|x64.ActiveCfg = Debug|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Debug|x64.Build.0 = Debug|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Debug|x86.ActiveCfg = Debug|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Debug|x86.Build.0 = Debug|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Release|Any CPU.Build.0 = Release|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Release|x64.ActiveCfg = Release|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Release|x64.Build.0 = Release|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Release|x86.ActiveCfg = Release|Any CPU + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5}.Release|x86.Build.0 = Release|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Debug|x64.ActiveCfg = Debug|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Debug|x64.Build.0 = Debug|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Debug|x86.ActiveCfg = Debug|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Debug|x86.Build.0 = Debug|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Release|Any CPU.Build.0 = Release|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Release|x64.ActiveCfg = Release|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Release|x64.Build.0 = Release|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Release|x86.ActiveCfg = Release|Any CPU + {59070222-4F02-4C0B-8980-BCFE4D0F1619}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {6ADB078A-97C4-45D4-BE76-62328767ABB6} = {062EAA7A-79B6-4BF2-AF58-9DEF0A73B024} {4CC7445F-B783-4CB4-8125-E128F6747FE0} = {15A02DE6-0793-497A-BD0E-F9E778FB3C13} + {8EAB9A1D-B95E-451B-858C-B9F4291FCBD7} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {6ADB078A-97C4-45D4-BE76-62328767ABB6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {FE8AE6CF-938B-45EB-BCB7-189037C10DB5} = {C565D4D9-889D-4B05-B3AA-054E1BEFD2B5} + {59070222-4F02-4C0B-8980-BCFE4D0F1619} = {C565D4D9-889D-4B05-B3AA-054E1BEFD2B5} EndGlobalSection EndGlobal diff --git a/Writerside/c.list b/Writerside/c.list new file mode 100644 index 0000000..c4c77a2 --- /dev/null +++ b/Writerside/c.list @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Writerside/cfg/buildprofiles.xml b/Writerside/cfg/buildprofiles.xml new file mode 100644 index 0000000..457d3ff --- /dev/null +++ b/Writerside/cfg/buildprofiles.xml @@ -0,0 +1,13 @@ + + + + + + + + false + + + + diff --git a/Writerside/images/completion_procedure.png b/Writerside/images/completion_procedure.png new file mode 100644 index 0000000..3535a3f Binary files /dev/null and b/Writerside/images/completion_procedure.png differ diff --git a/Writerside/images/completion_procedure_dark.png b/Writerside/images/completion_procedure_dark.png new file mode 100644 index 0000000..a65beb0 Binary files /dev/null and b/Writerside/images/completion_procedure_dark.png differ diff --git a/Writerside/images/convert_table_to_xml.png b/Writerside/images/convert_table_to_xml.png new file mode 100644 index 0000000..2518a64 Binary files /dev/null and b/Writerside/images/convert_table_to_xml.png differ diff --git a/Writerside/images/convert_table_to_xml_dark.png b/Writerside/images/convert_table_to_xml_dark.png new file mode 100644 index 0000000..4716122 Binary files /dev/null and b/Writerside/images/convert_table_to_xml_dark.png differ diff --git a/Writerside/images/new_topic_options.png b/Writerside/images/new_topic_options.png new file mode 100644 index 0000000..bc6abb6 Binary files /dev/null and b/Writerside/images/new_topic_options.png differ diff --git a/Writerside/images/new_topic_options_dark.png b/Writerside/images/new_topic_options_dark.png new file mode 100644 index 0000000..bf3e48d Binary files /dev/null and b/Writerside/images/new_topic_options_dark.png differ diff --git a/Writerside/topics/AOT-Support.md b/Writerside/topics/AOT-Support.md new file mode 100644 index 0000000..cd5e3db --- /dev/null +++ b/Writerside/topics/AOT-Support.md @@ -0,0 +1,306 @@ +# AOT Support + +FastComponents is designed to work with .NET Ahead-Of-Time (AOT) compilation, enabling smaller deployments and faster startup times. + +## Overview + +AOT compilation pre-compiles your .NET code to native code, eliminating JIT compilation at runtime. FastComponents supports AOT with some considerations due to its use of reflection for component rendering. + +## Enabling AOT + +### Project Configuration + +In your `.csproj` file: + +```xml + + true + true + +``` + +### Required Packages + +```xml + + + + + +``` + +## AOT Considerations + +### Component Rendering + +FastComponents uses reflection for component rendering, which requires special handling: + +```C# +[RequiresUnreferencedCode("Component rendering uses reflection")] +[RequiresDynamicCode("Component parameter binding requires dynamic code")] +public static class HtmxComponentEndpoints +{ + // Extension methods for mapping components +} +``` + +### Suppressing Warnings + +When mapping endpoints, suppress AOT warnings at the application level: + +```C# +[UnconditionalSuppressMessage("Trimming", "IL2072", + Justification = "Component types are preserved")] +[UnconditionalSuppressMessage("AOT", "IL3050", + Justification = "Component rendering requires dynamic code")] +public static void MapComponents(this WebApplication app) +{ + app.MapHtmxGet("/htmx/counter"); +} +``` + +## JSON Serialization + +### Source-Generated Serialization + +Use System.Text.Json source generators for AOT-compatible JSON: + +```C# +[JsonSerializable(typeof(CounterState))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(Dictionary))] +public partial class AppJsonSerializerContext : JsonSerializerContext +{ +} + +// Configure in Program.cs +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); +}); +``` + +### Component State Serialization + +Ensure all component state types are included: + +```C# +public class CounterState : HtmxComponentParameters +{ + public int Count { get; set; } + + // Simple types work well with AOT + public string? Message { get; set; } + public DateTime LastUpdated { get; set; } +} +``` + +## Component Registration + +### Explicit Registration (Recommended for AOT) + +```C# +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddFastComponents(); + +var app = builder.Build(); + +// Explicitly register each component +app.MapHtmxGet("/"); +app.MapHtmxGet("/htmx/counter"); +app.MapHtmxPost("/htmx/form"); +``` + +### Convention-Based Registration + +When using convention-based registration with AOT: + +```C# +[UnconditionalSuppressMessage("AOT", "IL3050")] +[UnconditionalSuppressMessage("Trimming", "IL2026")] +public static void ConfigureApp(this WebApplication app) +{ + // AOT warnings are suppressed for this method + app.UseFastComponentsAuto(); +} +``` + +## Trimming Configuration + +### Preserve Assemblies + +In your `.csproj`: + +```xml + + + + +``` + +### Preserve Component Types + +```xml + + + +``` + +`TrimmerRoots.xml`: + +```xml + + + + + +``` + +## Testing AOT Builds + +### Local AOT Publish + +```bash +# Publish with AOT +dotnet publish -c Release -r linux-x64 + +# Test the native executable +./bin/Release/net9.0/linux-x64/publish/YourApp +``` + +### Size Comparison + +Typical size improvements with AOT: + +- Without AOT: ~80-150 MB +- With AOT: ~30-50 MB +- With trimming: ~15-30 MB + +## Performance Benefits + +### Startup Time + +- JIT: 200-500ms +- AOT: 50-100ms + +### Memory Usage + +- Lower memory footprint +- No JIT compiler overhead +- Predictable performance + +## Limitations + +### Dynamic Features + +Some features have limitations with AOT: + +1. **Reflection**: Limited to pre-compiled types +2. **Dynamic Assembly Loading**: Not supported +3. **Expression Trees**: Limited support +4. **Runtime Code Generation**: Not available + +### Workarounds + +```C# +// Instead of dynamic creation +var component = Activator.CreateInstance(componentType); + +// Use compile-time known types +var component = componentType switch +{ + Type t when t == typeof(CounterComponent) => new CounterComponent(), + Type t when t == typeof(FormComponent) => new FormComponent(), + _ => throw new NotSupportedException() +}; +``` + +## Best Practices + +### 1. Explicit Type Registration + +```C# +// Register all component types explicitly +services.AddScoped(); +services.AddScoped(); +``` + +### 2. Avoid Dynamic Types + +```C# +// ❌ Avoid +object dynamicState = GetDynamicState(); + +// ✅ Prefer +CounterState typedState = GetCounterState(); +``` + +### 3. Source Generators + +Use source generators for compile-time code generation: + +```C# +[GenerateParameterMethods] +public partial class MyComponent : HtmxComponentBase +{ + // Methods generated at compile time +} +``` + +## Debugging AOT Issues + +### Enable Detailed Warnings + +```xml + + true + true + +``` + +### Common Issues + +1. **Missing Metadata**: Add types to TrimmerRootAssembly +2. **Serialization Failures**: Include types in JsonSerializerContext +3. **Reflection Errors**: Use compile-time alternatives + +## Example: AOT-Ready Application + +```C# +using System.Text.Json.Serialization; +using FastComponents; + +var builder = WebApplication.CreateBuilder(args); + +// Configure AOT-friendly services +builder.Services.AddFastComponents(); +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default); +}); + +var app = builder.Build(); + +// Map components with AOT suppressions +MapComponents(app); + +app.Run(); + +[UnconditionalSuppressMessage("AOT", "IL3050")] +static void MapComponents(WebApplication app) +{ + app.MapHtmxGet("/"); + app.MapHtmxGet("/htmx/counter"); +} + +// JSON serialization context +[JsonSerializable(typeof(CounterState))] +[JsonSerializable(typeof(EmptyState))] +partial class AppJsonContext : JsonSerializerContext { } +``` + +## See Also + +- [Deployment](Deployment.md) +- [Performance](Performance.md) +- [Source Generators](Source-Generators.md) \ No newline at end of file diff --git a/Writerside/topics/API-Reference.md b/Writerside/topics/API-Reference.md new file mode 100644 index 0000000..b3accca --- /dev/null +++ b/Writerside/topics/API-Reference.md @@ -0,0 +1,297 @@ +# API Reference + +Complete API documentation for FastComponents, organized by namespace and functionality. + + + +FastComponents : Core components and base classes +FastComponents.Http : HTTP request/response handling +FastComponents.Events : HTMX event constants +FastComponents.Generators : Source generators and analyzers + + + + + +Components API +: Component base classes and interfaces + +Endpoints API +: Endpoint mapping and routing + +HTTP API +: Request and response headers + +Events API +: HTMX event handling + +Builders API +: Fluent builders and helpers + + + +## Core Types + +### HtmxComponentBase + +Base class for all HTMX-enabled components. + +```C# +public abstract class HtmxComponentBase : ComponentBase, IHxAttributes +{ + // Core HTMX attributes + public string? HxGet { get; set; } + public string? HxPost { get; set; } + public string? HxPut { get; set; } + public string? HxDelete { get; set; } + public string? HxPatch { get; set; } + + // Targeting and swapping + public string? HxTarget { get; set; } + public string? HxSwap { get; set; } + public string? HxTrigger { get; set; } + + // Additional features + public string? HxConfirm { get; set; } + public string? HxIndicator { get; set; } + public string? HxPushUrl { get; set; } + + // CSS classes + public string? ClassName { get; protected set; } + + // Lifecycle + protected virtual void OnBuildClassNames(ClassNamesBuilder builder) { } +} +``` + +### SimpleHtmxComponent\ + +Simplified base class for stateful components. + +```C# +public abstract class SimpleHtmxComponent : HtmxComponentBase + where TState : HtmxComponentParameters, new() +{ + public TState State { get; protected set; } + public string Url => State.ToComponentUrl(GetRoute()); + + // HTTP method handlers + protected virtual TState OnGet(TState state) => state; + protected virtual TState OnPost(TState state) => state; + protected virtual TState OnPut(TState state) => state; + protected virtual TState OnDelete(TState state) => state; + protected virtual TState OnPatch(TState state) => state; +} +``` + +### HtmxComponentParameters + +Base class for component state/parameters. + +```C# +public abstract record HtmxComponentParameters +{ + // Query string serialization + protected virtual string BuildQueryString(); + public virtual HtmxComponentParameters BindFromQuery(IQueryCollection query); + + // URL generation + public string ToComponentUrl(string baseUrl); + + // Helper methods + protected static string? GetQueryValue(IQueryCollection query, string key); + protected static int? GetQueryInt(IQueryCollection query, string key); +} +``` + +## Extension Methods + +### Service Registration + +```C# +// Manual registration +services.AddFastComponents(); + +// Auto-registration with assembly scanning +services.AddFastComponentsAuto(); +``` + +### Endpoint Mapping + +```C# +// Manual mapping +app.MapHtmxGet("/my-component"); +app.MapHtmxPost("/my-component"); + +// Convention-based mapping +app.MapHtmxComponentsByConvention(); + +// Auto-mapping with middleware +app.UseFastComponentsAuto(); +``` + +### HTTP Context Extensions + +```C# +// Check if request is from HTMX +if (context.IsHtmxRequest()) +{ + // Get request headers + var headers = context.GetHtmxRequestHeaders(); + var trigger = headers.Trigger; + + // Set response headers + var response = context.GetHtmxResponseHeaders(); + response.Trigger("my-event"); + response.PushUrl("/new-url"); +} +``` + +## Attributes + +### [GenerateParameterMethods] + +Generates query string serialization methods. + +```C# +[GenerateParameterMethods(SkipDefaults = true)] +public partial record MyState : HtmxComponentParameters +{ + public string Name { get; init; } = ""; + public int Page { get; init; } = 1; +} +``` + +## Interfaces + +### IHxAttributes + +Complete set of HTMX attributes. + +```C# +public interface IHxAttributes : IHxCoreAttributes, IHxAdditionalAttributes +{ + // Combines all HTMX attribute interfaces +} +``` + +### IHxCoreAttributes + +Core HTMX functionality. + +```C# +public interface IHxCoreAttributes +{ + string? HxGet { get; set; } + string? HxPost { get; set; } + string? HxTrigger { get; set; } + string? HxTarget { get; set; } + string? HxSwap { get; set; } + // ... more attributes +} +``` + +## Builders + +### HtmxBuilder + +Fluent API for building HTMX elements. + +```C# +var builder = HtmxBuilder.Create() + .Div() + .Get("/api/data") + .Trigger("click") + .Target("#result") + .Swap("innerHTML") + .Class("my-component") + .Content(childContent); +``` + +### ClassNamesBuilder + +Fluent API for building CSS classes. + +```C# +var classes = ClassNamesBuilder.Default("base-class") + .AddClass("active", isActive) + .AddClass("disabled", isDisabled) + .AddClass($"theme-{theme}") + .Build(); +``` + +## Services + +### ComponentHtmlResponseService + +Renders components as HTML responses. + +```C# +public class ComponentHtmlResponseService +{ + public async Task RenderComponentAsync( + Dictionary? parameters = null) + where TComponent : IComponent; + + public async Task RenderAsHtmlContentAsync( + Dictionary? parameters = null) + where TComponent : IComponent; +} +``` + +### HtmlBeautifier + +Formats HTML output for readability. + +```C# +public static class HtmlBeautifier +{ + public static string BeautifyHtml(string html); +} +``` + +## Constants + +### HtmxEvents + +All HTMX JavaScript events. + +```C# +public static class HtmxEvents +{ + // Lifecycle events + public const string BeforeRequest = "htmx:beforeRequest"; + public const string AfterRequest = "htmx:afterRequest"; + public const string BeforeSwap = "htmx:beforeSwap"; + public const string AfterSwap = "htmx:afterSwap"; + + // Error events + public const string ResponseError = "htmx:responseError"; + public const string SendError = "htmx:sendError"; + + // ... many more events +} +``` + +### HtmxDefaults + +Default values for HTMX behavior. + +```C# +public static class HtmxDefaults +{ + public const string Swap = "innerHTML"; + public const string LoadOnceTrigger = "load once"; + public const string SearchTrigger = "keyup changed delay:500ms"; +} +``` + +## Detailed Documentation + +For detailed information about specific APIs, see: + +- [Components API](Components-API.md) - Detailed component class documentation +- [Endpoints API](Endpoints-API.md) - Endpoint mapping and routing details +- [HTTP API](HTTP-API.md) - Request/response header documentation +- [Events API](Events-API.md) - Event handling and constants +- [Builders API](Builders-API.md) - Builder pattern implementations \ No newline at end of file diff --git a/Writerside/topics/Advanced-Features.md b/Writerside/topics/Advanced-Features.md new file mode 100644 index 0000000..be7e9f5 --- /dev/null +++ b/Writerside/topics/Advanced-Features.md @@ -0,0 +1,477 @@ +# Advanced Features + +Explore the advanced capabilities of FastComponents for building sophisticated web applications. + + + +Server-Sent Events enable real-time, one-way communication from server to client. FastComponents provides the `HtmxSseTag` component for easy SSE integration. + + + +Create the SSE component: + + +<HtmxSseTag SseConnect="/notifications/stream" + SseSwap="message"> + <div id="notifications"> + <!-- Messages will appear here --> + </div> +</HtmxSseTag> + + + +Configure the server endpoint: + + +app.MapGet("/notifications/stream", async (HttpContext context) => +{ + context.Response.Headers.Append("Content-Type", "text/event-stream"); + context.Response.Headers.Append("Cache-Control", "no-cache"); + context.Response.Headers.Append("Connection", "keep-alive"); + + await foreach (var notification in GetNotificationsAsync(context.RequestAborted)) + { + var html = $"<div class='notification'>{notification.Message}</div>"; + await context.Response.WriteAsync($"event: message\n"); + await context.Response.WriteAsync($"data: {html}\n\n"); + await context.Response.Body.FlushAsync(); + } +}); + + + + + + + +### Multiple Event Types + +```razor + +
0
+
$0
+
+
+ +@* Server sends different event types *@ +// User count update +await response.WriteAsync("event: users\n"); +await response.WriteAsync($"data: {userCount}\n\n"); + +// Sales update +await response.WriteAsync("event: sales\n"); +await response.WriteAsync($"data: ${salesTotal:N2}\n\n"); + +// Alert +await response.WriteAsync("event: alerts\n"); +await response.WriteAsync($"data:
{alertMessage}
\n\n"); +``` + +## WebSocket Support + +### Overview + +WebSockets provide full-duplex communication for real-time applications. Use `HtmxWsTag` for WebSocket integration. + +### Chat Application Example + +```razor +@* Chat component *@ + +
+
+ +
+ +
+ + +
+
+
+ +@* WebSocket handler *@ +app.UseWebSockets(); +app.Map("/ws/chat", async context => +{ + if (context.WebSockets.IsWebSocketRequest) + { + var webSocket = await context.WebSockets.AcceptWebSocketAsync(); + await HandleChatWebSocket(webSocket); + } +}); + +async Task HandleChatWebSocket(WebSocket webSocket) +{ + var buffer = new byte[1024 * 4]; + + while (webSocket.State == WebSocketState.Open) + { + var result = await webSocket.ReceiveAsync( + new ArraySegment(buffer), + CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Text) + { + var message = Encoding.UTF8.GetString(buffer, 0, result.Count); + var response = ProcessChatMessage(message); + + await webSocket.SendAsync( + Encoding.UTF8.GetBytes(response), + WebSocketMessageType.Text, + true, + CancellationToken.None); + } + } +} +``` + +## Source Generators + +### GenerateParameterMethods Attribute + +The source generator creates efficient query string handling: + +```C# +[GenerateParameterMethods(SkipDefaults = true)] +public partial record SearchParameters : HtmxComponentParameters +{ + public string Query { get; init; } = ""; + public int Page { get; init; } = 1; + public string Category { get; init; } = "all"; + public List Tags { get; init; } = []; +} + +// Generated code includes: +// - BuildQueryString() with optimal serialization +// - BindFromQuery() with efficient parsing +// - GetQueryBool() helper for boolean parsing +``` + +### Custom Analyzers + +FastComponents includes analyzers to catch common mistakes: + +- **FC0001**: Missing `[GenerateParameterMethods]` attribute +- **FC0002**: Record must be partial for code generation +- **FC0003**: Must inherit from `HtmxComponentParameters` +- **FC0004**: Properties should be init-only +- **FC0005**: Manual implementation conflicts with generated code + +## AOT (Ahead-of-Time) Compilation + +### Enabling AOT + +FastComponents supports AOT compilation with some considerations: + +```xml + + + true + true + + +``` + +### AOT-Safe Component Registration + +```C# +// Use explicit registration for AOT +app.MapHtmxGet("/counter") + .RequireUnreferencedCode("Component rendering"); + +// Suppress warnings where necessary +[UnconditionalSuppressMessage("Trimming", "IL2026")] +public static void RegisterComponents(IEndpointRouteBuilder app) +{ + app.MapHtmxComponentsByConvention(); +} +``` + +### Trimming Considerations + +Some features require runtime code generation: +- Convention-based mapping uses reflection +- Component rendering uses dynamic code +- Parameter binding may use reflection + +For full AOT support, use explicit registration and avoid convention-based features. + +## Performance Optimization + +### Component Caching + +Cache rendered components for better performance: + +```C# +// Output caching +app.MapHtmxGet("/expensive") + .CacheOutput(policy => policy + .Expire(TimeSpan.FromMinutes(5)) + .VaryByQuery("id", "category")); + +// Response caching +[ResponseCache(Duration = 300, VaryByQueryKeys = ["page"])] +public class ProductListComponent : SimpleHtmxComponent +{ + // Component implementation +} +``` + +### Efficient State Serialization + +Optimize query string generation: + +```C# +[GenerateParameterMethods(SkipDefaults = true)] +public partial record OptimizedState : HtmxComponentParameters +{ + // Only non-default values appear in URL + public int Page { get; init; } = 1; + public int PageSize { get; init; } = 20; + public string SortBy { get; init; } = "name"; +} +``` + +### Streaming Responses + +Stream large responses for better perceived performance: + +```C# +public class StreamingComponent : SimpleHtmxComponent +{ + protected override async Task OnGetAsync(StreamingState state) + { + Response.Headers.Append("X-Content-Type-Options", "nosniff"); + + await Response.WriteAsync("
"); + + await foreach (var item in GetItemsAsync()) + { + var html = RenderItem(item); + await Response.WriteAsync(html); + await Response.Body.FlushAsync(); + } + + await Response.WriteAsync("
"); + } +} +``` + +## Security Features + +### CSRF Protection + +Integrate with ASP.NET Core's anti-forgery tokens: + +```Razor +@inject IAntiforgery Antiforgery + +
+ @{ + var token = Antiforgery.GetAndStoreTokens(HttpContext); + } + + + + +
+``` + +### Content Security Policy + +Configure CSP headers for HTMX: + +```C# +app.Use(async (context, next) => +{ + context.Response.Headers.Add("Content-Security-Policy", + "default-src 'self'; " + + "script-src 'self' 'unsafe-inline' unpkg.com; " + + "style-src 'self' 'unsafe-inline'; " + + "connect-src 'self' ws: wss:;"); + + await next(); +}); +``` + +### Input Validation + +Validate all input on the server: + +```C# +public record SecureState : HtmxComponentParameters +{ + private string _email = ""; + + public string Email + { + get => _email; + init => _email = SanitizeEmail(value); + } + + private static string SanitizeEmail(string email) + { + // Validation and sanitization logic + return email.Trim().ToLowerInvariant(); + } +} +``` + +## Custom Extensions + +### Creating HTMX Extensions + +Create custom HTMX extensions for specialized behavior: + +```javascript +// wwwroot/js/custom-extension.js +htmx.defineExtension('custom-loader', { + onEvent: function(name, evt) { + if (name === "htmx:beforeRequest") { + // Show custom loader + showCustomLoader(evt.detail.elt); + } else if (name === "htmx:afterRequest") { + // Hide custom loader + hideCustomLoader(evt.detail.elt); + } + } +}); +``` + +Usage in components: + +```razor +
+ Load with custom loader +
+``` + +### Component Middleware + +Create middleware for cross-cutting concerns: + +```C# +public class HtmxComponentMiddleware +{ + private readonly RequestDelegate _next; + + public async Task InvokeAsync(HttpContext context) + { + // Before component processing + if (context.IsHtmxRequest()) + { + context.Items["RequestTime"] = DateTime.UtcNow; + } + + await _next(context); + + // After component processing + if (context.IsHtmxRequest()) + { + var elapsed = DateTime.UtcNow - (DateTime)context.Items["RequestTime"]!; + context.Response.Headers.Append("X-Processing-Time", + elapsed.TotalMilliseconds.ToString()); + } + } +} + +// Register middleware +app.UseMiddleware(); +``` + +## Integration Patterns + +### Hybrid Architectures + +Combine FastComponents with other technologies: + +```C# +// API endpoints for data +app.MapGet("/api/products", () => GetProducts()); + +// FastComponents for UI +app.MapHtmxGet("/products"); + +// Static pages with enhancement +app.MapFallbackToFile("index.html"); +``` + +### Progressive Enhancement + +Start with working HTML, enhance with HTMX: + +```razor +@* Works without JavaScript *@ +
+ + +
+ +
+ @* Results render here with or without HTMX *@ +
+``` + +## Advanced Patterns + +### Component Composition + +Build complex UIs from simple components: + +```C# +public abstract class ComposableComponent : SimpleHtmxComponent + where TState : HtmxComponentParameters, new() +{ + protected RenderFragment RenderChild(TChildState state) + where TChild : SimpleHtmxComponent, new() + where TChildState : HtmxComponentParameters, new() + { + return builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, "State", state); + builder.CloseComponent(); + }; + } +} +``` + +### Event-Driven Architecture + +Use HTMX events for loose coupling: + +```razor +@* Publisher component *@ + + +@* Subscriber components *@ +
+ +
+ +
+ +
+``` + +## Next Steps + +- [SSE & WebSockets](SSE-WebSockets.md) - Deep dive into real-time features +- [Source Generators](Source-Generators.md) - Custom code generation +- [AOT Support](AOT-Support.md) - Native compilation details +- [Performance](Performance.md) - Optimization guide +- [Security](Security.md) - Security best practices \ No newline at end of file diff --git a/Writerside/topics/Builders-API.md b/Writerside/topics/Builders-API.md new file mode 100644 index 0000000..0edef52 --- /dev/null +++ b/Writerside/topics/Builders-API.md @@ -0,0 +1,208 @@ +# Builders API + +FastComponents provides fluent builder APIs for constructing HTMX elements programmatically. + +## HtmxBuilder + +A fluent builder for creating HTMX-enabled HTML elements. + +### Creating Builders + +```C# +// Generic builder +var builder = HtmxBuilder.Create(); + +// Pre-configured builders +var button = HtmxBuilder.Button(); +var form = HtmxBuilder.Form(); +var div = HtmxBuilder.Div(); +``` + +### Setting Attributes + +#### Element Type + +```C# +builder.Element("span") + .Text("Hello World"); +``` + +#### HTMX Attributes + +```C# +builder + .Get("/api/data") + .Target("#result") + .Swap("innerHTML") + .Trigger("click"); +``` + +#### CSS Classes + +```C# +builder + .Class("btn btn-primary") + .Class("active"); +``` + +#### Custom Attributes + +```C# +builder + .Attr("id", "my-button") + .Attr("data-value", "123") + .Attr("disabled", true); +``` + +### Content + +#### Text Content + +```C# +builder.Text("Click me!"); +``` + +#### Render Fragment + +```C# +builder.Content(@
Complex content here
); +``` + +### Common Patterns + +#### Self-Updating Button + +```C# +HtmxBuilder.Button() + .GetSelf("/htmx/counter", "counter-1") + .Text("Count: 0"); +``` + +#### Search Input + +```C# +HtmxBuilder.Create() + .Element("input") + .Search("/api/search", "#results", 300) + .Attr("placeholder", "Search..."); +``` + +#### Load Once Container + +```C# +HtmxBuilder.Div() + .LoadOnce("/api/expensive-data") + .Attr("id", "data-container"); +``` + +### Rendering + +```C# +@code { + void BuildContent(RenderTreeBuilder builder) + { + HtmxBuilder.Button() + .Post("/api/submit") + .Target("#result") + .Text("Submit") + .Render(builder); + } +} +``` + +## HtmxBuilderExtensions + +Extension methods for common HTMX patterns. + +### Button + +Creates a complete button with text, URL, and target. + +```C# +HtmxBuilder.Button("Click Me", "/api/action", "#result"); +``` + +### SearchInput + +Creates a search input with debouncing. + +```C# +HtmxBuilder.SearchInput("/api/search", "#results", "Search products..."); +``` + +### LoadContainer + +Creates a container that loads content on page load. + +```C# +HtmxBuilder.LoadContainer("/api/dashboard", "#dashboard"); +``` + +## HtmxPatterns + +Static helper methods for common HTMX patterns. + +### SelfUpdatingButton + +```C# +var attrs = HtmxPatterns.SelfUpdatingButton("/htmx/counter", "counter-1"); +``` + +### SearchInput + +```csharp +var attrs = HtmxPatterns.SearchInput("/api/search", "#results"); +``` + +### LoadOnce + +```csharp +var attrs = HtmxPatterns.LoadOnce("/api/expensive-operation"); +``` + +## Complete Example + +```csharp +@page "/builder-demo" +@using FastComponents + +

HtmxBuilder Demo

+ +@{ + void RenderSearchForm(RenderTreeBuilder builder) + { + HtmxBuilder.Form() + .Post("/api/search") + .Target("#search-results") + .Class("search-form") + .Content(@ + + @{ + HtmxBuilder.Button() + .Class("btn btn-primary") + .Text("Search") + .Render(builder); + } + ) + .Render(builder); + } +} + +@{ RenderSearchForm(__builder); } + +
+ @{ + HtmxBuilder.Div() + .LoadOnce("/api/initial-results") + .Class("results-container") + .Text("Loading...") + .Render(__builder); + } +
+``` + +## See Also + +- [Components API](Components-API.md) +- [HTMX Attributes](HTMX-Attributes.md) +- [Component Development](Component-Development.md) \ No newline at end of file diff --git a/Writerside/topics/Changelog.md b/Writerside/topics/Changelog.md new file mode 100644 index 0000000..bb13aed --- /dev/null +++ b/Writerside/topics/Changelog.md @@ -0,0 +1,166 @@ +# Changelog + +All notable changes to FastComponents will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Enhanced documentation with complete API reference +- Migration guide for upgrading from previous versions +- Performance optimization examples +- Security best practices guide + +### Changed +- Improved table of contents structure in documentation + +## [1.0.0] - 2024-12-XX + +### Added +- **Core Components** + - `HtmxComponentBase` - Base class for HTMX-enabled components + - `SimpleHtmxComponent` - Simplified component with automatic routing + - `HtmxComponentParameters` - Base class for component state management + - `ClassNamesBuilder` - Fluent API for CSS class name construction + +- **HTTP Integration** + - `HtmxHttpContextExtensions` - Extension methods for HTMX request/response handling + - `HtmxRequestHeaders` - Strongly-typed HTMX request headers + - `HtmxResponseHeaders` - Helper for setting HTMX response headers + +- **Endpoint Registration** + - `HtmxComponentEndpoints` - Extension methods for mapping components to endpoints + - Support for GET, POST, PUT, PATCH, DELETE HTTP methods + - Convention-based automatic component discovery and registration + +- **Builder APIs** + - `HtmxBuilder` - Fluent builder for creating HTMX elements programmatically + - `HtmxBuilderExtensions` - Common patterns and shortcuts + - `HtmxPatterns` - Static helpers for frequent HTMX patterns + +- **Real-time Communication** + - `HtmxSseTag` - Server-Sent Events support + - `HtmxWsTag` - WebSocket integration + - Built-in connection management and error handling + +- **Events API** + - `HtmxEvents` - Constants for all HTMX event names + - Type-safe event handling + - Complete coverage of HTMX lifecycle events + +- **Source Generators** + - `GenerateParameterMethodsAttribute` - Automatic fluent method generation + - `HtmxParametersGenerator` - Source generator implementation + - `HtmxParametersAnalyzer` - Code analysis for best practices + +- **Services** + - `ComponentHtmlResponseService` - Blazor component rendering to HTML + - `HtmlBeautifier` - HTML formatting and beautification + - `MainExtensions` - Service registration helpers + - `SimplifiedExtensions` - Auto-registration for convention-based setup + +- **AOT Support** + - Full compatibility with .NET Native AOT compilation + - Appropriate `RequiresUnreferencedCode` and `RequiresDynamicCode` attributes + - Optimized for trimming and AOT scenarios + +- **Testing Support** + - Unit testing helpers and utilities + - Integration testing patterns + - Component rendering test helpers + +### Framework Support +- **.NET 9.0** - Primary target framework +- **AngleSharp 1.3.0** - HTML parsing and manipulation +- **HTMX** - Client-side JavaScript library included + +### Documentation +- Complete API reference documentation +- Getting started guide +- Component development patterns +- Advanced features documentation +- Performance optimization guide +- Security best practices +- Deployment guidance + +## [0.9.0-preview] - 2024-11-XX + +### Added +- Initial preview release +- Basic component infrastructure +- HTMX attribute support +- Simple endpoint mapping + +### Known Issues +- Limited documentation +- Missing advanced features +- No source generator support + +## Project Milestones + +### Version 1.0 Goals ✅ +- [x] Stable API surface +- [x] Complete HTMX attribute coverage +- [x] Source generator implementation +- [x] AOT compatibility +- [x] Comprehensive documentation +- [x] Real-time communication support +- [x] Performance optimizations + +### Future Roadmap + +#### Version 1.1 (Planned) +- [ ] Enhanced debugging tools +- [ ] Additional builder patterns +- [ ] Performance monitoring integration +- [ ] Extended validation support +- [ ] Custom authentication helpers + +#### Version 1.2 (Planned) +- [ ] Visual Studio tooling +- [ ] Project templates +- [ ] CLI scaffolding tools +- [ ] Enhanced IDE integration +- [ ] Component library ecosystem + +#### Version 2.0 (Future) +- [ ] Multi-framework support (.NET 10+) +- [ ] Advanced caching strategies +- [ ] Built-in state management +- [ ] Component marketplace +- [ ] Enterprise features + +## Breaking Changes + +### 1.0.0 +- **Namespace consolidation**: All public APIs moved to `FastComponents` namespace +- **Component base classes**: Renamed for clarity and consistency + - `HtmxComponent` → `HtmxComponentBase` + - `SimpleComponent` → `SimpleHtmxComponent` +- **Service registration**: Simplified API with `AddFastComponentsAuto()` +- **Route conventions**: Updated automatic route generation logic + +### Migration Guide +See [Migration Guide](Migration-Guide.md) for detailed upgrade instructions. + +## Contributors + +- **Development Team**: Atypical Consulting SRL +- **Community**: Thanks to all contributors and early adopters + +## License + +FastComponents is licensed under the Apache License 2.0. See the [LICENSE](../LICENSE) file for details. + +## Support + +- **Documentation**: [FastComponents Docs](https://docs.fastcomponents.dev) +- **Issues**: [GitHub Issues](https://github.com/atypical-consulting/FastComponents/issues) +- **Discussions**: [GitHub Discussions](https://github.com/atypical-consulting/FastComponents/discussions) +- **NuGet**: [FastComponents Package](https://www.nuget.org/packages/FastComponents/) + +--- + +For older versions and detailed release notes, see the [GitHub Releases](https://github.com/atypical-consulting/FastComponents/releases) page. \ No newline at end of file diff --git a/Writerside/topics/Component-Development.md b/Writerside/topics/Component-Development.md new file mode 100644 index 0000000..4cd470e --- /dev/null +++ b/Writerside/topics/Component-Development.md @@ -0,0 +1,556 @@ +# Component Development + +This guide covers advanced techniques for developing FastComponents, from basic patterns to complex scenarios. + +## Component Structure + +### Basic Component Template + +```razor +@inherits HtmxComponentBase +@* or @inherits SimpleHtmxComponent *@ + +
+ +
+ +@code { + // Component logic +} +``` + +### Component Organization + +Organize your components for maintainability: + +``` +Components/ +├── Shared/ +│ ├── Button.razor +│ ├── Modal.razor +│ └── LoadingSpinner.razor +├── Features/ +│ ├── Products/ +│ │ ├── ProductList.razor +│ │ ├── ProductCard.razor +│ │ └── ProductDetail.razor +│ └── Cart/ +│ ├── CartSummary.razor +│ └── CartItem.razor +└── _Imports.razor +``` + +## Component Patterns + +### 1. Self-Updating Components + +Components that update themselves without full page interaction: + +```razor +@inherits SimpleHtmxComponent + +
+

Current Time: @State.CurrentTime

+
+ +@code { + protected override void OnGet(TimerState state) + { + State = state with { CurrentTime = DateTime.Now }; + } +} +``` + +### 2. Form Components + +Handle form submissions with validation: + +```razor +@inherits SimpleHtmxComponent + +
+ @if (!string.IsNullOrEmpty(State.SuccessMessage)) + { +
@State.SuccessMessage
+ } + + @if (State.Errors.Any()) + { +
+ @foreach (var error in State.Errors) + { +

@error

+ } +
+ } + + + + + + + + +
+ +@code { + protected override ContactFormState OnPost(ContactFormState state) + { + var errors = ValidateForm(state); + if (errors.Any()) + { + return state with { Errors = errors }; + } + + // Process form... + return state with + { + SuccessMessage = "Message sent successfully!", + Name = "", + Email = "", + Message = "", + Errors = [] + }; + } + + private List ValidateForm(ContactFormState state) + { + var errors = new List(); + + if (string.IsNullOrWhiteSpace(state.Name)) + errors.Add("Name is required"); + + if (string.IsNullOrWhiteSpace(state.Email)) + errors.Add("Email is required"); + else if (!IsValidEmail(state.Email)) + errors.Add("Invalid email format"); + + if (string.IsNullOrWhiteSpace(state.Message)) + errors.Add("Message is required"); + + return errors; + } +} +``` + +### 3. List Components with Actions + +Interactive lists with inline actions: + +```razor +@inherits SimpleHtmxComponent + +
+

Todo List (@State.Items.Count(i => !i.IsCompleted) remaining)

+ +
+ + +
+ +
    + @foreach (var item in State.Items) + { +
  • +
    + + + @item.Text + + @if (!item.IsCompleted) + { + + } + else + { + + } + + +
    +
  • + } +
+
+ +@code { + protected override TodoListState OnPost(TodoListState state) + { + var form = Request.Form; + var action = form["action"].ToString(); + + return action switch + { + "add" => AddItem(state, form["newItem"]), + "complete" => ToggleItem(state, form["itemId"], true), + "uncomplete" => ToggleItem(state, form["itemId"], false), + "delete" => DeleteItem(state, form["itemId"]), + _ => state + }; + } +} +``` + +### 4. Modal Components + +Reusable modal pattern: + +```razor +@inherits HtmxComponentBase + + + +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } +} +``` + +Usage: + +```razor + +``` + +### 5. Infinite Scroll Components + +Load more content as user scrolls: + +```razor +@inherits SimpleHtmxComponent + +
+ @foreach (var article in State.Articles) + { +
+

@article.Title

+

@article.Summary

+
+ } + + @if (State.HasMore) + { +
+ Loading more... +
+ } +
+ +@code { + protected override void OnGet(ArticleListState state) + { + var newArticles = LoadArticles(state.Page); + State = state with + { + Articles = state.Articles.Concat(newArticles).ToList(), + Page = state.Page + 1, + HasMore = newArticles.Any() + }; + } +} +``` + +## Advanced Techniques + +### Dynamic Attribute Building + +Build attributes conditionally: + +```razor +@inherits HtmxComponentBase + +@code { + protected override void OnInitialized() + { + if (ShouldAutoRefresh) + { + HxGet = "/api/status"; + HxTrigger = "every 5s"; + HxSwap = "innerHTML"; + } + + if (RequiresConfirmation) + { + HxConfirm = "Are you sure?"; + } + } +} +``` + +### Component Composition + +Compose complex components from simpler ones: + +```razor +@* DataTable.razor *@ +@inherits HtmxComponentBase +@typeparam TItem + + + + + @HeaderTemplate + + + + @foreach (var item in Items) + { + + @RowTemplate(item) + + } + +
+ +@if (ShowPagination) +{ + +} + +@code { + [Parameter] public IEnumerable Items { get; set; } = []; + [Parameter] public RenderFragment? HeaderTemplate { get; set; } + [Parameter] public RenderFragment? RowTemplate { get; set; } + [Parameter] public bool ShowPagination { get; set; } + [Parameter] public int CurrentPage { get; set; } + [Parameter] public int TotalPages { get; set; } + [Parameter] public EventCallback OnPageChange { get; set; } +} +``` + +### Event Coordination + +Coordinate events between components: + +```razor +@inherits SimpleHtmxComponent + +
+ + + +
+ @RenderContent() +
+
+ +@code { + private RenderFragment RenderContent() + { + return builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, "Filter", State.Filter); + builder.CloseComponent(); + }; + } + + protected override DashboardState OnPost(DashboardState state) + { + var filter = Request.Query["filter"].ToString(); + return state with { Filter = filter }; + } +} +``` + +### Lazy Loading + +Load expensive content on demand: + +```razor +@inherits HtmxComponentBase + +
+ @if (!IsLoaded) + { +
+ +
+ } + else + { + @ChildContent + } +
+ +@code { + [Parameter] public string LoadUrl { get; set; } = ""; + [Parameter] public string LoadTrigger { get; set; } = "revealed"; + [Parameter] public bool IsLoaded { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } +} +``` + +### Error Handling + +Graceful error handling in components: + +```razor +@inherits SimpleHtmxComponent + +
+ @if (State.IsLoading) + { + + } + else if (State.Error != null) + { + + } + else + { + @RenderData() + } +
+ +@code { + protected override async Task OnGetAsync(DataState state) + { + try + { + State = state with { IsLoading = true, Error = null }; + + var data = await LoadDataAsync(); + State = state with + { + Data = data, + IsLoading = false + }; + } + catch (Exception ex) + { + State = state with + { + Error = "Failed to load data", + IsLoading = false + }; + Logger.LogError(ex, "Error loading data"); + } + } +} +``` + +## Performance Optimization + +### 1. Component Output Caching + +Cache rendered components: + +```C# +app.MapHtmxGet("/expensive") + .CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(5))); +``` + +### 2. Conditional Rendering + +Only render what's necessary: + +```razor +@if (ShouldRender) +{ + +} +``` + +### 3. Debounced Inputs + +Prevent excessive requests: + +```razor + +``` + +## Testing Components + +### Unit Testing + +Test component logic: + +```C# +[Fact] +public void Counter_Increments_On_Post() +{ + // Arrange + var state = new CounterState { Count = 5 }; + var component = new CounterComponent(); + + // Act + var newState = component.OnPost(state); + + // Assert + Assert.Equal(6, newState.Count); +} +``` + +### Integration Testing + +Test component rendering: + +```C# +[Fact] +public async Task Component_Renders_Correctly() +{ + // Arrange + using var ctx = new TestContext(); + var state = new ProductState { Name = "Test Product" }; + + // Act + var component = ctx.RenderComponent( + parameters => parameters.Add(p => p.State, state)); + + // Assert + Assert.Contains("Test Product", component.Markup); +} +``` + +## Best Practices + +1. **Keep Components Focused**: Each component should have a single, clear purpose +2. **Use Meaningful Names**: Component names should describe what they display or do +3. **Minimize State**: Only include essential data in component state +4. **Handle Edge Cases**: Always handle loading, error, and empty states +5. **Test Thoroughly**: Write tests for component logic and rendering +6. **Document Complex Logic**: Add comments for non-obvious behavior +7. **Optimize Selectively**: Only optimize when you have measured performance issues + +## Next Steps + +- [HTMX Attributes](HTMX-Attributes.md) - Complete attribute reference +- [State Management](State-Management.md) - Advanced state patterns +- [Examples](Examples.md) - Real-world examples +- [Performance](Performance.md) - Optimization guide \ No newline at end of file diff --git a/Writerside/topics/Components-API.md b/Writerside/topics/Components-API.md new file mode 100644 index 0000000..44dc36a --- /dev/null +++ b/Writerside/topics/Components-API.md @@ -0,0 +1,181 @@ +# Components API + +This section covers the core component APIs provided by FastComponents. + + + +The foundation for all HTMX-enabled components. + + + +HxGet +: URL for GET requests + +HxPost +: URL for POST requests + +HxPut +: URL for PUT requests + +HxPatch +: URL for PATCH requests + +HxDelete +: URL for DELETE requests + +HxTrigger +: Event that triggers the request + +HxTarget +: Target element for swapping content + +HxSwap +: How to swap the content + + + + + +HxBoost +: Progressively enhance links and forms + +HxConfirm +: Confirmation dialog before request + +HxDisable +: Disable element during request + +HxIndicator +: Loading indicator element + +HxParams +: Parameters to include/exclude + +HxHeaders +: Additional headers to send + + + + + +### Example {#htmx-component-example} + + +@inherits HtmxComponentBase + +<div @attributes="@HtmxAttributes"> + <button hx-get="@HxGet" + hx-target="@HxTarget" + hx-swap="@HxSwap"> + Click Me + </button> +</div> + +@code { + protected override void OnInitialized() + { + HxGet = "/api/data"; + HxTarget = "#result"; + HxSwap = "innerHTML"; + } +} + + +## SimpleHtmxComponent + +A simplified base class that automatically generates routes based on component name. + +### Properties + +State +: The component's state object + +Url +: Auto-generated URL for the component + +### Example {#simple-component-example} + + +@inherits SimpleHtmxComponent<CounterState> + +<div> + <p>Count: @State.Count</p> + <button hx-get="@Url" hx-target="this" hx-swap="outerHTML"> + Increment + </button> +</div> + +@code { + public class CounterState : HtmxComponentParameters + { + public int Count { get; set; } + } +} + + +## HtmxComponentParameters + +Base class for component state/parameters that can be bound from query strings. + +### Methods {#parameters-methods} + +BindFromQuery(IQueryCollection) +: Bind parameters from query string + +BuildQueryString() +: Convert parameters to query string + +ToComponentUrl(string) +: Create URL with parameters + +### Example {#parameters-example} + + +public class SearchParameters : HtmxComponentParameters +{ + public string Query { get; set; } = ""; + public int Page { get; set; } = 1; + + public override void BindFromQuery(IQueryCollection query) + { + Query = GetQueryValue(query, nameof(Query)) ?? ""; + Page = GetQueryInt(query, nameof(Page)) ?? 1; + } +} + + +## ClassNamesBuilder + +Fluent API for building CSS class names dynamically. + +### Methods {#classnames-methods} + +AddClass(string, bool) +: Add class conditionally + +AddRawValue(string) +: Add raw CSS class string + +Build() +: Build final class string + +### Example {#classnames-example} + + +@code { + protected override void OnBuildClassNames( + ClassNamesBuilder builder) + { + builder + .AddClass("active", IsActive) + .AddClass("disabled", IsDisabled) + .AddClass("primary", IsPrimary); + } +} + + +## See Also + +- [Core Concepts](Core-Concepts.md) +- [Component Development](Component-Development.md) +- [HTMX Attributes](HTMX-Attributes.md) \ No newline at end of file diff --git a/Writerside/topics/Core-Concepts.md b/Writerside/topics/Core-Concepts.md new file mode 100644 index 0000000..638a2ce --- /dev/null +++ b/Writerside/topics/Core-Concepts.md @@ -0,0 +1,422 @@ +# Core Concepts + + +

Master the fundamental concepts: component architecture, state management, HTMX integration, and routing patterns.

+

Build on these foundations to create powerful, interactive web applications.

+
+ +Understanding the core concepts of FastComponents will help you build more effective and maintainable applications. + + + +Learn about the different base classes and their use cases + + +Understand how to manage and pass state between components + + +Discover how HTMX attributes work with C# properties + + +Master URL patterns and endpoint configuration + + + + + +FastComponents provides several base classes for different scenarios, each optimized for specific use cases. + + + +The foundation class providing full HTMX attribute access and flexibility. + + +Streamlined base class for stateless components with convention-based routing. + + +Stateful components with automatic state management and URL binding. + + + + + +Use HtmxComponentBase when you need: +- Full control over HTMX attributes +- Custom attribute handling +- Complex component logic +- Integration with existing Blazor patterns + + +Use SimpleHtmxComponent when you need: +- Simple, stateless components +- Convention-based routing +- Minimal boilerplate code +- Quick prototyping + + +Use SimpleHtmxComponent<TState> when you need: +- State management with URL binding +- Type-safe parameter handling +- Automatic serialization/deserialization +- Component state persistence + + + + +### HtmxComponentBase + +The foundation for all HTMX-enabled components: + +```Razor +@inherits HtmxComponentBase + +
+ +
+``` + +Features: +- Access to all HTMX attributes as strongly-typed properties +- Automatic attribute rendering +- CSS class building support +- Custom attribute handling + +#### SimpleHtmxComponent {#simple-component} + +A simplified base class for stateless components: + +```Razor +@inherits SimpleHtmxComponent + + +``` + +#### SimpleHtmxComponent<TState> {#simple-component-state} + +For components with state management: + +```Razor +@inherits SimpleHtmxComponent + +
+

@State.Title

+ +
+ +@code { + protected override TodoState OnPost(TodoState state) + { + return state with { IsCompleted = true }; + } +} +``` + +## HTMX Attribute System + +### Attribute Categories + +FastComponents organizes HTMX attributes into logical groups: + +#### Core Attributes (IHxCoreAttributes) +Essential HTMX functionality: +- `HxGet`, `HxPost`, `HxPut`, `HxDelete`, `HxPatch` - HTTP methods +- `HxTrigger` - Event triggers +- `HxTarget` - Target element selection +- `HxSwap` - Content swapping strategies +- `HxPushUrl` - URL management + +#### Additional Attributes (IHxAdditionalAttributes) +Extended functionality: +- `HxConfirm` - Confirmation dialogs +- `HxDisable` - Element disabling +- `HxIndicator` - Loading indicators +- `HxValidate` - Form validation +- `HxSync` - Request synchronization + +#### CSS Classes (IHxCssClasses) +Dynamic styling during requests: +- `HxCssRequest` - Applied during requests +- `HxCssSwapping` - Applied during content swap +- `HxCssSettling` - Applied during settling phase +- `HxCssAdded` - Applied to new content + +### Using Attributes + +Attributes can be set in multiple ways: + +```Razor +// In code +@code { + protected override void OnInitialized() + { + HxGet = "/api/data"; + HxTrigger = "click"; + HxTarget = "#result"; + HxSwap = "innerHTML"; + } +} + +// Or inline +
+ Click me +
+``` + +## State Management + +### Component Parameters + +Define strongly-typed parameters using records: + +```C# +[GenerateParameterMethods] +public partial record ProductListState : HtmxComponentParameters +{ + public string SearchTerm { get; init; } = ""; + public int Page { get; init; } = 1; + public string SortBy { get; init; } = "name"; +} +``` + +The `[GenerateParameterMethods]` attribute generates: +- `BuildQueryString()` - Serializes state to query string +- `BindFromQuery()` - Deserializes state from query string + +### State Flow + +1. **Initial Request**: State is created from query parameters +2. **Component Render**: Component receives state and renders +3. **User Interaction**: HTMX sends request with current state +4. **State Update**: Handler methods update state +5. **Re-render**: Component re-renders with new state + +Example flow: + +```Razor +@inherits SimpleHtmxComponent + + +
+ +
+ +
+ @foreach (var item in GetResults()) + { +
@item.Name
+ } +
+ +@code { + // 2. State is automatically bound from query string + private IEnumerable GetResults() + { + return ProductService.Search(State.Query); + } +} +``` + +
+ + + +### Manual Mapping + +Map components to specific routes: + +```C# +// GET requests +app.MapHtmxGet("/products"); + +// POST requests +app.MapHtmxPost("/cart/add"); + +// Multiple verbs +app.MapHtmxGet("/todo/{id}"); +app.MapHtmxPost("/todo/{id}"); +app.MapHtmxDelete("/todo/{id}"); +``` + +### Convention-Based Mapping + +Automatically map components by naming convention: + +```C# +// Maps all components ending with "Component" or "Example" +app.MapHtmxComponentsByConvention(); + +// Component: ProductListComponent +// Route: /htmx/product-list + +// Component: SearchExample +// Route: /htmx/search +``` + +### Route Parameters + +Access route values in your components: + +```Razor +@inherits SimpleHtmxComponent +@inject IHttpContextAccessor HttpContextAccessor + +@code { + protected override void OnInitialized() + { + var id = HttpContextAccessor.HttpContext? + .GetRouteValue("id")?.ToString(); + // Use the ID... + } +} +``` + +## Request/Response Headers + +### Request Headers + +Access HTMX request headers: + +```C# +public class MyEndpoint +{ + public IResult Handle(HttpContext context) + { + var htmxHeaders = context.GetHtmxRequestHeaders(); + + if (htmxHeaders.IsHtmxRequest) + { + // Handle HTMX request + var trigger = htmxHeaders.Trigger; + var target = htmxHeaders.Target; + + if (htmxHeaders.IsBoosted) + { + // Handle boosted request + } + } + + return Results.Ok(); + } +} +``` + +### Response Headers + +Control HTMX behavior with response headers: + +```C# +public IResult Handle(HttpContext context) +{ + var htmxResponse = context.GetHtmxResponseHeaders(); + + // Trigger events + htmxResponse.Trigger("dataUpdated"); + + // Redirect + htmxResponse.Redirect("/success"); + + // Update URL + htmxResponse.PushUrl("/products?page=2"); + + // Retarget response + htmxResponse.Retarget("#different-element"); + + return Results.Ok(); +} +``` + +## Component Lifecycle + +### Initialization Flow + +1. **Parameter Binding**: State is bound from query string +2. **OnInitialized**: Component initialization +3. **OnParametersSet**: Parameters are set +4. **BuildRenderTree**: Component renders + +### HTTP Method Handlers + +Components can handle different HTTP methods: + +```C# +@inherits SimpleHtmxComponent + +@code { + // Handle GET requests (default behavior) + protected override void OnGet(ItemState state) + { + // Load data + } + + // Handle POST requests + protected override ItemState OnPost(ItemState state) + { + // Update and return new state + return state with { Updated = true }; + } + + // Handle DELETE requests + protected override ItemState OnDelete(ItemState state) + { + // Delete logic + return state with { Deleted = true }; + } +} +``` + + + + + +Use the `ClassNamesBuilder` for dynamic CSS classes: + +```Razor +@inherits HtmxComponentBase + +@code { + protected override void OnBuildClassNames( + ClassNamesBuilder builder) + { + builder + .AddClass("card") + .AddClass("active", IsActive) + .AddClass("disabled", IsDisabled) + .AddClass($"theme-{Theme}"); + } +} +``` + + + +## Best Practices + +### 1. Component Granularity +- Keep components focused on a single responsibility +- Break complex UIs into smaller, reusable components +- Use composition over inheritance + +### 2. State Management +- Use immutable records for state +- Keep state minimal and normalized +- Consider using URL state for shareable views + +### 3. Performance +- Use appropriate swap strategies +- Implement proper caching headers +- Consider using SSE or WebSockets for real-time updates + +### 4. Security +- Always validate input on the server +- Use anti-forgery tokens for state-changing operations +- Implement proper authorization checks + +## Next Steps + +- [Component Development](Component-Development.md) - Advanced component techniques +- [HTMX Attributes](HTMX-Attributes.md) - Complete attribute reference +- [State Management](State-Management.md) - Advanced state patterns +- [Performance](Performance.md) - Optimization techniques \ No newline at end of file diff --git a/Writerside/topics/Deployment.md b/Writerside/topics/Deployment.md new file mode 100644 index 0000000..6d055be --- /dev/null +++ b/Writerside/topics/Deployment.md @@ -0,0 +1,448 @@ +# Deployment + +This guide covers deploying FastComponents applications to various hosting platforms and environments. + +## Production Configuration + +### Environment Settings + +```C# +var builder = WebApplication.CreateBuilder(args); + +// Configure for production +if (builder.Environment.IsProduction()) +{ + builder.Services.AddHsts(options => + { + options.IncludeSubDomains = true; + options.MaxAge = TimeSpan.FromDays(365); + }); +} + +// Add FastComponents +builder.Services.AddFastComponents(); + +var app = builder.Build(); + +// Production middleware +if (app.Environment.IsProduction()) +{ + app.UseHsts(); + app.UseHttpsRedirection(); +} + +app.UseFastComponents(); +app.Run(); +``` + +### Static File Optimization + +```C# +// Configure static files +app.UseStaticFiles(new StaticFileOptions +{ + OnPrepareResponse = ctx => + { + // Cache static assets for 30 days + if (ctx.File.Name.EndsWith(".js") || + ctx.File.Name.EndsWith(".css") || + ctx.File.Name.EndsWith(".woff2")) + { + ctx.Context.Response.Headers.CacheControl = + "public,max-age=2592000"; // 30 days + } + } +}); +``` + +## Docker Deployment + +### Dockerfile + +```dockerfile +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["YourApp/YourApp.csproj", "YourApp/"] +COPY ["FastComponents/FastComponents.csproj", "FastComponents/"] +RUN dotnet restore "YourApp/YourApp.csproj" +COPY . . +WORKDIR "/src/YourApp" +RUN dotnet build "YourApp.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "YourApp.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "YourApp.dll"] +``` + +### Docker Compose + +```yaml +version: '3.8' +services: + app: + build: . + ports: + - "8080:8080" + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ASPNETCORE_URLS=http://+:8080 + - ConnectionStrings__DefaultConnection=Server=db;Database=AppDB;User Id=sa;Password=YourPassword; + depends_on: + - db + volumes: + - ./logs:/app/logs + + db: + image: mcr.microsoft.com/mssql/server:2022-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=YourPassword + volumes: + - db_data:/var/opt/mssql + +volumes: + db_data: +``` + +## Cloud Deployment + +### Azure App Service + +#### Deploy with Azure CLI + +```bash +# Create resource group +az group create --name myResourceGroup --location "East US" + +# Create App Service plan +az appservice plan create --name myAppServicePlan --resource-group myResourceGroup --sku B1 --is-linux + +# Create web app +az webapp create --resource-group myResourceGroup --plan myAppServicePlan --name myFastComponentsApp --runtime "DOTNETCORE:9.0" + +# Deploy from ZIP +az webapp deploy --resource-group myResourceGroup --name myFastComponentsApp --src-path ./publish.zip --type zip +``` + +#### App Service Configuration + +```json +{ + "ASPNETCORE_ENVIRONMENT": "Production", + "WEBSITE_RUN_FROM_PACKAGE": "1", + "ConnectionStrings__DefaultConnection": "your-connection-string" +} +``` + +### AWS Elastic Beanstalk + +#### Configuration File (.ebextensions/01-aspnetcore.config) + +```yaml +option_settings: + aws:elasticbeanstalk:container:dotnet: + Target: "/aws-eb-reqs.txt" + aws:elasticbeanstalk:application:environment: + ASPNETCORE_ENVIRONMENT: Production + ConnectionStrings__DefaultConnection: "your-connection-string" +``` + +### Google Cloud Run + +#### Deploy Script + +```bash +# Build and push container +gcloud builds submit --tag gcr.io/PROJECT_ID/fastcomponents-app + +# Deploy to Cloud Run +gcloud run deploy fastcomponents-app \ + --image gcr.io/PROJECT_ID/fastcomponents-app \ + --platform managed \ + --region us-central1 \ + --allow-unauthenticated \ + --set-env-vars ASPNETCORE_ENVIRONMENT=Production +``` + +## Performance Optimization + +### Response Compression + +```C# +builder.Services.AddResponseCompression(options => +{ + options.Providers.Add(); + options.Providers.Add(); + options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat( + new[] { "application/javascript", "text/css", "text/html" }); +}); + +app.UseResponseCompression(); +``` + +### Output Caching + +```C# +builder.Services.AddOutputCache(options => +{ + options.AddBasePolicy(builder => builder.Cache()); + options.AddPolicy("StaticContent", builder => + builder.Cache().Expire(TimeSpan.FromHours(1))); +}); + +app.UseOutputCache(); + +// Apply to endpoints +app.MapHtmxGet("/htmx/static") + .CacheOutput("StaticContent"); +``` + +### CDN Configuration + +For static assets, configure a CDN: + +```C# +builder.Services.Configure(options => +{ + if (builder.Environment.IsProduction()) + { + options.FileProvider = new PhysicalFileProvider( + Path.Combine(builder.Environment.WebRootPath, "static")); + options.RequestPath = "/static"; + } +}); +``` + +## Database Deployment + +### Entity Framework Migrations + +```C# +// In Program.cs for automatic migrations +if (app.Environment.IsProduction()) +{ + using var scope = app.Services.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + context.Database.Migrate(); +} +``` + +### Migration Scripts + +```bash +# Generate migration script +dotnet ef migrations script --startup-project YourApp --project YourApp.Data --output migration.sql + +# Apply in production +sqlcmd -S server -d database -i migration.sql +``` + +## Security Configuration + +### HTTPS Configuration + +```C# +builder.Services.AddHttpsRedirection(options => +{ + options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect; + options.HttpsPort = 443; +}); + +// Certificate configuration +builder.Services.Configure(options => +{ + options.ConfigureHttpsDefaults(httpsOptions => + { + httpsOptions.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13; + }); +}); +``` + +### Security Headers + +```C# +app.Use(async (context, next) => +{ + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "DENY"); + context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); + context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); + + await next(); +}); +``` + +## Environment-Specific Configuration + +### appsettings.Production.json + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "yourdomain.com", + "ConnectionStrings": { + "DefaultConnection": "Production-Connection-String" + }, + "FastComponents": { + "EnableDebugHeaders": false, + "CacheComponentsInProduction": true + } +} +``` + +### Secrets Management + +```bash +# Azure Key Vault +dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets + +# AWS Systems Manager +dotnet add package Amazon.Extensions.Configuration.SystemsManager + +# Google Secret Manager +dotnet add package Google.Cloud.SecretManager.V1 +``` + +```C# +// Configure secrets +if (builder.Environment.IsProduction()) +{ + builder.Configuration.AddAzureKeyVault( + vaultUri: "https://yourvault.vault.azure.net/", + credential: new DefaultAzureCredential()); +} +``` + +## Monitoring and Logging + +### Application Insights + +```csharp +builder.Services.AddApplicationInsightsTelemetry(); + +// Custom telemetry +builder.Services.AddSingleton(); +``` + +### Structured Logging + +```csharp +builder.Services.AddLogging(builder => +{ + builder.AddConsole(); + builder.AddApplicationInsights(); + + if (builder.Environment.IsProduction()) + { + builder.AddEventLog(); + } +}); +``` + +## Health Checks + +### Configure Health Checks + +```csharp +builder.Services.AddHealthChecks() + .AddDbContext() + .AddCheck("htmx-endpoints", () => + { + // Custom health check for HTMX endpoints + return HealthCheckResult.Healthy(); + }); + +app.MapHealthChecks("/health", new HealthCheckOptions +{ + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); +``` + +## Deployment Checklist + +### Pre-Deployment + +- [ ] Environment variables configured +- [ ] Connection strings updated +- [ ] HTTPS certificates installed +- [ ] Database migrations prepared +- [ ] Static files optimized +- [ ] Security headers configured + +### Post-Deployment + +- [ ] Health checks passing +- [ ] Logs are being generated +- [ ] Performance metrics baseline +- [ ] Error tracking configured +- [ ] Backup procedures tested +- [ ] Rollback plan ready + +### Monitoring + +- [ ] Application performance monitoring +- [ ] Error rate monitoring +- [ ] Database performance monitoring +- [ ] Infrastructure monitoring +- [ ] Security monitoring +- [ ] User experience monitoring + +## Troubleshooting + +### Common Issues + +#### HTMX Requests Failing + +```csharp +// Enable detailed errors temporarily +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} +else +{ + app.UseExceptionHandler("/Error"); +} +``` + +#### Static Files Not Loading + +```csharp +// Verify static file configuration +app.UseStaticFiles(new StaticFileOptions +{ + FileProvider = new PhysicalFileProvider( + Path.Combine(builder.Environment.ContentRootPath, "wwwroot")), + RequestPath = "/static" +}); +``` + +#### Performance Issues + +1. Enable response compression +2. Configure output caching +3. Optimize database queries +4. Use CDN for static assets +5. Monitor with APM tools + +## See Also + +- [Performance](Performance.md) +- [Security](Security.md) +- [AOT Support](AOT-Support.md) \ No newline at end of file diff --git a/Writerside/topics/Endpoints-API.md b/Writerside/topics/Endpoints-API.md new file mode 100644 index 0000000..ac6be51 --- /dev/null +++ b/Writerside/topics/Endpoints-API.md @@ -0,0 +1,116 @@ +# Endpoints API + +FastComponents provides extension methods for mapping HTMX components to ASP.NET Core endpoints. + +## HtmxComponentEndpoints + +Extension methods for registering HTMX components with the ASP.NET Core routing system. + +### Methods + +#### MapHtmxGet + +Maps a component to handle GET requests. + +```C# +app.MapHtmxGet("/htmx/counter") + .AllowAnonymous(); +``` + +#### MapHtmxPost + +Maps a component to handle POST requests. + +```C# +app.MapHtmxPost("/htmx/form") + .RequireAuthorization(); +``` + +#### MapHtmxPut + +Maps a component to handle PUT requests. + +```C# +app.MapHtmxPut("/htmx/update/{id}") + .RequireAuthorization("Admin"); +``` + +#### MapHtmxDelete + +Maps a component to handle DELETE requests. + +```C# +app.MapHtmxDelete("/htmx/delete/{id}") + .RequireAuthorization(); +``` + +#### MapHtmxPatch + +Maps a component to handle PATCH requests. + +```C# +app.MapHtmxPatch("/htmx/patch/{id}") + .RequireAuthorization(); +``` + +## Convention-Based Registration + +Automatically discover and register components based on naming conventions. + +### MapHtmxComponentsByConvention + +```C# +// Register all components in the calling assembly +app.MapHtmxComponentsByConvention(); + +// Register components from specific assemblies +app.MapHtmxComponentsByConvention( + typeof(Program).Assembly, + typeof(SharedComponents).Assembly +); +``` + +### Naming Conventions + +- `CounterComponent` → `/htmx/counter` +- `MovieCharactersExample` → `/htmx/movie-characters` +- `UserProfileComponent` → `/htmx/user-profile` + +Common suffixes like "Component" and "Example" are automatically removed, and names are converted to kebab-case. + +## Simplified API + +Use the auto-registration methods for the simplest setup: + +```C# +// In Program.cs +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddFastComponentsAuto(); + +var app = builder.Build(); +app.UseFastComponentsAuto(); +``` + +## Route Parameters + +Components can accept route parameters through their state objects: + +```C# +public class ProductState : HtmxComponentParameters +{ + [FromRoute] + public int Id { get; set; } + + [FromQuery] + public string? Category { get; set; } +} + +// Register with route template +app.MapHtmxGet("/htmx/product/{id}"); +``` + +## See Also + +- [Getting Started](Getting-Started.md) +- [Components API](Components-API.md) +- [State Management](State-Management.md) \ No newline at end of file diff --git a/Writerside/topics/Events-API.md b/Writerside/topics/Events-API.md new file mode 100644 index 0000000..5d4be3d --- /dev/null +++ b/Writerside/topics/Events-API.md @@ -0,0 +1,230 @@ +# Events API + +FastComponents provides constants for all HTMX events to ensure type safety and consistency. + +## HtmxEvents + +Static class containing all HTMX event names as constants. + +### Request Lifecycle Events + +#### BeforeRequest +Triggered before an AJAX request is sent. + +```C# +HxOn = $"{HtmxEvents.BeforeRequest}: showLoader()" +``` + +#### AfterRequest +Triggered after an AJAX request completes. + +```C# +HxOn = $"{HtmxEvents.AfterRequest}: hideLoader()" +``` + +#### BeforeSwap +Triggered before content is swapped into the DOM. + +```C# +HxOn = $"{HtmxEvents.BeforeSwap}: prepareContent(event)" +``` + +#### AfterSwap +Triggered after content is swapped into the DOM. + +```C# +HxOn = $"{HtmxEvents.AfterSwap}: initializeComponents()" +``` + +#### AfterSettle +Triggered after content has settled (animations complete). + +```C# +HxOn = $"{HtmxEvents.AfterSettle}: focusFirstInput()" +``` + +### Error Events + +#### ResponseError +Triggered when a response error occurs. + +```C# +HxOn = $"{HtmxEvents.ResponseError}: handleError(event)" +``` + +#### SendError +Triggered when a network error occurs. + +```C# +HxOn = $"{HtmxEvents.SendError}: showNetworkError()" +``` + +#### TargetError +Triggered when the target element is not found. + +```C# +HxOn = $"{HtmxEvents.TargetError}: console.error('Target not found')" +``` + +#### Timeout +Triggered when a request times out. + +```C# +HxOn = $"{HtmxEvents.Timeout}: showTimeoutMessage()" +``` + +### History Events + +#### HistoryRestore +Triggered when history is restored. + +```C# +HxOn = $"{HtmxEvents.HistoryRestore}: restorePageState()" +``` + +#### BeforeHistorySave +Triggered before history is saved. + +```C# +HxOn = $"{HtmxEvents.BeforeHistorySave}: savePageState()" +``` + +### Validation Events + +#### ValidationValidate +Triggered during form validation. + +```C# +HxOn = $"{HtmxEvents.ValidationValidate}: customValidation(event)" +``` + +#### ValidationFailed +Triggered when validation fails. + +```C# +HxOn = $"{HtmxEvents.ValidationFailed}: showValidationErrors(event)" +``` + +### Other Events + +#### Confirm +Triggered for confirmation dialogs. + +```C# +HxOn = $"{HtmxEvents.Confirm}: customConfirm(event)" +``` + +#### Prompt +Triggered for prompt dialogs. + +```csharp +HxOn = $"{HtmxEvents.Prompt}: customPrompt(event)" +``` + +#### Load +Triggered when content is loaded. + +```csharp +HxOn = $"{HtmxEvents.Load}: onContentLoaded()" +``` + +#### ConfigRequest +Triggered to configure requests. + +```csharp +HxOn = $"{HtmxEvents.ConfigRequest}: addAuthHeaders(event)" +``` + +## Usage Examples + +### Multiple Event Handlers + +```csharp +@inherits HtmxComponentBase + +
+ Load Data +
+``` + +### Component Example + +```csharp +@inherits HtmxComponentBase + +@code { + protected override void OnInitialized() + { + HxOn = $@" + {HtmxEvents.BeforeRequest}: this.classList.add('loading'); + {HtmxEvents.AfterRequest}: this.classList.remove('loading'); + {HtmxEvents.ResponseError}: alert('Error loading data'); + "; + } +} +``` + +### JavaScript Integration + +```javascript +document.body.addEventListener('htmx:beforeRequest', function(evt) { + // Show loading indicator +}); + +document.body.addEventListener('htmx:afterSwap', function(evt) { + // Initialize new components +}); +``` + +## Complete Event List + +- `Abort` +- `AfterOnLoad` +- `AfterProcessNode` +- `AfterRequest` +- `AfterSettle` +- `AfterSwap` +- `BeforeCleanupElement` +- `BeforeHistorySave` +- `BeforeOnLoad` +- `BeforeProcessNode` +- `BeforeRequest` +- `BeforeSwap` +- `BeforeTransition` +- `Cancel` +- `ConfigRequest` +- `Confirm` +- `HistoryCacheMiss` +- `HistoryCacheMissError` +- `HistoryCacheMissLoad` +- `HistoryRestore` +- `Load` +- `NoSseSourceError` +- `OobAfterSwap` +- `OobBeforeSwap` +- `OobErrorNoTarget` +- `Prompt` +- `PushedIntoHistory` +- `ReplacedInHistory` +- `ResponseError` +- `SendError` +- `SseError` +- `SseMessage` +- `SseOpen` +- `SwapError` +- `TargetError` +- `Timeout` +- `ValidationFailed` +- `ValidationHalted` +- `ValidationValidate` +- `XhrAbort` +- `XhrLoadEnd` +- `XhrLoadStart` +- `XhrProgress` + +## See Also + +- [HTMX Attributes](HTMX-Attributes.md) +- [HTTP API](HTTP-API.md) +- [Component Development](Component-Development.md) \ No newline at end of file diff --git a/Writerside/topics/Examples.md b/Writerside/topics/Examples.md new file mode 100644 index 0000000..e929235 --- /dev/null +++ b/Writerside/topics/Examples.md @@ -0,0 +1,729 @@ +# Examples and Tutorials + + +

Practical examples and real-world patterns for building interactive web applications with FastComponents.

+

From simple forms to complex dashboards - learn by example.

+
+ +This section provides practical examples of common patterns and real-world scenarios using FastComponents. + +## Interactive Examples + + + +Build a responsive search component that updates results as you type + + +Create seamless in-place editing without page navigation + + +Implement a dynamic cart with real-time updates + + +Create dynamic tabs with lazy-loaded content + + +Build feature-rich tables with sorting and filtering + + + + + +A search component that updates results as you type, providing instant feedback to users. + + + +Create the search state record: + + +// Models/SearchState.cs +[GenerateParameterMethods] +public partial record SearchState : HtmxComponentParameters +{ + public string Query { get; init; } = ""; + public List<Product> Results { get; init; } = []; + public bool IsLoading { get; init; } +} + + + +Implement the search component: + + +// Components/LiveSearch.razor +@inherits SimpleHtmxComponent<SearchState> + +<div class="search-container"> + <form hx-get="@Url" + hx-target="#search-results" + hx-trigger="keyup changed delay:500ms from:input"> + <input type="search" + name="query" + value="@State.Query" + placeholder="Search products..." + autocomplete="off" /> + </form> + + <div id="search-results"> + @if (!string.IsNullOrEmpty(State.Query)) + { + @if (State.IsLoading) + { + <div class="loading">Searching...</div> + } + else if (!State.Results.Any()) + { + <div class="no-results"> + No results found for "@State.Query" + </div> + } + else + { + <div class="results"> + @foreach (var product in State.Results) + { + <div class="result-item"> + <h4>@product.Name</h4> + <p>@product.Description</p> + <span class="price">$@product.Price</span> + </div> + } + </div> + } + } + </div> +</div> + +@code { + [Inject] private IProductService ProductService { get; set; } = null!; + + protected override async Task OnGetAsync(SearchState state) + { + if (string.IsNullOrWhiteSpace(state.Query)) + { + State = state with { Results = [], IsLoading = false }; + return; + } + + State = state with { IsLoading = true }; + + var results = await ProductService.SearchAsync(state.Query); + State = state with { Results = results, IsLoading = false }; + } +} + + + + + + + +The `delay:500ms` trigger prevents excessive server requests while typing. Adjust the delay based on your application's needs. + + + + + + +Edit content in place without navigating away, providing a seamless user experience. + + +Always validate and sanitize user input on the server side, even for inline editing scenarios. + + + +// Components/EditableField.razor +@inherits SimpleHtmxComponent<EditableFieldState> + +@if (State.IsEditing) +{ +
+ + + +
+} +else +{ +
+ @State.Value + +
+} + +@code { + protected override EditableFieldState OnGet(EditableFieldState state) + { + var isEditing = Request.Query["edit"] == "true"; + return state with { IsEditing = isEditing }; + } + + protected override async Task OnPostAsync(EditableFieldState state) + { + var action = Request.Form["action"]; + + if (action == "save") + { + var newValue = Request.Form["value"].ToString(); + // Save to database + await SaveValueAsync(state.FieldId, newValue); + return state with { Value = newValue, IsEditing = false }; + } + + return state with { IsEditing = false }; + } +} +
+ +
+ + + +A dynamic shopping cart with real-time updates that demonstrates complex state management. + + +This example shows how to handle multiple actions (add, remove, update quantities) within a single component. + + + +// Components/ShoppingCart.razor +@inherits SimpleHtmxComponent<CartState> + +
+

Shopping Cart (@State.Items.Count items)

+ + @if (!State.Items.Any()) + { +

Your cart is empty

+ } + else + { +
+ @foreach (var item in State.Items) + { +
+ @item.Name +
+

@item.Name

+

$@item.Price

+
+
+ + @item.Quantity + +
+ +
+ } +
+ +
+
+ Subtotal: + $@State.Subtotal +
+
+ Tax: + $@State.Tax +
+
+ Total: + $@State.Total +
+ +
+ } +
+ +@code { + [Inject] private ICartService CartService { get; set; } = null!; + + protected override async Task OnPostAsync(CartState state) + { + var action = Request.Form["action"].ToString(); + var productId = int.Parse(Request.Form["productId"]); + + switch (action) + { + case "increase": + await CartService.IncreaseQuantityAsync(productId); + break; + case "decrease": + await CartService.DecreaseQuantityAsync(productId); + break; + } + + return await LoadCartStateAsync(); + } + + protected override async Task OnDeleteAsync(CartState state) + { + var productId = int.Parse(Request.Form["productId"]); + await CartService.RemoveItemAsync(productId); + return await LoadCartStateAsync(); + } +} +
+ +
+ + + +Dynamic tabs with lazy-loaded content: + +```razor +@* Components/TabbedContent.razor *@ +@inherits SimpleHtmxComponent + +
+
+ @foreach (var tab in State.Tabs) + { + + } +
+ +
+ @if (State.IsLoading) + { +
Loading @State.ActiveTabTitle...
+ } + else + { + @State.Content + } +
+
+ +@code { + protected override async Task OnGetAsync(TabState state) + { + var tabId = Request.Query["tab"].ToString(); + if (string.IsNullOrEmpty(tabId)) + tabId = state.Tabs.First().Id; + + State = state with { ActiveTab = tabId, IsLoading = true }; + + // Load tab content + var content = await LoadTabContentAsync(tabId); + State = state with + { + Content = content, + IsLoading = false, + ActiveTabTitle = state.Tabs.First(t => t.Id == tabId).Title + }; + } +} +``` + +### 5. Data Table with Sorting and Filtering + +A feature-rich data table: + +```razor +@* Components/DataTable.razor *@ +@inherits SimpleHtmxComponent + +
+ +
+ + + +
+ + + + + + + + + + + + + @foreach (var user in State.Users) + { + + + + + + + } + +
+ + Name @GetSortIcon("name") + + + + Email @GetSortIcon("email") + + + + Status @GetSortIcon("status") + + Actions
@user.Name@user.Email + + @user.Status + + + + +
+ + + +
+ +@code { + private string GetSortUrl(string column) + { + var direction = State.SortColumn == column && State.SortDirection == "asc" + ? "desc" : "asc"; + return $"{Url}?sort={column}&dir={direction}"; + } + + private string GetSortIcon(string column) + { + if (State.SortColumn != column) return "↕"; + return State.SortDirection == "asc" ? "↑" : "↓"; + } +} +``` + +## Complete Application Examples + + + +Learn how to build complex forms with validation and progressive disclosure. + + +Create live dashboards with Server-Sent Events and WebSocket integration. + + + + + +A complete wizard implementation demonstrating step management, validation, and data flow. + + + +Define the wizard state: + + +[GenerateParameterMethods] +public partial record WizardState : HtmxComponentParameters +{ + public int CurrentStep { get; init; } = 1; + public int TotalSteps { get; init; } = 3; + public PersonalInfo PersonalInfo { get; init; } = new(); + public Address Address { get; init; } = new(); + public bool ShowErrors { get; init; } +} + + + +Implement the wizard component: + + +@* Components/FormWizard.razor *@ +@inherits SimpleHtmxComponent + +
+ +
+ @for (int i = 1; i <= State.TotalSteps; i++) + { +
+ @i + @GetStepTitle(i) +
+ } +
+ + +
+ @switch (State.CurrentStep) + { + case 1: + + break; + case 2: + + break; + case 3: + + break; + } +
+ + +
+ @if (State.CurrentStep > 1) + { + + } + + @if (State.CurrentStep < State.TotalSteps) + { + + } + else + { + + } +
+
+ +@code { + protected override WizardState OnPost(WizardState state) + { + var action = Request.Form["action"].ToString(); + + return action switch + { + "next" => NextStep(state), + "previous" => state with { CurrentStep = state.CurrentStep - 1 }, + "submit" => SubmitWizard(state), + _ => state + }; + } + + private WizardState NextStep(WizardState state) + { + // Validate current step + if (!ValidateStep(state.CurrentStep, state)) + { + return state with { ShowErrors = true }; + } + + // Save form data based on current step + return state.CurrentStep switch + { + 1 => state with + { + PersonalInfo = GetPersonalInfoFromForm(), + CurrentStep = 2 + }, + 2 => state with + { + Address = GetAddressFromForm(), + CurrentStep = 3 + }, + _ => state + }; + } +} +
+
+ +
+ +
+ + + +A dashboard with live updates: + +```razor +@* Components/Dashboard.razor *@ +@inherits SimpleHtmxComponent + +
+ +
+
+

Total Users

+
@State.TotalUsers
+
+ +
+

Revenue

+
$@State.Revenue
+
+ +
+

Orders Today

+
@State.OrdersToday
+
+
+ + +
+

Recent Activity

+
+ @foreach (var activity in State.RecentActivities) + { +
+ @activity.Time + @activity.Description +
+ } +
+
+ + +
+ +
+
+ + + + + + +Use SSE for one-way real-time updates and WebSockets for +bidirectional communication. + + +
+ +## Best Practices from Examples + + + +Always provide visual feedback during operations - loading states, +success/error messages, progress indicators, and confirmation dialogs. + + +Start with a working non-JavaScript version when possible - forms should +work without HTMX, links should have valid href attributes. + + +Keep component state minimal and focused - only store what's needed +for rendering, derive computed values, use URL state for shareable views. + + +Handle errors gracefully - show user-friendly error messages, +provide retry mechanisms, log errors for debugging. + + +Optimize for performance - debounce rapid triggers, use appropriate +swap strategies, implement caching where beneficial, lazy load +content. + + + +## Next Steps + +- [State Management](State-Management.md) - Advanced state patterns +- [Performance](Performance.md) - Optimization techniques +- [Testing](Testing.md) - Testing strategies +- [Deployment](Deployment.md) - Production deployment \ No newline at end of file diff --git a/Writerside/topics/FAQ.md b/Writerside/topics/FAQ.md new file mode 100644 index 0000000..258c95f --- /dev/null +++ b/Writerside/topics/FAQ.md @@ -0,0 +1,334 @@ +# Frequently Asked Questions + +Common questions and answers about FastComponents. + +## General Questions + +### What is FastComponents? + +FastComponents is a .NET library that combines the component model of Blazor with the simplicity of HTMX. It allows you to build dynamic web applications using server-side rendering and C# without writing JavaScript. + +### How is it different from Blazor? + +While Blazor can run on the client (WebAssembly) or server (Server-Side Blazor with SignalR), FastComponents always renders on the server and uses HTMX for dynamic updates. This means: + +- No WebAssembly download +- No SignalR connection +- Works without JavaScript (progressive enhancement) +- SEO-friendly by default +- Simpler deployment model + +### Do I need to know HTMX to use FastComponents? + +Basic HTMX knowledge is helpful but not required. FastComponents provides strongly-typed C# properties for all HTMX attributes, so you get IntelliSense and compile-time checking. + +### Can I use it with existing ASP.NET Core applications? + +Yes! FastComponents integrates seamlessly with existing ASP.NET Core applications. You can: +- Add it to existing MVC or Razor Pages apps +- Use it alongside Web API endpoints +- Mix it with other frontend frameworks + +## Technical Questions + +### How does state management work? + +FastComponents uses immutable records for state that can be serialized to query strings: + +```C# +[GenerateParameterMethods] +public partial record MyState : HtmxComponentParameters +{ + public int Page { get; init; } = 1; +} +``` + +State flows through URL parameters, making it shareable and bookmarkable. + +### Can I use dependency injection? + +Yes, components support full dependency injection: + +```C# +public class MyComponent : SimpleHtmxComponent +{ + [Inject] private IMyService MyService { get; set; } = null!; + + protected override async Task OnGetAsync(MyState state) + { + var data = await MyService.GetDataAsync(); + // Use the service + } +} +``` + +### How do I handle forms? + +Forms work like regular HTML forms with HTMX enhancement: + +```Razor +
+ + +
+ +@code { + protected override MyState OnPost(MyState state) + { + var email = Request.Form["email"]; + // Process form + } +} +``` + +### Can I use it with a CSS framework? + +Yes! FastComponents renders plain HTML that works with any CSS framework: +- Bootstrap +- Tailwind CSS +- Bulma +- Material Design +- Custom CSS + +### How do I handle authentication? + +Use standard ASP.NET Core authentication: + +```C# +app.MapHtmxGet("/secure") + .RequireAuthorization(); + +// In component +[Authorize] +public class SecureComponent : SimpleHtmxComponent +{ + // Only authenticated users can access +} +``` + +## Performance Questions + +### Is it faster than Blazor Server? + +For many scenarios, yes: +- No SignalR overhead +- No WebSocket connection to maintain +- Stateless HTTP requests +- Better caching opportunities +- Works with CDNs + +### How do I optimize performance? + +1. **Use output caching**: +```C# +app.MapHtmxGet("/cached") + .CacheOutput(); +``` + +2. **Minimize state**: +```C# +[GenerateParameterMethods(SkipDefaults = true)] +``` + +3. **Use appropriate swap strategies** +4. **Enable response compression** + +### Can it scale horizontally? + +Yes! Since FastComponents is stateless (no SignalR), you can: +- Load balance across multiple servers +- Use container orchestration (Kubernetes) +- Deploy to serverless platforms +- Scale based on HTTP request load + +## Development Questions + +### What IDE should I use? + +FastComponents works with: +- Visual Studio 2022 +- Visual Studio Code +- JetBrains Rider +- Any editor with C# support + +### How do I debug components? + +Standard debugging techniques work: +```C# +protected override MyState OnPost(MyState state) +{ + // Set breakpoint here + Debugger.Break(); + + // Or use logging + _logger.LogInformation("State: {@State}", state); +} +``` + +### Can I unit test components? + +Yes, components are easily testable: +```C# +[Fact] +public void Component_Updates_State() +{ + var component = new MyComponent(); + var oldState = new MyState { Count = 1 }; + + var newState = component.OnPost(oldState); + + Assert.Equal(2, newState.Count); +} +``` + +### How do I handle errors? + +Implement error handling in your components: +```C# +protected override async Task OnGetAsync(MyState state) +{ + try + { + return await LoadDataAsync(state); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to load data"); + return state with { Error = "An error occurred" }; + } +} +``` + +## Deployment Questions + +### Where can I deploy FastComponents apps? + +Anywhere that supports ASP.NET Core: +- Azure App Service +- AWS Elastic Beanstalk +- Google Cloud Run +- Docker containers +- Traditional IIS +- Linux VPS + +### Does it support AOT compilation? + +Yes, with some limitations: +```xml +true +``` + +Convention-based features require reflection, so use explicit registration for full AOT. + +### What are the hosting requirements? + +- .NET 9.0 or later runtime +- Any OS that supports .NET (Windows, Linux, macOS) +- No special server requirements +- Works behind reverse proxies + +## Migration Questions + +### Can I migrate from Blazor? + +Yes, components are similar: +1. Change base class to `SimpleHtmxComponent` +2. Replace `@onclick` with HTMX attributes +3. Update state management pattern +4. Remove JavaScript interop + +### Can I use both Blazor and FastComponents? + +Yes, you can use both in the same application: +- Use Blazor for complex interactivity +- Use FastComponents for simpler, SEO-friendly pages +- Share services and models + +### How do I migrate from MVC/Razor Pages? + +1. Extract view logic into components +2. Convert controllers/pages to component endpoints +3. Add HTMX attributes for dynamic behavior +4. Keep existing routing if desired + +## Best Practices + +### When should I use FastComponents? + +FastComponents is ideal for: +- Content-focused websites +- Forms and CRUD operations +- SEO-critical applications +- Progressive enhancement scenarios +- Teams familiar with server-side rendering + +### When should I use something else? + +Consider alternatives for: +- Heavy client-side interactivity (use Blazor WebAssembly) +- Real-time collaboration (use Blazor Server) +- Offline-first applications (use PWA) +- Complex state management (use React/Angular) + +### How do I structure large applications? + +``` +/Components + /Shared # Reusable components + /Features # Feature-specific components + /Layouts # Layout components +/Models # Shared models +/Services # Business logic +/wwwroot # Static files +``` + +## Troubleshooting + +### Why isn't HTMX working? + +Check: +1. HTMX JavaScript is included +2. Correct attribute syntax (`hx-get` not `get`) +3. Target elements exist +4. No JavaScript errors + +### Why is my state not persisting? + +Ensure: +1. Using `[GenerateParameterMethods]` +2. Record is `partial` +3. Inherits from `HtmxComponentParameters` +4. Properties are `init`-only + +### Why am I getting 404 errors? + +Verify: +1. Endpoints are mapped +2. Routes are correct +3. Services are registered +4. Middleware is configured + +## Getting Help + +### Where can I get support? + +- GitHub Issues for bugs +- GitHub Discussions for questions +- Stack Overflow with tag `fastcomponents` +- Official documentation + +### How can I contribute? + +- Report bugs +- Suggest features +- Submit pull requests +- Improve documentation +- Share your experiences + +### Is there commercial support? + +Contact Atypical Consulting SRL for: +- Training +- Consulting +- Custom development +- Priority support \ No newline at end of file diff --git a/Writerside/topics/Getting-Started.md b/Writerside/topics/Getting-Started.md new file mode 100644 index 0000000..594903c --- /dev/null +++ b/Writerside/topics/Getting-Started.md @@ -0,0 +1,236 @@ +# Getting Started with FastComponents + +This guide will help you set up your first FastComponents project and create a simple interactive component. + + + +Before you begin, ensure you have: + +.NET 9.0 SDK or later +: Download from [Microsoft's official site](https://dotnet.microsoft.com/download) + +Code Editor +: Visual Studio, VS Code, or JetBrains Rider + +Basic Knowledge +: C# and ASP.NET Core fundamentals + + + + + + + +Create a new ASP.NET Core Web Application: + + +dotnet new web -n MyFastComponentsApp +cd MyFastComponentsApp + + + +Install the FastComponents NuGet package: + + +dotnet add package FastComponents + + + +Configure services in `Program.cs`: + + +using FastComponents; + +var builder = WebApplication.CreateBuilder(args); + +// Add FastComponents services +builder.Services.AddFastComponents(); + +var app = builder.Build(); + +// Use FastComponents middleware +app.UseFastComponents(); + +// Your endpoints will go here + +app.Run(); + + + + + + + + + + + +Create the component file `Components/Counter.razor`: + + +@inherits SimpleHtmxComponent<CounterState> + +<div class="counter"> + <h2>Counter: @State.Count</h2> + <button hx-post="@Url" + hx-target="closest .counter" + hx-swap="outerHTML"> + Increment + </button> +</div> + +@code { + protected override CounterState OnPost(CounterState state) + { + return state with { Count = state.Count + 1 }; + } +} + + + +Define the component state in `Models/CounterState.cs`: + + +using FastComponents; + +[GenerateParameterMethods] +public partial record CounterState : HtmxComponentParameters +{ + public int Count { get; init; } = 0; +} + + + + + + + + + +Choose your preferred registration method: + + + +

Explicit endpoint mapping in your Program.cs:

+ + + // Map the counter component + app.MapHtmxGet<Counter, CounterState>("/counter"); + app.MapHtmxPost<Counter, CounterState>("/counter"); + +
+ + +

Convention-based automatic discovery:

+ + + // Replace AddFastComponents() and UseFastComponents() with: + builder.Services.AddFastComponentsAuto(); + + var app = builder.Build(); + app.UseFastComponentsAuto(); + + // Components are automatically mapped based on naming conventions + // Counter component → /htmx/counter + +
+
+ +
+ +### 4. Create the Main Page + +Create an `index.html` file in `wwwroot`: + + +<!DOCTYPE html> +<html> +<head> + <title>FastComponents Demo</title> + <script src="https://unpkg.com/htmx.org@2.0.0"> + </script> + <style> + body { + font-family: system-ui, -apple-system, sans-serif; + max-width: 800px; + margin: 0 auto; + padding: 2rem; + } + .counter { + border: 1px solid #ddd; + border-radius: 8px; + padding: 1rem; + margin: 1rem 0; + } + button { + padding: 0.5rem 1rem; + font-size: 1rem; + cursor: pointer; + } + </style> +</head> +<body> + <h1>FastComponents Demo</h1> + + <!-- Load the counter component --> + <div hx-get="/counter" hx-trigger="load"></div> +</body> +</html> + + +### 5. Serve Static Files + +Update `Program.cs` to serve static files: + +```C# +app.UseStaticFiles(); +app.MapFallbackToFile("index.html"); +``` + +## Running the Application + +1. Run the application: + ```bash + dotnet run + ``` + +2. Open your browser and navigate to `https://localhost:5001` (or the port shown in the console) + +3. You should see a counter that increments when you click the button - all without page refreshes! + +## What's Happening? + +1. **Initial Load**: The `hx-get="/counter"` attribute on the div loads the counter component when the page loads +2. **Component Rendering**: The server renders the Counter component and returns it as HTML +3. **Interactions**: When you click the button, HTMX sends a POST request to `/counter` +4. **State Update**: The `OnPost` method increments the count and returns the updated state +5. **DOM Update**: HTMX replaces the counter div with the newly rendered component + +## Using the Simplified API + +For even quicker setup, use the auto-configuration methods: + +```C# +var builder = WebApplication.CreateBuilder(args); + +// Automatically configure everything +builder.Services.AddFastComponentsAuto(); + +var app = builder.Build(); + +// Automatically map all components by convention +app.UseFastComponentsAuto(); + +app.Run(); +``` + +This will automatically discover and map all components in your assembly that inherit from `SimpleHtmxComponent` or `HtmxComponentBase`. + +## Next Steps + +Now that you have a working FastComponents application, explore: + +- [Component Development](Component-Development.md) - Learn advanced component techniques +- [HTMX Attributes](HTMX-Attributes.md) - Understand all available HTMX attributes +- [State Management](State-Management.md) - Handle complex state scenarios +- [Examples](Examples.md) - See more complex examples \ No newline at end of file diff --git a/Writerside/topics/HTMX-Attributes.md b/Writerside/topics/HTMX-Attributes.md new file mode 100644 index 0000000..c4e6d3e --- /dev/null +++ b/Writerside/topics/HTMX-Attributes.md @@ -0,0 +1,573 @@ +# HTMX Attributes Reference + +This comprehensive guide covers all HTMX attributes available in FastComponents, organized by category. + +## Core Attributes + +These are the fundamental HTMX attributes for making requests and updating content. + +### HTTP Methods + +#### HxGet +Performs a GET request to the specified URL. + +```razor + + +@code { + HxGet = "/api/users"; +} +``` + +#### HxPost +Performs a POST request to the specified URL. + +```razor +
+ + +
+ +@code { + HxPost = "/api/users"; +} +``` + +#### HxPut +Performs a PUT request to the specified URL. + +```razor +
+ + +
+``` + +#### HxDelete +Performs a DELETE request to the specified URL. + +```razor + +``` + +#### HxPatch +Performs a PATCH request to the specified URL. + +```razor + +``` + +### Request Configuration + +#### HxTrigger +Specifies what triggers the request. + +```razor + +
Click me
+ +
+ + + + + + + + + + + + +@code { + HxTarget = "#result-container"; +} +``` + +Special targets: +- `this` - The element itself +- `closest ` - Closest ancestor matching selector +- `next ` - Next sibling matching selector +- `previous ` - Previous sibling matching selector + +#### HxSwap +Specifies how to swap the content. + +```razor + +
+ Replace inner content (default) +
+ +
+ Replace entire element +
+ +
+ Insert before element +
+ +
+ Insert after element +
+ + +
+ Settle after 500ms +
+ +
+ Scroll to top after swap +
+ +@code { + HxSwap = "outerHTML"; +} +``` + +Swap options: +- `innerHTML` - Replace inner HTML +- `outerHTML` - Replace entire element +- `beforebegin` - Insert before element +- `afterbegin` - Insert at start of element +- `beforeend` - Insert at end of element +- `afterend` - Insert after element +- `delete` - Delete target element +- `none` - No swap + +### URL Management + +#### HxPushUrl +Updates the browser URL without reload. + +```razor + + Go to Page 2 (updates URL) + + + + Load Page 2 (sets custom URL) + + +@code { + HxPushUrl = "true"; +} +``` + +#### HxReplaceUrl +Replaces the current URL in history. + +```razor + +``` + +#### HxBoost +Converts normal links and forms to use HTMX. + +```razor +
+ + This uses HTMX +
+ This form uses HTMX +
+
+``` + +## Additional Attributes + +These attributes provide extended functionality beyond basic requests. + +### User Interaction + +#### HxConfirm +Shows a confirmation dialog before request. + +```razor + + +@code { + HxConfirm = "This action cannot be undone. Continue?"; +} +``` + +#### HxPrompt +Shows a prompt dialog and includes the value. + +```razor + +``` + +### Request Control + +#### HxDisable +Disables elements during the request. + +```razor +
+ + +
+``` + +#### HxDisabledElt +Specifies which elements to disable. + +```razor + + + +``` + +#### HxIndicator +Shows/hides elements during requests. + +```razor + + +
+ Loading... +
+ + +``` + +### Data Handling + +#### HxVals +Includes additional values with the request. + +```razor + + + + +@code { + HxVals = JsonSerializer.Serialize(new { key = "value" }); +} +``` + +#### HxVars +Sets variables that can be used in other attributes. + +```razor +
+ + +
+``` + +#### HxParams +Controls which parameters are submitted. + +```razor + +
+ + +
+ + +
+ +
+ + +
+ + + +
+``` + +#### HxHeaders +Adds custom headers to the request. + +```razor + + +@code { + HxHeaders = JsonSerializer.Serialize(new + { + Authorization = "Bearer token123" + }); +} +``` + +### Selection and Filtering + +#### HxSelect +Selects a subset of the response. + +```razor +
+ Load only #content from response +
+``` + +#### HxSelectOob +Selects out-of-band content to swap. + +```razor +
+ Also updates #notifications from response +
+``` + +### Advanced Features + +#### HxSync +Synchronizes requests with other elements. + +```razor + + + + + +``` + +#### HxValidate +Validates an element before including in request. + +```razor +
+ + +
+``` + +#### HxOn +Handles HTMX and DOM events inline. + +```razor +
+ +
+ + +``` + +#### HxExt +Enables HTMX extensions. + +```razor +
+ +
+ +
+
+``` + +## CSS Classes + +HTMX adds these classes during various stages of requests. + +```razor +@inherits HtmxComponentBase + +@code { + protected override void OnInitialized() + { + // Classes added during request + HxCssRequest = "loading"; + + // Classes added while swapping + HxCssSwapping = "swapping"; + + // Classes added during settle phase + HxCssSettling = "settling"; + + // Classes added to new content + HxCssAdded = "new-content"; + + // Classes for indicators + HxCssIndicator = "spinner"; + } +} +``` + +### CSS Class Lifecycle + +```css +/* Element making request */ +.htmx-request { opacity: 0.5; } +.loading { cursor: wait; } + +/* Target being swapped */ +.htmx-swapping { opacity: 0; } +.swapping { transform: scale(0.95); } + +/* During settle phase */ +.htmx-settling { } +.settling { transition: all 0.3s; } + +/* New content */ +.htmx-added { } +.new-content { + animation: fadeIn 0.3s; +} + +/* Loading indicators */ +.htmx-indicator { display: none; } +.htmx-request .htmx-indicator { display: block; } +``` + +## Special Attributes + +### SSE (Server-Sent Events) + +```razor +@inherits HtmxSseTag + + +
+ +
+
+``` + +### WebSocket + +```razor +@inherits HtmxWsTag + + +
+
+
+ + +
+
+
+``` + +## Attribute Inheritance + +Some attributes can be inherited from parent elements: + +```razor +
+ + + + + + +
+``` + +Use `hx-disinherit` to stop inheritance: + +```razor +
+
+ + +
+
+``` + +## Best Practices + +1. **Use Appropriate Triggers**: Choose triggers that match user expectations +2. **Set Clear Targets**: Be specific about where content should be placed +3. **Provide Feedback**: Use indicators and CSS classes for loading states +4. **Confirm Destructive Actions**: Always use `hx-confirm` for deletions +5. **Optimize Requests**: Use `hx-trigger` modifiers to debounce/throttle +6. **Handle Errors**: Implement proper error handling and display +7. **Test Accessibility**: Ensure HTMX enhancements don't break accessibility + +## Next Steps + +- [Component Development](Component-Development.md) - Building components +- [Examples](Examples.md) - Real-world usage examples +- [Events](Events.md) - Handling HTMX events +- [Performance](Performance.md) - Optimization techniques \ No newline at end of file diff --git a/Writerside/topics/HTTP-API.md b/Writerside/topics/HTTP-API.md new file mode 100644 index 0000000..f05ba3f --- /dev/null +++ b/Writerside/topics/HTTP-API.md @@ -0,0 +1,182 @@ +# HTTP API + +FastComponents provides extensions and helpers for working with HTMX HTTP headers. + +## HtmxHttpContextExtensions + +Extension methods for HttpContext to work with HTMX requests and responses. + +### Request Methods + +#### IsHtmxRequest + +Checks if the current request is from HTMX. + +```C# +if (HttpContext.IsHtmxRequest()) +{ + // Handle HTMX request +} +``` + +#### IsHtmxBoostedRequest + +Checks if the request is a boosted request (progressive enhancement). + +```C# +if (HttpContext.IsHtmxBoostedRequest()) +{ + // Return partial content for boosted requests +} +``` + +#### GetHtmxRequestHeaders + +Gets the HTMX request headers. + +```C# +var htmxHeaders = HttpContext.GetHtmxRequestHeaders(); +var trigger = htmxHeaders.Trigger; +var target = htmxHeaders.Target; +``` + +#### GetHtmxResponseHeaders + +Gets the HTMX response headers helper. + +```C# +var htmxResponse = HttpContext.GetHtmxResponseHeaders(); +htmxResponse.Redirect("/new-page"); +``` + +## HtmxRequestHeaders + +Represents HTMX request headers sent by the client. + +### Properties + +- `IsHtmxRequest` - True if this is an HTMX request +- `IsBoosted` - True if this is a boosted request +- `IsHistoryRestoreRequest` - True if restoring from history +- `CurrentUrl` - The current URL of the browser +- `Target` - The target element ID +- `Trigger` - The ID of the triggered element +- `TriggerName` - The name of the triggered element +- `Prompt` - User response to hx-prompt + +### Example + +```C# +public IResult HandleRequest() +{ + var headers = HttpContext.GetHtmxRequestHeaders(); + + if (headers.Trigger == "search-button") + { + // Handle search + } + + return Results.Ok(); +} +``` + +## HtmxResponseHeaders + +Helper for setting HTMX response headers. + +### Methods + +#### Redirect + +Triggers a client-side redirect. + +```C# +response.Redirect("/new-location"); +``` + +#### Refresh + +Triggers a full page refresh. + +```csharp +response.Refresh(); +``` + +#### Location + +Sets the HX-Location header for client-side navigation. + +```csharp +response.Location = new +{ + path = "/new-page", + target = "#content", + swap = "innerHTML" +}; +``` + +#### PushUrl / ReplaceUrl + +Updates the browser URL without navigation. + +```csharp +response.PushUrl("/new-url"); +response.ReplaceUrl("/replacement-url"); +``` + +#### Retarget / Reswap / Reselect + +Overrides client-side targeting and swapping. + +```csharp +response.Retarget("#different-target"); +response.Reswap("outerHTML"); +response.Reselect(".new-selector"); +``` + +#### Trigger Events + +Triggers client-side events. + +```csharp +// Simple trigger +response.Trigger = "dataUpdated"; + +// Trigger with details +response.TriggerWithDetails(new { + eventName = "itemAdded", + itemId = 123 +}); + +// Trigger after swap/settle +response.TriggerAfterSwap = "afterSwapEvent"; +response.TriggerAfterSettle = "afterSettleEvent"; +``` + +### Complete Example + +```csharp +app.MapPost("/api/update", (HttpContext context) => +{ + var htmxResponse = context.GetHtmxResponseHeaders(); + + // Update data... + + // Set response headers + htmxResponse.Retarget("#notifications"); + htmxResponse.TriggerWithDetails(new + { + eventName = "itemUpdated", + itemId = 42, + timestamp = DateTime.UtcNow + }); + + return Results.Ok("
Update successful!
"); +}); +``` + +## See Also + +- [HTMX Attributes](HTMX-Attributes.md) +- [Events API](Events-API.md) +- [Core Concepts](Core-Concepts.md) \ No newline at end of file diff --git a/Writerside/topics/Migration-Guide.md b/Writerside/topics/Migration-Guide.md new file mode 100644 index 0000000..46dd0cb --- /dev/null +++ b/Writerside/topics/Migration-Guide.md @@ -0,0 +1,529 @@ +# Migration Guide + +This guide helps you migrate existing applications to FastComponents and upgrade between versions. + +## Migrating from Blazor Server + +### Component Migration + +#### Before: Blazor Server Component + +```C# +@page "/counter" +@using Microsoft.AspNetCore.Components + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} +``` + +#### After: FastComponents + +```C# +@inherits SimpleHtmxComponent + +
+

Counter

+

Current count: @State.Count

+ +
+ +@code { + public class CounterState : HtmxComponentParameters + { + public int Count { get; set; } + + public override void BindFromQuery(IQueryCollection query) + { + Count = GetQueryInt(query, nameof(Count)) ?? 0; + Count++; // Increment on each request + } + } +} +``` + +### Service Registration Migration + +#### Before: Blazor Server + +```C# +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); +``` + +#### After: FastComponents + +```C# +builder.Services.AddFastComponentsAuto(); + +var app = builder.Build(); +app.UseFastComponentsAuto(); +``` + +## Migrating from MVC/Razor Pages + +### View to Component Migration + +#### Before: MVC View + +```html +@model ProductListViewModel + +

Products

+ +
+ + +
+ +
+ @foreach (var product in Model.Products) + { +
+

@product.Name

+

@product.Description

+ $@product.Price +
+ } +
+``` + +#### After: FastComponents + +```C# +@inherits HtmxComponentBase + +
+

Products

+ +
+ + +
+ +
+ @foreach (var product in Products) + { +
+

@product.Name

+

@product.Description

+ $@product.Price +
+ } +
+
+ +@code { + [Inject] private IProductService ProductService { get; set; } = default!; + + private List Products { get; set; } = new(); + + protected override async Task OnParametersSetAsync() + { + Products = await ProductService.SearchAsync(Parameters.Search ?? ""); + } +} +``` + +### Controller to Endpoint Migration + +#### Before: MVC Controller + +```csharp +public class ProductController : Controller +{ + private readonly IProductService _productService; + + public ProductController(IProductService productService) + { + _productService = productService; + } + + public async Task Index(string search = "") + { + var products = await _productService.SearchAsync(search); + var model = new ProductListViewModel + { + Products = products, + Search = search + }; + + return View(model); + } +} +``` + +#### After: FastComponents Endpoint + +```csharp +// Automatic registration with convention-based approach +app.UseFastComponentsAuto(); + +// Or explicit registration +app.MapHtmxGet("/products"); +``` + +## Migrating from SPA Frameworks + +### React/Vue to FastComponents + +#### Before: React Component + +```jsx +import React, { useState, useEffect } from 'react'; + +function ProductSearch() { + const [search, setSearch] = useState(''); + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const searchProducts = async () => { + if (!search) return; + + setLoading(true); + const response = await fetch(`/api/products?search=${search}`); + const data = await response.json(); + setProducts(data); + setLoading(false); + }; + + const debounce = setTimeout(searchProducts, 300); + return () => clearTimeout(debounce); + }, [search]); + + return ( +
+ setSearch(e.target.value)} + placeholder="Search products..." + /> + {loading &&
Loading...
} +
+ {products.map(product => ( +
+

{product.name}

+

${product.price}

+
+ ))} +
+
+ ); +} +``` + +#### After: FastComponents + +```csharp +@inherits HtmxComponentBase + +
+ + +
Loading...
+ +
+ @foreach (var product in Products) + { +
+

@product.Name

+

$@product.Price

+
+ } +
+
+ +@code { + [Inject] private IProductService ProductService { get; set; } = default!; + + private List Products { get; set; } = new(); + + protected override async Task OnParametersSetAsync() + { + if (!string.IsNullOrEmpty(Parameters.Search)) + { + Products = await ProductService.SearchAsync(Parameters.Search); + } + } +} +``` + +## Version Migration + +### From 0.x to 1.0 + +#### Breaking Changes + +1. **Namespace Changes** + ```csharp + // Before + using FastComponents.Core; + using FastComponents.Extensions; + + // After + using FastComponents; + ``` + +2. **Component Base Class Changes** + ```csharp + // Before + public class MyComponent : HtmxComponent + + // After + public class MyComponent : HtmxComponentBase + ``` + +3. **Service Registration Changes** + ```csharp + // Before + services.AddFastComponents(options => { + options.EnableConventionRouting = true; + }); + + // After + services.AddFastComponentsAuto(); + ``` + +#### Migration Steps + +1. **Update Package References** + ```xml + + ``` + +2. **Update Namespace Imports** + ```bash + # Use find and replace in your IDE + Find: "using FastComponents.Core;" + Replace: "using FastComponents;" + ``` + +3. **Update Component Base Classes** + ```csharp + // Update all component base classes + // HtmxComponent → HtmxComponentBase + // SimpleComponent → SimpleHtmxComponent + ``` + +4. **Update Service Registration** + ```csharp + // In Program.cs + builder.Services.AddFastComponentsAuto(); + + var app = builder.Build(); + app.UseFastComponentsAuto(); + ``` + +### From 1.0 to 1.1 + +#### New Features + +1. **Enhanced Builder API** + ```csharp + // New fluent builder + HtmxBuilder.Button() + .Get("/api/action") + .Target("#result") + .Text("Click Me") + .Render(builder); + ``` + +2. **Improved Source Generators** + ```csharp + [GenerateParameterMethods(SkipDefaults = true)] + public partial class MyComponent : HtmxComponentBase + { + // Generated methods available + } + ``` + +## Common Migration Patterns + +### State Management Migration + +#### From Session State + +```csharp +// Before: Session-based state +public class ProductController : Controller +{ + public IActionResult Index() + { + var filter = HttpContext.Session.GetString("ProductFilter") ?? ""; + // ... + } +} + +// After: Component state +public class ProductListState : HtmxComponentParameters +{ + public string Filter { get; set; } = ""; + + public override void BindFromQuery(IQueryCollection query) + { + Filter = GetQueryValue(query, nameof(Filter)) ?? ""; + } +} +``` + +#### From ViewBag/ViewData + +```csharp +// Before: ViewBag +public IActionResult Index() +{ + ViewBag.Title = "Products"; + ViewData["ShowFilter"] = true; + return View(); +} + +// After: Strongly-typed state +public class ProductState : HtmxComponentParameters +{ + public string Title { get; set; } = "Products"; + public bool ShowFilter { get; set; } = true; +} +``` + +### Form Handling Migration + +#### Before: MVC Form Handling + +```csharp +[HttpPost] +public async Task Create(ProductModel model) +{ + if (!ModelState.IsValid) + { + return View(model); + } + + await _service.CreateAsync(model); + return RedirectToAction("Index"); +} +``` + +#### After: FastComponents Form Handling + +```csharp +@inherits HtmxComponentBase + +
+
+ + + + + +
+ +@code { + [Inject] private IProductService ProductService { get; set; } = default!; + + protected override async Task OnParametersSetAsync() + { + if (HttpContext.Request.Method == "POST") + { + if (ModelState.IsValid) + { + await ProductService.CreateAsync(Parameters); + // Redirect or show success message + } + } + } +} +``` + +## Migration Checklist + +### Pre-Migration + +- [ ] Audit existing components/views +- [ ] Identify shared state management +- [ ] Document current API endpoints +- [ ] Plan component hierarchy +- [ ] Set up development environment + +### During Migration + +- [ ] Migrate components incrementally +- [ ] Update service registrations +- [ ] Test each migrated component +- [ ] Update routing configuration +- [ ] Verify HTMX integration + +### Post-Migration + +- [ ] Remove obsolete code +- [ ] Update documentation +- [ ] Performance testing +- [ ] User acceptance testing +- [ ] Production deployment + +## Migration Tools + +### Automated Migration Script + +```bash +#!/bin/bash +# migration-helper.sh + +echo "FastComponents Migration Helper" + +# Update namespace imports +find . -name "*.cs" -type f -exec sed -i 's/using FastComponents\.Core;/using FastComponents;/g' {} + +find . -name "*.cs" -type f -exec sed -i 's/using FastComponents\.Extensions;/using FastComponents;/g' {} + + +# Update component base classes +find . -name "*.cs" -type f -exec sed -i 's/HtmxComponent +

FastComponents = Blazor Components + HTMX + ASP.NET Core

+

Build interactive web apps with server-side rendering, type safety, and zero JavaScript.

+ + +## What is FastComponents? + +FastComponents bridges the gap between ASP.NET Core's server-side capabilities and HTMX's dynamic HTML approach. It allows you to: + +- Build interactive web applications using familiar Blazor component syntax +- Leverage HTMX attributes through strongly-typed C# properties +- Create reusable components that render as pure HTML +- Handle dynamic updates without full page reloads +- Maintain type safety throughout your application + +## Key Features + + + +All HTMX attributes are exposed as strongly-typed C# properties, providing IntelliSense support and compile-time validation. + + + +Build your UI using reusable Blazor components that render to clean, semantic HTML enhanced with HTMX attributes. + + + +Components are rendered on the server and sent as HTML fragments, ensuring fast initial page loads and SEO-friendly content. + + + +Seamlessly integrates with ASP.NET Core Minimal APIs for endpoint routing and request handling. + + + +Create fully interactive web applications without writing any client-side JavaScript code. + + + +Works with any CSS framework - components render clean HTML that you can style however you prefer. + + + +## Architecture Overview + +FastComponents follows a Multiple Resources Application (MRA) architecture: + +1. **Server-Side Components**: Blazor components inherit from `HtmxComponentBase` to gain access to HTMX attributes +2. **HTML Rendering**: Components are rendered as HTML fragments on the server +3. **HTMX Enhancement**: The rendered HTML includes HTMX attributes for dynamic behavior +4. **Minimal API Endpoints**: ASP.NET Core endpoints handle HTMX requests and return rendered components + +## Core Concepts + +### HTMX Components +Components that inherit from `HtmxComponentBase` automatically gain access to all HTMX attributes as C# properties: + +```Razor +@inherits HtmxComponentBase + +
+ +
+
+``` + +### Component Parameters +Create strongly-typed parameters for your components using records that inherit from `HtmxComponentParameters`: + +```C# +[GenerateParameterMethods] +public partial record CounterState : HtmxComponentParameters +{ + public int Count { get; init; } = 0; +} +``` + +### Endpoint Mapping +Map components to HTTP endpoints using the fluent API: + +```Razor +app.MapHtmxGet("/counter"); +``` + +## Why Choose FastComponents? + +- **Familiar Development Model**: If you know Blazor, you already know how to use FastComponents +- **Progressive Enhancement**: Start with server-rendered HTML and enhance with HTMX +- **Type Safety**: Catch errors at compile-time rather than runtime +- **Performance**: Minimal JavaScript payload and efficient server-side rendering +- **Maintainability**: Component-based architecture promotes code reuse and organization + +## Next Steps + +- [Getting Started](Getting-Started.md) - Set up your first FastComponents project +- [Core Concepts](Core-Concepts.md) - Deep dive into the architecture +- [Component Development](Component-Development.md) - Learn how to build components +- [API Reference](API-Reference.md) - Detailed API documentation \ No newline at end of file diff --git a/Writerside/topics/Performance.md b/Writerside/topics/Performance.md new file mode 100644 index 0000000..b580cc3 --- /dev/null +++ b/Writerside/topics/Performance.md @@ -0,0 +1,339 @@ +# Performance + +FastComponents is designed for high-performance web applications. This guide covers optimization techniques and best practices. + + +Performance optimization should be based on actual measurements. Always profile your application before making changes. + + + + +### Server-Side Rendering + +- **No JavaScript Framework Overhead**: Pure HTML responses +- **Minimal Client Processing**: HTMX handles DOM updates efficiently +- **Reduced Bundle Size**: ~14KB (minified + gzipped) for HTMX vs 100KB+ for typical SPAs + +### Network Efficiency + +- **Partial Updates**: Only send changed content +- **HTTP Caching**: Full support for browser caching +- **Compression**: Works with standard HTTP compression + +## Optimization Techniques + +### Component Design + +#### 1. Minimize Component State + +```C# +// ❌ Large state object +public class ProductListState : HtmxComponentParameters +{ + public List Products { get; set; } = new(); + public Dictionary ExpandedStates { get; set; } = new(); + public UserPreferences Preferences { get; set; } = new(); +} + +// ✅ Focused state +public class ProductListState : HtmxComponentParameters +{ + public int Page { get; set; } = 1; + public string? Filter { get; set; } + public string? Sort { get; set; } +} +``` + +#### 2. Use Streaming for Large Lists + +```csharp +@inherits HtmxComponentBase + +@foreach (var item in GetItemsStreaming()) +{ +
@item.Name
+} + +@code { + private async IAsyncEnumerable GetItemsStreaming() + { + await foreach (var item in repository.GetItemsAsync()) + { + yield return item; + } + } +} +``` + +### Response Optimization + +#### 1. Enable Response Compression + +```csharp +builder.Services.AddResponseCompression(options => +{ + options.Providers.Add(); + options.Providers.Add(); + options.EnableForHttps = true; +}); + +app.UseResponseCompression(); +``` + +#### 2. Use HTTP Caching + +```csharp +app.MapHtmxGet("/htmx/static") + .CacheOutput(policy => policy.Expire(TimeSpan.FromHours(1))); +``` + +#### 3. Implement ETags + +```csharp +public class CachedComponent : HtmxComponentBase +{ + protected override void OnParametersSet() + { + var etag = ComputeETag(Parameters); + if (Context.Request.Headers.IfNoneMatch == etag) + { + Context.Response.StatusCode = 304; // Not Modified + return; + } + + Context.Response.Headers.ETag = etag; + } +} +``` + +### Database Optimization + +#### 1. Use Projection + +```csharp +// ❌ Loading entire entities +var products = await context.Products + .Include(p => p.Category) + .Include(p => p.Reviews) + .ToListAsync(); + +// ✅ Project only needed data +var products = await context.Products + .Select(p => new ProductViewModel + { + Id = p.Id, + Name = p.Name, + Price = p.Price, + CategoryName = p.Category.Name + }) + .ToListAsync(); +``` + +#### 2. Implement Pagination + +```csharp +public class PaginatedList : HtmxComponentBase +{ + private const int PageSize = 20; + + protected override async Task OnParametersSetAsync() + { + var query = repository.GetQuery(); + + TotalCount = await query.CountAsync(); + Items = await query + .Skip((Parameters.Page - 1) * PageSize) + .Take(PageSize) + .ToListAsync(); + } +} +``` + +### HTMX-Specific Optimizations + +#### 1. Use hx-boost Selectively + +```csharp +// Boost only navigation links + + +// Regular forms without boost +
+ +
+``` + +#### 2. Optimize Polling Intervals + +```csharp +// ❌ Too frequent polling +HxTrigger = "every 100ms" + +// ✅ Reasonable intervals +HxTrigger = "every 2s" + +// ✅ Even better: Use SSE for real-time updates + +
+
+``` + +#### 3. Debounce User Input + +```csharp +// Search with debouncing +HxTrigger = "keyup changed delay:300ms, search" +``` + +## Memory Management + +### Component Lifecycle + +```csharp +public class ResourceIntensiveComponent : HtmxComponentBase, IDisposable +{ + private readonly MemoryCache _cache; + + public ResourceIntensiveComponent(IMemoryCache cache) + { + _cache = cache; + } + + public void Dispose() + { + // Clean up resources + _cache.Dispose(); + } +} +``` + +### String Optimization + +```csharp +// Use StringBuilder for large HTML generation +protected override void BuildRenderTree(RenderTreeBuilder builder) +{ + var sb = new StringBuilder(); + foreach (var item in LargeCollection) + { + sb.AppendFormat("
{0}
", item.Name); + } + + builder.AddMarkupContent(0, sb.ToString()); +} +``` + +## Monitoring and Profiling + +### Request Timing + +```csharp +public class PerformanceMiddleware +{ + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + var sw = Stopwatch.StartNew(); + + await next(context); + + if (context.IsHtmxRequest()) + { + context.Response.Headers.Add("X-Response-Time", + $"{sw.ElapsedMilliseconds}ms"); + } + } +} +``` + +### Component Metrics + +```csharp +public abstract class InstrumentedComponent : HtmxComponentBase + where T : HtmxComponentParameters, new() +{ + private readonly ILogger> _logger; + + protected override async Task OnParametersSetAsync() + { + using var activity = Activity.StartActivity("Component.Render"); + activity?.SetTag("component.type", GetType().Name); + + var sw = Stopwatch.StartNew(); + await base.OnParametersSetAsync(); + + _logger.LogDebug("Component {Name} rendered in {Time}ms", + GetType().Name, sw.ElapsedMilliseconds); + } +} +``` + +## Load Testing + +### Example with k6 + +```javascript +import http from 'k6/http'; +import { check } from 'k6'; + +export let options = { + stages: [ + { duration: '30s', target: 100 }, + { duration: '1m', target: 100 }, + { duration: '30s', target: 0 }, + ], +}; + +export default function() { + let response = http.get('https://yourapp.com/htmx/component', { + headers: { + 'HX-Request': 'true', + 'HX-Target': '#content' + } + }); + + check(response, { + 'status is 200': (r) => r.status === 200, + 'response time < 200ms': (r) => r.timings.duration < 200, + }); +} +``` + +## Best Practices Summary + +### Do's + +1. ✅ Use partial updates instead of full page reloads +2. ✅ Implement proper caching strategies +3. ✅ Optimize database queries with projection +4. ✅ Use SSE/WebSockets for real-time data +5. ✅ Profile and monitor performance regularly + +### Don'ts + +1. ❌ Don't over-poll with aggressive intervals +2. ❌ Don't load unnecessary data in components +3. ❌ Don't ignore browser caching capabilities +4. ❌ Don't render large lists without pagination +5. ❌ Don't neglect compression settings + +## Performance Checklist + +- [ ] Response compression enabled +- [ ] HTTP caching configured +- [ ] Database queries optimized +- [ ] Component state minimized +- [ ] Polling intervals reasonable +- [ ] Large lists paginated +- [ ] Static assets cached +- [ ] CDN configured for static files +- [ ] Monitoring in place +- [ ] Load testing performed + +## See Also + +- [AOT Support](AOT-Support.md) +- [Deployment](Deployment.md) +- [Advanced Features](Advanced-Features.md) \ No newline at end of file diff --git a/Writerside/topics/SSE-WebSockets.md b/Writerside/topics/SSE-WebSockets.md new file mode 100644 index 0000000..bf29748 --- /dev/null +++ b/Writerside/topics/SSE-WebSockets.md @@ -0,0 +1,247 @@ +# SSE & WebSockets + +FastComponents provides built-in support for Server-Sent Events (SSE) and WebSockets through dedicated components. + +## Server-Sent Events (SSE) + +### HtmxSseTag + +The `HtmxSseTag` component enables SSE connections in your components. + +```C# + +
+ +
+
+``` + +### Properties + +- `SseConnect` - The URL to connect for SSE +- `SseSwap` - The event name to listen for swapping content +- `ChildContent` - The content to render inside the SSE container + +### Example: Live Notifications + +```C# +@page "/notifications" + +

Live Notifications

+ + +
+

Waiting for notifications...

+
+
+ +@code { + // Server endpoint sends SSE messages like: + // event: notification + // data:
New message received!
+} +``` + +### Server-Side SSE Implementation + +```C# +app.MapGet("/api/live-notifications", async (HttpContext context) => +{ + context.Response.Headers.Add("Content-Type", "text/event-stream"); + context.Response.Headers.Add("Cache-Control", "no-cache"); + + while (!context.RequestAborted.IsCancellationRequested) + { + var notification = GetNextNotification(); + await context.Response.WriteAsync($"event: notification\n"); + await context.Response.WriteAsync($"data: {notification}\n\n"); + await context.Response.Body.FlushAsync(); + + await Task.Delay(1000); + } +}); +``` + +## WebSockets + +### HtmxWsTag + +The `HtmxWsTag` component enables WebSocket connections. + +```csharp + +
+ + +
+
+ +
+
+``` + +### Properties + +- `WsConnect` - The WebSocket URL to connect to +- `WsSend` - Whether to send form data through WebSocket +- `ChildContent` - The content to render inside the WebSocket container + +### Example: Real-Time Chat + +```csharp +@page "/chat" + +

Real-Time Chat

+ + +
+
+ +
+ +
+ + + +
+
+
+``` + +### Server-Side WebSocket Implementation + +```csharp +app.UseWebSockets(); + +app.Map("/ws/chat", async context => +{ + if (context.WebSockets.IsWebSocketRequest) + { + var webSocket = await context.WebSockets.AcceptWebSocketAsync(); + await HandleChatWebSocket(webSocket); + } + else + { + context.Response.StatusCode = 400; + } +}); + +async Task HandleChatWebSocket(WebSocket webSocket) +{ + var buffer = new byte[1024 * 4]; + + while (webSocket.State == WebSocketState.Open) + { + var result = await webSocket.ReceiveAsync( + new ArraySegment(buffer), + CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Text) + { + var message = Encoding.UTF8.GetString(buffer, 0, result.Count); + // Parse message and broadcast to other clients + var response = ProcessChatMessage(message); + + await webSocket.SendAsync( + Encoding.UTF8.GetBytes(response), + WebSocketMessageType.Text, + true, + CancellationToken.None); + } + } +} +``` + +## HTMX Integration + +### SSE Events + +HTMX automatically handles SSE events and swaps content: + +```javascript +// Custom SSE event handling +document.body.addEventListener('htmx:sseMessage', function(evt) { + console.log('SSE message received:', evt.detail); +}); + +// Handle SSE errors +document.body.addEventListener('htmx:sseError', function(evt) { + console.error('SSE error:', evt.detail); +}); +``` + +### WebSocket Events + +```javascript +// WebSocket connected +document.body.addEventListener('htmx:wsConnecting', function(evt) { + console.log('WebSocket connecting...'); +}); + +// Message received +document.body.addEventListener('htmx:wsMessage', function(evt) { + console.log('WebSocket message:', evt.detail.message); +}); +``` + +## Best Practices + +### Connection Management + +1. **Automatic Reconnection**: HTMX handles reconnection automatically +2. **Error Handling**: Implement fallbacks for connection failures +3. **Resource Cleanup**: Connections are closed when elements are removed + +### Security + +1. **Authentication**: Validate connections server-side +2. **Message Validation**: Sanitize all incoming messages +3. **Rate Limiting**: Implement rate limiting for WebSocket messages + +### Performance + +1. **Message Batching**: Batch multiple updates when possible +2. **Compression**: Enable WebSocket compression for large messages +3. **Connection Pooling**: Reuse connections when appropriate + +## Complete Example: Live Dashboard + +```csharp +@page "/dashboard" +@inherits HtmxComponentBase + +
+

Live Dashboard

+ + + +
+
+

Active Users

+ - +
+
+

CPU Usage

+ - +
+
+
+ + + +
+
+
+ + +
+
+
+
+``` + +## See Also + +- [Events API](Events-API.md) +- [Advanced Features](Advanced-Features.md) +- [Component Development](Component-Development.md) \ No newline at end of file diff --git a/Writerside/topics/Security.md b/Writerside/topics/Security.md new file mode 100644 index 0000000..6ab5045 --- /dev/null +++ b/Writerside/topics/Security.md @@ -0,0 +1,376 @@ +# Security + +FastComponents follows security best practices for building secure web applications with HTMX and ASP.NET Core. + + +Security is critical for web applications. Always validate input, authenticate users, and follow the principle of least privilege. + + + + +### Automatic CSRF Protection + +ASP.NET Core provides built-in CSRF protection. FastComponents integrates seamlessly: + +```C# +// Enable antiforgery services +builder.Services.AddAntiforgery(options => +{ + options.HeaderName = "X-CSRF-TOKEN"; +}); + +// Components automatically include CSRF tokens +@inherits HtmxComponentBase + +
+ @Html.AntiForgeryToken() + + +
+``` + +### HTMX CSRF Configuration + +Configure HTMX to send CSRF tokens: + +```javascript +// In your layout or app initialization +document.body.addEventListener('htmx:configRequest', + (evt) => { + let token = document.querySelector( + '[name=__RequestVerificationToken]').value; + evt.detail.headers['X-CSRF-TOKEN'] = token; +}); +``` + +
+ +## XSS Prevention + +### Content Encoding + +FastComponents automatically encodes output to prevent XSS: + +```Razor +@inherits HtmxComponentBase + + +
@Parameters.UserInput
+ + +
@((MarkupString)Parameters.TrustedHtml)
+``` + +### Input Validation + +```C# +public class SecureFormState : HtmxComponentParameters +{ + private string _email = ""; + + [Required] + [EmailAddress] + public string Email + { + get => _email; + set => _email = HtmlEncoder.Default.Encode(value); + } + + [Required] + [StringLength(100, MinimumLength = 3)] + [RegularExpression(@"^[a-zA-Z0-9\s]+$")] + public string Name { get; set; } = ""; +} +``` + +## Authentication & Authorization + +### Component-Level Authorization + +```Razor +@inherits HtmxComponentBase +@attribute [Authorize] + +
+

Authorized Content Only

+

Welcome, @User.Identity.Name!

+
+``` + +### Endpoint Authorization + +```C# +app.MapHtmxGet("/htmx/admin") + .RequireAuthorization("AdminPolicy"); + +app.MapHtmxPost("/htmx/user") + .RequireAuthorization(); +``` + +### Role-Based Access + +```Razor +@inherits HtmxComponentBase + +@if (User.IsInRole("Admin")) +{ + +} +``` + +## Secure Headers + +### Configure Security Headers + +```C# +app.Use(async (context, next) => +{ + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "DENY"); + context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); + context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin"); + + await next(); +}); + +// Or use a security headers middleware +app.UseSecurityHeaders(policies => +{ + policies.AddFrameOptionsDeny() + .AddXssProtectionBlock() + .AddContentTypeOptionsNoSniff() + .AddReferrerPolicyStrictOriginWhenCrossOrigin() + .AddContentSecurityPolicy(builder => + { + builder.AddDefaultSrc().Self(); + builder.AddScriptSrc().Self().UnsafeInline(); + builder.AddStyleSrc().Self().UnsafeInline(); + }); +}); +``` + +## Input Sanitization + +### Server-Side Sanitization + +```C# +using Ganss.XSS; + +public class SanitizedComponent : HtmxComponentBase +{ + private readonly HtmlSanitizer _sanitizer; + + public SanitizedComponent() + { + _sanitizer = new HtmlSanitizer(); + _sanitizer.AllowedTags.Add("em"); + _sanitizer.AllowedTags.Add("strong"); + } + + protected override void OnParametersSet() + { + // Sanitize user content + Parameters.Content = _sanitizer.Sanitize(Parameters.Content); + } +} +``` + +### Validation Attributes + +```C# +public class SecureInputState : HtmxComponentParameters +{ + [Required] + [SqlInjectionValidator] // Custom validator + public string Query { get; set; } = ""; + + [Required] + [FileExtensions(Extensions = "jpg,png,gif")] + public string FileName { get; set; } = ""; + + [Url] + [AllowedDomains("example.com", "trusted.com")] + public string? ExternalUrl { get; set; } +} +``` + +## Rate Limiting + +### Configure Rate Limiting + +```C# +builder.Services.AddRateLimiter(options => +{ + options.AddFixedWindowLimiter("htmx", options => + { + options.PermitLimit = 100; + options.Window = TimeSpan.FromMinutes(1); + options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + options.QueueLimit = 50; + }); +}); + +// Apply to HTMX endpoints +app.MapHtmxPost("/htmx/search") + .RequireRateLimiting("htmx"); +``` + +### Component-Specific Rate Limiting + +```C# +[RateLimit("search", PermitLimit = 10, Window = "00:01:00")] +public class SearchComponent : HtmxComponentBase +{ + // Component implementation +} +``` + +## Secure Communication + +### HTTPS Enforcement + +```C# +// Redirect HTTP to HTTPS +app.UseHttpsRedirection(); + +// Enforce HTTPS in production +if (app.Environment.IsProduction()) +{ + app.UseHsts(); +} +``` + +### Secure WebSocket/SSE + +```Razor + + + + + + + +``` + +## Data Protection + +### Encrypt Sensitive Data + +```C# +public class ProtectedComponent : HtmxComponentBase +{ + private readonly IDataProtector _protector; + + public ProtectedComponent(IDataProtectionProvider provider) + { + _protector = provider.CreateProtector("ComponentData"); + } + + protected override void OnParametersSet() + { + // Decrypt sensitive data + if (!string.IsNullOrEmpty(Parameters.EncryptedId)) + { + Parameters.ActualId = _protector.Unprotect(Parameters.EncryptedId); + } + } +} +``` + +### Secure State Management + +```C# +public class SecureState : HtmxComponentParameters +{ + [JsonIgnore] // Don't serialize sensitive data + public string? ApiKey { get; set; } + + [Encrypted] // Custom attribute for encryption + public string? SensitiveData { get; set; } + + public override void BindFromQuery(IQueryCollection query) + { + // Validate before binding + if (!IsValidRequest(query)) + { + throw new SecurityException("Invalid request"); + } + + base.BindFromQuery(query); + } +} +``` + +## Security Checklist + +### Development + +- [ ] Enable CSRF protection +- [ ] Validate all user input +- [ ] Encode output properly +- [ ] Use parameterized queries +- [ ] Implement proper authentication +- [ ] Apply authorization checks + +### Deployment + +- [ ] Use HTTPS everywhere +- [ ] Set security headers +- [ ] Enable HSTS +- [ ] Configure rate limiting +- [ ] Disable detailed errors +- [ ] Remove debug endpoints + +### Monitoring + +- [ ] Log security events +- [ ] Monitor failed auth attempts +- [ ] Track rate limit violations +- [ ] Alert on suspicious patterns +- [ ] Regular security audits + +## Common Vulnerabilities + +### 1. HTMX-Specific XSS + +```Razor +// ❌ Vulnerable: Unescaped attribute +
+ +// ✅ Safe: Properly encoded +
+``` + +### 2. Insecure Direct Object References + +```C# +// ❌ Vulnerable: No authorization check +app.MapHtmxGet("/htmx/user/{id}"); + +// ✅ Safe: Verify user access +app.MapHtmxGet("/htmx/user/{id}") + .RequireAuthorization() + .Add(endpointBuilder => + { + endpointBuilder.Metadata.Add(new AuthorizeUserAccessAttribute()); + }); +``` + +### 3. Mass Assignment + +```C# +// ❌ Vulnerable: Binding all properties +public void UpdateUser(User user) { } + +// ✅ Safe: Use specific DTOs +public void UpdateUser(UserUpdateDto dto) { } +``` + +## See Also + +- [](Deployment.md) +- [](Testing.md) +- [](Advanced-Features.md) \ No newline at end of file diff --git a/Writerside/topics/Source-Generators.md b/Writerside/topics/Source-Generators.md new file mode 100644 index 0000000..ad1fa8f --- /dev/null +++ b/Writerside/topics/Source-Generators.md @@ -0,0 +1,243 @@ +# Source Generators + +FastComponents uses C# Source Generators to provide compile-time code generation for enhanced developer productivity. + +## GenerateParameterMethodsAttribute + +This attribute triggers source generation for component parameter helper methods. + +### Basic Usage + +```C# +[GenerateParameterMethods] +public partial class MyComponent : HtmxComponentBase +{ + // Source generator will create helper methods +} + +public class MyComponentState : HtmxComponentParameters +{ + public string Title { get; set; } = ""; + public int Count { get; set; } + public bool IsActive { get; set; } +} +``` + +### Generated Methods + +The source generator creates fluent methods for each property: + +```C# +// Generated methods: +public MyComponent WithTitle(string value) { ... } +public MyComponent WithCount(int value) { ... } +public MyComponent WithIsActive(bool value) { ... } +``` + +### Usage Example + +```C# +@page "/products" + + + @WithTitle("Featured Products") + @WithCount(10) + @WithIsActive(true) + +``` + +## Configuration Options + +### SkipDefaults + +Skip generating methods for properties with default values: + +```C# +[GenerateParameterMethods(SkipDefaults = true)] +public partial class OptimizedComponent : HtmxComponentBase +{ + // Only generates methods for non-default property values +} +``` + +## How It Works + +### Source Generation Process + +1. **Discovery**: Analyzer finds classes with `[GenerateParameterMethods]` +2. **Analysis**: Examines the component's state/parameter type +3. **Generation**: Creates partial class with fluent methods +4. **Compilation**: Generated code is included in compilation + +### Generated Code Structure + +```csharp +// Generated file: MyComponent.g.cs +public partial class MyComponent +{ + public MyComponent WithTitle(string value) + { + Parameters.Title = value; + return this; + } + + public MyComponent WithCount(int value) + { + Parameters.Count = value; + return this; + } +} +``` + +## Benefits + +### Type Safety + +- Compile-time checking of parameter names +- IntelliSense support for all parameters +- Refactoring-safe parameter usage + +### Developer Experience + +- Fluent API for setting parameters +- No magic strings +- Clear, readable component configuration + +### Performance + +- Zero runtime overhead +- No reflection for parameter setting +- Optimized IL code generation + +## Advanced Scenarios + +### Custom Parameter Types + +```csharp +public class ComplexState : HtmxComponentParameters +{ + public List Tags { get; set; } = new(); + public Dictionary Metadata { get; set; } = new(); + public ProductFilter? Filter { get; set; } +} + +[GenerateParameterMethods] +public partial class ComplexComponent : HtmxComponentBase +{ + // Generates methods for complex types too +} +``` + +### Nested Components + +```csharp +[GenerateParameterMethods] +public partial class ParentComponent : HtmxComponentBase +{ + [GenerateParameterMethods] + public partial class ChildComponent : HtmxComponentBase + { + // Nested components also get generated methods + } +} +``` + +## Analyzer Diagnostics + +The source generator provides helpful diagnostics: + +### HTMX001: Missing Partial Modifier + +```csharp +// ❌ Error: Class must be partial +[GenerateParameterMethods] +public class MyComponent : HtmxComponentBase { } + +// ✅ Correct: Partial class +[GenerateParameterMethods] +public partial class MyComponent : HtmxComponentBase { } +``` + +### HTMX002: Invalid Base Class + +```csharp +// ❌ Error: Must inherit from HtmxComponentBase +[GenerateParameterMethods] +public partial class MyComponent { } + +// ✅ Correct: Proper inheritance +[GenerateParameterMethods] +public partial class MyComponent : HtmxComponentBase { } +``` + +## Debugging Source Generators + +### Enable Generated File Output + +In your `.csproj`: + +```xml + + true + $(BaseIntermediateOutputPath)Generated + +``` + +### View Generated Code + +1. Build the project +2. Check `obj/Generated/FastComponents.Generators/` directory +3. Inspect generated `.g.cs` files + +## Integration with IDEs + +### Visual Studio + +- Full IntelliSense support +- Go to Definition works with generated methods +- Refactoring includes generated code + +### JetBrains Rider + +- Source generator support enabled by default +- Generated code visible in "External Sources" + +### VS Code + +- Requires OmniSharp with source generator support +- Enable in settings: `"omnisharp.enableRoslynAnalyzers": true` + +## Best Practices + +### Component Design + +1. Keep parameter types simple and serializable +2. Use immutable properties where possible +3. Provide sensible defaults + +### Performance + +1. Minimize the number of parameters +2. Use value types for simple data +3. Consider parameter grouping for related values + +### Maintenance + +1. Document parameter purposes +2. Use meaningful parameter names +3. Version parameter changes carefully + +## Future Enhancements + +Planned improvements for source generators: + +- Automatic JSON serialization context generation +- Parameter validation method generation +- Component factory method generation +- Automatic route parameter binding + +## See Also + +- [Component Development](Component-Development.md) +- [AOT Support](AOT-Support.md) +- [API Reference](API-Reference.md) \ No newline at end of file diff --git a/Writerside/topics/State-Management.md b/Writerside/topics/State-Management.md new file mode 100644 index 0000000..2a316ea --- /dev/null +++ b/Writerside/topics/State-Management.md @@ -0,0 +1,631 @@ +# State Management + +Learn how to effectively manage state in FastComponents applications, from simple component state to complex application-wide state patterns. + +## Understanding State + +### What is State? + +In FastComponents, state represents the data that: +- Determines what a component displays +- Changes over time based on user interactions +- Can be serialized to/from query strings +- Is immutable (using C# records) + +### State Flow + +```mermaid +graph LR + A[User Action] --> B[HTMX Request] + B --> C[Server Handler] + C --> D[State Update] + D --> E[Component Render] + E --> F[HTML Response] + F --> G[DOM Update] +``` + +## Component State Basics + +### Defining State + +Use records that inherit from `HtmxComponentParameters`: + +```C# +[GenerateParameterMethods] +public partial record TodoListState : HtmxComponentParameters +{ + public List Items { get; init; } = []; + public string Filter { get; init; } = "all"; + public string SortBy { get; init; } = "created"; +} + +public record TodoItem +{ + public int Id { get; init; } + public string Text { get; init; } = ""; + public bool IsCompleted { get; init; } + public DateTime CreatedAt { get; init; } +} +``` + +### State Immutability + +Always create new state instances instead of modifying existing ones: + +```C# +// ❌ Wrong - Modifying state +State.Items.Add(newItem); + +// ✅ Correct - Creating new state +State = State with +{ + Items = State.Items.Concat([newItem]).ToList() +}; +``` + +### State Initialization + +Initialize state in different ways: + +```Razor +@inherits SimpleHtmxComponent + +@code { + // Option 1: Default values in record + public record ProductListState : HtmxComponentParameters + { + public int Page { get; init; } = 1; + public int PageSize { get; init; } = 20; + } + + // Option 2: Override CreateDefaultParameters + protected override ProductListState CreateDefaultParameters() + { + return new ProductListState + { + Page = 1, + PageSize = GetUserPreference("pageSize") ?? 20 + }; + } + + // Option 3: Initialize in OnInitialized + protected override void OnInitialized() + { + if (State.Products.Count == 0) + { + State = State with { Products = LoadInitialProducts() }; + } + } +} +``` + +## URL State Management + +### Query String Serialization + +The `[GenerateParameterMethods]` attribute generates methods for URL state: + +```C# +[GenerateParameterMethods] +public partial record SearchState : HtmxComponentParameters +{ + public string Query { get; init; } = ""; + public int Page { get; init; } = 1; + public string Category { get; init; } = "all"; +} + +// Generated methods: +// - BuildQueryString() => "query=shoes&page=2&category=footwear" +// - BindFromQuery(IQueryCollection) => new SearchState { ... } +``` + +### Controlling Serialization + +Use `SkipDefaults` to exclude default values: + +```C# +[GenerateParameterMethods(SkipDefaults = true)] +public partial record FilterState : HtmxComponentParameters +{ + public string Status { get; init; } = "active"; // Won't appear in URL if "active" + public int MinPrice { get; init; } = 0; // Won't appear if 0 +} +``` + +### Manual URL State + +For complex scenarios, implement custom serialization: + +```C# +public record ComplexState : HtmxComponentParameters +{ + public List SelectedIds { get; init; } = []; + public Dictionary Filters { get; init; } = new(); + + protected override string BuildQueryString() + { + var parts = new List(); + + // Custom serialization for lists + if (SelectedIds.Any()) + { + parts.Add($"ids={string.Join(",", SelectedIds)}"); + } + + // Custom serialization for dictionaries + foreach (var (key, value) in Filters) + { + parts.Add($"filter_{key}={Uri.EscapeDataString(value)}"); + } + + return string.Join("&", parts); + } + + public override HtmxComponentParameters BindFromQuery(IQueryCollection query) + { + var ids = query["ids"].ToString() + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(int.Parse) + .ToList(); + + var filters = query + .Where(q => q.Key.StartsWith("filter_")) + .ToDictionary( + q => q.Key[7..], + q => q.Value.ToString() + ); + + return this with { SelectedIds = ids, Filters = filters }; + } +} +``` + +## Complex State Patterns + +### Nested State + +Organize complex state with nested records: + +```C# +public record AppState : HtmxComponentParameters +{ + public UserState User { get; init; } = new(); + public CartState Cart { get; init; } = new(); + public UIState UI { get; init; } = new(); +} + +public record UserState +{ + public int Id { get; init; } + public string Name { get; init; } = ""; + public bool IsAuthenticated { get; init; } +} + +public record CartState +{ + public List Items { get; init; } = []; + public decimal Total => Items.Sum(i => i.Price * i.Quantity); +} + +public record UIState +{ + public bool IsSidebarOpen { get; init; } + public string Theme { get; init; } = "light"; + public string ActiveModal { get; init; } = ""; +} +``` + +### State Composition + +Compose state from multiple sources: + +```Razor +@inherits SimpleHtmxComponent +@inject IUserService UserService +@inject IStatsService StatsService + +@code { + protected override async Task OnInitializedAsync() + { + // Compose state from multiple services + var user = await UserService.GetCurrentUserAsync(); + var stats = await StatsService.GetUserStatsAsync(user.Id); + + State = new DashboardState + { + UserName = user.Name, + TotalOrders = stats.OrderCount, + Revenue = stats.TotalRevenue, + RecentActivity = stats.RecentActivity + }; + } +} +``` + +### State Reducers + +Implement reducer pattern for complex state updates: + +```C# +public static class TodoReducers +{ + public static TodoState Reduce(TodoState state, TodoAction action) + { + return action switch + { + AddTodoAction add => state with + { + Items = state.Items.Append(new TodoItem + { + Id = state.NextId, + Text = add.Text, + CreatedAt = DateTime.Now + }).ToList(), + NextId = state.NextId + 1 + }, + + ToggleTodoAction toggle => state with + { + Items = state.Items.Select(item => + item.Id == toggle.Id + ? item with { IsCompleted = !item.IsCompleted } + : item + ).ToList() + }, + + DeleteTodoAction delete => state with + { + Items = state.Items.Where(i => i.Id != delete.Id).ToList() + }, + + FilterTodoAction filter => state with + { + Filter = filter.FilterType + }, + + _ => state + }; + } +} + +// Usage in component +protected override TodoState OnPost(TodoState state) +{ + var action = ParseAction(Request.Form); + return TodoReducers.Reduce(state, action); +} +``` + +## Server-Side State + +### Session State + +Store user-specific state in sessions: + +```C# +public class CartComponent : SimpleHtmxComponent +{ + [Inject] private IHttpContextAccessor HttpContextAccessor { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + var session = HttpContextAccessor.HttpContext?.Session; + + // Load cart from session + var cartJson = session?.GetString("cart"); + if (!string.IsNullOrEmpty(cartJson)) + { + State = JsonSerializer.Deserialize(cartJson)!; + } + } + + protected override CartState OnPost(CartState state) + { + // Update state + var newState = UpdateCart(state); + + // Save to session + var session = HttpContextAccessor.HttpContext?.Session; + session?.SetString("cart", JsonSerializer.Serialize(newState)); + + return newState; + } +} +``` + +### Database State + +Persist state to database: + +```C# +public class UserPreferencesComponent : SimpleHtmxComponent +{ + [Inject] private IDbContext DbContext { get; set; } = null!; + [Inject] private ICurrentUser CurrentUser { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + var userId = CurrentUser.Id; + var prefs = await DbContext.UserPreferences + .FirstOrDefaultAsync(p => p.UserId == userId); + + if (prefs != null) + { + State = new PreferencesState + { + Theme = prefs.Theme, + Language = prefs.Language, + PageSize = prefs.PageSize + }; + } + } + + protected override async Task OnPostAsync(PreferencesState state) + { + // Save to database + var prefs = await DbContext.UserPreferences + .FirstOrDefaultAsync(p => p.UserId == CurrentUser.Id); + + if (prefs == null) + { + prefs = new UserPreferences { UserId = CurrentUser.Id }; + DbContext.UserPreferences.Add(prefs); + } + + prefs.Theme = state.Theme; + prefs.Language = state.Language; + prefs.PageSize = state.PageSize; + + await DbContext.SaveChangesAsync(); + + return state; + } +} +``` + +### Distributed State + +Share state across servers using cache: + +```C# +public class LiveDashboard : SimpleHtmxComponent +{ + [Inject] private IDistributedCache Cache { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + var cacheKey = "dashboard:global"; + var cached = await Cache.GetStringAsync(cacheKey); + + if (!string.IsNullOrEmpty(cached)) + { + State = JsonSerializer.Deserialize(cached)!; + } + else + { + State = await BuildDashboardStateAsync(); + await Cache.SetStringAsync( + cacheKey, + JsonSerializer.Serialize(State), + new DistributedCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromMinutes(5) + }); + } + } +} +``` + +## State Synchronization + +### Cross-Component Communication + +Use events to synchronize state between components: + +```C# +// Shared event service +public class StateEventService +{ + public event EventHandler? CartUpdated; + + public void NotifyCartUpdate(CartState newState) + { + CartUpdated?.Invoke(this, new CartUpdatedEventArgs(newState)); + } +} + +// Cart component +public class CartComponent : SimpleHtmxComponent +{ + [Inject] private StateEventService Events { get; set; } = null!; + + protected override CartState OnPost(CartState state) + { + var newState = UpdateCart(state); + Events.NotifyCartUpdate(newState); + return newState; + } +} + +// Header component showing cart count +public class HeaderComponent : SimpleHtmxComponent +{ + [Inject] private StateEventService Events { get; set; } = null!; + + protected override void OnInitialized() + { + Events.CartUpdated += OnCartUpdated; + } + + private void OnCartUpdated(object? sender, CartUpdatedEventArgs e) + { + State = State with { CartItemCount = e.CartState.Items.Count }; + // Trigger re-render via response header + HttpContext.GetHtmxResponseHeaders() + .Trigger("cart-updated"); + } +} +``` + +### Real-Time State Updates + +Use SSE or WebSockets for real-time updates: + +```Razor +@* Real-time notifications component *@ +
+
+ @foreach (var notification in State.Notifications) + { +
@notification.Message
+ } +
+
+ +@* Server-side SSE endpoint *@ +app.MapGet("/notifications/stream", async (HttpContext context) => +{ + context.Response.Headers.Append("Content-Type", "text/event-stream"); + + while (!context.RequestAborted.IsCancellationRequested) + { + var notification = await GetNextNotificationAsync(); + if (notification != null) + { + var html = RenderNotification(notification); + await context.Response.WriteAsync($"event: notification\n"); + await context.Response.WriteAsync($"data: {html}\n\n"); + await context.Response.Body.FlushAsync(); + } + + await Task.Delay(1000); + } +}); +``` + +## State Validation + +### Client-Side Validation + +Use HTMX validation attributes: + +```Razor +
+ + + +
+``` + +### Server-Side Validation + +Validate state on the server: + +```C# +public record RegistrationState : HtmxComponentParameters, IValidatableObject +{ + public string Email { get; init; } = ""; + public string Password { get; init; } = ""; + public string ConfirmPassword { get; init; } = ""; + + public IEnumerable Validate(ValidationContext context) + { + if (!IsValidEmail(Email)) + yield return new ValidationResult("Invalid email", [nameof(Email)]); + + if (Password.Length < 8) + yield return new ValidationResult("Password too short", [nameof(Password)]); + + if (Password != ConfirmPassword) + yield return new ValidationResult("Passwords don't match", [nameof(ConfirmPassword)]); + } +} + +// In component +protected override RegistrationState OnPost(RegistrationState state) +{ + var validationResults = new List(); + var isValid = Validator.TryValidateObject( + state, + new ValidationContext(state), + validationResults, + true); + + if (!isValid) + { + return state with { Errors = validationResults }; + } + + // Process valid state... +} +``` + +## Performance Considerations + +### State Size + +Keep state minimal: + +```C# +// ❌ Don't store derived values +public record BadState : HtmxComponentParameters +{ + public List Products { get; init; } = []; + public decimal TotalPrice { get; init; } // Can be calculated + public int ProductCount { get; init; } // Can be derived +} + +// ✅ Calculate derived values +public record GoodState : HtmxComponentParameters +{ + public List Products { get; init; } = []; + public decimal TotalPrice => Products.Sum(p => p.Price); + public int ProductCount => Products.Count; +} +``` + +### Lazy Loading + +Load expensive data on demand: + +```C# +public record DashboardState : HtmxComponentParameters +{ + public bool IsStatsLoaded { get; init; } + public DashboardStats? Stats { get; init; } +} + +// Component +
+ @if (!State.IsStatsLoaded) + { +
+ Loading stats... +
+ } + else + { + + } +
+``` + +## Best Practices + +1. **Immutability**: Always use immutable state with C# records +2. **Minimize State**: Only store essential data +3. **URL State**: Use URL state for shareable views +4. **Validation**: Validate both client and server side +5. **Performance**: Consider state size and serialization cost +6. **Testing**: State should be easily testable +7. **Documentation**: Document complex state structures + +## Next Steps + +- [Performance](Performance.md) - Optimization techniques +- [Testing](Testing.md) - Testing state management +- [Advanced Features](Advanced-Features.md) - Complex patterns +- [Troubleshooting](Troubleshooting.md) - Common issues \ No newline at end of file diff --git a/Writerside/topics/Testing.md b/Writerside/topics/Testing.md new file mode 100644 index 0000000..5193e9a --- /dev/null +++ b/Writerside/topics/Testing.md @@ -0,0 +1,468 @@ +# Testing + +FastComponents supports comprehensive testing strategies for both unit and integration testing of HTMX-enabled components. + + +FastComponents components are standard Blazor components, so you can use all existing Blazor testing patterns and tools. + + + + +### Component Testing with xUnit + +```C# +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +public class CounterComponentTests +{ + [Fact] + public void Counter_RendersCorrectly() + { + // Arrange + using var serviceProvider = new ServiceCollection() + .AddScoped() + .AddScoped() + .BuildServiceProvider(); + + var htmlRenderer = serviceProvider.GetRequiredService(); + var component = new CounterComponent(); + + // Act + var result = htmlRenderer.RenderComponentAsync( + ParameterView.FromDictionary(new Dictionary + { + ["State"] = new CounterState { Count = 5 } + })).Result; + + // Assert + Assert.Contains("Count: 5", result.ToHtmlString()); + } +} +``` + +### Testing Component State + +```C# +public class ComponentStateTests +{ + [Fact] + public void CounterState_BindsFromQuery() + { + // Arrange + var query = new QueryCollection(new Dictionary + { + ["count"] = "42", + ["message"] = "test" + }); + + var state = new CounterState(); + + // Act + state.BindFromQuery(query); + + // Assert + Assert.Equal(42, state.Count); + Assert.Equal("test", state.Message); + } + + [Fact] + public void CounterState_BuildsQueryString() + { + // Arrange + var state = new CounterState + { + Count = 10, + Message = "hello" + }; + + // Act + var queryString = state.BuildQueryString(); + + // Assert + Assert.Equal("?count=10&message=hello", queryString); + } +} +``` + +## Integration Testing + +### HTMX Endpoint Testing + +```C# +using Microsoft.AspNetCore.Mvc.Testing; +using System.Net.Http; +using Xunit; + +public class HtmxEndpointTests : IClassFixture> +{ + private readonly WebApplicationFactory _factory; + private readonly HttpClient _client; + + public HtmxEndpointTests(WebApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + } + + [Fact] + public async Task CounterEndpoint_ReturnsHtml() + { + // Arrange + _client.DefaultRequestHeaders.Add("HX-Request", "true"); + + // Act + var response = await _client.GetAsync("/htmx/counter?count=5"); + + // Assert + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("Count: 5", content); + Assert.Equal("text/html", response.Content.Headers.ContentType?.MediaType); + } + + [Fact] + public async Task CounterEndpoint_HandlesPost() + { + // Arrange + _client.DefaultRequestHeaders.Add("HX-Request", "true"); + var formData = new FormUrlEncodedContent(new[] + { + new KeyValuePair("count", "10") + }); + + // Act + var response = await _client.PostAsync("/htmx/counter", formData); + + // Assert + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("Count: 11", content); // Assuming increment logic + } +} +``` + +### Testing HTMX Headers + +```C# +[Fact] +public async Task Component_SetsCorrectHtmxHeaders() +{ + // Arrange + _client.DefaultRequestHeaders.Add("HX-Request", "true"); + _client.DefaultRequestHeaders.Add("HX-Target", "#content"); + + // Act + var response = await _client.GetAsync("/htmx/redirect-component"); + + // Assert + Assert.True(response.Headers.Contains("HX-Redirect")); + Assert.Equal("/new-page", response.Headers.GetValues("HX-Redirect").First()); +} +``` + +## Testing with Playwright + +### End-to-End HTMX Testing + +```C# +using Microsoft.Playwright; +using Microsoft.Playwright.MSTest; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[TestClass] +public class HtmxBehaviorTests : PageTest +{ + [TestMethod] + public async Task Counter_IncrementsOnClick() + { + // Arrange + await Page.GotoAsync("https://localhost:5001/counter"); + + // Act + await Page.ClickAsync("#increment-button"); + + // Wait for HTMX to complete + await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Assert + var countText = await Page.TextContentAsync("#count-display"); + Assert.AreEqual("Count: 1", countText); + } + + [TestMethod] + public async Task Search_UpdatesResults() + { + // Arrange + await Page.GotoAsync("https://localhost:5001/search"); + + // Act + await Page.FillAsync("#search-input", "test query"); + + // Wait for debounced request + await Page.WaitForTimeoutAsync(500); + await Page.WaitForSelectorAsync("#search-results [data-testid='result-item']"); + + // Assert + var results = await Page.QuerySelectorAllAsync("#search-results [data-testid='result-item']"); + Assert.IsTrue(results.Count > 0); + } +} +``` + +### Testing WebSocket/SSE + +```C# +[TestMethod] +public async Task SSE_ReceivesUpdates() +{ + // Arrange + await Page.GotoAsync("https://localhost:5001/live-updates"); + + // Wait for SSE connection + await Page.WaitForSelectorAsync("[data-sse-connected='true']"); + + // Trigger server-side event + await Page.EvaluateAsync(@" + fetch('/api/trigger-event', { method: 'POST' }) + "); + + // Wait for SSE update + await Page.WaitForSelectorAsync("#notification:has-text('New notification')"); + + // Assert + var notification = await Page.TextContentAsync("#notification"); + Assert.IsTrue(notification.Contains("New notification")); +} +``` + +## Testing Utilities + +### HTMX Test Client + +```C# +public class HtmxTestClient +{ + private readonly HttpClient _client; + + public HtmxTestClient(HttpClient client) + { + _client = client; + _client.DefaultRequestHeaders.Add("HX-Request", "true"); + } + + public async Task GetAsync(string url, string? target = null) + { + if (target != null) + { + _client.DefaultRequestHeaders.Add("HX-Target", target); + } + + var response = await _client.GetAsync(url); + return new HtmxResponse(response); + } + + public async Task PostAsync(string url, object data) + { + var json = JsonSerializer.Serialize(data); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await _client.PostAsync(url, content); + return new HtmxResponse(response); + } +} + +public class HtmxResponse +{ + private readonly HttpResponseMessage _response; + + public HtmxResponse(HttpResponseMessage response) + { + _response = response; + } + + public async Task GetContentAsync() + => await _response.Content.ReadAsStringAsync(); + + public string? GetHeader(string name) + => _response.Headers.GetValues(name)?.FirstOrDefault(); + + public bool HasTrigger(string eventName) + => GetHeader("HX-Trigger")?.Contains(eventName) ?? false; + + public string? RedirectUrl => GetHeader("HX-Redirect"); + public string? RetargetSelector => GetHeader("HX-Retarget"); +} +``` + +### Component Test Base + +```C# +public abstract class ComponentTestBase + where TComponent : HtmxComponentBase, new() + where TState : HtmxComponentParameters, new() +{ + protected IServiceProvider ServiceProvider { get; private set; } + protected ComponentHtmlResponseService RenderService { get; private set; } + + protected ComponentTestBase() + { + var services = new ServiceCollection(); + ConfigureServices(services); + + ServiceProvider = services.BuildServiceProvider(); + RenderService = ServiceProvider.GetRequiredService(); + } + + protected virtual void ConfigureServices(IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + } + + protected async Task RenderComponentAsync(TState state) + { + var parameters = new Dictionary { ["State"] = state }; + var result = await RenderService.RenderComponentAsync(parameters); + return result.ToHtmlString(); + } + + protected async Task RenderComponentAsync( + Dictionary parameters) + { + var result = await RenderService.RenderComponentAsync(parameters); + return result.ToHtmlString(); + } +} +``` + +## Mocking Dependencies + +### Mocking Services + +```C# +[Fact] +public async Task SearchComponent_UsesSearchService() +{ + // Arrange + var mockSearchService = new Mock(); + mockSearchService + .Setup(s => s.SearchAsync("test")) + .ReturnsAsync(new[] { new SearchResult { Title = "Test Result" } }); + + var services = new ServiceCollection() + .AddScoped() + .AddScoped() + .AddScoped(_ => mockSearchService.Object) + .BuildServiceProvider(); + + var renderService = services.GetRequiredService(); + + // Act + var result = await renderService.RenderComponentAsync( + new Dictionary + { + ["State"] = new SearchState { Query = "test" } + }); + + // Assert + var html = result.ToHtmlString(); + Assert.Contains("Test Result", html); + mockSearchService.Verify(s => s.SearchAsync("test"), Times.Once); +} +``` + +## Test Coverage + +### Measuring Coverage + +Use the coverage script provided in the project: + +```bash +# Run tests with coverage +./coverage.sh + +# View HTML report +open CoverageReport/index.html +``` + +### Coverage Configuration + +```xml + + + true + cobertura + ./TestResults/ + [FastComponents]* + [FastComponents.Tests]* + +``` + +## Best Practices + +### Test Organization + +1. **Arrange-Act-Assert**: Follow AAA pattern consistently +2. **One Concept Per Test**: Test one thing at a time +3. **Descriptive Names**: Use clear, descriptive test names +4. **Test Data Builders**: Use builders for complex test data + +### HTMX-Specific Testing + +1. **Test HTMX Headers**: Verify correct headers are set +2. **Test Partial Updates**: Ensure only necessary content is returned +3. **Test Error Scenarios**: Verify error handling works correctly +4. **Test State Transitions**: Verify component state changes properly + +### Performance Testing + +```C# +[Fact] +public async Task Component_RendersWithinTimeLimit() +{ + // Arrange + var stopwatch = Stopwatch.StartNew(); + var state = new ComplexState { /* large data */ }; + + // Act + await RenderComponentAsync(state); + stopwatch.Stop(); + + // Assert + Assert.True(stopwatch.ElapsedMilliseconds < 100, + $"Component took {stopwatch.ElapsedMilliseconds}ms to render"); +} +``` + +## CI/CD Integration + +### GitHub Actions + +```yaml +name: Test +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '9.0.x' + + - name: Run tests + run: dotnet test --configuration Release --logger trx --collect:"XPlat Code Coverage" + + - name: Upload coverage + uses: codecov/codecov-action@v3 +``` + +## See Also + +- [Performance](Performance.md) +- [Deployment](Deployment.md) +- [Examples](Examples.md) \ No newline at end of file diff --git a/Writerside/topics/Troubleshooting.md b/Writerside/topics/Troubleshooting.md new file mode 100644 index 0000000..7f72e55 --- /dev/null +++ b/Writerside/topics/Troubleshooting.md @@ -0,0 +1,416 @@ +# Troubleshooting + +This guide helps you diagnose and fix common issues when working with FastComponents. + +## Common Issues + +### Components Not Rendering + +**Symptom**: Component endpoints return 404 or empty responses. + +**Solutions**: + +1. **Check service registration**: +```C# +// Ensure services are registered +builder.Services.AddFastComponents(); + +// And middleware is configured +app.UseFastComponents(); +``` + +2. **Verify endpoint mapping**: +```C# +// Check that endpoints are mapped +app.MapHtmxGet("/my-component"); + +// Or using auto-mapping +app.UseFastComponentsAuto(); +``` + +3. **Confirm component inheritance**: +```C# +// Component must inherit from base class +@inherits SimpleHtmxComponent +// or +@inherits HtmxComponentBase +``` + +### HTMX Attributes Not Working + +**Symptom**: HTMX attributes don't trigger requests or updates. + +**Solutions**: + +1. **Include HTMX JavaScript**: +```html + + + +``` + +2. **Check attribute syntax**: +```Razor + + + + + +``` + +3. **Verify target elements exist**: +```html + +
+ +``` + +### State Not Persisting + +**Symptom**: Component state resets on each request. + +**Solutions**: + +1. **Use query string state**: +```C# +[GenerateParameterMethods] +public partial record MyState : HtmxComponentParameters +{ + public int Counter { get; init; } +} + +// State persists in URL: /component?counter=5 +``` + +2. **Check state binding**: +```C# +// Ensure state is properly bound from query +protected override MyState OnPost(MyState state) +{ + // State parameter contains current values + return state with { Counter = state.Counter + 1 }; +} +``` + +3. **Use session or database for complex state**: +```C# +protected override async Task OnGetAsync(MyState state) +{ + // Load from session + var sessionState = HttpContext.Session.GetString("state"); + if (sessionState != null) + { + state = JsonSerializer.Deserialize(sessionState)!; + } + return state; +} +``` + +### Validation Errors + +**Symptom**: Forms don't validate or show errors properly. + +**Solutions**: + +1. **Enable HTMX validation**: +```html +
+ + +
+``` + +2. **Handle validation in component**: +```C# +protected override FormState OnPost(FormState state) +{ + var errors = new List(); + + if (string.IsNullOrEmpty(state.Email)) + errors.Add("Email is required"); + + if (errors.Any()) + return state with { Errors = errors }; + + // Process valid form... +} +``` + +3. **Display validation errors**: +```Razor +@if (State.Errors.Any()) +{ +
+ @foreach (var error in State.Errors) + { +

@error

+ } +
+} +``` + +### Performance Issues + +**Symptom**: Slow component rendering or response times. + +**Solutions**: + +1. **Enable output caching**: +```C# +app.MapHtmxGet("/slow") + .CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(5))); +``` + +2. **Optimize state serialization**: +```C# +[GenerateParameterMethods(SkipDefaults = true)] +public partial record OptimizedState : HtmxComponentParameters +{ + // Only non-default values in query string +} +``` + +3. **Use appropriate swap strategies**: +```html + +
+ + +
+``` + +## Debugging Techniques + +### Enable HTMX Debugging + +Add HTMX debug extension: + +```html + + + +app.UseMiddleware(); +``` + +### Browser Developer Tools + +1. **Network tab**: Monitor HTMX requests +2. **Console**: Check for JavaScript errors +3. **Elements**: Inspect DOM changes +4. **Headers**: Verify HTMX headers + +### Server-Side Logging + +```C# +public class MyComponent : SimpleHtmxComponent +{ + private readonly ILogger _logger; + + protected override MyState OnPost(MyState state) + { + _logger.LogInformation("Processing request: {@State}", state); + + try + { + // Component logic + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing component"); + throw; + } + } +} +``` + +### Request/Response Inspection + +```C# +// Log HTMX headers +app.Use(async (context, next) => +{ + if (context.IsHtmxRequest()) + { + var headers = context.GetHtmxRequestHeaders(); + logger.LogInformation("HTMX Request: {Trigger} -> {Target}", + headers.Trigger, headers.Target); + } + + await next(); +}); +``` + +## Error Messages + +### "Record with [GenerateParameterMethods] must be partial" + +**Cause**: Source generator requires partial modifier. + +**Fix**: +```C# +// ❌ Wrong +[GenerateParameterMethods] +public record MyState : HtmxComponentParameters { } + +// ✅ Correct +[GenerateParameterMethods] +public partial record MyState : HtmxComponentParameters { } +``` + +### "Component must inherit from HtmxComponentParameters" + +**Cause**: State record missing base class. + +**Fix**: +```C# +// ❌ Wrong +public partial record MyState { } + +// ✅ Correct +public partial record MyState : HtmxComponentParameters { } +``` + +### "Cannot resolve service for type 'HtmlRenderer'" + +**Cause**: FastComponents services not registered. + +**Fix**: +```C# +// Add in Program.cs +builder.Services.AddFastComponents(); +``` + +### "The response has already started" + +**Cause**: Trying to modify response after writing content. + +**Fix**: +```C# +// Set headers before writing response +protected override async Task OnGetAsync(MyState state) +{ + // ✅ Headers first + HttpContext.GetHtmxResponseHeaders().Trigger("my-event"); + + // Then render content + await base.OnGetAsync(state); +} +``` + +## Platform-Specific Issues + +### Linux/macOS Case Sensitivity + +File paths are case-sensitive on Linux/macOS: + +```Razor +// Windows (works) +@using components.shared + +// Linux/macOS (correct) +@using Components.Shared +``` + +### AOT Compilation Warnings + +For AOT deployment: + +```C# +// Suppress warnings where necessary +[UnconditionalSuppressMessage("Trimming", "IL2026")] +[UnconditionalSuppressMessage("AOT", "IL3050")] +public static void MapComponents(IEndpointRouteBuilder app) +{ + app.MapHtmxComponentsByConvention(); +} +``` + +### Container Deployment + +Ensure static files are included: + +```docker +# Copy wwwroot +COPY wwwroot ./wwwroot + +# Ensure HTMX files are present +RUN test -f wwwroot/lib/htmx.min.js +``` + +## Getting Help + +### Check Documentation + +1. Review the [Getting Started](Getting-Started.md) guide +2. Check [Examples](Examples.md) for similar use cases +3. Read the [API Reference](API-Reference.md) + +### Enable Verbose Logging + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "FastComponents": "Debug", + "Microsoft.AspNetCore": "Warning" + } + } +} +``` + +### Community Resources + +- **GitHub Issues**: Report bugs and request features +- **Discussions**: Ask questions and share solutions +- **Stack Overflow**: Tag questions with `fastcomponents` + +### Minimal Reproduction + +When reporting issues, provide: + +1. **Component code**: +```razor +@inherits SimpleHtmxComponent + +``` + +2. **State definition**: +```C# +[GenerateParameterMethods] +public partial record MyState : HtmxComponentParameters +{ + // Properties +} +``` + +3. **Registration code**: +```C# +// Program.cs setup +builder.Services.AddFastComponents(); +app.MapHtmxGet("/test"); +``` + +4. **Error messages** and stack traces + +## Diagnostic Checklist + +- [ ] HTMX JavaScript is loaded +- [ ] Services are registered (`AddFastComponents()`) +- [ ] Middleware is configured (`UseFastComponents()`) +- [ ] Endpoints are mapped +- [ ] Component inherits from correct base class +- [ ] State record is partial with `[GenerateParameterMethods]` +- [ ] Target elements exist in DOM +- [ ] No JavaScript console errors +- [ ] Network requests show correct headers +- [ ] Response content-type is `text/html` + +## Next Steps + +- [FAQ](FAQ.md) - Frequently asked questions +- [Migration Guide](Migration-Guide.md) - Upgrading FastComponents +- [API Reference](API-Reference.md) - Detailed API documentation +- [Examples](Examples.md) - Working examples \ No newline at end of file diff --git a/Writerside/ug.tree b/Writerside/ug.tree new file mode 100644 index 0000000..59c9a9e --- /dev/null +++ b/Writerside/ug.tree @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Writerside/v.list b/Writerside/v.list new file mode 100644 index 0000000..2d12cb3 --- /dev/null +++ b/Writerside/v.list @@ -0,0 +1,5 @@ + + + + + diff --git a/Writerside/writerside.cfg b/Writerside/writerside.cfg new file mode 100644 index 0000000..9f01600 --- /dev/null +++ b/Writerside/writerside.cfg @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/coverage.sh b/coverage.sh new file mode 100755 index 0000000..c2bd3c8 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# FastComponents Code Coverage Script +# Generates code coverage reports using Microsoft Testing Platform and ReportGenerator + +set -e + +echo "🧪 Running tests with code coverage..." + +# Clean previous coverage results +rm -rf CoverageReport +rm -f coverage.cobertura.xml + +# Run tests with coverage for all test projects +dotnet test --configuration Release -- --coverage --coverage-output-format cobertura --coverage-output coverage.cobertura.xml + +echo "📊 Generating HTML coverage report..." + +# Find the generated coverage file (it may be in different locations) +COVERAGE_FILE="" +if [ -f "tests/FastComponents.UnitTests/bin/Release/net9.0/TestResults/coverage.cobertura.xml" ]; then + COVERAGE_FILE="tests/FastComponents.UnitTests/bin/Release/net9.0/TestResults/coverage.cobertura.xml" +elif [ -f "tests/FastComponents.UnitTests/bin/Debug/net9.0/TestResults/coverage.cobertura.xml" ]; then + COVERAGE_FILE="tests/FastComponents.UnitTests/bin/Debug/net9.0/TestResults/coverage.cobertura.xml" +else + echo "❌ Coverage file not found. Looking for available files..." + find . -name "coverage.cobertura.xml" -type f + exit 1 +fi + +echo "📄 Using coverage file: $COVERAGE_FILE" + +# Generate HTML report +reportgenerator \ + -reports:"$COVERAGE_FILE" \ + -targetdir:CoverageReport \ + -reporttypes:Html + +echo "✅ Coverage report generated successfully!" + +# Display summary +if command -v reportgenerator &> /dev/null; then + echo "" + echo "📈 Coverage Summary:" + reportgenerator \ + -reports:"$COVERAGE_FILE" \ + -targetdir:temp_summary \ + -reporttypes:TextSummary + cat temp_summary/Summary.txt 2>/dev/null || echo "Summary not available" + rm -rf temp_summary +fi + +# Ask user if they want to open the report +echo "" +read -p "🌐 Would you like to open the coverage report in your browser? (y/n) " -n 1 -r +echo "" + +if [[ $REPLY =~ ^[Yy]$ ]]; then + # Detect OS and open the appropriate browser + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + open CoverageReport/index.html + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + if command -v xdg-open &> /dev/null; then + xdg-open CoverageReport/index.html + else + echo "❌ Could not detect browser opener. Please open CoverageReport/index.html manually." + fi + elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then + # Windows + start CoverageReport/index.html + else + echo "❌ Unknown OS type. Please open CoverageReport/index.html manually." + fi +else + echo "📁 You can open CoverageReport/index.html manually to view the report." +fi \ No newline at end of file diff --git a/demo/Directory.Build.props b/demo/Directory.Build.props new file mode 100644 index 0000000..5b93b2a --- /dev/null +++ b/demo/Directory.Build.props @@ -0,0 +1,22 @@ + + + + + + + false + false + false + + + + + + $(NoWarn);IDE0160 + + $(NoWarn);IDE0008 + + $(NoWarn);IDE0055 + + + \ No newline at end of file diff --git a/demo/HtmxAppServer/Components/App.razor b/demo/HtmxAppServer/Components/App.razor index 439b0ee..eebf875 100644 --- a/demo/HtmxAppServer/Components/App.razor +++ b/demo/HtmxAppServer/Components/App.razor @@ -1,4 +1,4 @@ -@inherits HtmxComponentBase +@inherits HtmxComponentBase @@ -8,6 +8,7 @@ Demo HTMX Blazor @* TODO: load this script from the FastComponents package *@ + @* Basic styling for demo purposes *@ ') + } + } + + function getMetaConfig() { + /** @type HTMLMetaElement */ + const element = getDocument().querySelector('meta[name="htmx-config"]') + if (element) { + return parseJSON(element.content) + } else { + return null + } + } + + function mergeMetaConfig() { + const metaConfig = getMetaConfig() + if (metaConfig) { + htmx.config = mergeObjects(htmx.config, metaConfig) + } + } + + // initialize the document + ready(function() { + mergeMetaConfig() + insertIndicatorStyles() + let body = getDocument().body + processNode(body) + const restoredElts = getDocument().querySelectorAll( + "[hx-trigger='restored'],[data-hx-trigger='restored']" + ) + body.addEventListener('htmx:abort', function(evt) { + const target = evt.target + const internalData = getInternalData(target) + if (internalData && internalData.xhr) { + internalData.xhr.abort() + } + }) + /** @type {(ev: PopStateEvent) => any} */ + const originalPopstate = window.onpopstate ? window.onpopstate.bind(window) : null + /** @type {(ev: PopStateEvent) => any} */ + window.onpopstate = function(event) { + if (event.state && event.state.htmx) { + restoreHistory() + forEach(restoredElts, function(elt) { + triggerEvent(elt, 'htmx:restored', { + document: getDocument(), + triggerEvent + }) + }) + } else { + if (originalPopstate) { + originalPopstate(event) + } + } + } + getWindow().setTimeout(function() { + triggerEvent(body, 'htmx:load', {}) // give ready handlers a chance to load up before firing this event + body = null // kill reference for gc + }, 0) + }) + + return htmx +})() + +/** @typedef {'get'|'head'|'post'|'put'|'delete'|'connect'|'options'|'trace'|'patch'} HttpVerb */ + +/** + * @typedef {Object} SwapOptions + * @property {string} [select] + * @property {string} [selectOOB] + * @property {*} [eventInfo] + * @property {string} [anchor] + * @property {Element} [contextElement] + * @property {swapCallback} [afterSwapCallback] + * @property {swapCallback} [afterSettleCallback] + */ + +/** + * @callback swapCallback + */ + +/** + * @typedef {'innerHTML' | 'outerHTML' | 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend' | 'delete' | 'none' | string} HtmxSwapStyle + */ + +/** + * @typedef HtmxSwapSpecification + * @property {HtmxSwapStyle} swapStyle + * @property {number} swapDelay + * @property {number} settleDelay + * @property {boolean} [transition] + * @property {boolean} [ignoreTitle] + * @property {string} [head] + * @property {'top' | 'bottom'} [scroll] + * @property {string} [scrollTarget] + * @property {string} [show] + * @property {string} [showTarget] + * @property {boolean} [focusScroll] + */ + +/** + * @typedef {((this:Node, evt:Event) => boolean) & {source: string}} ConditionalFunction + */ + +/** + * @typedef {Object} HtmxTriggerSpecification + * @property {string} trigger + * @property {number} [pollInterval] + * @property {ConditionalFunction} [eventFilter] + * @property {boolean} [changed] + * @property {boolean} [once] + * @property {boolean} [consume] + * @property {number} [delay] + * @property {string} [from] + * @property {string} [target] + * @property {number} [throttle] + * @property {string} [queue] + * @property {string} [root] + * @property {string} [threshold] + */ + +/** + * @typedef {{elt: Element, message: string, validity: ValidityState}} HtmxElementValidationError + */ + +/** + * @typedef {Record} HtmxHeaderSpecification + * @property {'true'} HX-Request + * @property {string|null} HX-Trigger + * @property {string|null} HX-Trigger-Name + * @property {string|null} HX-Target + * @property {string} HX-Current-URL + * @property {string} [HX-Prompt] + * @property {'true'} [HX-Boosted] + * @property {string} [Content-Type] + * @property {'true'} [HX-History-Restore-Request] + */ + +/** @typedef HtmxAjaxHelperContext + * @property {Element|string} [source] + * @property {Event} [event] + * @property {HtmxAjaxHandler} [handler] + * @property {Element|string} [target] + * @property {HtmxSwapStyle} [swap] + * @property {Object|FormData} [values] + * @property {Record} [headers] + * @property {string} [select] + */ + +/** + * @typedef {Object} HtmxRequestConfig + * @property {boolean} boosted + * @property {boolean} useUrlParams + * @property {FormData} formData + * @property {Object} parameters formData proxy + * @property {FormData} unfilteredFormData + * @property {Object} unfilteredParameters unfilteredFormData proxy + * @property {HtmxHeaderSpecification} headers + * @property {Element} target + * @property {HttpVerb} verb + * @property {HtmxElementValidationError[]} errors + * @property {boolean} withCredentials + * @property {number} timeout + * @property {string} path + * @property {Event} triggeringEvent + */ + +/** + * @typedef {Object} HtmxResponseInfo + * @property {XMLHttpRequest} xhr + * @property {Element} target + * @property {HtmxRequestConfig} requestConfig + * @property {HtmxAjaxEtc} etc + * @property {boolean} boosted + * @property {string} select + * @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo + * @property {boolean} [failed] + * @property {boolean} [successful] + * @property {boolean} [keepIndicators] + */ + +/** + * @typedef {Object} HtmxAjaxEtc + * @property {boolean} [returnPromise] + * @property {HtmxAjaxHandler} [handler] + * @property {string} [select] + * @property {Element} [targetOverride] + * @property {HtmxSwapStyle} [swapOverride] + * @property {Record} [headers] + * @property {Object|FormData} [values] + * @property {boolean} [credentials] + * @property {number} [timeout] + */ + +/** + * @typedef {Object} HtmxResponseHandlingConfig + * @property {string} [code] + * @property {boolean} swap + * @property {boolean} [error] + * @property {boolean} [ignoreTitle] + * @property {string} [select] + * @property {string} [target] + * @property {string} [swapOverride] + * @property {string} [event] + */ + +/** + * @typedef {HtmxResponseInfo & {shouldSwap: boolean, serverResponse: any, isError: boolean, ignoreTitle: boolean, selectOverride:string, swapOverride:string}} HtmxBeforeSwapDetails + */ + +/** + * @callback HtmxAjaxHandler + * @param {Element} elt + * @param {HtmxResponseInfo} responseInfo + */ + +/** + * @typedef {(() => void)} HtmxSettleTask + */ + +/** + * @typedef {Object} HtmxSettleInfo + * @property {HtmxSettleTask[]} tasks + * @property {Element[]} elts + * @property {string} [title] + */ + +/** + * @see https://github.com/bigskysoftware/htmx-extensions/blob/main/README.md + * @typedef {Object} HtmxExtension + * @property {(api: any) => void} init + * @property {(name: string, event: Event|CustomEvent) => boolean} onEvent + * @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse + * @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap + * @property {(swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean|Node[]} handleSwap + * @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Node) => *|string|null} encodeParameters + * @property {() => string[]|null} getSelectors + */ diff --git a/demo/HtmxAppServer/wwwroot/lib/htmx.min.js b/demo/HtmxAppServer/wwwroot/lib/htmx.min.js index 6c0a0f2..59937d7 100644 --- a/demo/HtmxAppServer/wwwroot/lib/htmx.min.js +++ b/demo/HtmxAppServer/wwwroot/lib/htmx.min.js @@ -1 +1 @@ -(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Q={onLoad:t,process:Bt,on:Z,off:K,trigger:ce,ajax:Or,find:C,findAll:f,closest:v,values:function(e,t){var r=ur(e,t||"post");return r.values},remove:B,addClass:F,removeClass:n,toggleClass:V,takeClass:j,defineExtension:kr,removeExtension:Pr,logAll:X,logNone:U,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false,ignoreTitle:false,scrollIntoViewOnBoost:true},parseInterval:d,_:e,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Q.config.wsBinaryType;return t},version:"1.9.9"};var r={addTriggerHandler:Tt,bodyContains:se,canAccessLocalStorage:M,findThisElement:de,filterValues:dr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Cr,getHeaders:vr,getInputValues:ur,getInternalData:ae,getSwapSpecification:mr,getTriggerSpecs:Qe,getTarget:ge,makeFragment:l,mergeObjects:le,makeSettleInfo:R,oobSwap:xe,querySelectorExt:ue,selectAndSwap:Ue,settleImmediately:Yt,shouldCancel:it,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:T};var b=["get","post","put","delete","patch"];var w=b.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}if(e.slice(-2)=="ms"){return parseFloat(e.slice(0,-2))||undefined}if(e.slice(-1)=="s"){return parseFloat(e.slice(0,-1))*1e3||undefined}if(e.slice(-1)=="m"){return parseFloat(e.slice(0,-1))*1e3*60||undefined}return parseFloat(e)||undefined}function ee(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){return e.parentElement}function re(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function S(e,t,r){var n=te(t,r);var i=te(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function ne(t,r){var n=null;c(t,function(e){return n=S(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function q(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function i(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=re().createDocumentFragment()}return i}function H(e){return e.match(/",0);return r.querySelector("template").content}else{var n=q(e);switch(n){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return i(""+e+"
",1);case"col":return i(""+e+"
",2);case"tr":return i(""+e+"
",2);case"td":case"th":return i(""+e+"
",3);case"script":case"style":return i("
"+e+"
",1);default:return i(e,0)}}}function ie(e){if(e){e()}}function L(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function A(e){return L(e,"Function")}function N(e){return L(e,"Object")}function ae(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function I(e){var t=[];if(e){for(var r=0;r=0}function se(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return re().body.contains(e.getRootNode().host)}else{return re().body.contains(e)}}function P(e){return e.trim().split(/\s+/)}function le(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function E(e){try{return JSON.parse(e)}catch(e){x(e);return null}}function M(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function D(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!t.match("^/$")){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function e(e){return wr(re().body,function(){return eval(e)})}function t(t){var e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function X(){Q.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function U(){Q.logger=null}function C(e,t){if(t){return e.querySelector(t)}else{return C(re(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(re(),e)}}function B(e,t){e=s(e);if(t){setTimeout(function(){B(e);e=null},t)}else{e.parentElement.removeChild(e)}}function F(e,t,r){e=s(e);if(r){setTimeout(function(){F(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=s(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function V(e,t){e=s(e);e.classList.toggle(t)}function j(e,t){e=s(e);oe(e.parentElement.children,function(e){n(e,t)});F(e,t)}function v(e,t){e=s(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function g(e,t){return e.substring(0,t.length)===t}function _(e,t){return e.substring(e.length-t.length)===t}function z(e){var t=e.trim();if(g(t,"<")&&_(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function W(e,t){if(t.indexOf("closest ")===0){return[v(e,z(t.substr(8)))]}else if(t.indexOf("find ")===0){return[C(e,z(t.substr(5)))]}else if(t==="next"){return[e.nextElementSibling]}else if(t.indexOf("next ")===0){return[$(e,z(t.substr(5)))]}else if(t==="previous"){return[e.previousElementSibling]}else if(t.indexOf("previous ")===0){return[G(e,z(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return re().querySelectorAll(z(t))}}var $=function(e,t){var r=re().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ue(e,t){if(t){return W(e,t)[0]}else{return W(re().body,e)[0]}}function s(e){if(L(e,"String")){return C(e)}else{return e}}function J(e,t,r){if(A(t)){return{target:re().body,event:e,listener:t}}else{return{target:s(e),event:t,listener:r}}}function Z(t,r,n){Dr(function(){var e=J(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=A(r);return e?r:n}function K(t,r,n){Dr(function(){var e=J(t,r,n);e.target.removeEventListener(e.event,e.listener)});return A(r)?r:n}var ve=re().createElement("output");function Y(e,t){var r=ne(e,t);if(r){if(r==="this"){return[de(e,t)]}else{var n=W(e,r);if(n.length===0){x('The selector "'+r+'" on '+t+" returned no matches!");return[ve]}else{return n}}}}function de(e,t){return c(e,function(e){return te(e,t)!=null})}function ge(e){var t=ne(e,"hx-target");if(t){if(t==="this"){return de(e,"hx-target")}else{return ue(e,t)}}else{var r=ae(e);if(r.boosted){return re().body}else{return e}}}function me(e){var t=Q.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=re().querySelectorAll(t);if(r){oe(r,function(e){var t;var r=i.cloneNode(true);t=re().createDocumentFragment();t.appendChild(r);if(!ye(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!ce(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){De(o,e,e,t,a)}oe(a.elts,function(e){ce(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);fe(re().body,"htmx:oobErrorNoTarget",{content:i})}return e}function be(e,t,r){var n=ne(e,"hx-select-oob");if(n){var i=n.split(",");for(let e=0;e0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();pe(e,i);s.tasks.push(function(){pe(e,a)})}}})}function Ee(e){return function(){n(e,Q.config.addedClass);Bt(e);Ot(e);Ce(e);ce(e,"htmx:load")}}function Ce(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function a(e,t,r,n){Se(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;F(i,Q.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(Ee(i))}}}function Te(e,t){var r=0;while(r-1){var t=e.replace(/]*>|>)([\s\S]*?)<\/svg>/gim,"");var r=t.match(/]*>|>)([\s\S]*?)<\/title>/im);if(r){return r[2]}}}function Ue(e,t,r,n,i,a){i.title=Xe(n);var o=l(n);if(o){be(r,o,i);o=Me(r,o,a);we(o);return De(e,r,t,o,i)}}function Be(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=E(n);for(var a in i){if(i.hasOwnProperty(a)){var o=i[a];if(!N(o)){o={value:o}}ce(r,a,o)}}}else{var s=n.split(",");for(var l=0;l0){var o=t[0];if(o==="]"){n--;if(n===0){if(a===null){i=i+"true"}t.shift();i+=")})";try{var s=wr(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){fe(re().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(o==="["){n++}if(Je(o,a,r)){i+="(("+r+"."+o+") ? ("+r+"."+o+") : (window."+o+"))"}else{i=i+o}a=t.shift()}}}function y(e,t){var r="";while(e.length>0&&!e[0].match(t)){r+=e.shift()}return r}function Ke(e){var t;if(e.length>0&&We.test(e[0])){e.shift();t=y(e,$e).trim();e.shift()}else{t=y(e,p)}return t}var Ye="input, textarea, select";function Qe(e){var t=te(e,"hx-trigger");var r=[];if(t){var n=Ge(t);do{y(n,ze);var i=n.length;var a=y(n,/[,\[\s]/);if(a!==""){if(a==="every"){var o={trigger:"every"};y(n,ze);o.pollInterval=d(y(n,/[,\[\s]/));y(n,ze);var s=Ze(e,n,"event");if(s){o.eventFilter=s}r.push(o)}else if(a.indexOf("sse:")===0){r.push({trigger:"sse",sseEvent:a.substr(4)})}else{var l={trigger:a};var s=Ze(e,n,"event");if(s){l.eventFilter=s}while(n.length>0&&n[0]!==","){y(n,ze);var u=n.shift();if(u==="changed"){l.changed=true}else if(u==="once"){l.once=true}else if(u==="consume"){l.consume=true}else if(u==="delay"&&n[0]===":"){n.shift();l.delay=d(y(n,p))}else if(u==="from"&&n[0]===":"){n.shift();if(We.test(n[0])){var f=Ke(n)}else{var f=y(n,p);if(f==="closest"||f==="find"||f==="next"||f==="previous"){n.shift();var c=Ke(n);if(c.length>0){f+=" "+c}}}l.from=f}else if(u==="target"&&n[0]===":"){n.shift();l.target=Ke(n)}else if(u==="throttle"&&n[0]===":"){n.shift();l.throttle=d(y(n,p))}else if(u==="queue"&&n[0]===":"){n.shift();l.queue=y(n,p)}else if(u==="root"&&n[0]===":"){n.shift();l[u]=Ke(n)}else if(u==="threshold"&&n[0]===":"){n.shift();l[u]=y(n,p)}else{fe(e,"htmx:syntax:error",{token:n.shift()})}}r.push(l)}}if(n.length===i){fe(e,"htmx:syntax:error",{token:n.shift()})}y(n,ze)}while(n[0]===","&&n.shift())}if(r.length>0){return r}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,Ye)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function et(e){ae(e).cancelled=true}function tt(e,t,r){var n=ae(e);n.timeout=setTimeout(function(){if(se(e)&&n.cancelled!==true){if(!ot(r,e,Vt("hx:poll:trigger",{triggerSpec:r,target:e}))){t(e)}tt(e,t,r)}},r.pollInterval)}function rt(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function nt(t,r,e){if(t.tagName==="A"&&rt(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=ee(t,"href")}else{var a=ee(t,"method");n=a?a.toLowerCase():"get";if(n==="get"){}i=ee(t,"action")}e.forEach(function(e){st(t,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(n,i,e,t)},r,e,true)})}}function it(e,t){if(e.type==="submit"||e.type==="click"){if(t.tagName==="FORM"){return true}if(h(t,'input[type="submit"], button')&&v(t,"form")!==null){return true}if(t.tagName==="A"&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function at(e,t){return ae(e).boosted&&e.tagName==="A"&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function ot(e,t,r){var n=e.eventFilter;if(n){try{return n.call(t,r)!==true}catch(e){fe(re().body,"htmx:eventFilter:error",{error:e,source:n.source});return true}}return false}function st(a,o,e,s,l){var u=ae(a);var t;if(s.from){t=W(a,s.from)}else{t=[a]}if(s.changed){t.forEach(function(e){var t=ae(e);t.lastValue=e.value})}oe(t,function(n){var i=function(e){if(!se(a)){n.removeEventListener(s.trigger,i);return}if(at(a,e)){return}if(l||it(e,a)){e.preventDefault()}if(ot(s,a,e)){return}var t=ae(e);t.triggerSpec=s;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(a)<0){t.handledFor.push(a);if(s.consume){e.stopPropagation()}if(s.target&&e.target){if(!h(e.target,s.target)){return}}if(s.once){if(u.triggeredOnce){return}else{u.triggeredOnce=true}}if(s.changed){var r=ae(n);if(r.lastValue===n.value){return}r.lastValue=n.value}if(u.delayed){clearTimeout(u.delayed)}if(u.throttle){return}if(s.throttle){if(!u.throttle){o(a,e);u.throttle=setTimeout(function(){u.throttle=null},s.throttle)}}else if(s.delay){u.delayed=setTimeout(function(){o(a,e)},s.delay)}else{ce(a,"htmx:trigger");o(a,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:s.trigger,listener:i,on:n});n.addEventListener(s.trigger,i)})}var lt=false;var ut=null;function ft(){if(!ut){ut=function(){lt=true};window.addEventListener("scroll",ut);setInterval(function(){if(lt){lt=false;oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){ct(e)})}},200)}}function ct(t){if(!o(t,"data-hx-revealed")&&k(t)){t.setAttribute("data-hx-revealed","true");var e=ae(t);if(e.initHash){ce(t,"revealed")}else{t.addEventListener("htmx:afterProcessNode",function(e){ce(t,"revealed")},{once:true})}}}function ht(e,t,r){var n=P(r);for(var i=0;i=0){var t=mt(n);setTimeout(function(){vt(s,r,n+1)},t)}};t.onopen=function(e){n=0};ae(s).webSocket=t;t.addEventListener("message",function(e){if(dt(s)){return}var t=e.data;T(s,function(e){t=e.transformResponse(t,null,s)});var r=R(s);var n=l(t);var i=I(n.children);for(var a=0;a0){ce(u,"htmx:validation:halted",i);return}t.send(JSON.stringify(l));if(it(e,u)){e.preventDefault()}})}else{fe(u,"htmx:noWebSocketSourceError")}}function mt(e){var t=Q.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}x('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function pt(e,t,r){var n=P(r);for(var i=0;i0){var o=n.shift();var s=o.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);if(a===0&&s){o.split(":");i=s[1].slice(0,-1);r[i]=s[2]}else{r[i]+=o}a+=Pt(o)}for(var l in r){Mt(e,l,r[l])}}}function Xt(t){Oe(t);for(var e=0;eQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(re().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function $t(e){if(!M()){return null}e=D(e);var t=E(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",o);var e=l(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=zt();var r=R(t);var n=Xe(this.response);if(n){var i=C("title");if(i){i.innerHTML=n}else{window.document.title=n}}Pe(t,e,r);Yt(r.tasks);_t=a;ce(re().body,"htmx:historyRestore",{path:a,cacheMiss:true,serverResponse:this.response})}else{fe(re().body,"htmx:historyCacheMissLoadError",o)}};e.send()}function er(e){Jt();e=e||location.pathname+location.search;var t=$t(e);if(t){var r=l(t.content);var n=zt();var i=R(n);Pe(n,r,i);Yt(i.tasks);document.title=t.title;setTimeout(function(){window.scrollTo(0,t.scroll)},0);_t=e;ce(re().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{Qt(e)}}}function tr(e){var t=Y(e,"hx-indicator");if(t==null){t=[e]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.classList["add"].call(e.classList,Q.config.requestClass)});return t}function rr(e){var t=Y(e,"hx-disabled-elt");if(t==null){t=[]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","")});return t}function nr(e,t){oe(e,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList["remove"].call(e.classList,Q.config.requestClass)}});oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled")}})}function ir(e,t){for(var r=0;r=0}function mr(e,t){var r=t?t:ne(e,"hx-swap");var n={swapStyle:ae(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(e).boosted&&!gr(e)){n["show"]="top"}if(r){var i=P(r);if(i.length>0){for(var a=0;a0?l.join(":"):null;n["scroll"]=u;n["scrollTarget"]=f}else if(o.indexOf("show:")===0){var c=o.substr(5);var l=c.split(":");var h=l.pop();var f=l.length>0?l.join(":"):null;n["show"]=h;n["showTarget"]=f}else if(o.indexOf("focus-scroll:")===0){var v=o.substr("focus-scroll:".length);n["focusScroll"]=v=="true"}else if(a==0){n["swapStyle"]=o}else{x("Unknown modifier in hx-swap: "+o)}}}}return n}function pr(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function yr(t,r,n){var i=null;T(r,function(e){if(i==null){i=e.encodeParameters(t,n,r)}});if(i!=null){return i}else{if(pr(r)){return hr(n)}else{return cr(n)}}}function R(e){return{tasks:[],elts:[e]}}function xr(e,t){var r=e[0];var n=e[e.length-1];if(t.scroll){var i=null;if(t.scrollTarget){i=ue(r,t.scrollTarget)}if(t.scroll==="top"&&(r||i)){i=i||r;i.scrollTop=0}if(t.scroll==="bottom"&&(n||i)){i=i||n;i.scrollTop=i.scrollHeight}}if(t.show){var i=null;if(t.showTarget){var a=t.showTarget;if(t.showTarget==="window"){a="body"}i=ue(r,a)}if(t.show==="top"&&(r||i)){i=i||r;i.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(n||i)){i=i||n;i.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function br(e,t,r,n){if(n==null){n={}}if(e==null){return n}var i=te(e,t);if(i){var a=i.trim();var o=r;if(a==="unset"){return null}if(a.indexOf("javascript:")===0){a=a.substr(11);o=true}else if(a.indexOf("js:")===0){a=a.substr(3);o=true}if(a.indexOf("{")!==0){a="{"+a+"}"}var s;if(o){s=wr(e,function(){return Function("return ("+a+")")()},{})}else{s=E(a)}for(var l in s){if(s.hasOwnProperty(l)){if(n[l]==null){n[l]=s[l]}}}}return br(u(e),t,r,n)}function wr(e,t,r){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return r}}function Sr(e,t){return br(e,"hx-vars",true,t)}function Er(e,t){return br(e,"hx-vals",false,t)}function Cr(e){return le(Sr(e),Er(e))}function Tr(t,r,n){if(n!==null){try{t.setRequestHeader(r,n)}catch(e){t.setRequestHeader(r,encodeURIComponent(n));t.setRequestHeader(r+"-URI-AutoEncoded","true")}}}function Rr(t){if(t.responseURL&&typeof URL!=="undefined"){try{var e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(re().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function O(e,t){return e.getAllResponseHeaders().match(t)}function Or(e,t,r){e=e.toLowerCase();if(r){if(r instanceof Element||L(r,"String")){return he(e,t,null,null,{targetOverride:s(r),returnPromise:true})}else{return he(e,t,s(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:s(r.target),swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function qr(e){var t=[];while(e){t.push(e);e=e.parentElement}return t}function Hr(e,t,r){var n;var i;if(typeof URL==="function"){i=new URL(t,document.location.href);var a=document.location.origin;n=a===i.origin}else{i=t;n=g(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!n){return false}}return ce(e,"htmx:validateUrl",le({url:i,sameHost:n},r))}function he(t,r,n,i,a,e){var o=null;var s=null;a=a!=null?a:{};if(a.returnPromise&&typeof Promise!=="undefined"){var l=new Promise(function(e,t){o=e;s=t})}if(n==null){n=re().body}var M=a.handler||Ar;var D=a.select||null;if(!se(n)){ie(o);return l}var u=a.targetOverride||ge(n);if(u==null||u==ve){fe(n,"htmx:targetError",{target:te(n,"hx-target")});ie(s);return l}var f=ae(n);var c=f.lastButtonClicked;if(c){var h=ee(c,"formaction");if(h!=null){r=h}var v=ee(c,"formmethod");if(v!=null){if(v.toLowerCase()!=="dialog"){t=v}}}var d=ne(n,"hx-confirm");if(e===undefined){var X=function(e){return he(t,r,n,i,a,!!e)};var U={target:u,elt:n,path:r,verb:t,triggeringEvent:i,etc:a,issueRequest:X,question:d};if(ce(n,"htmx:confirm",U)===false){ie(o);return l}}var g=n;var m=ne(n,"hx-sync");var p=null;var y=false;if(m){var B=m.split(":");var F=B[0].trim();if(F==="this"){g=de(n,"hx-sync")}else{g=ue(n,F)}m=(B[1]||"drop").trim();f=ae(g);if(m==="drop"&&f.xhr&&f.abortable!==true){ie(o);return l}else if(m==="abort"){if(f.xhr){ie(o);return l}else{y=true}}else if(m==="replace"){ce(g,"htmx:abort")}else if(m.indexOf("queue")===0){var V=m.split(" ");p=(V[1]||"last").trim()}}if(f.xhr){if(f.abortable){ce(g,"htmx:abort")}else{if(p==null){if(i){var x=ae(i);if(x&&x.triggerSpec&&x.triggerSpec.queue){p=x.triggerSpec.queue}}if(p==null){p="last"}}if(f.queuedRequests==null){f.queuedRequests=[]}if(p==="first"&&f.queuedRequests.length===0){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(p==="all"){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(p==="last"){f.queuedRequests=[];f.queuedRequests.push(function(){he(t,r,n,i,a)})}ie(o);return l}}var b=new XMLHttpRequest;f.xhr=b;f.abortable=y;var w=function(){f.xhr=null;f.abortable=false;if(f.queuedRequests!=null&&f.queuedRequests.length>0){var e=f.queuedRequests.shift();e()}};var j=ne(n,"hx-prompt");if(j){var S=prompt(j);if(S===null||!ce(n,"htmx:prompt",{prompt:S,target:u})){ie(o);w();return l}}if(d&&!e){if(!confirm(d)){ie(o);w();return l}}var E=vr(n,u,S);if(t!=="get"&&!pr(n)){E["Content-Type"]="application/x-www-form-urlencoded"}if(a.headers){E=le(E,a.headers)}var _=ur(n,t);var C=_.errors;var T=_.values;if(a.values){T=le(T,a.values)}var z=Cr(n);var W=le(T,z);var R=dr(W,n);if(Q.config.getCacheBusterParam&&t==="get"){R["org.htmx.cache-buster"]=ee(u,"id")||"true"}if(r==null||r===""){r=re().location.href}var O=br(n,"hx-request");var $=ae(n).boosted;var q=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;var H={boosted:$,useUrlParams:q,parameters:R,unfilteredParameters:W,headers:E,target:u,verb:t,errors:C,withCredentials:a.credentials||O.credentials||Q.config.withCredentials,timeout:a.timeout||O.timeout||Q.config.timeout,path:r,triggeringEvent:i};if(!ce(n,"htmx:configRequest",H)){ie(o);w();return l}r=H.path;t=H.verb;E=H.headers;R=H.parameters;C=H.errors;q=H.useUrlParams;if(C&&C.length>0){ce(n,"htmx:validation:halted",H);ie(o);w();return l}var G=r.split("#");var J=G[0];var L=G[1];var A=r;if(q){A=J;var Z=Object.keys(R).length!==0;if(Z){if(A.indexOf("?")<0){A+="?"}else{A+="&"}A+=cr(R);if(L){A+="#"+L}}}if(!Hr(n,A,H)){fe(n,"htmx:invalidPath",H);ie(s);return l}b.open(t.toUpperCase(),A,true);b.overrideMimeType("text/html");b.withCredentials=H.withCredentials;b.timeout=H.timeout;if(O.noHeaders){}else{for(var N in E){if(E.hasOwnProperty(N)){var K=E[N];Tr(b,N,K)}}}var I={xhr:b,target:u,requestConfig:H,etc:a,boosted:$,select:D,pathInfo:{requestPath:r,finalRequestPath:A,anchor:L}};b.onload=function(){try{var e=qr(n);I.pathInfo.responsePath=Rr(b);M(n,I);nr(k,P);ce(n,"htmx:afterRequest",I);ce(n,"htmx:afterOnLoad",I);if(!se(n)){var t=null;while(e.length>0&&t==null){var r=e.shift();if(se(r)){t=r}}if(t){ce(t,"htmx:afterRequest",I);ce(t,"htmx:afterOnLoad",I)}}ie(o);w()}catch(e){fe(n,"htmx:onLoadError",le({error:e},I));throw e}};b.onerror=function(){nr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendError",I);ie(s);w()};b.onabort=function(){nr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendAbort",I);ie(s);w()};b.ontimeout=function(){nr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:timeout",I);ie(s);w()};if(!ce(n,"htmx:beforeRequest",I)){ie(o);w();return l}var k=tr(n);var P=rr(n);oe(["loadstart","loadend","progress","abort"],function(t){oe([b,b.upload],function(e){e.addEventListener(t,function(e){ce(n,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ce(n,"htmx:beforeSend",I);var Y=q?null:yr(b,n,R);b.send(Y);return l}function Lr(e,t){var r=t.xhr;var n=null;var i=null;if(O(r,/HX-Push:/i)){n=r.getResponseHeader("HX-Push");i="push"}else if(O(r,/HX-Push-Url:/i)){n=r.getResponseHeader("HX-Push-Url");i="push"}else if(O(r,/HX-Replace-Url:/i)){n=r.getResponseHeader("HX-Replace-Url");i="replace"}if(n){if(n==="false"){return{}}else{return{type:i,path:n}}}var a=t.pathInfo.finalRequestPath;var o=t.pathInfo.responsePath;var s=ne(e,"hx-push-url");var l=ne(e,"hx-replace-url");var u=ae(e).boosted;var f=null;var c=null;if(s){f="push";c=s}else if(l){f="replace";c=l}else if(u){f="push";c=o||a}if(c){if(c==="false"){return{}}if(c==="true"){c=o||a}if(t.pathInfo.anchor&&c.indexOf("#")===-1){c=c+"#"+t.pathInfo.anchor}return{type:f,path:c}}else{return{}}}function Ar(l,u){var f=u.xhr;var c=u.target;var e=u.etc;var t=u.requestConfig;var h=u.select;if(!ce(l,"htmx:beforeOnLoad",u))return;if(O(f,/HX-Trigger:/i)){Be(f,"HX-Trigger",l)}if(O(f,/HX-Location:/i)){Jt();var r=f.getResponseHeader("HX-Location");var v;if(r.indexOf("{")===0){v=E(r);r=v["path"];delete v["path"]}Or("GET",r,v).then(function(){Zt(r)});return}var n=O(f,/HX-Refresh:/i)&&"true"===f.getResponseHeader("HX-Refresh");if(O(f,/HX-Redirect:/i)){location.href=f.getResponseHeader("HX-Redirect");n&&location.reload();return}if(n){location.reload();return}if(O(f,/HX-Retarget:/i)){u.target=re().querySelector(f.getResponseHeader("HX-Retarget"))}var d=Lr(l,u);var i=f.status>=200&&f.status<400&&f.status!==204;var g=f.response;var a=f.status>=400;var m=Q.config.ignoreTitle;var o=le({shouldSwap:i,serverResponse:g,isError:a,ignoreTitle:m},u);if(!ce(c,"htmx:beforeSwap",o))return;c=o.target;g=o.serverResponse;a=o.isError;m=o.ignoreTitle;u.target=c;u.failed=a;u.successful=!a;if(o.shouldSwap){if(f.status===286){et(l)}T(l,function(e){g=e.transformResponse(g,f,l)});if(d.type){Jt()}var s=e.swapOverride;if(O(f,/HX-Reswap:/i)){s=f.getResponseHeader("HX-Reswap")}var v=mr(l,s);if(v.hasOwnProperty("ignoreTitle")){m=v.ignoreTitle}c.classList.add(Q.config.swappingClass);var p=null;var y=null;var x=function(){try{var e=document.activeElement;var t={};try{t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null}}catch(e){}var r;if(h){r=h}if(O(f,/HX-Reselect:/i)){r=f.getResponseHeader("HX-Reselect")}if(d.type){ce(re().body,"htmx:beforeHistoryUpdate",le({history:d},u));if(d.type==="push"){Zt(d.path);ce(re().body,"htmx:pushedIntoHistory",{path:d.path})}else{Kt(d.path);ce(re().body,"htmx:replacedInHistory",{path:d.path})}}var n=R(c);Ue(v.swapStyle,c,l,g,n,r);if(t.elt&&!se(t.elt)&&ee(t.elt,"id")){var i=document.getElementById(ee(t.elt,"id"));var a={preventScroll:v.focusScroll!==undefined?!v.focusScroll:!Q.config.defaultFocusScroll};if(i){if(t.start&&i.setSelectionRange){try{i.setSelectionRange(t.start,t.end)}catch(e){}}i.focus(a)}}c.classList.remove(Q.config.swappingClass);oe(n.elts,function(e){if(e.classList){e.classList.add(Q.config.settlingClass)}ce(e,"htmx:afterSwap",u)});if(O(f,/HX-Trigger-After-Swap:/i)){var o=l;if(!se(l)){o=re().body}Be(f,"HX-Trigger-After-Swap",o)}var s=function(){oe(n.tasks,function(e){e.call()});oe(n.elts,function(e){if(e.classList){e.classList.remove(Q.config.settlingClass)}ce(e,"htmx:afterSettle",u)});if(u.pathInfo.anchor){var e=re().getElementById(u.pathInfo.anchor);if(e){e.scrollIntoView({block:"start",behavior:"auto"})}}if(n.title&&!m){var t=C("title");if(t){t.innerHTML=n.title}else{window.document.title=n.title}}xr(n.elts,v);if(O(f,/HX-Trigger-After-Settle:/i)){var r=l;if(!se(l)){r=re().body}Be(f,"HX-Trigger-After-Settle",r)}ie(p)};if(v.settleDelay>0){setTimeout(s,v.settleDelay)}else{s()}}catch(e){fe(l,"htmx:swapError",u);ie(y);throw e}};var b=Q.config.globalViewTransitions;if(v.hasOwnProperty("transition")){b=v.transition}if(b&&ce(l,"htmx:beforeTransition",u)&&typeof Promise!=="undefined"&&document.startViewTransition){var w=new Promise(function(e,t){p=e;y=t});var S=x;x=function(){document.startViewTransition(function(){S();return w})}}if(v.swapDelay>0){setTimeout(x,v.swapDelay)}else{x()}}if(a){fe(l,"htmx:responseError",le({error:"Response Status Error Code "+f.status+" from "+u.pathInfo.requestPath},u))}}var Nr={};function Ir(){return{init:function(e){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function kr(e,t){if(t.init){t.init(r)}Nr[e]=le(Ir(),t)}function Pr(e){delete Nr[e]}function Mr(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=te(e,"hx-ext");if(t){oe(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Nr[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return Mr(u(e),r,n)}function Dr(e){var t=function(){if(!e)return;e();e=null};if(re().readyState==="complete"){t()}else{re().addEventListener("DOMContentLoaded",function(){t()});re().addEventListener("readystatechange",function(){if(re().readyState!=="complete")return;t()})}}function Xr(){if(Q.config.includeIndicatorStyles!==false){re().head.insertAdjacentHTML("beforeend","")}}function Ur(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function Br(){var e=Ur();if(e){Q.config=le(Q.config,e)}}Dr(function(){Br();Xr();var e=re().body;Bt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});var r=window.onpopstate;window.onpopstate=function(e){if(e.state&&e.state.htmx){er();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); \ No newline at end of file +var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=cn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true},parseInterval:null,_:null,version:"2.0.4"};Q.onLoad=j;Q.process=kt;Q.on=ye;Q.off=be;Q.trigger=he;Q.ajax=Rn;Q.find=u;Q.findAll=x;Q.closest=g;Q.remove=z;Q.addClass=K;Q.removeClass=G;Q.toggleClass=W;Q.takeClass=Z;Q.swap=$e;Q.defineExtension=Fn;Q.removeExtension=Bn;Q.logAll=V;Q.logNone=_;Q.parseInterval=d;Q._=e;const n={addTriggerHandler:St,bodyContains:le,canAccessLocalStorage:B,findThisElement:Se,filterValues:hn,swap:$e,hasAttribute:s,getAttributeValue:te,getClosestAttributeValue:re,getClosestMatch:o,getExpressionVars:En,getHeaders:fn,getInputValues:cn,getInternalData:ie,getSwapSpecification:gn,getTriggerSpecs:st,getTarget:Ee,makeFragment:P,mergeObjects:ce,makeSettleInfo:xn,oobSwap:He,querySelectorExt:ae,settleImmediately:Kt,shouldCancel:ht,triggerEvent:he,triggerErrorEvent:fe,withExtensions:Ft};const r=["get","post","put","delete","patch"];const H=r.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function c(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function ne(){return document}function m(e,t){return e.getRootNode?e.getRootNode({composed:t}):ne()}function o(e,t){while(e&&!t(e)){e=c(e)}return e||null}function i(e,t,n){const r=te(t,n);const o=te(t,"hx-disinherit");var i=te(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function re(t,n){let r=null;o(t,function(e){return!!(r=i(t,ue(e),n))});if(r!=="unset"){return r}}function h(e,t){const n=e instanceof Element&&(e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector);return!!n&&n.call(e,t)}function T(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function q(e){const t=new DOMParser;return t.parseFromString(e,"text/html")}function L(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function A(e){const t=ne().createElement("script");se(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function N(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function I(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(N(e)){const t=A(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){O(e)}finally{e.remove()}}})}function P(e){const t=e.replace(/]*)?>[\s\S]*?<\/head>/i,"");const n=T(t);let r;if(n==="html"){r=new DocumentFragment;const i=q(e);L(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=q(t);L(r,i.body);r.title=i.title}else{const i=q('");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){I(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function oe(e){if(e){e()}}function t(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return typeof e==="function"}function D(e){return t(e,"Object")}function ie(e){const t="htmx-internal-data";let n=e[t];if(!n){n=e[t]={}}return n}function M(t){const n=[];if(t){for(let e=0;e=0}function le(e){return e.getRootNode({composed:true})===document}function F(e){return e.trim().split(/\s+/)}function ce(e,t){for(const n in t){if(t.hasOwnProperty(n)){e[n]=t[n]}}return e}function S(e){try{return JSON.parse(e)}catch(e){O(e);return null}}function B(){const e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function U(t){try{const e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function e(e){return vn(ne().body,function(){return eval(e)})}function j(t){const e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,n){if(console){console.log(t,e,n)}}}function _(){Q.logger=null}function u(e,t){if(typeof e!=="string"){return e.querySelector(t)}else{return u(ne(),e)}}function x(e,t){if(typeof e!=="string"){return e.querySelectorAll(t)}else{return x(ne(),e)}}function E(){return window}function z(e,t){e=y(e);if(t){E().setTimeout(function(){z(e);e=null},t)}else{c(e).removeChild(e)}}function ue(e){return e instanceof Element?e:null}function $(e){return e instanceof HTMLElement?e:null}function J(e){return typeof e==="string"?e:null}function f(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function K(e,t,n){e=ue(y(e));if(!e){return}if(n){E().setTimeout(function(){K(e,t);e=null},n)}else{e.classList&&e.classList.add(t)}}function G(e,t,n){let r=ue(y(e));if(!r){return}if(n){E().setTimeout(function(){G(r,t);r=null},n)}else{if(r.classList){r.classList.remove(t);if(r.classList.length===0){r.removeAttribute("class")}}}}function W(e,t){e=y(e);e.classList.toggle(t)}function Z(e,t){e=y(e);se(e.parentElement.children,function(e){G(e,t)});K(ue(e),t)}function g(e,t){e=ue(y(e));if(e&&e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&ue(c(e)));return null}}function l(e,t){return e.substring(0,t.length)===t}function Y(e,t){return e.substring(e.length-t.length)===t}function ge(e){const t=e.trim();if(l(t,"<")&&Y(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function p(t,r,n){if(r.indexOf("global ")===0){return p(t,r.slice(7),true)}t=y(t);const o=[];{let t=0;let n=0;for(let e=0;e"){t--}}if(n0){const r=ge(o.shift());let e;if(r.indexOf("closest ")===0){e=g(ue(t),ge(r.substr(8)))}else if(r.indexOf("find ")===0){e=u(f(t),ge(r.substr(5)))}else if(r==="next"||r==="nextElementSibling"){e=ue(t).nextElementSibling}else if(r.indexOf("next ")===0){e=pe(t,ge(r.substr(5)),!!n)}else if(r==="previous"||r==="previousElementSibling"){e=ue(t).previousElementSibling}else if(r.indexOf("previous ")===0){e=me(t,ge(r.substr(9)),!!n)}else if(r==="document"){e=document}else if(r==="window"){e=window}else if(r==="body"){e=document.body}else if(r==="root"){e=m(t,!!n)}else if(r==="host"){e=t.getRootNode().host}else{s.push(r)}if(e){i.push(e)}}if(s.length>0){const e=s.join(",");const c=f(m(t,!!n));i.push(...M(c.querySelectorAll(e)))}return i}var pe=function(t,e,n){const r=f(m(t,n)).querySelectorAll(e);for(let e=0;e=0;e--){const o=r[e];if(o.compareDocumentPosition(t)===Node.DOCUMENT_POSITION_FOLLOWING){return o}}};function ae(e,t){if(typeof e!=="string"){return p(e,t)[0]}else{return p(ne().body,e)[0]}}function y(e,t){if(typeof e==="string"){return u(f(t)||document,e)}else{return e}}function xe(e,t,n,r){if(k(t)){return{target:ne().body,event:J(e),listener:t,options:n}}else{return{target:y(e),event:J(t),listener:n,options:r}}}function ye(t,n,r,o){Vn(function(){const e=xe(t,n,r,o);e.target.addEventListener(e.event,e.listener,e.options)});const e=k(n);return e?n:r}function be(t,n,r){Vn(function(){const e=xe(t,n,r);e.target.removeEventListener(e.event,e.listener)});return k(n)?n:r}const ve=ne().createElement("output");function we(e,t){const n=re(e,t);if(n){if(n==="this"){return[Se(e,t)]}else{const r=p(e,n);if(r.length===0){O('The selector "'+n+'" on '+t+" returned no matches!");return[ve]}else{return r}}}}function Se(e,t){return ue(o(e,function(e){return te(ue(e),t)!=null}))}function Ee(e){const t=re(e,"hx-target");if(t){if(t==="this"){return Se(e,"hx-target")}else{return ae(e,t)}}else{const n=ie(e);if(n.boosted){return ne().body}else{return e}}}function Ce(t){const n=Q.config.attributesToSettle;for(let e=0;e0){s=e.substring(0,e.indexOf(":"));n=e.substring(e.indexOf(":")+1)}else{s=e}o.removeAttribute("hx-swap-oob");o.removeAttribute("data-hx-swap-oob");const r=p(t,n,false);if(r){se(r,function(e){let t;const n=o.cloneNode(true);t=ne().createDocumentFragment();t.appendChild(n);if(!Re(s,e)){t=f(n)}const r={shouldSwap:true,target:e,fragment:t};if(!he(e,"htmx:oobBeforeSwap",r))return;e=r.target;if(r.shouldSwap){qe(t);_e(s,e,e,t,i);Te()}se(i.elts,function(e){he(e,"htmx:oobAfterSwap",r)})});o.parentNode.removeChild(o)}else{o.parentNode.removeChild(o);fe(ne().body,"htmx:oobErrorNoTarget",{content:o})}return e}function Te(){const e=u("#--htmx-preserve-pantry--");if(e){for(const t of[...e.children]){const n=u("#"+t.id);n.parentNode.moveBefore(t,n);n.remove()}e.remove()}}function qe(e){se(x(e,"[hx-preserve], [data-hx-preserve]"),function(e){const t=te(e,"id");const n=ne().getElementById(t);if(n!=null){if(e.moveBefore){let e=u("#--htmx-preserve-pantry--");if(e==null){ne().body.insertAdjacentHTML("afterend","
");e=u("#--htmx-preserve-pantry--")}e.moveBefore(n,null)}else{e.parentNode.replaceChild(n,e)}}})}function Le(l,e,c){se(e.querySelectorAll("[id]"),function(t){const n=ee(t,"id");if(n&&n.length>0){const r=n.replace("'","\\'");const o=t.tagName.replace(":","\\:");const e=f(l);const i=e&&e.querySelector(o+"[id='"+r+"']");if(i&&i!==e){const s=t.cloneNode();Oe(t,i);c.tasks.push(function(){Oe(t,s)})}}})}function Ae(e){return function(){G(e,Q.config.addedClass);kt(ue(e));Ne(f(e));he(e,"htmx:load")}}function Ne(e){const t="[autofocus]";const n=$(h(e,t)?e:e.querySelector(t));if(n!=null){n.focus()}}function a(e,t,n,r){Le(e,n,r);while(n.childNodes.length>0){const o=n.firstChild;K(ue(o),Q.config.addedClass);e.insertBefore(o,t);if(o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE){r.tasks.push(Ae(o))}}}function Ie(e,t){let n=0;while(n0}function $e(e,t,r,o){if(!o){o={}}e=y(e);const i=o.contextElement?m(o.contextElement,false):ne();const n=document.activeElement;let s={};try{s={elt:n,start:n?n.selectionStart:null,end:n?n.selectionEnd:null}}catch(e){}const l=xn(e);if(r.swapStyle==="textContent"){e.textContent=t}else{let n=P(t);l.title=n.title;if(o.selectOOB){const u=o.selectOOB.split(",");for(let t=0;t0){E().setTimeout(c,r.settleDelay)}else{c()}}function Je(e,t,n){const r=e.getResponseHeader(t);if(r.indexOf("{")===0){const o=S(r);for(const i in o){if(o.hasOwnProperty(i)){let e=o[i];if(D(e)){n=e.target!==undefined?e.target:n}else{e={value:e}}he(n,i,e)}}}else{const s=r.split(",");for(let e=0;e0){const s=o[0];if(s==="]"){e--;if(e===0){if(n===null){t=t+"true"}o.shift();t+=")})";try{const l=vn(r,function(){return Function(t)()},function(){return true});l.source=t;return l}catch(e){fe(ne().body,"htmx:syntax:error",{error:e,source:t});return null}}}else if(s==="["){e++}if(tt(s,n,i)){t+="(("+i+"."+s+") ? ("+i+"."+s+") : (window."+s+"))"}else{t=t+s}n=o.shift()}}}function C(e,t){let n="";while(e.length>0&&!t.test(e[0])){n+=e.shift()}return n}function rt(e){let t;if(e.length>0&&Ye.test(e[0])){e.shift();t=C(e,Qe).trim();e.shift()}else{t=C(e,v)}return t}const ot="input, textarea, select";function it(e,t,n){const r=[];const o=et(t);do{C(o,w);const l=o.length;const c=C(o,/[,\[\s]/);if(c!==""){if(c==="every"){const u={trigger:"every"};C(o,w);u.pollInterval=d(C(o,/[,\[\s]/));C(o,w);var i=nt(e,o,"event");if(i){u.eventFilter=i}r.push(u)}else{const a={trigger:c};var i=nt(e,o,"event");if(i){a.eventFilter=i}C(o,w);while(o.length>0&&o[0]!==","){const f=o.shift();if(f==="changed"){a.changed=true}else if(f==="once"){a.once=true}else if(f==="consume"){a.consume=true}else if(f==="delay"&&o[0]===":"){o.shift();a.delay=d(C(o,v))}else if(f==="from"&&o[0]===":"){o.shift();if(Ye.test(o[0])){var s=rt(o)}else{var s=C(o,v);if(s==="closest"||s==="find"||s==="next"||s==="previous"){o.shift();const h=rt(o);if(h.length>0){s+=" "+h}}}a.from=s}else if(f==="target"&&o[0]===":"){o.shift();a.target=rt(o)}else if(f==="throttle"&&o[0]===":"){o.shift();a.throttle=d(C(o,v))}else if(f==="queue"&&o[0]===":"){o.shift();a.queue=C(o,v)}else if(f==="root"&&o[0]===":"){o.shift();a[f]=rt(o)}else if(f==="threshold"&&o[0]===":"){o.shift();a[f]=C(o,v)}else{fe(e,"htmx:syntax:error",{token:o.shift()})}C(o,w)}r.push(a)}}if(o.length===l){fe(e,"htmx:syntax:error",{token:o.shift()})}C(o,w)}while(o[0]===","&&o.shift());if(n){n[t]=r}return r}function st(e){const t=te(e,"hx-trigger");let n=[];if(t){const r=Q.config.triggerSpecsCache;n=r&&r[t]||it(e,t,r)}if(n.length>0){return n}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,ot)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function lt(e){ie(e).cancelled=true}function ct(e,t,n){const r=ie(e);r.timeout=E().setTimeout(function(){if(le(e)&&r.cancelled!==true){if(!gt(n,e,Mt("hx:poll:trigger",{triggerSpec:n,target:e}))){t(e)}ct(e,t,n)}},n.pollInterval)}function ut(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function at(e){return g(e,Q.config.disableSelector)}function ft(t,n,e){if(t instanceof HTMLAnchorElement&&ut(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"&&String(ee(t,"method")).toLowerCase()!=="dialog"){n.boosted=true;let r,o;if(t.tagName==="A"){r="get";o=ee(t,"href")}else{const i=ee(t,"method");r=i?i.toLowerCase():"get";o=ee(t,"action");if(o==null||o===""){o=ne().location.href}if(r==="get"&&o.includes("?")){o=o.replace(/\?[^#]+/,"")}}e.forEach(function(e){pt(t,function(e,t){const n=ue(e);if(at(n)){b(n);return}de(r,o,n,t)},n,e,true)})}}function ht(e,t){const n=ue(t);if(!n){return false}if(e.type==="submit"||e.type==="click"){if(n.tagName==="FORM"){return true}if(h(n,'input[type="submit"], button')&&(h(n,"[form]")||g(n,"form")!==null)){return true}if(n instanceof HTMLAnchorElement&&n.href&&(n.getAttribute("href")==="#"||n.getAttribute("href").indexOf("#")!==0)){return true}}return false}function dt(e,t){return ie(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function gt(e,t,n){const r=e.eventFilter;if(r){try{return r.call(t,n)!==true}catch(e){const o=r.source;fe(ne().body,"htmx:eventFilter:error",{error:e,source:o});return true}}return false}function pt(l,c,e,u,a){const f=ie(l);let t;if(u.from){t=p(l,u.from)}else{t=[l]}if(u.changed){if(!("lastValue"in f)){f.lastValue=new WeakMap}t.forEach(function(e){if(!f.lastValue.has(u)){f.lastValue.set(u,new WeakMap)}f.lastValue.get(u).set(e,e.value)})}se(t,function(i){const s=function(e){if(!le(l)){i.removeEventListener(u.trigger,s);return}if(dt(l,e)){return}if(a||ht(e,l)){e.preventDefault()}if(gt(u,l,e)){return}const t=ie(e);t.triggerSpec=u;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(l)<0){t.handledFor.push(l);if(u.consume){e.stopPropagation()}if(u.target&&e.target){if(!h(ue(e.target),u.target)){return}}if(u.once){if(f.triggeredOnce){return}else{f.triggeredOnce=true}}if(u.changed){const n=event.target;const r=n.value;const o=f.lastValue.get(u);if(o.has(n)&&o.get(n)===r){return}o.set(n,r)}if(f.delayed){clearTimeout(f.delayed)}if(f.throttle){return}if(u.throttle>0){if(!f.throttle){he(l,"htmx:trigger");c(l,e);f.throttle=E().setTimeout(function(){f.throttle=null},u.throttle)}}else if(u.delay>0){f.delayed=E().setTimeout(function(){he(l,"htmx:trigger");c(l,e)},u.delay)}else{he(l,"htmx:trigger");c(l,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:u.trigger,listener:s,on:i});i.addEventListener(u.trigger,s)})}let mt=false;let xt=null;function yt(){if(!xt){xt=function(){mt=true};window.addEventListener("scroll",xt);window.addEventListener("resize",xt);setInterval(function(){if(mt){mt=false;se(ne().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){bt(e)})}},200)}}function bt(e){if(!s(e,"data-hx-revealed")&&X(e)){e.setAttribute("data-hx-revealed","true");const t=ie(e);if(t.initHash){he(e,"revealed")}else{e.addEventListener("htmx:afterProcessNode",function(){he(e,"revealed")},{once:true})}}}function vt(e,t,n,r){const o=function(){if(!n.loaded){n.loaded=true;he(e,"htmx:trigger");t(e)}};if(r>0){E().setTimeout(o,r)}else{o()}}function wt(t,n,e){let i=false;se(r,function(r){if(s(t,"hx-"+r)){const o=te(t,"hx-"+r);i=true;n.path=o;n.verb=r;e.forEach(function(e){St(t,e,n,function(e,t){const n=ue(e);if(g(n,Q.config.disableSelector)){b(n);return}de(r,o,n,t)})})}});return i}function St(r,e,t,n){if(e.trigger==="revealed"){yt();pt(r,n,t,e);bt(ue(r))}else if(e.trigger==="intersect"){const o={};if(e.root){o.root=ae(r,e.root)}if(e.threshold){o.threshold=parseFloat(e.threshold)}const i=new IntersectionObserver(function(t){for(let e=0;e0){t.polling=true;ct(ue(r),n,e)}else{pt(r,n,t,e)}}function Et(e){const t=ue(e);if(!t){return false}const n=t.attributes;for(let e=0;e", "+e).join(""));return o}else{return[]}}function Tt(e){const t=g(ue(e.target),"button, input[type='submit']");const n=Lt(e);if(n){n.lastButtonClicked=t}}function qt(e){const t=Lt(e);if(t){t.lastButtonClicked=null}}function Lt(e){const t=g(ue(e.target),"button, input[type='submit']");if(!t){return}const n=y("#"+ee(t,"form"),t.getRootNode())||g(t,"form");if(!n){return}return ie(n)}function At(e){e.addEventListener("click",Tt);e.addEventListener("focusin",Tt);e.addEventListener("focusout",qt)}function Nt(t,e,n){const r=ie(t);if(!Array.isArray(r.onHandlers)){r.onHandlers=[]}let o;const i=function(e){vn(t,function(){if(at(t)){return}if(!o){o=new Function("event",n)}o.call(t,e)})};t.addEventListener(e,i);r.onHandlers.push({event:e,listener:i})}function It(t){ke(t);for(let e=0;eQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(ne().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Vt(t){if(!B()){return null}t=U(t);const n=S(localStorage.getItem("htmx-history-cache"))||[];for(let e=0;e=200&&this.status<400){he(ne().body,"htmx:historyCacheMissLoad",i);const e=P(this.response);const t=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;const n=Ut();const r=xn(n);kn(e.title);qe(e);Ve(n,t,r);Te();Kt(r.tasks);Bt=o;he(ne().body,"htmx:historyRestore",{path:o,cacheMiss:true,serverResponse:this.response})}else{fe(ne().body,"htmx:historyCacheMissLoadError",i)}};e.send()}function Wt(e){zt();e=e||location.pathname+location.search;const t=Vt(e);if(t){const n=P(t.content);const r=Ut();const o=xn(r);kn(t.title);qe(n);Ve(r,n,o);Te();Kt(o.tasks);E().setTimeout(function(){window.scrollTo(0,t.scroll)},0);Bt=e;he(ne().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{Gt(e)}}}function Zt(e){let t=we(e,"hx-indicator");if(t==null){t=[e]}se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)+1;e.classList.add.call(e.classList,Q.config.requestClass)});return t}function Yt(e){let t=we(e,"hx-disabled-elt");if(t==null){t=[]}se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","");e.setAttribute("data-disabled-by-htmx","")});return t}function Qt(e,t){se(e.concat(t),function(e){const t=ie(e);t.requestCount=(t.requestCount||1)-1});se(e,function(e){const t=ie(e);if(t.requestCount===0){e.classList.remove.call(e.classList,Q.config.requestClass)}});se(t,function(e){const t=ie(e);if(t.requestCount===0){e.removeAttribute("disabled");e.removeAttribute("data-disabled-by-htmx")}})}function en(t,n){for(let e=0;en.indexOf(e)<0)}else{e=e.filter(e=>e!==n)}r.delete(t);se(e,e=>r.append(t,e))}}function on(t,n,r,o,i){if(o==null||en(t,o)){return}else{t.push(o)}if(tn(o)){const s=ee(o,"name");let e=o.value;if(o instanceof HTMLSelectElement&&o.multiple){e=M(o.querySelectorAll("option:checked")).map(function(e){return e.value})}if(o instanceof HTMLInputElement&&o.files){e=M(o.files)}nn(s,e,n);if(i){sn(o,r)}}if(o instanceof HTMLFormElement){se(o.elements,function(e){if(t.indexOf(e)>=0){rn(e.name,e.value,n)}else{t.push(e)}if(i){sn(e,r)}});new FormData(o).forEach(function(e,t){if(e instanceof File&&e.name===""){return}nn(t,e,n)})}}function sn(e,t){const n=e;if(n.willValidate){he(n,"htmx:validation:validate");if(!n.checkValidity()){t.push({elt:n,message:n.validationMessage,validity:n.validity});he(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})}}}function ln(n,e){for(const t of e.keys()){n.delete(t)}e.forEach(function(e,t){n.append(t,e)});return n}function cn(e,t){const n=[];const r=new FormData;const o=new FormData;const i=[];const s=ie(e);if(s.lastButtonClicked&&!le(s.lastButtonClicked)){s.lastButtonClicked=null}let l=e instanceof HTMLFormElement&&e.noValidate!==true||te(e,"hx-validate")==="true";if(s.lastButtonClicked){l=l&&s.lastButtonClicked.formNoValidate!==true}if(t!=="get"){on(n,o,i,g(e,"form"),l)}on(n,r,i,e,l);if(s.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&ee(e,"type")==="submit"){const u=s.lastButtonClicked||e;const a=ee(u,"name");nn(a,u.value,o)}const c=we(e,"hx-include");se(c,function(e){on(n,r,i,ue(e),l);if(!h(e,"form")){se(f(e).querySelectorAll(ot),function(e){on(n,r,i,e,l)})}});ln(r,o);return{errors:i,formData:r,values:An(r)}}function un(e,t,n){if(e!==""){e+="&"}if(String(n)==="[object Object]"){n=JSON.stringify(n)}const r=encodeURIComponent(n);e+=encodeURIComponent(t)+"="+r;return e}function an(e){e=qn(e);let n="";e.forEach(function(e,t){n=un(n,t,e)});return n}function fn(e,t,n){const r={"HX-Request":"true","HX-Trigger":ee(e,"id"),"HX-Trigger-Name":ee(e,"name"),"HX-Target":te(t,"id"),"HX-Current-URL":ne().location.href};bn(e,"hx-headers",false,r);if(n!==undefined){r["HX-Prompt"]=n}if(ie(e).boosted){r["HX-Boosted"]="true"}return r}function hn(n,e){const t=re(e,"hx-params");if(t){if(t==="none"){return new FormData}else if(t==="*"){return n}else if(t.indexOf("not ")===0){se(t.slice(4).split(","),function(e){e=e.trim();n.delete(e)});return n}else{const r=new FormData;se(t.split(","),function(t){t=t.trim();if(n.has(t)){n.getAll(t).forEach(function(e){r.append(t,e)})}});return r}}else{return n}}function dn(e){return!!ee(e,"href")&&ee(e,"href").indexOf("#")>=0}function gn(e,t){const n=t||re(e,"hx-swap");const r={swapStyle:ie(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ie(e).boosted&&!dn(e)){r.show="top"}if(n){const s=F(n);if(s.length>0){for(let e=0;e0?o.join(":"):null;r.scroll=u;r.scrollTarget=i}else if(l.indexOf("show:")===0){const a=l.slice(5);var o=a.split(":");const f=o.pop();var i=o.length>0?o.join(":"):null;r.show=f;r.showTarget=i}else if(l.indexOf("focus-scroll:")===0){const h=l.slice("focus-scroll:".length);r.focusScroll=h=="true"}else if(e==0){r.swapStyle=l}else{O("Unknown modifier in hx-swap: "+l)}}}}return r}function pn(e){return re(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function mn(t,n,r){let o=null;Ft(n,function(e){if(o==null){o=e.encodeParameters(t,r,n)}});if(o!=null){return o}else{if(pn(n)){return ln(new FormData,qn(r))}else{return an(r)}}}function xn(e){return{tasks:[],elts:[e]}}function yn(e,t){const n=e[0];const r=e[e.length-1];if(t.scroll){var o=null;if(t.scrollTarget){o=ue(ae(n,t.scrollTarget))}if(t.scroll==="top"&&(n||o)){o=o||n;o.scrollTop=0}if(t.scroll==="bottom"&&(r||o)){o=o||r;o.scrollTop=o.scrollHeight}}if(t.show){var o=null;if(t.showTarget){let e=t.showTarget;if(t.showTarget==="window"){e="body"}o=ue(ae(n,e))}if(t.show==="top"&&(n||o)){o=o||n;o.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(r||o)){o=o||r;o.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function bn(r,e,o,i){if(i==null){i={}}if(r==null){return i}const s=te(r,e);if(s){let e=s.trim();let t=o;if(e==="unset"){return null}if(e.indexOf("javascript:")===0){e=e.slice(11);t=true}else if(e.indexOf("js:")===0){e=e.slice(3);t=true}if(e.indexOf("{")!==0){e="{"+e+"}"}let n;if(t){n=vn(r,function(){return Function("return ("+e+")")()},{})}else{n=S(e)}for(const l in n){if(n.hasOwnProperty(l)){if(i[l]==null){i[l]=n[l]}}}}return bn(ue(c(r)),e,o,i)}function vn(e,t,n){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return n}}function wn(e,t){return bn(e,"hx-vars",true,t)}function Sn(e,t){return bn(e,"hx-vals",false,t)}function En(e){return ce(wn(e),Sn(e))}function Cn(t,n,r){if(r!==null){try{t.setRequestHeader(n,r)}catch(e){t.setRequestHeader(n,encodeURIComponent(r));t.setRequestHeader(n+"-URI-AutoEncoded","true")}}}function On(t){if(t.responseURL&&typeof URL!=="undefined"){try{const e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(ne().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function R(e,t){return t.test(e.getAllResponseHeaders())}function Rn(t,n,r){t=t.toLowerCase();if(r){if(r instanceof Element||typeof r==="string"){return de(t,n,null,null,{targetOverride:y(r)||ve,returnPromise:true})}else{let e=y(r.target);if(r.target&&!e||r.source&&!e&&!y(r.source)){e=ve}return de(t,n,y(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:e,swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return de(t,n,null,null,{returnPromise:true})}}function Hn(e){const t=[];while(e){t.push(e);e=e.parentElement}return t}function Tn(e,t,n){let r;let o;if(typeof URL==="function"){o=new URL(t,document.location.href);const i=document.location.origin;r=i===o.origin}else{o=t;r=l(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!r){return false}}return he(e,"htmx:validateUrl",ce({url:o,sameHost:r},n))}function qn(e){if(e instanceof FormData)return e;const t=new FormData;for(const n in e){if(e.hasOwnProperty(n)){if(e[n]&&typeof e[n].forEach==="function"){e[n].forEach(function(e){t.append(n,e)})}else if(typeof e[n]==="object"&&!(e[n]instanceof Blob)){t.append(n,JSON.stringify(e[n]))}else{t.append(n,e[n])}}}return t}function Ln(r,o,e){return new Proxy(e,{get:function(t,e){if(typeof e==="number")return t[e];if(e==="length")return t.length;if(e==="push"){return function(e){t.push(e);r.append(o,e)}}if(typeof t[e]==="function"){return function(){t[e].apply(t,arguments);r.delete(o);t.forEach(function(e){r.append(o,e)})}}if(t[e]&&t[e].length===1){return t[e][0]}else{return t[e]}},set:function(e,t,n){e[t]=n;r.delete(o);e.forEach(function(e){r.append(o,e)});return true}})}function An(o){return new Proxy(o,{get:function(e,t){if(typeof t==="symbol"){const r=Reflect.get(e,t);if(typeof r==="function"){return function(){return r.apply(o,arguments)}}else{return r}}if(t==="toJSON"){return()=>Object.fromEntries(o)}if(t in e){if(typeof e[t]==="function"){return function(){return o[t].apply(o,arguments)}}else{return e[t]}}const n=o.getAll(t);if(n.length===0){return undefined}else if(n.length===1){return n[0]}else{return Ln(e,t,n)}},set:function(t,n,e){if(typeof n!=="string"){return false}t.delete(n);if(e&&typeof e.forEach==="function"){e.forEach(function(e){t.append(n,e)})}else if(typeof e==="object"&&!(e instanceof Blob)){t.append(n,JSON.stringify(e))}else{t.append(n,e)}return true},deleteProperty:function(e,t){if(typeof t==="string"){e.delete(t)}return true},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function de(t,n,r,o,i,D){let s=null;let l=null;i=i!=null?i:{};if(i.returnPromise&&typeof Promise!=="undefined"){var e=new Promise(function(e,t){s=e;l=t})}if(r==null){r=ne().body}const M=i.handler||Dn;const X=i.select||null;if(!le(r)){oe(s);return e}const c=i.targetOverride||ue(Ee(r));if(c==null||c==ve){fe(r,"htmx:targetError",{target:te(r,"hx-target")});oe(l);return e}let u=ie(r);const a=u.lastButtonClicked;if(a){const L=ee(a,"formaction");if(L!=null){n=L}const A=ee(a,"formmethod");if(A!=null){if(A.toLowerCase()!=="dialog"){t=A}}}const f=re(r,"hx-confirm");if(D===undefined){const K=function(e){return de(t,n,r,o,i,!!e)};const G={target:c,elt:r,path:n,verb:t,triggeringEvent:o,etc:i,issueRequest:K,question:f};if(he(r,"htmx:confirm",G)===false){oe(s);return e}}let h=r;let d=re(r,"hx-sync");let g=null;let F=false;if(d){const N=d.split(":");const I=N[0].trim();if(I==="this"){h=Se(r,"hx-sync")}else{h=ue(ae(r,I))}d=(N[1]||"drop").trim();u=ie(h);if(d==="drop"&&u.xhr&&u.abortable!==true){oe(s);return e}else if(d==="abort"){if(u.xhr){oe(s);return e}else{F=true}}else if(d==="replace"){he(h,"htmx:abort")}else if(d.indexOf("queue")===0){const W=d.split(" ");g=(W[1]||"last").trim()}}if(u.xhr){if(u.abortable){he(h,"htmx:abort")}else{if(g==null){if(o){const P=ie(o);if(P&&P.triggerSpec&&P.triggerSpec.queue){g=P.triggerSpec.queue}}if(g==null){g="last"}}if(u.queuedRequests==null){u.queuedRequests=[]}if(g==="first"&&u.queuedRequests.length===0){u.queuedRequests.push(function(){de(t,n,r,o,i)})}else if(g==="all"){u.queuedRequests.push(function(){de(t,n,r,o,i)})}else if(g==="last"){u.queuedRequests=[];u.queuedRequests.push(function(){de(t,n,r,o,i)})}oe(s);return e}}const p=new XMLHttpRequest;u.xhr=p;u.abortable=F;const m=function(){u.xhr=null;u.abortable=false;if(u.queuedRequests!=null&&u.queuedRequests.length>0){const e=u.queuedRequests.shift();e()}};const B=re(r,"hx-prompt");if(B){var x=prompt(B);if(x===null||!he(r,"htmx:prompt",{prompt:x,target:c})){oe(s);m();return e}}if(f&&!D){if(!confirm(f)){oe(s);m();return e}}let y=fn(r,c,x);if(t!=="get"&&!pn(r)){y["Content-Type"]="application/x-www-form-urlencoded"}if(i.headers){y=ce(y,i.headers)}const U=cn(r,t);let b=U.errors;const j=U.formData;if(i.values){ln(j,qn(i.values))}const V=qn(En(r));const v=ln(j,V);let w=hn(v,r);if(Q.config.getCacheBusterParam&&t==="get"){w.set("org.htmx.cache-buster",ee(c,"id")||"true")}if(n==null||n===""){n=ne().location.href}const S=bn(r,"hx-request");const _=ie(r).boosted;let E=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;const C={boosted:_,useUrlParams:E,formData:w,parameters:An(w),unfilteredFormData:v,unfilteredParameters:An(v),headers:y,target:c,verb:t,errors:b,withCredentials:i.credentials||S.credentials||Q.config.withCredentials,timeout:i.timeout||S.timeout||Q.config.timeout,path:n,triggeringEvent:o};if(!he(r,"htmx:configRequest",C)){oe(s);m();return e}n=C.path;t=C.verb;y=C.headers;w=qn(C.parameters);b=C.errors;E=C.useUrlParams;if(b&&b.length>0){he(r,"htmx:validation:halted",C);oe(s);m();return e}const z=n.split("#");const $=z[0];const O=z[1];let R=n;if(E){R=$;const Z=!w.keys().next().done;if(Z){if(R.indexOf("?")<0){R+="?"}else{R+="&"}R+=an(w);if(O){R+="#"+O}}}if(!Tn(r,R,C)){fe(r,"htmx:invalidPath",C);oe(l);return e}p.open(t.toUpperCase(),R,true);p.overrideMimeType("text/html");p.withCredentials=C.withCredentials;p.timeout=C.timeout;if(S.noHeaders){}else{for(const k in y){if(y.hasOwnProperty(k)){const Y=y[k];Cn(p,k,Y)}}}const H={xhr:p,target:c,requestConfig:C,etc:i,boosted:_,select:X,pathInfo:{requestPath:n,finalRequestPath:R,responsePath:null,anchor:O}};p.onload=function(){try{const t=Hn(r);H.pathInfo.responsePath=On(p);M(r,H);if(H.keepIndicators!==true){Qt(T,q)}he(r,"htmx:afterRequest",H);he(r,"htmx:afterOnLoad",H);if(!le(r)){let e=null;while(t.length>0&&e==null){const n=t.shift();if(le(n)){e=n}}if(e){he(e,"htmx:afterRequest",H);he(e,"htmx:afterOnLoad",H)}}oe(s);m()}catch(e){fe(r,"htmx:onLoadError",ce({error:e},H));throw e}};p.onerror=function(){Qt(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendError",H);oe(l);m()};p.onabort=function(){Qt(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendAbort",H);oe(l);m()};p.ontimeout=function(){Qt(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:timeout",H);oe(l);m()};if(!he(r,"htmx:beforeRequest",H)){oe(s);m();return e}var T=Zt(r);var q=Yt(r);se(["loadstart","loadend","progress","abort"],function(t){se([p,p.upload],function(e){e.addEventListener(t,function(e){he(r,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});he(r,"htmx:beforeSend",H);const J=E?null:mn(p,r,w);p.send(J);return e}function Nn(e,t){const n=t.xhr;let r=null;let o=null;if(R(n,/HX-Push:/i)){r=n.getResponseHeader("HX-Push");o="push"}else if(R(n,/HX-Push-Url:/i)){r=n.getResponseHeader("HX-Push-Url");o="push"}else if(R(n,/HX-Replace-Url:/i)){r=n.getResponseHeader("HX-Replace-Url");o="replace"}if(r){if(r==="false"){return{}}else{return{type:o,path:r}}}const i=t.pathInfo.finalRequestPath;const s=t.pathInfo.responsePath;const l=re(e,"hx-push-url");const c=re(e,"hx-replace-url");const u=ie(e).boosted;let a=null;let f=null;if(l){a="push";f=l}else if(c){a="replace";f=c}else if(u){a="push";f=s||i}if(f){if(f==="false"){return{}}if(f==="true"){f=s||i}if(t.pathInfo.anchor&&f.indexOf("#")===-1){f=f+"#"+t.pathInfo.anchor}return{type:a,path:f}}else{return{}}}function In(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function Pn(e){for(var t=0;t0){E().setTimeout(e,x.swapDelay)}else{e()}}if(f){fe(o,"htmx:responseError",ce({error:"Response Status Error Code "+s.status+" from "+i.pathInfo.requestPath},i))}}const Mn={};function Xn(){return{init:function(e){return null},getSelectors:function(){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,n){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,n,r){return false},encodeParameters:function(e,t,n){return null}}}function Fn(e,t){if(t.init){t.init(n)}Mn[e]=ce(Xn(),t)}function Bn(e){delete Mn[e]}function Un(e,n,r){if(n==undefined){n=[]}if(e==undefined){return n}if(r==undefined){r=[]}const t=te(e,"hx-ext");if(t){se(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){r.push(e.slice(7));return}if(r.indexOf(e)<0){const t=Mn[e];if(t&&n.indexOf(t)<0){n.push(t)}}})}return Un(ue(c(e)),n,r)}var jn=false;ne().addEventListener("DOMContentLoaded",function(){jn=true});function Vn(e){if(jn||ne().readyState==="complete"){e()}else{ne().addEventListener("DOMContentLoaded",e)}}function _n(){if(Q.config.includeIndicatorStyles!==false){const e=Q.config.inlineStyleNonce?` nonce="${Q.config.inlineStyleNonce}"`:"";ne().head.insertAdjacentHTML("beforeend"," ."+Q.config.indicatorClass+"{opacity:0} ."+Q.config.requestClass+" ."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ."+Q.config.requestClass+"."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ")}}function zn(){const e=ne().querySelector('meta[name="htmx-config"]');if(e){return S(e.content)}else{return null}}function $n(){const e=zn();if(e){Q.config=ce(Q.config,e)}}Vn(function(){$n();_n();let e=ne().body;kt(e);const t=ne().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){const t=e.target;const n=ie(t);if(n&&n.xhr){n.xhr.abort()}});const n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){Wt();se(t,function(e){he(e,"htmx:restored",{document:ne(),triggerEvent:he})})}else{if(n){n(e)}}};E().setTimeout(function(){he(e,"htmx:load",{});e=null},0)});return Q}(); \ No newline at end of file diff --git a/demo/HtmxAppServer/wwwroot/lib/pico.css b/demo/HtmxAppServer/wwwroot/lib/pico.css index 2b6a365..567927e 100644 --- a/demo/HtmxAppServer/wwwroot/lib/pico.css +++ b/demo/HtmxAppServer/wwwroot/lib/pico.css @@ -1,138 +1,91 @@ @charset "UTF-8"; /*! - * Pico CSS v1.5.11 (https://picocss.com) - * Copyright 2019-2023 - Licensed under MIT + * Pico CSS ✨ v2.1.1 (https://picocss.com) + * Copyright 2019-2025 - Licensed under MIT */ /** - * Theme: default + * Styles */ -:root { - --font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu", - "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol", "Noto Color Emoji"; - --line-height: 1.5; - --font-weight: 400; - --font-size: 16px; - --border-radius: 0.25rem; - --border-width: 1px; - --outline-width: 3px; - --spacing: 1rem; - --typography-spacing-vertical: 1.5rem; - --block-spacing-vertical: calc(var(--spacing) * 2); - --block-spacing-horizontal: var(--spacing); - --grid-spacing-vertical: 0; - --grid-spacing-horizontal: var(--spacing); - --form-element-spacing-vertical: 0.75rem; - --form-element-spacing-horizontal: 1rem; - --nav-element-spacing-vertical: 1rem; - --nav-element-spacing-horizontal: 0.5rem; - --nav-link-spacing-vertical: 0.5rem; - --nav-link-spacing-horizontal: 0.5rem; - --form-label-font-weight: var(--font-weight); - --transition: 0.2s ease-in-out; - --modal-overlay-backdrop-filter: blur(0.25rem); +:root, +:host { + --pico-font-family-emoji: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --pico-font-family-sans-serif: system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, Helvetica, Arial, "Helvetica Neue", sans-serif, var(--pico-font-family-emoji); + --pico-font-family-monospace: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace, var(--pico-font-family-emoji); + --pico-font-family: var(--pico-font-family-sans-serif); + --pico-line-height: 1.5; + --pico-font-weight: 400; + --pico-font-size: 100%; + --pico-text-underline-offset: 0.1rem; + --pico-border-radius: 0.25rem; + --pico-border-width: 0.0625rem; + --pico-outline-width: 0.125rem; + --pico-transition: 0.2s ease-in-out; + --pico-spacing: 1rem; + --pico-typography-spacing-vertical: 1rem; + --pico-block-spacing-vertical: var(--pico-spacing); + --pico-block-spacing-horizontal: var(--pico-spacing); + --pico-grid-column-gap: var(--pico-spacing); + --pico-grid-row-gap: var(--pico-spacing); + --pico-form-element-spacing-vertical: 0.75rem; + --pico-form-element-spacing-horizontal: 1rem; + --pico-group-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-group-box-shadow-focus-with-button: 0 0 0 var(--pico-outline-width) var(--pico-primary-focus); + --pico-group-box-shadow-focus-with-input: 0 0 0 0.0625rem var(--pico-form-element-border-color); + --pico-modal-overlay-backdrop-filter: blur(0.375rem); + --pico-nav-element-spacing-vertical: 1rem; + --pico-nav-element-spacing-horizontal: 0.5rem; + --pico-nav-link-spacing-vertical: 0.5rem; + --pico-nav-link-spacing-horizontal: 0.5rem; + --pico-nav-breadcrumb-divider: ">"; + --pico-icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + --pico-icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); + --pico-icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --pico-icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); + --pico-icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); + --pico-icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); + --pico-icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); + --pico-icon-loading: url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E"); } @media (min-width: 576px) { - :root { - --font-size: 17px; + :root, + :host { + --pico-font-size: 106.25%; } } @media (min-width: 768px) { - :root { - --font-size: 18px; + :root, + :host { + --pico-font-size: 112.5%; } } -@media (min-width: 992px) { - :root { - --font-size: 19px; +@media (min-width: 1024px) { + :root, + :host { + --pico-font-size: 118.75%; } } -@media (min-width: 1200px) { - :root { - --font-size: 20px; +@media (min-width: 1280px) { + :root, + :host { + --pico-font-size: 125%; } } - -@media (min-width: 576px) { - body > header, - body > main, - body > footer, - section { - --block-spacing-vertical: calc(var(--spacing) * 2.5); - } -} -@media (min-width: 768px) { - body > header, - body > main, - body > footer, - section { - --block-spacing-vertical: calc(var(--spacing) * 3); - } -} -@media (min-width: 992px) { - body > header, - body > main, - body > footer, - section { - --block-spacing-vertical: calc(var(--spacing) * 3.5); - } -} -@media (min-width: 1200px) { - body > header, - body > main, - body > footer, - section { - --block-spacing-vertical: calc(var(--spacing) * 4); - } -} - -@media (min-width: 576px) { - article { - --block-spacing-horizontal: calc(var(--spacing) * 1.25); - } -} -@media (min-width: 768px) { - article { - --block-spacing-horizontal: calc(var(--spacing) * 1.5); - } -} -@media (min-width: 992px) { - article { - --block-spacing-horizontal: calc(var(--spacing) * 1.75); - } -} -@media (min-width: 1200px) { - article { - --block-spacing-horizontal: calc(var(--spacing) * 2); - } -} - -dialog > article { - --block-spacing-vertical: calc(var(--spacing) * 2); - --block-spacing-horizontal: var(--spacing); -} -@media (min-width: 576px) { - dialog > article { - --block-spacing-vertical: calc(var(--spacing) * 2.5); - --block-spacing-horizontal: calc(var(--spacing) * 1.25); - } -} -@media (min-width: 768px) { - dialog > article { - --block-spacing-vertical: calc(var(--spacing) * 3); - --block-spacing-horizontal: calc(var(--spacing) * 1.5); +@media (min-width: 1536px) { + :root, + :host { + --pico-font-size: 131.25%; } } a { - --text-decoration: none; + --pico-text-decoration: underline; } a.secondary, a.contrast { - --text-decoration: underline; + --pico-text-decoration: underline; } small { - --font-size: 0.875em; + --pico-font-size: 0.875em; } h1, @@ -141,391 +94,533 @@ h3, h4, h5, h6 { - --font-weight: 700; + --pico-font-weight: 700; } h1 { - --font-size: 2rem; - --typography-spacing-vertical: 3rem; + --pico-font-size: 2rem; + --pico-line-height: 1.125; + --pico-typography-spacing-top: 3rem; } h2 { - --font-size: 1.75rem; - --typography-spacing-vertical: 2.625rem; + --pico-font-size: 1.75rem; + --pico-line-height: 1.15; + --pico-typography-spacing-top: 2.625rem; } h3 { - --font-size: 1.5rem; - --typography-spacing-vertical: 2.25rem; + --pico-font-size: 1.5rem; + --pico-line-height: 1.175; + --pico-typography-spacing-top: 2.25rem; } h4 { - --font-size: 1.25rem; - --typography-spacing-vertical: 1.874rem; + --pico-font-size: 1.25rem; + --pico-line-height: 1.2; + --pico-typography-spacing-top: 1.874rem; } h5 { - --font-size: 1.125rem; - --typography-spacing-vertical: 1.6875rem; -} - -[type=checkbox], -[type=radio] { - --border-width: 2px; + --pico-font-size: 1.125rem; + --pico-line-height: 1.225; + --pico-typography-spacing-top: 1.6875rem; } -[type=checkbox][role=switch] { - --border-width: 3px; +h6 { + --pico-font-size: 1rem; + --pico-line-height: 1.25; + --pico-typography-spacing-top: 1.5rem; } thead th, thead td, tfoot th, tfoot td { - --border-width: 3px; -} - -:not(thead, tfoot) > * > td { - --font-size: 0.875em; + --pico-font-weight: 600; + --pico-border-width: 0.1875rem; } pre, code, kbd, samp { - --font-family: "Menlo", "Consolas", "Roboto Mono", "Ubuntu Monospace", - "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --pico-font-family: var(--pico-font-family-monospace); } kbd { - --font-weight: bolder; + --pico-font-weight: bolder; +} + +input:not([type=submit], +[type=button], +[type=reset], +[type=checkbox], +[type=radio], +[type=file]), +:where(select, textarea) { + --pico-outline-width: 0.0625rem; +} + +[type=search] { + --pico-border-radius: 5rem; +} + +[type=checkbox], +[type=radio] { + --pico-border-width: 0.125rem; +} + +[type=checkbox][role=switch] { + --pico-border-width: 0.1875rem; +} + +details.dropdown summary:not([role=button]) { + --pico-outline-width: 0.0625rem; +} + +nav details.dropdown summary:focus-visible { + --pico-outline-width: 0.125rem; +} + +[role=search] { + --pico-border-radius: 5rem; +} + +[role=search]:has(button.secondary:focus, +[type=submit].secondary:focus, +[type=button].secondary:focus, +[role=button].secondary:focus), +[role=group]:has(button.secondary:focus, +[type=submit].secondary:focus, +[type=button].secondary:focus, +[role=button].secondary:focus) { + --pico-group-box-shadow-focus-with-button: 0 0 0 var(--pico-outline-width) var(--pico-secondary-focus); +} +[role=search]:has(button.contrast:focus, +[type=submit].contrast:focus, +[type=button].contrast:focus, +[role=button].contrast:focus), +[role=group]:has(button.contrast:focus, +[type=submit].contrast:focus, +[type=button].contrast:focus, +[role=button].contrast:focus) { + --pico-group-box-shadow-focus-with-button: 0 0 0 var(--pico-outline-width) var(--pico-contrast-focus); +} +[role=search] button, +[role=search] [type=submit], +[role=search] [type=button], +[role=search] [role=button], +[role=group] button, +[role=group] [type=submit], +[role=group] [type=button], +[role=group] [role=button] { + --pico-form-element-spacing-horizontal: 2rem; +} + +details summary[role=button]:not(.outline)::after { + filter: brightness(0) invert(1); +} + +[aria-busy=true]:not(input, select, textarea):is(button, [type=submit], [type=button], [type=reset], [role=button]):not(.outline)::before { + filter: brightness(0) invert(1); } +/** + * Color schemes + */ [data-theme=light], -:root:not([data-theme=dark]) { - --background-color: #fff; - --color: hsl(205, 20%, 32%); - --h1-color: hsl(205, 30%, 15%); - --h2-color: #24333e; - --h3-color: hsl(205, 25%, 23%); - --h4-color: #374956; - --h5-color: hsl(205, 20%, 32%); - --h6-color: #4d606d; - --muted-color: hsl(205, 10%, 50%); - --muted-border-color: hsl(205, 20%, 94%); - --primary: hsl(195, 85%, 41%); - --primary-hover: hsl(195, 90%, 32%); - --primary-focus: rgba(16, 149, 193, 0.125); - --primary-inverse: #fff; - --secondary: hsl(205, 15%, 41%); - --secondary-hover: hsl(205, 20%, 32%); - --secondary-focus: rgba(89, 107, 120, 0.125); - --secondary-inverse: #fff; - --contrast: hsl(205, 30%, 15%); - --contrast-hover: #000; - --contrast-focus: rgba(89, 107, 120, 0.125); - --contrast-inverse: #fff; - --mark-background-color: #fff2ca; - --mark-color: #543a26; - --ins-color: #388e3c; - --del-color: #c62828; - --blockquote-border-color: var(--muted-border-color); - --blockquote-footer-color: var(--muted-color); - --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); - --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); - --form-element-background-color: transparent; - --form-element-border-color: hsl(205, 14%, 68%); - --form-element-color: var(--color); - --form-element-placeholder-color: var(--muted-color); - --form-element-active-background-color: transparent; - --form-element-active-border-color: var(--primary); - --form-element-focus-color: var(--primary-focus); - --form-element-disabled-background-color: hsl(205, 18%, 86%); - --form-element-disabled-border-color: hsl(205, 14%, 68%); - --form-element-disabled-opacity: 0.5; - --form-element-invalid-border-color: #c62828; - --form-element-invalid-active-border-color: #d32f2f; - --form-element-invalid-focus-color: rgba(211, 47, 47, 0.125); - --form-element-valid-border-color: #388e3c; - --form-element-valid-active-border-color: #43a047; - --form-element-valid-focus-color: rgba(67, 160, 71, 0.125); - --switch-background-color: hsl(205, 16%, 77%); - --switch-color: var(--primary-inverse); - --switch-checked-background-color: var(--primary); - --range-border-color: hsl(205, 18%, 86%); - --range-active-border-color: hsl(205, 16%, 77%); - --range-thumb-border-color: var(--background-color); - --range-thumb-color: var(--secondary); - --range-thumb-hover-color: var(--secondary-hover); - --range-thumb-active-color: var(--primary); - --table-border-color: var(--muted-border-color); - --table-row-stripped-background-color: #f6f8f9; - --code-background-color: hsl(205, 20%, 94%); - --code-color: var(--muted-color); - --code-kbd-background-color: var(--contrast); - --code-kbd-color: var(--contrast-inverse); - --code-tag-color: hsl(330, 40%, 50%); - --code-property-color: hsl(185, 40%, 40%); - --code-value-color: hsl(40, 20%, 50%); - --code-comment-color: hsl(205, 14%, 68%); - --accordion-border-color: var(--muted-border-color); - --accordion-close-summary-color: var(--color); - --accordion-open-summary-color: var(--muted-color); - --card-background-color: var(--background-color); - --card-border-color: var(--muted-border-color); - --card-box-shadow: - 0.0145rem 0.029rem 0.174rem rgba(27, 40, 50, 0.01698), - 0.0335rem 0.067rem 0.402rem rgba(27, 40, 50, 0.024), - 0.0625rem 0.125rem 0.75rem rgba(27, 40, 50, 0.03), - 0.1125rem 0.225rem 1.35rem rgba(27, 40, 50, 0.036), - 0.2085rem 0.417rem 2.502rem rgba(27, 40, 50, 0.04302), - 0.5rem 1rem 6rem rgba(27, 40, 50, 0.06), - 0 0 0 0.0625rem rgba(27, 40, 50, 0.015); - --card-sectionning-background-color: #fbfbfc; - --dropdown-background-color: #fbfbfc; - --dropdown-border-color: #e1e6eb; - --dropdown-box-shadow: var(--card-box-shadow); - --dropdown-color: var(--color); - --dropdown-hover-background-color: hsl(205, 20%, 94%); - --modal-overlay-background-color: rgba(213, 220, 226, 0.7); - --progress-background-color: hsl(205, 18%, 86%); - --progress-color: var(--primary); - --loading-spinner-opacity: 0.5; - --tooltip-background-color: var(--contrast); - --tooltip-color: var(--contrast-inverse); - --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); - --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); - --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(198, 40, 40)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); - --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); - --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); - --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(56, 142, 60)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); +:root:not([data-theme=dark]), +:host(:not([data-theme=dark])) { color-scheme: light; + --pico-background-color: #fff; + --pico-color: #373c44; + --pico-text-selection-color: rgba(2, 154, 232, 0.25); + --pico-muted-color: #646b79; + --pico-muted-border-color: rgb(231, 234, 239.5); + --pico-primary: #0172ad; + --pico-primary-background: #0172ad; + --pico-primary-border: var(--pico-primary-background); + --pico-primary-underline: rgba(1, 114, 173, 0.5); + --pico-primary-hover: #015887; + --pico-primary-hover-background: #02659a; + --pico-primary-hover-border: var(--pico-primary-hover-background); + --pico-primary-hover-underline: var(--pico-primary-hover); + --pico-primary-focus: rgba(2, 154, 232, 0.5); + --pico-primary-inverse: #fff; + --pico-secondary: #5d6b89; + --pico-secondary-background: #525f7a; + --pico-secondary-border: var(--pico-secondary-background); + --pico-secondary-underline: rgba(93, 107, 137, 0.5); + --pico-secondary-hover: #48536b; + --pico-secondary-hover-background: #48536b; + --pico-secondary-hover-border: var(--pico-secondary-hover-background); + --pico-secondary-hover-underline: var(--pico-secondary-hover); + --pico-secondary-focus: rgba(93, 107, 137, 0.25); + --pico-secondary-inverse: #fff; + --pico-contrast: #181c25; + --pico-contrast-background: #181c25; + --pico-contrast-border: var(--pico-contrast-background); + --pico-contrast-underline: rgba(24, 28, 37, 0.5); + --pico-contrast-hover: #000; + --pico-contrast-hover-background: #000; + --pico-contrast-hover-border: var(--pico-contrast-hover-background); + --pico-contrast-hover-underline: var(--pico-secondary-hover); + --pico-contrast-focus: rgba(93, 107, 137, 0.25); + --pico-contrast-inverse: #fff; + --pico-box-shadow: 0.0145rem 0.029rem 0.174rem rgba(129, 145, 181, 0.01698), 0.0335rem 0.067rem 0.402rem rgba(129, 145, 181, 0.024), 0.0625rem 0.125rem 0.75rem rgba(129, 145, 181, 0.03), 0.1125rem 0.225rem 1.35rem rgba(129, 145, 181, 0.036), 0.2085rem 0.417rem 2.502rem rgba(129, 145, 181, 0.04302), 0.5rem 1rem 6rem rgba(129, 145, 181, 0.06), 0 0 0 0.0625rem rgba(129, 145, 181, 0.015); + --pico-h1-color: #2d3138; + --pico-h2-color: #373c44; + --pico-h3-color: #424751; + --pico-h4-color: #4d535e; + --pico-h5-color: #5c6370; + --pico-h6-color: #646b79; + --pico-mark-background-color: rgb(252.5, 230.5, 191.5); + --pico-mark-color: #0f1114; + --pico-ins-color: rgb(28.5, 105.5, 84); + --pico-del-color: rgb(136, 56.5, 53); + --pico-blockquote-border-color: var(--pico-muted-border-color); + --pico-blockquote-footer-color: var(--pico-muted-color); + --pico-button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-table-border-color: var(--pico-muted-border-color); + --pico-table-row-stripped-background-color: rgba(111, 120, 135, 0.0375); + --pico-code-background-color: rgb(243, 244.5, 246.75); + --pico-code-color: #646b79; + --pico-code-kbd-background-color: var(--pico-color); + --pico-code-kbd-color: var(--pico-background-color); + --pico-form-element-background-color: rgb(251, 251.5, 252.25); + --pico-form-element-selected-background-color: #dfe3eb; + --pico-form-element-border-color: #cfd5e2; + --pico-form-element-color: #23262c; + --pico-form-element-placeholder-color: var(--pico-muted-color); + --pico-form-element-active-background-color: #fff; + --pico-form-element-active-border-color: var(--pico-primary-border); + --pico-form-element-focus-color: var(--pico-primary-border); + --pico-form-element-disabled-opacity: 0.5; + --pico-form-element-invalid-border-color: rgb(183.5, 105.5, 106.5); + --pico-form-element-invalid-active-border-color: rgb(200.25, 79.25, 72.25); + --pico-form-element-invalid-focus-color: var(--pico-form-element-invalid-active-border-color); + --pico-form-element-valid-border-color: rgb(76, 154.5, 137.5); + --pico-form-element-valid-active-border-color: rgb(39, 152.75, 118.75); + --pico-form-element-valid-focus-color: var(--pico-form-element-valid-active-border-color); + --pico-switch-background-color: #bfc7d9; + --pico-switch-checked-background-color: var(--pico-primary-background); + --pico-switch-color: #fff; + --pico-switch-thumb-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-range-border-color: #dfe3eb; + --pico-range-active-border-color: #bfc7d9; + --pico-range-thumb-border-color: var(--pico-background-color); + --pico-range-thumb-color: var(--pico-secondary-background); + --pico-range-thumb-active-color: var(--pico-primary-background); + --pico-accordion-border-color: var(--pico-muted-border-color); + --pico-accordion-active-summary-color: var(--pico-primary-hover); + --pico-accordion-close-summary-color: var(--pico-color); + --pico-accordion-open-summary-color: var(--pico-muted-color); + --pico-card-background-color: var(--pico-background-color); + --pico-card-border-color: var(--pico-muted-border-color); + --pico-card-box-shadow: var(--pico-box-shadow); + --pico-card-sectioning-background-color: rgb(251, 251.5, 252.25); + --pico-dropdown-background-color: #fff; + --pico-dropdown-border-color: #eff1f4; + --pico-dropdown-box-shadow: var(--pico-box-shadow); + --pico-dropdown-color: var(--pico-color); + --pico-dropdown-hover-background-color: #eff1f4; + --pico-loading-spinner-opacity: 0.5; + --pico-modal-overlay-background-color: rgba(232, 234, 237, 0.75); + --pico-progress-background-color: #dfe3eb; + --pico-progress-color: var(--pico-primary-background); + --pico-tooltip-background-color: var(--pico-contrast-background); + --pico-tooltip-color: var(--pico-contrast-inverse); + --pico-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 154.5, 137.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + --pico-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.25, 79.25, 72.25)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); +} +[data-theme=light] input:is([type=submit], +[type=button], +[type=reset], +[type=checkbox], +[type=radio], +[type=file]), +:root:not([data-theme=dark]) input:is([type=submit], +[type=button], +[type=reset], +[type=checkbox], +[type=radio], +[type=file]), +:host(:not([data-theme=dark])) input:is([type=submit], +[type=button], +[type=reset], +[type=checkbox], +[type=radio], +[type=file]) { + --pico-form-element-focus-color: var(--pico-primary-focus); } @media only screen and (prefers-color-scheme: dark) { - :root:not([data-theme]) { - --background-color: #11191f; - --color: hsl(205, 16%, 77%); - --h1-color: hsl(205, 20%, 94%); - --h2-color: #e1e6eb; - --h3-color: hsl(205, 18%, 86%); - --h4-color: #c8d1d8; - --h5-color: hsl(205, 16%, 77%); - --h6-color: #afbbc4; - --muted-color: hsl(205, 10%, 50%); - --muted-border-color: #1f2d38; - --primary: hsl(195, 85%, 41%); - --primary-hover: hsl(195, 80%, 50%); - --primary-focus: rgba(16, 149, 193, 0.25); - --primary-inverse: #fff; - --secondary: hsl(205, 15%, 41%); - --secondary-hover: hsl(205, 10%, 50%); - --secondary-focus: rgba(115, 130, 140, 0.25); - --secondary-inverse: #fff; - --contrast: hsl(205, 20%, 94%); - --contrast-hover: #fff; - --contrast-focus: rgba(115, 130, 140, 0.25); - --contrast-inverse: #000; - --mark-background-color: #d1c284; - --mark-color: #11191f; - --ins-color: #388e3c; - --del-color: #c62828; - --blockquote-border-color: var(--muted-border-color); - --blockquote-footer-color: var(--muted-color); - --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); - --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); - --form-element-background-color: #11191f; - --form-element-border-color: #374956; - --form-element-color: var(--color); - --form-element-placeholder-color: var(--muted-color); - --form-element-active-background-color: var(--form-element-background-color); - --form-element-active-border-color: var(--primary); - --form-element-focus-color: var(--primary-focus); - --form-element-disabled-background-color: hsl(205, 25%, 23%); - --form-element-disabled-border-color: hsl(205, 20%, 32%); - --form-element-disabled-opacity: 0.5; - --form-element-invalid-border-color: #b71c1c; - --form-element-invalid-active-border-color: #c62828; - --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25); - --form-element-valid-border-color: #2e7d32; - --form-element-valid-active-border-color: #388e3c; - --form-element-valid-focus-color: rgba(56, 142, 60, 0.25); - --switch-background-color: #374956; - --switch-color: var(--primary-inverse); - --switch-checked-background-color: var(--primary); - --range-border-color: #24333e; - --range-active-border-color: hsl(205, 25%, 23%); - --range-thumb-border-color: var(--background-color); - --range-thumb-color: var(--secondary); - --range-thumb-hover-color: var(--secondary-hover); - --range-thumb-active-color: var(--primary); - --table-border-color: var(--muted-border-color); - --table-row-stripped-background-color: rgba(115, 130, 140, 0.05); - --code-background-color: #18232c; - --code-color: var(--muted-color); - --code-kbd-background-color: var(--contrast); - --code-kbd-color: var(--contrast-inverse); - --code-tag-color: hsl(330, 30%, 50%); - --code-property-color: hsl(185, 30%, 50%); - --code-value-color: hsl(40, 10%, 50%); - --code-comment-color: #4d606d; - --accordion-border-color: var(--muted-border-color); - --accordion-active-summary-color: var(--primary); - --accordion-close-summary-color: var(--color); - --accordion-open-summary-color: var(--muted-color); - --card-background-color: #141e26; - --card-border-color: var(--card-background-color); - --card-box-shadow: - 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698), - 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024), - 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03), - 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036), - 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302), - 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06), - 0 0 0 0.0625rem rgba(0, 0, 0, 0.015); - --card-sectionning-background-color: #18232c; - --dropdown-background-color: hsl(205, 30%, 15%); - --dropdown-border-color: #24333e; - --dropdown-box-shadow: var(--card-box-shadow); - --dropdown-color: var(--color); - --dropdown-hover-background-color: rgba(36, 51, 62, 0.75); - --modal-overlay-background-color: rgba(36, 51, 62, 0.8); - --progress-background-color: #24333e; - --progress-color: var(--primary); - --loading-spinner-opacity: 0.5; - --tooltip-background-color: var(--contrast); - --tooltip-color: var(--contrast-inverse); - --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); - --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); - --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); - --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); - --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); - --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + :root:not([data-theme]), + :host(:not([data-theme])) { color-scheme: dark; + --pico-background-color: rgb(19, 22.5, 30.5); + --pico-color: #c2c7d0; + --pico-text-selection-color: rgba(1, 170, 255, 0.1875); + --pico-muted-color: #7b8495; + --pico-muted-border-color: #202632; + --pico-primary: #01aaff; + --pico-primary-background: #0172ad; + --pico-primary-border: var(--pico-primary-background); + --pico-primary-underline: rgba(1, 170, 255, 0.5); + --pico-primary-hover: #79c0ff; + --pico-primary-hover-background: #017fc0; + --pico-primary-hover-border: var(--pico-primary-hover-background); + --pico-primary-hover-underline: var(--pico-primary-hover); + --pico-primary-focus: rgba(1, 170, 255, 0.375); + --pico-primary-inverse: #fff; + --pico-secondary: #969eaf; + --pico-secondary-background: #525f7a; + --pico-secondary-border: var(--pico-secondary-background); + --pico-secondary-underline: rgba(150, 158, 175, 0.5); + --pico-secondary-hover: #b3b9c5; + --pico-secondary-hover-background: #5d6b89; + --pico-secondary-hover-border: var(--pico-secondary-hover-background); + --pico-secondary-hover-underline: var(--pico-secondary-hover); + --pico-secondary-focus: rgba(144, 158, 190, 0.25); + --pico-secondary-inverse: #fff; + --pico-contrast: #dfe3eb; + --pico-contrast-background: #eff1f4; + --pico-contrast-border: var(--pico-contrast-background); + --pico-contrast-underline: rgba(223, 227, 235, 0.5); + --pico-contrast-hover: #fff; + --pico-contrast-hover-background: #fff; + --pico-contrast-hover-border: var(--pico-contrast-hover-background); + --pico-contrast-hover-underline: var(--pico-contrast-hover); + --pico-contrast-focus: rgba(207, 213, 226, 0.25); + --pico-contrast-inverse: #000; + --pico-box-shadow: 0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698), 0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024), 0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03), 0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036), 0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302), 0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06), 0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015); + --pico-h1-color: #f0f1f3; + --pico-h2-color: #e0e3e7; + --pico-h3-color: #c2c7d0; + --pico-h4-color: #b3b9c5; + --pico-h5-color: #a4acba; + --pico-h6-color: #8891a4; + --pico-mark-background-color: #014063; + --pico-mark-color: #fff; + --pico-ins-color: #62af9a; + --pico-del-color: rgb(205.5, 126, 123); + --pico-blockquote-border-color: var(--pico-muted-border-color); + --pico-blockquote-footer-color: var(--pico-muted-color); + --pico-button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-table-border-color: var(--pico-muted-border-color); + --pico-table-row-stripped-background-color: rgba(111, 120, 135, 0.0375); + --pico-code-background-color: rgb(26, 30.5, 40.25); + --pico-code-color: #8891a4; + --pico-code-kbd-background-color: var(--pico-color); + --pico-code-kbd-color: var(--pico-background-color); + --pico-form-element-background-color: rgb(28, 33, 43.5); + --pico-form-element-selected-background-color: #2a3140; + --pico-form-element-border-color: #2a3140; + --pico-form-element-color: #e0e3e7; + --pico-form-element-placeholder-color: #8891a4; + --pico-form-element-active-background-color: rgb(26, 30.5, 40.25); + --pico-form-element-active-border-color: var(--pico-primary-border); + --pico-form-element-focus-color: var(--pico-primary-border); + --pico-form-element-disabled-opacity: 0.5; + --pico-form-element-invalid-border-color: rgb(149.5, 74, 80); + --pico-form-element-invalid-active-border-color: rgb(183.25, 63.5, 59); + --pico-form-element-invalid-focus-color: var(--pico-form-element-invalid-active-border-color); + --pico-form-element-valid-border-color: #2a7b6f; + --pico-form-element-valid-active-border-color: rgb(22, 137, 105.5); + --pico-form-element-valid-focus-color: var(--pico-form-element-valid-active-border-color); + --pico-switch-background-color: #333c4e; + --pico-switch-checked-background-color: var(--pico-primary-background); + --pico-switch-color: #fff; + --pico-switch-thumb-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-range-border-color: #202632; + --pico-range-active-border-color: #2a3140; + --pico-range-thumb-border-color: var(--pico-background-color); + --pico-range-thumb-color: var(--pico-secondary-background); + --pico-range-thumb-active-color: var(--pico-primary-background); + --pico-accordion-border-color: var(--pico-muted-border-color); + --pico-accordion-active-summary-color: var(--pico-primary-hover); + --pico-accordion-close-summary-color: var(--pico-color); + --pico-accordion-open-summary-color: var(--pico-muted-color); + --pico-card-background-color: #181c25; + --pico-card-border-color: var(--pico-card-background-color); + --pico-card-box-shadow: var(--pico-box-shadow); + --pico-card-sectioning-background-color: rgb(26, 30.5, 40.25); + --pico-dropdown-background-color: #181c25; + --pico-dropdown-border-color: #202632; + --pico-dropdown-box-shadow: var(--pico-box-shadow); + --pico-dropdown-color: var(--pico-color); + --pico-dropdown-hover-background-color: #202632; + --pico-loading-spinner-opacity: 0.5; + --pico-modal-overlay-background-color: rgba(7.5, 8.5, 10, 0.75); + --pico-progress-background-color: #202632; + --pico-progress-color: var(--pico-primary-background); + --pico-tooltip-background-color: var(--pico-contrast-background); + --pico-tooltip-color: var(--pico-contrast-inverse); + --pico-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + --pico-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); + } + :root:not([data-theme]) input:is([type=submit], + [type=button], + [type=reset], + [type=checkbox], + [type=radio], + [type=file]), + :host(:not([data-theme])) input:is([type=submit], + [type=button], + [type=reset], + [type=checkbox], + [type=radio], + [type=file]) { + --pico-form-element-focus-color: var(--pico-primary-focus); + } + :root:not([data-theme]) details summary[role=button].contrast:not(.outline)::after, + :host(:not([data-theme])) details summary[role=button].contrast:not(.outline)::after { + filter: brightness(0); + } + :root:not([data-theme]) [aria-busy=true]:not(input, select, textarea).contrast:is(button, + [type=submit], + [type=button], + [type=reset], + [role=button]):not(.outline)::before, + :host(:not([data-theme])) [aria-busy=true]:not(input, select, textarea).contrast:is(button, + [type=submit], + [type=button], + [type=reset], + [role=button]):not(.outline)::before { + filter: brightness(0); } } [data-theme=dark] { - --background-color: #11191f; - --color: hsl(205, 16%, 77%); - --h1-color: hsl(205, 20%, 94%); - --h2-color: #e1e6eb; - --h3-color: hsl(205, 18%, 86%); - --h4-color: #c8d1d8; - --h5-color: hsl(205, 16%, 77%); - --h6-color: #afbbc4; - --muted-color: hsl(205, 10%, 50%); - --muted-border-color: #1f2d38; - --primary: hsl(195, 85%, 41%); - --primary-hover: hsl(195, 80%, 50%); - --primary-focus: rgba(16, 149, 193, 0.25); - --primary-inverse: #fff; - --secondary: hsl(205, 15%, 41%); - --secondary-hover: hsl(205, 10%, 50%); - --secondary-focus: rgba(115, 130, 140, 0.25); - --secondary-inverse: #fff; - --contrast: hsl(205, 20%, 94%); - --contrast-hover: #fff; - --contrast-focus: rgba(115, 130, 140, 0.25); - --contrast-inverse: #000; - --mark-background-color: #d1c284; - --mark-color: #11191f; - --ins-color: #388e3c; - --del-color: #c62828; - --blockquote-border-color: var(--muted-border-color); - --blockquote-footer-color: var(--muted-color); - --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); - --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); - --form-element-background-color: #11191f; - --form-element-border-color: #374956; - --form-element-color: var(--color); - --form-element-placeholder-color: var(--muted-color); - --form-element-active-background-color: var(--form-element-background-color); - --form-element-active-border-color: var(--primary); - --form-element-focus-color: var(--primary-focus); - --form-element-disabled-background-color: hsl(205, 25%, 23%); - --form-element-disabled-border-color: hsl(205, 20%, 32%); - --form-element-disabled-opacity: 0.5; - --form-element-invalid-border-color: #b71c1c; - --form-element-invalid-active-border-color: #c62828; - --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25); - --form-element-valid-border-color: #2e7d32; - --form-element-valid-active-border-color: #388e3c; - --form-element-valid-focus-color: rgba(56, 142, 60, 0.25); - --switch-background-color: #374956; - --switch-color: var(--primary-inverse); - --switch-checked-background-color: var(--primary); - --range-border-color: #24333e; - --range-active-border-color: hsl(205, 25%, 23%); - --range-thumb-border-color: var(--background-color); - --range-thumb-color: var(--secondary); - --range-thumb-hover-color: var(--secondary-hover); - --range-thumb-active-color: var(--primary); - --table-border-color: var(--muted-border-color); - --table-row-stripped-background-color: rgba(115, 130, 140, 0.05); - --code-background-color: #18232c; - --code-color: var(--muted-color); - --code-kbd-background-color: var(--contrast); - --code-kbd-color: var(--contrast-inverse); - --code-tag-color: hsl(330, 30%, 50%); - --code-property-color: hsl(185, 30%, 50%); - --code-value-color: hsl(40, 10%, 50%); - --code-comment-color: #4d606d; - --accordion-border-color: var(--muted-border-color); - --accordion-active-summary-color: var(--primary); - --accordion-close-summary-color: var(--color); - --accordion-open-summary-color: var(--muted-color); - --card-background-color: #141e26; - --card-border-color: var(--card-background-color); - --card-box-shadow: - 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698), - 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024), - 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03), - 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036), - 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302), - 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06), - 0 0 0 0.0625rem rgba(0, 0, 0, 0.015); - --card-sectionning-background-color: #18232c; - --dropdown-background-color: hsl(205, 30%, 15%); - --dropdown-border-color: #24333e; - --dropdown-box-shadow: var(--card-box-shadow); - --dropdown-color: var(--color); - --dropdown-hover-background-color: rgba(36, 51, 62, 0.75); - --modal-overlay-background-color: rgba(36, 51, 62, 0.8); - --progress-background-color: #24333e; - --progress-color: var(--primary); - --loading-spinner-opacity: 0.5; - --tooltip-background-color: var(--contrast); - --tooltip-color: var(--contrast-inverse); - --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); - --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); - --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); - --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); - --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); - --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); - --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); color-scheme: dark; + --pico-background-color: rgb(19, 22.5, 30.5); + --pico-color: #c2c7d0; + --pico-text-selection-color: rgba(1, 170, 255, 0.1875); + --pico-muted-color: #7b8495; + --pico-muted-border-color: #202632; + --pico-primary: #01aaff; + --pico-primary-background: #0172ad; + --pico-primary-border: var(--pico-primary-background); + --pico-primary-underline: rgba(1, 170, 255, 0.5); + --pico-primary-hover: #79c0ff; + --pico-primary-hover-background: #017fc0; + --pico-primary-hover-border: var(--pico-primary-hover-background); + --pico-primary-hover-underline: var(--pico-primary-hover); + --pico-primary-focus: rgba(1, 170, 255, 0.375); + --pico-primary-inverse: #fff; + --pico-secondary: #969eaf; + --pico-secondary-background: #525f7a; + --pico-secondary-border: var(--pico-secondary-background); + --pico-secondary-underline: rgba(150, 158, 175, 0.5); + --pico-secondary-hover: #b3b9c5; + --pico-secondary-hover-background: #5d6b89; + --pico-secondary-hover-border: var(--pico-secondary-hover-background); + --pico-secondary-hover-underline: var(--pico-secondary-hover); + --pico-secondary-focus: rgba(144, 158, 190, 0.25); + --pico-secondary-inverse: #fff; + --pico-contrast: #dfe3eb; + --pico-contrast-background: #eff1f4; + --pico-contrast-border: var(--pico-contrast-background); + --pico-contrast-underline: rgba(223, 227, 235, 0.5); + --pico-contrast-hover: #fff; + --pico-contrast-hover-background: #fff; + --pico-contrast-hover-border: var(--pico-contrast-hover-background); + --pico-contrast-hover-underline: var(--pico-contrast-hover); + --pico-contrast-focus: rgba(207, 213, 226, 0.25); + --pico-contrast-inverse: #000; + --pico-box-shadow: 0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698), 0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024), 0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03), 0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036), 0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302), 0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06), 0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015); + --pico-h1-color: #f0f1f3; + --pico-h2-color: #e0e3e7; + --pico-h3-color: #c2c7d0; + --pico-h4-color: #b3b9c5; + --pico-h5-color: #a4acba; + --pico-h6-color: #8891a4; + --pico-mark-background-color: #014063; + --pico-mark-color: #fff; + --pico-ins-color: #62af9a; + --pico-del-color: rgb(205.5, 126, 123); + --pico-blockquote-border-color: var(--pico-muted-border-color); + --pico-blockquote-footer-color: var(--pico-muted-color); + --pico-button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-table-border-color: var(--pico-muted-border-color); + --pico-table-row-stripped-background-color: rgba(111, 120, 135, 0.0375); + --pico-code-background-color: rgb(26, 30.5, 40.25); + --pico-code-color: #8891a4; + --pico-code-kbd-background-color: var(--pico-color); + --pico-code-kbd-color: var(--pico-background-color); + --pico-form-element-background-color: rgb(28, 33, 43.5); + --pico-form-element-selected-background-color: #2a3140; + --pico-form-element-border-color: #2a3140; + --pico-form-element-color: #e0e3e7; + --pico-form-element-placeholder-color: #8891a4; + --pico-form-element-active-background-color: rgb(26, 30.5, 40.25); + --pico-form-element-active-border-color: var(--pico-primary-border); + --pico-form-element-focus-color: var(--pico-primary-border); + --pico-form-element-disabled-opacity: 0.5; + --pico-form-element-invalid-border-color: rgb(149.5, 74, 80); + --pico-form-element-invalid-active-border-color: rgb(183.25, 63.5, 59); + --pico-form-element-invalid-focus-color: var(--pico-form-element-invalid-active-border-color); + --pico-form-element-valid-border-color: #2a7b6f; + --pico-form-element-valid-active-border-color: rgb(22, 137, 105.5); + --pico-form-element-valid-focus-color: var(--pico-form-element-valid-active-border-color); + --pico-switch-background-color: #333c4e; + --pico-switch-checked-background-color: var(--pico-primary-background); + --pico-switch-color: #fff; + --pico-switch-thumb-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --pico-range-border-color: #202632; + --pico-range-active-border-color: #2a3140; + --pico-range-thumb-border-color: var(--pico-background-color); + --pico-range-thumb-color: var(--pico-secondary-background); + --pico-range-thumb-active-color: var(--pico-primary-background); + --pico-accordion-border-color: var(--pico-muted-border-color); + --pico-accordion-active-summary-color: var(--pico-primary-hover); + --pico-accordion-close-summary-color: var(--pico-color); + --pico-accordion-open-summary-color: var(--pico-muted-color); + --pico-card-background-color: #181c25; + --pico-card-border-color: var(--pico-card-background-color); + --pico-card-box-shadow: var(--pico-box-shadow); + --pico-card-sectioning-background-color: rgb(26, 30.5, 40.25); + --pico-dropdown-background-color: #181c25; + --pico-dropdown-border-color: #202632; + --pico-dropdown-box-shadow: var(--pico-box-shadow); + --pico-dropdown-color: var(--pico-color); + --pico-dropdown-hover-background-color: #202632; + --pico-loading-spinner-opacity: 0.5; + --pico-modal-overlay-background-color: rgba(7.5, 8.5, 10, 0.75); + --pico-progress-background-color: #202632; + --pico-progress-color: var(--pico-primary-background); + --pico-tooltip-background-color: var(--pico-contrast-background); + --pico-tooltip-color: var(--pico-contrast-inverse); + --pico-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + --pico-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); +} +[data-theme=dark] input:is([type=submit], +[type=button], +[type=reset], +[type=checkbox], +[type=radio], +[type=file]) { + --pico-form-element-focus-color: var(--pico-primary-focus); +} +[data-theme=dark] details summary[role=button].contrast:not(.outline)::after { + filter: brightness(0); +} +[data-theme=dark] [aria-busy=true]:not(input, select, textarea).contrast:is(button, +[type=submit], +[type=button], +[type=reset], +[role=button]):not(.outline)::before { + filter: brightness(0); } progress, [type=checkbox], [type=radio], [type=range] { - accent-color: var(--primary); + accent-color: var(--pico-primary); } /** @@ -545,56 +640,61 @@ progress, vertical-align: inherit; } -:where(:root) { +:where(:root), +:where(:host) { -webkit-tap-highlight-color: transparent; -webkit-text-size-adjust: 100%; -moz-text-size-adjust: 100%; text-size-adjust: 100%; - background-color: var(--background-color); - color: var(--color); - font-weight: var(--font-weight); - font-size: var(--font-size); - line-height: var(--line-height); - font-family: var(--font-family); + background-color: var(--pico-background-color); + color: var(--pico-color); + font-weight: var(--pico-font-weight); + font-size: var(--pico-font-size); + line-height: var(--pico-line-height); + font-family: var(--pico-font-family); + text-underline-offset: var(--pico-text-underline-offset); text-rendering: optimizeLegibility; overflow-wrap: break-word; - cursor: default; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; } /** - * Sectioning - * Container and responsive spacings for header, main, footer + * Landmarks */ -main { - display: block; -} - body { width: 100%; margin: 0; } + +main { + display: block; +} + body > header, body > main, body > footer { - width: 100%; - margin-right: auto; - margin-left: auto; - padding: var(--block-spacing-vertical) 0; + padding-block: var(--pico-block-spacing-vertical); +} + +/** + * Section + */ +section { + margin-bottom: var(--pico-block-spacing-vertical); } /** -* Container -*/ + * Container + */ .container, .container-fluid { width: 100%; margin-right: auto; margin-left: auto; - padding-right: var(--spacing); - padding-left: var(--spacing); + padding-right: var(--pico-spacing); + padding-left: var(--pico-spacing); } @media (min-width: 576px) { @@ -609,37 +709,33 @@ body > footer { max-width: 700px; } } -@media (min-width: 992px) { +@media (min-width: 1024px) { .container { - max-width: 920px; + max-width: 950px; } } -@media (min-width: 1200px) { +@media (min-width: 1280px) { .container { - max-width: 1130px; + max-width: 1200px; } } - -/** - * Section - * Responsive spacings for section - */ -section { - margin-bottom: var(--block-spacing-vertical); +@media (min-width: 1536px) { + .container { + max-width: 1450px; + } } /** -* Grid -* Minimal grid system with auto-layout columns -*/ + * Grid + * Minimal grid system with auto-layout columns + */ .grid { - grid-column-gap: var(--grid-spacing-horizontal); - grid-row-gap: var(--grid-spacing-vertical); + grid-column-gap: var(--pico-grid-column-gap); + grid-row-gap: var(--pico-grid-row-gap); display: grid; grid-template-columns: 1fr; - margin: 0; } -@media (min-width: 992px) { +@media (min-width: 768px) { .grid { grid-template-columns: repeat(auto-fit, minmax(0%, 1fr)); } @@ -649,17 +745,10 @@ section { } /** - * Horizontal scroller (
) + * Overflow auto */ -figure { - display: block; - margin: 0; - padding: 0; - overflow-x: auto; -} -figure figcaption { - padding: calc(var(--spacing) * 0.5) 0; - color: var(--muted-color); +.overflow-auto { + overflow: auto; } /** @@ -689,66 +778,16 @@ sup { address, blockquote, dl, -figure, -form, ol, p, pre, table, ul { margin-top: 0; - margin-bottom: var(--typography-spacing-vertical); - color: var(--color); + margin-bottom: var(--pico-typography-spacing-vertical); + color: var(--pico-color); font-style: normal; - font-weight: var(--font-weight); - font-size: var(--font-size); -} - -a, -[role=link] { - --color: var(--primary); - --background-color: transparent; - outline: none; - background-color: var(--background-color); - color: var(--color); - -webkit-text-decoration: var(--text-decoration); - text-decoration: var(--text-decoration); - transition: background-color var(--transition), color var(--transition), box-shadow var(--transition), -webkit-text-decoration var(--transition); - transition: background-color var(--transition), color var(--transition), text-decoration var(--transition), box-shadow var(--transition); - transition: background-color var(--transition), color var(--transition), text-decoration var(--transition), box-shadow var(--transition), -webkit-text-decoration var(--transition); -} -a:is([aria-current], :hover, :active, :focus), -[role=link]:is([aria-current], :hover, :active, :focus) { - --color: var(--primary-hover); - --text-decoration: underline; -} -a:focus, -[role=link]:focus { - --background-color: var(--primary-focus); -} -a.secondary, -[role=link].secondary { - --color: var(--secondary); -} -a.secondary:is([aria-current], :hover, :active, :focus), -[role=link].secondary:is([aria-current], :hover, :active, :focus) { - --color: var(--secondary-hover); -} -a.secondary:focus, -[role=link].secondary:focus { - --background-color: var(--secondary-focus); -} -a.contrast, -[role=link].contrast { - --color: var(--contrast); -} -a.contrast:is([aria-current], :hover, :active, :focus), -[role=link].contrast:is([aria-current], :hover, :active, :focus) { - --color: var(--contrast-hover); -} -a.contrast:focus, -[role=link].contrast:focus { - --background-color: var(--contrast-focus); + font-weight: var(--pico-font-weight); } h1, @@ -758,78 +797,66 @@ h4, h5, h6 { margin-top: 0; - margin-bottom: var(--typography-spacing-vertical); - color: var(--color); - font-weight: var(--font-weight); - font-size: var(--font-size); - font-family: var(--font-family); + margin-bottom: var(--pico-typography-spacing-vertical); + color: var(--pico-color); + font-weight: var(--pico-font-weight); + font-size: var(--pico-font-size); + line-height: var(--pico-line-height); + font-family: var(--pico-font-family); } h1 { - --color: var(--h1-color); + --pico-color: var(--pico-h1-color); } h2 { - --color: var(--h2-color); + --pico-color: var(--pico-h2-color); } h3 { - --color: var(--h3-color); + --pico-color: var(--pico-h3-color); } h4 { - --color: var(--h4-color); + --pico-color: var(--pico-h4-color); } h5 { - --color: var(--h5-color); + --pico-color: var(--pico-h5-color); } h6 { - --color: var(--h6-color); + --pico-color: var(--pico-h6-color); } -:where(address, blockquote, dl, figure, form, ol, p, pre, table, ul) ~ :is(h1, h2, h3, h4, h5, h6) { - margin-top: var(--typography-spacing-vertical); +:where(article, address, blockquote, dl, figure, form, ol, p, pre, table, ul) ~ :is(h1, h2, h3, h4, h5, h6) { + margin-top: var(--pico-typography-spacing-top); } -hgroup, -.headings { - margin-bottom: var(--typography-spacing-vertical); +p { + margin-bottom: var(--pico-typography-spacing-vertical); +} + +hgroup { + margin-bottom: var(--pico-typography-spacing-vertical); } -hgroup > *, -.headings > * { +hgroup > * { + margin-top: 0; margin-bottom: 0; } -hgroup > *:last-child, -.headings > *:last-child { - --color: var(--muted-color); - --font-weight: unset; +hgroup > *:not(:first-child):last-child { + --pico-color: var(--pico-muted-color); + --pico-font-weight: unset; font-size: 1rem; - font-family: unset; -} - -p { - margin-bottom: var(--typography-spacing-vertical); -} - -small { - font-size: var(--font-size); } -:where(dl, ol, ul) { - padding-right: 0; - padding-left: var(--spacing); - padding-inline-start: var(--spacing); - padding-inline-end: 0; -} -:where(dl, ol, ul) li { - margin-bottom: calc(var(--typography-spacing-vertical) * 0.25); +:where(ol, ul) li { + margin-bottom: calc(var(--pico-typography-spacing-vertical) * 0.25); } -:where(dl, ol, ul) :is(dl, ol, ul) { +:where(dl, ol, ul) :where(dl, ol, ul) { margin: 0; - margin-top: calc(var(--typography-spacing-vertical) * 0.25); + margin-top: calc(var(--pico-typography-spacing-vertical) * 0.25); } ul li { @@ -838,23 +865,23 @@ ul li { mark { padding: 0.125rem 0.25rem; - background-color: var(--mark-background-color); - color: var(--mark-color); + background-color: var(--pico-mark-background-color); + color: var(--pico-mark-color); vertical-align: baseline; } blockquote { display: block; - margin: var(--typography-spacing-vertical) 0; - padding: var(--spacing); + margin: var(--pico-typography-spacing-vertical) 0; + padding: var(--pico-spacing); border-right: none; - border-left: 0.25rem solid var(--blockquote-border-color); - border-inline-start: 0.25rem solid var(--blockquote-border-color); + border-left: 0.25rem solid var(--pico-blockquote-border-color); + border-inline-start: 0.25rem solid var(--pico-blockquote-border-color); border-inline-end: none; } blockquote footer { - margin-top: calc(var(--typography-spacing-vertical) * 0.5); - color: var(--blockquote-footer-color); + margin-top: calc(var(--pico-typography-spacing-vertical) * 0.5); + color: var(--pico-blockquote-footer-color); } abbr[title] { @@ -864,55 +891,74 @@ abbr[title] { } ins { - color: var(--ins-color); + color: var(--pico-ins-color); text-decoration: none; } del { - color: var(--del-color); + color: var(--pico-del-color); } ::-moz-selection { - background-color: var(--primary-focus); + background-color: var(--pico-text-selection-color); } ::selection { - background-color: var(--primary-focus); + background-color: var(--pico-text-selection-color); } /** - * Embedded content + * Link */ -:where(audio, canvas, iframe, img, svg, video) { - vertical-align: middle; -} - -audio, -video { - display: inline-block; -} - -audio:not([controls]) { - display: none; - height: 0; +:where(a:not([role=button])), +[role=link] { + --pico-color: var(--pico-primary); + --pico-background-color: transparent; + --pico-underline: var(--pico-primary-underline); + outline: none; + background-color: var(--pico-background-color); + color: var(--pico-color); + -webkit-text-decoration: var(--pico-text-decoration); + text-decoration: var(--pico-text-decoration); + text-decoration-color: var(--pico-underline); + text-underline-offset: 0.125em; + transition: background-color var(--pico-transition), color var(--pico-transition), box-shadow var(--pico-transition), -webkit-text-decoration var(--pico-transition); + transition: background-color var(--pico-transition), color var(--pico-transition), text-decoration var(--pico-transition), box-shadow var(--pico-transition); + transition: background-color var(--pico-transition), color var(--pico-transition), text-decoration var(--pico-transition), box-shadow var(--pico-transition), -webkit-text-decoration var(--pico-transition); +} +:where(a:not([role=button])):is([aria-current]:not([aria-current=false]), :hover, :active, :focus), +[role=link]:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-color: var(--pico-primary-hover); + --pico-underline: var(--pico-primary-hover-underline); + --pico-text-decoration: underline; +} +:where(a:not([role=button])):focus-visible, +[role=link]:focus-visible { + box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-primary-focus); +} +:where(a:not([role=button])).secondary, +[role=link].secondary { + --pico-color: var(--pico-secondary); + --pico-underline: var(--pico-secondary-underline); } - -:where(iframe) { - border-style: none; +:where(a:not([role=button])).secondary:is([aria-current]:not([aria-current=false]), :hover, :active, :focus), +[role=link].secondary:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-color: var(--pico-secondary-hover); + --pico-underline: var(--pico-secondary-hover-underline); } - -img { - max-width: 100%; - height: auto; - border-style: none; +:where(a:not([role=button])).contrast, +[role=link].contrast { + --pico-color: var(--pico-contrast); + --pico-underline: var(--pico-contrast-underline); } - -:where(svg:not([fill])) { - fill: currentColor; +:where(a:not([role=button])).contrast:is([aria-current]:not([aria-current=false]), :hover, :active, :focus), +[role=link].contrast:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-color: var(--pico-contrast-hover); + --pico-underline: var(--pico-contrast-hover-underline); } -svg:not(:root) { - overflow: hidden; +a[role=button] { + display: inline-block; } /** @@ -926,179 +972,358 @@ button { } button, -[type=button], +[type=submit], [type=reset], -[type=submit] { +[type=button] { -webkit-appearance: button; } -button { - display: block; - width: 100%; - margin-bottom: var(--spacing); -} - -[role=button] { - display: inline-block; - text-decoration: none; -} - button, -input[type=submit], -input[type=button], -input[type=reset], +[type=submit], +[type=reset], +[type=button], +[type=file]::file-selector-button, [role=button] { - --background-color: var(--primary); - --border-color: var(--primary); - --color: var(--primary-inverse); - --box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); - padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal); - border: var(--border-width) solid var(--border-color); - border-radius: var(--border-radius); + --pico-background-color: var(--pico-primary-background); + --pico-border-color: var(--pico-primary-border); + --pico-color: var(--pico-primary-inverse); + --pico-box-shadow: var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal); + border: var(--pico-border-width) solid var(--pico-border-color); + border-radius: var(--pico-border-radius); outline: none; - background-color: var(--background-color); - box-shadow: var(--box-shadow); - color: var(--color); - font-weight: var(--font-weight); + background-color: var(--pico-background-color); + box-shadow: var(--pico-box-shadow); + color: var(--pico-color); + font-weight: var(--pico-font-weight); font-size: 1rem; - line-height: var(--line-height); + line-height: var(--pico-line-height); text-align: center; + text-decoration: none; cursor: pointer; - transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); -} -button:is([aria-current], :hover, :active, :focus), -input[type=submit]:is([aria-current], :hover, :active, :focus), -input[type=button]:is([aria-current], :hover, :active, :focus), -input[type=reset]:is([aria-current], :hover, :active, :focus), -[role=button]:is([aria-current], :hover, :active, :focus) { - --background-color: var(--primary-hover); - --border-color: var(--primary-hover); - --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); - --color: var(--primary-inverse); -} -button:focus, -input[type=submit]:focus, -input[type=button]:focus, -input[type=reset]:focus, -[role=button]:focus { - --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), - 0 0 0 var(--outline-width) var(--primary-focus); -} - -:is(button, input[type=submit], input[type=button], [role=button]).secondary, -input[type=reset] { - --background-color: var(--secondary); - --border-color: var(--secondary); - --color: var(--secondary-inverse); + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + transition: background-color var(--pico-transition), border-color var(--pico-transition), color var(--pico-transition), box-shadow var(--pico-transition); +} +button:is([aria-current]:not([aria-current=false])), button:is(:hover, :active, :focus), +[type=submit]:is([aria-current]:not([aria-current=false])), +[type=submit]:is(:hover, :active, :focus), +[type=reset]:is([aria-current]:not([aria-current=false])), +[type=reset]:is(:hover, :active, :focus), +[type=button]:is([aria-current]:not([aria-current=false])), +[type=button]:is(:hover, :active, :focus), +[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])), +[type=file]::file-selector-button:is(:hover, :active, :focus), +[role=button]:is([aria-current]:not([aria-current=false])), +[role=button]:is(:hover, :active, :focus) { + --pico-background-color: var(--pico-primary-hover-background); + --pico-border-color: var(--pico-primary-hover-border); + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); + --pico-color: var(--pico-primary-inverse); +} +button:focus, button:is([aria-current]:not([aria-current=false])):focus, +[type=submit]:focus, +[type=submit]:is([aria-current]:not([aria-current=false])):focus, +[type=reset]:focus, +[type=reset]:is([aria-current]:not([aria-current=false])):focus, +[type=button]:focus, +[type=button]:is([aria-current]:not([aria-current=false])):focus, +[type=file]::file-selector-button:focus, +[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus, +[role=button]:focus, +[role=button]:is([aria-current]:not([aria-current=false])):focus { + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), 0 0 0 var(--pico-outline-width) var(--pico-primary-focus); +} + +[type=submit], +[type=reset], +[type=button] { + margin-bottom: var(--pico-spacing); +} + +:is(button, [type=submit], [type=button], [role=button]).secondary, +[type=reset], +[type=file]::file-selector-button { + --pico-background-color: var(--pico-secondary-background); + --pico-border-color: var(--pico-secondary-border); + --pico-color: var(--pico-secondary-inverse); cursor: pointer; } -:is(button, input[type=submit], input[type=button], [role=button]).secondary:is([aria-current], :hover, :active, :focus), -input[type=reset]:is([aria-current], :hover, :active, :focus) { - --background-color: var(--secondary-hover); - --border-color: var(--secondary-hover); - --color: var(--secondary-inverse); +:is(button, [type=submit], [type=button], [role=button]).secondary:is([aria-current]:not([aria-current=false]), :hover, :active, :focus), +[type=reset]:is([aria-current]:not([aria-current=false]), :hover, :active, :focus), +[type=file]::file-selector-button:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-background-color: var(--pico-secondary-hover-background); + --pico-border-color: var(--pico-secondary-hover-border); + --pico-color: var(--pico-secondary-inverse); } -:is(button, input[type=submit], input[type=button], [role=button]).secondary:focus, -input[type=reset]:focus { - --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), - 0 0 0 var(--outline-width) var(--secondary-focus); +:is(button, [type=submit], [type=button], [role=button]).secondary:focus, :is(button, [type=submit], [type=button], [role=button]).secondary:is([aria-current]:not([aria-current=false])):focus, +[type=reset]:focus, +[type=reset]:is([aria-current]:not([aria-current=false])):focus, +[type=file]::file-selector-button:focus, +[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus { + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), 0 0 0 var(--pico-outline-width) var(--pico-secondary-focus); } -:is(button, input[type=submit], input[type=button], [role=button]).contrast { - --background-color: var(--contrast); - --border-color: var(--contrast); - --color: var(--contrast-inverse); +:is(button, [type=submit], [type=button], [role=button]).contrast { + --pico-background-color: var(--pico-contrast-background); + --pico-border-color: var(--pico-contrast-border); + --pico-color: var(--pico-contrast-inverse); } -:is(button, input[type=submit], input[type=button], [role=button]).contrast:is([aria-current], :hover, :active, :focus) { - --background-color: var(--contrast-hover); - --border-color: var(--contrast-hover); - --color: var(--contrast-inverse); +:is(button, [type=submit], [type=button], [role=button]).contrast:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-background-color: var(--pico-contrast-hover-background); + --pico-border-color: var(--pico-contrast-hover-border); + --pico-color: var(--pico-contrast-inverse); } -:is(button, input[type=submit], input[type=button], [role=button]).contrast:focus { - --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), - 0 0 0 var(--outline-width) var(--contrast-focus); +:is(button, [type=submit], [type=button], [role=button]).contrast:focus, :is(button, [type=submit], [type=button], [role=button]).contrast:is([aria-current]:not([aria-current=false])):focus { + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), 0 0 0 var(--pico-outline-width) var(--pico-contrast-focus); } -:is(button, input[type=submit], input[type=button], [role=button]).outline, -input[type=reset].outline { - --background-color: transparent; - --color: var(--primary); +:is(button, [type=submit], [type=button], [role=button]).outline, +[type=reset].outline { + --pico-background-color: transparent; + --pico-color: var(--pico-primary); + --pico-border-color: var(--pico-primary); } -:is(button, input[type=submit], input[type=button], [role=button]).outline:is([aria-current], :hover, :active, :focus), -input[type=reset].outline:is([aria-current], :hover, :active, :focus) { - --background-color: transparent; - --color: var(--primary-hover); +:is(button, [type=submit], [type=button], [role=button]).outline:is([aria-current]:not([aria-current=false]), :hover, :active, :focus), +[type=reset].outline:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-background-color: transparent; + --pico-color: var(--pico-primary-hover); + --pico-border-color: var(--pico-primary-hover); } -:is(button, input[type=submit], input[type=button], [role=button]).outline.secondary, -input[type=reset].outline { - --color: var(--secondary); +:is(button, [type=submit], [type=button], [role=button]).outline.secondary, +[type=reset].outline { + --pico-color: var(--pico-secondary); + --pico-border-color: var(--pico-secondary); } -:is(button, input[type=submit], input[type=button], [role=button]).outline.secondary:is([aria-current], :hover, :active, :focus), -input[type=reset].outline:is([aria-current], :hover, :active, :focus) { - --color: var(--secondary-hover); +:is(button, [type=submit], [type=button], [role=button]).outline.secondary:is([aria-current]:not([aria-current=false]), :hover, :active, :focus), +[type=reset].outline:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-color: var(--pico-secondary-hover); + --pico-border-color: var(--pico-secondary-hover); } -:is(button, input[type=submit], input[type=button], [role=button]).outline.contrast { - --color: var(--contrast); +:is(button, [type=submit], [type=button], [role=button]).outline.contrast { + --pico-color: var(--pico-contrast); + --pico-border-color: var(--pico-contrast); } -:is(button, input[type=submit], input[type=button], [role=button]).outline.contrast:is([aria-current], :hover, :active, :focus) { - --color: var(--contrast-hover); +:is(button, [type=submit], [type=button], [role=button]).outline.contrast:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { + --pico-color: var(--pico-contrast-hover); + --pico-border-color: var(--pico-contrast-hover); } -:where(button, [type=submit], [type=button], [type=reset], [role=button])[disabled], -:where(fieldset[disabled]) :is(button, [type=submit], [type=button], [type=reset], [role=button]), -a[role=button]:not([href]) { +:where(button, [type=submit], [type=reset], [type=button], [role=button])[disabled], +:where(fieldset[disabled]) :is(button, [type=submit], [type=button], [type=reset], [role=button]) { opacity: 0.5; pointer-events: none; } /** - * Form elements + * Table */ -input, -optgroup, -select, -textarea { - margin: 0; - font-size: 1rem; - line-height: var(--line-height); - font-family: inherit; - letter-spacing: inherit; +:where(table) { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + text-indent: 0; } -input { - overflow: visible; +th, +td { + padding: calc(var(--pico-spacing) / 2) var(--pico-spacing); + border-bottom: var(--pico-border-width) solid var(--pico-table-border-color); + background-color: var(--pico-background-color); + color: var(--pico-color); + font-weight: var(--pico-font-weight); + text-align: left; + text-align: start; } -select { - text-transform: none; +tfoot th, +tfoot td { + border-top: var(--pico-border-width) solid var(--pico-table-border-color); + border-bottom: 0; } -legend { - max-width: 100%; - padding: 0; - color: inherit; - white-space: normal; +table.striped tbody tr:nth-child(odd) th, +table.striped tbody tr:nth-child(odd) td { + background-color: var(--pico-table-row-stripped-background-color); } -textarea { - overflow: auto; +/** + * Embedded content + */ +:where(audio, canvas, iframe, img, svg, video) { + vertical-align: middle; } -[type=checkbox], -[type=radio] { - padding: 0; +audio, +video { + display: inline-block; } -::-webkit-inner-spin-button, -::-webkit-outer-spin-button { - height: auto; +audio:not([controls]) { + display: none; + height: 0; } -[type=search] { - -webkit-appearance: textfield; - outline-offset: -2px; +:where(iframe) { + border-style: none; +} + +img { + max-width: 100%; + height: auto; + border-style: none; +} + +:where(svg:not([fill])) { + fill: currentColor; +} + +svg:not(:root), +svg:not(:host) { + overflow: hidden; +} + +/** + * Code + */ +pre, +code, +kbd, +samp { + font-size: 0.875em; + font-family: var(--pico-font-family); +} + +pre code, +pre samp { + font-size: inherit; + font-family: inherit; +} + +pre { + -ms-overflow-style: scrollbar; + overflow: auto; +} + +pre, +code, +kbd, +samp { + border-radius: var(--pico-border-radius); + background: var(--pico-code-background-color); + color: var(--pico-code-color); + font-weight: var(--pico-font-weight); + line-height: initial; +} + +code, +kbd, +samp { + display: inline-block; + padding: 0.375rem; +} + +pre { + display: block; + margin-bottom: var(--pico-spacing); + overflow-x: auto; +} +pre > code, +pre > samp { + display: block; + padding: var(--pico-spacing); + background: none; + line-height: var(--pico-line-height); +} + +kbd { + background-color: var(--pico-code-kbd-background-color); + color: var(--pico-code-kbd-color); + vertical-align: baseline; +} + +/** + * Figure + */ +figure { + display: block; + margin: 0; + padding: 0; +} +figure figcaption { + padding: calc(var(--pico-spacing) * 0.5) 0; + color: var(--pico-muted-color); +} + +/** + * Misc + */ +hr { + height: 0; + margin: var(--pico-typography-spacing-vertical) 0; + border: 0; + border-top: 1px solid var(--pico-muted-border-color); + color: inherit; +} + +[hidden], +template { + display: none !important; +} + +canvas { + display: inline-block; +} + +/** + * Basics form elements + */ +input, +optgroup, +select, +textarea { + margin: 0; + font-size: 1rem; + line-height: var(--pico-line-height); + font-family: inherit; + letter-spacing: inherit; +} + +input { + overflow: visible; +} + +select { + text-transform: none; +} + +legend { + max-width: 100%; + padding: 0; + color: inherit; + white-space: normal; +} + +textarea { + overflow: auto; +} + +[type=checkbox], +[type=radio] { + padding: 0; +} + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px; } [type=search]::-webkit-search-decoration { @@ -1134,12 +1359,13 @@ textarea { } input:not([type=checkbox], [type=radio], [type=range]) { - height: calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2); + height: calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2); } fieldset { + width: 100%; margin: 0; - margin-bottom: var(--spacing); + margin-bottom: var(--pico-spacing); padding: 0; border: 0; } @@ -1147,11 +1373,17 @@ fieldset { label, fieldset legend { display: block; - margin-bottom: calc(var(--spacing) * 0.25); - font-weight: var(--form-label-font-weight, var(--font-weight)); + margin-bottom: calc(var(--pico-spacing) * 0.375); + color: var(--pico-color); + font-weight: var(--pico-form-label-font-weight, var(--pico-font-weight)); +} + +fieldset legend { + margin-bottom: calc(var(--pico-spacing) * 0.5); } input:not([type=checkbox], [type=radio]), +button[type=submit], select, textarea { width: 100%; @@ -1163,80 +1395,117 @@ textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; - padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal); + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal); } input, select, textarea { - --background-color: var(--form-element-background-color); - --border-color: var(--form-element-border-color); - --color: var(--form-element-color); - --box-shadow: none; - border: var(--border-width) solid var(--border-color); - border-radius: var(--border-radius); + --pico-background-color: var(--pico-form-element-background-color); + --pico-border-color: var(--pico-form-element-border-color); + --pico-color: var(--pico-form-element-color); + --pico-box-shadow: none; + border: var(--pico-border-width) solid var(--pico-border-color); + border-radius: var(--pico-border-radius); outline: none; - background-color: var(--background-color); - box-shadow: var(--box-shadow); - color: var(--color); - font-weight: var(--font-weight); - transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); + background-color: var(--pico-background-color); + box-shadow: var(--pico-box-shadow); + color: var(--pico-color); + font-weight: var(--pico-font-weight); + transition: background-color var(--pico-transition), border-color var(--pico-transition), color var(--pico-transition), box-shadow var(--pico-transition); } -input:not([type=submit], [type=button], [type=reset], [type=checkbox], [type=radio], [readonly]):is(:active, :focus), -:where(select, textarea):is(:active, :focus) { - --background-color: var(--form-element-active-background-color); +input:not([type=submit], +[type=button], +[type=reset], +[type=checkbox], +[type=radio], +[readonly]):is(:active, :focus), +:where(select, textarea):not([readonly]):is(:active, :focus) { + --pico-background-color: var(--pico-form-element-active-background-color); } input:not([type=submit], [type=button], [type=reset], [role=switch], [readonly]):is(:active, :focus), -:where(select, textarea):is(:active, :focus) { - --border-color: var(--form-element-active-border-color); +:where(select, textarea):not([readonly]):is(:active, :focus) { + --pico-border-color: var(--pico-form-element-active-border-color); } -input:not([type=submit], [type=button], [type=reset], [type=range], [type=file], [readonly]):focus, -select:focus, -textarea:focus { - --box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color); +input:not([type=submit], +[type=button], +[type=reset], +[type=range], +[type=file], +[readonly]):focus, +:where(select, textarea):not([readonly]):focus { + --pico-box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color); } input:not([type=submit], [type=button], [type=reset])[disabled], select[disabled], textarea[disabled], +label[aria-disabled=true], :where(fieldset[disabled]) :is(input:not([type=submit], [type=button], [type=reset]), select, textarea) { - --background-color: var(--form-element-disabled-background-color); - --border-color: var(--form-element-disabled-border-color); - opacity: var(--form-element-disabled-opacity); + opacity: var(--pico-form-element-disabled-opacity); pointer-events: none; } -:where(input, select, textarea):not([type=checkbox], [type=radio], [type=date], [type=datetime-local], [type=month], [type=time], [type=week])[aria-invalid] { - padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem) !important; - padding-left: var(--form-element-spacing-horizontal); - padding-inline-start: var(--form-element-spacing-horizontal) !important; - padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem) !important; +label[aria-disabled=true] input[disabled] { + opacity: 1; +} + +:where(input, select, textarea):not([type=checkbox], +[type=radio], +[type=date], +[type=datetime-local], +[type=month], +[type=time], +[type=week], +[type=range])[aria-invalid] { + padding-right: calc(var(--pico-form-element-spacing-horizontal) + 1.5rem) !important; + padding-left: var(--pico-form-element-spacing-horizontal); + padding-inline-start: var(--pico-form-element-spacing-horizontal) !important; + padding-inline-end: calc(var(--pico-form-element-spacing-horizontal) + 1.5rem) !important; background-position: center right 0.75rem; background-size: 1rem auto; background-repeat: no-repeat; } -:where(input, select, textarea):not([type=checkbox], [type=radio], [type=date], [type=datetime-local], [type=month], [type=time], [type=week])[aria-invalid=false] { - background-image: var(--icon-valid); -} -:where(input, select, textarea):not([type=checkbox], [type=radio], [type=date], [type=datetime-local], [type=month], [type=time], [type=week])[aria-invalid=true] { - background-image: var(--icon-invalid); +:where(input, select, textarea):not([type=checkbox], +[type=radio], +[type=date], +[type=datetime-local], +[type=month], +[type=time], +[type=week], +[type=range])[aria-invalid=false]:not(select) { + background-image: var(--pico-icon-valid); +} +:where(input, select, textarea):not([type=checkbox], +[type=radio], +[type=date], +[type=datetime-local], +[type=month], +[type=time], +[type=week], +[type=range])[aria-invalid=true]:not(select) { + background-image: var(--pico-icon-invalid); } :where(input, select, textarea)[aria-invalid=false] { - --border-color: var(--form-element-valid-border-color); + --pico-border-color: var(--pico-form-element-valid-border-color); } :where(input, select, textarea)[aria-invalid=false]:is(:active, :focus) { - --border-color: var(--form-element-valid-active-border-color) !important; - --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important; + --pico-border-color: var(--pico-form-element-valid-active-border-color) !important; +} +:where(input, select, textarea)[aria-invalid=false]:is(:active, :focus):not([type=checkbox], [type=radio]) { + --pico-box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-form-element-valid-focus-color) !important; } :where(input, select, textarea)[aria-invalid=true] { - --border-color: var(--form-element-invalid-border-color); + --pico-border-color: var(--pico-form-element-invalid-border-color); } :where(input, select, textarea)[aria-invalid=true]:is(:active, :focus) { - --border-color: var(--form-element-invalid-active-border-color) !important; - --box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important; + --pico-border-color: var(--pico-form-element-invalid-active-border-color) !important; +} +:where(input, select, textarea)[aria-invalid=true]:is(:active, :focus):not([type=checkbox], [type=radio]) { + --pico-box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-form-element-invalid-focus-color) !important; } [dir=rtl] :where(input, select, textarea):not([type=checkbox], [type=radio]):is([aria-invalid], [aria-invalid=true], [aria-invalid=false]) { @@ -1248,14 +1517,14 @@ input::-webkit-input-placeholder, textarea::placeholder, textarea::-webkit-input-placeholder, select:invalid { - color: var(--form-element-placeholder-color); + color: var(--pico-form-element-placeholder-color); opacity: 1; } input:not([type=checkbox], [type=radio]), select, textarea { - margin-bottom: var(--spacing); + margin-bottom: var(--pico-spacing); } select::-ms-expand { @@ -1263,36 +1532,61 @@ select::-ms-expand { background-color: transparent; } select:not([multiple], [size]) { - padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem); - padding-left: var(--form-element-spacing-horizontal); - padding-inline-start: var(--form-element-spacing-horizontal); - padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem); - background-image: var(--icon-chevron); + padding-right: calc(var(--pico-form-element-spacing-horizontal) + 1.5rem); + padding-left: var(--pico-form-element-spacing-horizontal); + padding-inline-start: var(--pico-form-element-spacing-horizontal); + padding-inline-end: calc(var(--pico-form-element-spacing-horizontal) + 1.5rem); + background-image: var(--pico-icon-chevron); background-position: center right 0.75rem; background-size: 1rem auto; background-repeat: no-repeat; } +select[multiple] option:checked { + background: var(--pico-form-element-selected-background-color); + color: var(--pico-form-element-color); +} [dir=rtl] select:not([multiple], [size]) { background-position: center left 0.75rem; } -:where(input, select, textarea, .grid) + small { +textarea { + display: block; + resize: vertical; +} +textarea[aria-invalid] { + --pico-icon-height: calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2); + background-position: top right 0.75rem !important; + background-size: 1rem var(--pico-icon-height) !important; +} + +:where(input, select, textarea, fieldset, .grid) + small { display: block; width: 100%; - margin-top: calc(var(--spacing) * -0.75); - margin-bottom: var(--spacing); - color: var(--muted-color); + margin-top: calc(var(--pico-spacing) * -0.75); + margin-bottom: var(--pico-spacing); + color: var(--pico-muted-color); +} +:where(input, select, textarea, fieldset, .grid)[aria-invalid=false] + small { + color: var(--pico-ins-color); +} +:where(input, select, textarea, fieldset, .grid)[aria-invalid=true] + small { + color: var(--pico-del-color); } label > :where(input, select, textarea) { - margin-top: calc(var(--spacing) * 0.25); + margin-top: calc(var(--pico-spacing) * 0.25); } /** - * Form elements - * Checkboxes & Radios + * Checkboxes, Radios and Switches */ +label:has([type=checkbox], [type=radio]) { + width: -moz-fit-content; + width: fit-content; + cursor: pointer; +} + [type=checkbox], [type=radio] { -webkit-appearance: none; @@ -1301,12 +1595,8 @@ label > :where(input, select, textarea) { width: 1.25em; height: 1.25em; margin-top: -0.125em; - margin-right: 0.375em; - margin-left: 0; - margin-inline-start: 0; - margin-inline-end: 0.375em; - border-width: var(--border-width); - font-size: inherit; + margin-inline-end: 0.5em; + border-width: var(--pico-border-width); vertical-align: middle; cursor: pointer; } @@ -1318,9 +1608,9 @@ label > :where(input, select, textarea) { [type=radio]:checked, [type=radio]:checked:active, [type=radio]:checked:focus { - --background-color: var(--primary); - --border-color: var(--primary); - background-image: var(--icon-checkbox); + --pico-background-color: var(--pico-primary-background); + --pico-border-color: var(--pico-primary-border); + background-image: var(--pico-icon-checkbox); background-position: center; background-size: 0.75em auto; background-repeat: no-repeat; @@ -1328,15 +1618,18 @@ label > :where(input, select, textarea) { [type=checkbox] ~ label, [type=radio] ~ label { display: inline-block; - margin-right: 0.375em; margin-bottom: 0; cursor: pointer; } +[type=checkbox] ~ label:not(:last-of-type), +[type=radio] ~ label:not(:last-of-type) { + margin-inline-end: 1em; +} [type=checkbox]:indeterminate { - --background-color: var(--primary); - --border-color: var(--primary); - background-image: var(--icon-minus); + --pico-background-color: var(--pico-primary-background); + --pico-border-color: var(--pico-primary-border); + background-image: var(--pico-icon-minus); background-position: center; background-size: 0.75em auto; background-repeat: no-repeat; @@ -1346,67 +1639,84 @@ label > :where(input, select, textarea) { border-radius: 50%; } [type=radio]:checked, [type=radio]:checked:active, [type=radio]:checked:focus { - --background-color: var(--primary-inverse); + --pico-background-color: var(--pico-primary-inverse); border-width: 0.35em; background-image: none; } [type=checkbox][role=switch] { - --background-color: var(--switch-background-color); - --border-color: var(--switch-background-color); - --color: var(--switch-color); + --pico-background-color: var(--pico-switch-background-color); + --pico-color: var(--pico-switch-color); width: 2.25em; height: 1.25em; - border: var(--border-width) solid var(--border-color); + border: var(--pico-border-width) solid var(--pico-border-color); border-radius: 1.25em; - background-color: var(--background-color); + background-color: var(--pico-background-color); line-height: 1.25em; } -[type=checkbox][role=switch]:focus { - --background-color: var(--switch-background-color); - --border-color: var(--switch-background-color); -} -[type=checkbox][role=switch]:checked { - --background-color: var(--switch-checked-background-color); - --border-color: var(--switch-checked-background-color); +[type=checkbox][role=switch]:not([aria-invalid]) { + --pico-border-color: var(--pico-switch-background-color); } [type=checkbox][role=switch]:before { display: block; - width: calc(1.25em - (var(--border-width) * 2)); + aspect-ratio: 1; height: 100%; border-radius: 50%; - background-color: var(--color); + background-color: var(--pico-color); + box-shadow: var(--pico-switch-thumb-box-shadow); content: ""; transition: margin 0.1s ease-in-out; } +[type=checkbox][role=switch]:focus { + --pico-background-color: var(--pico-switch-background-color); + --pico-border-color: var(--pico-switch-background-color); +} [type=checkbox][role=switch]:checked { + --pico-background-color: var(--pico-switch-checked-background-color); + --pico-border-color: var(--pico-switch-checked-background-color); background-image: none; } [type=checkbox][role=switch]:checked::before { - margin-left: calc(1.125em - var(--border-width)); - margin-inline-start: calc(1.125em - var(--border-width)); + margin-inline-start: calc(2.25em - 1.25em); +} +[type=checkbox][role=switch][disabled] { + --pico-background-color: var(--pico-border-color); } -[type=checkbox][aria-invalid=false], -[type=checkbox]:checked[aria-invalid=false], -[type=radio][aria-invalid=false], -[type=radio]:checked[aria-invalid=false], -[type=checkbox][role=switch][aria-invalid=false], -[type=checkbox][role=switch]:checked[aria-invalid=false] { - --border-color: var(--form-element-valid-border-color); +[type=checkbox][aria-invalid=false]:checked, [type=checkbox][aria-invalid=false]:checked:active, [type=checkbox][aria-invalid=false]:checked:focus, +[type=checkbox][role=switch][aria-invalid=false]:checked, +[type=checkbox][role=switch][aria-invalid=false]:checked:active, +[type=checkbox][role=switch][aria-invalid=false]:checked:focus { + --pico-background-color: var(--pico-form-element-valid-border-color); +} +[type=checkbox]:checked[aria-invalid=true], [type=checkbox]:checked:active[aria-invalid=true], [type=checkbox]:checked:focus[aria-invalid=true], +[type=checkbox][role=switch]:checked[aria-invalid=true], +[type=checkbox][role=switch]:checked:active[aria-invalid=true], +[type=checkbox][role=switch]:checked:focus[aria-invalid=true] { + --pico-background-color: var(--pico-form-element-invalid-border-color); } -[type=checkbox][aria-invalid=true], -[type=checkbox]:checked[aria-invalid=true], -[type=radio][aria-invalid=true], + +[type=checkbox][aria-invalid=false]:checked, [type=checkbox][aria-invalid=false]:checked:active, [type=checkbox][aria-invalid=false]:checked:focus, +[type=radio][aria-invalid=false]:checked, +[type=radio][aria-invalid=false]:checked:active, +[type=radio][aria-invalid=false]:checked:focus, +[type=checkbox][role=switch][aria-invalid=false]:checked, +[type=checkbox][role=switch][aria-invalid=false]:checked:active, +[type=checkbox][role=switch][aria-invalid=false]:checked:focus { + --pico-border-color: var(--pico-form-element-valid-border-color); +} +[type=checkbox]:checked[aria-invalid=true], [type=checkbox]:checked:active[aria-invalid=true], [type=checkbox]:checked:focus[aria-invalid=true], [type=radio]:checked[aria-invalid=true], -[type=checkbox][role=switch][aria-invalid=true], -[type=checkbox][role=switch]:checked[aria-invalid=true] { - --border-color: var(--form-element-invalid-border-color); +[type=radio]:checked:active[aria-invalid=true], +[type=radio]:checked:focus[aria-invalid=true], +[type=checkbox][role=switch]:checked[aria-invalid=true], +[type=checkbox][role=switch]:checked:active[aria-invalid=true], +[type=checkbox][role=switch]:checked:focus[aria-invalid=true] { + --pico-border-color: var(--pico-form-element-invalid-border-color); } /** - * Form elements - * Alternatives input types (Not Checkboxes & Radios) + * Input type color */ [type=color]::-webkit-color-swatch-wrapper { padding: 0; @@ -1416,24 +1726,27 @@ label > :where(input, select, textarea) { } [type=color]::-webkit-color-swatch { border: 0; - border-radius: calc(var(--border-radius) * 0.5); + border-radius: calc(var(--pico-border-radius) * 0.5); } [type=color]::-moz-color-swatch { border: 0; - border-radius: calc(var(--border-radius) * 0.5); + border-radius: calc(var(--pico-border-radius) * 0.5); } +/** + * Input type datetime + */ input:not([type=checkbox], [type=radio], [type=range], [type=file]):is([type=date], [type=datetime-local], [type=month], [type=time], [type=week]) { - --icon-position: 0.75rem; - --icon-width: 1rem; - padding-right: calc(var(--icon-width) + var(--icon-position)); - background-image: var(--icon-date); - background-position: center right var(--icon-position); - background-size: var(--icon-width) auto; + --pico-icon-position: 0.75rem; + --pico-icon-width: 1rem; + padding-right: calc(var(--pico-icon-width) + var(--pico-icon-position)); + background-image: var(--pico-icon-date); + background-position: center right var(--pico-icon-position); + background-size: var(--pico-icon-width) auto; background-repeat: no-repeat; } input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=time] { - background-image: var(--icon-time); + background-image: var(--pico-icon-time); } [type=date]::-webkit-calendar-picker-indicator, @@ -1441,114 +1754,53 @@ input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=time] { [type=month]::-webkit-calendar-picker-indicator, [type=time]::-webkit-calendar-picker-indicator, [type=week]::-webkit-calendar-picker-indicator { - width: var(--icon-width); - margin-right: calc(var(--icon-width) * -1); - margin-left: var(--icon-position); + width: var(--pico-icon-width); + margin-right: calc(var(--pico-icon-width) * -1); + margin-left: var(--pico-icon-position); opacity: 0; } -[dir=rtl] :is([type=date], [type=datetime-local], [type=month], [type=time], [type=week]) { - text-align: right; -} - @-moz-document url-prefix() { [type=date], [type=datetime-local], [type=month], [type=time], [type=week] { - padding-right: var(--form-element-spacing-horizontal) !important; + padding-right: var(--pico-form-element-spacing-horizontal) !important; background-image: none !important; } } +[dir=rtl] :is([type=date], [type=datetime-local], [type=month], [type=time], [type=week]) { + text-align: right; +} + +/** + * Input type file + */ [type=file] { - --color: var(--muted-color); - padding: calc(var(--form-element-spacing-vertical) * 0.5) 0; + --pico-color: var(--pico-muted-color); + margin-left: calc(var(--pico-outline-width) * -1); + padding: calc(var(--pico-form-element-spacing-vertical) * 0.5) 0; + padding-left: var(--pico-outline-width); border: 0; border-radius: 0; background: none; } [type=file]::file-selector-button { - --background-color: var(--secondary); - --border-color: var(--secondary); - --color: var(--secondary-inverse); - margin-right: calc(var(--spacing) / 2); - margin-left: 0; - margin-inline-start: 0; - margin-inline-end: calc(var(--spacing) / 2); - padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5); - border: var(--border-width) solid var(--border-color); - border-radius: var(--border-radius); - outline: none; - background-color: var(--background-color); - box-shadow: var(--box-shadow); - color: var(--color); - font-weight: var(--font-weight); - font-size: 1rem; - line-height: var(--line-height); - text-align: center; - cursor: pointer; - transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); + margin-right: calc(var(--pico-spacing) / 2); + padding: calc(var(--pico-form-element-spacing-vertical) * 0.5) var(--pico-form-element-spacing-horizontal); } -[type=file]::file-selector-button:is(:hover, :active, :focus) { - --background-color: var(--secondary-hover); - --border-color: var(--secondary-hover); -} -[type=file]::-webkit-file-upload-button { - --background-color: var(--secondary); - --border-color: var(--secondary); - --color: var(--secondary-inverse); - margin-right: calc(var(--spacing) / 2); - margin-left: 0; - margin-inline-start: 0; - margin-inline-end: calc(var(--spacing) / 2); - padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5); - border: var(--border-width) solid var(--border-color); - border-radius: var(--border-radius); - outline: none; - background-color: var(--background-color); - box-shadow: var(--box-shadow); - color: var(--color); - font-weight: var(--font-weight); - font-size: 1rem; - line-height: var(--line-height); - text-align: center; - cursor: pointer; - -webkit-transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); - transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); -} -[type=file]::-webkit-file-upload-button:is(:hover, :active, :focus) { - --background-color: var(--secondary-hover); - --border-color: var(--secondary-hover); -} -[type=file]::-ms-browse { - --background-color: var(--secondary); - --border-color: var(--secondary); - --color: var(--secondary-inverse); - margin-right: calc(var(--spacing) / 2); - margin-left: 0; - margin-inline-start: 0; - margin-inline-end: calc(var(--spacing) / 2); - padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5); - border: var(--border-width) solid var(--border-color); - border-radius: var(--border-radius); - outline: none; - background-color: var(--background-color); - box-shadow: var(--box-shadow); - color: var(--color); - font-weight: var(--font-weight); - font-size: 1rem; - line-height: var(--line-height); - text-align: center; - cursor: pointer; - -ms-transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); - transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); +[type=file]:is(:hover, :active, :focus)::file-selector-button { + --pico-background-color: var(--pico-secondary-hover-background); + --pico-border-color: var(--pico-secondary-hover-border); } -[type=file]::-ms-browse:is(:hover, :active, :focus) { - --background-color: var(--secondary-hover); - --border-color: var(--secondary-hover); +[type=file]:focus::file-selector-button { + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), 0 0 0 var(--pico-outline-width) var(--pico-secondary-focus); } +/** + * Input type range + */ [type=range] { -webkit-appearance: none; -moz-appearance: none; @@ -1559,70 +1811,67 @@ input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=time] { } [type=range]::-webkit-slider-runnable-track { width: 100%; - height: 0.25rem; - border-radius: var(--border-radius); - background-color: var(--range-border-color); - -webkit-transition: background-color var(--transition), box-shadow var(--transition); - transition: background-color var(--transition), box-shadow var(--transition); + height: 0.375rem; + border-radius: var(--pico-border-radius); + background-color: var(--pico-range-border-color); + -webkit-transition: background-color var(--pico-transition), box-shadow var(--pico-transition); + transition: background-color var(--pico-transition), box-shadow var(--pico-transition); } [type=range]::-moz-range-track { width: 100%; - height: 0.25rem; - border-radius: var(--border-radius); - background-color: var(--range-border-color); - -moz-transition: background-color var(--transition), box-shadow var(--transition); - transition: background-color var(--transition), box-shadow var(--transition); + height: 0.375rem; + border-radius: var(--pico-border-radius); + background-color: var(--pico-range-border-color); + -moz-transition: background-color var(--pico-transition), box-shadow var(--pico-transition); + transition: background-color var(--pico-transition), box-shadow var(--pico-transition); } [type=range]::-ms-track { width: 100%; - height: 0.25rem; - border-radius: var(--border-radius); - background-color: var(--range-border-color); - -ms-transition: background-color var(--transition), box-shadow var(--transition); - transition: background-color var(--transition), box-shadow var(--transition); + height: 0.375rem; + border-radius: var(--pico-border-radius); + background-color: var(--pico-range-border-color); + -ms-transition: background-color var(--pico-transition), box-shadow var(--pico-transition); + transition: background-color var(--pico-transition), box-shadow var(--pico-transition); } [type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 1.25rem; height: 1.25rem; - margin-top: -0.5rem; - border: 2px solid var(--range-thumb-border-color); + margin-top: -0.4375rem; + border: 2px solid var(--pico-range-thumb-border-color); border-radius: 50%; - background-color: var(--range-thumb-color); + background-color: var(--pico-range-thumb-color); cursor: pointer; - -webkit-transition: background-color var(--transition), transform var(--transition); - transition: background-color var(--transition), transform var(--transition); + -webkit-transition: background-color var(--pico-transition), transform var(--pico-transition); + transition: background-color var(--pico-transition), transform var(--pico-transition); } [type=range]::-moz-range-thumb { -webkit-appearance: none; width: 1.25rem; height: 1.25rem; - margin-top: -0.5rem; - border: 2px solid var(--range-thumb-border-color); + margin-top: -0.4375rem; + border: 2px solid var(--pico-range-thumb-border-color); border-radius: 50%; - background-color: var(--range-thumb-color); + background-color: var(--pico-range-thumb-color); cursor: pointer; - -moz-transition: background-color var(--transition), transform var(--transition); - transition: background-color var(--transition), transform var(--transition); + -moz-transition: background-color var(--pico-transition), transform var(--pico-transition); + transition: background-color var(--pico-transition), transform var(--pico-transition); } [type=range]::-ms-thumb { -webkit-appearance: none; width: 1.25rem; height: 1.25rem; - margin-top: -0.5rem; - border: 2px solid var(--range-thumb-border-color); + margin-top: -0.4375rem; + border: 2px solid var(--pico-range-thumb-border-color); border-radius: 50%; - background-color: var(--range-thumb-color); + background-color: var(--pico-range-thumb-color); cursor: pointer; - -ms-transition: background-color var(--transition), transform var(--transition); - transition: background-color var(--transition), transform var(--transition); + -ms-transition: background-color var(--pico-transition), transform var(--pico-transition); + transition: background-color var(--pico-transition), transform var(--pico-transition); } -[type=range]:hover, [type=range]:focus { - --range-border-color: var(--range-active-border-color); - --range-thumb-color: var(--range-thumb-hover-color); -} -[type=range]:active { - --range-thumb-color: var(--range-thumb-active-color); +[type=range]:active, [type=range]:focus-within { + --pico-range-border-color: var(--pico-range-active-border-color); + --pico-range-thumb-color: var(--pico-range-thumb-active-color); } [type=range]:active::-webkit-slider-thumb { transform: scale(1.25); @@ -1634,28 +1883,25 @@ input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=time] { transform: scale(1.25); } +/** + * Input type search + */ input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search] { - padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem); - border-radius: 5rem; - background-image: var(--icon-search); - background-position: center left 1.125rem; + padding-inline-start: calc(var(--pico-form-element-spacing-horizontal) + 1.75rem); + background-image: var(--pico-icon-search); + background-position: center left calc(var(--pico-form-element-spacing-horizontal) + 0.125rem); background-size: 1rem auto; background-repeat: no-repeat; } input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid] { - padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem) !important; + padding-inline-start: calc(var(--pico-form-element-spacing-horizontal) + 1.75rem) !important; background-position: center left 1.125rem, center right 0.75rem; } input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid=false] { - background-image: var(--icon-search), var(--icon-valid); + background-image: var(--pico-icon-search), var(--pico-icon-valid); } input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid=true] { - background-image: var(--icon-search), var(--icon-invalid); -} - -[type=search]::-webkit-search-cancel-button { - -webkit-appearance: none; - display: none; + background-image: var(--pico-icon-search), var(--pico-icon-invalid); } [dir=rtl] :where(input):not([type=checkbox], [type=radio], [type=range], [type=file])[type=search] { @@ -1665,141 +1911,21 @@ input:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search] background-position: center right 1.125rem, center left 0.75rem; } -/** - * Table - */ -:where(table) { - width: 100%; - border-collapse: collapse; - border-spacing: 0; - text-indent: 0; -} - -th, -td { - padding: calc(var(--spacing) / 2) var(--spacing); - border-bottom: var(--border-width) solid var(--table-border-color); - color: var(--color); - font-weight: var(--font-weight); - font-size: var(--font-size); - text-align: left; - text-align: start; -} - -tfoot th, -tfoot td { - border-top: var(--border-width) solid var(--table-border-color); - border-bottom: 0; -} - -table[role=grid] tbody tr:nth-child(odd) { - background-color: var(--table-row-stripped-background-color); -} - -/** - * Code - */ -pre, -code, -kbd, -samp { - font-size: 0.875em; - font-family: var(--font-family); -} - -pre { - -ms-overflow-style: scrollbar; - overflow: auto; -} - -pre, -code, -kbd { - border-radius: var(--border-radius); - background: var(--code-background-color); - color: var(--code-color); - font-weight: var(--font-weight); - line-height: initial; -} - -code, -kbd { - display: inline-block; - padding: 0.375rem 0.5rem; -} - -pre { - display: block; - margin-bottom: var(--spacing); - overflow-x: auto; -} -pre > code { - display: block; - padding: var(--spacing); - background: none; - font-size: 14px; - line-height: var(--line-height); -} - -code b { - color: var(--code-tag-color); - font-weight: var(--font-weight); -} -code i { - color: var(--code-property-color); - font-style: normal; -} -code u { - color: var(--code-value-color); - text-decoration: none; -} -code em { - color: var(--code-comment-color); - font-style: normal; -} - -kbd { - background-color: var(--code-kbd-background-color); - color: var(--code-kbd-color); - vertical-align: baseline; -} - -/** - * Miscs - */ -hr { - height: 0; - border: 0; - border-top: 1px solid var(--muted-border-color); - color: inherit; -} - -[hidden], -template { - display: none !important; -} - -canvas { - display: inline-block; -} - /** * Accordion (
) */ details { display: block; - margin-bottom: var(--spacing); - padding-bottom: var(--spacing); - border-bottom: var(--border-width) solid var(--accordion-border-color); + margin-bottom: var(--pico-spacing); } details summary { line-height: 1rem; list-style-type: none; cursor: pointer; - transition: color var(--transition); + transition: color var(--pico-transition); } details summary:not([role]) { - color: var(--accordion-close-summary-color); + color: var(--pico-accordion-close-summary-color); } details summary::-webkit-details-marker { display: none; @@ -1814,88 +1940,410 @@ details summary::after { display: block; width: 1rem; height: 1rem; - margin-inline-start: calc(var(--spacing, 1rem) * 0.5); + margin-inline-start: calc(var(--pico-spacing, 1rem) * 0.5); float: right; transform: rotate(-90deg); - background-image: var(--icon-chevron); + background-image: var(--pico-icon-chevron); background-position: right center; background-size: 1rem auto; background-repeat: no-repeat; content: ""; - transition: transform var(--transition); + transition: transform var(--pico-transition); } details summary:focus { outline: none; } -details summary:focus:not([role=button]) { - color: var(--accordion-active-summary-color); +details summary:focus:not([role]) { + color: var(--pico-accordion-active-summary-color); +} +details summary:focus-visible:not([role]) { + outline: var(--pico-outline-width) solid var(--pico-primary-focus); + outline-offset: calc(var(--pico-spacing, 1rem) * 0.5); + color: var(--pico-primary); } details summary[role=button] { width: 100%; text-align: left; } details summary[role=button]::after { - height: calc(1rem * var(--line-height, 1.5)); - background-image: var(--icon-chevron-button); + height: calc(1rem * var(--pico-line-height, 1.5)); +} +details[open] > summary { + margin-bottom: var(--pico-spacing); +} +details[open] > summary:not([role]):not(:focus) { + color: var(--pico-accordion-open-summary-color); +} +details[open] > summary::after { + transform: rotate(0); +} + +[dir=rtl] details summary { + text-align: right; +} +[dir=rtl] details summary::after { + float: left; + background-position: left center; +} + +/** + * Card (
) + */ +article { + margin-bottom: var(--pico-block-spacing-vertical); + padding: var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal); + border-radius: var(--pico-border-radius); + background: var(--pico-card-background-color); + box-shadow: var(--pico-card-box-shadow); +} +article > header, +article > footer { + margin-right: calc(var(--pico-block-spacing-horizontal) * -1); + margin-left: calc(var(--pico-block-spacing-horizontal) * -1); + padding: calc(var(--pico-block-spacing-vertical) * 0.66) var(--pico-block-spacing-horizontal); + background-color: var(--pico-card-sectioning-background-color); +} +article > header { + margin-top: calc(var(--pico-block-spacing-vertical) * -1); + margin-bottom: var(--pico-block-spacing-vertical); + border-bottom: var(--pico-border-width) solid var(--pico-card-border-color); + border-top-right-radius: var(--pico-border-radius); + border-top-left-radius: var(--pico-border-radius); +} +article > footer { + margin-top: var(--pico-block-spacing-vertical); + margin-bottom: calc(var(--pico-block-spacing-vertical) * -1); + border-top: var(--pico-border-width) solid var(--pico-card-border-color); + border-bottom-right-radius: var(--pico-border-radius); + border-bottom-left-radius: var(--pico-border-radius); +} + +/** + * Dropdown (details.dropdown) + */ +details.dropdown { + position: relative; + border-bottom: none; +} +details.dropdown > summary::after, +details.dropdown > button::after, +details.dropdown > a::after { + display: block; + width: 1rem; + height: calc(1rem * var(--pico-line-height, 1.5)); + margin-inline-start: 0.25rem; + float: right; + transform: rotate(0deg) translateX(0.2rem); + background-image: var(--pico-icon-chevron); + background-position: right center; + background-size: 1rem auto; + background-repeat: no-repeat; + content: ""; +} + +nav details.dropdown { + margin-bottom: 0; +} + +details.dropdown > summary:not([role]) { + height: calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2); + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal); + border: var(--pico-border-width) solid var(--pico-form-element-border-color); + border-radius: var(--pico-border-radius); + background-color: var(--pico-form-element-background-color); + color: var(--pico-form-element-placeholder-color); + line-height: inherit; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + transition: background-color var(--pico-transition), border-color var(--pico-transition), color var(--pico-transition), box-shadow var(--pico-transition); +} +details.dropdown > summary:not([role]):active, details.dropdown > summary:not([role]):focus { + border-color: var(--pico-form-element-active-border-color); + background-color: var(--pico-form-element-active-background-color); +} +details.dropdown > summary:not([role]):focus { + box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color); +} +details.dropdown > summary:not([role]):focus-visible { + outline: none; +} +details.dropdown > summary:not([role])[aria-invalid=false] { + --pico-form-element-border-color: var(--pico-form-element-valid-border-color); + --pico-form-element-active-border-color: var(--pico-form-element-valid-focus-color); + --pico-form-element-focus-color: var(--pico-form-element-valid-focus-color); +} +details.dropdown > summary:not([role])[aria-invalid=true] { + --pico-form-element-border-color: var(--pico-form-element-invalid-border-color); + --pico-form-element-active-border-color: var(--pico-form-element-invalid-focus-color); + --pico-form-element-focus-color: var(--pico-form-element-invalid-focus-color); +} + +nav details.dropdown { + display: inline; + margin: calc(var(--pico-nav-element-spacing-vertical) * -1) 0; +} +nav details.dropdown > summary::after { + transform: rotate(0deg) translateX(0rem); +} +nav details.dropdown > summary:not([role]) { + height: calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 2); + padding: calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal); +} +nav details.dropdown > summary:not([role]):focus-visible { + box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-primary-focus); +} + +details.dropdown > summary + ul { + display: flex; + z-index: 99; + position: absolute; + left: 0; + flex-direction: column; + width: 100%; + min-width: -moz-fit-content; + min-width: fit-content; + margin: 0; + margin-top: var(--pico-outline-width); + padding: 0; + border: var(--pico-border-width) solid var(--pico-dropdown-border-color); + border-radius: var(--pico-border-radius); + background-color: var(--pico-dropdown-background-color); + box-shadow: var(--pico-dropdown-box-shadow); + color: var(--pico-dropdown-color); + white-space: nowrap; + opacity: 0; + transition: opacity var(--pico-transition), transform 0s ease-in-out 1s; +} +details.dropdown > summary + ul[dir=rtl] { + right: 0; + left: auto; +} +details.dropdown > summary + ul li { + width: 100%; + margin-bottom: 0; + padding: calc(var(--pico-form-element-spacing-vertical) * 0.5) var(--pico-form-element-spacing-horizontal); + list-style: none; +} +details.dropdown > summary + ul li:first-of-type { + margin-top: calc(var(--pico-form-element-spacing-vertical) * 0.5); +} +details.dropdown > summary + ul li:last-of-type { + margin-bottom: calc(var(--pico-form-element-spacing-vertical) * 0.5); +} +details.dropdown > summary + ul li a { + display: block; + margin: calc(var(--pico-form-element-spacing-vertical) * -0.5) calc(var(--pico-form-element-spacing-horizontal) * -1); + padding: calc(var(--pico-form-element-spacing-vertical) * 0.5) var(--pico-form-element-spacing-horizontal); + overflow: hidden; + border-radius: 0; + color: var(--pico-dropdown-color); + text-decoration: none; + text-overflow: ellipsis; +} +details.dropdown > summary + ul li a:hover, details.dropdown > summary + ul li a:focus, details.dropdown > summary + ul li a:active, details.dropdown > summary + ul li a:focus-visible, details.dropdown > summary + ul li a[aria-current]:not([aria-current=false]) { + background-color: var(--pico-dropdown-hover-background-color); +} +details.dropdown > summary + ul li label { + width: 100%; +} +details.dropdown > summary + ul li:has(label):hover { + background-color: var(--pico-dropdown-hover-background-color); +} + +details.dropdown[open] > summary { + margin-bottom: 0; +} + +details.dropdown[open] > summary + ul { + transform: scaleY(1); + opacity: 1; + transition: opacity var(--pico-transition), transform 0s ease-in-out 0s; +} + +details.dropdown[open] > summary::before { + display: block; + z-index: 1; + position: fixed; + width: 100vw; + height: 100vh; + inset: 0; + background: none; + content: ""; + cursor: default; } -details summary[role=button]:not(.outline).contrast::after { - background-image: var(--icon-chevron-button-inverse); + +label > details.dropdown { + margin-top: calc(var(--pico-spacing) * 0.25); +} + +/** + * Group ([role="group"], [role="search"]) + */ +[role=search], +[role=group] { + display: inline-flex; + position: relative; + width: 100%; + margin-bottom: var(--pico-spacing); + border-radius: var(--pico-border-radius); + box-shadow: var(--pico-group-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); + vertical-align: middle; + transition: box-shadow var(--pico-transition); +} +[role=search] > *, +[role=search] input:not([type=checkbox], [type=radio]), +[role=search] select, +[role=group] > *, +[role=group] input:not([type=checkbox], [type=radio]), +[role=group] select { + position: relative; + flex: 1 1 auto; + margin-bottom: 0; } -details[open] > summary { - margin-bottom: calc(var(--spacing)); +[role=search] > *:not(:first-child), +[role=search] input:not([type=checkbox], [type=radio]):not(:first-child), +[role=search] select:not(:first-child), +[role=group] > *:not(:first-child), +[role=group] input:not([type=checkbox], [type=radio]):not(:first-child), +[role=group] select:not(:first-child) { + margin-left: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } -details[open] > summary:not([role]):not(:focus) { - color: var(--accordion-open-summary-color); +[role=search] > *:not(:last-child), +[role=search] input:not([type=checkbox], [type=radio]):not(:last-child), +[role=search] select:not(:last-child), +[role=group] > *:not(:last-child), +[role=group] input:not([type=checkbox], [type=radio]):not(:last-child), +[role=group] select:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } -details[open] > summary::after { - transform: rotate(0); +[role=search] > *:focus, +[role=search] input:not([type=checkbox], [type=radio]):focus, +[role=search] select:focus, +[role=group] > *:focus, +[role=group] input:not([type=checkbox], [type=radio]):focus, +[role=group] select:focus { + z-index: 2; +} +[role=search] button:not(:first-child), +[role=search] [type=submit]:not(:first-child), +[role=search] [type=reset]:not(:first-child), +[role=search] [type=button]:not(:first-child), +[role=search] [role=button]:not(:first-child), +[role=search] input:not([type=checkbox], [type=radio]):not(:first-child), +[role=search] select:not(:first-child), +[role=group] button:not(:first-child), +[role=group] [type=submit]:not(:first-child), +[role=group] [type=reset]:not(:first-child), +[role=group] [type=button]:not(:first-child), +[role=group] [role=button]:not(:first-child), +[role=group] input:not([type=checkbox], [type=radio]):not(:first-child), +[role=group] select:not(:first-child) { + margin-left: calc(var(--pico-border-width) * -1); +} +[role=search] button, +[role=search] [type=submit], +[role=search] [type=reset], +[role=search] [type=button], +[role=search] [role=button], +[role=group] button, +[role=group] [type=submit], +[role=group] [type=reset], +[role=group] [type=button], +[role=group] [role=button] { + width: auto; +} +@supports selector(:has(*)) { + [role=search]:has(button:focus, [type=submit]:focus, [type=button]:focus, [role=button]:focus), + [role=group]:has(button:focus, [type=submit]:focus, [type=button]:focus, [role=button]:focus) { + --pico-group-box-shadow: var(--pico-group-box-shadow-focus-with-button); + } + [role=search]:has(button:focus, [type=submit]:focus, [type=button]:focus, [role=button]:focus) input:not([type=checkbox], [type=radio]), + [role=search]:has(button:focus, [type=submit]:focus, [type=button]:focus, [role=button]:focus) select, + [role=group]:has(button:focus, [type=submit]:focus, [type=button]:focus, [role=button]:focus) input:not([type=checkbox], [type=radio]), + [role=group]:has(button:focus, [type=submit]:focus, [type=button]:focus, [role=button]:focus) select { + border-color: transparent; + } + [role=search]:has(input:not([type=submit], [type=button]):focus, select:focus), + [role=group]:has(input:not([type=submit], [type=button]):focus, select:focus) { + --pico-group-box-shadow: var(--pico-group-box-shadow-focus-with-input); + } + [role=search]:has(input:not([type=submit], [type=button]):focus, select:focus) button, + [role=search]:has(input:not([type=submit], [type=button]):focus, select:focus) [type=submit], + [role=search]:has(input:not([type=submit], [type=button]):focus, select:focus) [type=button], + [role=search]:has(input:not([type=submit], [type=button]):focus, select:focus) [role=button], + [role=group]:has(input:not([type=submit], [type=button]):focus, select:focus) button, + [role=group]:has(input:not([type=submit], [type=button]):focus, select:focus) [type=submit], + [role=group]:has(input:not([type=submit], [type=button]):focus, select:focus) [type=button], + [role=group]:has(input:not([type=submit], [type=button]):focus, select:focus) [role=button] { + --pico-button-box-shadow: 0 0 0 var(--pico-border-width) var(--pico-primary-border); + --pico-button-hover-box-shadow: 0 0 0 var(--pico-border-width) var(--pico-primary-hover-border); + } + [role=search] button:focus, + [role=search] [type=submit]:focus, + [role=search] [type=reset]:focus, + [role=search] [type=button]:focus, + [role=search] [role=button]:focus, + [role=group] button:focus, + [role=group] [type=submit]:focus, + [role=group] [type=reset]:focus, + [role=group] [type=button]:focus, + [role=group] [role=button]:focus { + box-shadow: none; + } } -[dir=rtl] details summary { - text-align: right; +[role=search] > *:first-child { + border-top-left-radius: 5rem; + border-bottom-left-radius: 5rem; } -[dir=rtl] details summary::after { - float: left; - background-position: left center; +[role=search] > *:last-child { + border-top-right-radius: 5rem; + border-bottom-right-radius: 5rem; } /** - * Card (
) + * Loading ([aria-busy=true]) */ -article { - margin: var(--block-spacing-vertical) 0; - padding: var(--block-spacing-vertical) var(--block-spacing-horizontal); - border-radius: var(--border-radius); - background: var(--card-background-color); - box-shadow: var(--card-box-shadow); +[aria-busy=true]:not(input, select, textarea, html, form) { + white-space: nowrap; } -article > header, -article > footer { - margin-right: calc(var(--block-spacing-horizontal) * -1); - margin-left: calc(var(--block-spacing-horizontal) * -1); - padding: calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal); - background-color: var(--card-sectionning-background-color); +[aria-busy=true]:not(input, select, textarea, html, form)::before { + display: inline-block; + width: 1em; + height: 1em; + background-image: var(--pico-icon-loading); + background-size: 1em auto; + background-repeat: no-repeat; + content: ""; + vertical-align: -0.125em; } -article > header { - margin-top: calc(var(--block-spacing-vertical) * -1); - margin-bottom: var(--block-spacing-vertical); - border-bottom: var(--border-width) solid var(--card-border-color); - border-top-right-radius: var(--border-radius); - border-top-left-radius: var(--border-radius); +[aria-busy=true]:not(input, select, textarea, html, form):not(:empty)::before { + margin-inline-end: calc(var(--pico-spacing) * 0.5); } -article > footer { - margin-top: var(--block-spacing-vertical); - margin-bottom: calc(var(--block-spacing-vertical) * -1); - border-top: var(--border-width) solid var(--card-border-color); - border-bottom-right-radius: var(--border-radius); - border-bottom-left-radius: var(--border-radius); +[aria-busy=true]:not(input, select, textarea, html, form):empty { + text-align: center; +} + +button[aria-busy=true], +[type=submit][aria-busy=true], +[type=button][aria-busy=true], +[type=reset][aria-busy=true], +[role=button][aria-busy=true], +a[aria-busy=true] { + pointer-events: none; } /** * Modal () */ -:root { - --scrollbar-width: 0px; +:root, +:host { + --pico-scrollbar-width: 0px; } dialog { @@ -1912,63 +2360,66 @@ dialog { min-width: 100%; height: inherit; min-height: 100%; - padding: var(--spacing); + padding: 0; border: 0; - -webkit-backdrop-filter: var(--modal-overlay-backdrop-filter); - backdrop-filter: var(--modal-overlay-backdrop-filter); - background-color: var(--modal-overlay-background-color); - color: var(--color); + -webkit-backdrop-filter: var(--pico-modal-overlay-backdrop-filter); + backdrop-filter: var(--pico-modal-overlay-backdrop-filter); + background-color: var(--pico-modal-overlay-background-color); + color: var(--pico-color); } -dialog article { - max-height: calc(100vh - var(--spacing) * 2); +dialog > article { + width: 100%; + max-height: calc(100vh - var(--pico-spacing) * 2); + margin: var(--pico-spacing); overflow: auto; } @media (min-width: 576px) { - dialog article { + dialog > article { max-width: 510px; } } @media (min-width: 768px) { - dialog article { + dialog > article { max-width: 700px; } } -dialog article > header, -dialog article > footer { - padding: calc(var(--block-spacing-vertical) * 0.5) var(--block-spacing-horizontal); +dialog > article > header > * { + margin-bottom: 0; } -dialog article > header .close { +dialog > article > header .close, dialog > article > header :is(a, button)[rel=prev] { margin: 0; - margin-left: var(--spacing); + margin-left: var(--pico-spacing); + padding: 0; float: right; } -dialog article > footer { +dialog > article > footer { text-align: right; } -dialog article > footer [role=button] { +dialog > article > footer button, +dialog > article > footer [role=button] { margin-bottom: 0; } -dialog article > footer [role=button]:not(:first-of-type) { - margin-left: calc(var(--spacing) * 0.5); -} -dialog article p:last-of-type { - margin: 0; +dialog > article > footer button:not(:first-of-type), +dialog > article > footer [role=button]:not(:first-of-type) { + margin-left: calc(var(--pico-spacing) * 0.5); } -dialog article .close { +dialog > article .close, dialog > article :is(a, button)[rel=prev] { display: block; width: 1rem; height: 1rem; - margin-top: calc(var(--block-spacing-vertical) * -0.5); - margin-bottom: var(--typography-spacing-vertical); + margin-top: calc(var(--pico-spacing) * -1); + margin-bottom: var(--pico-spacing); margin-left: auto; - background-image: var(--icon-close); + border: none; + background-image: var(--pico-icon-close); background-position: center; background-size: auto 1rem; background-repeat: no-repeat; + background-color: transparent; opacity: 0.5; - transition: opacity var(--transition); + transition: opacity var(--pico-transition); } -dialog article .close:is([aria-current], :hover, :active, :focus) { +dialog > article .close:is([aria-current]:not([aria-current=false]), :hover, :active, :focus), dialog > article :is(a, button)[rel=prev]:is([aria-current]:not([aria-current=false]), :hover, :active, :focus) { opacity: 1; } dialog:not([open]), dialog[open=false] { @@ -1976,13 +2427,14 @@ dialog:not([open]), dialog[open=false] { } .modal-is-open { - padding-right: var(--scrollbar-width, 0px); + padding-right: var(--pico-scrollbar-width, 0px); overflow: hidden; pointer-events: none; touch-action: none; } .modal-is-open dialog { pointer-events: auto; + touch-action: auto; } :where(.modal-is-opening, .modal-is-closing) dialog, @@ -2034,6 +2486,7 @@ nav ul { nav { justify-content: space-between; + overflow: visible; } nav ol, nav ul { @@ -2044,56 +2497,65 @@ nav ul { } nav ol:first-of-type, nav ul:first-of-type { - margin-left: calc(var(--nav-element-spacing-horizontal) * -1); + margin-left: calc(var(--pico-nav-element-spacing-horizontal) * -1); } nav ol:last-of-type, nav ul:last-of-type { - margin-right: calc(var(--nav-element-spacing-horizontal) * -1); + margin-right: calc(var(--pico-nav-element-spacing-horizontal) * -1); } nav li { display: inline-block; margin: 0; - padding: var(--nav-element-spacing-vertical) var(--nav-element-spacing-horizontal); + padding: var(--pico-nav-element-spacing-vertical) var(--pico-nav-element-spacing-horizontal); } -nav li > * { - --spacing: 0; -} -nav :where(a, [role=link]) { +nav li :where(a, [role=link]) { display: inline-block; - margin: calc(var(--nav-link-spacing-vertical) * -1) calc(var(--nav-link-spacing-horizontal) * -1); - padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); - border-radius: var(--border-radius); - text-decoration: none; + margin: calc(var(--pico-nav-link-spacing-vertical) * -1) calc(var(--pico-nav-link-spacing-horizontal) * -1); + padding: var(--pico-nav-link-spacing-vertical) var(--pico-nav-link-spacing-horizontal); + border-radius: var(--pico-border-radius); } -nav :where(a, [role=link]):is([aria-current], :hover, :active, :focus) { +nav li :where(a, [role=link]):not(:hover) { text-decoration: none; } +nav li button, +nav li [role=button], +nav li [type=button], +nav li input:not([type=checkbox], [type=radio], [type=range], [type=file]), +nav li select { + height: auto; + margin-right: inherit; + margin-bottom: 0; + margin-left: inherit; + padding: calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal); +} nav[aria-label=breadcrumb] { align-items: center; justify-content: start; } nav[aria-label=breadcrumb] ul li:not(:first-child) { - margin-inline-start: var(--nav-link-spacing-horizontal); + margin-inline-start: var(--pico-nav-link-spacing-horizontal); +} +nav[aria-label=breadcrumb] ul li a { + margin: calc(var(--pico-nav-link-spacing-vertical) * -1) 0; + margin-inline-start: calc(var(--pico-nav-link-spacing-horizontal) * -1); } -nav[aria-label=breadcrumb] ul li:not(:last-child) ::after { +nav[aria-label=breadcrumb] ul li:not(:last-child)::after { + display: inline-block; position: absolute; - width: calc(var(--nav-link-spacing-horizontal) * 2); - margin-inline-start: calc(var(--nav-link-spacing-horizontal) / 2); - content: "/"; - color: var(--muted-color); + width: calc(var(--pico-nav-link-spacing-horizontal) * 4); + margin: 0 calc(var(--pico-nav-link-spacing-horizontal) * -1); + content: var(--pico-nav-breadcrumb-divider); + color: var(--pico-muted-color); text-align: center; + text-decoration: none; + white-space: nowrap; } -nav[aria-label=breadcrumb] a[aria-current] { +nav[aria-label=breadcrumb] a[aria-current]:not([aria-current=false]) { background-color: transparent; color: inherit; text-decoration: none; pointer-events: none; } -nav [role=button] { - margin-right: inherit; - margin-left: inherit; - padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); -} aside nav, aside ol, @@ -2102,7 +2564,7 @@ aside li { display: block; } aside li { - padding: calc(var(--nav-element-spacing-vertical) * 0.5) var(--nav-element-spacing-horizontal); + padding: calc(var(--pico-nav-element-spacing-vertical) * 0.5) var(--pico-nav-element-spacing-horizontal); } aside li a { display: block; @@ -2130,26 +2592,28 @@ progress { appearance: none; width: 100%; height: 0.5rem; - margin-bottom: calc(var(--spacing) * 0.5); + margin-bottom: calc(var(--pico-spacing) * 0.5); overflow: hidden; border: 0; - border-radius: var(--border-radius); - background-color: var(--progress-background-color); - color: var(--progress-color); + border-radius: var(--pico-border-radius); + background-color: var(--pico-progress-background-color); + color: var(--pico-progress-color); } progress::-webkit-progress-bar { - border-radius: var(--border-radius); + border-radius: var(--pico-border-radius); background: none; } progress[value]::-webkit-progress-value { - background-color: var(--progress-color); + background-color: var(--pico-progress-color); + -webkit-transition: inline-size var(--pico-transition); + transition: inline-size var(--pico-transition); } progress::-moz-progress-bar { - background-color: var(--progress-color); + background-color: var(--pico-progress-color); } @media (prefers-reduced-motion: no-preference) { progress:indeterminate { - background: var(--progress-background-color) linear-gradient(to right, var(--progress-color) 30%, var(--progress-background-color) 30%) top left/150% 150% no-repeat; + background: var(--pico-progress-background-color) linear-gradient(to right, var(--pico-progress-color) 30%, var(--pico-progress-background-color) 30%) top left/150% 150% no-repeat; animation: progress-indeterminate 1s linear infinite; } progress:indeterminate[value]::-webkit-progress-value { @@ -2174,228 +2638,13 @@ progress::-moz-progress-bar { background-position: -200% 0; } } -/** - * Dropdown ([role="list"]) - */ -details[role=list], -li[role=list] { - position: relative; -} - -details[role=list] summary + ul, -li[role=list] > ul { - display: flex; - z-index: 99; - position: absolute; - top: auto; - right: 0; - left: 0; - flex-direction: column; - margin: 0; - padding: 0; - border: var(--border-width) solid var(--dropdown-border-color); - border-radius: var(--border-radius); - border-top-right-radius: 0; - border-top-left-radius: 0; - background-color: var(--dropdown-background-color); - box-shadow: var(--card-box-shadow); - color: var(--dropdown-color); - white-space: nowrap; -} -details[role=list] summary + ul li, -li[role=list] > ul li { - width: 100%; - margin-bottom: 0; - padding: calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal); - list-style: none; -} -details[role=list] summary + ul li:first-of-type, -li[role=list] > ul li:first-of-type { - margin-top: calc(var(--form-element-spacing-vertical) * 0.5); -} -details[role=list] summary + ul li:last-of-type, -li[role=list] > ul li:last-of-type { - margin-bottom: calc(var(--form-element-spacing-vertical) * 0.5); -} -details[role=list] summary + ul li a, -li[role=list] > ul li a { - display: block; - margin: calc(var(--form-element-spacing-vertical) * -0.5) calc(var(--form-element-spacing-horizontal) * -1); - padding: calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal); - overflow: hidden; - color: var(--dropdown-color); - text-decoration: none; - text-overflow: ellipsis; -} -details[role=list] summary + ul li a:hover, -li[role=list] > ul li a:hover { - background-color: var(--dropdown-hover-background-color); -} - -details[role=list] summary::after, -li[role=list] > a::after { - display: block; - width: 1rem; - height: calc(1rem * var(--line-height, 1.5)); - margin-inline-start: 0.5rem; - float: right; - transform: rotate(0deg); - background-image: var(--icon-chevron); - background-position: right center; - background-size: 1rem auto; - background-repeat: no-repeat; - content: ""; -} - -details[role=list] { - padding: 0; - border-bottom: none; -} -details[role=list] summary { - margin-bottom: 0; -} -details[role=list] summary:not([role]) { - height: calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2); - padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal); - border: var(--border-width) solid var(--form-element-border-color); - border-radius: var(--border-radius); - background-color: var(--form-element-background-color); - color: var(--form-element-placeholder-color); - line-height: inherit; - cursor: pointer; - transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); -} -details[role=list] summary:not([role]):active, details[role=list] summary:not([role]):focus { - border-color: var(--form-element-active-border-color); - background-color: var(--form-element-active-background-color); -} -details[role=list] summary:not([role]):focus { - box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color); -} -details[role=list][open] summary { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -details[role=list][open] summary::before { - display: block; - z-index: 1; - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: none; - content: ""; - cursor: default; -} - -nav details[role=list] summary, -nav li[role=list] a { - display: flex; - direction: ltr; -} - -nav details[role=list] summary + ul, -nav li[role=list] > ul { - min-width: -moz-fit-content; - min-width: fit-content; - border-radius: var(--border-radius); -} -nav details[role=list] summary + ul li a, -nav li[role=list] > ul li a { - border-radius: 0; -} - -nav details[role=list] summary, -nav details[role=list] summary:not([role]) { - height: auto; - padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); -} -nav details[role=list][open] summary { - border-radius: var(--border-radius); -} -nav details[role=list] summary + ul { - margin-top: var(--outline-width); - margin-inline-start: 0; -} -nav details[role=list] summary[role=link] { - margin-bottom: calc(var(--nav-link-spacing-vertical) * -1); - line-height: var(--line-height); -} -nav details[role=list] summary[role=link] + ul { - margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width)); - margin-inline-start: calc(var(--nav-link-spacing-horizontal) * -1); -} - -li[role=list]:hover > ul, -li[role=list] a:active ~ ul, -li[role=list] a:focus ~ ul { - display: flex; -} -li[role=list] > ul { - display: none; - margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width)); - margin-inline-start: calc(var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal)); -} -li[role=list] > a::after { - background-image: var(--icon-chevron); -} - -label > details[role=list] { - margin-top: calc(var(--spacing) * 0.25); - margin-bottom: var(--spacing); -} - -/** - * Loading ([aria-busy=true]) - */ -[aria-busy=true] { - cursor: progress; -} - -[aria-busy=true]:not(input, select, textarea, html)::before { - display: inline-block; - width: 1em; - height: 1em; - border: 0.1875em solid currentColor; - border-radius: 1em; - border-right-color: transparent; - content: ""; - vertical-align: text-bottom; - vertical-align: -0.125em; - animation: spinner 0.75s linear infinite; - opacity: var(--loading-spinner-opacity); -} -[aria-busy=true]:not(input, select, textarea, html):not(:empty)::before { - margin-right: calc(var(--spacing) * 0.5); - margin-left: 0; - margin-inline-start: 0; - margin-inline-end: calc(var(--spacing) * 0.5); -} -[aria-busy=true]:not(input, select, textarea, html):empty { - text-align: center; -} - -button[aria-busy=true], -input[type=submit][aria-busy=true], -input[type=button][aria-busy=true], -input[type=reset][aria-busy=true], -a[aria-busy=true] { - pointer-events: none; -} - -@keyframes spinner { - to { - transform: rotate(360deg); - } -} /** * Tooltip ([data-tooltip]) */ [data-tooltip] { position: relative; } -[data-tooltip]:not(a, button, input) { +[data-tooltip]:not(a, button, input, [role=button]) { border-bottom: 1px dotted; text-decoration: none; cursor: help; @@ -2409,12 +2658,12 @@ a[aria-busy=true] { padding: 0.25rem 0.5rem; overflow: hidden; transform: translate(-50%, -0.25rem); - border-radius: var(--border-radius); - background: var(--tooltip-background-color); + border-radius: var(--pico-border-radius); + background: var(--pico-tooltip-background-color); content: attr(data-tooltip); - color: var(--tooltip-color); + color: var(--pico-tooltip-color); font-style: normal; - font-weight: var(--font-weight); + font-weight: var(--pico-font-weight); font-size: 0.875rem; text-decoration: none; text-overflow: ellipsis; @@ -2431,7 +2680,7 @@ a[aria-busy=true] { border-radius: 0; background-color: transparent; content: ""; - color: var(--tooltip-background-color); + color: var(--pico-tooltip-background-color); } [data-tooltip][data-placement=bottom]::before, [data-tooltip][data-placement=bottom]::after { top: 100%; @@ -2471,124 +2720,62 @@ a[aria-busy=true] { opacity: 1; } @media (hover: hover) and (pointer: fine) { - [data-tooltip][data-placement=bottom]:focus::before, [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::before, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after, [data-tooltip]:hover::before, [data-tooltip]:hover::after { + [data-tooltip]:focus::before, [data-tooltip]:focus::after, [data-tooltip]:hover::before, [data-tooltip]:hover::after { + --pico-tooltip-slide-to: translate(-50%, -0.25rem); + transform: translate(-50%, 0.75rem); animation-duration: 0.2s; - animation-name: tooltip-slide-top; + animation-fill-mode: forwards; + animation-name: tooltip-slide; + opacity: 0; } - [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after, [data-tooltip]:hover::after { - animation-name: tooltip-caret-slide-top; + [data-tooltip]:focus::after, [data-tooltip]:hover::after { + --pico-tooltip-caret-slide-to: translate(-50%, 0rem); + transform: translate(-50%, -0.25rem); + animation-name: tooltip-caret-slide; } [data-tooltip][data-placement=bottom]:focus::before, [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover::before, [data-tooltip][data-placement=bottom]:hover::after { - animation-duration: 0.2s; - animation-name: tooltip-slide-bottom; + --pico-tooltip-slide-to: translate(-50%, 0.25rem); + transform: translate(-50%, -0.75rem); + animation-name: tooltip-slide; } [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover::after { - animation-name: tooltip-caret-slide-bottom; + --pico-tooltip-caret-slide-to: translate(-50%, -0.3rem); + transform: translate(-50%, -0.5rem); + animation-name: tooltip-caret-slide; } [data-tooltip][data-placement=left]:focus::before, [data-tooltip][data-placement=left]:focus::after, [data-tooltip][data-placement=left]:hover::before, [data-tooltip][data-placement=left]:hover::after { - animation-duration: 0.2s; - animation-name: tooltip-slide-left; + --pico-tooltip-slide-to: translate(-0.25rem, -50%); + transform: translate(0.75rem, -50%); + animation-name: tooltip-slide; } [data-tooltip][data-placement=left]:focus::after, [data-tooltip][data-placement=left]:hover::after { - animation-name: tooltip-caret-slide-left; + --pico-tooltip-caret-slide-to: translate(0.3rem, -50%); + transform: translate(0.05rem, -50%); + animation-name: tooltip-caret-slide; } [data-tooltip][data-placement=right]:focus::before, [data-tooltip][data-placement=right]:focus::after, [data-tooltip][data-placement=right]:hover::before, [data-tooltip][data-placement=right]:hover::after { - animation-duration: 0.2s; - animation-name: tooltip-slide-right; + --pico-tooltip-slide-to: translate(0.25rem, -50%); + transform: translate(-0.75rem, -50%); + animation-name: tooltip-slide; } [data-tooltip][data-placement=right]:focus::after, [data-tooltip][data-placement=right]:hover::after { - animation-name: tooltip-caret-slide-right; - } -} -@keyframes tooltip-slide-top { - from { - transform: translate(-50%, 0.75rem); - opacity: 0; - } - to { - transform: translate(-50%, -0.25rem); - opacity: 1; - } -} -@keyframes tooltip-caret-slide-top { - from { - opacity: 0; - } - 50% { - transform: translate(-50%, -0.25rem); - opacity: 0; - } - to { - transform: translate(-50%, 0rem); - opacity: 1; - } -} -@keyframes tooltip-slide-bottom { - from { - transform: translate(-50%, -0.75rem); - opacity: 0; - } - to { - transform: translate(-50%, 0.25rem); - opacity: 1; - } -} -@keyframes tooltip-caret-slide-bottom { - from { - opacity: 0; - } - 50% { - transform: translate(-50%, -0.5rem); - opacity: 0; - } - to { - transform: translate(-50%, -0.3rem); - opacity: 1; - } -} -@keyframes tooltip-slide-left { - from { - transform: translate(0.75rem, -50%); - opacity: 0; - } - to { - transform: translate(-0.25rem, -50%); - opacity: 1; - } -} -@keyframes tooltip-caret-slide-left { - from { - opacity: 0; - } - 50% { - transform: translate(0.05rem, -50%); - opacity: 0; - } - to { - transform: translate(0.3rem, -50%); - opacity: 1; + --pico-tooltip-caret-slide-to: translate(-0.3rem, -50%); + transform: translate(-0.05rem, -50%); + animation-name: tooltip-caret-slide; } } -@keyframes tooltip-slide-right { - from { - transform: translate(-0.75rem, -50%); - opacity: 0; - } +@keyframes tooltip-slide { to { - transform: translate(0.25rem, -50%); + transform: var(--pico-tooltip-slide-to); opacity: 1; } } -@keyframes tooltip-caret-slide-right { - from { - opacity: 0; - } +@keyframes tooltip-caret-slide { 50% { - transform: translate(-0.05rem, -50%); opacity: 0; } to { - transform: translate(-0.3rem, -50%); + transform: var(--pico-tooltip-caret-slide-to); opacity: 1; } } @@ -2631,8 +2818,8 @@ textarea, } /** -* Reduce Motion Features -*/ + * Reduce Motion Features + */ @media (prefers-reduced-motion: reduce) { *:not([aria-busy=true]), :not([aria-busy=true])::before, @@ -2646,5 +2833,3 @@ textarea, transition-duration: 0s !important; } } - -/*# sourceMappingURL=pico.css.map */ \ No newline at end of file diff --git a/demo/HtmxAppServer/wwwroot/lib/pico.css.map b/demo/HtmxAppServer/wwwroot/lib/pico.css.map deleted file mode 100644 index ec25c5a..0000000 --- a/demo/HtmxAppServer/wwwroot/lib/pico.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["pico.css","../scss/pico.scss","../scss/themes/default.scss","../scss/themes/default/_styles.scss","../scss/themes/default/_light.scss","../scss/themes/default/_dark.scss","../scss/layout/_document.scss","../scss/layout/_sectioning.scss","../scss/layout/_container.scss","../scss/layout/_section.scss","../scss/layout/_grid.scss","../scss/layout/_scroller.scss","../scss/content/_typography.scss","../scss/content/_embedded.scss","../scss/content/_button.scss","../scss/content/_form.scss","../scss/content/_form-checkbox-radio.scss","../scss/content/_form-alt-input-types.scss","../scss/content/_table.scss","../scss/content/_code.scss","../scss/content/_miscs.scss","../scss/components/_accordion.scss","../scss/components/_card.scss","../scss/components/_modal.scss","../scss/components/_nav.scss","../scss/components/_progress.scss","../scss/components/_dropdown.scss","../scss/utilities/_loading.scss","../scss/utilities/_tooltip.scss","../scss/utilities/_accessibility.scss","../scss/utilities/_reduce-motion.scss"],"names":[],"mappings":"AAAA,gBAAgB;ACAhB;;;EAAA;ACAA;;EAAA;ACCA;EAEE;;yCAAA;EAGA,kBAAA;EACA,kBAAA;EACA,iBAAA;EA8BA,wBAAA;EACA,mBAAA;EACA,oBAAA;EAGA,eAAA;EAGA,qCAAA;EAGA,kDAAA;EACA,0CAAA;EAGE,0BAAA;EACA,yCAAA;EAIF,wCAAA;EACA,uCAAA;EAGA,oCAAA;EACA,wCAAA;EACA,mCAAA;EACA,qCAAA;EAGA,4CAAA;EAGA,8BAAA;EAGA,8CAAA;AHzCF;AGpBM;EAZN;IAaQ,iBAAA;EHuBN;AACF;AGnBM;EAlBN;IAmBQ,iBAAA;EHsBN;AACF;AGlBM;EAxBN;IAyBQ,iBAAA;EHqBN;AACF;AGjBM;EA9BN;IA+BQ,iBAAA;EHoBN;AACF;;AGgCM;EALJ;;;;IAMM,oDAAA;EHzBN;AACF;AG6BM;EAXJ;;;;IAYM,kDAAA;EHvBN;AACF;AG2BM;EAjBJ;;;;IAkBM,oDAAA;EHrBN;AACF;AGyBM;EAvBJ;;;;IAwBM,kDAAA;EHnBN;AACF;;AG0BM;EAFJ;IAGM,uDAAA;EHtBN;AACF;AG0BM;EARJ;IASM,sDAAA;EHvBN;AACF;AG2BM;EAdJ;IAeM,uDAAA;EHxBN;AACF;AG4BM;EApBJ;IAqBM,oDAAA;EHzBN;AACF;;AG8BE;EAEE,kDAAA;EACA,0CAAA;AH5BJ;AG+BM;EANJ;IAOM,oDAAA;IACA,uDAAA;EH5BN;AACF;AGgCM;EAbJ;IAcM,kDAAA;IACA,sDAAA;EH7BN;AACF;;AGmCA;EACE,uBAAA;AHhCF;AGoCI;EAEE,4BAAA;AHnCN;;AGyCA;EACE,oBAAA;AHtCF;;AG0CA;;;;;;EAME,kBAAA;AHvCF;;AG0CA;EACE,iBAAA;EACA,mCAAA;AHvCF;;AG0CA;EACE,oBAAA;EACA,uCAAA;AHvCF;;AG0CA;EACE,mBAAA;EACA,sCAAA;AHvCF;;AG0CA;EACE,oBAAA;EACA,uCAAA;AHvCF;;AG0CA;EACE,qBAAA;EACA,wCAAA;AHvCF;;AG2CA;;EAEE,mBAAA;AHxCF;;AG2CA;EACE,mBAAA;AHxCF;;AG8CE;;;;EAEE,mBAAA;AHzCJ;;AG6CA;EACE,oBAAA;AH1CF;;AG8CA;;;;EAIE;;gFAAA;AHzCF;;AG8CA;EACE,qBAAA;AH3CF;;AIvMA;;EAEE,wBAAA;EAGA,2BAAA;EACA,8BAAA;EACA,mBAAA;EACA,8BAAA;EACA,mBAAA;EACA,8BAAA;EACA,mBAAA;EAGA,iCAAA;EACA,wCAAA;EAGA,6BAAA;EACA,mCAAA;EACA,0CAAA;EACA,uBAAA;EAGA,+BAAA;EACA,qCAAA;EACA,4CAAA;EACA,yBAAA;EAGA,8BAAA;EACA,sBAAA;EACA,2CAAA;EACA,wBAAA;EAGA,gCAAA;EACA,qBAAA;EAGA,oBAAA;EACA,oBAAA;EAGA,oDAAA;EACA,6CAAA;EAKA,2CAAA;EACA,iDAAA;EAGA,4CAAA;EACA,+CAAA;EACA,kCAAA;EACA,oDAAA;EACA,mDAAA;EACA,kDAAA;EACA,gDAAA;EACA,4DAAA;EACA,wDAAA;EACA,oCAAA;EACA,4CAAA;EACA,mDAAA;EACA,4DAAA;EACA,0CAAA;EACA,iDAAA;EACA,0DAAA;EAGA,6CAAA;EACA,sCAAA;EACA,iDAAA;EAGA,wCAAA;EACA,+CAAA;EACA,mDAAA;EACA,qCAAA;EACA,iDAAA;EACA,0CAAA;EAGA,+CAAA;EACA,8CAAA;EAGA,2CAAA;EACA,gCAAA;EACA,4CAAA;EACA,yCAAA;EACA,oCAAA;EACA,yCAAA;EACA,qCAAA;EACA,wCAAA;EAGA,mDAAA;EACA,6CAAA;EACA,kDAAA;EAMA,gDAAA;EACA,8CAAA;EACA;;;;;;;2CAAA;EAQA,4CAAA;EAGA,oCAAA;EACA,gCAAA;EACA,6CAAA;EACA,8BAAA;EACA,qDAAA;EAGA,0DAAA;EAGA,+CAAA;EACA,gCAAA;EAGA,8BAAA;EAGA,2CAAA;EACA,wCAAA;EAGA,ySAAA;EACA,qSAAA;EACA,+SAAA;EACA,uTAAA;EACA,uVAAA;EACA,2cAAA;EACA,6YAAA;EACA,qSAAA;EACA,yVAAA;EACA,oVAAA;EACA,oSAAA;EAGA,mBAAA;AJuJF;;AElSA;EACE;IGfA,2BAAA;IAGA,2BAAA;IACA,8BAAA;IACA,mBAAA;IACA,8BAAA;IACA,mBAAA;IACA,8BAAA;IACA,mBAAA;IAGA,iCAAA;IACA,6BAAA;IAGA,6BAAA;IACA,mCAAA;IACA,yCAAA;IACA,uBAAA;IAGA,+BAAA;IACA,qCAAA;IACA,4CAAA;IACA,yBAAA;IAGA,8BAAA;IACA,sBAAA;IACA,2CAAA;IACA,wBAAA;IAGA,gCAAA;IACA,qBAAA;IAGA,oBAAA;IACA,oBAAA;IAGA,oDAAA;IACA,6CAAA;IAKA,2CAAA;IACA,iDAAA;IAGA,wCAAA;IACA,oCAAA;IACA,kCAAA;IACA,oDAAA;IACA,4EAAA;IACA,kDAAA;IACA,gDAAA;IACA,4DAAA;IACA,wDAAA;IACA,oCAAA;IACA,4CAAA;IACA,mDAAA;IACA,2DAAA;IACA,0CAAA;IACA,iDAAA;IACA,yDAAA;IAGA,kCAAA;IACA,sCAAA;IACA,iDAAA;IAGA,6BAAA;IACA,+CAAA;IACA,mDAAA;IACA,qCAAA;IACA,iDAAA;IACA,0CAAA;IAGA,+CAAA;IACA,gEAAA;IAGA,gCAAA;IACA,gCAAA;IACA,4CAAA;IACA,yCAAA;IACA,oCAAA;IACA,yCAAA;IACA,qCAAA;IACA,6BAAA;IAGA,mDAAA;IACA,gDAAA;IACA,6CAAA;IACA,kDAAA;IAMA,gCAAA;IACA,iDAAA;IACA;;;;;;;0CAAA;IAQA,4CAAA;IAGA,+CAAA;IACA,gCAAA;IACA,6CAAA;IACA,8BAAA;IACA,yDAAA;IAGA,uDAAA;IAGA,oCAAA;IACA,gCAAA;IAGA,8BAAA;IAGA,2CAAA;IACA,wCAAA;IAGA,ySAAA;IACA,wSAAA;IACA,+SAAA;IACA,iTAAA;IACA,uVAAA;IACA,8cAAA;IACA,6YAAA;IACA,qSAAA;IACA,4VAAA;IACA,uVAAA;IACA,oSAAA;IAGA,kBAAA;ELkQA;AACF;AEtYA;EGtBE,2BAAA;EAGA,2BAAA;EACA,8BAAA;EACA,mBAAA;EACA,8BAAA;EACA,mBAAA;EACA,8BAAA;EACA,mBAAA;EAGA,iCAAA;EACA,6BAAA;EAGA,6BAAA;EACA,mCAAA;EACA,yCAAA;EACA,uBAAA;EAGA,+BAAA;EACA,qCAAA;EACA,4CAAA;EACA,yBAAA;EAGA,8BAAA;EACA,sBAAA;EACA,2CAAA;EACA,wBAAA;EAGA,gCAAA;EACA,qBAAA;EAGA,oBAAA;EACA,oBAAA;EAGA,oDAAA;EACA,6CAAA;EAKA,2CAAA;EACA,iDAAA;EAGA,wCAAA;EACA,oCAAA;EACA,kCAAA;EACA,oDAAA;EACA,4EAAA;EACA,kDAAA;EACA,gDAAA;EACA,4DAAA;EACA,wDAAA;EACA,oCAAA;EACA,4CAAA;EACA,mDAAA;EACA,2DAAA;EACA,0CAAA;EACA,iDAAA;EACA,yDAAA;EAGA,kCAAA;EACA,sCAAA;EACA,iDAAA;EAGA,6BAAA;EACA,+CAAA;EACA,mDAAA;EACA,qCAAA;EACA,iDAAA;EACA,0CAAA;EAGA,+CAAA;EACA,gEAAA;EAGA,gCAAA;EACA,gCAAA;EACA,4CAAA;EACA,yCAAA;EACA,oCAAA;EACA,yCAAA;EACA,qCAAA;EACA,6BAAA;EAGA,mDAAA;EACA,gDAAA;EACA,6CAAA;EACA,kDAAA;EAMA,gCAAA;EACA,iDAAA;EACA;;;;;;;wCAAA;EAQA,4CAAA;EAGA,+CAAA;EACA,gCAAA;EACA,6CAAA;EACA,8BAAA;EACA,yDAAA;EAGA,uDAAA;EAGA,oCAAA;EACA,gCAAA;EAGA,8BAAA;EAGA,2CAAA;EACA,wCAAA;EAGA,ySAAA;EACA,wSAAA;EACA,+SAAA;EACA,iTAAA;EACA,uVAAA;EACA,8cAAA;EACA,6YAAA;EACA,qSAAA;EACA,4VAAA;EACA,uVAAA;EACA,oSAAA;EAGA,kBAAA;AL4WF;;AE1eA;;;;EAIE,4BAAA;AF6eF;;AMhhBA;;;EAAA;AAYA;;;EAGE,sBAAA;EACA,4BAAA;AN2gBF;;AMtgBA;;EAEE,wBAAA;EACA,uBAAA;ANygBF;;AMhgBA;EACE,wCAAA;EACA,8BAAA;EACA,2BAAA;EAAA,sBAAA;EACA,yCAAA;EACA,mBAAA;EACA,+BAAA;EACA,2BAAA;EACA,+BAAA;EACA,+BAAA;EACA,kCAAA;EACA,yBAAA;EACA,eAAA;EACA,gBAAA;EAAA,cAAA;EAAA,WAAA;ANmgBF;;AOjjBA;;;EAAA;AAWA;EACE,cAAA;AP6iBF;;AOtiBA;EACE,WAAA;EACA,SAAA;APyiBF;AOviBE;;;EAGE,WAAA;EACA,kBAAA;EACA,iBAAA;EAsCE,wCAAA;APogBN;;AQrkBE;;CAAA;AAIA;;EAEE,WAAA;EACA,kBAAA;EACA,iBAAA;EACA,6BAAA;EACA,4BAAA;ARukBJ;;AQlkBM;EAFJ;IAGM,gBAAA;IACA,gBAAA;IACA,eAAA;ERskBN;AACF;AQlkBM;EAVJ;IAWM,gBAAA;ERqkBN;AACF;AQjkBM;EAhBJ;IAiBM,gBAAA;ERokBN;AACF;AQhkBM;EAtBJ;IAuBM,iBAAA;ERmkBN;AACF;;ASzmBA;;;EAAA;AAKA;EACE,4CAAA;AT2mBF;;AUhnBE;;;CAAA;AAKA;EACE,+CAAA;EACA,0CAAA;EACA,aAAA;EACA,0BAAA;EACA,SAAA;AVknBJ;AU/mBM;EARJ;IASM,wDAAA;EVknBN;AACF;AU/mBI;EACE,YAAA;AVinBN;;AWroBA;;EAAA;AAKA;EACE,cAAA;EACA,SAAA;EACA,UAAA;EACA,gBAAA;AXsoBF;AWpoBE;EACE,qCAAA;EACA,yBAAA;AXsoBJ;;AYnpBA;;EAAA;AAUA;;EAEE,mBAAA;AZ+oBF;;AY3oBA;;EAEE,kBAAA;EACA,iBAAA;EACA,cAAA;EACA,wBAAA;AZ8oBF;;AY5oBA;EACE,eAAA;AZ+oBF;;AY7oBA;EACE,WAAA;AZgpBF;;AY1oBA;;;;;;;;;;EAUE,aAAA;EACA,iDAAA;EACA,mBAAA;EACA,kBAAA;EACA,+BAAA;EACA,2BAAA;AZ6oBF;;AYxoBA;;EAEE,uBAAA;EACA,+BAAA;EACA,aAAA;EACA,yCAAA;EACA,mBAAA;EACA,+CAAA;EAAA,uCAAA;EAGE,gJAAA;EAAA,wIAAA;EAAA,mLAAA;AZyoBJ;AYroBE;;EACE,6BAAA;EACA,4BAAA;AZwoBJ;AYroBE;;EACE,wCAAA;AZwoBJ;AYnoBI;;EACE,yBAAA;AZsoBN;AYpoBM;;EACE,+BAAA;AZuoBR;AYpoBM;;EACE,0CAAA;AZuoBR;AYloBI;;EACE,wBAAA;AZqoBN;AYnoBM;;EACE,8BAAA;AZsoBR;AYnoBM;;EACE,yCAAA;AZsoBR;;AY/nBA;;;;;;EAME,aAAA;EACA,iDAAA;EACA,mBAAA;EACA,+BAAA;EACA,2BAAA;EACA,+BAAA;AZkoBF;;AY/nBA;EACE,wBAAA;AZkoBF;;AYhoBA;EACE,wBAAA;AZmoBF;;AYjoBA;EACE,wBAAA;AZooBF;;AYloBA;EACE,wBAAA;AZqoBF;;AYnoBA;EACE,wBAAA;AZsoBF;;AYpoBA;EACE,wBAAA;AZuoBF;;AYloBE;EACE,8CAAA;AZqoBJ;;AY9mBE;;EAEE,iDAAA;AZinBJ;AY/mBI;;EACE,gBAAA;AZknBN;AY/mBI;;EACE,2BAAA;EACA,oBAAA;EACA,eAAA;EACA,kBAAA;AZknBN;;AY5mBA;EACE,iDAAA;AZ+mBF;;AY3mBA;EACE,2BAAA;AZ8mBF;;AY1mBA;EACE,gBAAA;EACA,4BAAA;EACA,oCAAA;EACA,qBAAA;AZ6mBF;AY3mBE;EACE,8DAAA;AZ6mBJ;;AYtmBE;EACE,SAAA;EACA,2DAAA;AZymBJ;;AYrmBA;EACE,kBAAA;AZwmBF;;AYpmBA;EACE,yBAAA;EACA,8CAAA;EACA,wBAAA;EACA,wBAAA;AZumBF;;AYnmBA;EACE,cAAA;EACA,4CAAA;EACA,uBAAA;EACA,kBAAA;EACA,yDAAA;EACA,iEAAA;EACA,uBAAA;AZsmBF;AYpmBE;EACE,0DAAA;EACA,qCAAA;AZsmBJ;;AYhmBA;EACE,yBAAA;EACA,qBAAA;EACA,YAAA;AZmmBF;;AY/lBA;EACE,uBAAA;EACA,qBAAA;AZkmBF;;AY9lBA;EACE,uBAAA;AZimBF;;AY7lBA;EACE,sCAAA;AZgmBF;;AYjmBA;EACE,sCAAA;AZgmBF;;Aat2BA;;EAAA;AAUA;EACE,sBAAA;Abk2BF;;Aa91BA;;EAEE,qBAAA;Abi2BF;;Aa71BA;EACE,aAAA;EACA,SAAA;Abg2BF;;Aa51BA;EACE,kBAAA;Ab+1BF;;Aa11BA;EACE,eAAA;EACA,YAAA;EACA,kBAAA;Ab61BF;;Aaz1BA;EACE,kBAAA;Ab41BF;;Aax1BA;EACE,gBAAA;Ab21BF;;Acz4BA;;EAAA;AAYA;EACE,SAAA;EACA,iBAAA;EACA,oBAAA;EACA,oBAAA;Adm4BF;;Ac/3BA;;;;EAIE,0BAAA;Adk4BF;;Ac53BA;EACE,cAAA;EACA,WAAA;EACA,6BAAA;Ad+3BF;;Ac53BA;EACE,qBAAA;EACA,qBAAA;Ad+3BF;;Ac53BA;;;;;EAKE,kCAAA;EACA,8BAAA;EACA,+BAAA;EACA,8DAAA;EACA,oFAAA;EAEA,qDAAA;EACA,mCAAA;EACA,aAAA;EACA,yCAAA;EACA,6BAAA;EACA,mBAAA;EACA,+BAAA;EACA,eAAA;EACA,+BAAA;EACA,kBAAA;EACA,eAAA;EAGE,qIAAA;Ad43BJ;Acv3BE;;;;;EACE,wCAAA;EACA,oCAAA;EACA,oEAAA;EACA,+BAAA;Ad63BJ;Ac13BE;;;;;EACE;mDAAA;Adi4BJ;;Acx3BE;;EAEE,oCAAA;EACA,gCAAA;EACA,iCAAA;EACA,eAAA;Ad23BJ;Acz3BI;;EACE,0CAAA;EACA,sCAAA;EACA,iCAAA;Ad43BN;Acz3BI;;EACE;qDAAA;Ad63BN;;Acv3BE;EACE,mCAAA;EACA,+BAAA;EACA,gCAAA;Ad03BJ;Acx3BI;EACE,yCAAA;EACA,qCAAA;EACA,gCAAA;Ad03BN;Acv3BI;EACE;oDAAA;Ad03BN;;Acp3BE;;EAEE,+BAAA;EACA,uBAAA;Adu3BJ;Acr3BI;;EACE,+BAAA;EACA,6BAAA;Adw3BN;;Acn3BE;;EAEE,yBAAA;Ads3BJ;Acp3BI;;EACE,+BAAA;Adu3BN;;Acl3BE;EACE,wBAAA;Adq3BJ;Acn3BI;EACE,8BAAA;Adq3BN;;Ac31BA;;;EAGE,YAAA;EACA,oBAAA;Ad81BF;;AenhCA;;EAAA;AAWA;;;;EAIE,SAAA;EACA,eAAA;EACA,+BAAA;EACA,oBAAA;EACA,uBAAA;Af8gCF;;Ae1gCA;EACE,iBAAA;Af6gCF;;AezgCA;EACE,oBAAA;Af4gCF;;AergCA;EACE,eAAA;EACA,UAAA;EACA,cAAA;EACA,mBAAA;AfwgCF;;AepgCA;EACE,cAAA;AfugCF;;AengCA;;EAEE,UAAA;AfsgCF;;AelgCA;;EAEE,YAAA;AfqgCF;;AehgCA;EACE,6BAAA;EACA,oBAAA;AfmgCF;;Ae//BA;EACE,wBAAA;AfkgCF;;Ae7/BA;EACE,0BAAA;EACA,aAAA;AfggCF;;Ae5/BA;EACE,UAAA;EACA,kBAAA;Af+/BF;;Ae3/BA;EACE,aAAA;Af8/BF;;Ae1/BA;EACE,gBAAA;Af6/BF;;Aez/BA;EACE,aAAA;Af4/BF;;Aex/BA;;EAEE,UAAA;EACA,eAAA;Af2/BF;;Aep/BA;EACE,4GAAA;Afu/BF;;Aeh/BA;EACE,SAAA;EACA,6BAAA;EACA,UAAA;EACA,SAAA;Afm/BF;;Ae/+BA;;EAEE,cAAA;EACA,0CAAA;EACA,8DAAA;Afk/BF;;Ae9+BA;;;EAGE,WAAA;Afi/BF;;Ae7+BA;;;EAGE,wBAAA;EAAA,qBAAA;EAAA,gBAAA;EACA,oFAAA;Afg/BF;;Ae3+BA;;;EAGE,wDAAA;EACA,gDAAA;EACA,kCAAA;EACA,kBAAA;EACA,qDAAA;EACA,mCAAA;EACA,aAAA;EACA,yCAAA;EACA,6BAAA;EACA,mBAAA;EACA,+BAAA;EAGE,qIAAA;Af4+BJ;;Aen+BE;;EACE,+DAAA;Afu+BJ;;Aeh+BE;;EACE,uDAAA;Afo+BJ;;Ae59BE;;;EACE,wEAAA;Afi+BJ;;Ae59BA;;;;EAIE,iEAAA;EACA,yDAAA;EACA,6CAAA;EACA,oBAAA;Af+9BF;;Aez9BI;EAEI,+EAAA;EAGA,oDAAA;EACA,uEAAA;EACA,oFAAA;EAUF,yCAAA;EACA,0BAAA;EACA,4BAAA;Afg9BN;Ae78BI;EACE,mCAAA;Af+8BN;Ae58BI;EACE,qCAAA;Af88BN;Ae18BE;EACE,sDAAA;Af48BJ;Ae18BI;EAEI,wEAAA;EACA,yFAAA;Af28BR;Ael8BE;EACE,wDAAA;Afo8BJ;Ael8BI;EAEI,0EAAA;EACA,2FAAA;Afm8BR;;Aet7BM;EACE,wCAAA;Afy7BR;;Ael7BA;;;;;EAKE,4CAAA;EACA,UAAA;Afq7BF;;Aej7BA;;;EAGE,6BAAA;Afo7BF;;Ae96BE;EACE,SAAA;EACA,6BAAA;Afi7BJ;Ae96BE;EACE,oEAAA;EACA,oDAAA;EACA,4DAAA;EACA,yEAAA;EACA,qCAAA;EACA,yCAAA;EACA,0BAAA;EACA,4BAAA;Afg7BJ;;Ae16BI;EACE,wCAAA;Af66BN;;Aeh6BE;EACE,cAAA;EACA,WAAA;EACA,wCAAA;EACA,6BAAA;EACA,yBAAA;Afm6BJ;;Ae75BE;EACE,uCAAA;Afg6BJ;;AgB7vCA;;;EAAA;AAKA;;EAEE,wBAAA;EACA,qBAAA;EACA,gBAAA;EACA,aAAA;EACA,cAAA;EACA,oBAAA;EACA,qBAAA;EACA,cAAA;EACA,sBAAA;EACA,0BAAA;EACA,iCAAA;EACA,kBAAA;EACA,sBAAA;EACA,eAAA;AhB+vCF;AgB7vCE;;EACE,aAAA;AhBgwCJ;AgB7vCE;;;;EAGE,kCAAA;EACA,8BAAA;EACA,sCAAA;EACA,2BAAA;EACA,4BAAA;EACA,4BAAA;AhBgwCJ;AgB7vCE;;EACE,qBAAA;EACA,qBAAA;EACA,gBAAA;EACA,eAAA;AhBgwCJ;;AgB1vCE;EACE,kCAAA;EACA,8BAAA;EACA,mCAAA;EACA,2BAAA;EACA,4BAAA;EACA,4BAAA;AhB6vCJ;;AgBxvCA;EACE,kBAAA;AhB2vCF;AgBzvCE;EAGE,0CAAA;EACA,oBAAA;EACA,sBAAA;AhByvCJ;;AgBpvCA;EACE,kDAAA;EACA,8CAAA;EACA,4BAAA;EAQA,aAJe;EAKf,cANgB;EAOhB,qDAAA;EACA,qBARgB;EAShB,yCAAA;EACA,mBAVgB;AhB0vClB;AgB9uCE;EACE,kDAAA;EACA,8CAAA;AhBgvCJ;AgB7uCE;EACE,0DAAA;EACA,sDAAA;AhB+uCJ;AgB5uCE;EACE,cAAA;EACA,+CAAA;EACA,YAAA;EACA,kBAAA;EACA,8BAAA;EACA,WAAA;EAGE,mCAAA;AhB4uCN;AgBxuCE;EACE,sBAAA;AhB0uCJ;AgBxuCI;EACE,gDAAA;EACA,wDAAA;AhB0uCN;;AgB7tCE;;;;;;EACE,sDAAA;AhBquCJ;AgBluCE;;;;;;EACE,wDAAA;AhByuCJ;;AiBh3CA;;;EAAA;AAYE;EAHE,UAAA;AjB+2CJ;AiBx2CE;EAPE,UAAA;AjBk3CJ;AiBj2CE;EAJE,SAAA;EACA,+CAAA;AjBw2CJ;AiBj2CE;EARE,SAAA;EACA,+CAAA;AjB42CJ;;AiB71CE;EACE,wBAAA;EACA,kBAAA;EACA,6DAAA;EACA,kCAAA;EACA,sDAAA;EACA,uCAAA;EACA,4BAAA;AjBg2CJ;AiB51CE;EACE,kCAAA;AjB81CJ;;AiBp1CE;;;;;EACE,wBAAA;EACA,0CAAA;EACA,iCAAA;EACA,UAAA;AjB21CJ;;AiBv1CA;EAEE,iBAAA;AjBy1CF;;AiBp1CE;EACE;;;;;IAKE,gEAAA;IACA,iCAAA;EjBu1CJ;AACF;AiBl1CA;EACE,2BAAA;EACA,2DAAA;EACA,SAAA;EACA,gBAAA;EACA,gBAAA;AjBo1CF;AiBhzCE;EAjCE,oCAAA;EACA,gCAAA;EACA,iCAAA;EACA,sCAAA;EACA,cAAA;EACA,sBAAA;EACA,2CAAA;EACA,4GAAA;EAEA,qDAAA;EACA,mCAAA;EACA,aAAA;EACA,yCAAA;EACA,6BAAA;EACA,mBAAA;EACA,+BAAA;EACA,eAAA;EACA,+BAAA;EACA,kBAAA;EACA,eAAA;EAGE,qIAAA;AjBi1CN;AiB50CI;EACE,0CAAA;EACA,sCAAA;AjB80CN;AiBt0CE;EArCE,oCAAA;EACA,gCAAA;EACA,iCAAA;EACA,sCAAA;EACA,cAAA;EACA,sBAAA;EACA,2CAAA;EACA,4GAAA;EAEA,qDAAA;EACA,mCAAA;EACA,aAAA;EACA,yCAAA;EACA,6BAAA;EACA,mBAAA;EACA,+BAAA;EACA,eAAA;EACA,+BAAA;EACA,kBAAA;EACA,eAAA;EAGE,6IAAA;EAAA,qIAAA;AjB22CN;AiBt2CI;EACE,0CAAA;EACA,sCAAA;AjBw2CN;AiB51CE;EAzCE,oCAAA;EACA,gCAAA;EACA,iCAAA;EACA,sCAAA;EACA,cAAA;EACA,sBAAA;EACA,2CAAA;EACA,4GAAA;EAEA,qDAAA;EACA,mCAAA;EACA,aAAA;EACA,yCAAA;EACA,6BAAA;EACA,mBAAA;EACA,+BAAA;EACA,eAAA;EACA,+BAAA;EACA,kBAAA;EACA,eAAA;EAGE,yIAAA;EAAA,qIAAA;AjBq4CN;AiBh4CI;EACE,0CAAA;EACA,sCAAA;AjBk4CN;;AiBh3CA;EAOE,wBAAA;EACA,qBAAA;EACA,gBAAA;EACA,WAAA;EACA,eARe;EASf,gBAAA;AjB62CF;AiB91CE;EAXE,WAAA;EACA,eAfa;EAgBb,mCAAA;EACA,2CAAA;EAGE,oFAAA;EAAA,4EAAA;AjB02CN;AiBj2CE;EAfE,WAAA;EACA,eAfa;EAgBb,mCAAA;EACA,2CAAA;EAGE,iFAAA;EAAA,4EAAA;AjBi3CN;AiBp2CE;EAnBE,WAAA;EACA,eAfa;EAgBb,mCAAA;EACA,2CAAA;EAGE,gFAAA;EAAA,4EAAA;AjBw3CN;AiBv1CE;EAdE,wBAAA;EACA,cAvCa;EAwCb,eAxCa;EAyCb,mBAAA;EACA,iDAAA;EACA,kBAAA;EACA,0CAAA;EACA,eAAA;EAGE,mFAAA;EAAA,2EAAA;AjBs2CN;AiB91CE;EAlBE,wBAAA;EACA,cAvCa;EAwCb,eAxCa;EAyCb,mBAAA;EACA,iDAAA;EACA,kBAAA;EACA,0CAAA;EACA,eAAA;EAGE,gFAAA;EAAA,2EAAA;AjBi3CN;AiBr2CE;EAtBE,wBAAA;EACA,cAvCa;EAwCb,eAxCa;EAyCb,mBAAA;EACA,iDAAA;EACA,kBAAA;EACA,0CAAA;EACA,eAAA;EAGE,+EAAA;EAAA,2EAAA;AjB43CN;AiB52CE;EAEE,sDAAA;EACA,mDAAA;AjB62CJ;AiB12CE;EACE,oDAAA;AjB42CJ;AiBz2CI;EACE,sBAAA;AjB22CN;AiBx2CI;EACE,sBAAA;AjB02CN;AiBv2CI;EACE,sBAAA;AjBy2CN;;AiBj2CE;EACE,4EAAA;EACA,mBAAA;EACA,oCAAA;EACA,yCAAA;EACA,0BAAA;EACA,4BAAA;AjBo2CJ;AiBl2CI;EAEI,uFAAA;EAKF,+DAAA;AjB+1CN;AiB51CI;EACE,uDAAA;AjB81CN;AiB31CI;EACE,yDAAA;AjB61CN;;AiBt1CE;EACE,wBAAA;EACA,aAAA;AjBy1CJ;;AiBl1CM;EACE,0CAAA;AjBq1CR;AiBn1CQ;EACE,+DAAA;AjBq1CV;;AkB7mDA;;EAAA;AAWA;EACE,WAAA;EACA,yBAAA;EACA,iBAAA;EACA,cAAA;AlBwmDF;;AkBjmDA;;EAEE,gDAAA;EACA,kEAAA;EACA,mBAAA;EACA,+BAAA;EACA,2BAAA;EACA,gBAAA;EACA,iBAAA;AlBomDF;;AkB/lDE;;EAEE,+DAAA;EACA,gBAAA;AlBkmDJ;;AkB3lDI;EACE,4DAAA;AlB8lDN;;AmB5oDA;;EAAA;AAWA;;;;EAIE,kBAAA;EACA,+BAAA;AnBuoDF;;AmBnoDA;EACE,6BAAA;EACA,cAAA;AnBsoDF;;AmBhoDA;;;EAGE,mCAAA;EACA,wCAAA;EACA,wBAAA;EACA,+BAAA;EACA,oBAAA;AnBmoDF;;AmBhoDA;;EAEE,qBAAA;EACA,wBAAA;AnBmoDF;;AmBhoDA;EACE,cAAA;EACA,6BAAA;EACA,gBAAA;AnBmoDF;AmBjoDE;EACE,cAAA;EACA,uBAAA;EACA,gBAAA;EACA,eAAA;EACA,+BAAA;AnBmoDJ;;AmB5nDE;EACE,4BAAA;EACA,+BAAA;AnB+nDJ;AmB3nDE;EACE,iCAAA;EACA,kBAAA;AnB6nDJ;AmBznDE;EACE,8BAAA;EACA,qBAAA;AnB2nDJ;AmBvnDE;EACE,gCAAA;EACA,kBAAA;AnBynDJ;;AmBpnDA;EACE,kDAAA;EACA,4BAAA;EACA,wBAAA;AnBunDF;;AoBhtDA;;EAAA;AAWA;EACE,SAAA;EACA,SAAA;EACA,+CAAA;EACA,cAAA;ApB2sDF;;AoBvsDA;;EAGI,wBAAA;ApBysDJ;;AoBjsDA;EACE,qBAAA;ApBosDF;;AqBnuDA;;EAAA;AAIA;EACE,cAAA;EACA,6BAAA;EACA,8BAAA;EACA,sEAAA;ArBquDF;AqBnuDE;EACE,iBAAA;EACA,qBAAA;EACA,eAAA;EAOE,mCAAA;ArB+tDN;AqBpuDI;EACE,2CAAA;ArBsuDN;AqB9tDI;EACE,aAAA;ArBguDN;AqB7tDI;EACE,aAAA;ArB+tDN;AqB5tDI;EACE,qBAAA;ArB8tDN;AqB1tDI;EACE,cAAA;EACA,WAAA;EACA,YAAA;EACA,qDAAA;EACA,YAAA;EACA,yBAAA;EACA,qCAAA;EACA,iCAAA;EACA,0BAAA;EACA,4BAAA;EACA,WAAA;EAGE,uCAAA;ArB0tDR;AqBttDI;EACE,aAAA;ArBwtDN;AqBttDM;EACE,4CAAA;ArBwtDR;AqBntDI;EACE,WAAA;EACA,gBAAA;ArBqtDN;AqBltDM;EACE,4CAAA;EACA,4CAAA;ArBotDR;AqB7sDU;EACE,oDAAA;ArB+sDZ;AqBtsDI;EACE,mCAAA;ArBwsDN;AqBrsDQ;EACE,0CAAA;ArBusDV;AqBnsDM;EACE,oBAAA;ArBqsDR;;AqB7rDI;EACE,iBAAA;ArBgsDN;AqB9rDM;EACE,WAAA;EACA,gCAAA;ArBgsDR;;AsB/yDA;;EAAA;AAIA;EACE,uCAAA;EACA,sEAAA;EACA,mCAAA;EACA,wCAAA;EACA,kCAAA;AtBizDF;AsB/yDE;;EAEE,wDAAA;EACA,uDAAA;EACA,mFAAA;EAEA,0DAAA;AtBgzDJ;AsB7yDE;EACE,oDAAA;EACA,4CAAA;EACA,iEAAA;EACA,6CAAA;EACA,4CAAA;AtB+yDJ;AsB5yDE;EACE,yCAAA;EACA,uDAAA;EACA,8DAAA;EACA,gDAAA;EACA,+CAAA;AtB8yDJ;;AuB/0DA;;EAAA;AAIA;EACE,sBAAA;AvBi1DF;;AuB90DA;EACE,aAAA;EACA,YAAA;EACA,eAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA;EACA,OAAA;EACA,mBAAA;EACA,uBAAA;EACA,cAAA;EACA,eAAA;EACA,eAAA;EACA,gBAAA;EACA,uBAAA;EACA,SAAA;EACA,6DAAA;EAAA,qDAAA;EACA,uDAAA;EACA,mBAAA;AvBi1DF;AuB90DE;EAEE,4CAAA;EACA,cAAA;AvB+0DJ;AuB50DM;EANJ;IAOM,gBAAA;EvB+0DN;AACF;AuB30DM;EAZJ;IAaM,gBAAA;EvB80DN;AACF;AuB30DI;;EAEE,kFAAA;AvB60DN;AuBx0DM;EACE,SAAA;EACA,2BAAA;EACA,YAAA;AvB00DR;AuBt0DI;EACE,iBAAA;AvBw0DN;AuBt0DM;EACE,gBAAA;AvBw0DR;AuBt0DQ;EACE,uCAAA;AvBw0DV;AuBl0DM;EACE,SAAA;AvBo0DR;AuB/zDI;EACE,cAAA;EACA,WAAA;EACA,YAAA;EACA,sDAAA;EACA,iDAAA;EACA,iBAAA;EACA,mCAAA;EACA,2BAAA;EACA,0BAAA;EACA,4BAAA;EACA,YAAA;EAGE,qCAAA;AvB+zDR;AuB5zDM;EACE,UAAA;AvB8zDR;AuBxzDE;EAEE,aAAA;AvByzDJ;;AuBnzDE;EACE,0CAAA;EACA,gBAAA;EACA,oBAAA;EACA,kBAAA;AvBszDJ;AuBpzDI;EACE,oBAAA;AvBszDN;;AuB5yDI;;EAEE,wBALiB;EAMjB,sCAAA;EACA,yBAAA;AvB+yDN;AuB5yDI;EACE,wBAAA;EACA,6BAAA;AvB8yDN;AuB5yDM;EACE,qBAfe;EAgBf,qBAAA;AvB8yDR;;AuBxyDI;;EAEE,mBAAA;EACA,4BAAA;AvB2yDN;;AuBvyDE;EACE;IACE,6BAAA;IAAA,qBAAA;IACA,6BAAA;EvB0yDJ;AACF;AuBvyDE;EACE;IACE,4BAAA;IACA,UAAA;EvByyDJ;AACF;AwB98DA;;EAAA;AAQA;EACE,WAAA;EACA,YAAA;AxB28DF;;AwBp8DA;;EAEE,aAAA;AxBu8DF;;AwBp8DA;EACE,8BAAA;AxBu8DF;AwBr8DE;;EAEE,mBAAA;EACA,gBAAA;EACA,UAAA;EACA,gBAAA;AxBu8DJ;AwBr8DI;;EACE,6DAAA;AxBw8DN;AwBt8DI;;EACE,8DAAA;AxBy8DN;AwBr8DE;EACE,qBAAA;EACA,SAAA;EACA,kFAAA;AxBu8DJ;AwBn8DI;EACE,YAAA;AxBq8DN;AwBj8DE;EACE,qBAAA;EACA,iGAAA;EAEA,4EAAA;EACA,mCAAA;EACA,qBAAA;AxBk8DJ;AwBh8DI;EACE,qBAAA;AxBk8DN;AwB77DE;EACE,mBAAA;EACA,sBAAA;AxB+7DJ;AwB57DM;EACE,uDAAA;AxB87DR;AwB17DQ;EACE,kBAAA;EACA,mDAAA;EACA,iEAAA;EACA,YAAA;EACA,yBAAA;EACA,kBAAA;AxB47DV;AwBv7DI;EACE,6BAAA;EACA,cAAA;EACA,qBAAA;EACA,oBAAA;AxBy7DN;AwBp7DE;EACE,qBAAA;EACA,oBAAA;EACA,4EAAA;AxBs7DJ;;AwBh7DE;;;;EAIE,cAAA;AxBm7DJ;AwBh7DE;EACE,8FAAA;AxBk7DJ;AwB/6DI;EACE,cAAA;AxBi7DN;AwB76DI;EACE,eAAA;AxB+6DN;;AwBp6DU;EACE,aAAA;AxBu6DZ;;AyB7iEA;;EAAA;AAWA;EACE,qBAAA;EACA,wBAAA;AzBwiEF;;AyBliEA;EAEE,wBAAA;EACA,qBAAA;EAGA,qBAAA;EACA,gBAAA;EACA,WAAA;EACA,cAAA;EACA,yCAAA;EACA,gBAAA;EAGA,SAAA;EACA,mCAAA;EACA,kDAAA;EAGA,4BAAA;AzB8hEF;AyB5hEE;EACE,mCAAA;EACA,gBAAA;AzB8hEJ;AyB5hEE;EACE,uCAAA;AzB8hEJ;AyB5hEE;EACE,uCAAA;AzB8hEJ;AyB1hEE;EACE;IACE,oKAAA;IAOA,oDAAA;EzBshEJ;EyBphEI;IACE,6BAAA;EzBshEN;EyBphEI;IACE,6BAAA;EzBshEN;AACF;;AyBhhEE;EACE;IACE,4BAAA;EzBmhEJ;AACF;;AyB/gEA;EACE;IACE,2BAAA;EzBkhEF;EyBhhEA;IACE,4BAAA;EzBkhEF;AACF;A0BzmEA;;EAAA;AAKA;;EAEE,kBAAA;A1BymEF;;A0BtmEA;;EAEE,aAAA;EACA,WAAA;EACA,kBAAA;EACA,SAAA;EACA,QAAA;EACA,OAAA;EACA,sBAAA;EACA,SAAA;EACA,UAAA;EACA,8DAAA;EACA,mCAAA;EACA,0BAAA;EACA,yBAAA;EACA,kDAAA;EACA,kCAAA;EACA,4BAAA;EACA,mBAAA;A1BymEF;A0BvmEE;;EACE,WAAA;EACA,gBAAA;EACA,gGAAA;EAEA,gBAAA;A1BymEJ;A0BvmEI;;EACE,4DAAA;A1B0mEN;A0BvmEI;;EACE,+DAAA;A1B0mEN;A0BvmEI;;EACE,cAAA;EACA,2GAAA;EAEA,gGAAA;EAEA,gBAAA;EACA,4BAAA;EACA,qBAAA;EACA,uBAAA;A1BwmEN;A0BtmEM;;EACE,wDAAA;A1BymER;;A0BhmEE;;EACE,cAAA;EACA,WAAA;EACA,4CAAA;EACA,2BAAA;EACA,YAAA;EACA,uBAAA;EACA,qCAAA;EACA,iCAAA;EACA,0BAAA;EACA,4BAAA;EACA,WAAA;A1BomEJ;;A0B/lEA;EACE,UAAA;EACA,mBAAA;A1BkmEF;A0B/lEE;EACE,gBAAA;A1BimEJ;A0B/lEI;EACE,4GAAA;EAIA,oFAAA;EAEA,kEAAA;EACA,mCAAA;EACA,sDAAA;EACA,4CAAA;EACA,oBAAA;EACA,eAAA;EAGE,qIAAA;A1B2lER;A0BtlEM;EAEE,qDAAA;EACA,6DAAA;A1BulER;A0BplEM;EACE,sEAAA;A1BslER;A0BhlEE;EACE,6BAAA;EACA,4BAAA;A1BklEJ;A0BhlEI;EACE,cAAA;EACA,UAAA;EACA,eAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA;EACA,OAAA;EACA,gBAAA;EACA,WAAA;EACA,eAAA;A1BklEN;;A0B5kEA;;EAEE,aAAA;EACA,cAAA;A1B+kEF;;A0B5kEA;;EAEE,2BAAA;EAAA,sBAAA;EACA,mCAAA;A1B+kEF;A0B7kEE;;EACE,gBAAA;A1BglEJ;;A0B1kEE;;EAEE,YAAA;EACA,4EAAA;A1B6kEJ;A0B1kEE;EACE,mCAAA;A1B4kEJ;A0BzkEE;EACE,gCAAA;EACA,sBAAA;A1B2kEJ;A0BxkEE;EACE,0DAAA;EACA,+BAAA;A1B0kEJ;A0BxkEI;EACE,yEAAA;EACA,kEAAA;A1B0kEN;;A0BjkEE;;;EAGE,aAAA;A1BokEJ;A0BjkEE;EACE,aAAA;EACA,yEAAA;EACA,qGAAA;A1BmkEJ;A0B9jEE;EACE,qCAAA;A1BgkEJ;;A0B5jEA;EACE,uCAAA;EACA,6BAAA;A1B+jEF;;A2BnxEA;;EAAA;AAMA;EACE,gBAAA;A3BmxEF;;A2B7wEE;EACE,qBAAA;EACA,UAAA;EACA,WAAA;EACA,mCAAA;EACA,kBAAA;EACA,+BAAA;EACA,WAAA;EACA,2BAAA;EACA,wBAAA;EACA,wCAAA;EACA,uCAAA;A3BgxEJ;A2B5wEI;EACE,wCAAA;EACA,cAAA;EACA,sBAAA;EACA,6CAAA;A3B8wEN;A2B1wEE;EACE,kBAAA;A3B4wEJ;;A2BlwEE;;;;;EACE,oBAAA;A3BywEJ;;A2BpwEA;EACE;IACE,yBAAA;E3BuwEF;AACF;A4B/zEA;;EAAA;AAIA;EACE,kBAAA;A5Bg0EF;A4B9zEE;EACE,yBAAA;EACA,qBAAA;EACA,YAAA;A5Bg0EJ;A4B7zEE;EAIE,cAAA;EACA,WAAA;EACA,kBAAA;EACA,YAAA;EACA,SAAA;EACA,uBAAA;EACA,gBAAA;EACA,oCAAA;EACA,mCAAA;EACA,2CAAA;EACA,2BAAA;EACA,2BAAA;EACA,kBAAA;EACA,+BAAA;EACA,mBAAA;EACA,qBAAA;EACA,uBAAA;EACA,mBAAA;EACA,UAAA;EACA,oBAAA;A5B4zEJ;A4BxzEE;EAEE,UAAA;EACA,gCAAA;EACA,wBAAA;EACA,sCAAA;EACA,qCAAA;EACA,gBAAA;EACA,6BAAA;EACA,WAAA;EACA,sCAAA;A5ByzEJ;A4BrzEI;EAEE,SAAA;EACA,YAAA;EACA,mCAAA;A5BszEN;A4BnzEI;EACE,mCAAA;EACA,gCAAA;EACA,2BAAA;A5BqzEN;A4BhzEI;EAEE,QAAA;EACA,WAAA;EACA,YAAA;EACA,UAAA;EACA,oCAAA;A5BizEN;A4B9yEI;EACE,kCAAA;EACA,gCAAA;EACA,yBAAA;A5BgzEN;A4B3yEI;EAEE,QAAA;EACA,WAAA;EACA,YAAA;EACA,UAAA;EACA,mCAAA;A5B4yEN;A4BzyEI;EACE,mCAAA;EACA,gCAAA;EACA,0BAAA;A5B2yEN;A4BpyEI;EAEE,UAAA;A5BqyEN;A4B7xEI;EAKI;IAEE,wBAAA;IACA,iCAAA;E5B0xER;E4BvxEM;IACE,uCAAA;E5ByxER;E4BlxEQ;IAEE,wBAAA;IACA,oCAAA;E5BmxEV;E4BhxEQ;IACE,0CAAA;E5BkxEV;E4B1wEQ;IAEE,wBAAA;IACA,kCAAA;E5B2wEV;E4BxwEQ;IACE,wCAAA;E5B0wEV;E4BlwEQ;IAEE,wBAAA;IACA,mCAAA;E5BmwEV;E4BhwEQ;IACE,yCAAA;E5BkwEV;AACF;A4B7vEI;EACE;IACE,mCAAA;IACA,UAAA;E5B+vEN;E4B7vEI;IACE,oCAAA;IACA,UAAA;E5B+vEN;AACF;A4B5vEI;EACE;IACE,UAAA;E5B8vEN;E4B5vEI;IACE,oCAAA;IACA,UAAA;E5B8vEN;E4B5vEI;IACE,gCAAA;IACA,UAAA;E5B8vEN;AACF;A4B3vEI;EACE;IACE,oCAAA;IACA,UAAA;E5B6vEN;E4B3vEI;IACE,mCAAA;IACA,UAAA;E5B6vEN;AACF;A4B1vEI;EACE;IACE,UAAA;E5B4vEN;E4B1vEI;IACE,mCAAA;IACA,UAAA;E5B4vEN;E4B1vEI;IACE,mCAAA;IACA,UAAA;E5B4vEN;AACF;A4BzvEI;EACE;IACE,mCAAA;IACA,UAAA;E5B2vEN;E4BzvEI;IACE,oCAAA;IACA,UAAA;E5B2vEN;AACF;A4BxvEI;EACE;IACE,UAAA;E5B0vEN;E4BxvEI;IACE,mCAAA;IACA,UAAA;E5B0vEN;E4BxvEI;IACE,kCAAA;IACA,UAAA;E5B0vEN;AACF;A4BvvEI;EACE;IACE,oCAAA;IACA,UAAA;E5ByvEN;E4BvvEI;IACE,mCAAA;IACA,UAAA;E5ByvEN;AACF;A4BtvEI;EACE;IACE,UAAA;E5BwvEN;E4BtvEI;IACE,oCAAA;IACA,UAAA;E5BwvEN;E4BtvEI;IACE,mCAAA;IACA,UAAA;E5BwvEN;AACF;;A6B1gFA;;EAAA;AAYA;EACE,eAAA;A7BogFF;;A6BhgFA;;EAEE,mBAAA;A7BmgFF;;A6B//EA;EACE,gBAAA;A7BkgFF;;A6B//EA;EACE,sBAAA;EACA,kBAAA;A7BkgFF;;A6B7/EA;;;;;;;;;EASE,8BAAA;A7BggFF;;A6B1/EA;EACE,cAAA;A7B6/EF;;A8B9iFE;;CAAA;AAYA;EACE;;;IAGE,yCAAA;IACA,kCAAA;IACA,gCAAA;IACA,uCAAA;IACA,gCAAA;IACA,+BAAA;IACA,kCAAA;E9BwiFJ;AACF","file":"pico.css","sourcesContent":["@charset \"UTF-8\";\n/*!\n * Pico CSS v1.5.11 (https://picocss.com)\n * Copyright 2019-2023 - Licensed under MIT\n */\n/**\n * Theme: default\n */\n:root {\n --font-family: system-ui, -apple-system, \"Segoe UI\", \"Roboto\", \"Ubuntu\",\n \"Cantarell\", \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\",\n \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --line-height: 1.5;\n --font-weight: 400;\n --font-size: 16px;\n --border-radius: 0.25rem;\n --border-width: 1px;\n --outline-width: 3px;\n --spacing: 1rem;\n --typography-spacing-vertical: 1.5rem;\n --block-spacing-vertical: calc(var(--spacing) * 2);\n --block-spacing-horizontal: var(--spacing);\n --grid-spacing-vertical: 0;\n --grid-spacing-horizontal: var(--spacing);\n --form-element-spacing-vertical: 0.75rem;\n --form-element-spacing-horizontal: 1rem;\n --nav-element-spacing-vertical: 1rem;\n --nav-element-spacing-horizontal: 0.5rem;\n --nav-link-spacing-vertical: 0.5rem;\n --nav-link-spacing-horizontal: 0.5rem;\n --form-label-font-weight: var(--font-weight);\n --transition: 0.2s ease-in-out;\n --modal-overlay-backdrop-filter: blur(0.25rem);\n}\n@media (min-width: 576px) {\n :root {\n --font-size: 17px;\n }\n}\n@media (min-width: 768px) {\n :root {\n --font-size: 18px;\n }\n}\n@media (min-width: 992px) {\n :root {\n --font-size: 19px;\n }\n}\n@media (min-width: 1200px) {\n :root {\n --font-size: 20px;\n }\n}\n\n@media (min-width: 576px) {\n body > header,\n body > main,\n body > footer,\n section {\n --block-spacing-vertical: calc(var(--spacing) * 2.5);\n }\n}\n@media (min-width: 768px) {\n body > header,\n body > main,\n body > footer,\n section {\n --block-spacing-vertical: calc(var(--spacing) * 3);\n }\n}\n@media (min-width: 992px) {\n body > header,\n body > main,\n body > footer,\n section {\n --block-spacing-vertical: calc(var(--spacing) * 3.5);\n }\n}\n@media (min-width: 1200px) {\n body > header,\n body > main,\n body > footer,\n section {\n --block-spacing-vertical: calc(var(--spacing) * 4);\n }\n}\n\n@media (min-width: 576px) {\n article {\n --block-spacing-horizontal: calc(var(--spacing) * 1.25);\n }\n}\n@media (min-width: 768px) {\n article {\n --block-spacing-horizontal: calc(var(--spacing) * 1.5);\n }\n}\n@media (min-width: 992px) {\n article {\n --block-spacing-horizontal: calc(var(--spacing) * 1.75);\n }\n}\n@media (min-width: 1200px) {\n article {\n --block-spacing-horizontal: calc(var(--spacing) * 2);\n }\n}\n\ndialog > article {\n --block-spacing-vertical: calc(var(--spacing) * 2);\n --block-spacing-horizontal: var(--spacing);\n}\n@media (min-width: 576px) {\n dialog > article {\n --block-spacing-vertical: calc(var(--spacing) * 2.5);\n --block-spacing-horizontal: calc(var(--spacing) * 1.25);\n }\n}\n@media (min-width: 768px) {\n dialog > article {\n --block-spacing-vertical: calc(var(--spacing) * 3);\n --block-spacing-horizontal: calc(var(--spacing) * 1.5);\n }\n}\n\na {\n --text-decoration: none;\n}\na.secondary, a.contrast {\n --text-decoration: underline;\n}\n\nsmall {\n --font-size: 0.875em;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n --font-weight: 700;\n}\n\nh1 {\n --font-size: 2rem;\n --typography-spacing-vertical: 3rem;\n}\n\nh2 {\n --font-size: 1.75rem;\n --typography-spacing-vertical: 2.625rem;\n}\n\nh3 {\n --font-size: 1.5rem;\n --typography-spacing-vertical: 2.25rem;\n}\n\nh4 {\n --font-size: 1.25rem;\n --typography-spacing-vertical: 1.874rem;\n}\n\nh5 {\n --font-size: 1.125rem;\n --typography-spacing-vertical: 1.6875rem;\n}\n\n[type=checkbox],\n[type=radio] {\n --border-width: 2px;\n}\n\n[type=checkbox][role=switch] {\n --border-width: 3px;\n}\n\nthead th,\nthead td,\ntfoot th,\ntfoot td {\n --border-width: 3px;\n}\n\n:not(thead, tfoot) > * > td {\n --font-size: 0.875em;\n}\n\npre,\ncode,\nkbd,\nsamp {\n --font-family: \"Menlo\", \"Consolas\", \"Roboto Mono\", \"Ubuntu Monospace\",\n \"Noto Mono\", \"Oxygen Mono\", \"Liberation Mono\", monospace,\n \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n}\n\nkbd {\n --font-weight: bolder;\n}\n\n[data-theme=light],\n:root:not([data-theme=dark]) {\n --background-color: #fff;\n --color: hsl(205, 20%, 32%);\n --h1-color: hsl(205, 30%, 15%);\n --h2-color: #24333e;\n --h3-color: hsl(205, 25%, 23%);\n --h4-color: #374956;\n --h5-color: hsl(205, 20%, 32%);\n --h6-color: #4d606d;\n --muted-color: hsl(205, 10%, 50%);\n --muted-border-color: hsl(205, 20%, 94%);\n --primary: hsl(195, 85%, 41%);\n --primary-hover: hsl(195, 90%, 32%);\n --primary-focus: rgba(16, 149, 193, 0.125);\n --primary-inverse: #fff;\n --secondary: hsl(205, 15%, 41%);\n --secondary-hover: hsl(205, 20%, 32%);\n --secondary-focus: rgba(89, 107, 120, 0.125);\n --secondary-inverse: #fff;\n --contrast: hsl(205, 30%, 15%);\n --contrast-hover: #000;\n --contrast-focus: rgba(89, 107, 120, 0.125);\n --contrast-inverse: #fff;\n --mark-background-color: #fff2ca;\n --mark-color: #543a26;\n --ins-color: #388e3c;\n --del-color: #c62828;\n --blockquote-border-color: var(--muted-border-color);\n --blockquote-footer-color: var(--muted-color);\n --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --form-element-background-color: transparent;\n --form-element-border-color: hsl(205, 14%, 68%);\n --form-element-color: var(--color);\n --form-element-placeholder-color: var(--muted-color);\n --form-element-active-background-color: transparent;\n --form-element-active-border-color: var(--primary);\n --form-element-focus-color: var(--primary-focus);\n --form-element-disabled-background-color: hsl(205, 18%, 86%);\n --form-element-disabled-border-color: hsl(205, 14%, 68%);\n --form-element-disabled-opacity: 0.5;\n --form-element-invalid-border-color: #c62828;\n --form-element-invalid-active-border-color: #d32f2f;\n --form-element-invalid-focus-color: rgba(211, 47, 47, 0.125);\n --form-element-valid-border-color: #388e3c;\n --form-element-valid-active-border-color: #43a047;\n --form-element-valid-focus-color: rgba(67, 160, 71, 0.125);\n --switch-background-color: hsl(205, 16%, 77%);\n --switch-color: var(--primary-inverse);\n --switch-checked-background-color: var(--primary);\n --range-border-color: hsl(205, 18%, 86%);\n --range-active-border-color: hsl(205, 16%, 77%);\n --range-thumb-border-color: var(--background-color);\n --range-thumb-color: var(--secondary);\n --range-thumb-hover-color: var(--secondary-hover);\n --range-thumb-active-color: var(--primary);\n --table-border-color: var(--muted-border-color);\n --table-row-stripped-background-color: #f6f8f9;\n --code-background-color: hsl(205, 20%, 94%);\n --code-color: var(--muted-color);\n --code-kbd-background-color: var(--contrast);\n --code-kbd-color: var(--contrast-inverse);\n --code-tag-color: hsl(330, 40%, 50%);\n --code-property-color: hsl(185, 40%, 40%);\n --code-value-color: hsl(40, 20%, 50%);\n --code-comment-color: hsl(205, 14%, 68%);\n --accordion-border-color: var(--muted-border-color);\n --accordion-close-summary-color: var(--color);\n --accordion-open-summary-color: var(--muted-color);\n --card-background-color: var(--background-color);\n --card-border-color: var(--muted-border-color);\n --card-box-shadow:\n 0.0145rem 0.029rem 0.174rem rgba(27, 40, 50, 0.01698),\n 0.0335rem 0.067rem 0.402rem rgba(27, 40, 50, 0.024),\n 0.0625rem 0.125rem 0.75rem rgba(27, 40, 50, 0.03),\n 0.1125rem 0.225rem 1.35rem rgba(27, 40, 50, 0.036),\n 0.2085rem 0.417rem 2.502rem rgba(27, 40, 50, 0.04302),\n 0.5rem 1rem 6rem rgba(27, 40, 50, 0.06),\n 0 0 0 0.0625rem rgba(27, 40, 50, 0.015);\n --card-sectionning-background-color: #fbfbfc;\n --dropdown-background-color: #fbfbfc;\n --dropdown-border-color: #e1e6eb;\n --dropdown-box-shadow: var(--card-box-shadow);\n --dropdown-color: var(--color);\n --dropdown-hover-background-color: hsl(205, 20%, 94%);\n --modal-overlay-background-color: rgba(213, 220, 226, 0.7);\n --progress-background-color: hsl(205, 18%, 86%);\n --progress-color: var(--primary);\n --loading-spinner-opacity: 0.5;\n --tooltip-background-color: var(--contrast);\n --tooltip-color: var(--contrast-inverse);\n --icon-checkbox: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button-inverse: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-close: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E\");\n --icon-date: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E\");\n --icon-invalid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(198, 40, 40)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\");\n --icon-minus: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E\");\n --icon-search: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E\");\n --icon-time: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-valid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(56, 142, 60)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n color-scheme: light;\n}\n\n@media only screen and (prefers-color-scheme: dark) {\n :root:not([data-theme]) {\n --background-color: #11191f;\n --color: hsl(205, 16%, 77%);\n --h1-color: hsl(205, 20%, 94%);\n --h2-color: #e1e6eb;\n --h3-color: hsl(205, 18%, 86%);\n --h4-color: #c8d1d8;\n --h5-color: hsl(205, 16%, 77%);\n --h6-color: #afbbc4;\n --muted-color: hsl(205, 10%, 50%);\n --muted-border-color: #1f2d38;\n --primary: hsl(195, 85%, 41%);\n --primary-hover: hsl(195, 80%, 50%);\n --primary-focus: rgba(16, 149, 193, 0.25);\n --primary-inverse: #fff;\n --secondary: hsl(205, 15%, 41%);\n --secondary-hover: hsl(205, 10%, 50%);\n --secondary-focus: rgba(115, 130, 140, 0.25);\n --secondary-inverse: #fff;\n --contrast: hsl(205, 20%, 94%);\n --contrast-hover: #fff;\n --contrast-focus: rgba(115, 130, 140, 0.25);\n --contrast-inverse: #000;\n --mark-background-color: #d1c284;\n --mark-color: #11191f;\n --ins-color: #388e3c;\n --del-color: #c62828;\n --blockquote-border-color: var(--muted-border-color);\n --blockquote-footer-color: var(--muted-color);\n --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --form-element-background-color: #11191f;\n --form-element-border-color: #374956;\n --form-element-color: var(--color);\n --form-element-placeholder-color: var(--muted-color);\n --form-element-active-background-color: var(--form-element-background-color);\n --form-element-active-border-color: var(--primary);\n --form-element-focus-color: var(--primary-focus);\n --form-element-disabled-background-color: hsl(205, 25%, 23%);\n --form-element-disabled-border-color: hsl(205, 20%, 32%);\n --form-element-disabled-opacity: 0.5;\n --form-element-invalid-border-color: #b71c1c;\n --form-element-invalid-active-border-color: #c62828;\n --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25);\n --form-element-valid-border-color: #2e7d32;\n --form-element-valid-active-border-color: #388e3c;\n --form-element-valid-focus-color: rgba(56, 142, 60, 0.25);\n --switch-background-color: #374956;\n --switch-color: var(--primary-inverse);\n --switch-checked-background-color: var(--primary);\n --range-border-color: #24333e;\n --range-active-border-color: hsl(205, 25%, 23%);\n --range-thumb-border-color: var(--background-color);\n --range-thumb-color: var(--secondary);\n --range-thumb-hover-color: var(--secondary-hover);\n --range-thumb-active-color: var(--primary);\n --table-border-color: var(--muted-border-color);\n --table-row-stripped-background-color: rgba(115, 130, 140, 0.05);\n --code-background-color: #18232c;\n --code-color: var(--muted-color);\n --code-kbd-background-color: var(--contrast);\n --code-kbd-color: var(--contrast-inverse);\n --code-tag-color: hsl(330, 30%, 50%);\n --code-property-color: hsl(185, 30%, 50%);\n --code-value-color: hsl(40, 10%, 50%);\n --code-comment-color: #4d606d;\n --accordion-border-color: var(--muted-border-color);\n --accordion-active-summary-color: var(--primary);\n --accordion-close-summary-color: var(--color);\n --accordion-open-summary-color: var(--muted-color);\n --card-background-color: #141e26;\n --card-border-color: var(--card-background-color);\n --card-box-shadow:\n 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),\n 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),\n 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),\n 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),\n 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),\n 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06),\n 0 0 0 0.0625rem rgba(0, 0, 0, 0.015);\n --card-sectionning-background-color: #18232c;\n --dropdown-background-color: hsl(205, 30%, 15%);\n --dropdown-border-color: #24333e;\n --dropdown-box-shadow: var(--card-box-shadow);\n --dropdown-color: var(--color);\n --dropdown-hover-background-color: rgba(36, 51, 62, 0.75);\n --modal-overlay-background-color: rgba(36, 51, 62, 0.8);\n --progress-background-color: #24333e;\n --progress-color: var(--primary);\n --loading-spinner-opacity: 0.5;\n --tooltip-background-color: var(--contrast);\n --tooltip-color: var(--contrast-inverse);\n --icon-checkbox: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button-inverse: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-close: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E\");\n --icon-date: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E\");\n --icon-invalid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\");\n --icon-minus: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E\");\n --icon-search: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E\");\n --icon-time: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-valid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n color-scheme: dark;\n }\n}\n[data-theme=dark] {\n --background-color: #11191f;\n --color: hsl(205, 16%, 77%);\n --h1-color: hsl(205, 20%, 94%);\n --h2-color: #e1e6eb;\n --h3-color: hsl(205, 18%, 86%);\n --h4-color: #c8d1d8;\n --h5-color: hsl(205, 16%, 77%);\n --h6-color: #afbbc4;\n --muted-color: hsl(205, 10%, 50%);\n --muted-border-color: #1f2d38;\n --primary: hsl(195, 85%, 41%);\n --primary-hover: hsl(195, 80%, 50%);\n --primary-focus: rgba(16, 149, 193, 0.25);\n --primary-inverse: #fff;\n --secondary: hsl(205, 15%, 41%);\n --secondary-hover: hsl(205, 10%, 50%);\n --secondary-focus: rgba(115, 130, 140, 0.25);\n --secondary-inverse: #fff;\n --contrast: hsl(205, 20%, 94%);\n --contrast-hover: #fff;\n --contrast-focus: rgba(115, 130, 140, 0.25);\n --contrast-inverse: #000;\n --mark-background-color: #d1c284;\n --mark-color: #11191f;\n --ins-color: #388e3c;\n --del-color: #c62828;\n --blockquote-border-color: var(--muted-border-color);\n --blockquote-footer-color: var(--muted-color);\n --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --form-element-background-color: #11191f;\n --form-element-border-color: #374956;\n --form-element-color: var(--color);\n --form-element-placeholder-color: var(--muted-color);\n --form-element-active-background-color: var(--form-element-background-color);\n --form-element-active-border-color: var(--primary);\n --form-element-focus-color: var(--primary-focus);\n --form-element-disabled-background-color: hsl(205, 25%, 23%);\n --form-element-disabled-border-color: hsl(205, 20%, 32%);\n --form-element-disabled-opacity: 0.5;\n --form-element-invalid-border-color: #b71c1c;\n --form-element-invalid-active-border-color: #c62828;\n --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25);\n --form-element-valid-border-color: #2e7d32;\n --form-element-valid-active-border-color: #388e3c;\n --form-element-valid-focus-color: rgba(56, 142, 60, 0.25);\n --switch-background-color: #374956;\n --switch-color: var(--primary-inverse);\n --switch-checked-background-color: var(--primary);\n --range-border-color: #24333e;\n --range-active-border-color: hsl(205, 25%, 23%);\n --range-thumb-border-color: var(--background-color);\n --range-thumb-color: var(--secondary);\n --range-thumb-hover-color: var(--secondary-hover);\n --range-thumb-active-color: var(--primary);\n --table-border-color: var(--muted-border-color);\n --table-row-stripped-background-color: rgba(115, 130, 140, 0.05);\n --code-background-color: #18232c;\n --code-color: var(--muted-color);\n --code-kbd-background-color: var(--contrast);\n --code-kbd-color: var(--contrast-inverse);\n --code-tag-color: hsl(330, 30%, 50%);\n --code-property-color: hsl(185, 30%, 50%);\n --code-value-color: hsl(40, 10%, 50%);\n --code-comment-color: #4d606d;\n --accordion-border-color: var(--muted-border-color);\n --accordion-active-summary-color: var(--primary);\n --accordion-close-summary-color: var(--color);\n --accordion-open-summary-color: var(--muted-color);\n --card-background-color: #141e26;\n --card-border-color: var(--card-background-color);\n --card-box-shadow:\n 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),\n 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),\n 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),\n 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),\n 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),\n 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06),\n 0 0 0 0.0625rem rgba(0, 0, 0, 0.015);\n --card-sectionning-background-color: #18232c;\n --dropdown-background-color: hsl(205, 30%, 15%);\n --dropdown-border-color: #24333e;\n --dropdown-box-shadow: var(--card-box-shadow);\n --dropdown-color: var(--color);\n --dropdown-hover-background-color: rgba(36, 51, 62, 0.75);\n --modal-overlay-background-color: rgba(36, 51, 62, 0.8);\n --progress-background-color: #24333e;\n --progress-color: var(--primary);\n --loading-spinner-opacity: 0.5;\n --tooltip-background-color: var(--contrast);\n --tooltip-color: var(--contrast-inverse);\n --icon-checkbox: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button-inverse: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-close: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E\");\n --icon-date: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E\");\n --icon-invalid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\");\n --icon-minus: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E\");\n --icon-search: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E\");\n --icon-time: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-valid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n color-scheme: dark;\n}\n\nprogress,\n[type=checkbox],\n[type=radio],\n[type=range] {\n accent-color: var(--primary);\n}\n\n/**\n * Document\n * Content-box & Responsive typography\n */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n background-repeat: no-repeat;\n}\n\n::before,\n::after {\n text-decoration: inherit;\n vertical-align: inherit;\n}\n\n:where(:root) {\n -webkit-tap-highlight-color: transparent;\n -webkit-text-size-adjust: 100%;\n text-size-adjust: 100%;\n background-color: var(--background-color);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: var(--font-size);\n line-height: var(--line-height);\n font-family: var(--font-family);\n text-rendering: optimizeLegibility;\n overflow-wrap: break-word;\n cursor: default;\n tab-size: 4;\n}\n\n/**\n * Sectioning\n * Container and responsive spacings for header, main, footer\n */\nmain {\n display: block;\n}\n\nbody {\n width: 100%;\n margin: 0;\n}\nbody > header,\nbody > main,\nbody > footer {\n width: 100%;\n margin-right: auto;\n margin-left: auto;\n padding: var(--block-spacing-vertical) 0;\n}\n\n/**\n* Container\n*/\n.container,\n.container-fluid {\n width: 100%;\n margin-right: auto;\n margin-left: auto;\n padding-right: var(--spacing);\n padding-left: var(--spacing);\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 510px;\n padding-right: 0;\n padding-left: 0;\n }\n}\n@media (min-width: 768px) {\n .container {\n max-width: 700px;\n }\n}\n@media (min-width: 992px) {\n .container {\n max-width: 920px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n max-width: 1130px;\n }\n}\n\n/**\n * Section\n * Responsive spacings for section\n */\nsection {\n margin-bottom: var(--block-spacing-vertical);\n}\n\n/**\n* Grid\n* Minimal grid system with auto-layout columns\n*/\n.grid {\n grid-column-gap: var(--grid-spacing-horizontal);\n grid-row-gap: var(--grid-spacing-vertical);\n display: grid;\n grid-template-columns: 1fr;\n margin: 0;\n}\n@media (min-width: 992px) {\n .grid {\n grid-template-columns: repeat(auto-fit, minmax(0%, 1fr));\n }\n}\n.grid > * {\n min-width: 0;\n}\n\n/**\n * Horizontal scroller (
)\n */\nfigure {\n display: block;\n margin: 0;\n padding: 0;\n overflow-x: auto;\n}\nfigure figcaption {\n padding: calc(var(--spacing) * 0.5) 0;\n color: var(--muted-color);\n}\n\n/**\n * Typography\n */\nb,\nstrong {\n font-weight: bolder;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 0.75em;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\naddress,\nblockquote,\ndl,\nfigure,\nform,\nol,\np,\npre,\ntable,\nul {\n margin-top: 0;\n margin-bottom: var(--typography-spacing-vertical);\n color: var(--color);\n font-style: normal;\n font-weight: var(--font-weight);\n font-size: var(--font-size);\n}\n\na,\n[role=link] {\n --color: var(--primary);\n --background-color: transparent;\n outline: none;\n background-color: var(--background-color);\n color: var(--color);\n text-decoration: var(--text-decoration);\n transition: background-color var(--transition), color var(--transition), text-decoration var(--transition), box-shadow var(--transition);\n}\na:is([aria-current], :hover, :active, :focus),\n[role=link]:is([aria-current], :hover, :active, :focus) {\n --color: var(--primary-hover);\n --text-decoration: underline;\n}\na:focus,\n[role=link]:focus {\n --background-color: var(--primary-focus);\n}\na.secondary,\n[role=link].secondary {\n --color: var(--secondary);\n}\na.secondary:is([aria-current], :hover, :active, :focus),\n[role=link].secondary:is([aria-current], :hover, :active, :focus) {\n --color: var(--secondary-hover);\n}\na.secondary:focus,\n[role=link].secondary:focus {\n --background-color: var(--secondary-focus);\n}\na.contrast,\n[role=link].contrast {\n --color: var(--contrast);\n}\na.contrast:is([aria-current], :hover, :active, :focus),\n[role=link].contrast:is([aria-current], :hover, :active, :focus) {\n --color: var(--contrast-hover);\n}\na.contrast:focus,\n[role=link].contrast:focus {\n --background-color: var(--contrast-focus);\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n margin-top: 0;\n margin-bottom: var(--typography-spacing-vertical);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: var(--font-size);\n font-family: var(--font-family);\n}\n\nh1 {\n --color: var(--h1-color);\n}\n\nh2 {\n --color: var(--h2-color);\n}\n\nh3 {\n --color: var(--h3-color);\n}\n\nh4 {\n --color: var(--h4-color);\n}\n\nh5 {\n --color: var(--h5-color);\n}\n\nh6 {\n --color: var(--h6-color);\n}\n\n:where(address, blockquote, dl, figure, form, ol, p, pre, table, ul) ~ :is(h1, h2, h3, h4, h5, h6) {\n margin-top: var(--typography-spacing-vertical);\n}\n\nhgroup,\n.headings {\n margin-bottom: var(--typography-spacing-vertical);\n}\nhgroup > *,\n.headings > * {\n margin-bottom: 0;\n}\nhgroup > *:last-child,\n.headings > *:last-child {\n --color: var(--muted-color);\n --font-weight: unset;\n font-size: 1rem;\n font-family: unset;\n}\n\np {\n margin-bottom: var(--typography-spacing-vertical);\n}\n\nsmall {\n font-size: var(--font-size);\n}\n\n:where(dl, ol, ul) {\n padding-right: 0;\n padding-left: var(--spacing);\n padding-inline-start: var(--spacing);\n padding-inline-end: 0;\n}\n:where(dl, ol, ul) li {\n margin-bottom: calc(var(--typography-spacing-vertical) * 0.25);\n}\n\n:where(dl, ol, ul) :is(dl, ol, ul) {\n margin: 0;\n margin-top: calc(var(--typography-spacing-vertical) * 0.25);\n}\n\nul li {\n list-style: square;\n}\n\nmark {\n padding: 0.125rem 0.25rem;\n background-color: var(--mark-background-color);\n color: var(--mark-color);\n vertical-align: baseline;\n}\n\nblockquote {\n display: block;\n margin: var(--typography-spacing-vertical) 0;\n padding: var(--spacing);\n border-right: none;\n border-left: 0.25rem solid var(--blockquote-border-color);\n border-inline-start: 0.25rem solid var(--blockquote-border-color);\n border-inline-end: none;\n}\nblockquote footer {\n margin-top: calc(var(--typography-spacing-vertical) * 0.5);\n color: var(--blockquote-footer-color);\n}\n\nabbr[title] {\n border-bottom: 1px dotted;\n text-decoration: none;\n cursor: help;\n}\n\nins {\n color: var(--ins-color);\n text-decoration: none;\n}\n\ndel {\n color: var(--del-color);\n}\n\n::selection {\n background-color: var(--primary-focus);\n}\n\n/**\n * Embedded content\n */\n:where(audio, canvas, iframe, img, svg, video) {\n vertical-align: middle;\n}\n\naudio,\nvideo {\n display: inline-block;\n}\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n:where(iframe) {\n border-style: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n border-style: none;\n}\n\n:where(svg:not([fill])) {\n fill: currentColor;\n}\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n/**\n * Button\n */\nbutton {\n margin: 0;\n overflow: visible;\n font-family: inherit;\n text-transform: none;\n}\n\nbutton,\n[type=button],\n[type=reset],\n[type=submit] {\n -webkit-appearance: button;\n}\n\nbutton {\n display: block;\n width: 100%;\n margin-bottom: var(--spacing);\n}\n\n[role=button] {\n display: inline-block;\n text-decoration: none;\n}\n\nbutton,\ninput[type=submit],\ninput[type=button],\ninput[type=reset],\n[role=button] {\n --background-color: var(--primary);\n --border-color: var(--primary);\n --color: var(--primary-inverse);\n --box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));\n padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);\n border: var(--border-width) solid var(--border-color);\n border-radius: var(--border-radius);\n outline: none;\n background-color: var(--background-color);\n box-shadow: var(--box-shadow);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: 1rem;\n line-height: var(--line-height);\n text-align: center;\n cursor: pointer;\n transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);\n}\nbutton:is([aria-current], :hover, :active, :focus),\ninput[type=submit]:is([aria-current], :hover, :active, :focus),\ninput[type=button]:is([aria-current], :hover, :active, :focus),\ninput[type=reset]:is([aria-current], :hover, :active, :focus),\n[role=button]:is([aria-current], :hover, :active, :focus) {\n --background-color: var(--primary-hover);\n --border-color: var(--primary-hover);\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));\n --color: var(--primary-inverse);\n}\nbutton:focus,\ninput[type=submit]:focus,\ninput[type=button]:focus,\ninput[type=reset]:focus,\n[role=button]:focus {\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),\n 0 0 0 var(--outline-width) var(--primary-focus);\n}\n\n:is(button, input[type=submit], input[type=button], [role=button]).secondary,\ninput[type=reset] {\n --background-color: var(--secondary);\n --border-color: var(--secondary);\n --color: var(--secondary-inverse);\n cursor: pointer;\n}\n:is(button, input[type=submit], input[type=button], [role=button]).secondary:is([aria-current], :hover, :active, :focus),\ninput[type=reset]:is([aria-current], :hover, :active, :focus) {\n --background-color: var(--secondary-hover);\n --border-color: var(--secondary-hover);\n --color: var(--secondary-inverse);\n}\n:is(button, input[type=submit], input[type=button], [role=button]).secondary:focus,\ninput[type=reset]:focus {\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),\n 0 0 0 var(--outline-width) var(--secondary-focus);\n}\n\n:is(button, input[type=submit], input[type=button], [role=button]).contrast {\n --background-color: var(--contrast);\n --border-color: var(--contrast);\n --color: var(--contrast-inverse);\n}\n:is(button, input[type=submit], input[type=button], [role=button]).contrast:is([aria-current], :hover, :active, :focus) {\n --background-color: var(--contrast-hover);\n --border-color: var(--contrast-hover);\n --color: var(--contrast-inverse);\n}\n:is(button, input[type=submit], input[type=button], [role=button]).contrast:focus {\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),\n 0 0 0 var(--outline-width) var(--contrast-focus);\n}\n\n:is(button, input[type=submit], input[type=button], [role=button]).outline,\ninput[type=reset].outline {\n --background-color: transparent;\n --color: var(--primary);\n}\n:is(button, input[type=submit], input[type=button], [role=button]).outline:is([aria-current], :hover, :active, :focus),\ninput[type=reset].outline:is([aria-current], :hover, :active, :focus) {\n --background-color: transparent;\n --color: var(--primary-hover);\n}\n\n:is(button, input[type=submit], input[type=button], [role=button]).outline.secondary,\ninput[type=reset].outline {\n --color: var(--secondary);\n}\n:is(button, input[type=submit], input[type=button], [role=button]).outline.secondary:is([aria-current], :hover, :active, :focus),\ninput[type=reset].outline:is([aria-current], :hover, :active, :focus) {\n --color: var(--secondary-hover);\n}\n\n:is(button, input[type=submit], input[type=button], [role=button]).outline.contrast {\n --color: var(--contrast);\n}\n:is(button, input[type=submit], input[type=button], [role=button]).outline.contrast:is([aria-current], :hover, :active, :focus) {\n --color: var(--contrast-hover);\n}\n\n:where(button, [type=submit], [type=button], [type=reset], [role=button])[disabled],\n:where(fieldset[disabled]) :is(button, [type=submit], [type=button], [type=reset], [role=button]),\na[role=button]:not([href]) {\n opacity: 0.5;\n pointer-events: none;\n}\n\n/**\n * Form elements\n */\ninput,\noptgroup,\nselect,\ntextarea {\n margin: 0;\n font-size: 1rem;\n line-height: var(--line-height);\n font-family: inherit;\n letter-spacing: inherit;\n}\n\ninput {\n overflow: visible;\n}\n\nselect {\n text-transform: none;\n}\n\nlegend {\n max-width: 100%;\n padding: 0;\n color: inherit;\n white-space: normal;\n}\n\ntextarea {\n overflow: auto;\n}\n\n[type=checkbox],\n[type=radio] {\n padding: 0;\n}\n\n::-webkit-inner-spin-button,\n::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=search] {\n -webkit-appearance: textfield;\n outline-offset: -2px;\n}\n\n[type=search]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n -webkit-appearance: button;\n font: inherit;\n}\n\n::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\n:-moz-focusring {\n outline: none;\n}\n\n:-moz-ui-invalid {\n box-shadow: none;\n}\n\n::-ms-expand {\n display: none;\n}\n\n[type=file],\n[type=range] {\n padding: 0;\n border-width: 0;\n}\n\ninput:not([type=checkbox], [type=radio], [type=range]) {\n height: calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2);\n}\n\nfieldset {\n margin: 0;\n margin-bottom: var(--spacing);\n padding: 0;\n border: 0;\n}\n\nlabel,\nfieldset legend {\n display: block;\n margin-bottom: calc(var(--spacing) * 0.25);\n font-weight: var(--form-label-font-weight, var(--font-weight));\n}\n\ninput:not([type=checkbox], [type=radio]),\nselect,\ntextarea {\n width: 100%;\n}\n\ninput:not([type=checkbox], [type=radio], [type=range], [type=file]),\nselect,\ntextarea {\n appearance: none;\n padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);\n}\n\ninput,\nselect,\ntextarea {\n --background-color: var(--form-element-background-color);\n --border-color: var(--form-element-border-color);\n --color: var(--form-element-color);\n --box-shadow: none;\n border: var(--border-width) solid var(--border-color);\n border-radius: var(--border-radius);\n outline: none;\n background-color: var(--background-color);\n box-shadow: var(--box-shadow);\n color: var(--color);\n font-weight: var(--font-weight);\n transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);\n}\n\ninput:not([type=submit], [type=button], [type=reset], [type=checkbox], [type=radio], [readonly]):is(:active, :focus),\n:where(select, textarea):is(:active, :focus) {\n --background-color: var(--form-element-active-background-color);\n}\n\ninput:not([type=submit], [type=button], [type=reset], [role=switch], [readonly]):is(:active, :focus),\n:where(select, textarea):is(:active, :focus) {\n --border-color: var(--form-element-active-border-color);\n}\n\ninput:not([type=submit], [type=button], [type=reset], [type=range], [type=file], [readonly]):focus,\nselect:focus,\ntextarea:focus {\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color);\n}\n\ninput:not([type=submit], [type=button], [type=reset])[disabled],\nselect[disabled],\ntextarea[disabled],\n:where(fieldset[disabled]) :is(input:not([type=submit], [type=button], [type=reset]), select, textarea) {\n --background-color: var(--form-element-disabled-background-color);\n --border-color: var(--form-element-disabled-border-color);\n opacity: var(--form-element-disabled-opacity);\n pointer-events: none;\n}\n\n:where(input, select, textarea):not([type=checkbox], [type=radio], [type=date], [type=datetime-local], [type=month], [type=time], [type=week])[aria-invalid] {\n padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem) !important;\n padding-left: var(--form-element-spacing-horizontal);\n padding-inline-start: var(--form-element-spacing-horizontal) !important;\n padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem) !important;\n background-position: center right 0.75rem;\n background-size: 1rem auto;\n background-repeat: no-repeat;\n}\n:where(input, select, textarea):not([type=checkbox], [type=radio], [type=date], [type=datetime-local], [type=month], [type=time], [type=week])[aria-invalid=false] {\n background-image: var(--icon-valid);\n}\n:where(input, select, textarea):not([type=checkbox], [type=radio], [type=date], [type=datetime-local], [type=month], [type=time], [type=week])[aria-invalid=true] {\n background-image: var(--icon-invalid);\n}\n:where(input, select, textarea)[aria-invalid=false] {\n --border-color: var(--form-element-valid-border-color);\n}\n:where(input, select, textarea)[aria-invalid=false]:is(:active, :focus) {\n --border-color: var(--form-element-valid-active-border-color) !important;\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important;\n}\n:where(input, select, textarea)[aria-invalid=true] {\n --border-color: var(--form-element-invalid-border-color);\n}\n:where(input, select, textarea)[aria-invalid=true]:is(:active, :focus) {\n --border-color: var(--form-element-invalid-active-border-color) !important;\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important;\n}\n\n[dir=rtl] :where(input, select, textarea):not([type=checkbox], [type=radio]):is([aria-invalid], [aria-invalid=true], [aria-invalid=false]) {\n background-position: center left 0.75rem;\n}\n\ninput::placeholder,\ninput::-webkit-input-placeholder,\ntextarea::placeholder,\ntextarea::-webkit-input-placeholder,\nselect:invalid {\n color: var(--form-element-placeholder-color);\n opacity: 1;\n}\n\ninput:not([type=checkbox], [type=radio]),\nselect,\ntextarea {\n margin-bottom: var(--spacing);\n}\n\nselect::-ms-expand {\n border: 0;\n background-color: transparent;\n}\nselect:not([multiple], [size]) {\n padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem);\n padding-left: var(--form-element-spacing-horizontal);\n padding-inline-start: var(--form-element-spacing-horizontal);\n padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem);\n background-image: var(--icon-chevron);\n background-position: center right 0.75rem;\n background-size: 1rem auto;\n background-repeat: no-repeat;\n}\n\n[dir=rtl] select:not([multiple], [size]) {\n background-position: center left 0.75rem;\n}\n\n:where(input, select, textarea, .grid) + small {\n display: block;\n width: 100%;\n margin-top: calc(var(--spacing) * -0.75);\n margin-bottom: var(--spacing);\n color: var(--muted-color);\n}\n\nlabel > :where(input, select, textarea) {\n margin-top: calc(var(--spacing) * 0.25);\n}\n\n/**\n * Form elements\n * Checkboxes & Radios\n */\n[type=checkbox],\n[type=radio] {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n width: 1.25em;\n height: 1.25em;\n margin-top: -0.125em;\n margin-right: 0.375em;\n margin-left: 0;\n margin-inline-start: 0;\n margin-inline-end: 0.375em;\n border-width: var(--border-width);\n font-size: inherit;\n vertical-align: middle;\n cursor: pointer;\n}\n[type=checkbox]::-ms-check,\n[type=radio]::-ms-check {\n display: none;\n}\n[type=checkbox]:checked, [type=checkbox]:checked:active, [type=checkbox]:checked:focus,\n[type=radio]:checked,\n[type=radio]:checked:active,\n[type=radio]:checked:focus {\n --background-color: var(--primary);\n --border-color: var(--primary);\n background-image: var(--icon-checkbox);\n background-position: center;\n background-size: 0.75em auto;\n background-repeat: no-repeat;\n}\n[type=checkbox] ~ label,\n[type=radio] ~ label {\n display: inline-block;\n margin-right: 0.375em;\n margin-bottom: 0;\n cursor: pointer;\n}\n\n[type=checkbox]:indeterminate {\n --background-color: var(--primary);\n --border-color: var(--primary);\n background-image: var(--icon-minus);\n background-position: center;\n background-size: 0.75em auto;\n background-repeat: no-repeat;\n}\n\n[type=radio] {\n border-radius: 50%;\n}\n[type=radio]:checked, [type=radio]:checked:active, [type=radio]:checked:focus {\n --background-color: var(--primary-inverse);\n border-width: 0.35em;\n background-image: none;\n}\n\n[type=checkbox][role=switch] {\n --background-color: var(--switch-background-color);\n --border-color: var(--switch-background-color);\n --color: var(--switch-color);\n width: 2.25em;\n height: 1.25em;\n border: var(--border-width) solid var(--border-color);\n border-radius: 1.25em;\n background-color: var(--background-color);\n line-height: 1.25em;\n}\n[type=checkbox][role=switch]:focus {\n --background-color: var(--switch-background-color);\n --border-color: var(--switch-background-color);\n}\n[type=checkbox][role=switch]:checked {\n --background-color: var(--switch-checked-background-color);\n --border-color: var(--switch-checked-background-color);\n}\n[type=checkbox][role=switch]:before {\n display: block;\n width: calc(1.25em - (var(--border-width) * 2));\n height: 100%;\n border-radius: 50%;\n background-color: var(--color);\n content: \"\";\n transition: margin 0.1s ease-in-out;\n}\n[type=checkbox][role=switch]:checked {\n background-image: none;\n}\n[type=checkbox][role=switch]:checked::before {\n margin-left: calc(1.125em - var(--border-width));\n margin-inline-start: calc(1.125em - var(--border-width));\n}\n\n[type=checkbox][aria-invalid=false],\n[type=checkbox]:checked[aria-invalid=false],\n[type=radio][aria-invalid=false],\n[type=radio]:checked[aria-invalid=false],\n[type=checkbox][role=switch][aria-invalid=false],\n[type=checkbox][role=switch]:checked[aria-invalid=false] {\n --border-color: var(--form-element-valid-border-color);\n}\n[type=checkbox][aria-invalid=true],\n[type=checkbox]:checked[aria-invalid=true],\n[type=radio][aria-invalid=true],\n[type=radio]:checked[aria-invalid=true],\n[type=checkbox][role=switch][aria-invalid=true],\n[type=checkbox][role=switch]:checked[aria-invalid=true] {\n --border-color: var(--form-element-invalid-border-color);\n}\n\n/**\n * Form elements\n * Alternatives input types (Not Checkboxes & Radios)\n */\n[type=color]::-webkit-color-swatch-wrapper {\n padding: 0;\n}\n[type=color]::-moz-focus-inner {\n padding: 0;\n}\n[type=color]::-webkit-color-swatch {\n border: 0;\n border-radius: calc(var(--border-radius) * 0.5);\n}\n[type=color]::-moz-color-swatch {\n border: 0;\n border-radius: calc(var(--border-radius) * 0.5);\n}\n\ninput:not([type=checkbox], [type=radio], [type=range], [type=file]):is([type=date], [type=datetime-local], [type=month], [type=time], [type=week]) {\n --icon-position: 0.75rem;\n --icon-width: 1rem;\n padding-right: calc(var(--icon-width) + var(--icon-position));\n background-image: var(--icon-date);\n background-position: center right var(--icon-position);\n background-size: var(--icon-width) auto;\n background-repeat: no-repeat;\n}\ninput:not([type=checkbox], [type=radio], [type=range], [type=file])[type=time] {\n background-image: var(--icon-time);\n}\n\n[type=date]::-webkit-calendar-picker-indicator,\n[type=datetime-local]::-webkit-calendar-picker-indicator,\n[type=month]::-webkit-calendar-picker-indicator,\n[type=time]::-webkit-calendar-picker-indicator,\n[type=week]::-webkit-calendar-picker-indicator {\n width: var(--icon-width);\n margin-right: calc(var(--icon-width) * -1);\n margin-left: var(--icon-position);\n opacity: 0;\n}\n\n[dir=rtl] :is([type=date], [type=datetime-local], [type=month], [type=time], [type=week]) {\n text-align: right;\n}\n\n@-moz-document url-prefix() {\n [type=date],\n [type=datetime-local],\n [type=month],\n [type=time],\n [type=week] {\n padding-right: var(--form-element-spacing-horizontal) !important;\n background-image: none !important;\n }\n}\n[type=file] {\n --color: var(--muted-color);\n padding: calc(var(--form-element-spacing-vertical) * 0.5) 0;\n border: 0;\n border-radius: 0;\n background: none;\n}\n[type=file]::file-selector-button {\n --background-color: var(--secondary);\n --border-color: var(--secondary);\n --color: var(--secondary-inverse);\n margin-right: calc(var(--spacing) / 2);\n margin-left: 0;\n margin-inline-start: 0;\n margin-inline-end: calc(var(--spacing) / 2);\n padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);\n border: var(--border-width) solid var(--border-color);\n border-radius: var(--border-radius);\n outline: none;\n background-color: var(--background-color);\n box-shadow: var(--box-shadow);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: 1rem;\n line-height: var(--line-height);\n text-align: center;\n cursor: pointer;\n transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);\n}\n[type=file]::file-selector-button:is(:hover, :active, :focus) {\n --background-color: var(--secondary-hover);\n --border-color: var(--secondary-hover);\n}\n[type=file]::-webkit-file-upload-button {\n --background-color: var(--secondary);\n --border-color: var(--secondary);\n --color: var(--secondary-inverse);\n margin-right: calc(var(--spacing) / 2);\n margin-left: 0;\n margin-inline-start: 0;\n margin-inline-end: calc(var(--spacing) / 2);\n padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);\n border: var(--border-width) solid var(--border-color);\n border-radius: var(--border-radius);\n outline: none;\n background-color: var(--background-color);\n box-shadow: var(--box-shadow);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: 1rem;\n line-height: var(--line-height);\n text-align: center;\n cursor: pointer;\n transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);\n}\n[type=file]::-webkit-file-upload-button:is(:hover, :active, :focus) {\n --background-color: var(--secondary-hover);\n --border-color: var(--secondary-hover);\n}\n[type=file]::-ms-browse {\n --background-color: var(--secondary);\n --border-color: var(--secondary);\n --color: var(--secondary-inverse);\n margin-right: calc(var(--spacing) / 2);\n margin-left: 0;\n margin-inline-start: 0;\n margin-inline-end: calc(var(--spacing) / 2);\n padding: calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);\n border: var(--border-width) solid var(--border-color);\n border-radius: var(--border-radius);\n outline: none;\n background-color: var(--background-color);\n box-shadow: var(--box-shadow);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: 1rem;\n line-height: var(--line-height);\n text-align: center;\n cursor: pointer;\n transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);\n}\n[type=file]::-ms-browse:is(:hover, :active, :focus) {\n --background-color: var(--secondary-hover);\n --border-color: var(--secondary-hover);\n}\n\n[type=range] {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n width: 100%;\n height: 1.25rem;\n background: none;\n}\n[type=range]::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.25rem;\n border-radius: var(--border-radius);\n background-color: var(--range-border-color);\n transition: background-color var(--transition), box-shadow var(--transition);\n}\n[type=range]::-moz-range-track {\n width: 100%;\n height: 0.25rem;\n border-radius: var(--border-radius);\n background-color: var(--range-border-color);\n transition: background-color var(--transition), box-shadow var(--transition);\n}\n[type=range]::-ms-track {\n width: 100%;\n height: 0.25rem;\n border-radius: var(--border-radius);\n background-color: var(--range-border-color);\n transition: background-color var(--transition), box-shadow var(--transition);\n}\n[type=range]::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 1.25rem;\n height: 1.25rem;\n margin-top: -0.5rem;\n border: 2px solid var(--range-thumb-border-color);\n border-radius: 50%;\n background-color: var(--range-thumb-color);\n cursor: pointer;\n transition: background-color var(--transition), transform var(--transition);\n}\n[type=range]::-moz-range-thumb {\n -webkit-appearance: none;\n width: 1.25rem;\n height: 1.25rem;\n margin-top: -0.5rem;\n border: 2px solid var(--range-thumb-border-color);\n border-radius: 50%;\n background-color: var(--range-thumb-color);\n cursor: pointer;\n transition: background-color var(--transition), transform var(--transition);\n}\n[type=range]::-ms-thumb {\n -webkit-appearance: none;\n width: 1.25rem;\n height: 1.25rem;\n margin-top: -0.5rem;\n border: 2px solid var(--range-thumb-border-color);\n border-radius: 50%;\n background-color: var(--range-thumb-color);\n cursor: pointer;\n transition: background-color var(--transition), transform var(--transition);\n}\n[type=range]:hover, [type=range]:focus {\n --range-border-color: var(--range-active-border-color);\n --range-thumb-color: var(--range-thumb-hover-color);\n}\n[type=range]:active {\n --range-thumb-color: var(--range-thumb-active-color);\n}\n[type=range]:active::-webkit-slider-thumb {\n transform: scale(1.25);\n}\n[type=range]:active::-moz-range-thumb {\n transform: scale(1.25);\n}\n[type=range]:active::-ms-thumb {\n transform: scale(1.25);\n}\n\ninput:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search] {\n padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem);\n border-radius: 5rem;\n background-image: var(--icon-search);\n background-position: center left 1.125rem;\n background-size: 1rem auto;\n background-repeat: no-repeat;\n}\ninput:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid] {\n padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem) !important;\n background-position: center left 1.125rem, center right 0.75rem;\n}\ninput:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid=false] {\n background-image: var(--icon-search), var(--icon-valid);\n}\ninput:not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid=true] {\n background-image: var(--icon-search), var(--icon-invalid);\n}\n\n[type=search]::-webkit-search-cancel-button {\n -webkit-appearance: none;\n display: none;\n}\n\n[dir=rtl] :where(input):not([type=checkbox], [type=radio], [type=range], [type=file])[type=search] {\n background-position: center right 1.125rem;\n}\n[dir=rtl] :where(input):not([type=checkbox], [type=radio], [type=range], [type=file])[type=search][aria-invalid] {\n background-position: center right 1.125rem, center left 0.75rem;\n}\n\n/**\n * Table\n */\n:where(table) {\n width: 100%;\n border-collapse: collapse;\n border-spacing: 0;\n text-indent: 0;\n}\n\nth,\ntd {\n padding: calc(var(--spacing) / 2) var(--spacing);\n border-bottom: var(--border-width) solid var(--table-border-color);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: var(--font-size);\n text-align: left;\n text-align: start;\n}\n\ntfoot th,\ntfoot td {\n border-top: var(--border-width) solid var(--table-border-color);\n border-bottom: 0;\n}\n\ntable[role=grid] tbody tr:nth-child(odd) {\n background-color: var(--table-row-stripped-background-color);\n}\n\n/**\n * Code\n */\npre,\ncode,\nkbd,\nsamp {\n font-size: 0.875em;\n font-family: var(--font-family);\n}\n\npre {\n -ms-overflow-style: scrollbar;\n overflow: auto;\n}\n\npre,\ncode,\nkbd {\n border-radius: var(--border-radius);\n background: var(--code-background-color);\n color: var(--code-color);\n font-weight: var(--font-weight);\n line-height: initial;\n}\n\ncode,\nkbd {\n display: inline-block;\n padding: 0.375rem 0.5rem;\n}\n\npre {\n display: block;\n margin-bottom: var(--spacing);\n overflow-x: auto;\n}\npre > code {\n display: block;\n padding: var(--spacing);\n background: none;\n font-size: 14px;\n line-height: var(--line-height);\n}\n\ncode b {\n color: var(--code-tag-color);\n font-weight: var(--font-weight);\n}\ncode i {\n color: var(--code-property-color);\n font-style: normal;\n}\ncode u {\n color: var(--code-value-color);\n text-decoration: none;\n}\ncode em {\n color: var(--code-comment-color);\n font-style: normal;\n}\n\nkbd {\n background-color: var(--code-kbd-background-color);\n color: var(--code-kbd-color);\n vertical-align: baseline;\n}\n\n/**\n * Miscs\n */\nhr {\n height: 0;\n border: 0;\n border-top: 1px solid var(--muted-border-color);\n color: inherit;\n}\n\n[hidden],\ntemplate {\n display: none !important;\n}\n\ncanvas {\n display: inline-block;\n}\n\n/**\n * Accordion (
)\n */\ndetails {\n display: block;\n margin-bottom: var(--spacing);\n padding-bottom: var(--spacing);\n border-bottom: var(--border-width) solid var(--accordion-border-color);\n}\ndetails summary {\n line-height: 1rem;\n list-style-type: none;\n cursor: pointer;\n transition: color var(--transition);\n}\ndetails summary:not([role]) {\n color: var(--accordion-close-summary-color);\n}\ndetails summary::-webkit-details-marker {\n display: none;\n}\ndetails summary::marker {\n display: none;\n}\ndetails summary::-moz-list-bullet {\n list-style-type: none;\n}\ndetails summary::after {\n display: block;\n width: 1rem;\n height: 1rem;\n margin-inline-start: calc(var(--spacing, 1rem) * 0.5);\n float: right;\n transform: rotate(-90deg);\n background-image: var(--icon-chevron);\n background-position: right center;\n background-size: 1rem auto;\n background-repeat: no-repeat;\n content: \"\";\n transition: transform var(--transition);\n}\ndetails summary:focus {\n outline: none;\n}\ndetails summary:focus:not([role=button]) {\n color: var(--accordion-active-summary-color);\n}\ndetails summary[role=button] {\n width: 100%;\n text-align: left;\n}\ndetails summary[role=button]::after {\n height: calc(1rem * var(--line-height, 1.5));\n background-image: var(--icon-chevron-button);\n}\ndetails summary[role=button]:not(.outline).contrast::after {\n background-image: var(--icon-chevron-button-inverse);\n}\ndetails[open] > summary {\n margin-bottom: calc(var(--spacing));\n}\ndetails[open] > summary:not([role]):not(:focus) {\n color: var(--accordion-open-summary-color);\n}\ndetails[open] > summary::after {\n transform: rotate(0);\n}\n\n[dir=rtl] details summary {\n text-align: right;\n}\n[dir=rtl] details summary::after {\n float: left;\n background-position: left center;\n}\n\n/**\n * Card (
)\n */\narticle {\n margin: var(--block-spacing-vertical) 0;\n padding: var(--block-spacing-vertical) var(--block-spacing-horizontal);\n border-radius: var(--border-radius);\n background: var(--card-background-color);\n box-shadow: var(--card-box-shadow);\n}\narticle > header,\narticle > footer {\n margin-right: calc(var(--block-spacing-horizontal) * -1);\n margin-left: calc(var(--block-spacing-horizontal) * -1);\n padding: calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal);\n background-color: var(--card-sectionning-background-color);\n}\narticle > header {\n margin-top: calc(var(--block-spacing-vertical) * -1);\n margin-bottom: var(--block-spacing-vertical);\n border-bottom: var(--border-width) solid var(--card-border-color);\n border-top-right-radius: var(--border-radius);\n border-top-left-radius: var(--border-radius);\n}\narticle > footer {\n margin-top: var(--block-spacing-vertical);\n margin-bottom: calc(var(--block-spacing-vertical) * -1);\n border-top: var(--border-width) solid var(--card-border-color);\n border-bottom-right-radius: var(--border-radius);\n border-bottom-left-radius: var(--border-radius);\n}\n\n/**\n * Modal ()\n */\n:root {\n --scrollbar-width: 0px;\n}\n\ndialog {\n display: flex;\n z-index: 999;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n align-items: center;\n justify-content: center;\n width: inherit;\n min-width: 100%;\n height: inherit;\n min-height: 100%;\n padding: var(--spacing);\n border: 0;\n backdrop-filter: var(--modal-overlay-backdrop-filter);\n background-color: var(--modal-overlay-background-color);\n color: var(--color);\n}\ndialog article {\n max-height: calc(100vh - var(--spacing) * 2);\n overflow: auto;\n}\n@media (min-width: 576px) {\n dialog article {\n max-width: 510px;\n }\n}\n@media (min-width: 768px) {\n dialog article {\n max-width: 700px;\n }\n}\ndialog article > header,\ndialog article > footer {\n padding: calc(var(--block-spacing-vertical) * 0.5) var(--block-spacing-horizontal);\n}\ndialog article > header .close {\n margin: 0;\n margin-left: var(--spacing);\n float: right;\n}\ndialog article > footer {\n text-align: right;\n}\ndialog article > footer [role=button] {\n margin-bottom: 0;\n}\ndialog article > footer [role=button]:not(:first-of-type) {\n margin-left: calc(var(--spacing) * 0.5);\n}\ndialog article p:last-of-type {\n margin: 0;\n}\ndialog article .close {\n display: block;\n width: 1rem;\n height: 1rem;\n margin-top: calc(var(--block-spacing-vertical) * -0.5);\n margin-bottom: var(--typography-spacing-vertical);\n margin-left: auto;\n background-image: var(--icon-close);\n background-position: center;\n background-size: auto 1rem;\n background-repeat: no-repeat;\n opacity: 0.5;\n transition: opacity var(--transition);\n}\ndialog article .close:is([aria-current], :hover, :active, :focus) {\n opacity: 1;\n}\ndialog:not([open]), dialog[open=false] {\n display: none;\n}\n\n.modal-is-open {\n padding-right: var(--scrollbar-width, 0px);\n overflow: hidden;\n pointer-events: none;\n touch-action: none;\n}\n.modal-is-open dialog {\n pointer-events: auto;\n}\n\n:where(.modal-is-opening, .modal-is-closing) dialog,\n:where(.modal-is-opening, .modal-is-closing) dialog > article {\n animation-duration: 0.2s;\n animation-timing-function: ease-in-out;\n animation-fill-mode: both;\n}\n:where(.modal-is-opening, .modal-is-closing) dialog {\n animation-duration: 0.8s;\n animation-name: modal-overlay;\n}\n:where(.modal-is-opening, .modal-is-closing) dialog > article {\n animation-delay: 0.2s;\n animation-name: modal;\n}\n\n.modal-is-closing dialog,\n.modal-is-closing dialog > article {\n animation-delay: 0s;\n animation-direction: reverse;\n}\n\n@keyframes modal-overlay {\n from {\n backdrop-filter: none;\n background-color: transparent;\n }\n}\n@keyframes modal {\n from {\n transform: translateY(-100%);\n opacity: 0;\n }\n}\n/**\n * Nav\n */\n:where(nav li)::before {\n float: left;\n content: \"​\";\n}\n\nnav,\nnav ul {\n display: flex;\n}\n\nnav {\n justify-content: space-between;\n}\nnav ol,\nnav ul {\n align-items: center;\n margin-bottom: 0;\n padding: 0;\n list-style: none;\n}\nnav ol:first-of-type,\nnav ul:first-of-type {\n margin-left: calc(var(--nav-element-spacing-horizontal) * -1);\n}\nnav ol:last-of-type,\nnav ul:last-of-type {\n margin-right: calc(var(--nav-element-spacing-horizontal) * -1);\n}\nnav li {\n display: inline-block;\n margin: 0;\n padding: var(--nav-element-spacing-vertical) var(--nav-element-spacing-horizontal);\n}\nnav li > * {\n --spacing: 0;\n}\nnav :where(a, [role=link]) {\n display: inline-block;\n margin: calc(var(--nav-link-spacing-vertical) * -1) calc(var(--nav-link-spacing-horizontal) * -1);\n padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);\n border-radius: var(--border-radius);\n text-decoration: none;\n}\nnav :where(a, [role=link]):is([aria-current], :hover, :active, :focus) {\n text-decoration: none;\n}\nnav[aria-label=breadcrumb] {\n align-items: center;\n justify-content: start;\n}\nnav[aria-label=breadcrumb] ul li:not(:first-child) {\n margin-inline-start: var(--nav-link-spacing-horizontal);\n}\nnav[aria-label=breadcrumb] ul li:not(:last-child) ::after {\n position: absolute;\n width: calc(var(--nav-link-spacing-horizontal) * 2);\n margin-inline-start: calc(var(--nav-link-spacing-horizontal) / 2);\n content: \"/\";\n color: var(--muted-color);\n text-align: center;\n}\nnav[aria-label=breadcrumb] a[aria-current] {\n background-color: transparent;\n color: inherit;\n text-decoration: none;\n pointer-events: none;\n}\nnav [role=button] {\n margin-right: inherit;\n margin-left: inherit;\n padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);\n}\n\naside nav,\naside ol,\naside ul,\naside li {\n display: block;\n}\naside li {\n padding: calc(var(--nav-element-spacing-vertical) * 0.5) var(--nav-element-spacing-horizontal);\n}\naside li a {\n display: block;\n}\naside li [role=button] {\n margin: inherit;\n}\n\n[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after {\n content: \"\\\\\";\n}\n\n/**\n * Progress\n */\nprogress {\n display: inline-block;\n vertical-align: baseline;\n}\n\nprogress {\n -webkit-appearance: none;\n -moz-appearance: none;\n display: inline-block;\n appearance: none;\n width: 100%;\n height: 0.5rem;\n margin-bottom: calc(var(--spacing) * 0.5);\n overflow: hidden;\n border: 0;\n border-radius: var(--border-radius);\n background-color: var(--progress-background-color);\n color: var(--progress-color);\n}\nprogress::-webkit-progress-bar {\n border-radius: var(--border-radius);\n background: none;\n}\nprogress[value]::-webkit-progress-value {\n background-color: var(--progress-color);\n}\nprogress::-moz-progress-bar {\n background-color: var(--progress-color);\n}\n@media (prefers-reduced-motion: no-preference) {\n progress:indeterminate {\n background: var(--progress-background-color) linear-gradient(to right, var(--progress-color) 30%, var(--progress-background-color) 30%) top left/150% 150% no-repeat;\n animation: progress-indeterminate 1s linear infinite;\n }\n progress:indeterminate[value]::-webkit-progress-value {\n background-color: transparent;\n }\n progress:indeterminate::-moz-progress-bar {\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n [dir=rtl] progress:indeterminate {\n animation-direction: reverse;\n }\n}\n\n@keyframes progress-indeterminate {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n/**\n * Dropdown ([role=\"list\"])\n */\ndetails[role=list],\nli[role=list] {\n position: relative;\n}\n\ndetails[role=list] summary + ul,\nli[role=list] > ul {\n display: flex;\n z-index: 99;\n position: absolute;\n top: auto;\n right: 0;\n left: 0;\n flex-direction: column;\n margin: 0;\n padding: 0;\n border: var(--border-width) solid var(--dropdown-border-color);\n border-radius: var(--border-radius);\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n background-color: var(--dropdown-background-color);\n box-shadow: var(--card-box-shadow);\n color: var(--dropdown-color);\n white-space: nowrap;\n}\ndetails[role=list] summary + ul li,\nli[role=list] > ul li {\n width: 100%;\n margin-bottom: 0;\n padding: calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);\n list-style: none;\n}\ndetails[role=list] summary + ul li:first-of-type,\nli[role=list] > ul li:first-of-type {\n margin-top: calc(var(--form-element-spacing-vertical) * 0.5);\n}\ndetails[role=list] summary + ul li:last-of-type,\nli[role=list] > ul li:last-of-type {\n margin-bottom: calc(var(--form-element-spacing-vertical) * 0.5);\n}\ndetails[role=list] summary + ul li a,\nli[role=list] > ul li a {\n display: block;\n margin: calc(var(--form-element-spacing-vertical) * -0.5) calc(var(--form-element-spacing-horizontal) * -1);\n padding: calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);\n overflow: hidden;\n color: var(--dropdown-color);\n text-decoration: none;\n text-overflow: ellipsis;\n}\ndetails[role=list] summary + ul li a:hover,\nli[role=list] > ul li a:hover {\n background-color: var(--dropdown-hover-background-color);\n}\n\ndetails[role=list] summary::after,\nli[role=list] > a::after {\n display: block;\n width: 1rem;\n height: calc(1rem * var(--line-height, 1.5));\n margin-inline-start: 0.5rem;\n float: right;\n transform: rotate(0deg);\n background-image: var(--icon-chevron);\n background-position: right center;\n background-size: 1rem auto;\n background-repeat: no-repeat;\n content: \"\";\n}\n\ndetails[role=list] {\n padding: 0;\n border-bottom: none;\n}\ndetails[role=list] summary {\n margin-bottom: 0;\n}\ndetails[role=list] summary:not([role]) {\n height: calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2);\n padding: var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);\n border: var(--border-width) solid var(--form-element-border-color);\n border-radius: var(--border-radius);\n background-color: var(--form-element-background-color);\n color: var(--form-element-placeholder-color);\n line-height: inherit;\n cursor: pointer;\n transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);\n}\ndetails[role=list] summary:not([role]):active, details[role=list] summary:not([role]):focus {\n border-color: var(--form-element-active-border-color);\n background-color: var(--form-element-active-background-color);\n}\ndetails[role=list] summary:not([role]):focus {\n box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color);\n}\ndetails[role=list][open] summary {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\ndetails[role=list][open] summary::before {\n display: block;\n z-index: 1;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background: none;\n content: \"\";\n cursor: default;\n}\n\nnav details[role=list] summary,\nnav li[role=list] a {\n display: flex;\n direction: ltr;\n}\n\nnav details[role=list] summary + ul,\nnav li[role=list] > ul {\n min-width: fit-content;\n border-radius: var(--border-radius);\n}\nnav details[role=list] summary + ul li a,\nnav li[role=list] > ul li a {\n border-radius: 0;\n}\n\nnav details[role=list] summary,\nnav details[role=list] summary:not([role]) {\n height: auto;\n padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);\n}\nnav details[role=list][open] summary {\n border-radius: var(--border-radius);\n}\nnav details[role=list] summary + ul {\n margin-top: var(--outline-width);\n margin-inline-start: 0;\n}\nnav details[role=list] summary[role=link] {\n margin-bottom: calc(var(--nav-link-spacing-vertical) * -1);\n line-height: var(--line-height);\n}\nnav details[role=list] summary[role=link] + ul {\n margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width));\n margin-inline-start: calc(var(--nav-link-spacing-horizontal) * -1);\n}\n\nli[role=list]:hover > ul,\nli[role=list] a:active ~ ul,\nli[role=list] a:focus ~ ul {\n display: flex;\n}\nli[role=list] > ul {\n display: none;\n margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width));\n margin-inline-start: calc(var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal));\n}\nli[role=list] > a::after {\n background-image: var(--icon-chevron);\n}\n\nlabel > details[role=list] {\n margin-top: calc(var(--spacing) * 0.25);\n margin-bottom: var(--spacing);\n}\n\n/**\n * Loading ([aria-busy=true])\n */\n[aria-busy=true] {\n cursor: progress;\n}\n\n[aria-busy=true]:not(input, select, textarea, html)::before {\n display: inline-block;\n width: 1em;\n height: 1em;\n border: 0.1875em solid currentColor;\n border-radius: 1em;\n border-right-color: transparent;\n content: \"\";\n vertical-align: text-bottom;\n vertical-align: -0.125em;\n animation: spinner 0.75s linear infinite;\n opacity: var(--loading-spinner-opacity);\n}\n[aria-busy=true]:not(input, select, textarea, html):not(:empty)::before {\n margin-right: calc(var(--spacing) * 0.5);\n margin-left: 0;\n margin-inline-start: 0;\n margin-inline-end: calc(var(--spacing) * 0.5);\n}\n[aria-busy=true]:not(input, select, textarea, html):empty {\n text-align: center;\n}\n\nbutton[aria-busy=true],\ninput[type=submit][aria-busy=true],\ninput[type=button][aria-busy=true],\ninput[type=reset][aria-busy=true],\na[aria-busy=true] {\n pointer-events: none;\n}\n\n@keyframes spinner {\n to {\n transform: rotate(360deg);\n }\n}\n/**\n * Tooltip ([data-tooltip])\n */\n[data-tooltip] {\n position: relative;\n}\n[data-tooltip]:not(a, button, input) {\n border-bottom: 1px dotted;\n text-decoration: none;\n cursor: help;\n}\n[data-tooltip][data-placement=top]::before, [data-tooltip][data-placement=top]::after, [data-tooltip]::before, [data-tooltip]::after {\n display: block;\n z-index: 99;\n position: absolute;\n bottom: 100%;\n left: 50%;\n padding: 0.25rem 0.5rem;\n overflow: hidden;\n transform: translate(-50%, -0.25rem);\n border-radius: var(--border-radius);\n background: var(--tooltip-background-color);\n content: attr(data-tooltip);\n color: var(--tooltip-color);\n font-style: normal;\n font-weight: var(--font-weight);\n font-size: 0.875rem;\n text-decoration: none;\n text-overflow: ellipsis;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n}\n[data-tooltip][data-placement=top]::after, [data-tooltip]::after {\n padding: 0;\n transform: translate(-50%, 0rem);\n border-top: 0.3rem solid;\n border-right: 0.3rem solid transparent;\n border-left: 0.3rem solid transparent;\n border-radius: 0;\n background-color: transparent;\n content: \"\";\n color: var(--tooltip-background-color);\n}\n[data-tooltip][data-placement=bottom]::before, [data-tooltip][data-placement=bottom]::after {\n top: 100%;\n bottom: auto;\n transform: translate(-50%, 0.25rem);\n}\n[data-tooltip][data-placement=bottom]:after {\n transform: translate(-50%, -0.3rem);\n border: 0.3rem solid transparent;\n border-bottom: 0.3rem solid;\n}\n[data-tooltip][data-placement=left]::before, [data-tooltip][data-placement=left]::after {\n top: 50%;\n right: 100%;\n bottom: auto;\n left: auto;\n transform: translate(-0.25rem, -50%);\n}\n[data-tooltip][data-placement=left]:after {\n transform: translate(0.3rem, -50%);\n border: 0.3rem solid transparent;\n border-left: 0.3rem solid;\n}\n[data-tooltip][data-placement=right]::before, [data-tooltip][data-placement=right]::after {\n top: 50%;\n right: auto;\n bottom: auto;\n left: 100%;\n transform: translate(0.25rem, -50%);\n}\n[data-tooltip][data-placement=right]:after {\n transform: translate(-0.3rem, -50%);\n border: 0.3rem solid transparent;\n border-right: 0.3rem solid;\n}\n[data-tooltip]:focus::before, [data-tooltip]:focus::after, [data-tooltip]:hover::before, [data-tooltip]:hover::after {\n opacity: 1;\n}\n@media (hover: hover) and (pointer: fine) {\n [data-tooltip][data-placement=bottom]:focus::before, [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::before, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after, [data-tooltip]:hover::before, [data-tooltip]:hover::after {\n animation-duration: 0.2s;\n animation-name: tooltip-slide-top;\n }\n [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after, [data-tooltip]:hover::after {\n animation-name: tooltip-caret-slide-top;\n }\n [data-tooltip][data-placement=bottom]:focus::before, [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover::before, [data-tooltip][data-placement=bottom]:hover::after {\n animation-duration: 0.2s;\n animation-name: tooltip-slide-bottom;\n }\n [data-tooltip][data-placement=bottom]:focus::after, [data-tooltip][data-placement=bottom]:hover::after {\n animation-name: tooltip-caret-slide-bottom;\n }\n [data-tooltip][data-placement=left]:focus::before, [data-tooltip][data-placement=left]:focus::after, [data-tooltip][data-placement=left]:hover::before, [data-tooltip][data-placement=left]:hover::after {\n animation-duration: 0.2s;\n animation-name: tooltip-slide-left;\n }\n [data-tooltip][data-placement=left]:focus::after, [data-tooltip][data-placement=left]:hover::after {\n animation-name: tooltip-caret-slide-left;\n }\n [data-tooltip][data-placement=right]:focus::before, [data-tooltip][data-placement=right]:focus::after, [data-tooltip][data-placement=right]:hover::before, [data-tooltip][data-placement=right]:hover::after {\n animation-duration: 0.2s;\n animation-name: tooltip-slide-right;\n }\n [data-tooltip][data-placement=right]:focus::after, [data-tooltip][data-placement=right]:hover::after {\n animation-name: tooltip-caret-slide-right;\n }\n}\n@keyframes tooltip-slide-top {\n from {\n transform: translate(-50%, 0.75rem);\n opacity: 0;\n }\n to {\n transform: translate(-50%, -0.25rem);\n opacity: 1;\n }\n}\n@keyframes tooltip-caret-slide-top {\n from {\n opacity: 0;\n }\n 50% {\n transform: translate(-50%, -0.25rem);\n opacity: 0;\n }\n to {\n transform: translate(-50%, 0rem);\n opacity: 1;\n }\n}\n@keyframes tooltip-slide-bottom {\n from {\n transform: translate(-50%, -0.75rem);\n opacity: 0;\n }\n to {\n transform: translate(-50%, 0.25rem);\n opacity: 1;\n }\n}\n@keyframes tooltip-caret-slide-bottom {\n from {\n opacity: 0;\n }\n 50% {\n transform: translate(-50%, -0.5rem);\n opacity: 0;\n }\n to {\n transform: translate(-50%, -0.3rem);\n opacity: 1;\n }\n}\n@keyframes tooltip-slide-left {\n from {\n transform: translate(0.75rem, -50%);\n opacity: 0;\n }\n to {\n transform: translate(-0.25rem, -50%);\n opacity: 1;\n }\n}\n@keyframes tooltip-caret-slide-left {\n from {\n opacity: 0;\n }\n 50% {\n transform: translate(0.05rem, -50%);\n opacity: 0;\n }\n to {\n transform: translate(0.3rem, -50%);\n opacity: 1;\n }\n}\n@keyframes tooltip-slide-right {\n from {\n transform: translate(-0.75rem, -50%);\n opacity: 0;\n }\n to {\n transform: translate(0.25rem, -50%);\n opacity: 1;\n }\n}\n@keyframes tooltip-caret-slide-right {\n from {\n opacity: 0;\n }\n 50% {\n transform: translate(-0.05rem, -50%);\n opacity: 0;\n }\n to {\n transform: translate(-0.3rem, -50%);\n opacity: 1;\n }\n}\n\n/**\n * Accessibility & User interaction\n */\n[aria-controls] {\n cursor: pointer;\n}\n\n[aria-disabled=true],\n[disabled] {\n cursor: not-allowed;\n}\n\n[aria-hidden=false][hidden] {\n display: initial;\n}\n\n[aria-hidden=false][hidden]:not(:focus) {\n clip: rect(0, 0, 0, 0);\n position: absolute;\n}\n\na,\narea,\nbutton,\ninput,\nlabel,\nselect,\nsummary,\ntextarea,\n[tabindex] {\n -ms-touch-action: manipulation;\n}\n\n[dir=rtl] {\n direction: rtl;\n}\n\n/**\n* Reduce Motion Features\n*/\n@media (prefers-reduced-motion: reduce) {\n *:not([aria-busy=true]),\n :not([aria-busy=true])::before,\n :not([aria-busy=true])::after {\n background-attachment: initial !important;\n animation-duration: 1ms !important;\n animation-delay: -1ms !important;\n animation-iteration-count: 1 !important;\n scroll-behavior: auto !important;\n transition-delay: 0s !important;\n transition-duration: 0s !important;\n }\n}\n\n/*# sourceMappingURL=pico.css.map */\n","/*!\n * Pico CSS v1.5.11 (https://picocss.com)\n * Copyright 2019-2023 - Licensed under MIT\n */\n\n// Config\n@import \"variables\";\n\n// Theming\n@import \"themes/default\";\n\n// Layout\n@import \"layout/document\"; // html\n@import \"layout/sectioning\"; // body, header, main, footer\n@import \"layout/container\"; // .container, .container-fluid\n@import \"layout/section\"; // section\n@import \"layout/grid\"; // .grid\n@import \"layout/scroller\"; // figure\n\n// Content\n@import \"content/typography\"; // a, headings, p, ul, blockquote, ...\n@import \"content/embedded\"; // audio, canvas, iframe, img, svg, video\n@import \"content/button\"; // button, a[role=button], type=button, type=submit ...\n@import \"content/form\"; // input, select, textarea, label, fieldset, legend\n@import \"content/form-checkbox-radio\"; // type=checkbox, type=radio, role=switch\n@import \"content/form-alt-input-types\"; // type=color, type=date, type=file, type=search, ...\n@import \"content/table\"; // table, tr, td, ...\n@import \"content/code\"; // pre, code, ...\n@import \"content/miscs\"; // hr, template, [hidden], dialog, canvas\n\n// Components\n@import \"components/accordion\"; // details, summary\n@import \"components/card\"; // article\n@import \"components/modal\"; // dialog\n@import \"components/nav\"; // nav\n@import \"components/progress\"; // progress\n@import \"components/dropdown\"; // dropdown\n\n// Utilities\n@import \"utilities/loading\"; // aria-busy=true\n@import \"utilities/tooltip\"; // data-tooltip\n@import \"utilities/accessibility\"; // -ms-touch-action, aria-*\n@import \"utilities/reduce-motion\"; // prefers-reduced-motion\n","/**\n * Theme: default\n */\n\n// Variables\n@import \"../variables\";\n@import \"default/colors\";\n\n// Commons styles\n@import \"default/styles\";\n\n// Light theme (Default)\n// Can be forced with data-theme=\"light\"\n@import \"default/light\";\n\n// Dark theme (Auto)\n// Automatically enabled if user has Dark mode enabled\n@import \"default/dark\";\n@media only screen and (prefers-color-scheme: dark) {\n :root:not([data-theme]) {\n @include dark;\n }\n}\n\n// Dark theme (Forced)\n// Enabled if forced with data-theme=\"dark\"\n[data-theme=\"dark\"] {\n @include dark;\n}\n\n// Accent-color\nprogress,\n[type=\"checkbox\"],\n[type=\"radio\"],\n[type=\"range\"] {\n accent-color: var(--primary);\n}\n","// Commons Styles\n:root {\n // Typography\n --font-family: system-ui, -apple-system, \"Segoe UI\", \"Roboto\", \"Ubuntu\",\n \"Cantarell\", \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\",\n \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --line-height: 1.5;\n --font-weight: 400;\n --font-size: 16px;\n\n // Responsive typography\n @if $enable-responsive-typography {\n @if map-get($breakpoints, \"sm\") {\n @media (min-width: map-get($breakpoints, \"sm\")) {\n --font-size: 17px;\n }\n }\n\n @if map-get($breakpoints, \"md\") {\n @media (min-width: map-get($breakpoints, \"md\")) {\n --font-size: 18px;\n }\n }\n\n @if map-get($breakpoints, \"lg\") {\n @media (min-width: map-get($breakpoints, \"lg\")) {\n --font-size: 19px;\n }\n }\n\n @if map-get($breakpoints, \"xl\") {\n @media (min-width: map-get($breakpoints, \"xl\")) {\n --font-size: 20px;\n }\n }\n }\n\n // Borders\n --border-radius: 0.25rem;\n --border-width: 1px;\n --outline-width: 3px;\n\n // Spacings\n --spacing: 1rem;\n\n // Spacings for typography elements\n --typography-spacing-vertical: 1.5rem;\n\n // Spacings for body > header, body > main, body > footer, section, article\n --block-spacing-vertical: calc(var(--spacing) * 2);\n --block-spacing-horizontal: var(--spacing);\n\n @if ($enable-classes and $enable-grid) {\n --grid-spacing-vertical: 0;\n --grid-spacing-horizontal: var(--spacing);\n }\n\n // Spacings for form elements and button\n --form-element-spacing-vertical: 0.75rem;\n --form-element-spacing-horizontal: 1rem;\n\n // Spacings for nav component\n --nav-element-spacing-vertical: 1rem;\n --nav-element-spacing-horizontal: 0.5rem;\n --nav-link-spacing-vertical: 0.5rem;\n --nav-link-spacing-horizontal: 0.5rem;\n\n // Font weight for form labels & fieldsets legend\n --form-label-font-weight: var(--font-weight);\n\n // Transitions\n --transition: 0.2s ease-in-out;\n\n // Modal ()\n --modal-overlay-backdrop-filter: blur(0.25rem);\n}\n\n// Responsives spacings\n@if $enable-responsive-spacings {\n // Sectioning\n #{$semantic-root-element} > header,\n #{$semantic-root-element} > main,\n #{$semantic-root-element} > footer,\n section {\n @if map-get($breakpoints, \"sm\") {\n @media (min-width: map-get($breakpoints, \"sm\")) {\n --block-spacing-vertical: calc(var(--spacing) * 2.5);\n }\n }\n\n @if map-get($breakpoints, \"md\") {\n @media (min-width: map-get($breakpoints, \"md\")) {\n --block-spacing-vertical: calc(var(--spacing) * 3);\n }\n }\n\n @if map-get($breakpoints, \"lg\") {\n @media (min-width: map-get($breakpoints, \"lg\")) {\n --block-spacing-vertical: calc(var(--spacing) * 3.5);\n }\n }\n\n @if map-get($breakpoints, \"xl\") {\n @media (min-width: map-get($breakpoints, \"xl\")) {\n --block-spacing-vertical: calc(var(--spacing) * 4);\n }\n }\n }\n\n // Card (
)\n article {\n @if map-get($breakpoints, \"sm\") {\n @media (min-width: map-get($breakpoints, \"sm\")) {\n --block-spacing-horizontal: calc(var(--spacing) * 1.25);\n }\n }\n\n @if map-get($breakpoints, \"md\") {\n @media (min-width: map-get($breakpoints, \"md\")) {\n --block-spacing-horizontal: calc(var(--spacing) * 1.5);\n }\n }\n\n @if map-get($breakpoints, \"lg\") {\n @media (min-width: map-get($breakpoints, \"lg\")) {\n --block-spacing-horizontal: calc(var(--spacing) * 1.75);\n }\n }\n\n @if map-get($breakpoints, \"xl\") {\n @media (min-width: map-get($breakpoints, \"xl\")) {\n --block-spacing-horizontal: calc(var(--spacing) * 2);\n }\n }\n }\n\n // Modal\n dialog > article {\n\n --block-spacing-vertical: calc(var(--spacing) * 2);\n --block-spacing-horizontal: var(--spacing);\n\n @if map-get($breakpoints, \"sm\") {\n @media (min-width: map-get($breakpoints, \"sm\")) {\n --block-spacing-vertical: calc(var(--spacing) * 2.5);\n --block-spacing-horizontal: calc(var(--spacing) * 1.25);\n }\n }\n\n @if map-get($breakpoints, \"md\") {\n @media (min-width: map-get($breakpoints, \"md\")) {\n --block-spacing-vertical: calc(var(--spacing) * 3);\n --block-spacing-horizontal: calc(var(--spacing) * 1.5);\n }\n }\n }\n}\n\n// Link\na {\n --text-decoration: none;\n\n // Secondary & Contrast\n @if $enable-classes {\n &.secondary,\n &.contrast {\n --text-decoration: underline;\n }\n }\n}\n\n// Small\nsmall {\n --font-size: 0.875em;\n}\n\n// Headings\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n --font-weight: 700;\n}\n\nh1 {\n --font-size: 2rem;\n --typography-spacing-vertical: 3rem;\n}\n\nh2 {\n --font-size: 1.75rem;\n --typography-spacing-vertical: 2.625rem;\n}\n\nh3 {\n --font-size: 1.5rem;\n --typography-spacing-vertical: 2.25rem;\n}\n\nh4 {\n --font-size: 1.25rem;\n --typography-spacing-vertical: 1.874rem;\n}\n\nh5 {\n --font-size: 1.125rem;\n --typography-spacing-vertical: 1.6875rem;\n}\n\n// Forms elements\n[type=\"checkbox\"],\n[type=\"radio\"] {\n --border-width: 2px;\n}\n\n[type=\"checkbox\"][role=\"switch\"] {\n --border-width: 3px;\n}\n\n// Table\nthead,\ntfoot {\n th,\n td {\n --border-width: 3px;\n }\n}\n\n:not(thead, tfoot) > * > td {\n --font-size: 0.875em;\n}\n\n// Code\npre,\ncode,\nkbd,\nsamp {\n --font-family: \"Menlo\", \"Consolas\", \"Roboto Mono\", \"Ubuntu Monospace\",\n \"Noto Mono\", \"Oxygen Mono\", \"Liberation Mono\", monospace,\n \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n}\n\nkbd {\n --font-weight: bolder;\n}\n","@import \"../../functions\";\n\n// Default: Light theme\n[data-theme=\"light\"],\n:root:not([data-theme=\"dark\"]) {\n --background-color: #{$white};\n\n // Texts colors\n --color: #{$grey-700};\n --h1-color: #{$grey-900};\n --h2-color: #{mix($grey-900, $grey-800)};\n --h3-color: #{$grey-800};\n --h4-color: #{mix($grey-800, $grey-700)};\n --h5-color: #{$grey-700};\n --h6-color: #{mix($grey-700, $grey-600)};\n\n // Muted colors\n --muted-color: #{$grey-500};\n --muted-border-color: #{$grey-50};\n\n // Primary colors\n --primary: #{$primary-600};\n --primary-hover: #{$primary-700};\n --primary-focus: #{rgba($primary-600, 0.125)};\n --primary-inverse: #{$white};\n\n // Secondary colors\n --secondary: #{$grey-600};\n --secondary-hover: #{$grey-700};\n --secondary-focus: #{rgba($grey-600, 0.125)};\n --secondary-inverse: #{$white};\n\n // Contrast colors\n --contrast: #{$grey-900};\n --contrast-hover: #{$black};\n --contrast-focus: #{rgba($grey-600, 0.125)};\n --contrast-inverse: #{$white};\n\n // Highlighted text ()\n --mark-background-color: #{mix($amber-100, $amber-50)};\n --mark-color: #{mix($grey-900, $amber-900, 75%)};\n\n // Inserted () & Deleted ()\n --ins-color: #{$green-700};\n --del-color: #{$red-800};\n\n // Blockquote\n --blockquote-border-color: var(--muted-border-color);\n --blockquote-footer-color: var(--muted-color);\n\n // Button\n // To disable box-shadow, remove the var or set to '0 0 0 rgba(0, 0, 0, 0)'\n // Don't use, 'none, 'false, 'null', '0', etc.\n --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n\n // Form elements\n --form-element-background-color: transparent;\n --form-element-border-color: #{$grey-300};\n --form-element-color: var(--color);\n --form-element-placeholder-color: var(--muted-color);\n --form-element-active-background-color: transparent;\n --form-element-active-border-color: var(--primary);\n --form-element-focus-color: var(--primary-focus);\n --form-element-disabled-background-color: #{$grey-100};\n --form-element-disabled-border-color: #{$grey-300};\n --form-element-disabled-opacity: 0.5;\n --form-element-invalid-border-color: #{$red-800};\n --form-element-invalid-active-border-color: #{$red-700};\n --form-element-invalid-focus-color: #{rgba($red-700, 0.125)};\n --form-element-valid-border-color: #{$green-700};\n --form-element-valid-active-border-color: #{$green-600};\n --form-element-valid-focus-color: #{rgba($green-600, 0.125)};\n\n // Switch (input[type=\"checkbox\"][role=\"switch\"])\n --switch-background-color: #{$grey-200};\n --switch-color: var(--primary-inverse);\n --switch-checked-background-color: var(--primary);\n\n // Range (input[type=\"range\"])\n --range-border-color: #{$grey-100};\n --range-active-border-color: #{$grey-200};\n --range-thumb-border-color: var(--background-color);\n --range-thumb-color: var(--secondary);\n --range-thumb-hover-color: var(--secondary-hover);\n --range-thumb-active-color: var(--primary);\n\n // Table\n --table-border-color: var(--muted-border-color);\n --table-row-stripped-background-color: #{mix($grey-50, $white)};\n\n // Code\n --code-background-color: #{$grey-50};\n --code-color: var(--muted-color);\n --code-kbd-background-color: var(--contrast);\n --code-kbd-color: var(--contrast-inverse);\n --code-tag-color: #{hsl(330, 40%, 50%)};\n --code-property-color: #{hsl(185, 40%, 40%)};\n --code-value-color: #{hsl(40, 20%, 50%)};\n --code-comment-color: #{$grey-300};\n\n // Accordion (
)\n --accordion-border-color: var(--muted-border-color);\n --accordion-close-summary-color: var(--color);\n --accordion-open-summary-color: var(--muted-color);\n\n // Card (
)\n $box-shadow-elevation: 1rem;\n $box-shadow-blur-strengh: 6rem;\n $box-shadow-opacity: 0.06;\n --card-background-color: var(--background-color);\n --card-border-color: var(--muted-border-color);\n --card-box-shadow:\n #{($box-shadow-elevation * 0.5 * 0.029)} #{($box-shadow-elevation * 0.029)} #{($box-shadow-blur-strengh * 0.029)} #{rgba($grey-900, ($box-shadow-opacity * 0.283))},\n #{($box-shadow-elevation * 0.5 * 0.067)} #{($box-shadow-elevation * 0.067)} #{($box-shadow-blur-strengh * 0.067)} #{rgba($grey-900, ($box-shadow-opacity * 0.4))},\n #{($box-shadow-elevation * 0.5 * 0.125)} #{($box-shadow-elevation * 0.125)} #{($box-shadow-blur-strengh * 0.125)} #{rgba($grey-900, ($box-shadow-opacity * 0.5))},\n #{($box-shadow-elevation * 0.5 * 0.225)} #{($box-shadow-elevation * 0.225)} #{($box-shadow-blur-strengh * 0.225)} #{rgba($grey-900, ($box-shadow-opacity * 0.6))},\n #{($box-shadow-elevation * 0.5 * 0.417)} #{($box-shadow-elevation * 0.417)} #{($box-shadow-blur-strengh * 0.417)} #{rgba($grey-900, ($box-shadow-opacity * 0.717))},\n #{($box-shadow-elevation * 0.5)} #{$box-shadow-elevation} #{$box-shadow-blur-strengh} #{rgba($grey-900, $box-shadow-opacity)},\n 0 0 0 0.0625rem #{rgba($grey-900, ($box-shadow-opacity * 0.25) )};\n --card-sectionning-background-color: #{mix($grey-50, $white, 25%)};\n\n // Dropdown (
)\n --dropdown-background-color: #{mix($grey-50, $white, 25%)};\n --dropdown-border-color: #{mix($grey-100, $grey-50)};\n --dropdown-box-shadow: var(--card-box-shadow);\n --dropdown-color: var(--color);\n --dropdown-hover-background-color: #{$grey-50};\n\n // Modal ()\n --modal-overlay-background-color: #{rgba($grey-100, 0.7)};\n\n // Progress\n --progress-background-color: #{$grey-100};\n --progress-color: var(--primary);\n\n // Loading ([aria-busy=true])\n --loading-spinner-opacity: 0.5;\n\n // Tooltip ([data-tooltip])\n --tooltip-background-color: var(--contrast);\n --tooltip-color: var(--contrast-inverse);\n\n // Icons\n --icon-checkbox: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button-inverse: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-close: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-500)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E\");\n --icon-date: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E\");\n --icon-invalid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($red-800)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\");\n --icon-minus: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E\");\n --icon-search: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E\");\n --icon-time: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E\"); \n --icon-valid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($green-700)}' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n \n // Document\n color-scheme: light;\n}\n","@import \"../../functions\";\n\n// Default: Dark theme\n@mixin dark {\n --background-color: #{mix($black, $grey-900, 37.5%)};\n\n // Texts colors\n --color: #{$grey-200};\n --h1-color: #{$grey-50};\n --h2-color: #{mix($grey-100, $grey-50)};\n --h3-color: #{$grey-100};\n --h4-color: #{mix($grey-200, $grey-100)};\n --h5-color: #{$grey-200};\n --h6-color: #{mix($grey-300, $grey-200)};\n\n // Muted colors\n --muted-color: #{$grey-500};\n --muted-border-color: #{mix($grey-900, $grey-800, 75%)};\n\n // Primary colors\n --primary: #{$primary-600};\n --primary-hover: #{$primary-500};\n --primary-focus: #{rgba($primary-600, 0.25)};\n --primary-inverse: #{$white};\n\n // Secondary colors\n --secondary: #{$grey-600};\n --secondary-hover: #{$grey-500};\n --secondary-focus: #{rgba($grey-500, 0.25)};\n --secondary-inverse: #{$white};\n\n // Contrast colors\n --contrast: #{$grey-50};\n --contrast-hover: #{$white};\n --contrast-focus: #{rgba($grey-500, 0.25)};\n --contrast-inverse: #{$black};\n\n // Highlighted text ()\n --mark-background-color: #{mix($grey-300, $amber-300)};\n --mark-color: #{mix($black, $grey-900, 37.5%)};\n\n // Inserted () & Deleted ()\n --ins-color: #{$green-700};\n --del-color: #{$red-800};\n\n // Blockquote\n --blockquote-border-color: var(--muted-border-color);\n --blockquote-footer-color: var(--muted-color);\n\n // Button\n // To disable box-shadow, remove the var or set to '0 0 0 rgba(0, 0, 0, 0)'\n // Don't use, 'none, 'false, 'null', '0', etc.\n --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);\n\n // Form elements\n --form-element-background-color: #{mix($black, $grey-900, 37.5%)};\n --form-element-border-color: #{mix($grey-800, $grey-700)};\n --form-element-color: var(--color);\n --form-element-placeholder-color: var(--muted-color);\n --form-element-active-background-color: var(--form-element-background-color);\n --form-element-active-border-color: var(--primary);\n --form-element-focus-color: var(--primary-focus);\n --form-element-disabled-background-color: #{$grey-800};\n --form-element-disabled-border-color: #{$grey-700};\n --form-element-disabled-opacity: 0.5;\n --form-element-invalid-border-color: #{$red-900};\n --form-element-invalid-active-border-color: #{$red-800};\n --form-element-invalid-focus-color: #{rgba($red-800, 0.25)};\n --form-element-valid-border-color: #{$green-800};\n --form-element-valid-active-border-color: #{$green-700};\n --form-element-valid-focus-color: #{rgba($green-700, 0.25)};\n\n // Switch (input[type=\"checkbox\"][role=\"switch\"])\n --switch-background-color: #{mix($grey-800, $grey-700)};\n --switch-color: var(--primary-inverse);\n --switch-checked-background-color: var(--primary);\n\n // Range (input[type=\"range\"])\n --range-border-color: #{mix($grey-900, $grey-800)};\n --range-active-border-color: #{$grey-800};\n --range-thumb-border-color: var(--background-color);\n --range-thumb-color: var(--secondary);\n --range-thumb-hover-color: var(--secondary-hover);\n --range-thumb-active-color: var(--primary);\n\n // Table\n --table-border-color: var(--muted-border-color);\n --table-row-stripped-background-color: #{rgba($grey-500, 0.05)};\n\n // Code\n --code-background-color: #{mix($black, $grey-900, 12.5%)};\n --code-color: var(--muted-color);\n --code-kbd-background-color: var(--contrast);\n --code-kbd-color: var(--contrast-inverse);\n --code-tag-color: #{hsl(330, 30%, 50%)};\n --code-property-color: #{hsl(185, 30%, 50%)};\n --code-value-color: #{hsl(40, 10%, 50%)};\n --code-comment-color: #{mix($grey-700, $grey-600)};\n\n // Accordion (
)\n --accordion-border-color: var(--muted-border-color);\n --accordion-active-summary-color: var(--primary);\n --accordion-close-summary-color: var(--color);\n --accordion-open-summary-color: var(--muted-color);\n\n // Card (
)\n $box-shadow-elevation: 1rem;\n $box-shadow-blur-strengh: 6rem;\n $box-shadow-opacity: 0.06;\n --card-background-color: #{mix($black, $grey-900, 25%)};\n --card-border-color: var(--card-background-color);\n --card-box-shadow:\n #{($box-shadow-elevation * 0.5 * 0.029)} #{($box-shadow-elevation * 0.029)} #{($box-shadow-blur-strengh * 0.029)} #{rgba($black, ($box-shadow-opacity * 0.283))},\n #{($box-shadow-elevation * 0.5 * 0.067)} #{($box-shadow-elevation * 0.067)} #{($box-shadow-blur-strengh * 0.067)} #{rgba($black, ($box-shadow-opacity * 0.4))},\n #{($box-shadow-elevation * 0.5 * 0.125)} #{($box-shadow-elevation * 0.125)} #{($box-shadow-blur-strengh * 0.125)} #{rgba($black, ($box-shadow-opacity * 0.5))},\n #{($box-shadow-elevation * 0.5 * 0.225)} #{($box-shadow-elevation * 0.225)} #{($box-shadow-blur-strengh * 0.225)} #{rgba($black, ($box-shadow-opacity * 0.6))},\n #{($box-shadow-elevation * 0.5 * 0.417)} #{($box-shadow-elevation * 0.417)} #{($box-shadow-blur-strengh * 0.417)} #{rgba($black, ($box-shadow-opacity * 0.717))},\n #{($box-shadow-elevation * 0.5)} #{$box-shadow-elevation} #{$box-shadow-blur-strengh} #{rgba($black, $box-shadow-opacity)},\n 0 0 0 0.0625rem #{rgba($black, ($box-shadow-opacity * 0.25) )};\n --card-sectionning-background-color: #{mix($black, $grey-900, 12.5%)};\n\n // Dropdown (
)\n --dropdown-background-color: #{$grey-900};\n --dropdown-border-color: #{mix($grey-900, $grey-800)};\n --dropdown-box-shadow: var(--card-box-shadow);\n --dropdown-color: var(--color);\n --dropdown-hover-background-color: #{rgba(mix($grey-900, $grey-800), 0.75)};\n\n // Modal ()\n --modal-overlay-background-color: #{rgba(mix($grey-900, $grey-800), 0.8)};\n\n // Progress\n --progress-background-color: #{mix($grey-900, $grey-800)};\n --progress-color: var(--primary);\n\n // Loading ([aria-busy=true])\n --loading-spinner-opacity: 0.5;\n\n // Tooltip ([data-tooltip])\n --tooltip-background-color: var(--contrast);\n --tooltip-color: var(--contrast-inverse);\n\n // Icons\n --icon-checkbox: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-chevron-button-inverse: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($black)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-close: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-500)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E\");\n --icon-date: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E\");\n --icon-invalid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($red-900)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\");\n --icon-minus: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E\");\n --icon-search: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E\");\n --icon-time: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E\");\n --icon-valid: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($green-800)}' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n \n // Document\n color-scheme: dark;\n}\n","/**\n * Document\n * Content-box & Responsive typography\n */\n\n// Reboot based on :\n// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css\n// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css\n// ––––––––––––––––––––\n\n// 1. Add border box sizing in all browsers (opinionated)\n// 2. Backgrounds do not repeat by default (opinionated)\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n background-repeat: no-repeat; // 2\n}\n\n// 1. Add text decoration inheritance in all browsers (opinionated)\n// 2. Add vertical alignment inheritance in all browsers (opinionated)\n::before,\n::after {\n text-decoration: inherit; // 1\n vertical-align: inherit; // 2\n}\n\n// 1. Use the default cursor in all browsers (opinionated)\n// 2. Change the line height in all browsers (opinionated)\n// 3. Breaks words to prevent overflow in all browsers (opinionated)\n// 4. Use a 4-space tab width in all browsers (opinionated)\n// 5. Remove the grey highlight on links in iOS (opinionated)\n// 6. Prevent adjustments of font size after orientation changes in iOS\n:where(:root) {\n -webkit-tap-highlight-color: transparent; // 5\n -webkit-text-size-adjust: 100%; // 6\n text-size-adjust: 100%; // 6\n background-color: var(--background-color);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: var(--font-size);\n line-height: var(--line-height); // 2\n font-family: var(--font-family);\n text-rendering: optimizeLegibility;\n overflow-wrap: break-word; // 3\n cursor: default; // 1\n tab-size: 4; // 4\n}\n","/**\n * Sectioning\n * Container and responsive spacings for header, main, footer\n */\n\n// Reboot based on :\n// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css\n// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css\n// ––––––––––––––––––––\n\n// Render the `main` element consistently in IE\nmain {\n display: block;\n}\n\n// Pico\n// ––––––––––––––––––––\n\n// 1. Remove the margin in all browsers (opinionated)\n#{$semantic-root-element} {\n width: 100%;\n margin: 0; // 1\n\n > header,\n > main,\n > footer {\n width: 100%;\n margin-right: auto;\n margin-left: auto;\n\n // Semantic container\n @if $enable-semantic-container {\n padding: var(--block-spacing-vertical) var(--block-spacing-horizontal);\n\n // Centered viewport\n @if $enable-viewport {\n @if map-get($breakpoints, \"sm\") and $enable-viewport {\n @media (min-width: map-get($breakpoints, \"sm\")) {\n max-width: map-get($viewports, \"sm\");\n padding-right: 0;\n padding-left: 0;\n }\n }\n\n @if map-get($breakpoints, \"md\") and $enable-viewport {\n @media (min-width: map-get($breakpoints, \"md\")) {\n max-width: map-get($viewports, \"md\");\n }\n }\n\n @if map-get($breakpoints, \"lg\") and $enable-viewport {\n @media (min-width: map-get($breakpoints, \"lg\")) {\n max-width: map-get($viewports, \"lg\");\n }\n }\n\n @if map-get($breakpoints, \"xl\") and $enable-viewport {\n @media (min-width: map-get($breakpoints, \"xl\")) {\n max-width: map-get($viewports, \"xl\");\n }\n }\n }\n }\n\n // Semantic container\n @else {\n padding: var(--block-spacing-vertical) 0;\n }\n }\n}\n","@if ($enable-class-container and $enable-classes) {\n /**\n * Container\n */\n\n .container,\n .container-fluid {\n width: 100%;\n margin-right: auto;\n margin-left: auto;\n padding-right: var(--spacing);\n padding-left: var(--spacing);\n }\n\n .container {\n @if map-get($breakpoints, \"sm\") {\n @media (min-width: map-get($breakpoints, \"sm\")) {\n max-width: map-get($viewports, \"sm\");\n padding-right: 0;\n padding-left: 0;\n }\n }\n\n @if map-get($breakpoints, \"md\") {\n @media (min-width: map-get($breakpoints, \"md\")) {\n max-width: map-get($viewports, \"md\");\n }\n }\n\n @if map-get($breakpoints, \"lg\") {\n @media (min-width: map-get($breakpoints, \"lg\")) {\n max-width: map-get($viewports, \"lg\");\n }\n }\n\n @if map-get($breakpoints, \"xl\") {\n @media (min-width: map-get($breakpoints, \"xl\")) {\n max-width: map-get($viewports, \"xl\");\n }\n }\n }\n}\n","/**\n * Section\n * Responsive spacings for section\n */\n\nsection {\n margin-bottom: var(--block-spacing-vertical);\n}\n","@if ($enable-classes and $enable-grid) {\n /**\n * Grid\n * Minimal grid system with auto-layout columns\n */\n\n .grid {\n grid-column-gap: var(--grid-spacing-horizontal);\n grid-row-gap: var(--grid-spacing-vertical);\n display: grid;\n grid-template-columns: 1fr;\n margin: 0;\n\n @if map-get($breakpoints, \"lg\") {\n @media (min-width: map-get($breakpoints, \"lg\")) {\n grid-template-columns: repeat(auto-fit, minmax(0%, 1fr));\n }\n }\n\n & > * {\n min-width: 0; // HACK for childs in overflow\n }\n }\n}\n","/**\n * Horizontal scroller (
)\n */\n\n// Wrapper to make any content responsive across all viewports\nfigure {\n display: block;\n margin: 0;\n padding: 0;\n overflow-x: auto;\n\n figcaption {\n padding: calc(var(--spacing) * 0.5) 0;\n color: var(--muted-color);\n }\n}\n","/**\n * Typography\n */\n\n// Reboot based on :\n// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css\n// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css\n// ––––––––––––––––––––\n\n// Add the correct font weight in Chrome, Edge, and Safari\nb,\nstrong {\n font-weight: bolder;\n}\n\n// Prevent `sub` and `sup` elements from affecting the line height in all browsers\nsub,\nsup {\n position: relative;\n font-size: 0.75em;\n line-height: 0;\n vertical-align: baseline;\n}\nsub {\n bottom: -0.25em;\n}\nsup {\n top: -0.5em;\n}\n\n// Pico\n// ––––––––––––––––––––\n\naddress,\nblockquote,\ndl,\nfigure,\nform,\nol,\np,\npre,\ntable,\nul {\n margin-top: 0;\n margin-bottom: var(--typography-spacing-vertical);\n color: var(--color);\n font-style: normal;\n font-weight: var(--font-weight);\n font-size: var(--font-size);\n}\n\n// Links\n// 1. Remove the gray background on active links in IE 10\na,\n[role=\"link\"] {\n --color: var(--primary);\n --background-color: transparent;\n outline: none;\n background-color: var(--background-color); // 1\n color: var(--color);\n text-decoration: var(--text-decoration);\n\n @if $enable-transitions {\n transition: background-color var(--transition), color var(--transition),\n text-decoration var(--transition), box-shadow var(--transition);\n }\n\n &:is([aria-current], :hover, :active, :focus) {\n --color: var(--primary-hover);\n --text-decoration: underline;\n }\n\n &:focus {\n --background-color: var(--primary-focus);\n }\n\n @if $enable-classes {\n // Secondary\n &.secondary {\n --color: var(--secondary);\n\n &:is([aria-current], :hover, :active, :focus) {\n --color: var(--secondary-hover);\n }\n\n &:focus {\n --background-color: var(--secondary-focus);\n }\n }\n\n // Contrast\n &.contrast {\n --color: var(--contrast);\n\n &:is([aria-current], :hover, :active, :focus) {\n --color: var(--contrast-hover);\n }\n\n &:focus {\n --background-color: var(--contrast-focus);\n }\n }\n }\n}\n\n// Headings\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n margin-top: 0;\n margin-bottom: var(--typography-spacing-vertical);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: var(--font-size);\n font-family: var(--font-family);\n}\n\nh1 {\n --color: var(--h1-color);\n}\nh2 {\n --color: var(--h2-color);\n}\nh3 {\n --color: var(--h3-color);\n}\nh4 {\n --color: var(--h4-color);\n}\nh5 {\n --color: var(--h5-color);\n}\nh6 {\n --color: var(--h6-color);\n}\n\n// Margin-top for headings after a typography block\n:where(address, blockquote, dl, figure, form, ol, p, pre, table, ul) {\n ~ :is(h1, h2, h3, h4, h5, h6) {\n margin-top: var(--typography-spacing-vertical);\n }\n}\n\n// Heading group\n@if $enable-classes == false {\n hgroup {\n margin-bottom: var(--typography-spacing-vertical);\n\n > * {\n margin-bottom: 0;\n }\n\n > *:last-child {\n --color: var(--muted-color);\n --font-weight: unset;\n font-size: 1rem;\n font-family: unset;\n }\n }\n}\n\n@if $enable-classes {\n hgroup,\n .headings {\n margin-bottom: var(--typography-spacing-vertical);\n\n > * {\n margin-bottom: 0;\n }\n\n > *:last-child {\n --color: var(--muted-color);\n --font-weight: unset;\n font-size: 1rem;\n font-family: unset;\n }\n }\n}\n\n// Paragraphs\np {\n margin-bottom: var(--typography-spacing-vertical);\n}\n\n// Small\nsmall {\n font-size: var(--font-size);\n}\n\n// Lists\n:where(dl, ol, ul) {\n padding-right: 0;\n padding-left: var(--spacing);\n padding-inline-start: var(--spacing);\n padding-inline-end: 0;\n\n li {\n margin-bottom: calc(var(--typography-spacing-vertical) * 0.25);\n }\n}\n\n// Margin-top for nested lists\n// 1. Remove the margin on nested lists in Chrome, Edge, IE, and Safari\n:where(dl, ol, ul) {\n :is(dl, ol, ul) {\n margin: 0; // 1\n margin-top: calc(var(--typography-spacing-vertical) * 0.25);\n }\n}\n\nul li {\n list-style: square;\n}\n\n// Highlighted text\nmark {\n padding: 0.125rem 0.25rem;\n background-color: var(--mark-background-color);\n color: var(--mark-color);\n vertical-align: baseline;\n}\n\n// Blockquote\nblockquote {\n display: block;\n margin: var(--typography-spacing-vertical) 0;\n padding: var(--spacing);\n border-right: none;\n border-left: 0.25rem solid var(--blockquote-border-color);\n border-inline-start: 0.25rem solid var(--blockquote-border-color);\n border-inline-end: none;\n\n footer {\n margin-top: calc(var(--typography-spacing-vertical) * 0.5);\n color: var(--blockquote-footer-color);\n }\n}\n\n// Abbreviations\n// 1. Remove underline decoration in Chrome, Edge, IE, Opera, and Safari\nabbr[title] {\n border-bottom: 1px dotted;\n text-decoration: none; // 1\n cursor: help;\n}\n\n// Ins\nins {\n color: var(--ins-color);\n text-decoration: none;\n}\n\n// del\ndel {\n color: var(--del-color);\n}\n\n// selection\n::selection {\n background-color: var(--primary-focus);\n}\n","/**\n * Embedded content\n */\n\n// Reboot based on :\n// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css\n// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css\n// ––––––––––––––––––––\n\n// Change the alignment on media elements in all browsers (opinionated)\n:where(audio, canvas, iframe, img, svg, video) {\n vertical-align: middle;\n}\n\n// Add the correct display in IE 9-\naudio,\nvideo {\n display: inline-block;\n}\n\n// Add the correct display in iOS 4-7\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n// Remove the border on iframes in all browsers (opinionated)\n:where(iframe) {\n border-style: none;\n}\n\n// 1. Remove the border on images inside links in IE 10.\n// 2. Responsive by default\nimg {\n max-width: 100%; // 2\n height: auto; // 2\n border-style: none; // 1\n}\n\n// Change the fill color to match the text color in all browsers (opinionated)\n:where(svg:not([fill])) {\n fill: currentColor;\n}\n\n// Hide the overflow in IE\nsvg:not(:root) {\n overflow: hidden;\n}\n","/**\n * Button\n */\n\n// Reboot based on :\n// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css\n// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css\n// ––––––––––––––––––––\n\n// 1. Change the font styles in all browsers\n// 2. Remove the margin on controls in Safari\n// 3. Show the overflow in Edge\nbutton {\n margin: 0; // 2\n overflow: visible; // 3\n font-family: inherit; // 1\n text-transform: none; // 1\n}\n\n// Correct the inability to style buttons in iOS and Safari\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n// Pico\n// ––––––––––––––––––––\n\nbutton {\n display: block;\n width: 100%;\n margin-bottom: var(--spacing);\n}\n\n[role=\"button\"] {\n display: inline-block;\n text-decoration: none;\n}\n\nbutton,\ninput[type=\"submit\"],\ninput[type=\"button\"],\ninput[type=\"reset\"],\n[role=\"button\"] {\n --background-color: var(--primary);\n --border-color: var(--primary);\n --color: var(--primary-inverse);\n --box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));\n padding: var(--form-element-spacing-vertical)\n var(--form-element-spacing-horizontal);\n border: var(--border-width) solid var(--border-color);\n border-radius: var(--border-radius);\n outline: none;\n background-color: var(--background-color);\n box-shadow: var(--box-shadow);\n color: var(--color);\n font-weight: var(--font-weight);\n font-size: 1rem;\n line-height: var(--line-height);\n text-align: center;\n cursor: pointer;\n\n @if $enable-transitions {\n transition: background-color var(--transition),\n border-color var(--transition), color var(--transition),\n box-shadow var(--transition);\n }\n\n &:is([aria-current], :hover, :active, :focus) {\n --background-color: var(--primary-hover);\n --border-color: var(--primary-hover);\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));\n --color: var(--primary-inverse);\n }\n\n &:focus {\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),\n 0 0 0 var(--outline-width) var(--primary-focus);\n }\n}\n\n// .secondary, .contrast & .outline\n@if $enable-classes {\n\n // Secondary\n :is(button, input[type=\"submit\"], input[type=\"button\"], [role=\"button\"]).secondary,\n input[type=\"reset\"] {\n --background-color: var(--secondary);\n --border-color: var(--secondary);\n --color: var(--secondary-inverse);\n cursor: pointer;\n\n &:is([aria-current], :hover, :active, :focus) {\n --background-color: var(--secondary-hover);\n --border-color: var(--secondary-hover);\n --color: var(--secondary-inverse);\n }\n\n &:focus {\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),\n 0 0 0 var(--outline-width) var(--secondary-focus);\n }\n }\n\n // Contrast\n :is(button, input[type=\"submit\"], input[type=\"button\"], [role=\"button\"]).contrast {\n --background-color: var(--contrast);\n --border-color: var(--contrast);\n --color: var(--contrast-inverse);\n\n &:is([aria-current], :hover, :active, :focus) {\n --background-color: var(--contrast-hover);\n --border-color: var(--contrast-hover);\n --color: var(--contrast-inverse);\n }\n\n &:focus {\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),\n 0 0 0 var(--outline-width) var(--contrast-focus);\n }\n }\n\n // Outline (primary)\n :is(button, input[type=\"submit\"], input[type=\"button\"], [role=\"button\"]).outline,\n input[type=\"reset\"].outline {\n --background-color: transparent;\n --color: var(--primary);\n\n &:is([aria-current], :hover, :active, :focus) {\n --background-color: transparent;\n --color: var(--primary-hover);\n }\n }\n\n // Outline (secondary)\n :is(button, input[type=\"submit\"], input[type=\"button\"], [role=\"button\"]).outline.secondary,\n input[type=\"reset\"].outline {\n --color: var(--secondary);\n\n &:is([aria-current], :hover, :active, :focus) {\n --color: var(--secondary-hover);\n }\n }\n\n // Outline (contrast)\n :is(button, input[type=\"submit\"], input[type=\"button\"], [role=\"button\"]).outline.contrast {\n --color: var(--contrast);\n\n &:is([aria-current], :hover, :active, :focus) {\n --color: var(--contrast-hover);\n }\n }\n} \n@else {\n // Secondary button without .class\n input[type=\"reset\"] {\n --background-color: var(--secondary);\n --border-color: var(--secondary);\n --color: var(--secondary-inverse);\n cursor: pointer;\n\n &:is([aria-current], :hover, :active, :focus) {\n --background-color: var(--secondary-hover);\n --border-color: var(--secondary-hover);\n }\n\n &:focus {\n --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),\n 0 0 0 var(--outline-width) var(--secondary-focus);\n }\n }\n}\n\n// Button [disabled]\n// Links without href are disabled by default\n:where(button, [type=\"submit\"], [type=\"button\"], [type=\"reset\"], [role=\"button\"])[disabled],\n:where(fieldset[disabled]) :is(button, [type=\"submit\"], [type=\"button\"], [type=\"reset\"], [role=\"button\"]),\na[role=\"button\"]:not([href]) {\n opacity: 0.5;\n pointer-events: none;\n}\n","/**\n * Form elements\n */\n\n// Reboot based on :\n// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css\n// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css\n// ––––––––––––––––––––\n\n// 1. Change the font styles in all browsers\n// 2. Remove the margin in Firefox and Safari\ninput,\noptgroup,\nselect,\ntextarea {\n margin: 0; // 2\n font-size: 1rem; // 1\n line-height: var(--line-height); // 1\n font-family: inherit; // 1\n letter-spacing: inherit; // 2\n}\n\n// Show the overflow in IE.\ninput {\n overflow: visible;\n}\n\n// Remove the inheritance of text transform in Edge, Firefox, and IE\nselect {\n text-transform: none;\n}\n\n// 1. Correct the text wrapping in Edge and IE\n// 2. Correct the color inheritance from `fieldset` elements in IE\n// 3. Remove the padding so developers are not caught out when they zero out\n// `fieldset` elements in all browsers\nlegend {\n max-width: 100%; // 1\n padding: 0; // 3\n color: inherit; // 2\n white-space: normal; // 1\n}\n\n// 1. Remove the default vertical scrollbar in IE\ntextarea {\n overflow: auto; // 1\n}\n\n// Remove the padding in IE 10\n[type=\"checkbox\"],\n[type=\"radio\"] {\n padding: 0;\n}\n\n// Correct the cursor style of increment and decrement buttons in Safari\n::-webkit-inner-spin-button,\n::-webkit-outer-spin-button {\n height: auto;\n}\n\n// 1. Correct the odd appearance in Chrome and Safari\n// 2. Correct the outline style in Safari\n[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n outline-offset: -2px; // 2\n}\n\n// Remove the inner padding in Chrome and Safari on macOS\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n// 1. Correct the inability to style clickable types in iOS and Safari\n// 2. Change font properties to `inherit` in Safari\n::-webkit-file-upload-button {\n -webkit-appearance: button; // 1\n font: inherit; // 2\n}\n\n// Remove the inner border and padding of focus outlines in Firefox\n::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\n// Remove the focus outline in Firefox\n:-moz-focusring {\n outline: none;\n}\n\n// Remove the additional :invalid styles in Firefox\n:-moz-ui-invalid {\n box-shadow: none;\n}\n\n// Change the inconsistent appearance in IE (opinionated)\n::-ms-expand {\n display: none;\n}\n\n// Remove the border and padding in all browsers (opinionated)\n[type=\"file\"],\n[type=\"range\"] {\n padding: 0;\n border-width: 0;\n}\n\n// Pico\n// ––––––––––––––––––––\n\n// Force height for alternatives input types\ninput:not([type=\"checkbox\"], [type=\"radio\"], [type=\"range\"]) {\n height: calc(\n (1rem * var(--line-height)) + (var(--form-element-spacing-vertical) * 2) +\n (var(--border-width) * 2)\n );\n}\n\n// Fieldset\nfieldset {\n margin: 0;\n margin-bottom: var(--spacing);\n padding: 0;\n border: 0;\n}\n\n// Label & legend\nlabel,\nfieldset legend {\n display: block;\n margin-bottom: calc(var(--spacing) * 0.25);\n font-weight: var(--form-label-font-weight, var(--font-weight));\n}\n\n// Blocks, 100%\ninput:not([type=\"checkbox\"], [type=\"radio\"]),\nselect,\ntextarea {\n width: 100%;\n}\n\n// Reset appearance (Not Checkboxes, Radios, Range and File)\ninput:not([type=\"checkbox\"], [type=\"radio\"], [type=\"range\"], [type=\"file\"]),\nselect,\ntextarea {\n appearance: none;\n padding: var(--form-element-spacing-vertical)\n var(--form-element-spacing-horizontal);\n}\n\n// Commons styles\ninput,\nselect,\ntextarea {\n --background-color: var(--form-element-background-color);\n --border-color: var(--form-element-border-color);\n --color: var(--form-element-color);\n --box-shadow: none;\n border: var(--border-width) solid var(--border-color);\n border-radius: var(--border-radius);\n outline: none;\n background-color: var(--background-color);\n box-shadow: var(--box-shadow);\n color: var(--color);\n font-weight: var(--font-weight);\n\n @if $enable-transitions {\n transition: background-color var(--transition),\n border-color var(--transition), color var(--transition),\n box-shadow var(--transition);\n }\n}\n\n// Active & Focus\ninput:not([type=\"submit\"], [type=\"button\"], [type=\"reset\"], [type=\"checkbox\"], [type=\"radio\"], [readonly]),\n:where(select, textarea) {\n &:is(:active, :focus) {\n --background-color: var(--form-element-active-background-color);\n }\n}\n\n// Active & Focus\ninput:not([type=\"submit\"], [type=\"button\"], [type=\"reset\"], [role=\"switch\"], [readonly]),\n:where(select, textarea) {\n &:is(:active, :focus) {\n --border-color: var(--form-element-active-border-color);\n }\n}\n\n// Focus\ninput:not([type=\"submit\"], [type=\"button\"], [type=\"reset\"], [type=\"range\"], [type=\"file\"], [readonly]),\nselect,\ntextarea {\n &:focus {\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color);\n }\n}\n\n// Disabled\ninput:not([type=\"submit\"], [type=\"button\"], [type=\"reset\"])[disabled],\nselect[disabled],\ntextarea[disabled],\n:where(fieldset[disabled]) :is(input:not([type=\"submit\"], [type=\"button\"], [type=\"reset\"]), select, textarea) {\n --background-color: var(--form-element-disabled-background-color);\n --border-color: var(--form-element-disabled-border-color);\n opacity: var(--form-element-disabled-opacity);\n pointer-events: none;\n}\n\n// Aria-invalid\n:where(input, select, textarea) {\n &:not([type=\"checkbox\"], [type=\"radio\"], [type=\"date\"], [type=\"datetime-local\"], [type=\"month\"], [type=\"time\"], [type=\"week\"]) {\n &[aria-invalid] {\n @if $enable-important {\n padding-right: calc(\n var(--form-element-spacing-horizontal) + 1.5rem\n ) !important;\n padding-left: var(--form-element-spacing-horizontal);\n padding-inline-start: var(--form-element-spacing-horizontal) !important;\n padding-inline-end: calc(\n var(--form-element-spacing-horizontal) + 1.5rem\n ) !important;\n }\n @else {\n padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem);\n padding-left: var(--form-element-spacing-horizontal);\n padding-inline-start: var(--form-element-spacing-horizontal);\n padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem);\n }\n background-position: center right 0.75rem;\n background-size: 1rem auto;\n background-repeat: no-repeat;\n }\n\n &[aria-invalid=\"false\"] {\n background-image: var(--icon-valid);\n }\n\n &[aria-invalid=\"true\"] {\n background-image: var(--icon-invalid);\n }\n }\n\n &[aria-invalid=\"false\"] {\n --border-color: var(--form-element-valid-border-color);\n\n &:is(:active, :focus) {\n @if $enable-important {\n --border-color: var(--form-element-valid-active-border-color) !important;\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important;\n }\n @else {\n --border-color: var(--form-element-valid-active-border-color);\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color);\n }\n }\n }\n\n &[aria-invalid=\"true\"] {\n --border-color: var(--form-element-invalid-border-color);\n\n &:is(:active, :focus) {\n @if $enable-important {\n --border-color: var(--form-element-invalid-active-border-color) !important;\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important;\n }\n @else {\n --border-color: var(--form-element-invalid-active-border-color);\n --box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color);\n }\n }\n }\n}\n\n[dir=\"rtl\"] {\n :where(input, select, textarea) {\n &:not([type=\"checkbox\"], [type=\"radio\"]) {\n &:is([aria-invalid], [aria-invalid=\"true\"], [aria-invalid=\"false\"] ){\n background-position: center left 0.75rem;\n }\n }\n }\n}\n\n// Placeholder\ninput::placeholder,\ninput::-webkit-input-placeholder,\ntextarea::placeholder,\ntextarea::-webkit-input-placeholder,\nselect:invalid {\n color: var(--form-element-placeholder-color);\n opacity: 1;\n}\n\n// Margin bottom (Not Checkboxes and Radios)\ninput:not([type=\"checkbox\"], [type=\"radio\"]),\nselect,\ntextarea {\n margin-bottom: var(--spacing);\n}\n\n// Select\nselect {\n // Unstyle the caret on `\n summary {\n margin-bottom: 0;\n\n &:not([role]) {\n height: calc(\n 1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 +\n var(--border-width) * 2\n );\n padding: var(--form-element-spacing-vertical)\n var(--form-element-spacing-horizontal);\n border: var(--border-width) solid var(--form-element-border-color);\n border-radius: var(--border-radius);\n background-color: var(--form-element-background-color);\n color: var(--form-element-placeholder-color);\n line-height: inherit;\n cursor: pointer;\n\n @if $enable-transitions {\n transition: background-color var(--transition),\n border-color var(--transition), color var(--transition),\n box-shadow var(--transition);\n }\n\n &:active,\n &:focus {\n border-color: var(--form-element-active-border-color);\n background-color: var(--form-element-active-background-color);\n }\n\n &:focus {\n box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color);\n }\n }\n }\n\n // Close for details[role=\"list\"]\n &[open] summary {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &::before {\n display: block;\n z-index: 1;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background: none;\n content: \"\";\n cursor: default;\n }\n }\n}\n\n// All Dropdowns inside