diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 24c35fee15..1f23cb22fd 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -18,7 +18,7 @@
-
+
@@ -28,6 +28,7 @@
+
@@ -38,13 +39,14 @@
+
-
+
diff --git a/src/app/dev/DevToys.Api/Core/ResultInfo.cs b/src/app/dev/DevToys.Api/Core/ResultInfo.cs
index a6e02d45e6..88ca765d56 100644
--- a/src/app/dev/DevToys.Api/Core/ResultInfo.cs
+++ b/src/app/dev/DevToys.Api/Core/ResultInfo.cs
@@ -1,18 +1,107 @@
-namespace DevToys.Api;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace DevToys.Api;
///
/// Record to contain both whether the task was a success and the resulting data
///
/// Type of the result
-/// The resulting data or the task
-/// Whether the task succeeded
-public record ResultInfo(T Data, bool HasSucceeded = true);
+public record ResultInfo
+{
+ ///
+ /// The resulting data or the task
+ ///
+ public T? Data { get; }
+
+ ///
+ /// Whether the task succeeded
+ ///
+ public bool HasSucceeded { get; }
+
+ ///
+ /// Error message to display
+ ///
+ public string? ErrorMessage { get; }
+
+ ///
+ /// Record to contain both whether the task was a success and the resulting data
+ ///
+ /// The resulting data or the task
+ /// Whether the task succeeded
+ public ResultInfo(T data, bool hasSucceeded = true)
+ {
+ Data = data;
+ HasSucceeded = hasSucceeded;
+ }
+
+ ///
+ /// Record to contain both whether the task was a success and the resulting data
+ ///
+ /// The resulting data or the task
+ /// The error message
+ /// Whether the task succeeded
+ public ResultInfo(T data, string errorMessage, bool hasSucceeded = false)
+ {
+ Data = data;
+ HasSucceeded = hasSucceeded;
+ ErrorMessage = errorMessage;
+ }
+}
///
/// Record to contain both whether the task was a success and the resulting data
///
/// Type of the result
-/// Type of the severity
-/// The resulting data or the task
-/// The severity of the result
-public record ResultInfo(T Data, U Severity);
+/// The severity of the result
+public record ResultInfo
+{
+ ///
+ /// The resulting data or the task
+ ///
+ public T? Data { get; }
+
+ ///
+ /// Severity of the result
+ ///
+ public ResultInfoSeverity Severity { get; }
+
+ ///
+ /// Error message to display
+ ///
+ public string? ErrorMessage { get; }
+
+ ///
+ /// Record to contain both whether the task was a success and the resulting data
+ ///
+ /// The resulting data or the task
+ /// The severity of the result
+ public ResultInfo(T data, ResultInfoSeverity severity)
+ {
+ Data = data;
+ Severity = severity;
+ }
+
+ ///
+ /// Record to contain both whether the task was a success and the resulting data
+ ///
+ /// The error message
+ /// The severity of the result
+ public ResultInfo(string errorMessage, ResultInfoSeverity severity)
+ {
+ Severity = severity;
+ ErrorMessage = errorMessage;
+ }
+
+ ///
+ /// Record to contain both whether the task was a success and the resulting data
+ ///
+ /// The resulting data or the task
+ /// The error message
+ /// The severity of the result
+ public ResultInfo(T data, string errorMessage, ResultInfoSeverity severity)
+ {
+ Data = data;
+ Severity = severity;
+ ErrorMessage = errorMessage;
+ }
+}
diff --git a/src/app/dev/DevToys.Api/Core/ResultInfoSeverity.cs b/src/app/dev/DevToys.Api/Core/ResultInfoSeverity.cs
new file mode 100644
index 0000000000..2da8e00c9c
--- /dev/null
+++ b/src/app/dev/DevToys.Api/Core/ResultInfoSeverity.cs
@@ -0,0 +1,8 @@
+namespace DevToys.Api;
+
+public enum ResultInfoSeverity
+{
+ Success,
+ Warning,
+ Error
+}
diff --git a/src/app/dev/DevToys.Api/Tool/GUI/Components/IUIMultiLineTextInput.cs b/src/app/dev/DevToys.Api/Tool/GUI/Components/IUIMultiLineTextInput.cs
index b0e7a866a3..af901b647d 100644
--- a/src/app/dev/DevToys.Api/Tool/GUI/Components/IUIMultiLineTextInput.cs
+++ b/src/app/dev/DevToys.Api/Tool/GUI/Components/IUIMultiLineTextInput.cs
@@ -10,6 +10,11 @@ public interface IUIMultiLineTextInput : IUISingleLineTextInput
///
IReadOnlyList HighlightedSpans { get; }
+ ///
+ /// Gets the list of tooltip to display on word hover.
+ ///
+ IReadOnlyList HoverTooltips { get; }
+
///
/// Gets the programming language name to use when colorizing the text in the control.
///
@@ -33,6 +38,11 @@ public interface IUIMultiLineTextInput : IUISingleLineTextInput
///
TextSpan Selection { get; }
+ ///
+ /// Raised when is changed.
+ ///
+ event EventHandler? HoverTooltipChanged;
+
///
/// Raised when is changed.
///
@@ -62,6 +72,7 @@ public interface IUIMultiLineTextInput : IUISingleLineTextInput
[DebuggerDisplay($"Id = {{{nameof(Id)}}}, Text = {{{nameof(Text)}}}, SyntaxColorizationLanguageName = {{{nameof(SyntaxColorizationLanguageName)}}}")]
internal class UIMultilineTextInput : UISingleLineTextInput, IUIMultiLineTextInput
{
+ private IReadOnlyList? _hoverTooltip;
private IReadOnlyList? _highlightedSpans;
private string? _syntaxColorizationLanguageName;
private bool _isExtendableToFullScreen;
@@ -79,6 +90,12 @@ public IReadOnlyList HighlightedSpans
internal set => SetPropertyValue(ref _highlightedSpans, value, HighlightedSpansChanged);
}
+ public IReadOnlyList HoverTooltips
+ {
+ get => _hoverTooltip ?? Array.Empty();
+ internal set => SetPropertyValue(ref _hoverTooltip, value, HoverTooltipChanged);
+ }
+
public string SyntaxColorizationLanguageName
{
get => _syntaxColorizationLanguageName ?? string.Empty;
@@ -122,6 +139,7 @@ internal set
}
}
+ public event EventHandler? HoverTooltipChanged;
public event EventHandler? HighlightedSpansChanged;
public event EventHandler? SyntaxColorizationLanguageNameChanged;
public event EventHandler? IsExtendableToFullScreenChanged;
@@ -176,6 +194,15 @@ public static IUIMultiLineTextInput Highlight(this IUIMultiLineTextInput element
return element;
}
+ ///
+ /// Sets the list of tooltips to display on Word hover in the text document.
+ ///
+ public static IUIMultiLineTextInput HoverTooltip(this IUIMultiLineTextInput element, params UIHoverTooltip[] tooltips)
+ {
+ ((UIMultilineTextInput)element).HoverTooltips = tooltips;
+ return element;
+ }
+
///
/// Sets the programming language name to use to colorize the text in the control.
///
diff --git a/src/app/dev/DevToys.Api/Tool/GUI/Components/UIHoverTooltip.cs b/src/app/dev/DevToys.Api/Tool/GUI/Components/UIHoverTooltip.cs
new file mode 100644
index 0000000000..c0030d5127
--- /dev/null
+++ b/src/app/dev/DevToys.Api/Tool/GUI/Components/UIHoverTooltip.cs
@@ -0,0 +1,26 @@
+namespace DevToys.Api;
+
+///
+/// Represents the Tooltip to display on hover
+///
+public record UIHoverTooltip
+{
+ ///
+ /// Contain the position of the span to search
+ ///
+ public TextSpan Span { get; }
+
+ ///
+ /// Contain the information we want to display on hover
+ ///
+ public string Description { get; }
+
+ ///
+ /// Create a new isntance of the class.
+ ///
+ public UIHoverTooltip(TextSpan span, string description)
+ {
+ Span = span;
+ Description = description;
+ }
+}
diff --git a/src/app/dev/DevToys.Blazor/Components/Layout/Expander/Expander.razor.scss b/src/app/dev/DevToys.Blazor/Components/Layout/Expander/Expander.razor.scss
index f520c6c6a6..0e72c81631 100644
--- a/src/app/dev/DevToys.Blazor/Components/Layout/Expander/Expander.razor.scss
+++ b/src/app/dev/DevToys.Blazor/Components/Layout/Expander/Expander.razor.scss
@@ -80,6 +80,10 @@
background-color: var(--card-background-color-secondary);
@include transition(transform 250ms cubic-bezier(1, 1, 0, 1));
transform: translateY(-100%);
+
+ .expander-card {
+ background-color: transparent;
+ }
}
&[aria-expanded='true'] {
diff --git a/src/app/dev/DevToys.Blazor/Components/Text/MonacoEditor/RicherMonacoEditorBase.cs b/src/app/dev/DevToys.Blazor/Components/Text/MonacoEditor/RicherMonacoEditorBase.cs
index 14362492cc..66b507a08d 100644
--- a/src/app/dev/DevToys.Blazor/Components/Text/MonacoEditor/RicherMonacoEditorBase.cs
+++ b/src/app/dev/DevToys.Blazor/Components/Text/MonacoEditor/RicherMonacoEditorBase.cs
@@ -1,4 +1,5 @@
-using System.Text.Json;
+using System.Reflection;
+using System.Text.Json;
using System.Text.Json.Serialization;
using DevToys.Blazor.Components.Monaco;
using DevToys.Blazor.Components.Monaco.Editor;
@@ -519,6 +520,13 @@ internal async Task GetOptionsAsync()
internal ValueTask GetRawOptionsAsync()
=> JSRuntime.InvokeAsync("devtoys.MonacoEditor.getRawOptions", Id);
+ ///
+ /// Get value of the current model attached to this editor.
+ /// See
+ ///
+ internal ValueTask GetWordAtPositionAsync(string position)
+ => JSRuntime.InvokeAsync("devtoys.MonacoEditor.getWordAtPosition", position);
+
///
/// Get value of the current model attached to this editor.
/// See
diff --git a/src/app/dev/DevToys.Blazor/Components/UIElements/UIGridPresenter.razor.cs b/src/app/dev/DevToys.Blazor/Components/UIElements/UIGridPresenter.razor.cs
index 3a415cc7f5..7260efbabb 100644
--- a/src/app/dev/DevToys.Blazor/Components/UIElements/UIGridPresenter.razor.cs
+++ b/src/app/dev/DevToys.Blazor/Components/UIElements/UIGridPresenter.razor.cs
@@ -1,7 +1,26 @@
-namespace DevToys.Blazor.Components.UIElements;
+using DevToys.Api;
+
+namespace DevToys.Blazor.Components.UIElements;
public partial class UIGridPresenter : ComponentBase
{
[Parameter]
public IUIGrid UIGrid { get; set; } = default!;
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+
+ UIGrid.CellsChanged += UIGrid_CellsChanged;
+ }
+
+ public void Dispose()
+ {
+ UIGrid.CellsChanged += UIGrid_CellsChanged;
+ }
+
+ private void UIGrid_CellsChanged(object? sender, EventArgs e)
+ {
+ StateHasChanged();
+ }
}
diff --git a/src/app/dev/DevToys.Blazor/Components/UIElements/UIMultiLineTextInputPresenter.razor.cs b/src/app/dev/DevToys.Blazor/Components/UIElements/UIMultiLineTextInputPresenter.razor.cs
index cd3f1df02e..d4b3a02f2d 100644
--- a/src/app/dev/DevToys.Blazor/Components/UIElements/UIMultiLineTextInputPresenter.razor.cs
+++ b/src/app/dev/DevToys.Blazor/Components/UIElements/UIMultiLineTextInputPresenter.razor.cs
@@ -1,5 +1,7 @@
using DevToys.Blazor.Components.Monaco;
using DevToys.Blazor.Components.Monaco.Editor;
+using Microsoft.VisualBasic;
+using Range = DevToys.Blazor.Components.Monaco.Range;
namespace DevToys.Blazor.Components.UIElements;
@@ -26,6 +28,7 @@ protected override void OnInitialized()
UIMultiLineTextInput.IsReadOnlyChanged += UIMultiLineTextInput_IsReadOnlyChanged;
UIMultiLineTextInput.SelectionChanged += UIMultiLineTextInput_SelectionChanged;
UIMultiLineTextInput.HighlightedSpansChanged += UIMultiLineTextInput_HighlightedSpansChanged;
+ UIMultiLineTextInput.HoverTooltipChanged += UIMultiLineTextInput_HoverTooltipChanged;
}
public override ValueTask DisposeAsync()
@@ -35,6 +38,7 @@ public override ValueTask DisposeAsync()
UIMultiLineTextInput.IsReadOnlyChanged -= UIMultiLineTextInput_IsReadOnlyChanged;
UIMultiLineTextInput.SelectionChanged -= UIMultiLineTextInput_SelectionChanged;
UIMultiLineTextInput.HighlightedSpansChanged -= UIMultiLineTextInput_HighlightedSpansChanged;
+ UIMultiLineTextInput.HoverTooltipChanged -= UIMultiLineTextInput_HoverTooltipChanged;
return base.DisposeAsync();
}
@@ -133,6 +137,52 @@ private async void UIMultiLineTextInput_HighlightedSpansChanged(object? sender,
}
}
+ private async void UIMultiLineTextInput_HoverTooltipChanged(object? sender, EventArgs e)
+ {
+ await _monacoInitializationAwaiter.Task;
+
+ using (await _semaphore.WaitAsync(CancellationToken.None))
+ {
+ TextModel model = await _monacoEditor.GetModelAsync();
+
+ if (model is not null)
+ {
+ var decorations = new ModelDeltaDecoration[UIMultiLineTextInput.HoverTooltips.Count];
+
+ for (int i = 0; i < decorations.Length; i++)
+ {
+ UIHoverTooltip hoverTooltip = UIMultiLineTextInput.HoverTooltips[i];
+ Position selectionStartPosition = await model.GetPositionAtAsync(JSRuntime, hoverTooltip.Span.StartPosition);
+ Position selectionEndPosition = await model.GetPositionAtAsync(JSRuntime, hoverTooltip.Span.EndPosition);
+
+ decorations[i]
+ = new ModelDeltaDecoration
+ {
+ Range = new Monaco.Range
+ {
+ StartLineNumber = selectionStartPosition.LineNumber,
+ StartColumn = selectionStartPosition.Column,
+ EndLineNumber = selectionEndPosition.LineNumber,
+ EndColumn = selectionEndPosition.Column
+ },
+ Options = new ModelDecorationOptions
+ {
+ HoverMessage = new[]
+ {
+ new MarkdownString()
+ {
+ Value = hoverTooltip.Description
+ }
+ }
+ }
+ };
+ }
+
+ await _monacoEditor.ReplaceAllDecorationsByAsync(decorations);
+ }
+ }
+ }
+
private async Task OnMonacoEditorTextChangedAsync(ModelContentChangedEvent ev)
{
if (ev.Changes is not null)
diff --git a/src/app/dev/DevToys.Blazor/Components/UIElements/UISettingGroupPresenter.razor.scss b/src/app/dev/DevToys.Blazor/Components/UIElements/UISettingGroupPresenter.razor.scss
index 96f3738f17..a0fd08383e 100644
--- a/src/app/dev/DevToys.Blazor/Components/UIElements/UISettingGroupPresenter.razor.scss
+++ b/src/app/dev/DevToys.Blazor/Components/UIElements/UISettingGroupPresenter.razor.scss
@@ -3,4 +3,14 @@
background-color: var(--ui-setting-group-presenter-inner-ui-setting-background-color);
border-color: var(--ui-setting-group-presenter-inner-ui-setting-border-color);
}
+
+ .card {
+ &.expander-card {
+ .card-control {
+ &.child-of-expander {
+ margin-right: 0;
+ }
+ }
+ }
+ }
}
diff --git a/src/app/dev/DevToys.Blazor/wwwroot/css/devtoys.g.css b/src/app/dev/DevToys.Blazor/wwwroot/css/devtoys.g.css
index fadc777222..7fbdecec3f 100644
--- a/src/app/dev/DevToys.Blazor/wwwroot/css/devtoys.g.css
+++ b/src/app/dev/DevToys.Blazor/wwwroot/css/devtoys.g.css
@@ -1 +1 @@
-.ui-card-presenter>div{width:100%}.ui-data-grid-presenter{border:2px dashed transparent;border-radius:var(--overlay-corner-radius)}.ui-data-grid-presenter-command-bar{min-height:32px}div[data-compactmode] .ui-data-grid-presenter-command-bar{min-height:26px}.ui-file-selector{border:2px dashed var(--ui-file-selector-stroke-color);border-radius:var(--overlay-corner-radius);background-color:var(--ui-file-selector-background-color);pointer-events:auto}.ui-file-selector-content{padding:24px}.ui-file-selector.dragging{border-color:var(--ui-file-selector-dragging-stroke-color);background-color:var(--ui-file-selector-dragging-background-color)}.ui-file-selector.dragging *{pointer-events:none!important}div[data-compactmode] .ui-file-selector-content{padding:8px}.ui-image-viewer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--card-foreground);border:var(--card-border-thickness);border-color:var(--card-border);border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--card-background-color);min-height:72px;padding-block:16px;padding-inline:11px}.ui-image-viewer img{object-fit:scale-down;object-position:center;display:block;position:relative;width:100%;height:100%;max-height:inherit;min-height:min-content;max-width:inherit;min-width:100%}div[data-compactmode] .ui-image-viewer{padding-block:6px;padding-inline:6px}.ui-multiline-text-input-highlighted-text-span-default{background-color:var(--text-box-selection-highlight-color);color:var(--text-box-selection-color)}.ui-multiline-text-input-highlighted-text-span-blue{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-blue);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-green{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-green);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-red{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-red);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-yellow{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-yellow);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-purple{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-purple);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-teal{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-teal);color:var(--text-box-selection-color-blue)}.ui-setting-group-presenter .ui-setting-presenter{background-color:var(--ui-setting-group-presenter-inner-ui-setting-background-color);border-color:var(--ui-setting-group-presenter-inner-ui-setting-border-color)}.ui-text-input-wrapper{border:2px dashed transparent;border-radius:var(--overlay-corner-radius)}.ui-text-input-wrapper-separator{border-left:1px solid var(--divider-stroke-color-default);width:1px;height:100%;margin-left:2px;margin-right:2px}.ui-text-input-wrapper-centered-progress-bar{position:absolute;top:50%;left:0;transform:translate(0,-50%);bottom:50%;right:0;height:fit-content}.ui-text-input-wrapper-icon{height:20px;width:20px}.ui-text-input-wrapper.dragging{border-color:var(--ui-file-selector-dragging-stroke-color);background-color:var(--ui-file-selector-dragging-background-color)}.ui-text-input-wrapper.dragging *{pointer-events:none!important}div[data-compactmode] .ui-text-input-wrapper-icon{height:16px;width:16px}.ui-web-view-title{height:20px!important;margin-top:14px}.ui-web-view-frame{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--card-foreground);border:var(--card-border-thickness);border-color:var(--card-border);border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--card-background-color);min-height:72px;width:100%;height:100%;pointer-events:all}div[data-compactmode] .ui-web-view-title{height:20px!important;margin-top:7px}button{display:inline-flex;justify-content:center;align-items:center;user-select:none;-webkit-user-select:none;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;position:relative;box-sizing:border-box;min-block-size:32px;padding-block:4px 6px;padding-inline:11px;text-decoration:none;border:none;outline:0;cursor:default;border-radius:var(--control-corner-radius);transition:background 83ms;width:inherit;height:inherit;min-width:fit-content;pointer-events:auto}button.type-neutral{border:var(--button-border-thickness);border-color:var(--button-border);background-color:var(--button-background);color:var(--button-foreground);background-clip:padding-box}button.type-neutral:hover{border-color:var(--button-border-pointer-over);background-color:var(--button-background-pointer-over);color:var(--button-foreground-pointer-over)}button.type-neutral:active{border-color:var(--button-border-pressed);background-color:var(--button-background-pressed);color:var(--button-foreground-pressed)}button.type-neutral.disabled{border-color:var(--button-border-disabled);background-color:var(--button-background-disabled);color:var(--button-foreground-disabled)}button.type-accent{border:var(--accent-button-border-thickness);border-color:var(--accent-button-border);background-color:var(--accent-button-background);color:var(--accent-button-foreground);transition:border-color 83ms}button.type-accent:hover{border-color:var(--accent-button-border-pointer-over);background-color:var(--accent-button-background-pointer-over);color:var(--accent-button-foreground-pointer-over)}button.type-accent:active{border-color:var(--accent-button-border-pressed);background-color:var(--accent-button-background-pressed);color:var(--accent-button-foreground-pressed)}button.type-accent.disabled{border-color:var(--accent-button-border-disabled);background-color:var(--accent-button-background-disabled);color:var(--accent-button-foreground-disabled)}button.type-stealth{border:var(--stealth-button-border-thickness);border-color:var(--stealth-button-border);background-color:var(--stealth-button-background);color:var(--stealth-button-foreground);background-clip:padding-box}button.type-stealth:hover{border-color:var(--stealth-button-border-pointer-over);background-color:var(--stealth-button-background-pointer-over);color:var(--stealth-button-foreground-pointer-over)}button.type-stealth:active{border-color:var(--stealth-button-border-pressed);background-color:var(--stealth-button-background-pressed);color:var(--stealth-button-foreground-pressed)}button.type-stealth.disabled{border-color:var(--stealth-button-border-disabled);background-color:var(--stealth-button-background-disabled);color:var(--stealth-button-foreground-disabled)}button.type-hyperlink{border:var(--hyperlink-button-border-thickness);border-color:var(--hyperlink-button-border);background-color:var(--hyperlink-button-background);color:var(--hyperlink-button-foreground);background-clip:padding-box}button.type-hyperlink:hover{border-color:var(--hyperlink-button-border-pointer-over);background-color:var(--hyperlink-button-background-pointer-over);color:var(--hyperlink-button-foreground-pointer-over)}button.type-hyperlink:active{border-color:var(--hyperlink-button-border-pressed);background-color:var(--hyperlink-button-background-pressed);color:var(--hyperlink-button-foreground-pressed)}button.type-hyperlink.disabled{border-color:var(--hyperlink-button-border-disabled);background-color:var(--hyperlink-button-background-disabled);color:var(--hyperlink-button-foreground-disabled)}button.disabled{pointer-events:none}div[data-compactmode] button{min-block-size:24px;padding-block:2px 2px}.checkbox{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;margin:0;border-width:1px;border-style:solid;border-color:var(--check-box-check-background-stroke-unchecked);border-radius:var(--check-box-check-corner-radius);outline:0;background-clip:padding-box;background-color:var(--check-box-check-background-fill-unchecked);color:var(--check-box-check-glyph-foreground-unchecked);appearance:none;inline-size:20px;block-size:20px;pointer-events:auto}.checkbox:hover{background-color:var(--check-box-check-background-fill-unchecked-pointer-over);border-color:var(--check-box-check-background-stroke-unchecked-pointer-over);color:var(--check-box-check-glyph-foreground-unchecked-pointer-over)}.checkbox:active{border-color:var(--check-box-check-background-stroke-unchecked-pressed);background-color:var(--check-box-check-background-fill-unchecked-pressed);color:var(--check-box-check-glyph-foreground-unchecked-pressed)}.checkbox:active+.checkbox-glyph{color:var(--text-on-accent-secondary)}.checkbox:disabled{border-color:var(--check-box-check-background-stroke-unchecked-disabled);background-color:var(--check-box-check-background-fill-unchecked-disabled);color:var(--check-box-check-glyph-foreground-unchecked-disabled);pointer-events:none}.checkbox:checked{border:none;border-color:var(--check-box-check-background-stroke-checked);background-color:var(--check-box-check-background-fill-checked);color:var(--check-box-check-glyph-foreground-checked)}.checkbox:checked:hover{border-color:var(--check-box-check-background-stroke-checked-pointer-over);background-color:var(--check-box-check-background-fill-checked-pointer-over);color:var(--check-box-check-glyph-foreground-checked-pointer-over)}.checkbox:checked:active{border-color:var(--check-box-check-background-stroke-checked-pressed);background-color:var(--check-box-check-background-fill-checked-pressed);color:var(--check-box-check-glyph-foreground-checked-pressed)}.checkbox:checked:disabled{border-color:var(--check-box-check-background-stroke-checked-disabled);background-color:var(--check-box-check-background-fill-checked-disabled);color:var(--check-box-check-glyph-foreground-checked-disabled)}.checkbox:checked:disabled+.checkbox-glyph{color:var(--text-on-accent-disabled)}.checkbox:checked+.checkbox-glyph .path-checkmark{transition:stroke-dashoffset 250ms cubic-bezier(.55,0,0,1);stroke-dashoffset:0}.checkbox-container{display:flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--check-box-foreground)!important;user-select:none;min-block-size:32px}.checkbox-container>span{padding-inline-start:8px}.checkbox-container.disabled{color:var(--check-box-foreground-disabled)!important}.checkbox-inner{display:flex;justify-content:center;align-items:center;position:relative}.checkbox-glyph{pointer-events:none;position:absolute;color:var(--check-box-check-glyph-foreground-unchecked);inline-size:12px;block-size:12px}.checkbox-glyph path{transform-origin:center}.checkbox-glyph .path-checkmark{transform:scale(1.2);stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:20.5;stroke-dashoffset:20.5}.combo-box{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;pointer-events:auto}.combo-box-with-header{display:flex;flex-direction:column;position:relative}.combo-box-with-header .combo-box-header{margin-bottom:4px}.combo-box-button .arrow-down-icon{margin-left:4px;transition-duration:.2s;transition-property:transform;color:currentColor!important}.combo-box-button:active .arrow-down-icon{transform:translateY(2px);color:currentColor!important}.combo-box-list-box{margin:0;padding:0;max-height:400px}.drop-down-list{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;pointer-events:auto}.drop-down-list-with-header{display:flex;flex-direction:column;position:relative}.drop-down-list-with-header .drop-down-list-header{margin-bottom:4px}.drop-down-list-button .arrow-down-icon{margin-left:4px;transition-duration:.2s;transition-property:transform;color:currentColor!important}.drop-down-list-button:active .arrow-down-icon{transform:translateY(2px);color:currentColor!important}.drop-down-list-drop-down{margin-top:4px!important}.drop-down-list-drop-down[data-popover-flip=flipped]{margin-top:-4px!important}.radio-button{display:inline-flex;justify-content:center;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;position:relative;margin:0;border-width:1px;border-style:solid;border-color:var(--radio-button-outer-ellipse-stroke);border-radius:20px;outline:0;background-clip:padding-box;background-color:var(--radio-button-outer-ellipse-fill);appearance:none;inline-size:20px;block-size:20px;pointer-events:auto}.radio-button::before{content:"";inline-size:4px;block-size:4px;visibility:hidden;position:absolute;border-radius:12px;background-color:var(--radio-button-check-glyph-fill)}.radio-button:hover{border-color:var(--radio-button-outer-ellipse-stroke-pointer-over);background-color:var(--radio-button-outer-ellipse-fill-pointer-over)}.radio-button:hover+span{color:var(--radio-button-foreground-pointer-over)}.radio-button:active{border-color:var(--radio-button-outer-ellipse-stroke-pressed);background-color:var(--radio-button-outer-ellipse-fill-pressed)}.radio-button:active::before{transition:250ms cubic-bezier(0,0,0,1);visibility:visible;inline-size:10px;block-size:10px}.radio-button:active+span{color:var(--radio-button-foreground-pressed)}.radio-button:disabled{border-color:var(--radio-button-outer-ellipse-stroke-disabled);background-color:var(--radio-button-outer-ellipse-fill-disabled)}.radio-button:disabled::before{visibility:hidden}.radio-button:disabled+span{color:var(--radio-button-foreground-disabled)}.radio-button:checked{border:none;background-color:var(--radio-button-outer-ellipse-checked-fill)}.radio-button:checked::before{visibility:visible;transition:250ms cubic-bezier(0,0,0,1);box-shadow:0 0 0 1px var(--radio-button-check-glyph-stroke);inline-size:12px;block-size:12px}.radio-button:checked:hover{background-color:var(--radio-button-outer-ellipse-checked-fill-pointer-over)}.radio-button:checked:hover::before{inline-size:14px;block-size:14px}.radio-button:checked:active{background-color:var(--radio-button-outer-ellipse-checked-fill-pressed)}.radio-button:checked:active::before{inline-size:10px;block-size:10px}.radio-button:checked:disabled{background-color:var(--radio-button-outer-ellipse-checked-fill-disabled)}.radio-button:checked:disabled::before{box-shadow:none;inline-size:12px;block-size:12px}.radio-button-container{display:flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--radio-button-foreground);background-color:var(--radio-button-background);user-select:none;min-block-size:32px}.radio-button-container:hover{background-color:var(--radio-button-background-pointer-over)}.radio-button-container:hover>span{color:var(--radio-button-foreground-pointer-over)!important}.radio-button-container:active{background-color:var(--radio-button-background-pressed)}.radio-button-container:active>span{color:var(--radio-button-foreground-pressed)!important}.radio-button-container.disabled{background-color:var(--radio-button-background-disabled)}.radio-button-container.disabled>span{color:var(--radio-button-foreground-disabled)!important}.radio-button-container>span{padding-inline-start:8px}.toggle-switch{display:inline-flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;position:relative;margin:0;border:var(--toggle-switch-outer-border-stroke-thickness) solid var(--toggle-switch-stroke-off);border-radius:20px;outline:0;background-color:var(--toggle-switch-fill-off);appearance:none;inline-size:var(--toggle-switch-width);block-size:var(--toggle-switch-height);pointer-events:auto}.toggle-switch::before{content:"";position:absolute;border-radius:var(--toggle-switch-knob-radius);background-color:var(--toggle-switch-knob-fill-off);transition:transform 167ms ease-in-out,height 167ms cubic-bezier(0,0,0,1),width 167ms cubic-bezier(0,0,0,1),margin 167ms cubic-bezier(0,0,0,1),background 167ms linear;inset-inline-start:var(--toggle-switch-knob-offset);inline-size:var(--toggle-switch-knob-width);block-size:var(--toggle-switch-knob-height)}.toggle-switch:hover{border-color:var(--toggle-switch-stroke-off-pointer-over);background-color:var(--toggle-switch-fill-off-pointer-over)}.toggle-switch:hover::before{inline-size:var(--toggle-switch-knob-width-pointer-over);block-size:var(--toggle-switch-knob-height-pointer-over)}.toggle-switch:active{border-color:var(--toggle-switch-stroke-off-pressed);background-color:var(--toggle-switch-fill-off-pressed)}.toggle-switch:active::before{inline-size:var(--toggle-switch-knob-width-pointer-over);block-size:var(--toggle-switch-knob-height-pointer-over)}.toggle-switch:disabled{border-color:var(--toggle-switch-stroke-off-disabled);background-color:var(--toggle-switch-fill-off-disabled)}.toggle-switch:disabled::before{margin:0!important;background-color:var(--toggle-switch-knob-fill-off-disabled);box-shadow:none;inline-size:var(--toggle-switch-knob-width);block-size:var(--toggle-switch-knob-height)}.toggle-switch:disabled+span{color:var(--toggle-switch-foreground-disabled)!important}.toggle-switch:checked{border:var(--toggle-switch-on-stroke-thickness) solid var(--toggle-switch-stroke-on);background-color:var(--toggle-switch-fill-on)}.toggle-switch:checked::before{background-color:var(--toggle-switch-knob-fill-on);box-shadow:0 0 0 1px solid var(--toggle-switch-knob-stroke-on);transform:translateX(var(--toggle-switch-knob-active-translation))}.toggle-switch:checked:hover{border-color:var(--toggle-switch-stroke-on-pointer-over);background-color:var(--toggle-switch-fill-on-pointer-over)}.toggle-switch:checked:hover::before{margin-inline-start:var(--toggle-switch-knob-zoom-pointer-over)}.toggle-switch:checked:active{border-color:var(--toggle-switch-stroke-on-pressed);background-color:var(--toggle-switch-fill-on-pressed)}.toggle-switch:checked:active::before{margin-inline-start:var(--toggle-switch-knob-zoom-pointer-over)}.toggle-switch:checked:disabled{border-color:var(--toggle-switch-stroke-on-disabled);background-color:var(--toggle-switch-fill-on-disabled)}.toggle-switch:checked:disabled::before{box-shadow:none;background-color:var(--toggle-switch-knob-fill-on-disabled)}.toggle-switch-container{display:flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--toggle-switch-foreground)!important;user-select:none;min-block-size:32px}.toggle-switch-container>span{padding-inline-end:8px}.data-grid{position:relative;width:100%;flex:1;border-collapse:collapse;border-spacing:0;pointer-events:auto}.data-grid-container{position:relative;width:100%;height:100%;flex:1;border-radius:var(--control-corner-radius);background-clip:padding-box;border-width:1px;border-style:solid;border-color:var(--data-grid-border)}.data-grid-underline{position:absolute;inset-inline-start:-1px;inset-block-start:-1px;inline-size:calc(100% + 2px);block-size:calc(100% + 2px);pointer-events:none;border-radius:var(--control-corner-radius);overflow:hidden}.data-grid-underline::after{content:"";box-sizing:border-box;position:absolute;inset-block-end:0;inset-inline-start:0;inline-size:100%;block-size:100%;border-bottom:1px solid var(--control-strong-stroke-default)}.data-grid-resizer{position:absolute;top:0;right:0;width:8px;cursor:col-resize;user-select:none}.data-grid thead th{display:table-cell;position:relative;color:var(--data-grid-column-header-foreground);background-color:var(--data-grid-column-header-background);border-right:1px solid var(--control-stroke-color-default);border-bottom:1px solid var(--control-stroke-color-default)}.data-grid thead th:hover{background-color:var(--data-grid-column-header-background-pointer-over)}.data-grid thead th:active{background-color:var(--data-grid-column-header-background-pressed)}.data-grid tbody tr{color:var(--data-grid-row-foreground);background-color:var(--data-grid-row-background)}.data-grid tbody tr:hover{background-color:var(--data-grid-row-background-pointer-over)}.data-grid tbody tr.odd{background-color:var(--data-grid-row-odd-background)}.data-grid tbody tr.odd:hover{background-color:var(--data-grid-row-odd-background-pointer-over)}.data-grid tbody tr.selected{background-color:var(--data-grid-row-selected-background)!important;color:var(--data-grid-row-selected-foreground)!important}.data-grid tbody tr.selected:hover{background-color:var(--data-grid-row-selected-background-pointer-over)!important;color:var(--data-grid-row-selected-foreground-pointer-over)!important}.data-grid td{color:currentColor}.grid-view{position:relative;width:100%;height:100%;flex:1;pointer-events:auto}.grid-view .header{margin-bottom:24px}.grid-view .footer{margin-top:24px}.grid-view .grid-view-group{position:relative}.grid-view .grid-view-group .grid-view-group-header{position:sticky;top:0;z-index:10;background-color:transparent;border-bottom:1px solid var(--grid-view-header-border-line);padding-bottom:8px;margin-top:8px;margin-bottom:4px}.grid-view .grid-view-group .grid-view-items-container{clip-path:none;display:flex!important;gap:12px;flex-wrap:wrap!important;padding:0;margin:16px 0 0}.grid-view .grid-view-group .grid-view-items-container .grid-view-item{background-color:var(--grid-view-item-background);border-style:solid;border-width:1px;border-color:var(--grid-view-item-stroke);border-radius:var(--control-corner-radius);list-style-type:none}.grid-view .grid-view-group .grid-view-items-container .grid-view-item .grid-view-item-hover-filter:hover{background-color:var(--grid-view-item-background-hover);height:100%}.list-box{margin:0;padding:0;pointer-events:auto}.list-box-item{display:flex;align-items:center;position:relative;box-sizing:border-box;flex:0 0 auto;margin:3px;padding-inline:12px;border-radius:var(--list-box-item-radius);outline:0;background-color:var(--list-box-item-background);color:var(--list-box-item-foreground);cursor:default;user-select:none;-webkit-user-select:none;min-block-size:34px;text-decoration:none;pointer-events:auto}.list-box-item::before{content:"";position:absolute;border-radius:3px;background-color:var(--list-box-item-selection-indicator);transition:transform 167ms cubic-bezier(0,0,0,1);opacity:0;inset-inline-start:0;inline-size:3px;min-block-size:16px;transform:scaleY(0)}.list-box-item.selected::before{transform:scaleY(1);opacity:1}.list-box-item:hover{background-color:var(--list-box-item-background-pointer-over)}.list-box-item.selected{background-color:var(--list-box-item-background-selected);color:var(--list-box-item-foreground-selected)!important}.list-box-item.selected *{color:var(--list-box-item-foreground-selected)!important}.list-box-item:active{background-color:var(--list-box-item-background-pressed);color:var(--list-box-item-foreground-pressed)}.list-box-item:active::before{transform:scaleY(.625)}.list-box-item.disabled{background-color:var(--list-box-item-background-disabled);color:var(--list-box-item-foreground-disabled);pointer-events:none}.list-box-item.disabled.selected{background-color:var(--list-box-item-background-selected-disabled)}.list-box-item.disabled.selected::before{background-color:var(--accent-disabled)}.list-box-item>:global(svg){inline-size:16px;min-block-size:auto;fill:currentColor;margin-inline-end:16px}div[data-compactmode] .list-box-item{min-block-size:24px}.font-icon{display:block;color:inherit;user-select:none;-webkit-user-select:none}.font-icon::before{content:attr(data-glyph)}.info-bar{display:flex;align-items:center;position:relative;min-block-size:48px;box-sizing:border-box;user-select:none;background-clip:padding-box;border:var(--info-bar-border-thickness) solid var(--info-bar-border);border-radius:var(--info-bar-border-corner-radius)}.info-bar.severity-success{background-color:var(--info-bar-success-severity-background)}.info-bar.severity-warning{background-color:var(--info-bar-warning-severity-background)}.info-bar.severity-error{background-color:var(--info-bar-error-severity-background)}.info-bar.severity-informational{background-color:var(--info-bar-informational-severity-background)}.info-bar-content-container{display:flex;align-items:center;width:100%;justify-content:center;position:relative;padding:12px 0 12px 16px}.info-bar-icon{align-self:flex-start;display:flex;flex:0 0 auto;margin-right:6px;margin-top:2px}.info-bar-icon .info-badge{display:inline-flex;justify-content:center;align-items:center;box-sizing:border-box;user-select:none;min-inline-size:16px;min-block-size:16px;border-radius:16px;padding:2px 4px;margin-inline-end:0}.info-bar-icon .info-badge.severity-informational{background-color:var(--info-bar-informational-severity-icon-background);color:var(--info-bar-informational-severity-icon-foreground);fill:var(--info-bar-informational-severity-icon-foreground)!important}.info-bar-icon .info-badge.severity-success{background-color:var(--info-bar-success-severity-icon-background);color:var(--info-bar-success-severity-icon-foreground);fill:var(--info-bar-success-severity-icon-foreground)!important}.info-bar-icon .info-badge.severity-warning{background-color:var(--info-bar-warning-severity-icon-background);color:var(--info-bar-warning-severity-icon-foreground);fill:var(--info-bar-warning-severity-icon-foreground)!important}.info-bar-icon .info-badge.severity-error{background-color:var(--info-bar-error-severity-icon-background);color:var(--info-bar-error-severity-icon-foreground);fill:var(--info-bar-error-severity-icon-foreground)!important}.info-bar-icon .info-badge svg{line-height:12px;font-size:12px;inline-size:8px;block-size:8px;fill:inherit}.info-bar-icon .info-badge svg path{fill:inherit}.info-bar-content{display:flex;align-items:center;flex-wrap:wrap;position:relative;box-sizing:border-box;flex:1 1 auto;margin-left:6px}.info-bar-content h5,.info-bar-content span{margin:0;line-height:20px}.info-bar-content h5{margin-inline-end:12px;color:var(--info-bar-title-foreground)}.info-bar-content span{flex:1 1 auto;margin-inline-end:15px;color:var(--info-bar-message-foreground)}.info-bar-button{margin-right:6px;margin-top:6px;margin-bottom:auto}.info-bar-button.action{margin-top:7px}.info-bar-button .close-button{height:36px;width:36px}div[data-compactmode] .info-bar{min-block-size:34px}div[data-compactmode] .info-bar-content-container{padding:6px 0 6px 16px}div[data-compactmode] .info-bar-button{margin-top:4px;margin-bottom:auto}div[data-compactmode] .info-bar-button.action{margin-top:3px}div[data-compactmode] .info-bar-button .close-button{height:16px;width:24px}@keyframes indeterminate-1{0%{opacity:1;transform:translateX(-100%)}70%{opacity:1;transform:translateX(100%)}70.01%{opacity:0}100%{opacity:0;transform:translateX(-100%)}}@keyframes indeterminate-2{0%{opacity:0}50%{opacity:0;transform:translateX(-100%)}50.01%{opacity:1;transform:translateX(-100%)}100%{transform:translateX(100%);opacity:1}}.progress-bar{display:flex;align-items:center;width:100%;height:3px;min-block-size:3px}.progress-bar-track{max-width:50%;height:3px;transition:fill 167ms linear;fill:var(--accent-default)}.progress-bar-rail{fill:var(--control-strong-stroke-default);width:100%;height:1px}.progress-bar.indeterminate .progress-bar-track{fill:transparent}.progress-bar.indeterminate .progress-bar-track:first-of-type{width:40%;fill:var(--accent-default);animation:2s infinite indeterminate-1}.progress-bar.indeterminate .progress-bar-track:nth-of-type(2){width:60%;fill:var(--accent-default);opacity:0;animation:2s infinite indeterminate-2}@keyframes progress-ring-indeterminate{0%{stroke-dasharray:.01px 43.97px;transform:rotate(0)}50%{stroke-dasharray:21.99px 21.99px;transform:rotate(450deg)}100%{stroke-dasharray:.01px 43.97px;transform:rotate(1080deg)}}.progress-ring{outline:0;min-inline-size:16px;min-block-size:16px}.progress-ring circle{transform:rotate(-90deg);transform-origin:50% 50%;transition:250ms linear;fill:none;stroke:var(--accent-default);stroke-width:1.5;stroke-linecap:round;stroke-dasharray:43.97}.progress-ring.indeterminate circle{animation:2s linear infinite progress-ring-indeterminate}.card{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--card-foreground);border:var(--card-border-thickness);border-color:var(--card-border);border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--card-background-color);min-height:72px;padding-block:16px;padding-inline:11px;pointer-events:auto}.card .card-header{display:grid;grid-template-columns:20px 1fr;gap:20px;grid-template-areas:"icon context";margin-left:4px}.card .card-header .card-icon{grid-area:icon}.card .card-header .card-context{grid-area:context;display:flex;flex-direction:column}.card .card-header .card-context .card-description{color:var(--card-foreground-description)}.card .card-control{display:inline-flex;gap:16px}.card .card-control.child-of-expander{margin-right:36px}div[data-compactmode] .card{min-height:48px;padding-block:4px}.dialog{border:solid 1px var(--dialog-border-color);border-radius:var(--overlay-corner-radius);background-color:var(--dialog-background-color);-webkit-box-shadow:var(--dialog-shadow);box-shadow:var(--dialog-shadow);animation:.25s cubic-bezier(.25,.1,.25,1) both dialog-open-animation}.dialog-auto-height{display:grid;min-width:300px;min-height:200px;max-width:min(100vw - 128px,800px);max-height:min(100vh - 128px,600px)}.dialog-footer{background-color:var(--dialog-footer-background-color);border-bottom-right-radius:var(--overlay-corner-radius);border-bottom-left-radius:var(--overlay-corner-radius)}.dialog-footer button{min-width:245px}.dialog-overlay{top:0;left:0;right:0;bottom:0;border-radius:inherit;position:absolute;height:100%;width:100%;border-color:transparent;animation:.25s dialog-overlay-fadein-animation;-webkit-animation:.25s dialog-overlay-fadein-animation;-moz-animation:.25s dialog-overlay-fadein-animation;-o-animation:.25s dialog-overlay-fadein-animation}.dialog-overlay-dim{background-color:var(--dialog-light-dismiss-overlay-background)!important}.dialog-container{z-index:20010;display:flex;position:fixed;top:0;left:0;bottom:0;right:0;margin:0;padding:0;align-items:center;justify-content:center;box-sizing:border-box;background:0 0;pointer-events:none}.dialog-container *{pointer-events:auto}@keyframes dialog-overlay-fadein-animation{0%{opacity:0}100%{opacity:1}}@keyframes dialog-open-animation{0%{-webkit-transform:scale(1.25);transform:scale(1.25);opacity:0}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.expander{pointer-events:auto}.expander .card[data-expanded=true]{border-bottom-left-radius:0;border-bottom-right-radius:0}.expander .card[data-expanded=true] .expander-expand-button .font-icon{transform:rotate(180deg);-webkit-transition:transform .2s ease-in-out;-o-transition:transform .2s ease-in-out;-ms-transition:transform .2s ease-in-out;transition:transform .2s ease-in-out}.expander .card.expander-card{-webkit-transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out;-o-transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out;-ms-transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out;transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out}.expander .card.expander-card .expander-expand-button{display:grid;height:32px;width:32px;border-radius:var(--control-corner-radius);border-color:var(--subtle-fill-transparent);background-color:var(--subtle-fill-transparent);color:var(--text-fill-color-primary)}.expander .card.expander-card .expander-expand-button .font-icon{-webkit-transition:transform .2s ease-in-out;-o-transition:transform .2s ease-in-out;-ms-transition:transform .2s ease-in-out;transition:transform .2s ease-in-out}.expander .card.expander-card:hover .expander-expand-button{border-color:var(--subtle-fill-transparent);background-color:var(--subtle-fill-secondary);color:var(--text-fill-color-primary)}.expander .card.expander-card:active .expander-expand-button{border-color:var(--subtle-fill-transparent);background-color:var(--subtle-fill-tertiary);color:var(--text-fill-color-primary)}.expander .card.expander-card:focus-visible{box-shadow:none}.expander .card.expander-card:focus-visible .expander-expand-button{box-shadow:var(--focus-stroke)}.expander .expander-content-anchor{max-height:0;position:relative;overflow:hidden;-webkit-transition:max-height linear 250ms;-o-transition:max-height linear 250ms;-ms-transition:0s linear 250ms max-height;transition:max-height linear 250ms}.expander .expander-content-anchor .expander-content{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;display:block;min-height:72px;margin-top:0;border-radius:var(--control-corner-radius);border-top-left-radius:0;border-top-right-radius:0;border:var(--card-border-thickness);border-top-width:0;border-color:var(--card-border);background-clip:padding-box;background-color:var(--card-background-color-secondary);-webkit-transition:transform 250ms cubic-bezier(1,1,0,1);-o-transition:transform 250ms cubic-bezier(1,1,0,1);-ms-transition:transform 250ms cubic-bezier(1,1,0,1);transition:transform 250ms cubic-bezier(1,1,0,1);transform:translateY(-100%)}.expander .expander-content-anchor[aria-expanded=true]{max-block-size:602000000000000000000000vmax;transition:none}.expander .expander-content-anchor[aria-expanded=true] .expander-content{transform:none;-webkit-transition:transform 250ms cubic-bezier(0,0,0,1);-o-transition:transform 250ms cubic-bezier(0,0,0,1);-ms-transition:250ms cubic-bezier(0,0,0,1) transform;transition:transform 250ms cubic-bezier(0,0,0,1)}.expander .expander-content-anchor[aria-expanded=false] .expander-content{display:none}div[data-compactmode] .expander .expander-content-anchor .expander-content{min-height:48px}.full-screen-container{height:100%;width:100%}.popover{outline:0;z-index:calc(var(--popover-zindex) + 1);position:absolute;opacity:0}.popover.popover-fixed{position:fixed}.popover.popover-relative-width{width:100%}.popover.popover-open{opacity:1;transition:opacity;margin:0;padding:0;min-inline-size:75px;box-sizing:border-box;border-radius:var(--overlay-corner-radius);border-width:1px;border-style:solid;border-color:var(--menu-flyout-presenter-border);background-color:var(--menu-flyout-presenter-background);-webkit-backdrop-filter:var(--menu-flyout-presenter-backdrop-filter);backdrop-filter:var(--menu-flyout-presenter-backdrop-filter);background-clip:padding-box;box-shadow:var(--menu-flyout-presenter-shadow)}.popover:not(.popover-open){pointer-events:none;transition-duration:0s!important;transition-delay:0s!important}.overlay{top:0;left:0;right:0;bottom:0;margin:0!important;align-items:center;justify-content:center;border-radius:inherit;background:0 0;cursor:default;display:flex;position:fixed;transition:.3s cubic-bezier(.25,.8,.5,1),z-index 1ms;z-index:5}.overlay.overlay-absolute{position:absolute}.overlay .overlay-content{position:relative}.scroll-viewer{overflow-y:overlay;overflow-x:overlay;height:100%;width:100%;pointer-events:auto}.scroll-viewer.vertical{overflow-y:overlay;overflow-x:hidden}.scroll-viewer.horizontal{overflow-y:hidden;overflow-x:overlay}.scroll-viewer.not-scrollable{overflow-x:hidden;overflow-y:hidden}.scroll-viewer.use-native-scroll::-webkit-scrollbar-track{border-radius:999px;background:var(--scrollbar-track-background-color)}.scroll-viewer.use-native-scroll::-webkit-scrollbar-thumb{border-radius:999px;border-width:3px;border-style:solid;border-color:var(--scrollbar-thumb-border-color);background-color:var(--scrollbar-thumb-background-color)}.scroll-viewer.use-native-scroll::-webkit-scrollbar{width:0;transition:.2s ease-in-out}.scroll-viewer.use-native-scroll:hover::-webkit-scrollbar{width:3px}.scroll-viewer.use-native-scroll.on-hover::-webkit-scrollbar{width:12px}.scroll-viewer.use-native-scroll.on-hover::-webkit-scrollbar-track{background:var(--scrollbar-track-background-color-hover)}.scroll-viewer.use-native-scroll.on-hover::-webkit-scrollbar-thumb{border-color:var(--scrollbar-thumb-border-color-hover);background-color:var(--scrollbar-thumb-background-color-hover)}[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto!important;height:auto!important;z-index:0}.simplebar-offset{direction:inherit!important;box-sizing:inherit!important;resize:none!important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0}.simplebar-content-wrapper{direction:inherit;box-sizing:border-box!important;position:relative;display:block;height:100%;width:auto;max-width:100%;max-height:100%;overflow:auto;scrollbar-width:none;-ms-overflow-style:none}.simplebar-content-wrapper:focus-visible{box-shadow:inset 0 0 0 2px var(--focus-stroke-outer)}.simplebar-content-wrapper::-webkit-scrollbar,.simplebar-hide-scrollbar::-webkit-scrollbar{display:none;width:0;height:0}.simplebar-content:after,.simplebar-content:before{content:" ";display:table}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none}.simplebar-height-auto-observer-wrapper{box-sizing:inherit!important;height:100%;width:100%;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0}.simplebar-height-auto-observer{box-sizing:inherit;display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1}.simplebar-scrollbar{position:absolute;left:0;right:0;min-height:10px}.simplebar-scrollbar:before{position:absolute;content:"";background:var(--scrollbar-thumb-background-color);border-radius:7px;border-width:1px;border-style:solid;border-color:var(--scrollbar-thumb-border-color);opacity:0;transition:.2s ease-in-out 2s}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;background:var(--scrollbar-track-background-color);border-radius:7px}.simplebar-track.simplebar-hover{background:var(--scrollbar-track-background-color-hover)}.simplebar-track.simplebar-hover .simplebar-scrollbar:before{border-color:var(--scrollbar-thumb-border-color-hover);background-color:var(--scrollbar-thumb-background-color-hover)}.simplebar-track.simplebar-vertical{top:0;width:5px;transition:width .2s ease-in-out}.simplebar-track.simplebar-vertical.simplebar-hover,.simplebar-track.simplebar-vertical.simplebar-hover .simplebar-scrollbar{width:12px}.simplebar-track.simplebar-horizontal{left:0;height:5px;transition:height .2s ease-in-out}.simplebar-track.simplebar-horizontal.simplebar-hover,.simplebar-track.simplebar-horizontal.simplebar-hover .simplebar-scrollbar{height:12px}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:0;bottom:0;min-height:0;min-width:10px;width:auto}[data-simplebar].simplebar-dragging,[data-simplebar].simplebar-dragging .simplebar-content{pointer-events:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all}.simplebar-scrollbar.simplebar-visible:before{opacity:1;transition-delay:0s;transition-duration:.2s}.simplebar-scrollbar:before{top:2px;bottom:2px;left:2px;right:2px}[data-simplebar-direction=rtl] .simplebar-track.simplebar-vertical{right:auto;left:0}.simplebar-dummy-scrollbar-size{direction:rtl;position:fixed;opacity:0;visibility:hidden;height:500px;width:500px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:scrollbar!important}.simplebar-dummy-scrollbar-size>div{width:200%;height:200%;margin:10px 0}.simplebar-hide-scrollbar{position:fixed;left:0;visibility:hidden;overflow-y:scroll;scrollbar-width:none;-ms-overflow-style:none}.simplebar-content{display:grid;grid-template-rows:minmax(min-content,1fr);height:100%}.split-grid{height:inherit;pointer-events:auto}.split-grid-gripper{display:flex;justify-content:center}.split-grid-gripper:active,.split-grid-gripper:hover{background-color:var(--subtle-fill-secondary)}.stack-vertical{display:flex;flex-direction:column;flex-wrap:nowrap;align-items:stretch;justify-content:stretch;width:100%}.stack-horizontal{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;justify-content:normal;width:100%;max-height:inherit!important;height:100%!important}.context-menu{display:contents;position:relative}.context-menu>div.context-menu-activator{display:contents}.context-menu-list-box .list-box{display:inline-block}.context-menu-list-box .list-box:focus,.context-menu-list-box .list-box:focus-visible,.context-menu-list-box .list-box:focus-within{outline:0}.context-menu-list-box .context-menu-item{padding:5px 8px;cursor:default;border-radius:var(--control-corner-radius);background-color:var(--context-menu-item-background);color:var(--context-menu-item-foreground);margin:3px;display:grid;grid-template-columns:16px 1fr auto;grid-template-rows:1fr;gap:0 12px;grid-template-areas:"icon title keyboard-shortcut"}.context-menu-list-box .context-menu-item.selected,.context-menu-list-box .context-menu-item:focus,.context-menu-list-box .context-menu-item:focus-visible,.context-menu-list-box .context-menu-item:hover{outline:0;background-color:var(--context-menu-item-background-pointer-over)}.context-menu-list-box .context-menu-item:active{background-color:var(--context-menu-item-background-pressed);color:var(--context-menu-item-foreground-pressed)}.context-menu-list-box .context-menu-item.disabled{background-color:var(--context-menu-item-background-disabled);color:var(--context-menu-item-foreground-disabled)}.context-menu-list-box .context-menu-item.disabled .keyboard-accelerator{color:var(--context-menu-item-key-accelerator-foreground-disabled)}.context-menu-list-box .context-menu-item.disabled .font-icon{color:var(--context-menu-item-foreground-disabled)}.context-menu-list-box .context-menu-item .icon-container{grid-area:icon;height:20px;width:20px;display:block;align-self:center}.context-menu-list-box .context-menu-item .font-icon{align-self:center;font-size:20px}.context-menu-list-box .context-menu-item .text{grid-area:title;align-self:center}.context-menu-list-box .context-menu-item .keyboard-accelerator{grid-area:keyboard-shortcut;color:var(--context-menu-item-key-accelerator-foreground);align-self:center;margin-left:32px}.nav-bar-root{pointer-events:auto;height:100%;display:grid;grid-template-columns:auto 1fr;grid-template-rows:48px calc(100vh - 48px);gap:0 0;grid-template-areas:"header header" "sidebar content"}.nav-bar-root.hidden{background:var(--navigation-view-content-background)}.nav-bar-root.hidden main{background:0 0;border:0 solid transparent;border-top-left-radius:0}.nav-bar-root .nav-bar-button-icon{font-size:16px;line-height:16px}.nav-bar-root .nav-bar-header{grid-area:header;z-index:10001}.nav-bar-root nav{grid-area:sidebar;width:320px;max-width:320px;overflow:hidden;display:grid;grid-template-columns:1fr;grid-template-rows:auto 1fr auto;gap:0 0;grid-template-areas:"sidebar-header" "sidebar-body" "sidebar-footer"}.nav-bar-root nav.transition{-webkit-transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out;-o-transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out;-ms-transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out;transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out}.nav-bar-root nav.hidden{width:0;max-width:0;overflow-x:hidden}.nav-bar-root nav.collapsed{width:49px;max-width:49px;overflow-x:hidden}.nav-bar-root nav.expanded-overlay{z-index:10000;position:absolute;padding-top:48px;height:100%;width:320px;max-width:320px;border-top-right-radius:var(--overlay-corner-radius);border-bottom-right-radius:var(--overlay-corner-radius);border:1px solid;border-color:var(--navigation-view-flyout-border-color);background-color:var(--navigation-view-flyout-background-color);-webkit-backdrop-filter:var(--navigation-view-flyout-backdrop-filter);backdrop-filter:var(--navigation-view-flyout-backdrop-filter);box-shadow:0 8px 16px rgba(0,0,0,.26)}.nav-bar-root nav .sidebar-header{grid-area:sidebar-header;display:block;width:100%}.nav-bar-root nav .sidebar-body{grid-area:sidebar-body;height:100%}.nav-bar-root nav .sidebar-footer{grid-area:sidebar-footer;padding-bottom:4px}.nav-bar-root nav .sidebar-items{margin:0;padding:0}.nav-bar-root main{grid-area:content;background:var(--navigation-view-content-background);border:var(--navigation-view-content-grid-border-thickness);border-color:var(--navigation-view-content-grid-border);border-top-left-radius:var(--overlay-corner-radius);position:relative}.nav-bar-root.expanded-overlay:not(.hidden) main{margin-left:49px}div[data-compactmode] .nav-bar-root .sidebar-header .text-box{min-block-size:32px;padding-inline:10px}nav{pointer-events:auto}nav .sidebar-item-separator{margin:4px 0;padding:0;height:1px;border-color:transparent;background-color:var(--navigation-view-separator-color)}nav .list-box-item{display:grid;grid-template-columns:auto 1fr auto;grid-template-rows:1fr;gap:0 16px;grid-template-areas:". . .";padding-right:0}nav .list-box-item.sidebar-item{margin:var(--sidebar-item-margin)}nav .list-box-item .sidebar-item-icon{height:16px;width:16px}nav .list-box-item .sidebar-expand-group-button{height:34px!important;transition-duration:.2s;transition-property:transform}nav .list-box-item .sidebar-expand-group-button:active,nav .list-box-item .sidebar-expand-group-button:hover{border-color:transparent;background-color:transparent}nav .list-box-item .sidebar-expand-group-button[data-expanded=true]{transform:rotate(180deg)}nav .sidebar-child-item{padding-left:42px}nav.collapsed:not(.expanded-overlay) .list-box-item{max-width:40px;overflow:hidden}nav.collapsed:not(.expanded-overlay) .list-box-item .sidebar-expand-group-button,nav.collapsed:not(.expanded-overlay) .list-box-item .sidebar-item-text,nav.collapsed:not(.expanded-overlay) .sidebar-items.children{display:none}div[data-usercompactmode] nav .list-box-item .sidebar-expand-group-button{height:24px!important}div[data-compactmode]:not(div[data-usercompactmode]) nav .list-box-item{block-size:34px}.auto-suggest-box-drop-down{pointer-events:auto;-webkit-border-radius:var(--overlay-corner-radius)!important;-webkit-border-top-left-radius:0!important;-webkit-border-top-right-radius:0!important;border-radius:var(--overlay-corner-radius)!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.monaco-editor-standalone{user-select:none;-webkit-user-select:none;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;box-sizing:border-box;border:none;outline:0;cursor:unset;margin:0;inline-size:100%;min-block-size:30px;padding-inline:10px;border-radius:var(--control-corner-radius);color:var(--text-box-foreground);background-color:transparent;pointer-events:auto}.monaco-editor-standalone.disabled{color:var(--text-fill-color-disabled)}.monaco-editor-standalone-instance{min-height:100px;height:inherit;display:grid}.monaco-editor-standalone-instance.disabled{pointer-events:none;cursor:none;color:var(--text-fill-color-disabled)!important}.monaco-editor-standalone-instance .monaco-editor,.monaco-editor-standalone-instance .monaco-editor .overflow-guard{height:100%!important;min-height:min-content!important}.monaco-editor-standalone-with-header{display:block;position:relative;height:100%;pointer-events:auto}.monaco-editor-standalone-with-header .monaco-editor-standalone-header{margin-bottom:4px}.monaco-editor-standalone-container{height:inherit;cursor:text;position:relative;border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--text-box-background);border-width:1px;border-style:solid;border-color:var(--text-box-border)}.monaco-editor-standalone-container:hover{color:var(--text-box-foreground-pointer-over);background-color:var(--text-box-background-pointer-over);border-color:var(--text-box-border-pointer-over)}.monaco-editor-standalone-container.disabled{cursor:default;color:var(--text-box-foreground-disabled);background-color:var(--text-box-background-disabled);border-color:var(--text-box-border-disabled)}.monaco-editor-standalone-container.disabled .monaco-editor-standalone-underline{display:none}.monaco-editor-standalone-container:focus-within{color:var(--text-box-foreground-focused);background-color:var(--text-box-background-focused);border-color:var(--text-box-border-focused)}.monaco-editor-standalone-container:focus-within .monaco-editor-standalone-underline::after{border-bottom:var(--text-box-underline-border-thickness-focused) solid var(--accent-default)}.monaco-editor-standalone-underline{position:absolute;inset-inline-start:-1px;inset-block-start:-1px;inline-size:calc(100% + var(--text-box-underline-border-thickness-focused));block-size:calc(100% + var(--text-box-underline-border-thickness-focused));pointer-events:none;border-radius:var(--control-corner-radius);overflow:hidden}.monaco-editor-standalone-underline::after{content:"";box-sizing:border-box;position:absolute;inset-block-end:0;inset-inline-start:0;inline-size:100%;block-size:100%;border-bottom:var(--text-box-underline-border-thickness) solid var(--control-strong-stroke-default)}.monaco-sash{background-color:var(--subtle-fill-secondary)}.text-block{color:currentColor;display:inline-block;margin:0;padding:0;cursor:default;user-select:none;white-space:pre-wrap;-webkit-user-select:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.text-block mark{background-color:var(--text-box-selection-highlight-color);color:var(--text-box-selection-color)}.text-block.type-display,.text-block.type-subtitle,.text-block.type-title,.text-block.type-title-large{font-family:var(--font-family-display);font-weight:var(--text-weight-bold)}.text-block.type-body,.text-block.type-body-large,.text-block.type-body-strong{font-family:var(--font-family-text)}.text-block.type-caption{line-height:16px;letter-spacing:.3px;color:var(--text-fill-color-secondary);font-size:var(--font-size-caption);font-weight:var(--text-weight-normal);font-family:var(--font-family-small)}.text-block.type-body,.text-block.type-body-large,.text-block.type-body-strong{line-height:20px;letter-spacing:.3px;font-weight:var(--text-weight-normal);font-size:var(--font-size-body)}.text-block.type-body-strong{font-weight:var(--text-weight-bolder)}.text-block.type-body-large{font-size:var(--font-size-body-large);line-height:24px}.text-block.type-subtitle{font-size:var(--font-size-subtitle);line-height:28px}.text-block.type-title{font-size:var(--font-size-title);line-height:36px}.text-block.type-title-large{font-size:var(--font-size-title-large);line-height:52px}.text-block.type-display{font-size:var(--font-size-display);line-height:92px}.text-block.no-wrap{white-space:pre}.text-block.trim{overflow:hidden!important;text-overflow:ellipsis;width:100%}.text-block.hide{display:none!important}.text-block.disabled{color:var(--text-fill-color-disabled)}.text-block.horizontal-center{text-align:center}.text-block.vertical-center{top:50%;bottom:50%;position:relative}*{--font-family-fallback:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Ubuntu",system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;--font-family-text:"Segoe UI Variable Text","Seoge UI Variable Static Text",var(--font-family-fallback);--font-family-small:"Segoe UI Variable Small","Seoge UI Variable Static Small",var(--font-family-fallback);--font-family-display:"Segoe UI Variable Display","Seoge UI Variable Static Display",var(--font-family-fallback);--font-size-caption:12px;--font-size-body:14px;--font-size-body-large:18px;--font-size-subtitle:20px;--font-size-title:28px;--font-size-title-large:40px;--font-size-display:68px;--vscode-sash-size:16px;--focus-stroke:inset 0 0 0 1px var(--focus-stroke-inner),0 0 0 2px var(--focus-stroke-outer);text-rendering:geometricPrecision}:focus-visible{box-shadow:var(--focus-stroke);outline:0}div[data-theme=windows-dark-theme]{--control-corner-radius:4px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:154,236,254;--accent-light-2:98,205,254;--accent-light-1:0,146,250;--accent-base:0,121,214;--accent-dark-1:0,95,184;--accent-dark-2:0,62,148;--accent-dark-3:0,24,102;--accent-default:rgba(var(--accent-light-2));--accent-secondary:rgba(var(--accent-light-2), 0.9);--accent-tertiary:rgba(var(--accent-light-2), 0.8);--accent-disabled:rgba(255, 255, 255, 0.158);--system-fill-color-success:rgb(108, 203, 95);--system-fill-color-caution:rgb(252, 225, 0);--system-fill-color-critical:rgb(255, 153, 164);--system-fill-color-neutral:rgba(255, 255, 255, 0.545);--system-fill-color-success-background:rgb(57, 61, 27);--system-fill-color-caution-background:rgb(67, 53, 25);--system-fill-color-critical-background:rgb(68, 39, 38);--system-fill-color-neutral-background:rgba(255, 255, 255, 0.031);--system-fill-color-attention-background:rgba(255, 255, 255, 0.031);--text-on-accent-primary:rgb(0, 0, 0);--text-on-accent-secondary:rgba(0, 0, 0, 0.5);--text-on-accent-disabled:rgba(255, 255, 255, 0.53);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgb(255, 255, 255);--focus-stroke-inner:rgba(0, 0, 0, 0.3);--text-fill-color-primary:rgba(255, 255, 255, 1);--text-fill-color-secondary:rgba(255, 255, 255, 0.77);--text-fill-color-tertiary:rgba(255, 255, 255, 0.53);--text-fill-color-disabled:rgba(255, 255, 255, 0.36);--text-fill-color-inverse:rgba(0, 0, 0, 0.89);--control-fill-color-default:rgba(255, 255, 255, 0.059);--control-fill-color-secondary:rgba(255, 255, 255, 0.082);--control-fill-color-tertiary:rgba(255, 255, 255, 0.031);--control-fill-color-disabled:rgba(255, 255, 255, 0.043);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(30, 30, 30, 0.702);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.098);--control-alt-fill-color-tertiary:rgba(255, 255, 255, 0.043);--control-alt-fill-color-quarternary:rgba(255, 255, 255, 0.071);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgba(255, 255, 255, 0.071);--control-stroke-color-secondary:rgba(255, 255, 255, 0.094);--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(255, 255, 255, 0.544);--control-strong-stroke-disabled:rgba(255, 255, 255, 0.157);--divider-stroke-color-default:rgba(255, 255, 255, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(255, 255, 255, 0.061);--subtle-fill-tertiary:rgba(255, 255, 255, 0.042);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgba(58, 58, 58, 0.3);--control-border-color-default:var(--control-stroke-color-secondary) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.1);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(255, 255, 255, 0.05);--card-background-color-secondary:rgba(255, 255, 255, 0.03);--card-hover-background-color:rgba(255, 255, 255, 0.06);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-secondary);--accent-button-background-pressed:var(--accent-tertiary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-secondary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-fill-color-secondary);--stealth-button-background-pressed:var(--control-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-border-color-default);--stealth-button-border-pressed:var(--control-stroke-color-default);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-secondary);--hyperlink-button-background-pressed:var(--control-fill-color-tertiary);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-default);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-disabled);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-border-color-default);--hyperlink-button-border-pressed:var(--control-stroke-color-default);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:1px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:40px;--toggle-switch-height:20px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-quarternary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-default);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-default);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:12px;--toggle-switch-knob-height:12px;--toggle-switch-knob-offset:3px;--toggle-switch-knob-zoom-pointer-over:-1px;--toggle-switch-knob-active-translation:20px;--toggle-switch-knob-width-pointer-over:14px;--toggle-switch-knob-height-pointer-over:14px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(255, 255, 255, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.3);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgba(32, 32, 32, 1);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(255, 255, 255, 0.0605);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:var(--subtle-fill-secondary);--list-box-item-background-selected-disabled:var(--subtle-fill-secondary);--list-box-item-background-pressed:var(--subtle-fill-tertiary);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-secondary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:var(--accent-default);--menu-flyout-presenter-background:rgb(44, 44, 44);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.2);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-default);--text-box-background-pointer-over:var(--control-fill-color-secondary);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:1px;--text-box-underline-border-thickness-focused:2px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:rgba(31, 31, 31, 0.9);--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(205, 205, 205);--scrollbar-thumb-background-color-hover:rgb(218, 218, 218);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.305);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(43, 43, 43);--dialog-footer-background-color:rgb(32, 32, 32);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgba(0, 120, 212);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(14, 119, 53);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(226, 36, 26);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(204, 146, 0);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(107, 105, 214);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(3, 131, 135);--hero-background-image:url("../img/hero/dark-theme-tile.png");--hero-background-color:rgba(91, 42, 134, 0.5);--hero-title-color:linear-gradient(to right, #db2777, #ec4b4b, #e8c137, #35d49b, #4eb1e0, #a445e8, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=windows-light-theme]{--control-corner-radius:4px;--overlay-corner-radius:8px;--text-weight-normal:400;--text-weight-bold:500;--text-weight-bolder:600;--accent-light-3:154,236,254;--accent-light-2:98,205,254;--accent-light-1:0,120,212;--accent-base:0,103,192;--accent-dark-1:0,95,184;--accent-dark-2:0,103,192;--accent-dark-3:0,26,104;--accent-default:rgba(var(--accent-dark-2));--accent-secondary:rgba(var(--accent-dark-2), 0.9);--accent-tertiary:rgba(var(--accent-dark-2), 0.8);--accent-disabled:rgba(155, 155, 155);--system-fill-color-success:rgb(15, 123, 15);--system-fill-color-caution:rgb(157, 93, 0);--system-fill-color-critical:rgb(196, 43, 28);--system-fill-color-neutral:rgba(0, 0, 0, 0.447);--system-fill-color-success-background:rgb(223, 246, 221);--system-fill-color-caution-background:rgb(255, 244, 206);--system-fill-color-critical-background:rgb(253, 231, 233);--system-fill-color-neutral-background:rgba(0, 0, 0, 0.024);--system-fill-color-attention-background:rgba(246, 246, 246, 0.502);--text-on-accent-primary:rgb(255, 255, 255);--text-on-accent-secondary:rgba(255, 255, 255, 0.702);--text-on-accent-disabled:rgb(255, 255, 255);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgba(0, 0, 0, 1);--focus-stroke-inner:rgb(255, 255, 255);--text-fill-color-primary:rgba(0, 0, 0, 0.894);--text-fill-color-secondary:rgba(0, 0, 0, 0.62);--text-fill-color-tertiary:rgba(0, 0, 0, 0.447);--text-fill-color-disabled:rgba(0, 0, 0, 0.361);--text-fill-color-inverse:rgb(255, 255, 255);--control-fill-color-default:rgba(255, 255, 255, 0.702);--control-fill-color-secondary:rgba(249, 249, 249, 0.502);--control-fill-color-tertiary:rgba(249, 249, 249, 0.302);--control-fill-color-disabled:rgba(249, 249, 249, 0.302);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgb(255, 255, 255);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.024);--control-alt-fill-color-tertiary:rgba(0, 0, 0, 0.059);--control-alt-fill-color-quarternary:rgba(0, 0, 0, 0.094);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgba(0, 0, 0, 0.059);--control-stroke-color-secondary:rgba(0, 0, 0, 0.161);--control-stroke-on-accent-default:rgba(255, 255, 255, 0.078);--control-stroke-on-accent-secondary:rgba(0, 0, 0, 0.4);--control-strong-stroke-default:rgba(0, 0, 0, 0.447);--control-strong-stroke-disabled:rgba(0, 0, 0, 0.216);--divider-stroke-color-default:rgba(0, 0, 0, 0.059);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(0, 0, 0, 0.035);--subtle-fill-tertiary:rgba(0, 0, 0, 0.024);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgba(255, 255, 255, 0.502);--control-border-color-default:var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-secondary) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-default);--card-stroke-color-default:rgba(0, 0, 0, 0.059);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(255, 255, 255, 0.702);--card-background-color-secondary:rgba(246, 246, 246, 0.502);--card-hover-background-color:rgba(0, 0, 0, 0.06);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-secondary);--accent-button-background-pressed:var(--accent-tertiary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-secondary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--subtle-fill-transparent);--stealth-button-background-pointer-over:var(--subtle-fill-secondary);--stealth-button-background-pressed:var(--subtle-fill-tertiary);--stealth-button-background-disabled:var(--subtle-fill-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--subtle-fill-transparent);--stealth-button-border-pointer-over:var(--subtle-fill-secondary);--stealth-button-border-pressed:var(--subtle-fill-tertiary);--stealth-button-border-disabled:var(--subtle-fill-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--subtle-fill-transparent);--hyperlink-button-background-pointer-over:var(--subtle-fill-secondary);--hyperlink-button-background-pressed:var(--subtle-fill-tertiary);--hyperlink-button-background-disabled:var(--subtle-fill-transparent);--hyperlink-button-foreground:rgb(0, 62, 146);--hyperlink-button-foreground-pointer-over:rgb(var(--accent-dark-3));--hyperlink-button-foreground-pressed:rgb(var(--accent-dark-3));--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--subtle-fill-transparent);--hyperlink-button-border-pointer-over:var(--subtle-fill-transparent);--hyperlink-button-border-pressed:var(--subtle-fill-transparent);--hyperlink-button-border-disabled:var(--subtle-fill-transparent);--hyperlink-button-border-thickness:1px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:40px;--toggle-switch-height:20px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-quarternary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-default);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-default);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:12px;--toggle-switch-knob-height:12px;--toggle-switch-knob-offset:3px;--toggle-switch-knob-zoom-pointer-over:-1px;--toggle-switch-knob-active-translation:20px;--toggle-switch-knob-width-pointer-over:14px;--toggle-switch-knob-height-pointer-over:14px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(0, 0, 0, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.1);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgb(243, 243, 243);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(0, 0, 0, 0.0605);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:var(--subtle-fill-secondary);--list-box-item-background-selected-disabled:var(--subtle-fill-secondary);--list-box-item-background-pressed:var(--subtle-fill-tertiary);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-secondary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:var(--accent-default);--menu-flyout-presenter-background:rgb(249, 249, 249);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.059);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-default);--text-box-background-pointer-over:var(--control-fill-color-secondary);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-light-1));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:1px;--text-box-underline-border-thickness-focused:2px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:rgba(213, 213, 213, 0.349);--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(205, 205, 205);--scrollbar-thumb-background-color-hover:rgb(218, 218, 218);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:var(--subtle-fill-secondary);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:var(--subtle-fill-secondary);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:var(--subtle-fill-tertiary);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.302);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(255, 255, 255);--dialog-footer-background-color:rgb(243, 243, 243);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--subtle-fill-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(66, 173, 255);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(66, 255, 132);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(255, 80, 71);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(255, 199, 58);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(157, 155, 255);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(6, 255, 229);--hero-background-image:url("../img/hero/light-theme-tile.png");--hero-background-color:rgba(131, 0, 255, 0.35);--hero-title-color:linear-gradient(to right, #db2777, #e60b0b, #498b13, #0a82d2, #822bc1, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=macos-dark-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:144,192,244;--accent-light-2:106,171,240;--accent-light-1:71,140,246;--accent-base:22,122,229;--accent-dark-1:20,109,204;--accent-dark-2:17,89,167;--accent-dark-3:13,69,130;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(255, 255, 255, 0.158);--system-fill-color-success:rgb(108, 203, 95);--system-fill-color-caution:rgb(252, 225, 0);--system-fill-color-critical:rgb(255, 153, 164);--system-fill-color-neutral:rgba(255, 255, 255, 0.545);--system-fill-color-success-background:rgb(57, 61, 27);--system-fill-color-caution-background:rgb(67, 53, 25);--system-fill-color-critical-background:rgb(68, 39, 38);--system-fill-color-neutral-background:rgba(255, 255, 255, 0.031);--system-fill-color-attention-background:rgba(255, 255, 255, 0.031);--text-on-accent-primary:rgb(0, 0, 0);--text-on-accent-secondary:rgba(0, 0, 0, 0.5);--text-on-accent-disabled:rgba(255, 255, 255, 0.53);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgb(255, 255, 255);--focus-stroke-inner:rgba(0, 0, 0, 0.3);--text-fill-color-primary:rgba(255, 255, 255, 1);--text-fill-color-secondary:rgba(255, 255, 255, 0.8471);--text-fill-color-tertiary:rgba(255, 255, 255, 0.53);--text-fill-color-disabled:rgba(255, 255, 255, 0.36);--text-fill-color-inverse:rgba(0, 0, 0, 0.89);--control-fill-color-default:rgba(255, 255, 255, 0.25);--control-fill-color-secondary:rgba(255, 255, 255, 0.25);--control-fill-color-tertiary:rgba(255, 255, 255, 0.35);--control-fill-color-disabled:rgba(255, 255, 255, 0.125);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(255, 255, 255, 0.15);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.09);--control-alt-fill-color-tertiary:rgba(255, 255, 255, 0.043);--control-alt-fill-color-quarternary:rgba(255, 255, 255, 0.071);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgba(0, 0, 0, 0.06);--control-stroke-color-secondary:rgba(0, 0, 0, 0.094);--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(255, 255, 255, 0.15);--control-strong-stroke-disabled:rgba(255, 255, 255, 0.157);--divider-stroke-color-default:rgba(255, 255, 255, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(255, 255, 255, 0.061);--subtle-fill-tertiary:rgba(255, 255, 255, 0.042);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgb(38, 38, 38);--control-border-color-default:var(--control-stroke-color-secondary) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.1);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(255, 255, 255, 0.05);--card-background-color-secondary:rgba(255, 255, 255, 0.03);--card-hover-background-color:rgba(255, 255, 255, 0.06);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-fill-color-secondary);--stealth-button-background-pressed:var(--control-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-border-color-default);--stealth-button-border-pressed:var(--control-stroke-color-default);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:26px;--toggle-switch-height:15px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-quarternary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:13px;--toggle-switch-knob-height:13px;--toggle-switch-knob-offset:0px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:12px;--toggle-switch-knob-width-pointer-over:13px;--toggle-switch-knob-height-pointer-over:13px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(255, 255, 255, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.3);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgba(32, 32, 32, 0.4);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(255, 255, 255, 0.0605);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-transparent);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgb(44, 44, 44);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.2);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:rgba(255, 255, 255, 0.05);--text-box-background-pointer-over:rgba(255, 255, 255, 0.05);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(118, 118, 118);--scrollbar-thumb-background-color-hover:rgb(169, 170, 170);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.305);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(43, 43, 43);--dialog-footer-background-color:rgb(32, 32, 32);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(0, 120, 212);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(14, 119, 53);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(226, 36, 26);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(204, 146, 0);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(107, 105, 214);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(3, 131, 135);--hero-background-image:url("../img/hero/dark-theme-tile.png");--hero-background-color:rgba(91, 42, 134, 0.5);--hero-title-color:linear-gradient(to right, #db2777, #ec4b4b, #e8c137, #35d49b, #4eb1e0, #a445e8, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=macos-light-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:0,122,255;--accent-light-2:0,122,255;--accent-light-1:0,122,255;--accent-base:0,122,255;--accent-dark-1:0,122,255;--accent-dark-2:0,122,255;--accent-dark-3:0,122,255;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(0, 0, 0, 0.158);--system-fill-color-success:rgb(15, 123, 15);--system-fill-color-caution:rgb(157, 93, 0);--system-fill-color-critical:rgb(196, 43, 28);--system-fill-color-neutral:rgba(0, 0, 0, 0.447);--system-fill-color-success-background:rgb(223, 246, 221);--system-fill-color-caution-background:rgb(255, 244, 206);--system-fill-color-critical-background:rgb(253, 231, 233);--system-fill-color-neutral-background:rgba(0, 0, 0, 0.024);--system-fill-color-attention-background:rgba(246, 246, 246, 0.502);--text-on-accent-primary:rgb(255, 255, 255);--text-on-accent-secondary:rgb(240, 240, 240);--text-on-accent-disabled:rgb(240, 240, 240);--text-on-accent-selected:rgb(240, 240, 240);--focus-stroke-outer:rgba(0, 0, 0, 1);--focus-stroke-inner:rgb(255, 255, 255);--text-fill-color-primary:rgba(0, 0, 0, 0.85);--text-fill-color-secondary:rgba(0, 0, 0, 0.5);--text-fill-color-tertiary:rgba(0, 0, 0, 0.25);--text-fill-color-disabled:rgba(0, 0, 0, 0.25);--text-fill-color-inverse:rgb(255, 255, 255);--control-fill-color-default:rgba(255, 255, 255, 1);--control-fill-color-secondary:rgba(255, 255, 255, 1);--control-fill-color-tertiary:rgba(255, 255, 255, 0.35);--control-fill-color-disabled:rgba(255, 255, 255, 0.5);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(255, 255, 255, 1);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.09);--control-alt-fill-color-tertiary:rgba(0, 0, 0, 0.043);--control-alt-fill-color-quarternary:rgba(0, 0, 0, 0.071);--control-alt-fill-color-disabled:rgba(0, 0, 0, 0.03);--control-stroke-color-default:rgba(0, 0, 0, 0.3);--control-stroke-color-secondary:rgba(0, 0, 0, 0.05);--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(0, 0, 0, 0.15);--control-strong-stroke-disabled:rgba(0, 0, 0, 0.157);--divider-stroke-color-default:rgba(0, 0, 0, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(0, 0, 0, 0.061);--subtle-fill-tertiary:rgba(0, 0, 0, 0.042);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgb(246, 246, 246);--control-border-color-default:var(--control-stroke-color-secondary) var(--control-stroke-color-secondary) var(--control-stroke-color-default) var(--control-stroke-color-secondary);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.1);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(0, 0, 0, 0.020);--card-background-color-secondary:rgba(0, 0, 0, 0.035);--card-hover-background-color:rgba(0, 0, 0, 0.035);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-primary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-border-color-default);--button-border-disabled:var(--control-border-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-alt-fill-color-secondary);--stealth-button-background-pressed:var(--control-alt-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-fill-color-transparent);--stealth-button-border-pressed:var(--control-fill-color-transparent);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:26px;--toggle-switch-height:15px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:13px;--toggle-switch-knob-height:13px;--toggle-switch-knob-offset:0px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:12px;--toggle-switch-knob-width-pointer-over:13px;--toggle-switch-knob-height-pointer-over:13px;--toggle-switch-knob-fill-off:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-off-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(0, 0, 0, 0.1);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.1);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgba(246, 246, 246, 0.6);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(0, 0, 0, 0.05);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-transparent);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-inverse);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgba(246, 246, 246);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.4);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-input-active);--text-box-background-pointer-over:var(--control-fill-color-input-active);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-border-color-default);--text-box-border-pointer-over:var(--control-border-color-default);--text-box-border-focused:var(--control-border-color-default);--text-box-border-disabled:var(--control-border-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(194, 194, 194);--scrollbar-thumb-background-color-hover:rgb(126, 126, 126);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.2);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgba(255, 255, 255, 1);--dialog-footer-background-color:rgb(246, 246, 246);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(66, 173, 255);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(66, 255, 132);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(255, 80, 71);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(255, 199, 58);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(157, 155, 255);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(6, 255, 229);--hero-background-image:url("../img/hero/light-theme-tile.png");--hero-background-color:rgba(131, 0, 255, 0.35);--hero-title-color:linear-gradient(to right, #db2777, #e60b0b, #498b13, #0a82d2, #822bc1, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=linux-dark-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:144,192,244;--accent-light-2:106,171,240;--accent-light-1:71,140,246;--accent-base:22,122,229;--accent-dark-1:20,109,204;--accent-dark-2:17,89,167;--accent-dark-3:13,69,130;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(255, 255, 255, 0.158);--system-fill-color-success:rgb(108, 203, 95);--system-fill-color-caution:rgb(252, 225, 0);--system-fill-color-critical:rgb(255, 153, 164);--system-fill-color-neutral:rgba(255, 255, 255, 0.545);--system-fill-color-success-background:rgb(57, 61, 27);--system-fill-color-caution-background:rgb(67, 53, 25);--system-fill-color-critical-background:rgb(68, 39, 38);--system-fill-color-neutral-background:rgba(255, 255, 255, 0.031);--system-fill-color-attention-background:rgba(255, 255, 255, 0.031);--text-on-accent-primary:rgb(0, 0, 0);--text-on-accent-secondary:rgba(0, 0, 0, 0.5);--text-on-accent-disabled:rgba(255, 255, 255, 0.53);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgb(255, 255, 255);--focus-stroke-inner:rgba(0, 0, 0, 0.3);--text-fill-color-primary:rgba(255, 255, 255, 1);--text-fill-color-secondary:rgba(255, 255, 255, 0.8471);--text-fill-color-tertiary:rgba(255, 255, 255, 0.53);--text-fill-color-disabled:rgba(255, 255, 255, 0.36);--text-fill-color-inverse:rgba(0, 0, 0, 0.89);--control-fill-color-default:rgb(55, 55, 55);--control-fill-color-secondary:rgb(60, 60, 60);--control-fill-color-tertiary:rgb(21, 21, 21);--control-fill-color-disabled:rgb(42, 42, 42);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgb(39, 39, 39);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgb(74, 74, 74);--control-alt-fill-color-tertiary:rgba(255, 255, 255, 0.043);--control-alt-fill-color-quarternary:rgba(255, 255, 255, 0.071);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgb(24, 24, 24);--control-stroke-color-secondary:transparent;--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(255, 255, 255, 0.15);--control-strong-stroke-disabled:rgba(255, 255, 255, 0.157);--divider-stroke-color-default:rgba(255, 255, 255, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgb(49, 49, 49);--subtle-fill-tertiary:rgb(55, 55, 55);--subtle-fill-disabled:transparent;--background-color:rgb(39, 39, 39);--layer-fill-color-default:rgb(44, 44, 44);--control-border-color-default:var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.4);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgb(39, 39, 39);--card-background-color-secondary:rgb(39, 39, 39);--card-hover-background-color:rgb(49, 49, 49);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-fill-color-secondary);--stealth-button-background-pressed:var(--control-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-border-color-default);--stealth-button-border-pressed:var(--control-stroke-color-default);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:51px;--toggle-switch-height:26px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:0px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-secondary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:20px;--toggle-switch-knob-width:22px;--toggle-switch-knob-height:22px;--toggle-switch-knob-offset:2px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:25px;--toggle-switch-knob-width-pointer-over:22px;--toggle-switch-knob-height-pointer-over:22px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(255, 255, 255, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.3);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-default);--navigation-view-flyout-background-color:rgba(32, 32, 32, 0.4);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(255, 255, 255, 0.0605);--sidebar-item-margin:0px;--list-box-item-radius:0px;--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgb(29, 29, 29);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.2);--menu-flyout-presenter-backdrop-filter:none;--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-input-active);--text-box-background-pointer-over:var(--control-fill-color-input-active);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(118, 118, 118);--scrollbar-thumb-background-color-hover:rgb(169, 170, 170);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.305);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(43, 43, 43);--dialog-footer-background-color:rgb(32, 32, 32);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:transparent;--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(0, 120, 212);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(14, 119, 53);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(226, 36, 26);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(204, 146, 0);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(107, 105, 214);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(3, 131, 135);--hero-background-image:url("../img/hero/dark-theme-tile.png");--hero-background-color:rgba(91, 42, 134, 0.5);--hero-title-color:linear-gradient(to right, #db2777, #ec4b4b, #e8c137, #35d49b, #4eb1e0, #a445e8, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=linux-light-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:0,122,255;--accent-light-2:0,122,255;--accent-light-1:0,122,255;--accent-base:0,122,255;--accent-dark-1:0,122,255;--accent-dark-2:0,122,255;--accent-dark-3:0,122,255;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(0, 0, 0, 0.158);--system-fill-color-success:rgb(15, 123, 15);--system-fill-color-caution:rgb(157, 93, 0);--system-fill-color-critical:rgb(196, 43, 28);--system-fill-color-neutral:rgba(0, 0, 0, 0.447);--system-fill-color-success-background:rgb(223, 246, 221);--system-fill-color-caution-background:rgb(255, 244, 206);--system-fill-color-critical-background:rgb(253, 231, 233);--system-fill-color-neutral-background:rgba(0, 0, 0, 0.024);--system-fill-color-attention-background:rgba(246, 246, 246, 0.502);--text-on-accent-primary:rgb(255, 255, 255);--text-on-accent-secondary:rgb(240, 240, 240);--text-on-accent-disabled:rgb(240, 240, 240);--text-on-accent-selected:rgb(240, 240, 240);--focus-stroke-outer:rgba(0, 0, 0, 1);--focus-stroke-inner:rgb(255, 255, 255);--text-fill-color-primary:rgba(0, 0, 0, 0.85);--text-fill-color-secondary:rgba(0, 0, 0, 0.5);--text-fill-color-tertiary:rgba(0, 0, 0, 0.25);--text-fill-color-disabled:rgba(0, 0, 0, 0.25);--text-fill-color-inverse:rgb(255, 255, 255);--control-fill-color-default:rgb(255, 255, 255);--control-fill-color-secondary:rgb(245, 245, 245);--control-fill-color-tertiary:rgb(214, 214, 214);--control-fill-color-disabled:rgb(252, 252, 252);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(255, 255, 255, 1);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgb(204, 204, 204);--control-alt-fill-color-tertiary:rgba(0, 0, 0, 0.043);--control-alt-fill-color-quarternary:rgba(0, 0, 0, 0.071);--control-alt-fill-color-disabled:rgba(0, 0, 0, 0.03);--control-stroke-color-default:rgb(199, 199, 199);--control-stroke-color-secondary:transparent;--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(0, 0, 0, 0.15);--control-strong-stroke-disabled:rgba(0, 0, 0, 0.157);--divider-stroke-color-default:rgba(0, 0, 0, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgb(222, 222, 222);--subtle-fill-tertiary:rgb(222, 222, 222);--subtle-fill-disabled:transparent;--background-color:rgb(255, 255, 255);--layer-fill-color-default:rgb(250, 250, 250);--control-border-color-default:var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgb(215, 215, 215);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgb(255, 255, 255);--card-background-color-secondary:rgb(255, 255, 255);--card-hover-background-color:rgb(246, 246, 246);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-primary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-border-color-default);--button-border-disabled:var(--control-border-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-alt-fill-color-secondary);--stealth-button-background-pressed:var(--control-alt-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-fill-color-transparent);--stealth-button-border-pressed:var(--control-fill-color-transparent);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:51px;--toggle-switch-height:26px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:0px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-secondary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:20px;--toggle-switch-knob-width:22px;--toggle-switch-knob-height:22px;--toggle-switch-knob-offset:2px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:25px;--toggle-switch-knob-width-pointer-over:22px;--toggle-switch-knob-height-pointer-over:22px;--toggle-switch-knob-fill-off:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-off-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(0, 0, 0, 0.1);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.1);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-default);--navigation-view-flyout-background-color:rgba(246, 246, 246, 0.6);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(0, 0, 0, 0.05);--sidebar-item-margin:0px;--list-box-item-radius:0px;--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-inverse);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgba(246, 246, 246);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.4);--menu-flyout-presenter-backdrop-filter:none;--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-input-active);--text-box-background-pointer-over:var(--control-fill-color-input-active);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-border-color-default);--text-box-border-pointer-over:var(--control-border-color-default);--text-box-border-focused:var(--control-border-color-default);--text-box-border-disabled:var(--control-border-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(194, 194, 194);--scrollbar-thumb-background-color-hover:rgb(126, 126, 126);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.2);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgba(255, 255, 255, 1);--dialog-footer-background-color:rgb(246, 246, 246);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:transparent;--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(66, 173, 255);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(66, 255, 132);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(255, 80, 71);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(255, 199, 58);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(157, 155, 255);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(6, 255, 229);--hero-background-image:url("../img/hero/light-theme-tile.png");--hero-background-color:rgba(131, 0, 255, 0.35);--hero-title-color:linear-gradient(to right, #db2777, #e60b0b, #498b13, #0a82d2, #822bc1, #db2777) 0 0/5000% 5000% no-repeat}div{color:currentColor}*,.main-layout{color:var(--text-fill-color-primary)}.theme-transition,.theme-transition *,.theme-transition :after,.theme-transition :before{transition-property:color,background-color;transition-timing-function:ease-out;transition-duration:0s;transition-delay:0!important}@keyframes entrance-theme-transition-key-frames{from{opacity:0;transform:translateY(150px)}to{opacity:1;transform:translateY(0)}}.entrance-theme-transition{animation-name:entrance-theme-transition-key-frames;animation-duration:.15s;animation-timing-function:ease-out}*,::after,::before{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-user-select:none;user-select:none;-webkit-user-drag:none;user-drag:none}body,html{padding:0;margin:0;overflow:hidden;--popover-zindex:90000}#blazor-error-ui{background:#ffffe0;bottom:0;box-shadow:0 -1px 2px rgba(0,0,0,.2);display:none;left:0;padding:.6rem 1.25rem .7rem;position:fixed;width:100%;z-index:2147483647}#blazor-error-ui .dismiss{cursor:pointer;position:absolute;right:.75rem;top:.5rem}.blazor-error-boundary{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) 1rem/1.8rem no-repeat,#b32121;padding:1rem 1rem 1rem 3.7rem;color:#fff}.blazor-error-boundary::after{content:"An error has occurred."}.text-box{user-select:none;-webkit-user-select:none;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;box-sizing:border-box;border:none;outline:0;cursor:unset;margin:0;flex:1 1 auto;inline-size:100%;min-block-size:30px;padding-inline:10px;border-radius:var(--control-corner-radius);color:var(--text-box-foreground);background-color:transparent;pointer-events:auto}.text-box:focus-visible{box-shadow:none}.text-box::placeholder{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;user-select:none;-webkit-user-select:none;color:var(--text-box-placeholder-foreground)}.text-box::selection{background:var(--text-box-selection-highlight-color);color:var(--text-box-selection-color)}.text-box::-webkit-search-cancel-button,.text-box::-webkit-search-decoration,.text-box::-webkit-search-results-button,.text-box::-webkit-search-results-decoration{-webkit-appearance:none}.text-box[type=number]{-webkit-appearance:textfield;-moz-appearance:textfield;appearance:textfield}.text-box[type=number]::-webkit-inner-spin-button,.text-box[type=number]::-webkit-outer-spin-button{-webkit-appearance:none}.text-box[type=search]{-webkit-appearance:none}.text-box::-ms-reveal{display:none}.text-box.disabled{color:var(--text-fill-color-disabled)}.text-box.disabled::placeholder{color:var(--text-box-placeholder-foreground-disabled)}.text-box-with-header{display:block;position:relative}.text-box-with-header .text-box-header{margin-bottom:4px}.text-box-container{display:flex;align-items:center;cursor:text;position:relative;border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--text-box-background);border-width:1px;border-style:solid;border-color:var(--text-box-border)}.text-box-container:hover{color:var(--text-box-foreground-pointer-over);background-color:var(--text-box-background-pointer-over);border-color:var(--text-box-border-pointer-over)}.text-box-container:hover .text-box::placeholder{color:var(--text-box-placeholder-foreground-pointer-over)}.text-box-container.disabled{cursor:default;color:var(--text-box-foreground-disabled);background-color:var(--text-box-background-disabled);border-color:var(--text-box-border-disabled)}.text-box-container.disabled .text-box-underline{display:none}.text-box-container.disabled .text-box::placeholder{color:var(--text-box-placeholder-foreground-disabled)}.text-box-container.is-context-menu-opened,.text-box-container:focus-within{color:var(--text-box-foreground-focused);background-color:var(--text-box-background-focused);border-color:var(--text-box-border-focused)}.text-box-container.is-context-menu-opened .text-box::placeholder,.text-box-container:focus-within .text-box::placeholder{color:var(--text-box-placeholder-foreground-focused)}.text-box-container.is-context-menu-opened .text-box-underline::after,.text-box-container:focus-within .text-box-underline::after{border-bottom:var(--text-box-underline-border-thickness-focused) solid var(--accent-default)}.text-box-container.is-context-menu-opened .text-box-clear-button,.text-box-container:focus-within .text-box-clear-button{display:flex}.text-box-underline{position:absolute;inset-inline-start:-1px;inset-block-start:-1px;inline-size:calc(100% + var(--text-box-underline-border-thickness-focused));block-size:calc(100% + var(--text-box-underline-border-thickness-focused));pointer-events:none;border-radius:var(--control-corner-radius);overflow:hidden}.text-box-underline::after{content:"";box-sizing:border-box;position:absolute;inset-block-end:0;inset-inline-start:0;inline-size:100%;block-size:100%;border-bottom:var(--text-box-underline-border-thickness) solid var(--control-strong-stroke-default)}.text-box-buttons{display:flex;align-items:center;cursor:default;flex:0 0 auto}.text-box-buttons button{margin-inline-start:6px;height:22px;min-block-size:22px;width:22px;padding:0}.text-box-buttons button:first-of-type{margin-inline-start:0}.text-box-buttons button:last-of-type{margin-inline-end:4px}.text-box-buttons .font-icon{height:18px;width:20px}.text-box-buttons .text-box-clear-button{display:none}div[data-compactmode] .text-box{min-block-size:24px;padding-inline:6px 2px}.sidebar-searchbar-result-item{display:grid;grid-template-columns:16px 1fr;grid-template-rows:1fr;gap:0 12px;grid-template-areas:"icon title"}.sidebar-searchbar-result-item .sidebar-searchbar-result-item-icon{grid-area:icon}.sidebar-searchbar-result-item .sidebar-searchbar-result-item-title{grid-area:title}.main-layout{margin:0;padding:0;position:absolute;height:100%;width:100%;background-color:var(--background-color)}@-webkit-keyframes hero-bg-scrolling{0%{background-position:0 196px}}@keyframes hero-bg-scrolling{0%{background-position:0 196px}}@keyframes hero-title-shade{to{background-position:100% 0}}.hero{height:250px;width:100%;position:absolute;top:0;right:0;bottom:0;left:0;border-top-left-radius:var(--overlay-corner-radius);mask:linear-gradient(0deg,transparent,#fff 65%);mask-composite:intersect}.hero::before{content:"";position:fixed;width:2000%;height:2000%;top:-1000%;left:-1000%;z-index:-1;background:var(--hero-background-image) repeat 0 0;background-color:var(--hero-background-color);transform:rotateX(15deg) rotateZ(-15deg) skewX(15deg);transform-style:preserve-3d;-webkit-animation:20s linear infinite hero-bg-scrolling;animation:20s linear infinite hero-bg-scrolling}.hero-title{display:flex;font-weight:900!important}.hero-title span:last-of-type{display:inline-block;background:var(--hero-title-color);background-clip:text;color:transparent;animation:10s linear infinite hero-title-shade}.tool-group-parallax{height:100vh;overflow-x:hidden;perspective:1px}.tool-group-grid-view{padding:40px;transform:translateZ(0)}.tool-group-grid-view .tool-group-grid-view-item{height:134px;max-height:134px;padding:16px;display:grid;grid-template-columns:min-content 1fr min-content;grid-template-rows:min-content 1fr;gap:0 0;grid-template-areas:"icon title buttons" "icon description description"}.tool-group-grid-view .tool-group-grid-view-item .icon{grid-area:icon;display:grid;height:100px;width:100px;padding:12px;background-color:var(--card-background-color);border-radius:var(--control-corner-radius)}.tool-group-grid-view .tool-group-grid-view-item .title{grid-area:title;margin-left:16px;max-height:75px;word-wrap:break-word;text-overflow:ellipsis;color:var(--text-fill-color-primary);overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.tool-group-grid-view .tool-group-grid-view-item .description{grid-area:description;margin-left:16px;margin-top:2px;margin-right:8px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical}.tool-group-grid-view .tool-group-grid-view-item .buttons{grid-area:buttons;margin-left:16px;top:0}.tool-group-grid-view .tool-group-grid-view-item .buttons .button{height:24px;min-block-size:24px;width:24px;padding:0}.tool-group-grid-view .tool-group-grid-view-item .buttons .fonticon{text-align:center;font-size:16px;width:21px}div[data-compactmode] .hero{height:200px}div[data-compactmode] .tool-group-grid-view{padding:8px 16px}.tool-page-content{padding:40px;height:inherit}div[data-compactmode] .tool-page-content{padding:8px 16px}
\ No newline at end of file
+.ui-card-presenter>div{width:100%}.ui-data-grid-presenter{border:2px dashed transparent;border-radius:var(--overlay-corner-radius)}.ui-data-grid-presenter-command-bar{min-height:32px}div[data-compactmode] .ui-data-grid-presenter-command-bar{min-height:26px}.ui-file-selector{border:2px dashed var(--ui-file-selector-stroke-color);border-radius:var(--overlay-corner-radius);background-color:var(--ui-file-selector-background-color);pointer-events:auto}.ui-file-selector-content{padding:24px}.ui-file-selector.dragging{border-color:var(--ui-file-selector-dragging-stroke-color);background-color:var(--ui-file-selector-dragging-background-color)}.ui-file-selector.dragging *{pointer-events:none!important}div[data-compactmode] .ui-file-selector-content{padding:8px}.ui-image-viewer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--card-foreground);border:var(--card-border-thickness);border-color:var(--card-border);border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--card-background-color);min-height:72px;padding-block:16px;padding-inline:11px}.ui-image-viewer img{object-fit:scale-down;object-position:center;display:block;position:relative;width:100%;height:100%;max-height:inherit;min-height:min-content;max-width:inherit;min-width:100%}div[data-compactmode] .ui-image-viewer{padding-block:6px;padding-inline:6px}.ui-multiline-text-input-highlighted-text-span-default{background-color:var(--text-box-selection-highlight-color);color:var(--text-box-selection-color)}.ui-multiline-text-input-highlighted-text-span-blue{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-blue);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-green{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-green);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-red{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-red);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-yellow{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-yellow);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-purple{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-purple);color:var(--text-box-selection-color-blue)}.ui-multiline-text-input-highlighted-text-span-teal{background-color:var(--ui-multiline-text-input-highlighted-text-span-background-color-teal);color:var(--text-box-selection-color-blue)}.ui-setting-group-presenter .ui-setting-presenter{background-color:var(--ui-setting-group-presenter-inner-ui-setting-background-color);border-color:var(--ui-setting-group-presenter-inner-ui-setting-border-color)}.ui-setting-group-presenter .card.expander-card .card-control.child-of-expander{margin-right:0}.ui-text-input-wrapper{border:2px dashed transparent;border-radius:var(--overlay-corner-radius)}.ui-text-input-wrapper-separator{border-left:1px solid var(--divider-stroke-color-default);width:1px;height:100%;margin-left:2px;margin-right:2px}.ui-text-input-wrapper-centered-progress-bar{position:absolute;top:50%;left:0;transform:translate(0,-50%);bottom:50%;right:0;height:fit-content}.ui-text-input-wrapper-icon{height:20px;width:20px}.ui-text-input-wrapper.dragging{border-color:var(--ui-file-selector-dragging-stroke-color);background-color:var(--ui-file-selector-dragging-background-color)}.ui-text-input-wrapper.dragging *{pointer-events:none!important}div[data-compactmode] .ui-text-input-wrapper-icon{height:16px;width:16px}.ui-web-view-title{height:20px!important;margin-top:14px}.ui-web-view-frame{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--card-foreground);border:var(--card-border-thickness);border-color:var(--card-border);border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--card-background-color);min-height:72px;width:100%;height:100%;pointer-events:all}div[data-compactmode] .ui-web-view-title{height:20px!important;margin-top:7px}button{display:inline-flex;justify-content:center;align-items:center;user-select:none;-webkit-user-select:none;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;position:relative;box-sizing:border-box;min-block-size:32px;padding-block:4px 6px;padding-inline:11px;text-decoration:none;border:none;outline:0;cursor:default;border-radius:var(--control-corner-radius);transition:background 83ms;width:inherit;height:inherit;min-width:fit-content;pointer-events:auto}button.type-neutral{border:var(--button-border-thickness);border-color:var(--button-border);background-color:var(--button-background);color:var(--button-foreground);background-clip:padding-box}button.type-neutral:hover{border-color:var(--button-border-pointer-over);background-color:var(--button-background-pointer-over);color:var(--button-foreground-pointer-over)}button.type-neutral:active{border-color:var(--button-border-pressed);background-color:var(--button-background-pressed);color:var(--button-foreground-pressed)}button.type-neutral.disabled{border-color:var(--button-border-disabled);background-color:var(--button-background-disabled);color:var(--button-foreground-disabled)}button.type-accent{border:var(--accent-button-border-thickness);border-color:var(--accent-button-border);background-color:var(--accent-button-background);color:var(--accent-button-foreground);transition:border-color 83ms}button.type-accent:hover{border-color:var(--accent-button-border-pointer-over);background-color:var(--accent-button-background-pointer-over);color:var(--accent-button-foreground-pointer-over)}button.type-accent:active{border-color:var(--accent-button-border-pressed);background-color:var(--accent-button-background-pressed);color:var(--accent-button-foreground-pressed)}button.type-accent.disabled{border-color:var(--accent-button-border-disabled);background-color:var(--accent-button-background-disabled);color:var(--accent-button-foreground-disabled)}button.type-stealth{border:var(--stealth-button-border-thickness);border-color:var(--stealth-button-border);background-color:var(--stealth-button-background);color:var(--stealth-button-foreground);background-clip:padding-box}button.type-stealth:hover{border-color:var(--stealth-button-border-pointer-over);background-color:var(--stealth-button-background-pointer-over);color:var(--stealth-button-foreground-pointer-over)}button.type-stealth:active{border-color:var(--stealth-button-border-pressed);background-color:var(--stealth-button-background-pressed);color:var(--stealth-button-foreground-pressed)}button.type-stealth.disabled{border-color:var(--stealth-button-border-disabled);background-color:var(--stealth-button-background-disabled);color:var(--stealth-button-foreground-disabled)}button.type-hyperlink{border:var(--hyperlink-button-border-thickness);border-color:var(--hyperlink-button-border);background-color:var(--hyperlink-button-background);color:var(--hyperlink-button-foreground);background-clip:padding-box}button.type-hyperlink:hover{border-color:var(--hyperlink-button-border-pointer-over);background-color:var(--hyperlink-button-background-pointer-over);color:var(--hyperlink-button-foreground-pointer-over)}button.type-hyperlink:active{border-color:var(--hyperlink-button-border-pressed);background-color:var(--hyperlink-button-background-pressed);color:var(--hyperlink-button-foreground-pressed)}button.type-hyperlink.disabled{border-color:var(--hyperlink-button-border-disabled);background-color:var(--hyperlink-button-background-disabled);color:var(--hyperlink-button-foreground-disabled)}button.disabled{pointer-events:none}div[data-compactmode] button{min-block-size:24px;padding-block:2px 2px}.combo-box{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;pointer-events:auto}.combo-box-with-header{display:flex;flex-direction:column;position:relative}.combo-box-with-header .combo-box-header{margin-bottom:4px}.combo-box-button .arrow-down-icon{margin-left:4px;transition-duration:.2s;transition-property:transform;color:currentColor!important}.combo-box-button:active .arrow-down-icon{transform:translateY(2px);color:currentColor!important}.combo-box-list-box{margin:0;padding:0;max-height:400px}.checkbox{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;margin:0;border-width:1px;border-style:solid;border-color:var(--check-box-check-background-stroke-unchecked);border-radius:var(--check-box-check-corner-radius);outline:0;background-clip:padding-box;background-color:var(--check-box-check-background-fill-unchecked);color:var(--check-box-check-glyph-foreground-unchecked);appearance:none;inline-size:20px;block-size:20px;pointer-events:auto}.checkbox:hover{background-color:var(--check-box-check-background-fill-unchecked-pointer-over);border-color:var(--check-box-check-background-stroke-unchecked-pointer-over);color:var(--check-box-check-glyph-foreground-unchecked-pointer-over)}.checkbox:active{border-color:var(--check-box-check-background-stroke-unchecked-pressed);background-color:var(--check-box-check-background-fill-unchecked-pressed);color:var(--check-box-check-glyph-foreground-unchecked-pressed)}.checkbox:active+.checkbox-glyph{color:var(--text-on-accent-secondary)}.checkbox:disabled{border-color:var(--check-box-check-background-stroke-unchecked-disabled);background-color:var(--check-box-check-background-fill-unchecked-disabled);color:var(--check-box-check-glyph-foreground-unchecked-disabled);pointer-events:none}.checkbox:checked{border:none;border-color:var(--check-box-check-background-stroke-checked);background-color:var(--check-box-check-background-fill-checked);color:var(--check-box-check-glyph-foreground-checked)}.checkbox:checked:hover{border-color:var(--check-box-check-background-stroke-checked-pointer-over);background-color:var(--check-box-check-background-fill-checked-pointer-over);color:var(--check-box-check-glyph-foreground-checked-pointer-over)}.checkbox:checked:active{border-color:var(--check-box-check-background-stroke-checked-pressed);background-color:var(--check-box-check-background-fill-checked-pressed);color:var(--check-box-check-glyph-foreground-checked-pressed)}.checkbox:checked:disabled{border-color:var(--check-box-check-background-stroke-checked-disabled);background-color:var(--check-box-check-background-fill-checked-disabled);color:var(--check-box-check-glyph-foreground-checked-disabled)}.checkbox:checked:disabled+.checkbox-glyph{color:var(--text-on-accent-disabled)}.checkbox:checked+.checkbox-glyph .path-checkmark{transition:stroke-dashoffset 250ms cubic-bezier(.55,0,0,1);stroke-dashoffset:0}.checkbox-container{display:flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--check-box-foreground)!important;user-select:none;min-block-size:32px}.checkbox-container>span{padding-inline-start:8px}.checkbox-container.disabled{color:var(--check-box-foreground-disabled)!important}.checkbox-inner{display:flex;justify-content:center;align-items:center;position:relative}.checkbox-glyph{pointer-events:none;position:absolute;color:var(--check-box-check-glyph-foreground-unchecked);inline-size:12px;block-size:12px}.checkbox-glyph path{transform-origin:center}.checkbox-glyph .path-checkmark{transform:scale(1.2);stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:20.5;stroke-dashoffset:20.5}.drop-down-list{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;pointer-events:auto}.drop-down-list-with-header{display:flex;flex-direction:column;position:relative}.drop-down-list-with-header .drop-down-list-header{margin-bottom:4px}.drop-down-list-button .arrow-down-icon{margin-left:4px;transition-duration:.2s;transition-property:transform;color:currentColor!important}.drop-down-list-button:active .arrow-down-icon{transform:translateY(2px);color:currentColor!important}.drop-down-list-drop-down{margin-top:4px!important}.drop-down-list-drop-down[data-popover-flip=flipped]{margin-top:-4px!important}.radio-button{display:inline-flex;justify-content:center;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;position:relative;margin:0;border-width:1px;border-style:solid;border-color:var(--radio-button-outer-ellipse-stroke);border-radius:20px;outline:0;background-clip:padding-box;background-color:var(--radio-button-outer-ellipse-fill);appearance:none;inline-size:20px;block-size:20px;pointer-events:auto}.radio-button::before{content:"";inline-size:4px;block-size:4px;visibility:hidden;position:absolute;border-radius:12px;background-color:var(--radio-button-check-glyph-fill)}.radio-button:hover{border-color:var(--radio-button-outer-ellipse-stroke-pointer-over);background-color:var(--radio-button-outer-ellipse-fill-pointer-over)}.radio-button:hover+span{color:var(--radio-button-foreground-pointer-over)}.radio-button:active{border-color:var(--radio-button-outer-ellipse-stroke-pressed);background-color:var(--radio-button-outer-ellipse-fill-pressed)}.radio-button:active::before{transition:250ms cubic-bezier(0,0,0,1);visibility:visible;inline-size:10px;block-size:10px}.radio-button:active+span{color:var(--radio-button-foreground-pressed)}.radio-button:disabled{border-color:var(--radio-button-outer-ellipse-stroke-disabled);background-color:var(--radio-button-outer-ellipse-fill-disabled)}.radio-button:disabled::before{visibility:hidden}.radio-button:disabled+span{color:var(--radio-button-foreground-disabled)}.radio-button:checked{border:none;background-color:var(--radio-button-outer-ellipse-checked-fill)}.radio-button:checked::before{visibility:visible;transition:250ms cubic-bezier(0,0,0,1);box-shadow:0 0 0 1px var(--radio-button-check-glyph-stroke);inline-size:12px;block-size:12px}.radio-button:checked:hover{background-color:var(--radio-button-outer-ellipse-checked-fill-pointer-over)}.radio-button:checked:hover::before{inline-size:14px;block-size:14px}.radio-button:checked:active{background-color:var(--radio-button-outer-ellipse-checked-fill-pressed)}.radio-button:checked:active::before{inline-size:10px;block-size:10px}.radio-button:checked:disabled{background-color:var(--radio-button-outer-ellipse-checked-fill-disabled)}.radio-button:checked:disabled::before{box-shadow:none;inline-size:12px;block-size:12px}.radio-button-container{display:flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--radio-button-foreground);background-color:var(--radio-button-background);user-select:none;min-block-size:32px}.radio-button-container:hover{background-color:var(--radio-button-background-pointer-over)}.radio-button-container:hover>span{color:var(--radio-button-foreground-pointer-over)!important}.radio-button-container:active{background-color:var(--radio-button-background-pressed)}.radio-button-container:active>span{color:var(--radio-button-foreground-pressed)!important}.radio-button-container.disabled{background-color:var(--radio-button-background-disabled)}.radio-button-container.disabled>span{color:var(--radio-button-foreground-disabled)!important}.radio-button-container>span{padding-inline-start:8px}.data-grid{position:relative;width:100%;flex:1;border-collapse:collapse;border-spacing:0;pointer-events:auto}.data-grid-container{position:relative;width:100%;height:100%;flex:1;border-radius:var(--control-corner-radius);background-clip:padding-box;border-width:1px;border-style:solid;border-color:var(--data-grid-border)}.data-grid-underline{position:absolute;inset-inline-start:-1px;inset-block-start:-1px;inline-size:calc(100% + 2px);block-size:calc(100% + 2px);pointer-events:none;border-radius:var(--control-corner-radius);overflow:hidden}.data-grid-underline::after{content:"";box-sizing:border-box;position:absolute;inset-block-end:0;inset-inline-start:0;inline-size:100%;block-size:100%;border-bottom:1px solid var(--control-strong-stroke-default)}.data-grid-resizer{position:absolute;top:0;right:0;width:8px;cursor:col-resize;user-select:none}.data-grid thead th{display:table-cell;position:relative;color:var(--data-grid-column-header-foreground);background-color:var(--data-grid-column-header-background);border-right:1px solid var(--control-stroke-color-default);border-bottom:1px solid var(--control-stroke-color-default)}.data-grid thead th:hover{background-color:var(--data-grid-column-header-background-pointer-over)}.data-grid thead th:active{background-color:var(--data-grid-column-header-background-pressed)}.data-grid tbody tr{color:var(--data-grid-row-foreground);background-color:var(--data-grid-row-background)}.data-grid tbody tr:hover{background-color:var(--data-grid-row-background-pointer-over)}.data-grid tbody tr.odd{background-color:var(--data-grid-row-odd-background)}.data-grid tbody tr.odd:hover{background-color:var(--data-grid-row-odd-background-pointer-over)}.data-grid tbody tr.selected{background-color:var(--data-grid-row-selected-background)!important;color:var(--data-grid-row-selected-foreground)!important}.data-grid tbody tr.selected:hover{background-color:var(--data-grid-row-selected-background-pointer-over)!important;color:var(--data-grid-row-selected-foreground-pointer-over)!important}.data-grid td{color:currentColor}.toggle-switch{display:inline-flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;position:relative;margin:0;border:var(--toggle-switch-outer-border-stroke-thickness) solid var(--toggle-switch-stroke-off);border-radius:20px;outline:0;background-color:var(--toggle-switch-fill-off);appearance:none;inline-size:var(--toggle-switch-width);block-size:var(--toggle-switch-height);pointer-events:auto}.toggle-switch::before{content:"";position:absolute;border-radius:var(--toggle-switch-knob-radius);background-color:var(--toggle-switch-knob-fill-off);transition:transform 167ms ease-in-out,height 167ms cubic-bezier(0,0,0,1),width 167ms cubic-bezier(0,0,0,1),margin 167ms cubic-bezier(0,0,0,1),background 167ms linear;inset-inline-start:var(--toggle-switch-knob-offset);inline-size:var(--toggle-switch-knob-width);block-size:var(--toggle-switch-knob-height)}.toggle-switch:hover{border-color:var(--toggle-switch-stroke-off-pointer-over);background-color:var(--toggle-switch-fill-off-pointer-over)}.toggle-switch:hover::before{inline-size:var(--toggle-switch-knob-width-pointer-over);block-size:var(--toggle-switch-knob-height-pointer-over)}.toggle-switch:active{border-color:var(--toggle-switch-stroke-off-pressed);background-color:var(--toggle-switch-fill-off-pressed)}.toggle-switch:active::before{inline-size:var(--toggle-switch-knob-width-pointer-over);block-size:var(--toggle-switch-knob-height-pointer-over)}.toggle-switch:disabled{border-color:var(--toggle-switch-stroke-off-disabled);background-color:var(--toggle-switch-fill-off-disabled)}.toggle-switch:disabled::before{margin:0!important;background-color:var(--toggle-switch-knob-fill-off-disabled);box-shadow:none;inline-size:var(--toggle-switch-knob-width);block-size:var(--toggle-switch-knob-height)}.toggle-switch:disabled+span{color:var(--toggle-switch-foreground-disabled)!important}.toggle-switch:checked{border:var(--toggle-switch-on-stroke-thickness) solid var(--toggle-switch-stroke-on);background-color:var(--toggle-switch-fill-on)}.toggle-switch:checked::before{background-color:var(--toggle-switch-knob-fill-on);box-shadow:0 0 0 1px solid var(--toggle-switch-knob-stroke-on);transform:translateX(var(--toggle-switch-knob-active-translation))}.toggle-switch:checked:hover{border-color:var(--toggle-switch-stroke-on-pointer-over);background-color:var(--toggle-switch-fill-on-pointer-over)}.toggle-switch:checked:hover::before{margin-inline-start:var(--toggle-switch-knob-zoom-pointer-over)}.toggle-switch:checked:active{border-color:var(--toggle-switch-stroke-on-pressed);background-color:var(--toggle-switch-fill-on-pressed)}.toggle-switch:checked:active::before{margin-inline-start:var(--toggle-switch-knob-zoom-pointer-over)}.toggle-switch:checked:disabled{border-color:var(--toggle-switch-stroke-on-disabled);background-color:var(--toggle-switch-fill-on-disabled)}.toggle-switch:checked:disabled::before{box-shadow:none;background-color:var(--toggle-switch-knob-fill-on-disabled)}.toggle-switch-container{display:flex;align-items:center;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--toggle-switch-foreground)!important;user-select:none;min-block-size:32px}.toggle-switch-container>span{padding-inline-end:8px}.grid-view{position:relative;width:100%;height:100%;flex:1;pointer-events:auto}.grid-view .header{margin-bottom:24px}.grid-view .footer{margin-top:24px}.grid-view .grid-view-group{position:relative}.grid-view .grid-view-group .grid-view-group-header{position:sticky;top:0;z-index:10;background-color:transparent;border-bottom:1px solid var(--grid-view-header-border-line);padding-bottom:8px;margin-top:8px;margin-bottom:4px}.grid-view .grid-view-group .grid-view-items-container{clip-path:none;display:flex!important;gap:12px;flex-wrap:wrap!important;padding:0;margin:16px 0 0}.grid-view .grid-view-group .grid-view-items-container .grid-view-item{background-color:var(--grid-view-item-background);border-style:solid;border-width:1px;border-color:var(--grid-view-item-stroke);border-radius:var(--control-corner-radius);list-style-type:none}.grid-view .grid-view-group .grid-view-items-container .grid-view-item .grid-view-item-hover-filter:hover{background-color:var(--grid-view-item-background-hover);height:100%}.list-box{margin:0;padding:0;pointer-events:auto}.list-box-item{display:flex;align-items:center;position:relative;box-sizing:border-box;flex:0 0 auto;margin:3px;padding-inline:12px;border-radius:var(--list-box-item-radius);outline:0;background-color:var(--list-box-item-background);color:var(--list-box-item-foreground);cursor:default;user-select:none;-webkit-user-select:none;min-block-size:34px;text-decoration:none;pointer-events:auto}.list-box-item::before{content:"";position:absolute;border-radius:3px;background-color:var(--list-box-item-selection-indicator);transition:transform 167ms cubic-bezier(0,0,0,1);opacity:0;inset-inline-start:0;inline-size:3px;min-block-size:16px;transform:scaleY(0)}.list-box-item.selected::before{transform:scaleY(1);opacity:1}.list-box-item:hover{background-color:var(--list-box-item-background-pointer-over)}.list-box-item.selected{background-color:var(--list-box-item-background-selected);color:var(--list-box-item-foreground-selected)!important}.list-box-item.selected *{color:var(--list-box-item-foreground-selected)!important}.list-box-item:active{background-color:var(--list-box-item-background-pressed);color:var(--list-box-item-foreground-pressed)}.list-box-item:active::before{transform:scaleY(.625)}.list-box-item.disabled{background-color:var(--list-box-item-background-disabled);color:var(--list-box-item-foreground-disabled);pointer-events:none}.list-box-item.disabled.selected{background-color:var(--list-box-item-background-selected-disabled)}.list-box-item.disabled.selected::before{background-color:var(--accent-disabled)}.list-box-item>:global(svg){inline-size:16px;min-block-size:auto;fill:currentColor;margin-inline-end:16px}div[data-compactmode] .list-box-item{min-block-size:24px}.info-bar{display:flex;align-items:center;position:relative;min-block-size:48px;box-sizing:border-box;user-select:none;background-clip:padding-box;border:var(--info-bar-border-thickness) solid var(--info-bar-border);border-radius:var(--info-bar-border-corner-radius)}.info-bar.severity-success{background-color:var(--info-bar-success-severity-background)}.info-bar.severity-warning{background-color:var(--info-bar-warning-severity-background)}.info-bar.severity-error{background-color:var(--info-bar-error-severity-background)}.info-bar.severity-informational{background-color:var(--info-bar-informational-severity-background)}.info-bar-content-container{display:flex;align-items:center;width:100%;justify-content:center;position:relative;padding:12px 0 12px 16px}.info-bar-icon{align-self:flex-start;display:flex;flex:0 0 auto;margin-right:6px;margin-top:2px}.info-bar-icon .info-badge{display:inline-flex;justify-content:center;align-items:center;box-sizing:border-box;user-select:none;min-inline-size:16px;min-block-size:16px;border-radius:16px;padding:2px 4px;margin-inline-end:0}.info-bar-icon .info-badge.severity-informational{background-color:var(--info-bar-informational-severity-icon-background);color:var(--info-bar-informational-severity-icon-foreground);fill:var(--info-bar-informational-severity-icon-foreground)!important}.info-bar-icon .info-badge.severity-success{background-color:var(--info-bar-success-severity-icon-background);color:var(--info-bar-success-severity-icon-foreground);fill:var(--info-bar-success-severity-icon-foreground)!important}.info-bar-icon .info-badge.severity-warning{background-color:var(--info-bar-warning-severity-icon-background);color:var(--info-bar-warning-severity-icon-foreground);fill:var(--info-bar-warning-severity-icon-foreground)!important}.info-bar-icon .info-badge.severity-error{background-color:var(--info-bar-error-severity-icon-background);color:var(--info-bar-error-severity-icon-foreground);fill:var(--info-bar-error-severity-icon-foreground)!important}.info-bar-icon .info-badge svg{line-height:12px;font-size:12px;inline-size:8px;block-size:8px;fill:inherit}.info-bar-icon .info-badge svg path{fill:inherit}.info-bar-content{display:flex;align-items:center;flex-wrap:wrap;position:relative;box-sizing:border-box;flex:1 1 auto;margin-left:6px}.info-bar-content h5,.info-bar-content span{margin:0;line-height:20px}.info-bar-content h5{margin-inline-end:12px;color:var(--info-bar-title-foreground)}.info-bar-content span{flex:1 1 auto;margin-inline-end:15px;color:var(--info-bar-message-foreground)}.info-bar-button{margin-right:6px;margin-top:6px;margin-bottom:auto}.info-bar-button.action{margin-top:7px}.info-bar-button .close-button{height:36px;width:36px}div[data-compactmode] .info-bar{min-block-size:34px}div[data-compactmode] .info-bar-content-container{padding:6px 0 6px 16px}div[data-compactmode] .info-bar-button{margin-top:4px;margin-bottom:auto}div[data-compactmode] .info-bar-button.action{margin-top:3px}div[data-compactmode] .info-bar-button .close-button{height:16px;width:24px}.font-icon{display:block;color:inherit;user-select:none;-webkit-user-select:none}.font-icon::before{content:attr(data-glyph)}@keyframes indeterminate-1{0%{opacity:1;transform:translateX(-100%)}70%{opacity:1;transform:translateX(100%)}70.01%{opacity:0}100%{opacity:0;transform:translateX(-100%)}}@keyframes indeterminate-2{0%{opacity:0}50%{opacity:0;transform:translateX(-100%)}50.01%{opacity:1;transform:translateX(-100%)}100%{transform:translateX(100%);opacity:1}}.progress-bar{display:flex;align-items:center;width:100%;height:3px;min-block-size:3px}.progress-bar-track{max-width:50%;height:3px;transition:fill 167ms linear;fill:var(--accent-default)}.progress-bar-rail{fill:var(--control-strong-stroke-default);width:100%;height:1px}.progress-bar.indeterminate .progress-bar-track{fill:transparent}.progress-bar.indeterminate .progress-bar-track:first-of-type{width:40%;fill:var(--accent-default);animation:2s infinite indeterminate-1}.progress-bar.indeterminate .progress-bar-track:nth-of-type(2){width:60%;fill:var(--accent-default);opacity:0;animation:2s infinite indeterminate-2}@keyframes progress-ring-indeterminate{0%{stroke-dasharray:.01px 43.97px;transform:rotate(0)}50%{stroke-dasharray:21.99px 21.99px;transform:rotate(450deg)}100%{stroke-dasharray:.01px 43.97px;transform:rotate(1080deg)}}.progress-ring{outline:0;min-inline-size:16px;min-block-size:16px}.progress-ring circle{transform:rotate(-90deg);transform-origin:50% 50%;transition:250ms linear;fill:none;stroke:var(--accent-default);stroke-width:1.5;stroke-linecap:round;stroke-dasharray:43.97}.progress-ring.indeterminate circle{animation:2s linear infinite progress-ring-indeterminate}.card{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;color:var(--card-foreground);border:var(--card-border-thickness);border-color:var(--card-border);border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--card-background-color);min-height:72px;padding-block:16px;padding-inline:11px;pointer-events:auto}.card .card-header{display:grid;grid-template-columns:20px 1fr;gap:20px;grid-template-areas:"icon context";margin-left:4px}.card .card-header .card-icon{grid-area:icon}.card .card-header .card-context{grid-area:context;display:flex;flex-direction:column}.card .card-header .card-context .card-description{color:var(--card-foreground-description)}.card .card-control{display:inline-flex;gap:16px}.card .card-control.child-of-expander{margin-right:36px}div[data-compactmode] .card{min-height:48px;padding-block:4px}.dialog{border:solid 1px var(--dialog-border-color);border-radius:var(--overlay-corner-radius);background-color:var(--dialog-background-color);-webkit-box-shadow:var(--dialog-shadow);box-shadow:var(--dialog-shadow);animation:.25s cubic-bezier(.25,.1,.25,1) both dialog-open-animation}.dialog-auto-height{display:grid;min-width:300px;min-height:200px;max-width:min(100vw - 128px,800px);max-height:min(100vh - 128px,600px)}.dialog-footer{background-color:var(--dialog-footer-background-color);border-bottom-right-radius:var(--overlay-corner-radius);border-bottom-left-radius:var(--overlay-corner-radius)}.dialog-footer button{min-width:245px}.dialog-overlay{top:0;left:0;right:0;bottom:0;border-radius:inherit;position:absolute;height:100%;width:100%;border-color:transparent;animation:.25s dialog-overlay-fadein-animation;-webkit-animation:.25s dialog-overlay-fadein-animation;-moz-animation:.25s dialog-overlay-fadein-animation;-o-animation:.25s dialog-overlay-fadein-animation}.dialog-overlay-dim{background-color:var(--dialog-light-dismiss-overlay-background)!important}.dialog-container{z-index:20010;display:flex;position:fixed;top:0;left:0;bottom:0;right:0;margin:0;padding:0;align-items:center;justify-content:center;box-sizing:border-box;background:0 0;pointer-events:none}.dialog-container *{pointer-events:auto}@keyframes dialog-overlay-fadein-animation{0%{opacity:0}100%{opacity:1}}@keyframes dialog-open-animation{0%{-webkit-transform:scale(1.25);transform:scale(1.25);opacity:0}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.expander{pointer-events:auto}.expander .card[data-expanded=true]{border-bottom-left-radius:0;border-bottom-right-radius:0}.expander .card[data-expanded=true] .expander-expand-button .font-icon{transform:rotate(180deg);-webkit-transition:transform .2s ease-in-out;-o-transition:transform .2s ease-in-out;-ms-transition:transform .2s ease-in-out;transition:transform .2s ease-in-out}.expander .card.expander-card{-webkit-transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out;-o-transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out;-ms-transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out;transition:border-bottom-left-radius .2s ease-in-out,border-bottom-right-radius .2s ease-in-out}.expander .card.expander-card .expander-expand-button{display:grid;height:32px;width:32px;border-radius:var(--control-corner-radius);border-color:var(--subtle-fill-transparent);background-color:var(--subtle-fill-transparent);color:var(--text-fill-color-primary)}.expander .card.expander-card .expander-expand-button .font-icon{-webkit-transition:transform .2s ease-in-out;-o-transition:transform .2s ease-in-out;-ms-transition:transform .2s ease-in-out;transition:transform .2s ease-in-out}.expander .card.expander-card:hover .expander-expand-button{border-color:var(--subtle-fill-transparent);background-color:var(--subtle-fill-secondary);color:var(--text-fill-color-primary)}.expander .card.expander-card:active .expander-expand-button{border-color:var(--subtle-fill-transparent);background-color:var(--subtle-fill-tertiary);color:var(--text-fill-color-primary)}.expander .card.expander-card:focus-visible{box-shadow:none}.expander .card.expander-card:focus-visible .expander-expand-button{box-shadow:var(--focus-stroke)}.expander .expander-content-anchor{max-height:0;position:relative;overflow:hidden;-webkit-transition:max-height linear 250ms;-o-transition:max-height linear 250ms;-ms-transition:0s linear 250ms max-height;transition:max-height linear 250ms}.expander .expander-content-anchor .expander-content{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;display:block;min-height:72px;margin-top:0;border-radius:var(--control-corner-radius);border-top-left-radius:0;border-top-right-radius:0;border:var(--card-border-thickness);border-top-width:0;border-color:var(--card-border);background-clip:padding-box;background-color:var(--card-background-color-secondary);-webkit-transition:transform 250ms cubic-bezier(1,1,0,1);-o-transition:transform 250ms cubic-bezier(1,1,0,1);-ms-transition:transform 250ms cubic-bezier(1,1,0,1);transition:transform 250ms cubic-bezier(1,1,0,1);transform:translateY(-100%)}.expander .expander-content-anchor .expander-content .expander-card{background-color:transparent}.expander .expander-content-anchor[aria-expanded=true]{max-block-size:602000000000000000000000vmax;transition:none}.expander .expander-content-anchor[aria-expanded=true] .expander-content{transform:none;-webkit-transition:transform 250ms cubic-bezier(0,0,0,1);-o-transition:transform 250ms cubic-bezier(0,0,0,1);-ms-transition:250ms cubic-bezier(0,0,0,1) transform;transition:transform 250ms cubic-bezier(0,0,0,1)}.expander .expander-content-anchor[aria-expanded=false] .expander-content{display:none}div[data-compactmode] .expander .expander-content-anchor .expander-content{min-height:48px}.full-screen-container{height:100%;width:100%}.overlay{top:0;left:0;right:0;bottom:0;margin:0!important;align-items:center;justify-content:center;border-radius:inherit;background:0 0;cursor:default;display:flex;position:fixed;transition:.3s cubic-bezier(.25,.8,.5,1),z-index 1ms;z-index:5}.overlay.overlay-absolute{position:absolute}.overlay .overlay-content{position:relative}.popover{outline:0;z-index:calc(var(--popover-zindex) + 1);position:absolute;opacity:0}.popover.popover-fixed{position:fixed}.popover.popover-relative-width{width:100%}.popover.popover-open{opacity:1;transition:opacity;margin:0;padding:0;min-inline-size:75px;box-sizing:border-box;border-radius:var(--overlay-corner-radius);border-width:1px;border-style:solid;border-color:var(--menu-flyout-presenter-border);background-color:var(--menu-flyout-presenter-background);-webkit-backdrop-filter:var(--menu-flyout-presenter-backdrop-filter);backdrop-filter:var(--menu-flyout-presenter-backdrop-filter);background-clip:padding-box;box-shadow:var(--menu-flyout-presenter-shadow)}.popover:not(.popover-open){pointer-events:none;transition-duration:0s!important;transition-delay:0s!important}.split-grid{height:inherit;pointer-events:auto}.split-grid-gripper{display:flex;justify-content:center}.split-grid-gripper:active,.split-grid-gripper:hover{background-color:var(--subtle-fill-secondary)}.scroll-viewer{overflow-y:overlay;overflow-x:overlay;height:100%;width:100%;pointer-events:auto}.scroll-viewer.vertical{overflow-y:overlay;overflow-x:hidden}.scroll-viewer.horizontal{overflow-y:hidden;overflow-x:overlay}.scroll-viewer.not-scrollable{overflow-x:hidden;overflow-y:hidden}.scroll-viewer.use-native-scroll::-webkit-scrollbar-track{border-radius:999px;background:var(--scrollbar-track-background-color)}.scroll-viewer.use-native-scroll::-webkit-scrollbar-thumb{border-radius:999px;border-width:3px;border-style:solid;border-color:var(--scrollbar-thumb-border-color);background-color:var(--scrollbar-thumb-background-color)}.scroll-viewer.use-native-scroll::-webkit-scrollbar{width:0;transition:.2s ease-in-out}.scroll-viewer.use-native-scroll:hover::-webkit-scrollbar{width:3px}.scroll-viewer.use-native-scroll.on-hover::-webkit-scrollbar{width:12px}.scroll-viewer.use-native-scroll.on-hover::-webkit-scrollbar-track{background:var(--scrollbar-track-background-color-hover)}.scroll-viewer.use-native-scroll.on-hover::-webkit-scrollbar-thumb{border-color:var(--scrollbar-thumb-border-color-hover);background-color:var(--scrollbar-thumb-background-color-hover)}[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto!important;height:auto!important;z-index:0}.simplebar-offset{direction:inherit!important;box-sizing:inherit!important;resize:none!important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0}.simplebar-content-wrapper{direction:inherit;box-sizing:border-box!important;position:relative;display:block;height:100%;width:auto;max-width:100%;max-height:100%;overflow:auto;scrollbar-width:none;-ms-overflow-style:none}.simplebar-content-wrapper:focus-visible{box-shadow:inset 0 0 0 2px var(--focus-stroke-outer)}.simplebar-content-wrapper::-webkit-scrollbar,.simplebar-hide-scrollbar::-webkit-scrollbar{display:none;width:0;height:0}.simplebar-content:after,.simplebar-content:before{content:" ";display:table}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none}.simplebar-height-auto-observer-wrapper{box-sizing:inherit!important;height:100%;width:100%;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0}.simplebar-height-auto-observer{box-sizing:inherit;display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1}.simplebar-scrollbar{position:absolute;left:0;right:0;min-height:10px}.simplebar-scrollbar:before{position:absolute;content:"";background:var(--scrollbar-thumb-background-color);border-radius:7px;border-width:1px;border-style:solid;border-color:var(--scrollbar-thumb-border-color);opacity:0;transition:.2s ease-in-out 2s}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;background:var(--scrollbar-track-background-color);border-radius:7px}.simplebar-track.simplebar-hover{background:var(--scrollbar-track-background-color-hover)}.simplebar-track.simplebar-hover .simplebar-scrollbar:before{border-color:var(--scrollbar-thumb-border-color-hover);background-color:var(--scrollbar-thumb-background-color-hover)}.simplebar-track.simplebar-vertical{top:0;width:5px;transition:width .2s ease-in-out}.simplebar-track.simplebar-vertical.simplebar-hover,.simplebar-track.simplebar-vertical.simplebar-hover .simplebar-scrollbar{width:12px}.simplebar-track.simplebar-horizontal{left:0;height:5px;transition:height .2s ease-in-out}.simplebar-track.simplebar-horizontal.simplebar-hover,.simplebar-track.simplebar-horizontal.simplebar-hover .simplebar-scrollbar{height:12px}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:0;bottom:0;min-height:0;min-width:10px;width:auto}[data-simplebar].simplebar-dragging,[data-simplebar].simplebar-dragging .simplebar-content{pointer-events:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all}.simplebar-scrollbar.simplebar-visible:before{opacity:1;transition-delay:0s;transition-duration:.2s}.simplebar-scrollbar:before{top:2px;bottom:2px;left:2px;right:2px}[data-simplebar-direction=rtl] .simplebar-track.simplebar-vertical{right:auto;left:0}.simplebar-dummy-scrollbar-size{direction:rtl;position:fixed;opacity:0;visibility:hidden;height:500px;width:500px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:scrollbar!important}.simplebar-dummy-scrollbar-size>div{width:200%;height:200%;margin:10px 0}.simplebar-hide-scrollbar{position:fixed;left:0;visibility:hidden;overflow-y:scroll;scrollbar-width:none;-ms-overflow-style:none}.simplebar-content{display:grid;grid-template-rows:minmax(min-content,1fr);height:100%}.stack-vertical{display:flex;flex-direction:column;flex-wrap:nowrap;align-items:stretch;justify-content:stretch;width:100%}.stack-horizontal{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;justify-content:normal;width:100%;max-height:inherit!important;height:100%!important}.auto-suggest-box-drop-down{pointer-events:auto;-webkit-border-radius:var(--overlay-corner-radius)!important;-webkit-border-top-left-radius:0!important;-webkit-border-top-right-radius:0!important;border-radius:var(--overlay-corner-radius)!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.monaco-editor-standalone{user-select:none;-webkit-user-select:none;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;box-sizing:border-box;border:none;outline:0;cursor:unset;margin:0;inline-size:100%;min-block-size:30px;padding-inline:10px;border-radius:var(--control-corner-radius);color:var(--text-box-foreground);background-color:transparent;pointer-events:auto}.monaco-editor-standalone.disabled{color:var(--text-fill-color-disabled)}.monaco-editor-standalone-instance{min-height:100px;height:inherit;display:grid}.monaco-editor-standalone-instance.disabled{pointer-events:none;cursor:none;color:var(--text-fill-color-disabled)!important}.monaco-editor-standalone-instance .monaco-editor,.monaco-editor-standalone-instance .monaco-editor .overflow-guard{height:100%!important;min-height:min-content!important}.monaco-editor-standalone-with-header{display:block;position:relative;height:100%;pointer-events:auto}.monaco-editor-standalone-with-header .monaco-editor-standalone-header{margin-bottom:4px}.monaco-editor-standalone-container{height:inherit;cursor:text;position:relative;border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--text-box-background);border-width:1px;border-style:solid;border-color:var(--text-box-border)}.monaco-editor-standalone-container:hover{color:var(--text-box-foreground-pointer-over);background-color:var(--text-box-background-pointer-over);border-color:var(--text-box-border-pointer-over)}.monaco-editor-standalone-container.disabled{cursor:default;color:var(--text-box-foreground-disabled);background-color:var(--text-box-background-disabled);border-color:var(--text-box-border-disabled)}.monaco-editor-standalone-container.disabled .monaco-editor-standalone-underline{display:none}.monaco-editor-standalone-container:focus-within{color:var(--text-box-foreground-focused);background-color:var(--text-box-background-focused);border-color:var(--text-box-border-focused)}.monaco-editor-standalone-container:focus-within .monaco-editor-standalone-underline::after{border-bottom:var(--text-box-underline-border-thickness-focused) solid var(--accent-default)}.monaco-editor-standalone-underline{position:absolute;inset-inline-start:-1px;inset-block-start:-1px;inline-size:calc(100% + var(--text-box-underline-border-thickness-focused));block-size:calc(100% + var(--text-box-underline-border-thickness-focused));pointer-events:none;border-radius:var(--control-corner-radius);overflow:hidden}.monaco-editor-standalone-underline::after{content:"";box-sizing:border-box;position:absolute;inset-block-end:0;inset-inline-start:0;inline-size:100%;block-size:100%;border-bottom:var(--text-box-underline-border-thickness) solid var(--control-strong-stroke-default)}.monaco-sash{background-color:var(--subtle-fill-secondary)}.context-menu{display:contents;position:relative}.context-menu>div.context-menu-activator{display:contents}.context-menu-list-box .list-box{display:inline-block}.context-menu-list-box .list-box:focus,.context-menu-list-box .list-box:focus-visible,.context-menu-list-box .list-box:focus-within{outline:0}.context-menu-list-box .context-menu-item{padding:5px 8px;cursor:default;border-radius:var(--control-corner-radius);background-color:var(--context-menu-item-background);color:var(--context-menu-item-foreground);margin:3px;display:grid;grid-template-columns:16px 1fr auto;grid-template-rows:1fr;gap:0 12px;grid-template-areas:"icon title keyboard-shortcut"}.context-menu-list-box .context-menu-item.selected,.context-menu-list-box .context-menu-item:focus,.context-menu-list-box .context-menu-item:focus-visible,.context-menu-list-box .context-menu-item:hover{outline:0;background-color:var(--context-menu-item-background-pointer-over)}.context-menu-list-box .context-menu-item:active{background-color:var(--context-menu-item-background-pressed);color:var(--context-menu-item-foreground-pressed)}.context-menu-list-box .context-menu-item.disabled{background-color:var(--context-menu-item-background-disabled);color:var(--context-menu-item-foreground-disabled)}.context-menu-list-box .context-menu-item.disabled .keyboard-accelerator{color:var(--context-menu-item-key-accelerator-foreground-disabled)}.context-menu-list-box .context-menu-item.disabled .font-icon{color:var(--context-menu-item-foreground-disabled)}.context-menu-list-box .context-menu-item .icon-container{grid-area:icon;height:20px;width:20px;display:block;align-self:center}.context-menu-list-box .context-menu-item .font-icon{align-self:center;font-size:20px}.context-menu-list-box .context-menu-item .text{grid-area:title;align-self:center}.context-menu-list-box .context-menu-item .keyboard-accelerator{grid-area:keyboard-shortcut;color:var(--context-menu-item-key-accelerator-foreground);align-self:center;margin-left:32px}.nav-bar-root{pointer-events:auto;height:100%;display:grid;grid-template-columns:auto 1fr;grid-template-rows:48px calc(100vh - 48px);gap:0 0;grid-template-areas:"header header" "sidebar content"}.nav-bar-root.hidden{background:var(--navigation-view-content-background)}.nav-bar-root.hidden main{background:0 0;border:0 solid transparent;border-top-left-radius:0}.nav-bar-root .nav-bar-button-icon{font-size:16px;line-height:16px}.nav-bar-root .nav-bar-header{grid-area:header;z-index:10001}.nav-bar-root nav{grid-area:sidebar;width:320px;max-width:320px;overflow:hidden;display:grid;grid-template-columns:1fr;grid-template-rows:auto 1fr auto;gap:0 0;grid-template-areas:"sidebar-header" "sidebar-body" "sidebar-footer"}.nav-bar-root nav.transition{-webkit-transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out;-o-transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out;-ms-transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out;transition:left .2s ease-in-out,width .2s ease-in-out,max-width .2s ease-in-out,box-shadow .2s ease-in-out}.nav-bar-root nav.hidden{width:0;max-width:0;overflow-x:hidden}.nav-bar-root nav.collapsed{width:49px;max-width:49px;overflow-x:hidden}.nav-bar-root nav.expanded-overlay{z-index:10000;position:absolute;padding-top:48px;height:100%;width:320px;max-width:320px;border-top-right-radius:var(--overlay-corner-radius);border-bottom-right-radius:var(--overlay-corner-radius);border:1px solid;border-color:var(--navigation-view-flyout-border-color);background-color:var(--navigation-view-flyout-background-color);-webkit-backdrop-filter:var(--navigation-view-flyout-backdrop-filter);backdrop-filter:var(--navigation-view-flyout-backdrop-filter);box-shadow:0 8px 16px rgba(0,0,0,.26)}.nav-bar-root nav .sidebar-header{grid-area:sidebar-header;display:block;width:100%}.nav-bar-root nav .sidebar-body{grid-area:sidebar-body;height:100%}.nav-bar-root nav .sidebar-footer{grid-area:sidebar-footer;padding-bottom:4px}.nav-bar-root nav .sidebar-items{margin:0;padding:0}.nav-bar-root main{grid-area:content;background:var(--navigation-view-content-background);border:var(--navigation-view-content-grid-border-thickness);border-color:var(--navigation-view-content-grid-border);border-top-left-radius:var(--overlay-corner-radius);position:relative}.nav-bar-root.expanded-overlay:not(.hidden) main{margin-left:49px}div[data-compactmode] .nav-bar-root .sidebar-header .text-box{min-block-size:32px;padding-inline:10px}nav{pointer-events:auto}nav .sidebar-item-separator{margin:4px 0;padding:0;height:1px;border-color:transparent;background-color:var(--navigation-view-separator-color)}nav .list-box-item{display:grid;grid-template-columns:auto 1fr auto;grid-template-rows:1fr;gap:0 16px;grid-template-areas:". . .";padding-right:0}nav .list-box-item.sidebar-item{margin:var(--sidebar-item-margin)}nav .list-box-item .sidebar-item-icon{height:16px;width:16px}nav .list-box-item .sidebar-expand-group-button{height:34px!important;transition-duration:.2s;transition-property:transform}nav .list-box-item .sidebar-expand-group-button:active,nav .list-box-item .sidebar-expand-group-button:hover{border-color:transparent;background-color:transparent}nav .list-box-item .sidebar-expand-group-button[data-expanded=true]{transform:rotate(180deg)}nav .sidebar-child-item{padding-left:42px}nav.collapsed:not(.expanded-overlay) .list-box-item{max-width:40px;overflow:hidden}nav.collapsed:not(.expanded-overlay) .list-box-item .sidebar-expand-group-button,nav.collapsed:not(.expanded-overlay) .list-box-item .sidebar-item-text,nav.collapsed:not(.expanded-overlay) .sidebar-items.children{display:none}div[data-usercompactmode] nav .list-box-item .sidebar-expand-group-button{height:24px!important}div[data-compactmode]:not(div[data-usercompactmode]) nav .list-box-item{block-size:34px}.text-block{color:currentColor;display:inline-block;margin:0;padding:0;cursor:default;user-select:none;white-space:pre-wrap;-webkit-user-select:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.text-block mark{background-color:var(--text-box-selection-highlight-color);color:var(--text-box-selection-color)}.text-block.type-display,.text-block.type-subtitle,.text-block.type-title,.text-block.type-title-large{font-family:var(--font-family-display);font-weight:var(--text-weight-bold)}.text-block.type-body,.text-block.type-body-large,.text-block.type-body-strong{font-family:var(--font-family-text)}.text-block.type-caption{line-height:16px;letter-spacing:.3px;color:var(--text-fill-color-secondary);font-size:var(--font-size-caption);font-weight:var(--text-weight-normal);font-family:var(--font-family-small)}.text-block.type-body,.text-block.type-body-large,.text-block.type-body-strong{line-height:20px;letter-spacing:.3px;font-weight:var(--text-weight-normal);font-size:var(--font-size-body)}.text-block.type-body-strong{font-weight:var(--text-weight-bolder)}.text-block.type-body-large{font-size:var(--font-size-body-large);line-height:24px}.text-block.type-subtitle{font-size:var(--font-size-subtitle);line-height:28px}.text-block.type-title{font-size:var(--font-size-title);line-height:36px}.text-block.type-title-large{font-size:var(--font-size-title-large);line-height:52px}.text-block.type-display{font-size:var(--font-size-display);line-height:92px}.text-block.no-wrap{white-space:pre}.text-block.trim{overflow:hidden!important;text-overflow:ellipsis;width:100%}.text-block.hide{display:none!important}.text-block.disabled{color:var(--text-fill-color-disabled)}.text-block.horizontal-center{text-align:center}.text-block.vertical-center{top:50%;bottom:50%;position:relative}*{--font-family-fallback:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Ubuntu",system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;--font-family-text:"Segoe UI Variable Text","Seoge UI Variable Static Text",var(--font-family-fallback);--font-family-small:"Segoe UI Variable Small","Seoge UI Variable Static Small",var(--font-family-fallback);--font-family-display:"Segoe UI Variable Display","Seoge UI Variable Static Display",var(--font-family-fallback);--font-size-caption:12px;--font-size-body:14px;--font-size-body-large:18px;--font-size-subtitle:20px;--font-size-title:28px;--font-size-title-large:40px;--font-size-display:68px;--vscode-sash-size:16px;--focus-stroke:inset 0 0 0 1px var(--focus-stroke-inner),0 0 0 2px var(--focus-stroke-outer);text-rendering:geometricPrecision}:focus-visible{box-shadow:var(--focus-stroke);outline:0}div[data-theme=windows-dark-theme]{--control-corner-radius:4px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:154,236,254;--accent-light-2:98,205,254;--accent-light-1:0,146,250;--accent-base:0,121,214;--accent-dark-1:0,95,184;--accent-dark-2:0,62,148;--accent-dark-3:0,24,102;--accent-default:rgba(var(--accent-light-2));--accent-secondary:rgba(var(--accent-light-2), 0.9);--accent-tertiary:rgba(var(--accent-light-2), 0.8);--accent-disabled:rgba(255, 255, 255, 0.158);--system-fill-color-success:rgb(108, 203, 95);--system-fill-color-caution:rgb(252, 225, 0);--system-fill-color-critical:rgb(255, 153, 164);--system-fill-color-neutral:rgba(255, 255, 255, 0.545);--system-fill-color-success-background:rgb(57, 61, 27);--system-fill-color-caution-background:rgb(67, 53, 25);--system-fill-color-critical-background:rgb(68, 39, 38);--system-fill-color-neutral-background:rgba(255, 255, 255, 0.031);--system-fill-color-attention-background:rgba(255, 255, 255, 0.031);--text-on-accent-primary:rgb(0, 0, 0);--text-on-accent-secondary:rgba(0, 0, 0, 0.5);--text-on-accent-disabled:rgba(255, 255, 255, 0.53);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgb(255, 255, 255);--focus-stroke-inner:rgba(0, 0, 0, 0.3);--text-fill-color-primary:rgba(255, 255, 255, 1);--text-fill-color-secondary:rgba(255, 255, 255, 0.77);--text-fill-color-tertiary:rgba(255, 255, 255, 0.53);--text-fill-color-disabled:rgba(255, 255, 255, 0.36);--text-fill-color-inverse:rgba(0, 0, 0, 0.89);--control-fill-color-default:rgba(255, 255, 255, 0.059);--control-fill-color-secondary:rgba(255, 255, 255, 0.082);--control-fill-color-tertiary:rgba(255, 255, 255, 0.031);--control-fill-color-disabled:rgba(255, 255, 255, 0.043);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(30, 30, 30, 0.702);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.098);--control-alt-fill-color-tertiary:rgba(255, 255, 255, 0.043);--control-alt-fill-color-quarternary:rgba(255, 255, 255, 0.071);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgba(255, 255, 255, 0.071);--control-stroke-color-secondary:rgba(255, 255, 255, 0.094);--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(255, 255, 255, 0.544);--control-strong-stroke-disabled:rgba(255, 255, 255, 0.157);--divider-stroke-color-default:rgba(255, 255, 255, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(255, 255, 255, 0.061);--subtle-fill-tertiary:rgba(255, 255, 255, 0.042);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgba(58, 58, 58, 0.3);--control-border-color-default:var(--control-stroke-color-secondary) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.1);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(255, 255, 255, 0.05);--card-background-color-secondary:rgba(255, 255, 255, 0.03);--card-hover-background-color:rgba(255, 255, 255, 0.06);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-secondary);--accent-button-background-pressed:var(--accent-tertiary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-secondary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-fill-color-secondary);--stealth-button-background-pressed:var(--control-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-border-color-default);--stealth-button-border-pressed:var(--control-stroke-color-default);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-secondary);--hyperlink-button-background-pressed:var(--control-fill-color-tertiary);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-default);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-disabled);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-border-color-default);--hyperlink-button-border-pressed:var(--control-stroke-color-default);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:1px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:40px;--toggle-switch-height:20px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-quarternary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-default);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-default);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:12px;--toggle-switch-knob-height:12px;--toggle-switch-knob-offset:3px;--toggle-switch-knob-zoom-pointer-over:-1px;--toggle-switch-knob-active-translation:20px;--toggle-switch-knob-width-pointer-over:14px;--toggle-switch-knob-height-pointer-over:14px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(255, 255, 255, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.3);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgba(32, 32, 32, 1);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(255, 255, 255, 0.0605);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:var(--subtle-fill-secondary);--list-box-item-background-selected-disabled:var(--subtle-fill-secondary);--list-box-item-background-pressed:var(--subtle-fill-tertiary);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-secondary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:var(--accent-default);--menu-flyout-presenter-background:rgb(44, 44, 44);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.2);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-default);--text-box-background-pointer-over:var(--control-fill-color-secondary);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:1px;--text-box-underline-border-thickness-focused:2px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:rgba(31, 31, 31, 0.9);--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(205, 205, 205);--scrollbar-thumb-background-color-hover:rgb(218, 218, 218);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.305);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(43, 43, 43);--dialog-footer-background-color:rgb(32, 32, 32);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgba(0, 120, 212);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(14, 119, 53);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(226, 36, 26);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(204, 146, 0);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(107, 105, 214);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(3, 131, 135);--hero-background-image:url("../img/hero/dark-theme-tile.png");--hero-background-color:rgba(91, 42, 134, 0.5);--hero-title-color:linear-gradient(to right, #db2777, #ec4b4b, #e8c137, #35d49b, #4eb1e0, #a445e8, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=windows-light-theme]{--control-corner-radius:4px;--overlay-corner-radius:8px;--text-weight-normal:400;--text-weight-bold:500;--text-weight-bolder:600;--accent-light-3:154,236,254;--accent-light-2:98,205,254;--accent-light-1:0,120,212;--accent-base:0,103,192;--accent-dark-1:0,95,184;--accent-dark-2:0,103,192;--accent-dark-3:0,26,104;--accent-default:rgba(var(--accent-dark-2));--accent-secondary:rgba(var(--accent-dark-2), 0.9);--accent-tertiary:rgba(var(--accent-dark-2), 0.8);--accent-disabled:rgba(155, 155, 155);--system-fill-color-success:rgb(15, 123, 15);--system-fill-color-caution:rgb(157, 93, 0);--system-fill-color-critical:rgb(196, 43, 28);--system-fill-color-neutral:rgba(0, 0, 0, 0.447);--system-fill-color-success-background:rgb(223, 246, 221);--system-fill-color-caution-background:rgb(255, 244, 206);--system-fill-color-critical-background:rgb(253, 231, 233);--system-fill-color-neutral-background:rgba(0, 0, 0, 0.024);--system-fill-color-attention-background:rgba(246, 246, 246, 0.502);--text-on-accent-primary:rgb(255, 255, 255);--text-on-accent-secondary:rgba(255, 255, 255, 0.702);--text-on-accent-disabled:rgb(255, 255, 255);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgba(0, 0, 0, 1);--focus-stroke-inner:rgb(255, 255, 255);--text-fill-color-primary:rgba(0, 0, 0, 0.894);--text-fill-color-secondary:rgba(0, 0, 0, 0.62);--text-fill-color-tertiary:rgba(0, 0, 0, 0.447);--text-fill-color-disabled:rgba(0, 0, 0, 0.361);--text-fill-color-inverse:rgb(255, 255, 255);--control-fill-color-default:rgba(255, 255, 255, 0.702);--control-fill-color-secondary:rgba(249, 249, 249, 0.502);--control-fill-color-tertiary:rgba(249, 249, 249, 0.302);--control-fill-color-disabled:rgba(249, 249, 249, 0.302);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgb(255, 255, 255);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.024);--control-alt-fill-color-tertiary:rgba(0, 0, 0, 0.059);--control-alt-fill-color-quarternary:rgba(0, 0, 0, 0.094);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgba(0, 0, 0, 0.059);--control-stroke-color-secondary:rgba(0, 0, 0, 0.161);--control-stroke-on-accent-default:rgba(255, 255, 255, 0.078);--control-stroke-on-accent-secondary:rgba(0, 0, 0, 0.4);--control-strong-stroke-default:rgba(0, 0, 0, 0.447);--control-strong-stroke-disabled:rgba(0, 0, 0, 0.216);--divider-stroke-color-default:rgba(0, 0, 0, 0.059);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(0, 0, 0, 0.035);--subtle-fill-tertiary:rgba(0, 0, 0, 0.024);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgba(255, 255, 255, 0.502);--control-border-color-default:var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-secondary) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-default);--card-stroke-color-default:rgba(0, 0, 0, 0.059);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(255, 255, 255, 0.702);--card-background-color-secondary:rgba(246, 246, 246, 0.502);--card-hover-background-color:rgba(0, 0, 0, 0.06);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-secondary);--accent-button-background-pressed:var(--accent-tertiary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-secondary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--subtle-fill-transparent);--stealth-button-background-pointer-over:var(--subtle-fill-secondary);--stealth-button-background-pressed:var(--subtle-fill-tertiary);--stealth-button-background-disabled:var(--subtle-fill-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--subtle-fill-transparent);--stealth-button-border-pointer-over:var(--subtle-fill-secondary);--stealth-button-border-pressed:var(--subtle-fill-tertiary);--stealth-button-border-disabled:var(--subtle-fill-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--subtle-fill-transparent);--hyperlink-button-background-pointer-over:var(--subtle-fill-secondary);--hyperlink-button-background-pressed:var(--subtle-fill-tertiary);--hyperlink-button-background-disabled:var(--subtle-fill-transparent);--hyperlink-button-foreground:rgb(0, 62, 146);--hyperlink-button-foreground-pointer-over:rgb(var(--accent-dark-3));--hyperlink-button-foreground-pressed:rgb(var(--accent-dark-3));--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--subtle-fill-transparent);--hyperlink-button-border-pointer-over:var(--subtle-fill-transparent);--hyperlink-button-border-pressed:var(--subtle-fill-transparent);--hyperlink-button-border-disabled:var(--subtle-fill-transparent);--hyperlink-button-border-thickness:1px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:40px;--toggle-switch-height:20px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-quarternary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-default);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-default);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:12px;--toggle-switch-knob-height:12px;--toggle-switch-knob-offset:3px;--toggle-switch-knob-zoom-pointer-over:-1px;--toggle-switch-knob-active-translation:20px;--toggle-switch-knob-width-pointer-over:14px;--toggle-switch-knob-height-pointer-over:14px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(0, 0, 0, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.1);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgb(243, 243, 243);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(0, 0, 0, 0.0605);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:var(--subtle-fill-secondary);--list-box-item-background-selected-disabled:var(--subtle-fill-secondary);--list-box-item-background-pressed:var(--subtle-fill-tertiary);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-secondary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:var(--accent-default);--menu-flyout-presenter-background:rgb(249, 249, 249);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.059);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-default);--text-box-background-pointer-over:var(--control-fill-color-secondary);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-light-1));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:1px;--text-box-underline-border-thickness-focused:2px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:rgba(213, 213, 213, 0.349);--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(205, 205, 205);--scrollbar-thumb-background-color-hover:rgb(218, 218, 218);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:var(--subtle-fill-secondary);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:var(--subtle-fill-secondary);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:var(--subtle-fill-tertiary);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.302);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(255, 255, 255);--dialog-footer-background-color:rgb(243, 243, 243);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--subtle-fill-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(66, 173, 255);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(66, 255, 132);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(255, 80, 71);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(255, 199, 58);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(157, 155, 255);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(6, 255, 229);--hero-background-image:url("../img/hero/light-theme-tile.png");--hero-background-color:rgba(131, 0, 255, 0.35);--hero-title-color:linear-gradient(to right, #db2777, #e60b0b, #498b13, #0a82d2, #822bc1, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=macos-dark-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:144,192,244;--accent-light-2:106,171,240;--accent-light-1:71,140,246;--accent-base:22,122,229;--accent-dark-1:20,109,204;--accent-dark-2:17,89,167;--accent-dark-3:13,69,130;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(255, 255, 255, 0.158);--system-fill-color-success:rgb(108, 203, 95);--system-fill-color-caution:rgb(252, 225, 0);--system-fill-color-critical:rgb(255, 153, 164);--system-fill-color-neutral:rgba(255, 255, 255, 0.545);--system-fill-color-success-background:rgb(57, 61, 27);--system-fill-color-caution-background:rgb(67, 53, 25);--system-fill-color-critical-background:rgb(68, 39, 38);--system-fill-color-neutral-background:rgba(255, 255, 255, 0.031);--system-fill-color-attention-background:rgba(255, 255, 255, 0.031);--text-on-accent-primary:rgb(0, 0, 0);--text-on-accent-secondary:rgba(0, 0, 0, 0.5);--text-on-accent-disabled:rgba(255, 255, 255, 0.53);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgb(255, 255, 255);--focus-stroke-inner:rgba(0, 0, 0, 0.3);--text-fill-color-primary:rgba(255, 255, 255, 1);--text-fill-color-secondary:rgba(255, 255, 255, 0.8471);--text-fill-color-tertiary:rgba(255, 255, 255, 0.53);--text-fill-color-disabled:rgba(255, 255, 255, 0.36);--text-fill-color-inverse:rgba(0, 0, 0, 0.89);--control-fill-color-default:rgba(255, 255, 255, 0.25);--control-fill-color-secondary:rgba(255, 255, 255, 0.25);--control-fill-color-tertiary:rgba(255, 255, 255, 0.35);--control-fill-color-disabled:rgba(255, 255, 255, 0.125);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(255, 255, 255, 0.15);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.09);--control-alt-fill-color-tertiary:rgba(255, 255, 255, 0.043);--control-alt-fill-color-quarternary:rgba(255, 255, 255, 0.071);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgba(0, 0, 0, 0.06);--control-stroke-color-secondary:rgba(0, 0, 0, 0.094);--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(255, 255, 255, 0.15);--control-strong-stroke-disabled:rgba(255, 255, 255, 0.157);--divider-stroke-color-default:rgba(255, 255, 255, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(255, 255, 255, 0.061);--subtle-fill-tertiary:rgba(255, 255, 255, 0.042);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgb(38, 38, 38);--control-border-color-default:var(--control-stroke-color-secondary) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.1);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(255, 255, 255, 0.05);--card-background-color-secondary:rgba(255, 255, 255, 0.03);--card-hover-background-color:rgba(255, 255, 255, 0.06);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-fill-color-secondary);--stealth-button-background-pressed:var(--control-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-border-color-default);--stealth-button-border-pressed:var(--control-stroke-color-default);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:26px;--toggle-switch-height:15px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-quarternary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:13px;--toggle-switch-knob-height:13px;--toggle-switch-knob-offset:0px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:12px;--toggle-switch-knob-width-pointer-over:13px;--toggle-switch-knob-height-pointer-over:13px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(255, 255, 255, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.3);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgba(32, 32, 32, 0.4);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(255, 255, 255, 0.0605);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-transparent);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgb(44, 44, 44);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.2);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:rgba(255, 255, 255, 0.05);--text-box-background-pointer-over:rgba(255, 255, 255, 0.05);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(118, 118, 118);--scrollbar-thumb-background-color-hover:rgb(169, 170, 170);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.305);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(43, 43, 43);--dialog-footer-background-color:rgb(32, 32, 32);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(0, 120, 212);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(14, 119, 53);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(226, 36, 26);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(204, 146, 0);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(107, 105, 214);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(3, 131, 135);--hero-background-image:url("../img/hero/dark-theme-tile.png");--hero-background-color:rgba(91, 42, 134, 0.5);--hero-title-color:linear-gradient(to right, #db2777, #ec4b4b, #e8c137, #35d49b, #4eb1e0, #a445e8, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=macos-light-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:0,122,255;--accent-light-2:0,122,255;--accent-light-1:0,122,255;--accent-base:0,122,255;--accent-dark-1:0,122,255;--accent-dark-2:0,122,255;--accent-dark-3:0,122,255;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(0, 0, 0, 0.158);--system-fill-color-success:rgb(15, 123, 15);--system-fill-color-caution:rgb(157, 93, 0);--system-fill-color-critical:rgb(196, 43, 28);--system-fill-color-neutral:rgba(0, 0, 0, 0.447);--system-fill-color-success-background:rgb(223, 246, 221);--system-fill-color-caution-background:rgb(255, 244, 206);--system-fill-color-critical-background:rgb(253, 231, 233);--system-fill-color-neutral-background:rgba(0, 0, 0, 0.024);--system-fill-color-attention-background:rgba(246, 246, 246, 0.502);--text-on-accent-primary:rgb(255, 255, 255);--text-on-accent-secondary:rgb(240, 240, 240);--text-on-accent-disabled:rgb(240, 240, 240);--text-on-accent-selected:rgb(240, 240, 240);--focus-stroke-outer:rgba(0, 0, 0, 1);--focus-stroke-inner:rgb(255, 255, 255);--text-fill-color-primary:rgba(0, 0, 0, 0.85);--text-fill-color-secondary:rgba(0, 0, 0, 0.5);--text-fill-color-tertiary:rgba(0, 0, 0, 0.25);--text-fill-color-disabled:rgba(0, 0, 0, 0.25);--text-fill-color-inverse:rgb(255, 255, 255);--control-fill-color-default:rgba(255, 255, 255, 1);--control-fill-color-secondary:rgba(255, 255, 255, 1);--control-fill-color-tertiary:rgba(255, 255, 255, 0.35);--control-fill-color-disabled:rgba(255, 255, 255, 0.5);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(255, 255, 255, 1);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgba(0, 0, 0, 0.09);--control-alt-fill-color-tertiary:rgba(0, 0, 0, 0.043);--control-alt-fill-color-quarternary:rgba(0, 0, 0, 0.071);--control-alt-fill-color-disabled:rgba(0, 0, 0, 0.03);--control-stroke-color-default:rgba(0, 0, 0, 0.3);--control-stroke-color-secondary:rgba(0, 0, 0, 0.05);--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(0, 0, 0, 0.15);--control-strong-stroke-disabled:rgba(0, 0, 0, 0.157);--divider-stroke-color-default:rgba(0, 0, 0, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgba(0, 0, 0, 0.061);--subtle-fill-tertiary:rgba(0, 0, 0, 0.042);--subtle-fill-disabled:transparent;--background-color:transparent;--layer-fill-color-default:rgb(246, 246, 246);--control-border-color-default:var(--control-stroke-color-secondary) var(--control-stroke-color-secondary) var(--control-stroke-color-default) var(--control-stroke-color-secondary);--accent-control-border-color-default:var(--control-stroke-on-accent-default) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.1);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgba(0, 0, 0, 0.020);--card-background-color-secondary:rgba(0, 0, 0, 0.035);--card-hover-background-color:rgba(0, 0, 0, 0.035);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-primary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-border-color-default);--button-border-disabled:var(--control-border-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-alt-fill-color-secondary);--stealth-button-background-pressed:var(--control-alt-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-fill-color-transparent);--stealth-button-border-pressed:var(--control-fill-color-transparent);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:26px;--toggle-switch-height:15px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:1px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-tertiary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-tertiary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:7px;--toggle-switch-knob-width:13px;--toggle-switch-knob-height:13px;--toggle-switch-knob-offset:0px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:12px;--toggle-switch-knob-width-pointer-over:13px;--toggle-switch-knob-height-pointer-over:13px;--toggle-switch-knob-fill-off:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-off-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(0, 0, 0, 0.1);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.1);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-secondary);--navigation-view-flyout-background-color:rgba(246, 246, 246, 0.6);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(0, 0, 0, 0.05);--sidebar-item-margin:3px 5px;--list-box-item-radius:var(--control-corner-radius);--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-transparent);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-inverse);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgba(246, 246, 246);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.4);--menu-flyout-presenter-backdrop-filter:blur(50px) saturate(125%);--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-input-active);--text-box-background-pointer-over:var(--control-fill-color-input-active);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-border-color-default);--text-box-border-pointer-over:var(--control-border-color-default);--text-box-border-focused:var(--control-border-color-default);--text-box-border-disabled:var(--control-border-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(194, 194, 194);--scrollbar-thumb-background-color-hover:rgb(126, 126, 126);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.2);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgba(255, 255, 255, 1);--dialog-footer-background-color:rgb(246, 246, 246);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:var(--card-border);--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(66, 173, 255);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(66, 255, 132);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(255, 80, 71);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(255, 199, 58);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(157, 155, 255);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(6, 255, 229);--hero-background-image:url("../img/hero/light-theme-tile.png");--hero-background-color:rgba(131, 0, 255, 0.35);--hero-title-color:linear-gradient(to right, #db2777, #e60b0b, #498b13, #0a82d2, #822bc1, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=linux-dark-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:144,192,244;--accent-light-2:106,171,240;--accent-light-1:71,140,246;--accent-base:22,122,229;--accent-dark-1:20,109,204;--accent-dark-2:17,89,167;--accent-dark-3:13,69,130;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(255, 255, 255, 0.158);--system-fill-color-success:rgb(108, 203, 95);--system-fill-color-caution:rgb(252, 225, 0);--system-fill-color-critical:rgb(255, 153, 164);--system-fill-color-neutral:rgba(255, 255, 255, 0.545);--system-fill-color-success-background:rgb(57, 61, 27);--system-fill-color-caution-background:rgb(67, 53, 25);--system-fill-color-critical-background:rgb(68, 39, 38);--system-fill-color-neutral-background:rgba(255, 255, 255, 0.031);--system-fill-color-attention-background:rgba(255, 255, 255, 0.031);--text-on-accent-primary:rgb(0, 0, 0);--text-on-accent-secondary:rgba(0, 0, 0, 0.5);--text-on-accent-disabled:rgba(255, 255, 255, 0.53);--text-on-accent-selected:rgb(255, 255, 255);--focus-stroke-outer:rgb(255, 255, 255);--focus-stroke-inner:rgba(0, 0, 0, 0.3);--text-fill-color-primary:rgba(255, 255, 255, 1);--text-fill-color-secondary:rgba(255, 255, 255, 0.8471);--text-fill-color-tertiary:rgba(255, 255, 255, 0.53);--text-fill-color-disabled:rgba(255, 255, 255, 0.36);--text-fill-color-inverse:rgba(0, 0, 0, 0.89);--control-fill-color-default:rgb(55, 55, 55);--control-fill-color-secondary:rgb(60, 60, 60);--control-fill-color-tertiary:rgb(21, 21, 21);--control-fill-color-disabled:rgb(42, 42, 42);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgb(39, 39, 39);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgb(74, 74, 74);--control-alt-fill-color-tertiary:rgba(255, 255, 255, 0.043);--control-alt-fill-color-quarternary:rgba(255, 255, 255, 0.071);--control-alt-fill-color-disabled:rgba(255, 255, 255, 0);--control-stroke-color-default:rgb(24, 24, 24);--control-stroke-color-secondary:transparent;--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(255, 255, 255, 0.15);--control-strong-stroke-disabled:rgba(255, 255, 255, 0.157);--divider-stroke-color-default:rgba(255, 255, 255, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgb(49, 49, 49);--subtle-fill-tertiary:rgb(55, 55, 55);--subtle-fill-disabled:transparent;--background-color:rgb(39, 39, 39);--layer-fill-color-default:rgb(44, 44, 44);--control-border-color-default:var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgba(0, 0, 0, 0.4);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgb(39, 39, 39);--card-background-color-secondary:rgb(39, 39, 39);--card-hover-background-color:rgb(49, 49, 49);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-secondary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-stroke-color-default);--button-border-disabled:var(--control-stroke-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-fill-color-secondary);--stealth-button-background-pressed:var(--control-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-border-color-default);--stealth-button-border-pressed:var(--control-stroke-color-default);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:51px;--toggle-switch-height:26px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:0px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-secondary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:20px;--toggle-switch-knob-width:22px;--toggle-switch-knob-height:22px;--toggle-switch-knob-offset:2px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:25px;--toggle-switch-knob-width-pointer-over:22px;--toggle-switch-knob-height-pointer-over:22px;--toggle-switch-knob-fill-off:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pointer-over:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-pressed:var(--text-fill-color-secondary);--toggle-switch-knob-fill-off-disabled:var(--text-fill-color-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(255, 255, 255, 0.2);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.3);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-default);--navigation-view-flyout-background-color:rgba(32, 32, 32, 0.4);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(255, 255, 255, 0.0605);--sidebar-item-margin:0px;--list-box-item-radius:0px;--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-primary);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgb(29, 29, 29);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.2);--menu-flyout-presenter-backdrop-filter:none;--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-input-active);--text-box-background-pointer-over:var(--control-fill-color-input-active);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-stroke-color-default);--text-box-border-pointer-over:var(--control-stroke-color-default);--text-box-border-focused:var(--control-stroke-color-default);--text-box-border-disabled:var(--control-stroke-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(118, 118, 118);--scrollbar-thumb-background-color-hover:rgb(169, 170, 170);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.305);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgb(43, 43, 43);--dialog-footer-background-color:rgb(32, 32, 32);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:transparent;--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(0, 120, 212);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(14, 119, 53);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(226, 36, 26);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(204, 146, 0);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(107, 105, 214);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(3, 131, 135);--hero-background-image:url("../img/hero/dark-theme-tile.png");--hero-background-color:rgba(91, 42, 134, 0.5);--hero-title-color:linear-gradient(to right, #db2777, #ec4b4b, #e8c137, #35d49b, #4eb1e0, #a445e8, #db2777) 0 0/5000% 5000% no-repeat}div[data-theme=linux-light-theme]{--control-corner-radius:6px;--overlay-corner-radius:8px;--text-weight-normal:300;--text-weight-bold:400;--text-weight-bolder:600;--accent-light-3:0,122,255;--accent-light-2:0,122,255;--accent-light-1:0,122,255;--accent-base:0,122,255;--accent-dark-1:0,122,255;--accent-dark-2:0,122,255;--accent-dark-3:0,122,255;--accent-default:rgba(var(--accent-base));--accent-secondary:rgba(var(--accent-light-1), 1);--accent-tertiary:rgba(var(--accent-light-2), 1);--accent-disabled:rgba(0, 0, 0, 0.158);--system-fill-color-success:rgb(15, 123, 15);--system-fill-color-caution:rgb(157, 93, 0);--system-fill-color-critical:rgb(196, 43, 28);--system-fill-color-neutral:rgba(0, 0, 0, 0.447);--system-fill-color-success-background:rgb(223, 246, 221);--system-fill-color-caution-background:rgb(255, 244, 206);--system-fill-color-critical-background:rgb(253, 231, 233);--system-fill-color-neutral-background:rgba(0, 0, 0, 0.024);--system-fill-color-attention-background:rgba(246, 246, 246, 0.502);--text-on-accent-primary:rgb(255, 255, 255);--text-on-accent-secondary:rgb(240, 240, 240);--text-on-accent-disabled:rgb(240, 240, 240);--text-on-accent-selected:rgb(240, 240, 240);--focus-stroke-outer:rgba(0, 0, 0, 1);--focus-stroke-inner:rgb(255, 255, 255);--text-fill-color-primary:rgba(0, 0, 0, 0.85);--text-fill-color-secondary:rgba(0, 0, 0, 0.5);--text-fill-color-tertiary:rgba(0, 0, 0, 0.25);--text-fill-color-disabled:rgba(0, 0, 0, 0.25);--text-fill-color-inverse:rgb(255, 255, 255);--control-fill-color-default:rgb(255, 255, 255);--control-fill-color-secondary:rgb(245, 245, 245);--control-fill-color-tertiary:rgb(214, 214, 214);--control-fill-color-disabled:rgb(252, 252, 252);--control-fill-color-transparent:rgba(255, 255, 255, 0);--control-fill-color-input-active:rgba(255, 255, 255, 1);--control-alt-fill-color-transparent:rgba(255, 255, 255, 0);--control-alt-fill-color-secondary:rgb(204, 204, 204);--control-alt-fill-color-tertiary:rgba(0, 0, 0, 0.043);--control-alt-fill-color-quarternary:rgba(0, 0, 0, 0.071);--control-alt-fill-color-disabled:rgba(0, 0, 0, 0.03);--control-stroke-color-default:rgb(199, 199, 199);--control-stroke-color-secondary:transparent;--control-stroke-on-accent-default:hsla(0, 0%, 100%, 8%);--control-stroke-on-accent-secondary:hsla(0, 0%, 0%, 14%);--control-strong-stroke-default:rgba(0, 0, 0, 0.15);--control-strong-stroke-disabled:rgba(0, 0, 0, 0.157);--divider-stroke-color-default:rgba(0, 0, 0, 0.082);--subtle-fill-transparent:transparent;--subtle-fill-secondary:rgb(222, 222, 222);--subtle-fill-tertiary:rgb(222, 222, 222);--subtle-fill-disabled:transparent;--background-color:rgb(255, 255, 255);--layer-fill-color-default:rgb(250, 250, 250);--control-border-color-default:var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default) var(--control-stroke-color-default);--accent-control-border-color-default:var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary) var(--control-stroke-on-accent-secondary);--card-stroke-color-default:rgb(215, 215, 215);--card-border:var(--card-stroke-color-default);--card-border-thickness:1px solid;--card-foreground:var(--text-fill-color-primary);--card-foreground-description:var(--text-fill-color-secondary);--card-background-color:rgb(255, 255, 255);--card-background-color-secondary:rgb(255, 255, 255);--card-hover-background-color:rgb(246, 246, 246);--button-background:var(--control-fill-color-default);--button-background-pointer-over:var(--control-fill-color-secondary);--button-background-pressed:var(--control-fill-color-tertiary);--button-background-disabled:var(--control-fill-color-disabled);--button-foreground:var(--text-fill-color-primary);--button-foreground-pointer-over:var(--text-fill-color-primary);--button-foreground-pressed:var(--text-fill-color-primary);--button-foreground-disabled:var(--text-fill-color-disabled);--button-border:var(--control-border-color-default);--button-border-pointer-over:var(--control-border-color-default);--button-border-pressed:var(--control-border-color-default);--button-border-disabled:var(--control-border-color-default);--button-border-thickness:1px solid;--accent-button-background:var(--accent-default);--accent-button-background-pointer-over:var(--accent-default);--accent-button-background-pressed:var(--accent-secondary);--accent-button-background-disabled:var(--accent-disabled);--accent-button-foreground:var(--text-on-accent-primary);--accent-button-foreground-pointer-over:var(--text-on-accent-primary);--accent-button-foreground-pressed:var(--text-on-accent-primary);--accent-button-foreground-disabled:var(--text-on-accent-disabled);--accent-button-border:var(--accent-control-border-color-default);--accent-button-border-pointer-over:var(--accent-control-border-color-default);--accent-button-border-pressed:var(--control-fill-color-transparent);--accent-button-border-disabled:var(--control-fill-color-transparent);--accent-button-border-thickness:1px solid;--stealth-button-background:var(--control-fill-color-transparent);--stealth-button-background-pointer-over:var(--control-alt-fill-color-secondary);--stealth-button-background-pressed:var(--control-alt-fill-color-tertiary);--stealth-button-background-disabled:var(--control-fill-color-transparent);--stealth-button-foreground:var(--text-fill-color-primary);--stealth-button-foreground-pointer-over:var(--text-fill-color-primary);--stealth-button-foreground-pressed:var(--text-fill-color-secondary);--stealth-button-foreground-disabled:var(--text-fill-color-disabled);--stealth-button-border:var(--control-fill-color-transparent);--stealth-button-border-pointer-over:var(--control-fill-color-transparent);--stealth-button-border-pressed:var(--control-fill-color-transparent);--stealth-button-border-disabled:var(--control-fill-color-transparent);--stealth-button-border-thickness:1px solid;--hyperlink-button-background:var(--control-fill-color-transparent);--hyperlink-button-background-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-background-pressed:var(--control-fill-color-transparent);--hyperlink-button-background-disabled:var(--control-fill-color-transparent);--hyperlink-button-foreground:var(--accent-tertiary);--hyperlink-button-foreground-pointer-over:var(--accent-tertiary);--hyperlink-button-foreground-pressed:var(--accent-tertiary);--hyperlink-button-foreground-disabled:var(--accent-disabled);--hyperlink-button-border:var(--control-fill-color-transparent);--hyperlink-button-border-pointer-over:var(--control-fill-color-transparent);--hyperlink-button-border-pressed:var(--control-fill-color-transparent);--hyperlink-button-border-disabled:var(--control-fill-color-transparent);--hyperlink-button-border-thickness:0px solid;--check-box-check-corner-radius:var(--control-corner-radius);--check-box-foreground:var(--text-fill-color-primary);--check-box-foreground-disabled:var(--text-fill-color-disabled);--check-box-check-background-stroke-unchecked:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pointer-over:var(--control-strong-stroke-default);--check-box-check-background-stroke-unchecked-pressed:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-unchecked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-stroke-checked:var(--accent-default);--check-box-check-background-stroke-checked-pointer-over:var(--accent-secondary);--check-box-check-background-stroke-checked-pressed:var(--accent-tertiary);--check-box-check-background-stroke-checked-disabled:var(--control-strong-stroke-disabled);--check-box-check-background-fill-unchecked:var(--control-alt-fill-color-secondary);--check-box-check-background-fill-unchecked-pointer-over:var(--control-alt-fill-color-tertiary);--check-box-check-background-fill-unchecked-pressed:var(--control-alt-fill-color-quarternary);--check-box-check-background-fill-unchecked-disabled:var(--control-alt-fill-color-disabled);--check-box-check-background-fill-checked:var(--accent-default);--check-box-check-background-fill-checked-pointer-over:var(--accent-secondary);--check-box-check-background-fill-checked-pressed:var(--accent-tertiary);--check-box-check-background-fill-checked-disabled:var(--accent-disabled);--check-box-check-glyph-foreground-unchecked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-pressed:var(--text-on-accent-primary);--check-box-check-glyph-foreground-unchecked-disabled:var(--text-on-accent-disabled);--check-box-check-glyph-foreground-checked:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pointer-over:var(--text-on-accent-primary);--check-box-check-glyph-foreground-checked-pressed:var(--text-on-accent-secondary);--check-box-check-glyph-foreground-checked-disabled:var(--text-on-accent-disabled);--radio-button-foreground:var(--text-fill-color-primary);--radio-button-foreground-pointer-over:var(--text-fill-color-primary);--radio-button-foreground-pressed:var(--text-fill-color-primary);--radio-button-foreground-disabled:var(--text-fill-color-disabled);--radio-button-background:var(--control-fill-color-transparent);--radio-button-background-pointer-over:var(--control-fill-color-transparent);--radio-button-background-pressed:var(--control-fill-color-transparent);--radio-button-background-disabled:var(--control-fill-color-transparent);--radio-button-border:var(--control-fill-color-transparent);--radio-button-border-pointer-over:var(--control-fill-color-transparent);--radio-button-border-pressed:var(--control-fill-color-transparent);--radio-button-border-disabled:var(--control-fill-color-transparent);--radio-button-outer-ellipse-stroke:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pointer-over:var(--control-strong-stroke-default);--radio-button-outer-ellipse-stroke-pressed:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-stroke-disabled:var(--control-strong-stroke-disabled);--radio-button-outer-ellipse-fill:var(--control-alt-fill-color-secondary);--radio-button-outer-ellipse-fill-pointer-over:var(--control-alt-fill-color-tertiary);--radio-button-outer-ellipse-fill-pressed:var(--control-alt-fill-color-quarternary);--radio-button-outer-ellipse-fill-disabled:var(--control-alt-fill-color-disabled);--radio-button-outer-ellipse-checked-stroke:var(--accent-default);--radio-button-outer-ellipse-checked-stroke-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-stroke-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-stroke-disabled:var(--accent-disabled);--radio-button-outer-ellipse-checked-fill:var(--accent-default);--radio-button-outer-ellipse-checked-fill-pointer-over:var(--accent-secondary);--radio-button-outer-ellipse-checked-fill-pressed:var(--accent-tertiary);--radio-button-outer-ellipse-checked-fill-disabled:var(--accent-disabled);--radio-button-check-glyph-fill:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pointer-over:var(--text-on-accent-primary);--radio-button-check-glyph-fill-pressed:var(--text-on-accent-primary);--radio-button-check-glyph-fill-disabled:var(--text-on-accent-primary);--radio-button-check-glyph-stroke:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pointer-over:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-pressed:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-disabled:var(--control-stroke-color-default);--radio-button-check-glyph-stroke-checked:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pointer-over:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-pressed:var(--control-stroke-on-accent-default);--radio-button-check-glyph-stroke-checked-disabled:var(--control-stroke-color-default);--toggle-switch-width:51px;--toggle-switch-height:26px;--toggle-switch-foreground:var(--text-fill-color-primary);--toggle-switch-foreground-disabled:var(--text-fill-color-disabled);--toggle-switch-on-stroke-thickness:0px;--toggle-switch-outer-border-stroke-thickness:0px;--toggle-switch-fill-off:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pointer-over:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-pressed:var(--control-alt-fill-color-secondary);--toggle-switch-fill-off-disabled:var(--control-alt-fill-color-disabled);--toggle-switch-stroke-off:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pointer-over:var(--control-strong-stroke-default);--toggle-switch-stroke-off-pressed:var(--control-strong-stroke-default);--toggle-switch-stroke-off-disabled:var(--control-strong-stroke-disabled);--toggle-switch-fill-on:var(--accent-secondary);--toggle-switch-fill-on-pointer-over:var(--accent-secondary);--toggle-switch-fill-on-pressed:var(--accent-secondary);--toggle-switch-fill-on-disabled:var(--accent-disabled);--toggle-switch-stroke-on:var(--accent-secondary);--toggle-switch-stroke-on-pointer-over:var(--accent-secondary);--toggle-switch-stroke-on-pressed:var(--accent-tertiary);--toggle-switch-stroke-on-disabled:var(--accent-disabled);--toggle-switch-knob-radius:20px;--toggle-switch-knob-width:22px;--toggle-switch-knob-height:22px;--toggle-switch-knob-offset:2px;--toggle-switch-knob-zoom-pointer-over:0px;--toggle-switch-knob-active-translation:25px;--toggle-switch-knob-width-pointer-over:22px;--toggle-switch-knob-height-pointer-over:22px;--toggle-switch-knob-fill-off:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-off-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-off-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-fill-on:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pointer-over:var(--text-on-accent-primary);--toggle-switch-knob-fill-on-pressed:var(--text-on-accent-selected);--toggle-switch-knob-fill-on-disabled:var(--text-on-accent-disabled);--toggle-switch-knob-stroke-on:var(--control-border-color-default);--grid-view-header-border-line:rgba(0, 0, 0, 0.1);--grid-view-item-background:var(--card-background-color);--grid-view-item-background-hover:var(--card-hover-background-color);--grid-view-item-stroke:rgba(0, 0, 0, 0.1);--navigation-view-content-background:var(--layer-fill-color-default);--navigation-view-content-grid-border:var(--card-stroke-color-default);--navigation-view-content-grid-border-thickness:1px solid;--navigation-view-flyout-border-color:var(--control-stroke-color-default);--navigation-view-flyout-background-color:rgba(246, 246, 246, 0.6);--navigation-view-flyout-backdrop-filter:blur(50px) saturate(125%);--navigation-view-separator-color:rgba(0, 0, 0, 0.05);--sidebar-item-margin:0px;--list-box-item-radius:0px;--list-box-item-background:var(--subtle-fill-transparent);--list-box-item-background-disabled:var(--subtle-fill-transparent);--list-box-item-background-pointer-over:var(--subtle-fill-secondary);--list-box-item-background-selected:rgb(var(--accent-dark-1));--list-box-item-background-selected-disabled:var(--subtle-fill-transparent);--list-box-item-background-pressed:var(--subtle-fill-transparent);--list-box-item-foreground:var(--text-fill-color-primary);--list-box-item-foreground-selected:var(--text-fill-color-inverse);--list-box-item-foreground-pressed:var(--text-fill-color-primary);--list-box-item-foreground-disabled:var(--text-fill-color-disabled);--list-box-item-selection-indicator:rgb(var(--accent-dark-1));--menu-flyout-presenter-background:rgba(246, 246, 246);--menu-flyout-presenter-border:rgba(0, 0, 0, 0.4);--menu-flyout-presenter-backdrop-filter:none;--menu-flyout-presenter-shadow:0px 8px 16px rgba(0, 0, 0, 0.14);--context-menu-item-background:var(--subtle-fill-transparent);--context-menu-item-background-disabled:var(--subtle-fill-transparent);--context-menu-item-background-pointer-over:var(--subtle-fill-secondary);--context-menu-item-background-pressed:var(--subtle-fill-tertiary);--context-menu-item-foreground:var(--text-fill-color-primary);--context-menu-item-foreground-pressed:var(--text-fill-color-secondary);--context-menu-item-foreground-disabled:var(--text-fill-color-disabled);--context-menu-item-key-accelerator-foreground:var(--text-fill-color-secondary);--context-menu-item-key-accelerator-foreground-disabled:var(--text-fill-color-disabled);--text-box-background:var(--control-fill-color-input-active);--text-box-background-pointer-over:var(--control-fill-color-input-active);--text-box-background-focused:var(--control-fill-color-input-active);--text-box-background-disabled:var(--control-fill-color-disabled);--text-box-border:var(--control-border-color-default);--text-box-border-pointer-over:var(--control-border-color-default);--text-box-border-focused:var(--control-border-color-default);--text-box-border-disabled:var(--control-border-color-default);--text-box-foreground:var(--text-fill-color-primary);--text-box-foreground-pointer-over:var(--text-fill-color-primary);--text-box-foreground-focused:var(--text-fill-color-primary);--text-box-foreground-disabled:var(--text-fill-color-disabled);--text-box-placeholder-foreground:var(--text-fill-color-secondary);--text-box-placeholder-foreground-pointer-over:var(--text-fill-color-secondary);--text-box-placeholder-foreground-focused:var(--text-fill-color-tertiary);--text-box-placeholder-foreground-disabled:var(--text-fill-color-disabled);--text-box-selection-highlight-color:rgb(var(--accent-base));--text-box-selection-color:rgb(255, 255, 255);--text-box-underline-border-thickness:0px;--text-box-underline-border-thickness-focused:0px;--scrollbar-track-background-color:transparent;--scrollbar-track-background-color-hover:transparent;--scrollbar-thumb-border-color:rgba(85, 85, 85, 0.231);--scrollbar-thumb-border-color-hover:rgba(183, 183, 183, 0.42);--scrollbar-thumb-background-color:rgb(194, 194, 194);--scrollbar-thumb-background-color-hover:rgb(126, 126, 126);--info-bar-error-severity-background:var(--system-fill-color-critical-background);--info-bar-warning-severity-background:var(--system-fill-color-caution-background);--info-bar-success-severity-background:var(--system-fill-color-success-background);--info-bar-informational-severity-background:var(--system-fill-color-attention-background);--info-bar-error-severity-icon-background:var(--system-fill-color-critical);--info-bar-warning-severity-icon-background:var(--system-fill-color-caution);--info-bar-success-severity-icon-background:var(--system-fill-color-success);--info-bar-informational-severity-icon-background:var(--accent-default);--info-bar-error-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-warning-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-success-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-informational-severity-icon-foreground:var(--text-fill-color-inverse);--info-bar-title-foreground:var(--text-fill-color-primary);--info-bar-message-foreground:var(--text-fill-color-primary);--info-bar-border:var(--card-stroke-color-default);--info-bar-border-thickness:1px;--info-bar-border-corner-radius:var(--control-corner-radius);--data-grid-column-header-foreground:var(--text-fill-color-tertiary);--data-grid-column-header-background:var(--control-fill-color-default);--data-grid-column-header-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-column-header-background-pressed:var(--subtle-fill-tertiary);--data-grid-row-foreground:var(--text-fill-color-primary);--data-grid-row-background:var(--control-fill-color-default);--data-grid-row-background-pointer-over:rgba(255, 255, 255, 0.15);--data-grid-row-odd-background:rgba(255, 255, 255, 0.095);--data-grid-row-odd-background-pointer-over:rgba(255, 255, 255, 0.18);--data-grid-row-selected-background:rgba(var(--accent-base), 0.9);--data-grid-row-selected-background-pointer-over:rgba(var(--accent-base), 0.8);--data-grid-row-selected-foreground:var(--text-box-selection-color);--data-grid-row-selected-foreground-pointer-over:var(--text-box-selection-color);--data-grid-border:var(--control-stroke-color-default);--dialog-light-dismiss-overlay-background:rgba(0, 0, 0, 0.2);--dialog-shadow:0px 30px 38px -3px rgba(0,0,0,0.24);--dialog-background-color:rgba(255, 255, 255, 1);--dialog-footer-background-color:rgb(246, 246, 246);--dialog-border-color:var(--control-stroke-color-default);--ui-setting-group-presenter-inner-ui-setting-background-color:transparent;--ui-setting-group-presenter-inner-ui-setting-border-color:transparent;--ui-file-selector-background-color:transparent;--ui-file-selector-stroke-color:var(--text-fill-color-disabled);--ui-file-selector-dragging-background-color:var(--control-fill-color-tertiary);--ui-file-selector-dragging-stroke-color:var(--text-fill-color-tertiary);--ui-multiline-text-input-highlighted-text-span-background-color-blue:rgb(66, 173, 255);--ui-multiline-text-input-highlighted-text-span-background-color-green:rgb(66, 255, 132);--ui-multiline-text-input-highlighted-text-span-background-color-red:rgb(255, 80, 71);--ui-multiline-text-input-highlighted-text-span-background-color-yellow:rgb(255, 199, 58);--ui-multiline-text-input-highlighted-text-span-background-color-purple:rgb(157, 155, 255);--ui-multiline-text-input-highlighted-text-span-background-color-teal:rgb(6, 255, 229);--hero-background-image:url("../img/hero/light-theme-tile.png");--hero-background-color:rgba(131, 0, 255, 0.35);--hero-title-color:linear-gradient(to right, #db2777, #e60b0b, #498b13, #0a82d2, #822bc1, #db2777) 0 0/5000% 5000% no-repeat}div{color:currentColor}*,.main-layout{color:var(--text-fill-color-primary)}.theme-transition,.theme-transition *,.theme-transition :after,.theme-transition :before{transition-property:color,background-color;transition-timing-function:ease-out;transition-duration:0s;transition-delay:0!important}@keyframes entrance-theme-transition-key-frames{from{opacity:0;transform:translateY(150px)}to{opacity:1;transform:translateY(0)}}.entrance-theme-transition{animation-name:entrance-theme-transition-key-frames;animation-duration:.15s;animation-timing-function:ease-out}*,::after,::before{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-user-select:none;user-select:none;-webkit-user-drag:none;user-drag:none}body,html{padding:0;margin:0;overflow:hidden;--popover-zindex:90000}#blazor-error-ui{background:#ffffe0;bottom:0;box-shadow:0 -1px 2px rgba(0,0,0,.2);display:none;left:0;padding:.6rem 1.25rem .7rem;position:fixed;width:100%;z-index:2147483647}#blazor-error-ui .dismiss{cursor:pointer;position:absolute;right:.75rem;top:.5rem}.blazor-error-boundary{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) 1rem/1.8rem no-repeat,#b32121;padding:1rem 1rem 1rem 3.7rem;color:#fff}.blazor-error-boundary::after{content:"An error has occurred."}.text-box{user-select:none;-webkit-user-select:none;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;box-sizing:border-box;border:none;outline:0;cursor:unset;margin:0;flex:1 1 auto;inline-size:100%;min-block-size:30px;padding-inline:10px;border-radius:var(--control-corner-radius);color:var(--text-box-foreground);background-color:transparent;pointer-events:auto}.text-box:focus-visible{box-shadow:none}.text-box::placeholder{user-select:none;-webkit-user-select:none;cursor:default;font-family:var(--font-family-text);font-size:var(--font-size-body);font-optical-sizing:none;font-weight:var(--text-weight-normal);line-height:20px;letter-spacing:.3px;user-select:none;-webkit-user-select:none;color:var(--text-box-placeholder-foreground)}.text-box::selection{background:var(--text-box-selection-highlight-color);color:var(--text-box-selection-color)}.text-box::-webkit-search-cancel-button,.text-box::-webkit-search-decoration,.text-box::-webkit-search-results-button,.text-box::-webkit-search-results-decoration{-webkit-appearance:none}.text-box[type=number]{-webkit-appearance:textfield;-moz-appearance:textfield;appearance:textfield}.text-box[type=number]::-webkit-inner-spin-button,.text-box[type=number]::-webkit-outer-spin-button{-webkit-appearance:none}.text-box[type=search]{-webkit-appearance:none}.text-box::-ms-reveal{display:none}.text-box.disabled{color:var(--text-fill-color-disabled)}.text-box.disabled::placeholder{color:var(--text-box-placeholder-foreground-disabled)}.text-box-with-header{display:block;position:relative}.text-box-with-header .text-box-header{margin-bottom:4px}.text-box-container{display:flex;align-items:center;cursor:text;position:relative;border-radius:var(--control-corner-radius);background-clip:padding-box;background-color:var(--text-box-background);border-width:1px;border-style:solid;border-color:var(--text-box-border)}.text-box-container:hover{color:var(--text-box-foreground-pointer-over);background-color:var(--text-box-background-pointer-over);border-color:var(--text-box-border-pointer-over)}.text-box-container:hover .text-box::placeholder{color:var(--text-box-placeholder-foreground-pointer-over)}.text-box-container.disabled{cursor:default;color:var(--text-box-foreground-disabled);background-color:var(--text-box-background-disabled);border-color:var(--text-box-border-disabled)}.text-box-container.disabled .text-box-underline{display:none}.text-box-container.disabled .text-box::placeholder{color:var(--text-box-placeholder-foreground-disabled)}.text-box-container.is-context-menu-opened,.text-box-container:focus-within{color:var(--text-box-foreground-focused);background-color:var(--text-box-background-focused);border-color:var(--text-box-border-focused)}.text-box-container.is-context-menu-opened .text-box::placeholder,.text-box-container:focus-within .text-box::placeholder{color:var(--text-box-placeholder-foreground-focused)}.text-box-container.is-context-menu-opened .text-box-underline::after,.text-box-container:focus-within .text-box-underline::after{border-bottom:var(--text-box-underline-border-thickness-focused) solid var(--accent-default)}.text-box-container.is-context-menu-opened .text-box-clear-button,.text-box-container:focus-within .text-box-clear-button{display:flex}.text-box-underline{position:absolute;inset-inline-start:-1px;inset-block-start:-1px;inline-size:calc(100% + var(--text-box-underline-border-thickness-focused));block-size:calc(100% + var(--text-box-underline-border-thickness-focused));pointer-events:none;border-radius:var(--control-corner-radius);overflow:hidden}.text-box-underline::after{content:"";box-sizing:border-box;position:absolute;inset-block-end:0;inset-inline-start:0;inline-size:100%;block-size:100%;border-bottom:var(--text-box-underline-border-thickness) solid var(--control-strong-stroke-default)}.text-box-buttons{display:flex;align-items:center;cursor:default;flex:0 0 auto}.text-box-buttons button{margin-inline-start:6px;height:22px;min-block-size:22px;width:22px;padding:0}.text-box-buttons button:first-of-type{margin-inline-start:0}.text-box-buttons button:last-of-type{margin-inline-end:4px}.text-box-buttons .font-icon{height:18px;width:20px}.text-box-buttons .text-box-clear-button{display:none}div[data-compactmode] .text-box{min-block-size:24px;padding-inline:6px 2px}.sidebar-searchbar-result-item{display:grid;grid-template-columns:16px 1fr;grid-template-rows:1fr;gap:0 12px;grid-template-areas:"icon title"}.sidebar-searchbar-result-item .sidebar-searchbar-result-item-icon{grid-area:icon}.sidebar-searchbar-result-item .sidebar-searchbar-result-item-title{grid-area:title}.main-layout{margin:0;padding:0;position:absolute;height:100%;width:100%;background-color:var(--background-color)}@-webkit-keyframes hero-bg-scrolling{0%{background-position:0 196px}}@keyframes hero-bg-scrolling{0%{background-position:0 196px}}@keyframes hero-title-shade{to{background-position:100% 0}}.hero{height:250px;width:100%;position:absolute;top:0;right:0;bottom:0;left:0;border-top-left-radius:var(--overlay-corner-radius);mask:linear-gradient(0deg,transparent,#fff 65%);mask-composite:intersect}.hero::before{content:"";position:fixed;width:2000%;height:2000%;top:-1000%;left:-1000%;z-index:-1;background:var(--hero-background-image) repeat 0 0;background-color:var(--hero-background-color);transform:rotateX(15deg) rotateZ(-15deg) skewX(15deg);transform-style:preserve-3d;-webkit-animation:20s linear infinite hero-bg-scrolling;animation:20s linear infinite hero-bg-scrolling}.hero-title{display:flex;font-weight:900!important}.hero-title span:last-of-type{display:inline-block;background:var(--hero-title-color);background-clip:text;color:transparent;animation:10s linear infinite hero-title-shade}.tool-group-parallax{height:100vh;overflow-x:hidden;perspective:1px}.tool-group-grid-view{padding:40px;transform:translateZ(0)}.tool-group-grid-view .tool-group-grid-view-item{height:134px;max-height:134px;padding:16px;display:grid;grid-template-columns:min-content 1fr min-content;grid-template-rows:min-content 1fr;gap:0 0;grid-template-areas:"icon title buttons" "icon description description"}.tool-group-grid-view .tool-group-grid-view-item .icon{grid-area:icon;display:grid;height:100px;width:100px;padding:12px;background-color:var(--card-background-color);border-radius:var(--control-corner-radius)}.tool-group-grid-view .tool-group-grid-view-item .title{grid-area:title;margin-left:16px;max-height:75px;word-wrap:break-word;text-overflow:ellipsis;color:var(--text-fill-color-primary);overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.tool-group-grid-view .tool-group-grid-view-item .description{grid-area:description;margin-left:16px;margin-top:2px;margin-right:8px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical}.tool-group-grid-view .tool-group-grid-view-item .buttons{grid-area:buttons;margin-left:16px;top:0}.tool-group-grid-view .tool-group-grid-view-item .buttons .button{height:24px;min-block-size:24px;width:24px;padding:0}.tool-group-grid-view .tool-group-grid-view-item .buttons .fonticon{text-align:center;font-size:16px;width:21px}div[data-compactmode] .hero{height:200px}div[data-compactmode] .tool-group-grid-view{padding:8px 16px}.tool-page-content{padding:40px;height:inherit}div[data-compactmode] .tool-page-content{padding:8px 16px}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.Tools/DevToys.Tools.csproj b/src/app/dev/DevToys.Tools/DevToys.Tools.csproj
index a04aae9f2c..4d25145f90 100644
--- a/src/app/dev/DevToys.Tools/DevToys.Tools.csproj
+++ b/src/app/dev/DevToys.Tools/DevToys.Tools.csproj
@@ -53,6 +53,11 @@
True
CronParser.resx
+
+ True
+ True
+ JsonWebTokenEncoderDecoder.resx
+
True
True
@@ -253,6 +258,10 @@
JsonFormatter.Designer.cs
ResXFileCodeGenerator
+
+ JsonWebTokenEncoderDecoder.Designer.cs
+ ResXFileCodeGenerator
+
SqlFormatter.Designer.cs
ResXFileCodeGenerator
@@ -398,6 +407,7 @@
+
diff --git a/src/app/dev/DevToys.Tools/Helpers/Core/JsonWebTokenPayloadConverter.cs b/src/app/dev/DevToys.Tools/Helpers/Core/JsonWebTokenPayloadConverter.cs
new file mode 100644
index 0000000000..5f82d3b33f
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Helpers/Core/JsonWebTokenPayloadConverter.cs
@@ -0,0 +1,134 @@
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace DevToys.Tools.Helpers.Core;
+
+internal sealed class JsonWebTokenPayloadConverter : JsonConverter>
+{
+ public override Dictionary? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException();
+ }
+
+ var dictionary = new Dictionary();
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.PropertyName)
+ {
+ string propertyName = reader.GetString()!;
+ dictionary[propertyName] = ReadPropertyValueAsObject(ref reader, true);
+ }
+ else if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+ }
+
+ return dictionary;
+ }
+
+ public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ internal static object ReadPropertyValueAsObject(ref Utf8JsonReader reader, bool read = false)
+ {
+ if (read)
+ reader.Read();
+
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.False:
+ return false;
+ case JsonTokenType.Number:
+ return ReadNumber(ref reader);
+ case JsonTokenType.True:
+ return true;
+ case JsonTokenType.Null:
+ return null!;
+ case JsonTokenType.String:
+ return ReadStringAsObject(ref reader);
+ case JsonTokenType.StartObject:
+ return ReadJsonElement(ref reader);
+ case JsonTokenType.StartArray:
+ return ReadJsonElement(ref reader);
+ default:
+ // There is something broken here as this was called when the reader is pointing at a property.
+ // It must be a known Json type.
+ Debug.Assert(false, $"Utf8JsonReader.TokenType is not one of the expected types: False, Number, True, Null, String, StartArray, StartObject. Is: '{reader.TokenType}'.");
+ return null;
+ }
+ }
+
+ internal static object ReadNumber(ref Utf8JsonReader reader)
+ {
+ if (reader.TryGetInt32(out int i))
+ return i;
+ else if (reader.TryGetInt64(out long l))
+ return l;
+ else if (reader.TryGetDouble(out double d))
+ return d;
+ else if (reader.TryGetUInt32(out uint u))
+ return u;
+ else if (reader.TryGetUInt64(out ulong ul))
+ return ul;
+ else if (reader.TryGetSingle(out float f))
+ return f;
+ else if (reader.TryGetDecimal(out decimal m))
+ return m;
+
+ Debug.Assert(false, "expected to read a number, but none of the Utf8JsonReader.TryGet... methods returned true.");
+
+ return ReadJsonElement(ref reader);
+ }
+
+ internal static object ReadStringAsObject(ref Utf8JsonReader reader, bool read = false)
+ {
+ if (read)
+ {
+ reader.Read();
+ }
+
+ // returning null keeps the same logic as JsonSerialization.ReadObject
+ if (reader.TokenType == JsonTokenType.Null)
+ {
+ return null!;
+ }
+
+ string? originalString = reader.GetString();
+ try
+ {
+ if (DateTime.TryParse(originalString, out DateTime dateTimeValue))
+ {
+ dateTimeValue = dateTimeValue.ToUniversalTime();
+ string dtUniversal = dateTimeValue.ToString("o", CultureInfo.InvariantCulture);
+ if (dtUniversal.Equals(originalString, StringComparison.Ordinal))
+ return dateTimeValue;
+ }
+ }
+ catch (Exception)
+ { }
+
+ return originalString!;
+ }
+
+ internal static JsonElement ReadJsonElement(ref Utf8JsonReader reader)
+ {
+#if NET6_0_OR_GREATER
+ JsonElement? jsonElement;
+ if (JsonElement.TryParseValue(ref reader, out jsonElement))
+ {
+ return jsonElement.Value;
+ }
+ return default;
+#else
+ using (JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader))
+ return jsonDocument.RootElement.Clone();
+#endif
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Helpers/JsonHelper.cs b/src/app/dev/DevToys.Tools/Helpers/JsonHelper.cs
index 8743aa7b0b..c8cddb9bf6 100644
--- a/src/app/dev/DevToys.Tools/Helpers/JsonHelper.cs
+++ b/src/app/dev/DevToys.Tools/Helpers/JsonHelper.cs
@@ -123,7 +123,7 @@ internal static async Task> FormatAsync(
jToken.WriteTo(jsonTextWriter);
}
- return new(stringBuilder.ToString(), true);
+ return new(stringBuilder.ToString());
}
catch (JsonReaderException ex)
{
diff --git a/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenDecoderHelper.cs b/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenDecoderHelper.cs
new file mode 100644
index 0000000000..9d00c99b3d
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenDecoderHelper.cs
@@ -0,0 +1,430 @@
+using System.Security.Cryptography;
+using System.Text;
+using DevToys.Tools.Models;
+using DevToys.Tools.Models.JwtDecoderEncoder;
+using DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+using Microsoft.Extensions.Logging;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Tokens;
+
+namespace DevToys.Tools.Helpers.JsonWebToken;
+
+using System.Security.Claims;
+using Microsoft.IdentityModel.JsonWebTokens;
+using YamlDotNet.Core.Tokens;
+
+internal static partial class JsonWebTokenDecoderHelper
+{
+ private static readonly List _dateFields = new() { "exp", "nbf", "iat", "auth_time", "updated_at" };
+
+ public static ResultInfo GetTokenAlgorithm(string token, ILogger logger)
+ {
+ Guard.IsNotNullOrWhiteSpace(token);
+ try
+ {
+ JsonWebTokenHandler handler = new();
+ JsonWebToken jsonWebToken = handler.ReadJsonWebToken(token);
+ if (!Enum.TryParse(jsonWebToken.Alg, out JsonWebTokenAlgorithm jwtAlgorithm))
+ {
+ return new ResultInfo(null, false);
+ }
+ return new ResultInfo(jwtAlgorithm, true);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Invalid token detected");
+ return new ResultInfo(null, false);
+ }
+ }
+
+ public static async ValueTask> DecodeTokenAsync(
+ DecoderParameters decodeParameters,
+ TokenParameters tokenParameters,
+ ILogger logger,
+ CancellationToken cancellationToken)
+ {
+ Guard.IsNotNull(decodeParameters);
+ Guard.IsNotNull(tokenParameters);
+ Guard.IsNotNullOrWhiteSpace(tokenParameters.Token);
+
+ var tokenResult = new JsonWebTokenResult();
+
+ try
+ {
+ IdentityModelEventSource.ShowPII = true;
+ JsonWebTokenHandler handler = new();
+ JsonWebToken jsonWebToken = handler.ReadJsonWebToken(tokenParameters.Token);
+
+ string decodedHeader = Base64Helper.FromBase64ToText(
+ jsonWebToken.EncodedHeader,
+ Base64Encoding.Utf8,
+ logger,
+ cancellationToken);
+ ResultInfo headerResult = await JsonHelper.FormatAsync(
+ decodedHeader,
+ Indentation.TwoSpaces,
+ false,
+ logger,
+ cancellationToken);
+ if (!headerResult.HasSucceeded)
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.InvalidHeader, ResultInfoSeverity.Error);
+ }
+ tokenResult.Header = headerResult.Data;
+
+ string decodedpayload = Base64Helper.FromBase64ToText(
+ jsonWebToken.EncodedPayload,
+ Base64Encoding.Utf8,
+ logger,
+ cancellationToken);
+ ResultInfo payloadResult = await JsonHelper.FormatAsync(
+ decodedpayload,
+ Indentation.TwoSpaces,
+ false,
+ logger,
+ cancellationToken);
+ if (!payloadResult.HasSucceeded)
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.InvalidPayload, ResultInfoSeverity.Error);
+ }
+ tokenResult.Payload = payloadResult.Data;
+ tokenResult.PayloadClaims = ProcessClaims(payloadResult.Data, jsonWebToken.Claims);
+
+ if (decodeParameters.ValidateSignature)
+ {
+ ResultInfo signatureValid = await ValidateTokenSignatureAsync(handler, decodeParameters, tokenParameters, tokenResult);
+ if (signatureValid.Severity == ResultInfoSeverity.Error)
+ {
+ return new ResultInfo(signatureValid.ErrorMessage!, signatureValid.Severity);
+ }
+ else if (signatureValid.Severity == ResultInfoSeverity.Warning)
+ {
+ return new ResultInfo(tokenResult, signatureValid.ErrorMessage!, signatureValid.Severity);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Invalid token detected");
+ return new ResultInfo(ex.Message, ResultInfoSeverity.Error);
+ }
+
+ return new ResultInfo(tokenResult, ResultInfoSeverity.Success);
+ }
+
+ ///
+ /// Validate the token using the Signing Credentials
+ ///
+ private static async Task> ValidateTokenSignatureAsync(
+ JsonWebTokenHandler handler,
+ DecoderParameters decodeParameters,
+ TokenParameters tokenParameters,
+ JsonWebTokenResult tokenResult)
+ {
+ var validationParameters = new TokenValidationParameters
+ {
+ ValidateActor = decodeParameters.ValidateActors,
+ ValidateLifetime = decodeParameters.ValidateLifetime,
+ ValidateIssuer = decodeParameters.ValidateIssuers,
+ ValidateAudience = decodeParameters.ValidateAudiences
+ };
+
+ if (decodeParameters.ValidateIssuersSigningKey)
+ {
+ ResultInfo signingCredentials = GetSigningCredentials(tokenParameters);
+ if (!signingCredentials.HasSucceeded)
+ {
+ return new ResultInfo(signingCredentials.ErrorMessage!, ResultInfoSeverity.Error);
+ }
+
+ validationParameters.ValidateIssuerSigningKey = decodeParameters.ValidateIssuersSigningKey;
+ validationParameters.IssuerSigningKey = signingCredentials.Data!.Key;
+ validationParameters.TryAllIssuerSigningKeys = true;
+ }
+ else
+ {
+ // Create a custom signature validator that does nothing so it always passes in this mode
+ validationParameters.SignatureValidator = (token, _) => new JsonWebToken(token);
+ }
+
+ // check if the token issuers are part of the user provided issuers
+ if (decodeParameters.ValidateIssuers)
+ {
+ if (tokenParameters.Issuers.Count == 0)
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.ValidIssuersEmptyError, ResultInfoSeverity.Error);
+ }
+ validationParameters.ValidIssuers = tokenParameters.Issuers;
+ }
+
+ // check if the token audience are part of the user provided audiences
+ if (decodeParameters.ValidateAudiences)
+ {
+ if (tokenParameters.Audiences.Count == 0)
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.ValidAudiencesEmptyError, ResultInfoSeverity.Error);
+ }
+ validationParameters.ValidAudiences = tokenParameters.Audiences;
+ }
+
+ try
+ {
+ TokenValidationResult validationResult = await handler.ValidateTokenAsync(tokenParameters.Token, validationParameters);
+ if (!validationResult.IsValid)
+ {
+ return new ResultInfo(validationResult.Exception.Message, ResultInfoSeverity.Error);
+ }
+ tokenResult.Signature = tokenParameters.Signature;
+ tokenResult.PublicKey = tokenParameters.PublicKey;
+
+ if (!decodeParameters.ValidateActors && !decodeParameters.ValidateLifetime &&
+ !decodeParameters.ValidateIssuers && !decodeParameters.ValidateAudiences &&
+ !decodeParameters.ValidateIssuersSigningKey)
+ {
+ return new ResultInfo(tokenResult, JsonWebTokenEncoderDecoder.TokenNotValidated, ResultInfoSeverity.Warning);
+ }
+ return new ResultInfo(tokenResult, ResultInfoSeverity.Success);
+ }
+ catch (Exception exception)
+ {
+ return new ResultInfo(exception.Message, ResultInfoSeverity.Error);
+ }
+ }
+
+ ///
+ /// Get the Signing Credentials depending on the token Algorithm
+ ///
+ private static ResultInfo GetSigningCredentials(
+ TokenParameters tokenParameters)
+ {
+ return tokenParameters.TokenAlgorithm switch
+ {
+ JsonWebTokenAlgorithm.HS256 or
+ JsonWebTokenAlgorithm.HS384 or
+ JsonWebTokenAlgorithm.HS512 => GetHmacShaSigningCredentials(tokenParameters.Signature, tokenParameters.TokenAlgorithm),
+ JsonWebTokenAlgorithm.RS256 or
+ JsonWebTokenAlgorithm.RS384 or
+ JsonWebTokenAlgorithm.RS512 or
+ JsonWebTokenAlgorithm.PS256 or
+ JsonWebTokenAlgorithm.PS384 or
+ JsonWebTokenAlgorithm.PS512 => GetRsaShaSigningCredentials(tokenParameters.PublicKey, tokenParameters.TokenAlgorithm),
+ JsonWebTokenAlgorithm.ES256 or
+ JsonWebTokenAlgorithm.ES384 or
+ JsonWebTokenAlgorithm.ES512 => GetECDsaSigningCredentials(tokenParameters.PublicKey, tokenParameters.TokenAlgorithm),
+ _ => throw new NotSupportedException()
+ };
+ }
+
+ ///
+ /// Generate a Symmetric Security Key using the token signature (base 64 or plain text)
+ ///
+ /// Token signature
+ ///
+ /// Supported Algorithm
+ /// HS256,
+ /// HS384,
+ /// HS512
+ ///
+ ///
+ private static ResultInfo GetHmacShaSigningCredentials(
+ string? signature,
+ JsonWebTokenAlgorithm jwtAlgorithm)
+ {
+ if (string.IsNullOrWhiteSpace(signature))
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.InvalidSignature, false);
+ }
+
+ byte[]? signatureByte;
+ if (Base64Helper.IsBase64DataStrict(signature))
+ {
+ signatureByte = Convert.FromBase64String(signature);
+ }
+ else
+ {
+ signatureByte = Encoding.UTF8.GetBytes(signature);
+ }
+
+ SigningCredentials signingCredentials;
+ switch (jwtAlgorithm)
+ {
+ case JsonWebTokenAlgorithm.HS256:
+ byte[] hs256Key = new HMACSHA256(signatureByte).Key;
+ var hs256SymmetricSecurityKey = new SymmetricSecurityKey(hs256Key);
+ signingCredentials = new SigningCredentials(hs256SymmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature);
+ break;
+ case JsonWebTokenAlgorithm.HS384:
+ byte[] hs384Key = new HMACSHA384(signatureByte).Key;
+ var hs384SymmetricSecurityKey = new SymmetricSecurityKey(hs384Key);
+ signingCredentials = new SigningCredentials(hs384SymmetricSecurityKey, SecurityAlgorithms.HmacSha384Signature);
+ break;
+ case JsonWebTokenAlgorithm.HS512:
+ byte[] hs512Key = new HMACSHA512(signatureByte).Key;
+ var hs512SymmetricSecurityKey = new SymmetricSecurityKey(hs512Key);
+ signingCredentials = new SigningCredentials(hs512SymmetricSecurityKey, SecurityAlgorithms.HmacSha512Signature);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ return new ResultInfo(signingCredentials);
+ }
+
+ ///
+ /// Build RSA signing credentials using the token public key
+ ///
+ /// Token public key
+ ///
+ /// Supported Algorithm
+ /// RS256,
+ /// RS384,
+ /// RS512,
+ /// PS256,
+ /// PS384,
+ /// PS512
+ ///
+ ///
+ ///
+ private static ResultInfo GetRsaShaSigningCredentials(
+ string? key,
+ JsonWebTokenAlgorithm jwtAlgorithm)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.InvalidPublicKey, false);
+ }
+
+ var rsa = RSA.Create();
+ if (key.StartsWith(JsonWebTokenPemEnumeration.PublicKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.PublicKey, key);
+ rsa.ImportSubjectPublicKeyInfo(keyBytes, out _);
+ }
+ else if (key.StartsWith(JsonWebTokenPemEnumeration.RsaPublicKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.RsaPublicKey, key);
+ rsa.ImportRSAPublicKey(keyBytes, out _);
+ }
+ else
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.PublicKeyNotSupported, false);
+ }
+
+ SigningCredentials signingCredentials;
+ switch (jwtAlgorithm)
+ {
+ case JsonWebTokenAlgorithm.RS256:
+ var rs256RsaSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(rs256RsaSecurityKey, SecurityAlgorithms.RsaSha256Signature);
+ break;
+ case JsonWebTokenAlgorithm.RS384:
+ var rs384SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(rs384SymmetricSecurityKey, SecurityAlgorithms.RsaSha384Signature);
+ break;
+ case JsonWebTokenAlgorithm.RS512:
+ var rs512SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(rs512SymmetricSecurityKey, SecurityAlgorithms.RsaSha512Signature);
+ break;
+ case JsonWebTokenAlgorithm.PS256:
+ var ps256RsaSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(ps256RsaSecurityKey, SecurityAlgorithms.RsaSsaPssSha256Signature);
+ break;
+ case JsonWebTokenAlgorithm.PS384:
+ var ps384SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(ps384SymmetricSecurityKey, SecurityAlgorithms.RsaSsaPssSha384Signature);
+ break;
+ case JsonWebTokenAlgorithm.PS512:
+ var ps512SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(ps512SymmetricSecurityKey, SecurityAlgorithms.RsaSsaPssSha512Signature);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ return new ResultInfo(signingCredentials);
+ }
+
+ ///
+ /// Build ECDsa signing credentials using the token public key
+ ///
+ /// Token public key
+ ///
+ /// Supported Algorithm
+ /// ES256,
+ /// ES384,
+ /// ES512
+ ///
+ ///
+ ///
+ private static ResultInfo GetECDsaSigningCredentials(
+ string? key,
+ JsonWebTokenAlgorithm jwtAlgorithm)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.InvalidPublicKey, false);
+ }
+
+ ECDsa ecd;
+ if (OperatingSystem.IsWindows())
+ {
+ ecd = ECDsaCng.Create();
+ }
+ else
+ {
+ ecd = ECDsaOpenSsl.Create();
+ }
+
+ if (key.StartsWith(JsonWebTokenPemEnumeration.PublicKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.PublicKey, key);
+ ecd.ImportSubjectPublicKeyInfo(keyBytes, out _);
+ }
+ else if (key.StartsWith(JsonWebTokenPemEnumeration.ECDPublicKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.ECDPublicKey, key);
+ ecd.ImportECPrivateKey(keyBytes, out _);
+ }
+ else
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.PublicKeyNotSupported, false);
+ }
+
+ SigningCredentials signingCredentials;
+ switch (jwtAlgorithm)
+ {
+ case JsonWebTokenAlgorithm.ES256:
+ var es256RsaSecurityKey = new ECDsaSecurityKey(ecd);
+ signingCredentials = new SigningCredentials(es256RsaSecurityKey, SecurityAlgorithms.EcdsaSha256Signature);
+ break;
+ case JsonWebTokenAlgorithm.ES384:
+ var es384SymmetricSecurityKey = new ECDsaSecurityKey(ecd);
+ signingCredentials = new SigningCredentials(es384SymmetricSecurityKey, SecurityAlgorithms.EcdsaSha384Signature);
+ break;
+ case JsonWebTokenAlgorithm.ES512:
+ var es512SymmetricSecurityKey = new ECDsaSecurityKey(ecd);
+ signingCredentials = new SigningCredentials(es512SymmetricSecurityKey, SecurityAlgorithms.EcdsaSha512Signature);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ return new ResultInfo(signingCredentials);
+ }
+
+ private static List ProcessClaims(ReadOnlySpan data, IEnumerable claims)
+ {
+ List processedClaims = new();
+
+ foreach (Claim claim in claims)
+ {
+ int claimStartPosition = data.IndexOf(claim.Type);
+ TextSpan span = new(claimStartPosition, claim.Type.Length);
+ JsonWebTokenClaim processedClaim = new(claim.Type, claim.Value, span);
+ if (_dateFields.Contains(claim.Type) && long.TryParse(claim.Value, out long value))
+ {
+ processedClaim.Value = $"{DateTimeOffset.FromUnixTimeSeconds(value).ToLocalTime()} ({claim.Value})";
+ }
+ processedClaims.Add(processedClaim);
+ }
+
+ return processedClaims;
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenEncoderHelper.cs b/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenEncoderHelper.cs
new file mode 100644
index 0000000000..f8059790d5
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenEncoderHelper.cs
@@ -0,0 +1,372 @@
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using DevToys.Tools.Helpers.Core;
+using DevToys.Tools.Models;
+using DevToys.Tools.Models.JwtDecoderEncoder;
+using DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+using Microsoft.Extensions.Logging;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Tokens;
+
+namespace DevToys.Tools.Helpers.JsonWebToken;
+
+using Microsoft.IdentityModel.JsonWebTokens;
+
+internal static partial class JsonWebTokenEncoderHelper
+{
+ private static readonly JsonSerializerOptions options = new()
+ {
+ Converters = {
+ new JsonWebTokenPayloadConverter()
+ }
+ };
+
+ public static ResultInfo GenerateToken(
+ EncoderParameters encodeParameters,
+ TokenParameters tokenParameters,
+ ILogger logger)
+ {
+ Guard.IsNotNull(encodeParameters);
+ Guard.IsNotNull(tokenParameters);
+ Guard.IsNotNullOrWhiteSpace(tokenParameters.Payload);
+
+ JsonWebTokenResult tokenResult = new();
+
+ try
+ {
+ IdentityModelEventSource.ShowPII = true;
+ Dictionary? payload = JsonSerializer.Deserialize>(tokenParameters.Payload!, options);
+ if (payload is null)
+ {
+ //return new ResultInfo(JsonWebTokenEncoderDecoder.ValidIssuersEmptyError, ResultInfoSeverity.Error);
+ }
+
+ ResultInfo signingCredentials = GetSigningCredentials(tokenParameters);
+ if (!signingCredentials.HasSucceeded)
+ {
+ return new ResultInfo(signingCredentials.ErrorMessage!, ResultInfoSeverity.Error);
+ }
+
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Claims = payload,
+ SigningCredentials = signingCredentials.Data,
+ IssuedAt = DateTime.UtcNow,
+ Expires = null
+ };
+
+ if (encodeParameters.HasIssuer)
+ {
+ if (tokenParameters.Issuers.Count == 0)
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.ValidIssuersEmptyError, ResultInfoSeverity.Error);
+ }
+
+ tokenDescriptor.Issuer = string.Join(',', tokenParameters.Issuers);
+
+ if (string.IsNullOrWhiteSpace(tokenDescriptor.Issuer))
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.ValidIssuersEmptyError, ResultInfoSeverity.Error);
+ }
+ }
+
+ if (encodeParameters.HasAudience)
+ {
+ if (tokenParameters.Audiences.Count == 0)
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.ValidAudiencesEmptyError, ResultInfoSeverity.Error);
+ }
+
+ tokenDescriptor.Audience = string.Join(',', tokenParameters.Audiences);
+ if (string.IsNullOrWhiteSpace(tokenDescriptor.Audience))
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.ValidAudiencesEmptyError, ResultInfoSeverity.Error);
+ }
+ }
+
+ if (encodeParameters.HasExpiration)
+ {
+ if (!tokenParameters.ExpirationYear.HasValue || !tokenParameters.ExpirationMonth.HasValue ||
+ !tokenParameters.ExpirationDay.HasValue || !tokenParameters.ExpirationHour.HasValue ||
+ !tokenParameters.ExpirationMinute.HasValue)
+ {
+ return new ResultInfo(JsonWebTokenEncoderDecoder.InvalidExpiration, ResultInfoSeverity.Error);
+ }
+ tokenDescriptor.HandleExpiration(tokenParameters);
+ }
+
+ var handler = new JsonWebTokenHandler
+ {
+ SetDefaultTimesOnTokenCreation = false
+ };
+
+ if (encodeParameters.HasDefaultTime)
+ {
+ handler.SetDefaultTimesOnTokenCreation = true;
+ tokenDescriptor.Expires = DateTime.UtcNow.AddHours(1);
+ }
+ string token = handler.CreateToken(tokenDescriptor);
+ tokenResult.Token = token;
+ tokenResult.Payload = tokenParameters.Payload;
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Invalid Payload detected");
+ return new ResultInfo(ex.Message, ResultInfoSeverity.Error);
+ }
+
+ return new ResultInfo(tokenResult, ResultInfoSeverity.Success);
+ }
+
+ ///
+ /// Get the Signing Credentials depending on the token Algorithm
+ ///
+ private static ResultInfo GetSigningCredentials(
+ TokenParameters tokenParameters)
+ {
+ return tokenParameters.TokenAlgorithm switch
+ {
+ JsonWebTokenAlgorithm.HS256 or
+ JsonWebTokenAlgorithm.HS384 or
+ JsonWebTokenAlgorithm.HS512 => GetHmacShaSigningCredentials(tokenParameters.Signature, tokenParameters.TokenAlgorithm),
+ JsonWebTokenAlgorithm.RS256 or
+ JsonWebTokenAlgorithm.RS384 or
+ JsonWebTokenAlgorithm.RS512 or
+ JsonWebTokenAlgorithm.PS256 or
+ JsonWebTokenAlgorithm.PS384 or
+ JsonWebTokenAlgorithm.PS512 => GetRsaShaSigningCredentials(tokenParameters.PrivateKey, tokenParameters.TokenAlgorithm),
+ JsonWebTokenAlgorithm.ES256 or
+ JsonWebTokenAlgorithm.ES384 or
+ JsonWebTokenAlgorithm.ES512 => GetECDsaSigningCredentials(tokenParameters.PrivateKey, tokenParameters.TokenAlgorithm),
+ _ => throw new NotSupportedException()
+ };
+ }
+
+ ///
+ /// Generate a Symmetric Security Key using the token signature (base 64 or plain text)
+ ///
+ /// Token signature
+ ///
+ /// Supported Algorithm
+ /// HS256,
+ /// HS384,
+ /// HS512
+ ///
+ ///
+ private static ResultInfo GetHmacShaSigningCredentials(
+ string? signature,
+ JsonWebTokenAlgorithm jsonWebTokenAlgorithm)
+ {
+ if (string.IsNullOrWhiteSpace(signature))
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.InvalidSignature, false);
+ }
+
+ byte[]? signatureByte;
+ if (Base64Helper.IsBase64DataStrict(signature))
+ {
+ signatureByte = Convert.FromBase64String(signature);
+ }
+ else
+ {
+ signatureByte = Encoding.UTF8.GetBytes(signature);
+ }
+
+ SigningCredentials signingCredentials;
+ switch (jsonWebTokenAlgorithm)
+ {
+ case JsonWebTokenAlgorithm.HS256:
+ byte[] hs256Key = new HMACSHA256(signatureByte).Key;
+ var hs256SymmetricSecurityKey = new SymmetricSecurityKey(hs256Key);
+ signingCredentials = new SigningCredentials(hs256SymmetricSecurityKey, SecurityAlgorithms.HmacSha256);
+ break;
+ case JsonWebTokenAlgorithm.HS384:
+ byte[] hs384Key = new HMACSHA384(signatureByte).Key;
+ var hs384SymmetricSecurityKey = new SymmetricSecurityKey(hs384Key);
+ signingCredentials = new SigningCredentials(hs384SymmetricSecurityKey, SecurityAlgorithms.HmacSha384);
+ break;
+ case JsonWebTokenAlgorithm.HS512:
+ byte[] hs512Key = new HMACSHA512(signatureByte).Key;
+ var hs512SymmetricSecurityKey = new SymmetricSecurityKey(hs512Key);
+ signingCredentials = new SigningCredentials(hs512SymmetricSecurityKey, SecurityAlgorithms.HmacSha512);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ return new ResultInfo(signingCredentials);
+ }
+
+ ///
+ /// Build RSA signing credentials using the token private key
+ ///
+ /// Token private key
+ ///
+ /// Supported Algorithm
+ /// RS256,
+ /// RS384,
+ /// RS512,
+ /// PS256,
+ /// PS384,
+ /// PS512
+ ///
+ ///
+ ///
+ private static ResultInfo GetRsaShaSigningCredentials(
+ string? key,
+ JsonWebTokenAlgorithm jsonWebTokenAlgorithm)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.InvalidPrivateKey, false);
+ }
+
+ var rsa = RSA.Create();
+ if (key.StartsWith(JsonWebTokenPemEnumeration.PrivateKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.PrivateKey, key);
+ rsa.ImportPkcs8PrivateKey(keyBytes, out _);
+ }
+ else if (key.StartsWith(JsonWebTokenPemEnumeration.PrivateKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.PrivateKey, key);
+ rsa.ImportPkcs8PrivateKey(keyBytes, out _);
+ }
+ else if (key.StartsWith(JsonWebTokenPemEnumeration.RsaPrivateKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.RsaPrivateKey, key);
+ rsa.ImportRSAPrivateKey(keyBytes, out _);
+ }
+ else
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.PrivateKeyNotSupported, false);
+ }
+
+ SigningCredentials signingCredentials;
+ switch (jsonWebTokenAlgorithm)
+ {
+ case JsonWebTokenAlgorithm.RS256:
+ var rs256RsaSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(rs256RsaSecurityKey, SecurityAlgorithms.RsaSha256Signature);
+ break;
+ case JsonWebTokenAlgorithm.RS384:
+ var rs384SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(rs384SymmetricSecurityKey, SecurityAlgorithms.RsaSha384Signature);
+ break;
+ case JsonWebTokenAlgorithm.RS512:
+ var rs512SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(rs512SymmetricSecurityKey, SecurityAlgorithms.RsaSha512Signature);
+ break;
+ case JsonWebTokenAlgorithm.PS256:
+ var ps256RsaSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(ps256RsaSecurityKey, SecurityAlgorithms.RsaSsaPssSha256Signature);
+ break;
+ case JsonWebTokenAlgorithm.PS384:
+ var ps384SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(ps384SymmetricSecurityKey, SecurityAlgorithms.RsaSsaPssSha384Signature);
+ break;
+ case JsonWebTokenAlgorithm.PS512:
+ var ps512SymmetricSecurityKey = new RsaSecurityKey(rsa);
+ signingCredentials = new SigningCredentials(ps512SymmetricSecurityKey, SecurityAlgorithms.RsaSsaPssSha512Signature);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ return new ResultInfo(signingCredentials);
+ }
+
+ ///
+ /// Build ECDsa signing credentials using the token private key
+ ///
+ /// Token public key
+ ///
+ /// Supported Algorithm
+ /// ES256,
+ /// ES384,
+ /// ES512
+ ///
+ ///
+ ///
+ private static ResultInfo GetECDsaSigningCredentials(
+ string? key,
+ JsonWebTokenAlgorithm jsonWebTokenAlgorithm)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.InvalidPrivateKey, false);
+ }
+
+ ECDsa ecd;
+ if (OperatingSystem.IsWindows())
+ {
+ ecd = ECDsaCng.Create();
+ }
+ else
+ {
+ ecd = ECDsaOpenSsl.Create();
+ }
+
+ if (key.StartsWith(JsonWebTokenPemEnumeration.PrivateKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.PrivateKey, key);
+ ecd.ImportPkcs8PrivateKey(keyBytes, out _);
+ }
+ else if (key.StartsWith(JsonWebTokenPemEnumeration.ECDPrivateKey.PemStart))
+ {
+ byte[] keyBytes = JsonWebTokenPemEnumeration.GetBytes(JsonWebTokenPemEnumeration.ECDPrivateKey, key);
+ ecd.ImportECPrivateKey(keyBytes, out _);
+ }
+ else
+ {
+ return new ResultInfo(null!, JsonWebTokenEncoderDecoder.PublicKeyNotSupported, false);
+ }
+
+ SigningCredentials signingCredentials;
+ switch (jsonWebTokenAlgorithm)
+ {
+ case JsonWebTokenAlgorithm.ES256:
+ var es256RsaSecurityKey = new ECDsaSecurityKey(ecd);
+ signingCredentials = new SigningCredentials(es256RsaSecurityKey, SecurityAlgorithms.EcdsaSha256Signature);
+ break;
+ case JsonWebTokenAlgorithm.ES384:
+ var es384SymmetricSecurityKey = new ECDsaSecurityKey(ecd);
+ signingCredentials = new SigningCredentials(es384SymmetricSecurityKey, SecurityAlgorithms.EcdsaSha384Signature);
+ break;
+ case JsonWebTokenAlgorithm.ES512:
+ var es512SymmetricSecurityKey = new ECDsaSecurityKey(ecd);
+ signingCredentials = new SigningCredentials(es512SymmetricSecurityKey, SecurityAlgorithms.EcdsaSha512Signature);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ return new ResultInfo(signingCredentials);
+ }
+
+ private static void HandleExpiration(
+ this SecurityTokenDescriptor tokenDescriptor,
+ TokenParameters tokenParameters)
+ {
+ DateTime expirationDate = DateTime.UtcNow;
+ if (tokenParameters.ExpirationYear.HasValue)
+ {
+ expirationDate.AddYears(tokenParameters.ExpirationYear.Value);
+ }
+ if (tokenParameters.ExpirationMonth.HasValue)
+ {
+ expirationDate.AddYears(tokenParameters.ExpirationMonth.Value);
+ }
+ if (tokenParameters.ExpirationDay.HasValue)
+ {
+ expirationDate.AddYears(tokenParameters.ExpirationDay.Value);
+ }
+ if (tokenParameters.ExpirationHour.HasValue)
+ {
+ expirationDate.AddYears(tokenParameters.ExpirationHour.Value);
+ }
+ if (tokenParameters.ExpirationMinute.HasValue)
+ {
+ expirationDate.AddYears(tokenParameters.ExpirationMinute.Value);
+ }
+ tokenDescriptor.Expires = expirationDate;
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenHelper.cs b/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenHelper.cs
new file mode 100644
index 0000000000..dc0c8645d0
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Helpers/JsonWebToken/JsonWebTokenHelper.cs
@@ -0,0 +1,46 @@
+using Microsoft.Extensions.Logging;
+
+namespace DevToys.Tools.Helpers.JsonWebToken;
+
+using Microsoft.IdentityModel.JsonWebTokens;
+
+internal static partial class JsonWebTokenHelper
+{
+ private static readonly string AuthorizationHeader = "Authorization:";
+ private static readonly string BearerScheme = "Bearer";
+
+ ///
+ /// Detects whether the given string is a JWT Token or not.
+ ///
+ internal static bool IsValid(string? input, ILogger logger)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ return false;
+ }
+
+ input = input!.Trim();
+
+ if (input.StartsWith(AuthorizationHeader))
+ {
+ input = input.Remove(0, AuthorizationHeader.Length).Trim();
+ }
+
+ if (input.StartsWith(BearerScheme))
+ {
+ input = input.Remove(0, BearerScheme.Length).Trim();
+ }
+
+ try
+ {
+ JsonWebTokenHandler handler = new();
+ JsonWebToken jsonWebToken = handler.ReadJsonWebToken(input);
+ return jsonWebToken is not null;
+ }
+ catch (Exception ex) //some other exception
+ {
+ logger.LogError(ex, "Invalid data detected '{input}'", input);
+ return false;
+ }
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Helpers/StringHelper.cs b/src/app/dev/DevToys.Tools/Helpers/StringHelper.cs
index 3cf4f43ef2..4d39b40ca4 100644
--- a/src/app/dev/DevToys.Tools/Helpers/StringHelper.cs
+++ b/src/app/dev/DevToys.Tools/Helpers/StringHelper.cs
@@ -20,7 +20,7 @@ internal static ResultInfo EscapeString(string? data, ILogger logger, Ca
{
if (string.IsNullOrWhiteSpace(data))
{
- return new(string.Empty, HasSucceeded: true);
+ return new(string.Empty, hasSucceeded: true);
}
var encoded = new StringBuilder();
@@ -32,7 +32,7 @@ internal static ResultInfo EscapeString(string? data, ILogger logger, Ca
{
if (cancellationToken.IsCancellationRequested)
{
- return new(string.Empty, HasSucceeded: false);
+ return new(string.Empty, hasSucceeded: false);
}
string replacementString = string.Empty;
@@ -88,17 +88,17 @@ internal static ResultInfo EscapeString(string? data, ILogger logger, Ca
catch (Exception ex)
{
logger.LogError(ex, "Failed to escape text");
- return new(ex.Message, HasSucceeded: false);
+ return new(ex.Message, hasSucceeded: false);
}
- return new(encoded.ToString(), HasSucceeded: true);
+ return new(encoded.ToString(), hasSucceeded: true);
}
internal static ResultInfo UnescapeString(string? data, ILogger logger, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(data))
{
- return new(string.Empty, HasSucceeded: false);
+ return new(string.Empty, hasSucceeded: false);
}
var decoded = new StringBuilder();
@@ -110,7 +110,7 @@ internal static ResultInfo UnescapeString(string? data, ILogger logger,
{
if (cancellationToken.IsCancellationRequested)
{
- return new(string.Empty, HasSucceeded: false);
+ return new(string.Empty, hasSucceeded: false);
}
string replacementString = string.Empty;
@@ -166,10 +166,10 @@ internal static ResultInfo UnescapeString(string? data, ILogger logger,
catch (Exception ex)
{
logger.LogError(ex, "Failed to escape text");
- return new(ex.Message, HasSucceeded: false);
+ return new(ex.Message, hasSucceeded: false);
}
- return new(decoded.ToString(), HasSucceeded: true);
+ return new(decoded.ToString(), hasSucceeded: true);
}
internal static string SortLinesAlphabetically(string text, EndOfLineSequence endOfLineSequence)
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/DecoderParameters.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/DecoderParameters.cs
new file mode 100644
index 0000000000..04739954f0
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/DecoderParameters.cs
@@ -0,0 +1,16 @@
+namespace DevToys.Tools.Models;
+
+internal record DecoderParameters
+{
+ public bool ValidateSignature { get; set; }
+
+ public bool ValidateIssuersSigningKey { get; set; }
+
+ public bool ValidateActors { get; set; }
+
+ public bool ValidateLifetime { get; set; }
+
+ public bool ValidateIssuers { get; set; }
+
+ public bool ValidateAudiences { get; set; }
+}
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/EncoderParameters.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/EncoderParameters.cs
new file mode 100644
index 0000000000..a14bb2b674
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/EncoderParameters.cs
@@ -0,0 +1,12 @@
+namespace DevToys.Tools.Models;
+
+internal record EncoderParameters
+{
+ public bool HasExpiration { get; set; }
+
+ public bool HasAudience { get; set; }
+
+ public bool HasIssuer { get; set; }
+
+ public bool HasDefaultTime { get; set; }
+}
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenAlgorithm.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenAlgorithm.cs
new file mode 100644
index 0000000000..39c694eae3
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenAlgorithm.cs
@@ -0,0 +1,17 @@
+namespace DevToys.Tools.Models;
+
+internal enum JsonWebTokenAlgorithm
+{
+ HS256,
+ HS384,
+ HS512,
+ RS256,
+ RS384,
+ RS512,
+ ES256,
+ ES384,
+ ES512,
+ PS256,
+ PS384,
+ PS512
+}
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenClaim.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenClaim.cs
new file mode 100644
index 0000000000..9db5f93409
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenClaim.cs
@@ -0,0 +1,19 @@
+using System.Security.Claims;
+
+namespace DevToys.Tools.Models;
+
+internal class JsonWebTokenClaim
+{
+ public string Key { get; }
+
+ public TextSpan Span { get; }
+
+ public string Value { get; set; }
+
+ public JsonWebTokenClaim(string key, string value, TextSpan span)
+ {
+ Key = key;
+ Value = value;
+ Span = span;
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenPemEnumeration.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenPemEnumeration.cs
new file mode 100644
index 0000000000..2454db1652
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenPemEnumeration.cs
@@ -0,0 +1,47 @@
+using System.Text;
+
+namespace DevToys.Tools.Models.JwtDecoderEncoder;
+
+internal class JsonWebTokenPemEnumeration
+{
+ public static readonly JsonWebTokenPemEnumeration PublicKey = new("-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----");
+
+ public static readonly JsonWebTokenPemEnumeration PrivateKey = new("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
+
+ public static readonly JsonWebTokenPemEnumeration RsaPublicKey = new("-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----");
+
+ public static readonly JsonWebTokenPemEnumeration RsaPrivateKey = new("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----");
+
+ public static readonly JsonWebTokenPemEnumeration RsaEncryptedPrivateKey = new("-----BEGIN ENCRYPTED PRIVATE KEY-----", "-----END ENCRYPTED PRIVATE KEY-----");
+
+ public static readonly JsonWebTokenPemEnumeration ECDPublicKey = new("-----BEGIN EC PUBLIC KEY-----", "-----END EC PUBLIC KEY-----");
+
+ public static readonly JsonWebTokenPemEnumeration ECDPrivateKey = new("-----BEGIN EC PRIVATE KEY-----", "-----END EC PRIVATE KEY-----");
+
+ public string PemStart { get; }
+
+ public string PemEnd { get; }
+
+ public static byte[] GetBytes(JsonWebTokenPemEnumeration jwtPem, string key)
+ {
+ var keyStringBuilder = new StringBuilder(key!.Trim());
+ keyStringBuilder.Replace(Environment.NewLine, string.Empty);
+ if (key.StartsWith(jwtPem.PemStart, StringComparison.OrdinalIgnoreCase))
+ {
+ keyStringBuilder.Remove(0, jwtPem.PemStart.Length);
+ }
+ if (key.Contains(jwtPem.PemEnd, StringComparison.OrdinalIgnoreCase))
+ {
+ keyStringBuilder.Replace(jwtPem.PemEnd, string.Empty);
+ }
+ return Convert.FromBase64String(keyStringBuilder.ToString());
+ }
+
+ private JsonWebTokenPemEnumeration(string pemStart, string pemEnd)
+ {
+ Guard.IsNotNullOrWhiteSpace(pemStart);
+ Guard.IsNotNullOrWhiteSpace(pemEnd);
+ PemStart = pemStart;
+ PemEnd = pemEnd;
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenResult.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenResult.cs
new file mode 100644
index 0000000000..882430ef5a
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JsonWebTokenResult.cs
@@ -0,0 +1,19 @@
+namespace DevToys.Tools.Models;
+
+internal class JsonWebTokenResult
+{
+ public string? Token { get; set; }
+
+ public string? Header { get; set; }
+
+ public string? Payload { get; set; }
+ public string? Signature { get; set; }
+
+ public string? PublicKey { get; set; }
+
+ public string? PrivateKey { get; set; }
+
+ public JsonWebTokenAlgorithm TokenAlgorithm { get; set; }
+
+ public List PayloadClaims { get; set; } = new List();
+}
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/JwtMode.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JwtMode.cs
new file mode 100644
index 0000000000..b47cb52031
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/JwtMode.cs
@@ -0,0 +1,7 @@
+namespace DevToys.Tools.Models;
+
+internal enum JwtMode
+{
+ Decode = 0,
+ Encode = 1
+}
diff --git a/src/app/dev/DevToys.Tools/Models/JsonWebToken/TokenParameters.cs b/src/app/dev/DevToys.Tools/Models/JsonWebToken/TokenParameters.cs
new file mode 100644
index 0000000000..d9b7ac1118
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Models/JsonWebToken/TokenParameters.cs
@@ -0,0 +1,39 @@
+namespace DevToys.Tools.Models;
+
+internal class TokenParameters
+{
+ public string? Token { get; set; }
+
+ public string? Payload { get; set; }
+
+ public string? Signature { get; set; }
+
+ public string? PublicKey { get; set; }
+
+ public string? PrivateKey { get; set; }
+
+ public JsonWebTokenAlgorithm TokenAlgorithm { get; set; }
+
+ public int? ExpirationYear { get; set; }
+
+ public int? ExpirationMonth { get; set; }
+
+ public int? ExpirationDay { get; set; }
+
+ public int? ExpirationHour { get; set; }
+
+ public int? ExpirationMinute { get; set; }
+
+ public HashSet Issuers { get; set; } = new HashSet();
+
+ public HashSet Audiences { get; set; } = new HashSet();
+
+ public void DefineExpirationDate(DateTimeOffset dateTimeOffset)
+ {
+ ExpirationYear = dateTimeOffset.Year;
+ ExpirationMonth = dateTimeOffset.Month;
+ ExpirationDay = dateTimeOffset.Day;
+ ExpirationHour = dateTimeOffset.Hour;
+ ExpirationMinute = dateTimeOffset.Minute;
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Tools/Converters/Date/DateConverterGuiTool.cs b/src/app/dev/DevToys.Tools/Tools/Converters/Date/DateConverterGuiTool.cs
index cd2b27aca6..a60732d617 100644
--- a/src/app/dev/DevToys.Tools/Tools/Converters/Date/DateConverterGuiTool.cs
+++ b/src/app/dev/DevToys.Tools/Tools/Converters/Date/DateConverterGuiTool.cs
@@ -95,8 +95,6 @@ private IUIDropDownListItem SelectedTimeZoneDropDownItem
private readonly IUISettingGroup _customEpochSetting = SettingGroup("date-converter-custom-epoch-setting");
- private readonly IUIDataGrid _dstInformation = DataGrid("date-converter-dst-information-data-grid");
-
#region EpochUiInputs
private readonly IUIStack _epochStack = Stack("date-converter-epoch-stack");
private readonly IUISwitch _useCustomEpochSwitch = Switch("date-converter-use-custom-epoch-switch");
diff --git a/src/app/dev/DevToys.Tools/Tools/Converters/JsonYaml/JsonYamlConverterGuiTool.cs b/src/app/dev/DevToys.Tools/Tools/Converters/JsonYaml/JsonYamlConverterGuiTool.cs
index 091c999229..a7d2828f21 100644
--- a/src/app/dev/DevToys.Tools/Tools/Converters/JsonYaml/JsonYamlConverterGuiTool.cs
+++ b/src/app/dev/DevToys.Tools/Tools/Converters/JsonYaml/JsonYamlConverterGuiTool.cs
@@ -214,7 +214,7 @@ private async Task ConvertAsync(string input, JsonToYamlConversion conversionMod
indentationModeSetting,
_logger,
cancellationToken);
- _outputTextArea.Text(conversionResult.Data);
+ _outputTextArea.Text(conversionResult.Data!);
}
}
diff --git a/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenDecoderGuiTool.cs b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenDecoderGuiTool.cs
new file mode 100644
index 0000000000..d57b4d6116
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenDecoderGuiTool.cs
@@ -0,0 +1,590 @@
+using DevToys.Tools.Helpers.JsonWebToken;
+using DevToys.Tools.Models;
+using Microsoft.Extensions.Logging;
+
+namespace DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+
+internal sealed partial class JsonWebTokenDecoderGuiTool
+{
+ ///
+ /// Define if we want to validate the token or not
+ ///
+ private static readonly SettingDefinition validateTokenSetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(validateTokenSetting)}",
+ defaultValue: false);
+
+ ///
+ /// Define if we want to validate the token issuer signing key or not
+ ///
+ private static readonly SettingDefinition validateTokenIssuerSigningKeySetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(validateTokenIssuerSigningKeySetting)}",
+ defaultValue: false);
+
+ #region TokenIssuers
+ ///
+ /// Define if we want to validate the token issuers or not
+ ///
+ private static readonly SettingDefinition validateTokenIssuerSetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(validateTokenIssuerSetting)}",
+ defaultValue: false);
+
+ ///
+ /// Define the issuers list
+ ///
+ private static readonly SettingDefinition tokenIssuerSetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(tokenIssuerSetting)}",
+ defaultValue: string.Empty);
+ #endregion
+
+ #region TokenAudiences
+ ///
+ /// Define if we want to validate the token audiences or not
+ ///
+ private static readonly SettingDefinition validateTokenAudiencesSetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(validateTokenAudiencesSetting)}",
+ defaultValue: false);
+
+ ///
+ /// Define the audiences list
+ ///
+ private static readonly SettingDefinition tokenAudiencesSetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(tokenAudiencesSetting)}",
+ defaultValue: string.Empty);
+ #endregion
+
+ ///
+ /// Define if we want to validate the token lifetime or not
+ ///
+ private static readonly SettingDefinition validateTokenLifetimeSetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(validateTokenLifetimeSetting)}",
+ defaultValue: false);
+
+ ///
+ /// Define if we want to validate the token actors
+ ///
+ private static readonly SettingDefinition validateTokenActorsSetting
+ = new(
+ name: $"{nameof(JsonWebTokenDecoderGuiTool)}.{nameof(validateTokenActorsSetting)}",
+ defaultValue: false);
+
+ private bool _showPayloadClaim;
+ private JsonWebTokenAlgorithm _currentAlgorithm = JsonWebTokenAlgorithm.HS256;
+
+ private readonly ILogger _logger;
+ private readonly ISettingsProvider _settingsProvider;
+
+ private readonly IUIInfoBar _infoBar = InfoBar("jwt-decode-info-bar");
+ private readonly IUIStack _viewStack = Stack("jwt-decode-view-stack");
+ private readonly IUIStack _decodeSettingsStack = Stack("jwt-decode-settings-stack");
+
+ private readonly IUISwitch _validateTokenSwitch = Switch("jwt-decode-validate-token-switch");
+ private readonly IUISwitch _validateTokenIssuerSigningKeySwitch = Switch("jwt-decode-validate-token-issuer-signing-key-switch");
+ private readonly IUISwitch _validateTokenIssuersSwitch = Switch("jwt-decode-validate-token-issuers-switch");
+ private readonly IUISwitch _validateTokenAudiencesSwitch = Switch("jwt-decode-validate-token-audiences-switch");
+ private readonly IUISwitch _validateLifetimeSwitch = Switch("jwt-decode-validate-token-lifetime-switch");
+ private readonly IUISwitch _validateActorsSwitch = Switch("jwt-decode-validate-token-actors-switch");
+
+ private readonly IUISingleLineTextInput _validateTokenIssuersInput = SingleLineTextInput("jwt-decode-validate-token-issuers-input");
+ private readonly IUISingleLineTextInput _validateTokenAudiencesInput = SingleLineTextInput("jwt-decode-validate-token-audiences-input");
+
+ private readonly IUIMultiLineTextInput _tokenInput = MultilineTextInput("jwt-decode-token-input");
+ private readonly IUIMultiLineTextInput _headerInput = MultilineTextInput("jwt-decode-header-input", "json");
+ private readonly IUIMultiLineTextInput _payloadInput = MultilineTextInput("jwt-decode-payload-input", "json");
+ private readonly IUIMultiLineTextInput _signatureInput = MultilineTextInput("jwt-decode-signature-input");
+ private readonly IUIMultiLineTextInput _publicKeyInput = MultilineTextInput("jwt-decode-public-key-input");
+
+ private readonly IUIDataGrid _payloadClaimsDataGrid = DataGrid("jwt-decode-payload-claims-data-grid");
+
+ private static readonly List dateFields = new() { "exp", "nbf", "iat", "auth_time", "updated_at" };
+
+ private DisposableSemaphore _semaphore = new();
+ private CancellationTokenSource? _cancellationTokenSource;
+
+ [ImportingConstructor]
+ public JsonWebTokenDecoderGuiTool(ISettingsProvider settingsProvider)
+ {
+ _logger = this.Log();
+ _settingsProvider = settingsProvider;
+ ConfigureUI();
+ GetTokenAlgorithm();
+ }
+
+ internal Task? WorkTask { get; private set; }
+
+ public IUIStack ViewStack
+ => _viewStack
+ .Vertical()
+ .WithChildren(
+ SettingGroup("jwt-decode-validate-token-setting")
+ .Icon("FluentSystemIcons", '\uec9e')
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenSettingsTitle)
+ .Description(JsonWebTokenEncoderDecoder.DecodeValidateTokenSettingsDescription)
+ .InteractiveElement(
+ _validateTokenSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnValidateTokenChanged)
+ )
+ .WithChildren(
+ Setting("jwt-decode-validate-token-issuer-signing-key-setting")
+ .Icon("FluentSystemIcons", '\ue30a')
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenIssuerSigningKeyTitle)
+ .InteractiveElement(
+ _validateTokenIssuerSigningKeySwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnValidateTokenIssuerSigningKey)
+ ),
+ SettingGroup("jwt-decode-validate-token-issuers-setting-group")
+ .Icon("FluentSystemIcons", '\ue30a')
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenIssuerTitle)
+ .InteractiveElement(
+ _validateTokenIssuersSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnValidateTokenIssuer)
+ )
+ .WithChildren(
+ _validateTokenIssuersInput
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenIssuerInputLabel)
+ .OnTextChanged(OnTokenIssuerInputChanged)
+ ),
+ SettingGroup("jwt-decode-validate-token-audiences-setting-group")
+ .Icon("FluentSystemIcons", '\ue30a')
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenAudiencesTitle)
+ .InteractiveElement(
+ _validateTokenAudiencesSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnValidateTokenAudiences)
+ )
+ .WithChildren(
+ _validateTokenAudiencesInput
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenAudiencesInputLabel)
+ .OnTextChanged(OnTokenAudiencesInputChanged)
+ ),
+ Setting("jwt-decode-validate-token-lifetime-setting")
+ .Icon("FluentSystemIcons", '\ue30a')
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenLifetimeTitle)
+ .InteractiveElement(
+ _validateLifetimeSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnValidateTokenLifetime)
+ ),
+ Setting("jwt-decode-validate-token-actors-setting")
+ .Icon("FluentSystemIcons", '\ue30a')
+ .Title(JsonWebTokenEncoderDecoder.DecodeValidateTokenActorsTitle)
+ .InteractiveElement(
+ _validateActorsSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnValidateTokenActors)
+ )
+ ),
+ _infoBar
+ .NonClosable(),
+ _tokenInput
+ .Title(JsonWebTokenEncoderDecoder.TokenInputTitle)
+ .OnTextChanged(OnTokenInputChanged),
+ SplitGrid()
+ .Vertical()
+ .WithLeftPaneChild(
+ _headerInput
+ .Title(JsonWebTokenEncoderDecoder.HeaderInputTitle)
+ .ReadOnly()
+ )
+ .WithRightPaneChild(
+ Stack()
+ .Vertical()
+ .WithChildren(
+ _payloadInput
+ .Title(JsonWebTokenEncoderDecoder.PayloadInputTitle)
+ .ReadOnly()
+ .Extendable()
+ .CommandBarExtraContent(
+ Stack("jwt-decode-payload-stack")
+ .Horizontal()
+ .WithChildren(
+ Button("jwt-decode-payload-claims-toggle-button")
+ .Icon("FluentSystemIcons", '\uf4a5')
+ .OnClick(OnPayloadClaimClicked)
+ )
+ ),
+ _payloadClaimsDataGrid
+ .Extendable()
+ .CommandBarExtraContent(
+ Stack("jwt-decode-payload-claims-stack")
+ .Horizontal()
+ .WithChildren(
+ Button("jwt-decode-payload-claims-toggle-button")
+ .Icon("FluentSystemIcons", '\uf4a5')
+ .OnClick(OnPayloadClaimClicked)
+ )
+ )
+ .WithColumns(JsonWebTokenEncoderDecoder.ClaimTypeTitle, JsonWebTokenEncoderDecoder.ClaimValueTitle)
+ )
+ ),
+ _signatureInput
+ .Title(JsonWebTokenEncoderDecoder.SignatureInputTitle)
+ .OnTextChanged(OnTokenInputChanged),
+ _publicKeyInput
+ .Title(JsonWebTokenEncoderDecoder.PublicKeyInputTitle)
+ .OnTextChanged(OnTokenInputChanged)
+ );
+
+ public void Show()
+ {
+ _viewStack.Show();
+ _semaphore = new();
+ _cancellationTokenSource = new CancellationTokenSource();
+ }
+
+ public void Hide()
+ {
+ _viewStack.Hide();
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _semaphore.Dispose();
+ }
+
+ private void OnValidateTokenChanged(bool validateToken)
+ {
+ _settingsProvider.SetSetting(validateTokenSetting, validateToken);
+ ConfigureUI();
+ GetTokenAlgorithm();
+ StartTokenDecode();
+ }
+
+ #region TokenIssuer
+
+ private void OnValidateTokenIssuerSigningKey(bool validateTokenIssuerSigningKey)
+ {
+ _settingsProvider.SetSetting(validateTokenIssuerSigningKeySetting, validateTokenIssuerSigningKey);
+ StartTokenDecode();
+ }
+
+ private void OnValidateTokenIssuer(bool validateTokenIssuer)
+ {
+ _settingsProvider.SetSetting(validateTokenIssuerSetting, validateTokenIssuer);
+ if (validateTokenIssuer)
+ {
+ _validateTokenIssuersInput.Enable();
+ }
+ else
+ {
+ _validateTokenIssuersInput.Disable();
+ }
+ StartTokenDecode();
+ }
+
+ private ValueTask OnTokenIssuerInputChanged(string issuer)
+ {
+ _settingsProvider.SetSetting(tokenIssuerSetting, issuer);
+ StartTokenDecode();
+ return ValueTask.CompletedTask;
+ }
+
+ #endregion
+
+ #region TokenAudiences
+
+ private void OnValidateTokenAudiences(bool validateTokenAudiences)
+ {
+ _settingsProvider.SetSetting(validateTokenAudiencesSetting, validateTokenAudiences);
+ if (validateTokenAudiences)
+ {
+ _validateTokenAudiencesInput.Enable();
+ }
+ else
+ {
+ _validateTokenAudiencesInput.Disable();
+ }
+ StartTokenDecode();
+ }
+
+ private ValueTask OnTokenAudiencesInputChanged(string audiences)
+ {
+ _settingsProvider.SetSetting(tokenAudiencesSetting, audiences);
+ StartTokenDecode();
+ return ValueTask.CompletedTask;
+ }
+
+ #endregion
+
+ #region TokenLifetime
+
+ private void OnValidateTokenLifetime(bool validateTokenLifetime)
+ {
+ _settingsProvider.SetSetting(validateTokenLifetimeSetting, validateTokenLifetime);
+ StartTokenDecode();
+ }
+
+ #endregion
+
+ #region TokenActors
+
+ private void OnValidateTokenActors(bool validateTokenActors)
+ {
+ _settingsProvider.SetSetting(validateTokenActorsSetting, validateTokenActors);
+ StartTokenDecode();
+ }
+
+ #endregion
+
+ private void OnPayloadClaimClicked()
+ {
+ if (_showPayloadClaim)
+ {
+ _payloadInput.Show();
+ _payloadClaimsDataGrid.Hide();
+ _showPayloadClaim = false;
+ return;
+ }
+
+ _payloadInput.Hide();
+ _payloadClaimsDataGrid.Show();
+ _showPayloadClaim = true;
+ }
+
+ private void OnTokenInputChanged(string text)
+ {
+ GetTokenAlgorithm();
+ StartTokenDecode();
+ }
+
+ private void StartTokenDecode()
+ {
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = new CancellationTokenSource();
+
+ if (string.IsNullOrWhiteSpace(_tokenInput.Text))
+ {
+ ClearUI();
+ _infoBar.Close();
+ return;
+ }
+
+ TokenParameters tokenParameters = new()
+ {
+ Token = _tokenInput.Text
+ };
+
+ DecoderParameters decoderParameters = new();
+ bool validateTokenSignature = _settingsProvider.GetSetting(validateTokenSetting);
+ if (validateTokenSignature)
+ {
+ decoderParameters.ValidateSignature = validateTokenSignature;
+ decoderParameters.ValidateActors = _settingsProvider.GetSetting(validateTokenActorsSetting);
+ decoderParameters.ValidateLifetime = _settingsProvider.GetSetting(validateTokenLifetimeSetting);
+ decoderParameters.ValidateIssuersSigningKey = _settingsProvider.GetSetting(validateTokenIssuerSigningKeySetting);
+
+ bool validateIssuers = _settingsProvider.GetSetting(validateTokenIssuerSetting);
+ if (validateIssuers)
+ {
+ decoderParameters.ValidateIssuers = validateIssuers;
+ tokenParameters.Issuers = _settingsProvider.GetSetting(tokenIssuerSetting).Split(',').ToHashSet();
+ }
+
+ bool validateAudiences = _settingsProvider.GetSetting(validateTokenAudiencesSetting);
+ if (validateAudiences)
+ {
+ decoderParameters.ValidateAudiences = validateAudiences;
+ tokenParameters.Audiences = _settingsProvider.GetSetting(tokenAudiencesSetting).Split(',').ToHashSet();
+ }
+ }
+
+ tokenParameters.TokenAlgorithm = _currentAlgorithm;
+ if (_currentAlgorithm is JsonWebTokenAlgorithm.HS256 ||
+ _currentAlgorithm is JsonWebTokenAlgorithm.HS384 ||
+ _currentAlgorithm is JsonWebTokenAlgorithm.HS512)
+ {
+ tokenParameters.Signature = _signatureInput.Text;
+ }
+ else
+ {
+ tokenParameters.PublicKey = _publicKeyInput.Text;
+ }
+
+ WorkTask = DecodeTokenAsync(
+ decoderParameters,
+ tokenParameters,
+ _cancellationTokenSource.Token);
+ }
+
+ private async Task DecodeTokenAsync(
+ DecoderParameters decoderParameters,
+ TokenParameters tokenParameters,
+ CancellationToken cancellationToken)
+ {
+ using (await _semaphore.WaitAsync(cancellationToken))
+ {
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decoderParameters,
+ tokenParameters,
+ _logger,
+ cancellationToken);
+
+ switch (result.Severity)
+ {
+ case ResultInfoSeverity.Success:
+ _infoBar.Close();
+ _headerInput.Text(result.Data!.Header!);
+ _payloadInput.Text(result.Data!.Payload!);
+ _infoBar
+ .Description(JsonWebTokenEncoderDecoder.ValidToken)
+ .Success()
+ .Open();
+ BuildClaimsDataGrid(_payloadInput, _payloadClaimsDataGrid, result.Data.PayloadClaims);
+ break;
+
+ case ResultInfoSeverity.Warning:
+ _headerInput.Text(result.Data!.Header!);
+ _payloadInput.Text(result.Data!.Payload!);
+ _infoBar
+ .Description(result.ErrorMessage)
+ .Warning()
+ .Open();
+ break;
+
+ case ResultInfoSeverity.Error:
+ _infoBar
+ .Description(result.ErrorMessage)
+ .Error()
+ .Open();
+ ClearUI();
+ break;
+
+ default:
+ throw new NotImplementedException();
+ }
+ }
+ }
+
+ private void ConfigureUI()
+ {
+ bool validateToken = _settingsProvider.GetSetting(validateTokenSetting);
+ if (validateToken)
+ {
+ _validateTokenSwitch.On();
+ _signatureInput.Show();
+ _validateTokenIssuerSigningKeySwitch.Enable();
+ _validateTokenIssuersSwitch.Enable();
+ _validateTokenAudiencesSwitch.Enable();
+ _validateLifetimeSwitch.Enable();
+ _validateActorsSwitch.Enable();
+ }
+ else
+ {
+ _validateTokenSwitch.Off();
+ _signatureInput.Hide();
+ _publicKeyInput.Hide();
+ _infoBar.Close();
+ _validateTokenIssuerSigningKeySwitch.Disable();
+ _validateTokenIssuersSwitch.Disable();
+ _validateTokenAudiencesSwitch.Disable();
+ _validateLifetimeSwitch.Disable();
+ _validateActorsSwitch.Disable();
+ }
+
+ ConfigureSwitch(_settingsProvider.GetSetting(validateTokenIssuerSigningKeySetting), _validateTokenIssuerSigningKeySwitch);
+ ConfigureSwitch(_settingsProvider.GetSetting(validateTokenIssuerSetting), _validateTokenIssuersSwitch, _validateTokenIssuersInput);
+ ConfigureSwitch(_settingsProvider.GetSetting(validateTokenAudiencesSetting), _validateTokenAudiencesSwitch, _validateTokenAudiencesInput);
+ ConfigureSwitch(_settingsProvider.GetSetting(validateTokenLifetimeSetting), _validateLifetimeSwitch);
+ ConfigureSwitch(_settingsProvider.GetSetting(validateTokenActorsSetting), _validateActorsSwitch);
+
+ _validateTokenIssuersInput.Text(_settingsProvider.GetSetting(tokenIssuerSetting));
+ _validateTokenAudiencesInput.Text(_settingsProvider.GetSetting(tokenAudiencesSetting));
+
+ _payloadClaimsDataGrid.Hide();
+ }
+
+ private void GetTokenAlgorithm()
+ {
+ bool validateToken = _settingsProvider.GetSetting(validateTokenSetting);
+ if (validateToken && !string.IsNullOrWhiteSpace(_tokenInput.Text))
+ {
+ ResultInfo tokenAlgorithm = JsonWebTokenDecoderHelper.GetTokenAlgorithm(_tokenInput.Text, _logger);
+ if (!tokenAlgorithm.HasSucceeded)
+ {
+ _infoBar
+ .Description(tokenAlgorithm.ErrorMessage)
+ .Error()
+ .Show();
+ return;
+ }
+ _currentAlgorithm = tokenAlgorithm.Data.GetValueOrDefault();
+ if (tokenAlgorithm.Data is JsonWebTokenAlgorithm.HS256 ||
+ tokenAlgorithm.Data is JsonWebTokenAlgorithm.HS384 ||
+ tokenAlgorithm.Data is JsonWebTokenAlgorithm.HS512)
+ {
+ _signatureInput.Show();
+ _publicKeyInput.Hide();
+ }
+ else
+ {
+ _signatureInput.Hide();
+ _publicKeyInput.Show();
+ }
+ }
+ else if (validateToken)
+ {
+ _signatureInput.Hide();
+ _publicKeyInput.Hide();
+ }
+ }
+
+ private void ClearUI()
+ {
+ _headerInput.Text(string.Empty);
+ _payloadInput.Text(string.Empty);
+ }
+
+ private static void BuildClaimsDataGrid(IUIMultiLineTextInput multilineInput, IUIDataGrid dataGrid, List claims)
+ {
+ dataGrid.Rows.Clear();
+ var rows = new List();
+ var tooltips = new List();
+ foreach (JsonWebTokenClaim claim in claims)
+ {
+ IUIDataGridCell typeCell = Cell(claim.Key);
+ IUIDataGridCell valueCell = Cell(claim.Value);
+
+ string? localizedDescription = JsonWebTokenEncoderDecoder.ResourceManager.GetString(claim.Key);
+ if (!string.IsNullOrWhiteSpace(localizedDescription))
+ {
+ rows.Add(Row(null, typeCell, valueCell));
+ UIHoverTooltip tooltip = new(claim.Span, localizedDescription);
+ tooltips.Add(tooltip);
+ }
+ }
+ multilineInput.HoverTooltip(tooltips.ToArray());
+ dataGrid.Rows.AddRange(rows);
+ }
+
+ private static void ConfigureSwitch(bool value, IUISwitch inputSwitch, IUISingleLineTextInput? input = null)
+ {
+ if (value)
+ {
+ inputSwitch.On();
+ if (input != null)
+ {
+ input.Enable();
+ }
+ return;
+ }
+ inputSwitch.Off();
+ if (input != null)
+ {
+ input.Disable();
+ }
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoder.Designer.cs b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoder.Designer.cs
new file mode 100644
index 0000000000..64da467973
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoder.Designer.cs
@@ -0,0 +1,891 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace DevToys.Tools.Tools.EncodersDecoders.JsonWebToken {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class JsonWebTokenEncoderDecoder {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal JsonWebTokenEncoderDecoder() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DevToys.Tools.Tools.EncodersDecoders.JsonWebToken.JsonWebTokenEncoderDecoder", typeof(JsonWebTokenEncoderDecoder).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to JWT Encoder and Decoder tool.
+ ///
+ internal static string AccessibleName {
+ get {
+ return ResourceManager.GetString("AccessibleName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "acr" claim with a value of 0 indicates the end-user authentication didn't meet the requirements of ISO/IEC 29115..
+ ///
+ internal static string acr {
+ get {
+ return ResourceManager.GetString("acr", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "acrs" claim indicates the Auth Context IDs of the operations that the bearer is eligible to perform. Auth Context IDs can be used to trigger a demand for step-up authentication from within your application and services. Often used along with the xms_cc claim..
+ ///
+ internal static string acrs {
+ get {
+ return ResourceManager.GetString("acrs", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "aio" claim is an internal claim used by Microsoft Entra ID to record data for token reuse. Resources shouldn't use this claim..
+ ///
+ internal static string aio {
+ get {
+ return ResourceManager.GetString("aio", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Indicates the algorithm used to sign the token.
+ ///
+ internal static string alg {
+ get {
+ return ResourceManager.GetString("alg", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unknown token algorithm.
+ ///
+ internal static string AlgorithmInvalidEncodeTokenHasDefaultTimeTitle {
+ get {
+ return ResourceManager.GetString("AlgorithmInvalidEncodeTokenHasDefaultTimeTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "amr" claim identifies the authentication method of the subject of the token..
+ ///
+ internal static string amr {
+ get {
+ return ResourceManager.GetString("amr", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "appid" claim indicate the application ID of the client using the token. The application can act as itself or on behalf of a user. The application ID typically represents an application object, but it can also represent a service principal object in Microsoft Entra ID..
+ ///
+ internal static string appid {
+ get {
+ return ResourceManager.GetString("appid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "appidacr" claim indicate the authentication method of the client. For a public client, the value is 0. When you use the client ID and client secret, the value is 1. When you use a client certificate for authentication, the value is 2..
+ ///
+ internal static string appidacr {
+ get {
+ return ResourceManager.GetString("appidacr", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" [rest of string was truncated]";.
+ ///
+ internal static string aud {
+ get {
+ return ResourceManager.GetString("aud", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "azp" claim is a replacement for appid. The application ID of the client using the token. The application can act as itself or on behalf of a user. The application ID typically represents an application object, but it can also represent a service principal object in Microsoft Entra ID..
+ ///
+ internal static string azp {
+ get {
+ return ResourceManager.GetString("azp", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "azpacr" claim is a replacement for appidacr. Indicates the authentication method of the client. For a public client, the value is 0. When you use the client ID and client secret, the value is 1. When you use a client certificate for authentication, the value is 2..
+ ///
+ internal static string azpacr {
+ get {
+ return ResourceManager.GetString("azpacr", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Description.
+ ///
+ internal static string ClaimDescriptionTitle {
+ get {
+ return ResourceManager.GetString("ClaimDescriptionTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Type.
+ ///
+ internal static string ClaimTypeTitle {
+ get {
+ return ResourceManager.GetString("ClaimTypeTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Value.
+ ///
+ internal static string ClaimValueTitle {
+ get {
+ return ResourceManager.GetString("ClaimValueTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Configuration.
+ ///
+ internal static string ConfigurationTitle {
+ get {
+ return ResourceManager.GetString("ConfigurationTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Decode.
+ ///
+ internal static string DecodeMode {
+ get {
+ return ResourceManager.GetString("DecodeMode", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Validate actors.
+ ///
+ internal static string DecodeValidateTokenActorsTitle {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenActorsTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token audiences (separated by comma).
+ ///
+ internal static string DecodeValidateTokenAudiencesInputLabel {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenAudiencesInputLabel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Validate audiences.
+ ///
+ internal static string DecodeValidateTokenAudiencesTitle {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenAudiencesTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token issuer (separated by comma).
+ ///
+ internal static string DecodeValidateTokenIssuerInputLabel {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenIssuerInputLabel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Validate issuer signing key.
+ ///
+ internal static string DecodeValidateTokenIssuerSigningKeyTitle {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenIssuerSigningKeyTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Validate issuer.
+ ///
+ internal static string DecodeValidateTokenIssuerTitle {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenIssuerTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Validate lifetime.
+ ///
+ internal static string DecodeValidateTokenLifetimeTitle {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenLifetimeTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select which token parameters to validate.
+ ///
+ internal static string DecodeValidateTokenSettingsDescription {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenSettingsDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token validation settings.
+ ///
+ internal static string DecodeValidateTokenSettingsTitle {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenSettingsTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Validate Token.
+ ///
+ internal static string DecodeValidateTokenTitle {
+ get {
+ return ResourceManager.GetString("DecodeValidateTokenTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Encode and decode Json Web Token .
+ ///
+ internal static string Description {
+ get {
+ return ResourceManager.GetString("Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Encode.
+ ///
+ internal static string EncodeMode {
+ get {
+ return ResourceManager.GetString("EncodeMode", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token hashing algorithm.
+ ///
+ internal static string EncodeTokenAlgorithmTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenAlgorithmTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token audience (separated by comma).
+ ///
+ internal static string EncodeTokenAudienceInputTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenAudienceInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Expire in day(s).
+ ///
+ internal static string EncodeTokenExpirationDayInputTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenExpirationDayInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Expire in hour(s).
+ ///
+ internal static string EncodeTokenExpirationHourInputTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenExpirationHourInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Expire in minute(s).
+ ///
+ internal static string EncodeTokenExpirationMinuteInputTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenExpirationMinuteInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Expire in month(s).
+ ///
+ internal static string EncodeTokenExpirationMonthInputTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenExpirationMonthInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Expire in year(s).
+ ///
+ internal static string EncodeTokenExpirationYearInputTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenExpirationYearInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token has audience.
+ ///
+ internal static string EncodeTokenHasAudienceTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenHasAudienceTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token has default time.
+ ///
+ internal static string EncodeTokenHasDefaultTimeTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenHasDefaultTimeTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token has expiration.
+ ///
+ internal static string EncodeTokenHasExpirationTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenHasExpirationTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token has issuer.
+ ///
+ internal static string EncodeTokenHasIssuerTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenHasIssuerTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token issuer (separated by comma).
+ ///
+ internal static string EncodeTokenIssuerInputTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenIssuerInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select token parameters.
+ ///
+ internal static string EncodeTokenSettingsDescription {
+ get {
+ return ResourceManager.GetString("EncodeTokenSettingsDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Settings.
+ ///
+ internal static string EncodeTokenSettingsTitle {
+ get {
+ return ResourceManager.GetString("EncodeTokenSettingsTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL..
+ ///
+ internal static string exp {
+ get {
+ return ResourceManager.GetString("exp", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "groups" claim provides object IDs that represent the group memberships of the subject. The groupMembershipClaims property of the application manifest configures the groups claim on a per-application basis. A value of null excludes all groups, a value of SecurityGroup includes only Active Directory Security Group memberships, and a value of All includes both Security Groups and Microsoft 365 Distribution Lists.See the hasgroups claim for details on using the groups claim with the implicit grant. For oth [rest of string was truncated]";.
+ ///
+ internal static string groups {
+ get {
+ return ResourceManager.GetString("groups", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "groups:src1" claim includes a link to the full groups list for the user when token requests are too large for the token. For JWTs as a distributed claim, for SAML as a new claim in place of the groups claim.Example JWT Value:"groups":"src1""_claim_sources: "src1" : { "endpoint" : "https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects" }.
+ ///
+ internal static string groups_src1 {
+ get {
+ return ResourceManager.GetString("groups:src1", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to If present, always true, indicates whether the user is in at least one group. Used in place of the groups claim for JWTs in implicit grant flows if the full groups claim would extend the URI fragment beyond the URL length limits (currently six or more groups). Indicates that the client should use the Microsoft Graph API to determine the groups (https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects) of the user..
+ ///
+ internal static string hasgroups {
+ get {
+ return ResourceManager.GetString("hasgroups", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Header.
+ ///
+ internal static string HeaderInputTitle {
+ get {
+ return ResourceManager.GetString("HeaderInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "iat" (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL..
+ ///
+ internal static string iat {
+ get {
+ return ResourceManager.GetString("iat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "idp" claim records the identity provider that authenticated the subject of the token. This value is identical to the value of the Issuer claim unless the user account isn't in the same tenant as the issuer, such as guests. Use the value of iss if the claim isn't present..
+ ///
+ internal static string idp {
+ get {
+ return ResourceManager.GetString("idp", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Expiartion Date is invalid.
+ ///
+ internal static string InvalidExpiration {
+ get {
+ return ResourceManager.GetString("InvalidExpiration", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unable to process the token header.
+ ///
+ internal static string InvalidHeader {
+ get {
+ return ResourceManager.GetString("InvalidHeader", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unable to process the token payload.
+ ///
+ internal static string InvalidPayload {
+ get {
+ return ResourceManager.GetString("InvalidPayload", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Private key is either null or empty.
+ ///
+ internal static string InvalidPrivateKey {
+ get {
+ return ResourceManager.GetString("InvalidPrivateKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Public key is either null or empty.
+ ///
+ internal static string InvalidPublicKey {
+ get {
+ return ResourceManager.GetString("InvalidPublicKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unable to process the token signature.
+ ///
+ internal static string InvalidSignature {
+ get {
+ return ResourceManager.GetString("InvalidSignature", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL..
+ ///
+ internal static string iss {
+ get {
+ return ResourceManager.GetString("iss", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case- sensitive string. Use of this claim is OPTIONAL [rest of string was truncated]";.
+ ///
+ internal static string jti {
+ get {
+ return ResourceManager.GetString("jti", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Specifies the thumbprint for the public key used for validating the signature of the token..
+ ///
+ internal static string kid {
+ get {
+ return ResourceManager.GetString("kid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to JWT Encoder / Decoder.
+ ///
+ internal static string LongDisplayTitle {
+ get {
+ return ResourceManager.GetString("LongDisplayTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "name" claim provides a human-readable value that identifies the subject of the token. The value can vary, it's mutable, and is for display purposes only. To receive this claim, use the profile scope..
+ ///
+ internal static string name {
+ get {
+ return ResourceManager.GetString("name", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL..
+ ///
+ internal static string nbf {
+ get {
+ return ResourceManager.GetString("nbf", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No.
+ ///
+ internal static string No {
+ get {
+ return ResourceManager.GetString("No", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The immutable identifier for the requestor, which is the verified identity of the user or service principal. This ID uniquely identifies the requestor across applications. Two different applications signing in the same user receive the same value in the oid claim. The oid can be used when making queries to Microsoft online services, such as the Microsoft Graph. The Microsoft Graph returns this ID as the id property for a given user account. Because the oid allows multiple applications to correlate principal [rest of string was truncated]";.
+ ///
+ internal static string oid {
+ get {
+ return ResourceManager.GetString("oid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Payload.
+ ///
+ internal static string PayloadInputTitle {
+ get {
+ return ResourceManager.GetString("PayloadInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "preferred_username" claim indicate the primary username that represents the user. The value could be an email address, phone number, or a generic username without a specified format. Use the value for username hints and in human-readable UI as a username. To receive this claim, use the profile scope..
+ ///
+ internal static string preferred_username {
+ get {
+ return ResourceManager.GetString("preferred_username", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Private Key.
+ ///
+ internal static string PrivateKeyInputTitle {
+ get {
+ return ResourceManager.GetString("PrivateKeyInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This type of privatec key is not supported.
+ ///
+ internal static string PrivateKeyNotSupported {
+ get {
+ return ResourceManager.GetString("PrivateKeyNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Public Key.
+ ///
+ internal static string PublicKeyInputTitle {
+ get {
+ return ResourceManager.GetString("PublicKeyInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to This type of public key is not supported.
+ ///
+ internal static string PublicKeyNotSupported {
+ get {
+ return ResourceManager.GetString("PublicKeyNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "rh" claim is an internal claim used by Azure to revalidate tokens. Resources shouldn't use this claim..
+ ///
+ internal static string rh {
+ get {
+ return ResourceManager.GetString("rh", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "roles" claim indicate the set of permissions exposed by the application that the requesting application or user has been given permission to call. The client credential flow uses this set of permission in place of user scopes for application tokens. For user tokens, this set of values contains the assigned roles of the user on the target application..
+ ///
+ internal static string roles {
+ get {
+ return ResourceManager.GetString("roles", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "scp" claim indicate the set of scopes exposed by the application for which the client application has requested (and received) consent. Only included for user tokens..
+ ///
+ internal static string scp {
+ get {
+ return ResourceManager.GetString("scp", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to JWT.
+ ///
+ internal static string ShortDisplayTitle {
+ get {
+ return ResourceManager.GetString("ShortDisplayTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Signature.
+ ///
+ internal static string SignatureInputTitle {
+ get {
+ return ResourceManager.GetString("SignatureInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The "sub" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL..
+ ///
+ internal static string sub {
+ get {
+ return ResourceManager.GetString("sub", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "tid" claim represents the tenant that the user is signing in to. For work and school accounts, the GUID is the immutable tenant ID of the organization that the user is signing in to..
+ ///
+ internal static string tid {
+ get {
+ return ResourceManager.GetString("tid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token.
+ ///
+ internal static string TokenInputTitle {
+ get {
+ return ResourceManager.GetString("TokenInputTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token not validated (no parameters selected).
+ ///
+ internal static string TokenNotValidated {
+ get {
+ return ResourceManager.GetString("TokenNotValidated", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select which mode you want to use.
+ ///
+ internal static string ToolModeDescription {
+ get {
+ return ResourceManager.GetString("ToolModeDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Tool Mode.
+ ///
+ internal static string ToolModeTitle {
+ get {
+ return ResourceManager.GetString("ToolModeTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Indicates the token type.
+ ///
+ internal static string typ {
+ get {
+ return ResourceManager.GetString("typ", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "unique_name" claim provides a human readable value that identifies the subject of the token..
+ ///
+ internal static string unique_name {
+ get {
+ return ResourceManager.GetString("unique_name", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "uti" claim (Token identifier claim), is equivalent to "jti" in the JWT specification. Unique, per-token identifier that is case-sensitive..
+ ///
+ internal static string uti {
+ get {
+ return ResourceManager.GetString("uti", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Audiences are empty or invalid.
+ ///
+ internal static string ValidAudiencesEmptyError {
+ get {
+ return ResourceManager.GetString("ValidAudiencesEmptyError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Issuers are empty or invalid.
+ ///
+ internal static string ValidIssuersEmptyError {
+ get {
+ return ResourceManager.GetString("ValidIssuersEmptyError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Token Validated.
+ ///
+ internal static string ValidToken {
+ get {
+ return ResourceManager.GetString("ValidToken", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "ver" claim indicates the version of the access token..
+ ///
+ internal static string ver {
+ get {
+ return ResourceManager.GetString("ver", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "wids" claim denotes the tenant-wide roles assigned to this user, from the section of roles present in Microsoft Entra built-in roles. The groupMembershipClaims property of the application manifest configures this claim on a per-application basis. Set the claim to All or DirectoryRole. May not be present in tokens obtained through the implicit flow due to token length concerns..
+ ///
+ internal static string wids {
+ get {
+ return ResourceManager.GetString("wids", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Functions the same as "kid". "x5t" is a legacy claim emitted only in v1.0 access tokens for compatibility purposes..
+ ///
+ internal static string x5t {
+ get {
+ return ResourceManager.GetString("x5t", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The "xms_cc" claim indicates whether the client application that acquired the token is capable of handling claims challenges. It's often used along with claim acrs. This claim is commonly used in Conditional Access and Continuous Access Evaluation scenarios. The resource server or service application that the token is issued for controls the presence of this claim in a token. A value of cp1 in the access token is the authoritative way to identify that a client application is capable of handling a claims cha [rest of string was truncated]";.
+ ///
+ internal static string xms_cc {
+ get {
+ return ResourceManager.GetString("xms_cc", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Yes.
+ ///
+ internal static string Yes {
+ get {
+ return ResourceManager.GetString("Yes", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoder.resx b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoder.resx
new file mode 100644
index 0000000000..b8f268f909
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoder.resx
@@ -0,0 +1,488 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ JWT Encoder and Decoder tool
+ Gui
+
+
+ The "acr" claim with a value of 0 indicates the end-user authentication didn't meet the requirements of ISO/IEC 29115.
+ Claim (Microsoft)
+
+
+ The "acrs" claim indicates the Auth Context IDs of the operations that the bearer is eligible to perform. Auth Context IDs can be used to trigger a demand for step-up authentication from within your application and services. Often used along with the xms_cc claim.
+ Claim (Microsoft)
+
+
+ The "aio" claim is an internal claim used by Microsoft Entra ID to record data for token reuse. Resources shouldn't use this claim.
+ Claim (Microsoft)
+
+
+ Indicates the algorithm used to sign the token
+ Claim
+
+
+ Unknown token algorithm
+ Error message
+
+
+ The "amr" claim identifies the authentication method of the subject of the token.
+ Claim (Microsoft)
+
+
+ The "appid" claim indicate the application ID of the client using the token. The application can act as itself or on behalf of a user. The application ID typically represents an application object, but it can also represent a service principal object in Microsoft Entra ID.
+ Claim (Microsoft)
+
+
+ The "appidacr" claim indicate the authentication method of the client. For a public client, the value is 0. When you use the client ID and client secret, the value is 1. When you use a client certificate for authentication, the value is 2.
+ Claim (Microsoft)
+
+
+ The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
+ Claim
+
+
+ The "azp" claim is a replacement for appid. The application ID of the client using the token. The application can act as itself or on behalf of a user. The application ID typically represents an application object, but it can also represent a service principal object in Microsoft Entra ID.
+ Claim (Microsoft)
+
+
+ The "azpacr" claim is a replacement for appidacr. Indicates the authentication method of the client. For a public client, the value is 0. When you use the client ID and client secret, the value is 1. When you use a client certificate for authentication, the value is 2.
+ Claim (Microsoft)
+
+
+ Description
+ Gui
+
+
+ Type
+ Gui
+
+
+ Value
+ Gui
+
+
+ Configuration
+ Gui
+
+
+ Decode
+ Gui
+
+
+ Validate actors
+ Gui
+
+
+ Token audiences (separated by comma)
+ Gui
+
+
+ Validate audiences
+ Gui
+
+
+ Token issuer (separated by comma)
+ Gui
+
+
+ Validate issuer signing key
+ Gui
+
+
+ Validate issuer
+ Gui
+
+
+ Validate lifetime
+ Gui
+
+
+ Select which token parameters to validate
+ Gui
+
+
+ Token validation settings
+ Gui
+
+
+ Validate Token
+ Gui
+
+
+ Encode and decode Json Web Token
+ Gui
+
+
+ Encode
+ Gui
+
+
+ Token hashing algorithm
+ Gui
+
+
+ Token audience (separated by comma)
+ Gui
+
+
+ Expire in day(s)
+ Gui
+
+
+ Expire in hour(s)
+ Gui
+
+
+ Expire in minute(s)
+ Gui
+
+
+ Expire in month(s)
+ Gui
+
+
+ Expire in year(s)
+ Gui
+
+
+ Token has audience
+ Gui
+
+
+ Token has expiration
+ Gui
+
+
+ Token has issuer
+ Gui
+
+
+ Token issuer (separated by comma)
+ Gui
+
+
+ Select token parameters
+ Gui
+
+
+ Settings
+ Gui
+
+
+ The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
+ Claim
+
+
+ The "groups" claim provides object IDs that represent the group memberships of the subject. The groupMembershipClaims property of the application manifest configures the groups claim on a per-application basis. A value of null excludes all groups, a value of SecurityGroup includes only Active Directory Security Group memberships, and a value of All includes both Security Groups and Microsoft 365 Distribution Lists.See the hasgroups claim for details on using the groups claim with the implicit grant. For other flows, if the number of groups the user is in goes over 150 for SAML and 200 for JWT, then Microsoft Entra ID adds an overage claim to the claim sources. The claim sources point to the Microsoft Graph endpoint that contains the list of groups for the user.
+ Claim (Microsoft)
+
+
+ The "groups:src1" claim includes a link to the full groups list for the user when token requests are too large for the token. For JWTs as a distributed claim, for SAML as a new claim in place of the groups claim.Example JWT Value:"groups":"src1""_claim_sources: "src1" : { "endpoint" : "https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects" }
+ Claim (Microsoft)
+
+
+ If present, always true, indicates whether the user is in at least one group. Used in place of the groups claim for JWTs in implicit grant flows if the full groups claim would extend the URI fragment beyond the URL length limits (currently six or more groups). Indicates that the client should use the Microsoft Graph API to determine the groups (https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects) of the user.
+ Claim (Microsoft)
+
+
+ Header
+ Gui
+
+
+ The "iat" (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
+ Claim
+
+
+ The "idp" claim records the identity provider that authenticated the subject of the token. This value is identical to the value of the Issuer claim unless the user account isn't in the same tenant as the issuer, such as guests. Use the value of iss if the claim isn't present.
+ Claim (Microsoft)
+
+
+ Expiartion Date is invalid
+ Error message
+
+
+ The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
+ Claim
+
+
+ The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case- sensitive string. Use of this claim is OPTIONAL.
+ Claim
+
+
+ Specifies the thumbprint for the public key used for validating the signature of the token.
+ Claim
+
+
+ JWT Encoder / Decoder
+ Gui
+
+
+ The "name" claim provides a human-readable value that identifies the subject of the token. The value can vary, it's mutable, and is for display purposes only. To receive this claim, use the profile scope.
+ Claim (Microsoft)
+
+
+ The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
+ Claim
+
+
+ No
+ Gui
+
+
+ The immutable identifier for the requestor, which is the verified identity of the user or service principal. This ID uniquely identifies the requestor across applications. Two different applications signing in the same user receive the same value in the oid claim. The oid can be used when making queries to Microsoft online services, such as the Microsoft Graph. The Microsoft Graph returns this ID as the id property for a given user account. Because the oid allows multiple applications to correlate principals, to receive this claim for users use the profile scope. If a single user exists in multiple tenants, the user contains a different object ID in each tenant. Even though the user logs into each account with the same credentials, the accounts are different.
+ Claim (Microsoft)
+
+
+ Payload
+ Gui
+
+
+ The "preferred_username" claim indicate the primary username that represents the user. The value could be an email address, phone number, or a generic username without a specified format. Use the value for username hints and in human-readable UI as a username. To receive this claim, use the profile scope.
+ Claim (Microsoft)
+
+
+ This type of privatec key is not supported
+ Error message
+
+
+ Public Key
+ Gui
+
+
+ This type of public key is not supported
+ Error message
+
+
+ The "rh" claim is an internal claim used by Azure to revalidate tokens. Resources shouldn't use this claim.
+ Claim (Microsoft)
+
+
+ The "roles" claim indicate the set of permissions exposed by the application that the requesting application or user has been given permission to call. The client credential flow uses this set of permission in place of user scopes for application tokens. For user tokens, this set of values contains the assigned roles of the user on the target application.
+ Claim (Microsoft)
+
+
+ The "scp" claim indicate the set of scopes exposed by the application for which the client application has requested (and received) consent. Only included for user tokens.
+ Claim (Microsoft)
+
+
+ JWT
+ Gui
+
+
+ Signature
+ Gui
+
+
+ The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The "sub" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
+ Claim
+
+
+ The "tid" claim represents the tenant that the user is signing in to. For work and school accounts, the GUID is the immutable tenant ID of the organization that the user is signing in to.
+ Claim (Microsoft)
+
+
+ Token
+ Gui
+
+
+ Token not validated (no parameters selected)
+ Error message
+
+
+ Select which mode you want to use
+ Gui
+
+
+ Tool Mode
+ Gui
+
+
+ Indicates the token type
+ Claim
+
+
+ The "unique_name" claim provides a human readable value that identifies the subject of the token.
+ Claim (Microsoft)
+
+
+ The "uti" claim (Token identifier claim), is equivalent to "jti" in the JWT specification. Unique, per-token identifier that is case-sensitive.
+ Claim (Microsoft)
+
+
+ Audiences are empty or invalid
+ Error message
+
+
+ Issuers are empty or invalid
+ Error message
+
+
+ The "ver" claim indicates the version of the access token.
+ Claim (Microsoft)
+
+
+ The "wids" claim denotes the tenant-wide roles assigned to this user, from the section of roles present in Microsoft Entra built-in roles. The groupMembershipClaims property of the application manifest configures this claim on a per-application basis. Set the claim to All or DirectoryRole. May not be present in tokens obtained through the implicit flow due to token length concerns.
+ Claim (Microsoft)
+
+
+ Functions the same as "kid". "x5t" is a legacy claim emitted only in v1.0 access tokens for compatibility purposes.
+ Claim
+
+
+ The "xms_cc" claim indicates whether the client application that acquired the token is capable of handling claims challenges. It's often used along with claim acrs. This claim is commonly used in Conditional Access and Continuous Access Evaluation scenarios. The resource server or service application that the token is issued for controls the presence of this claim in a token. A value of cp1 in the access token is the authoritative way to identify that a client application is capable of handling a claims challenge.
+ Claim (Microsoft)
+
+
+ Yes
+ Gui
+
+
+ Token has default time
+ Gui
+
+
+ Unable to process the token header
+ Error message
+
+
+ Unable to process the token payload
+ Error message
+
+
+ Private key is either null or empty
+ Error message
+
+
+ Public key is either null or empty
+ Error message
+
+
+ Private Key
+ Gui
+
+
+ Token Validated
+ Gui
+
+
+ Unable to process the token signature
+ Error message
+
+
\ No newline at end of file
diff --git a/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoderGuiTool.cs b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoderGuiTool.cs
new file mode 100644
index 0000000000..243a69d358
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoderGuiTool.cs
@@ -0,0 +1,164 @@
+using DevToys.Tools.Models;
+using Microsoft.Extensions.Logging;
+
+namespace DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+
+[Export(typeof(IGuiTool))]
+[Name("JsonWebTokenEncoderDecoder")]
+[ToolDisplayInformation(
+ IconFontName = "DevToys-Tools-Icons",
+ IconGlyph = '\u0110',
+ GroupName = PredefinedCommonToolGroupNames.EncodersDecoders,
+ ResourceManagerAssemblyIdentifier = nameof(DevToysToolsResourceManagerAssemblyIdentifier),
+ ResourceManagerBaseName = "DevToys.Tools.Tools.EncodersDecoders.JsonWebToken.JsonWebTokenEncoderDecoder",
+ ShortDisplayTitleResourceName = nameof(JsonWebTokenEncoderDecoder.ShortDisplayTitle),
+ LongDisplayTitleResourceName = nameof(JsonWebTokenEncoderDecoder.LongDisplayTitle),
+ DescriptionResourceName = nameof(JsonWebTokenEncoderDecoder.Description),
+ AccessibleNameResourceName = nameof(JsonWebTokenEncoderDecoder.AccessibleName))]
+internal sealed partial class JsonWebTokenEncoderDecoderGuiTool : IGuiTool, IDisposable
+{
+ ///
+ /// Whether the tool should encode or decode JWT Token.
+ ///
+ private static readonly SettingDefinition toolModeSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderDecoderGuiTool)}.{nameof(toolModeSetting)}",
+ defaultValue: JwtMode.Decode);
+
+ private readonly ILogger _logger;
+ private readonly ISettingsProvider _settingsProvider;
+ internal readonly JsonWebTokenDecoderGuiTool DecoderGuiTool;
+ internal readonly JsonWebTokenEncoderGuiTool EncoderGuiTool;
+
+ private readonly IUISwitch _conversionModeSwitch = Switch("jwt-token-conversion-mode-switch");
+
+ private readonly IUIGrid _jwtEncoderDecoderGuiGrid = Grid("jwt-encoder-decoder-grid");
+
+ [ImportingConstructor]
+ public JsonWebTokenEncoderDecoderGuiTool(ISettingsProvider settingsProvider)
+ {
+ _logger = this.Log();
+ _settingsProvider = settingsProvider;
+ DecoderGuiTool = new(_settingsProvider);
+ EncoderGuiTool = new(_settingsProvider);
+
+ JwtMode value = _settingsProvider.GetSetting(toolModeSetting);
+ if (value is JwtMode.Encode)
+ {
+ _conversionModeSwitch.On();
+ }
+ else
+ {
+ _conversionModeSwitch.Off();
+ }
+ LoadChildView();
+ }
+
+ // For unit tests.
+ internal Task? WorkTask { get; private set; }
+
+ public UIToolView View
+ => new(
+ isScrollable: true,
+ _jwtEncoderDecoderGuiGrid
+ .RowLargeSpacing()
+ .Rows(
+ (JsonWebTokenGridRows.Settings, Auto),
+ (JsonWebTokenGridRows.SubContainer, new UIGridLength(1, UIGridUnitType.Fraction))
+ )
+ .Columns(
+ (GridColumns.Stretch, new UIGridLength(1, UIGridUnitType.Fraction))
+ )
+ .Cells(
+ Cell(
+ JsonWebTokenGridRows.Settings,
+ GridColumns.Stretch,
+ Stack()
+ .Vertical()
+ .SmallSpacing()
+ .WithChildren(
+ Label()
+ .Text(JsonWebTokenEncoderDecoder.ConfigurationTitle),
+ Setting("jwt-token-conversion-mode-setting")
+ .Icon("FluentSystemIcons", '\uF18D')
+ .Title(JsonWebTokenEncoderDecoder.ToolModeTitle)
+ .Description(JsonWebTokenEncoderDecoder.ToolModeDescription)
+ .InteractiveElement(
+ _conversionModeSwitch
+ .OnText(JsonWebTokenEncoderDecoder.EncodeMode)
+ .OffText(JsonWebTokenEncoderDecoder.DecodeMode)
+ .OnToggle(OnConversionModeChanged)
+ )
+ )
+ ),
+ Cell(
+ JsonWebTokenGridRows.SubContainer,
+ GridColumns.Stretch,
+ Stack()
+ .Vertical()
+ .WithChildren(
+ DecoderGuiTool.ViewStack,
+ EncoderGuiTool.ViewStack
+ )
+ )
+ )
+ );
+
+ public void OnDataReceived(string dataTypeName, object? parsedData)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ // Todo need to dispose child view too?
+ }
+
+ private void OnConversionModeChanged(bool toolMode)
+ {
+ _settingsProvider.SetSetting(toolModeSetting, toolMode ? JwtMode.Encode : JwtMode.Decode);
+ LoadChildView();
+ }
+
+ private IUIGridCell EncodeDecodeSettings()
+ => Cell(
+ JsonWebTokenGridRows.Settings,
+ GridColumns.Stretch,
+ Stack()
+ .Vertical()
+ .SmallSpacing()
+ .WithChildren(
+ Label()
+ .Text(JsonWebTokenEncoderDecoder.ConfigurationTitle),
+ Setting("jwt-token-conversion-mode-setting")
+ .Icon("FluentSystemIcons", '\uF18D')
+ .Title(JsonWebTokenEncoderDecoder.ToolModeTitle)
+ .Description(JsonWebTokenEncoderDecoder.ToolModeDescription)
+ .InteractiveElement(
+ _conversionModeSwitch
+ .OnText(JsonWebTokenEncoderDecoder.EncodeMode)
+ .OffText(JsonWebTokenEncoderDecoder.DecodeMode)
+ .OnToggle(OnConversionModeChanged)
+ )
+ )
+ );
+
+ private void LoadChildView()
+ {
+ switch (_settingsProvider.GetSetting(toolModeSetting))
+ {
+ case JwtMode.Encode:
+ DecoderGuiTool.Hide();
+ EncoderGuiTool.Show();
+ break;
+
+ case JwtMode.Decode:
+ EncoderGuiTool.Hide();
+ DecoderGuiTool.Show();
+ break;
+
+ default:
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoderLayoutEnums.cs b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoderLayoutEnums.cs
new file mode 100644
index 0000000000..f135e6e2b2
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderDecoderLayoutEnums.cs
@@ -0,0 +1,26 @@
+namespace DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+
+internal enum JsonWebTokenGridRows
+{
+ Settings,
+ SubContainer
+}
+
+internal enum GridColumns
+{
+ Stretch
+}
+
+internal enum JsonWebTokenExpirationGridRow
+{
+ Content
+}
+
+internal enum JsonWebTokenExpirationGridColumn
+{
+ Year,
+ Month,
+ Day,
+ Hour,
+ Minute
+}
diff --git a/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderGuiTool.cs b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderGuiTool.cs
new file mode 100644
index 0000000000..88c2f1781e
--- /dev/null
+++ b/src/app/dev/DevToys.Tools/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderGuiTool.cs
@@ -0,0 +1,605 @@
+using DevToys.Tools.Helpers;
+using DevToys.Tools.Helpers.JsonWebToken;
+using DevToys.Tools.Models;
+using Microsoft.Extensions.Logging;
+
+namespace DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+
+internal sealed partial class JsonWebTokenEncoderGuiTool
+{
+ ///
+ /// Define if the token algorithm
+ ///
+ private static readonly SettingDefinition tokenAlgorithmSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenAlgorithmSetting)}",
+ defaultValue: JsonWebTokenAlgorithm.HS256);
+
+ ///
+ /// Define if the token has an issuer or not
+ ///
+ private static readonly SettingDefinition tokenHasIssuerSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenHasIssuerSetting)}",
+ defaultValue: false);
+
+ ///
+ /// Define the issuer list
+ ///
+ private static readonly SettingDefinition tokenIssuerSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenIssuerSetting)}",
+ defaultValue: string.Empty);
+
+ ///
+ /// Define if the token has an audience or not
+ ///
+ private static readonly SettingDefinition tokenHasAudienceSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenHasAudienceSetting)}",
+ defaultValue: false);
+
+ ///
+ /// Define the audience list
+ ///
+ private static readonly SettingDefinition tokenAudienceSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenAudienceSetting)}",
+ defaultValue: string.Empty);
+
+ ///
+ /// Define if the token has an expiration date or not
+ ///
+ private static readonly SettingDefinition tokenHasDefaultTimeSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenHasDefaultTimeSetting)}",
+ defaultValue: false);
+
+ ///
+ /// Define if the token has an expiration date or not
+ ///
+ private static readonly SettingDefinition tokenHasExpirationSetting
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenHasExpirationSetting)}",
+ defaultValue: false);
+
+ ///
+ /// The Expiration to use.
+ ///
+ private static readonly SettingDefinition tokenExpirationSettings
+ = new(
+ name: $"{nameof(JsonWebTokenEncoderGuiTool)}.{nameof(tokenExpirationSettings)}",
+ defaultValue: DateTime.UtcNow);
+
+ private readonly ILogger _logger;
+ private readonly ISettingsProvider _settingsProvider;
+
+ private readonly IUIInfoBar _infoBar = InfoBar("jwt-encode-info-bar");
+ private readonly IUISwitch _encodeTokenHasIssuerSwitch = Switch("jwt-encode-token-issuer-switch");
+ private readonly IUISwitch _encodeTokenHasAudienceSwitch = Switch("jwt-encode-token-audience-switch");
+ private readonly IUISingleLineTextInput _encodeTokenIssuerInput = SingleLineTextInput("jwt-encode-token-issuer-input");
+ private readonly IUISingleLineTextInput _encodeTokenAudienceInput = SingleLineTextInput("jwt-encode-token-audience-input");
+
+ private readonly IUISwitch _encodeTokenHasExpirationSwitch = Switch("jwt-encode-token-expiration-switch");
+ private readonly IUIGrid _encodeTokenExpirationGrid = Grid("jwt-encode-token-expiration-grid");
+ private readonly IUINumberInput _encodeTokenExpirationYearInputNumber = NumberInput("jwt-encode-token-expiration-input-year");
+ private readonly IUINumberInput _encodeTokenExpirationMonthInputNumber = NumberInput("jwt-encode-token-expiration-input-month");
+ private readonly IUINumberInput _encodeTokenExpirationDayInputNumber = NumberInput("jwt-encode-token-expiration-input-day");
+ private readonly IUINumberInput _encodeTokenExpirationHourInputNumber = NumberInput("jwt-encode-token-expiration-input-hour");
+ private readonly IUINumberInput _encodeTokenExpirationMinuteInputNumber = NumberInput("jwt-encode-token-expiration-input-minute");
+ private readonly IUINumberInput _encodeTokenExpirationSecondsInputNumber = NumberInput("jwt-encode-token-expiration-input-second");
+ private readonly IUINumberInput _encodeTokenExpirationMillisecondsInputNumber = NumberInput("jwt-encode-token-expiration-input-millisecond");
+
+ private readonly IUISwitch _encodeTokenDefaultTimeSwitch = Switch("jwt-encode-token-default-time-switch");
+
+ private readonly IUIStack _viewStack = Stack("jwt-encode-view-stack");
+ private readonly IUIStack _encodeSettingsStack = Stack("jwt-encode-settings-stack");
+
+ private readonly IUIMultiLineTextInput _tokenInput = MultilineTextInput("jwt-encode-token-input");
+ private readonly IUIMultiLineTextInput _signatureInput = MultilineTextInput("jwt-encode-signature-input");
+ private readonly IUIMultiLineTextInput _privateKeyInput = MultilineTextInput("jwt-encode-private-key-input");
+ private readonly IUIMultiLineTextInput _headerInput = MultilineTextInput("jwt-encode-header-input", "json");
+ private readonly IUIMultiLineTextInput _payloadInput = MultilineTextInput("jwt-encode-payload-input", "json");
+
+ private DisposableSemaphore _semaphore = new();
+ private CancellationTokenSource? _cancellationTokenSource;
+
+ [ImportingConstructor]
+ public JsonWebTokenEncoderGuiTool(ISettingsProvider settingsProvider)
+ {
+ _logger = this.Log();
+ _settingsProvider = settingsProvider;
+ ConfigureUIAsync();
+ }
+
+ internal Task? WorkTask { get; private set; }
+
+ public IUIStack ViewStack
+ => _viewStack
+ .Vertical()
+ .WithChildren(
+ SettingGroup("jwt-encode-validate-token-setting")
+ .Icon("FluentSystemIcons", '\uec9e')
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenSettingsTitle)
+ .Description(JsonWebTokenEncoderDecoder.EncodeTokenSettingsDescription)
+ .WithChildren(
+ Setting("jwt-encode-token-algorithm-setting")
+ .Icon("FluentSystemIcons", '\uF1EE')
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenAlgorithmTitle)
+ .Handle(
+ _settingsProvider,
+ tokenAlgorithmSetting,
+ onOptionSelected: async (value) => await OnAlgorithmChangedAsync(value),
+ Item(JsonWebTokenAlgorithm.HS256),
+ Item(JsonWebTokenAlgorithm.HS384),
+ Item(JsonWebTokenAlgorithm.HS512),
+ Item(JsonWebTokenAlgorithm.RS256),
+ Item(JsonWebTokenAlgorithm.RS384),
+ Item(JsonWebTokenAlgorithm.RS512),
+ Item(JsonWebTokenAlgorithm.PS256),
+ Item(JsonWebTokenAlgorithm.PS384),
+ Item(JsonWebTokenAlgorithm.PS512),
+ Item(JsonWebTokenAlgorithm.ES256),
+ Item(JsonWebTokenAlgorithm.ES384),
+ Item(JsonWebTokenAlgorithm.ES512)
+ ),
+ SettingGroup("jwt-encode-token-issuer-setting-group")
+ .Icon("FluentSystemIcons", '\ue30a')
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenHasIssuerTitle)
+ .InteractiveElement(
+ _encodeTokenHasIssuerSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnTokenHasIssuerChanged)
+ )
+ .WithChildren(
+ _encodeTokenIssuerInput
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenIssuerInputTitle)
+ .OnTextChanged(OnTokenIssuerInputChanged)
+ ),
+ SettingGroup("jwt-encode-token-audience-setting-group")
+ .Icon("FluentSystemIcons", '\ue30a')
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenHasAudienceTitle)
+ .InteractiveElement(
+ _encodeTokenHasAudienceSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnTokenHasAudienceChanged)
+ )
+ .WithChildren(
+ _encodeTokenAudienceInput
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenAudienceInputTitle)
+ .OnTextChanged(OnTokenAudienceInputChanged)
+ ),
+ SettingGroup("jwt-encode-token-expiration-setting-group")
+ .Icon("FluentSystemIcons", '\ue243')
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenHasExpirationTitle)
+ .InteractiveElement(
+ _encodeTokenHasExpirationSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnTokenHasExpirationChanged)
+ )
+ .WithChildren(
+ _encodeTokenExpirationGrid
+ .RowSmallSpacing()
+ .ColumnSmallSpacing()
+ .Rows(
+ (JsonWebTokenExpirationGridRow.Content, Auto)
+ )
+ .Columns(
+ (JsonWebTokenExpirationGridColumn.Year, new UIGridLength(1, UIGridUnitType.Fraction)),
+ (JsonWebTokenExpirationGridColumn.Month, new UIGridLength(1, UIGridUnitType.Fraction)),
+ (JsonWebTokenExpirationGridColumn.Day, new UIGridLength(1, UIGridUnitType.Fraction)),
+ (JsonWebTokenExpirationGridColumn.Hour, new UIGridLength(1, UIGridUnitType.Fraction)),
+ (JsonWebTokenExpirationGridColumn.Minute, new UIGridLength(1, UIGridUnitType.Fraction))
+ )
+ .Cells(
+ Cell(
+ JsonWebTokenExpirationGridRow.Content,
+ JsonWebTokenExpirationGridColumn.Year,
+ Stack()
+ .Vertical()
+ .SmallSpacing()
+ .WithChildren(
+ _encodeTokenExpirationYearInputNumber
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenExpirationYearInputTitle)
+ .HideCommandBar()
+ .OnTextChanged((value) =>
+ {
+ OnExpirationChanged(value, DateValueType.Year);
+ })
+ .Minimum(0)
+ .Maximum(9999)
+ )
+ ),
+ Cell(
+ JsonWebTokenExpirationGridRow.Content,
+ JsonWebTokenExpirationGridColumn.Month,
+ Stack()
+ .Vertical()
+ .SmallSpacing()
+ .WithChildren(
+ _encodeTokenExpirationMonthInputNumber
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenExpirationMonthInputTitle)
+ .HideCommandBar()
+ .OnTextChanged((value) =>
+ {
+ OnExpirationChanged(value, DateValueType.Month);
+ })
+ .Minimum(1)
+ .Maximum(12)
+ )
+ ),
+ Cell(
+ JsonWebTokenExpirationGridRow.Content,
+ JsonWebTokenExpirationGridColumn.Day,
+ Stack()
+ .Vertical()
+ .SmallSpacing()
+ .WithChildren(
+ _encodeTokenExpirationDayInputNumber
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenExpirationDayInputTitle)
+ .HideCommandBar()
+ .OnTextChanged((value) =>
+ {
+ OnExpirationChanged(value, DateValueType.Day);
+ })
+ .Minimum(1)
+ .Maximum(31)
+ )
+ ),
+ Cell(
+ JsonWebTokenExpirationGridRow.Content,
+ JsonWebTokenExpirationGridColumn.Hour,
+ Stack()
+ .Vertical()
+ .SmallSpacing()
+ .WithChildren(
+ _encodeTokenExpirationHourInputNumber
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenExpirationHourInputTitle)
+ .HideCommandBar()
+ .OnTextChanged((value) =>
+ {
+ OnExpirationChanged(value, DateValueType.Hour);
+ })
+ .Minimum(0)
+ .Maximum(24)
+ )
+ ),
+ Cell(
+ JsonWebTokenExpirationGridRow.Content,
+ JsonWebTokenExpirationGridColumn.Minute,
+ Stack()
+ .Vertical()
+ .SmallSpacing()
+ .WithChildren(
+ _encodeTokenExpirationMinuteInputNumber
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenExpirationMinuteInputTitle)
+ .HideCommandBar()
+ .OnTextChanged((value) =>
+ {
+ OnExpirationChanged(value, DateValueType.Minute);
+ })
+ .Minimum(0)
+ .Maximum(59)
+ )
+ )
+ )
+ ),
+ Setting("jwt-encode-token-default-time-setting")
+ .Icon("FluentSystemIcons", '\ue36e')
+ .Title(JsonWebTokenEncoderDecoder.EncodeTokenHasAudienceTitle)
+ .InteractiveElement(
+ _encodeTokenDefaultTimeSwitch
+ .OnText(JsonWebTokenEncoderDecoder.Yes)
+ .OffText(JsonWebTokenEncoderDecoder.No)
+ .OnToggle(OnTokenHasDefaultTimeChanged)
+ )
+ ),
+ _infoBar
+ .NonClosable(),
+ _tokenInput
+ .Title(JsonWebTokenEncoderDecoder.TokenInputTitle)
+ .ReadOnly(),
+ SplitGrid()
+ .Vertical()
+ .WithLeftPaneChild(
+ _headerInput
+ .Title(JsonWebTokenEncoderDecoder.HeaderInputTitle)
+ .Extendable()
+ .Language("json")
+ .OnTextChanged(OnTextInputChanged)
+ )
+ .WithRightPaneChild(
+ _payloadInput
+ .Title(JsonWebTokenEncoderDecoder.PayloadInputTitle)
+ .Extendable()
+ .Language("json")
+ .OnTextChanged(OnTextInputChanged)
+ ),
+ _signatureInput
+ .Title(JsonWebTokenEncoderDecoder.SignatureInputTitle)
+ .OnTextChanged(OnTextInputChanged),
+ _privateKeyInput
+ .Title(JsonWebTokenEncoderDecoder.PrivateKeyInputTitle)
+ .OnTextChanged(OnTextInputChanged)
+
+ );
+
+ public void Show()
+ {
+ _viewStack.Show();
+ _semaphore = new();
+ _cancellationTokenSource = new CancellationTokenSource();
+ }
+
+ public void Hide()
+ {
+ _viewStack.Hide();
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _semaphore.Dispose();
+ }
+
+ private void OnTokenHasIssuerChanged(bool tokenHasIssuer)
+ {
+ _settingsProvider.SetSetting(tokenHasIssuerSetting, tokenHasIssuer);
+ if (tokenHasIssuer)
+ {
+ _encodeTokenIssuerInput.Enable();
+ }
+ else
+ {
+ _encodeTokenIssuerInput.Disable();
+ }
+ StartTokenEncode();
+ }
+
+ private ValueTask OnTokenIssuerInputChanged(string issuer)
+ {
+ _settingsProvider.SetSetting(tokenIssuerSetting, issuer);
+ StartTokenEncode();
+ return ValueTask.CompletedTask;
+ }
+
+ private void OnTokenHasAudienceChanged(bool tokenHasAudience)
+ {
+ _settingsProvider.SetSetting(tokenHasAudienceSetting, tokenHasAudience);
+ if (tokenHasAudience)
+ {
+ _encodeTokenAudienceInput.Enable();
+ }
+ else
+ {
+ _encodeTokenAudienceInput.Disable();
+ }
+ StartTokenEncode();
+ }
+
+ private ValueTask OnTokenAudienceInputChanged(string audience)
+ {
+ _settingsProvider.SetSetting(tokenAudienceSetting, audience);
+ StartTokenEncode();
+ return ValueTask.CompletedTask;
+ }
+
+ private void OnTokenHasDefaultTimeChanged(bool tokenHasDefaultTime)
+ {
+ _settingsProvider.SetSetting(tokenHasDefaultTimeSetting, tokenHasDefaultTime);
+ StartTokenEncode();
+ }
+
+ private void OnTokenHasExpirationChanged(bool tokenHasExpiration)
+ {
+ _settingsProvider.SetSetting(tokenHasExpirationSetting, tokenHasExpiration);
+ if (tokenHasExpiration)
+ {
+ _encodeTokenExpirationGrid.Enable();
+ }
+ else
+ {
+ _encodeTokenExpirationGrid.Disable();
+ }
+ StartTokenEncode();
+ }
+
+ private void OnExpirationChanged(string value, DateValueType valueChanged)
+ {
+ DateTimeOffset epochToUse = _settingsProvider.GetSetting(tokenExpirationSettings);
+
+ ResultInfo result = DateHelper.ChangeDateTime(
+ Convert.ToInt32(value),
+ epochToUse,
+ TimeZoneInfo.Utc,
+ valueChanged);
+
+ _settingsProvider.SetSetting(tokenExpirationSettings, result.Data);
+ StartTokenEncode();
+ }
+
+ private async Task OnAlgorithmChangedAsync(JsonWebTokenAlgorithm algorithm)
+ {
+ _settingsProvider.SetSetting(tokenAlgorithmSetting, algorithm);
+ await ConfigureTokenAlgorithmUIAsync(algorithm);
+ StartTokenEncode();
+ }
+
+ private void OnTokenDefaultTimeChanged(bool tokenHasDefaultTime)
+ {
+ _settingsProvider.SetSetting(tokenHasDefaultTimeSetting, tokenHasDefaultTime);
+ StartTokenEncode();
+ }
+
+ private void OnTextInputChanged(string text)
+ {
+ StartTokenEncode();
+ }
+
+ private void StartTokenEncode()
+ {
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = new CancellationTokenSource();
+
+ if (string.IsNullOrWhiteSpace(_payloadInput.Text))
+ {
+ ClearUI();
+ return;
+ }
+
+ JsonWebTokenAlgorithm tokenAlgorithm = _settingsProvider.GetSetting(tokenAlgorithmSetting);
+ TokenParameters tokenParameters = new()
+ {
+ Payload = _payloadInput.Text,
+ TokenAlgorithm = tokenAlgorithm
+ };
+ EncoderParameters encoderParameters = new()
+ {
+ HasDefaultTime = _settingsProvider.GetSetting(tokenHasDefaultTimeSetting)
+ };
+ bool hasAudience = _settingsProvider.GetSetting(tokenHasAudienceSetting);
+ if (hasAudience)
+ {
+ encoderParameters.HasAudience = hasAudience;
+ tokenParameters.Audiences = _settingsProvider.GetSetting(tokenAudienceSetting).Split(',').ToHashSet();
+ }
+ bool hasIssuer = _settingsProvider.GetSetting(tokenHasIssuerSetting);
+ if (hasIssuer)
+ {
+ encoderParameters.HasIssuer = hasIssuer;
+ tokenParameters.Issuers = _settingsProvider.GetSetting(tokenIssuerSetting).Split(',').ToHashSet();
+ }
+ bool hasExpiration = _settingsProvider.GetSetting(tokenHasAudienceSetting);
+ if (hasExpiration)
+ {
+ DateTimeOffset dateTimeOffset = _settingsProvider.GetSetting(tokenExpirationSettings);
+ encoderParameters.HasExpiration = hasExpiration;
+ tokenParameters.DefineExpirationDate(dateTimeOffset);
+ }
+
+ if (tokenAlgorithm is JsonWebTokenAlgorithm.HS256 ||
+ tokenAlgorithm is JsonWebTokenAlgorithm.HS384 ||
+ tokenAlgorithm is JsonWebTokenAlgorithm.HS512)
+ {
+ tokenParameters.Signature = _signatureInput.Text;
+ }
+ else
+ {
+ tokenParameters.PrivateKey = _privateKeyInput.Text;
+ }
+
+ WorkTask = EncodeTokenAsync(
+ encoderParameters,
+ tokenParameters,
+ _cancellationTokenSource.Token);
+ }
+
+ private async Task EncodeTokenAsync(
+ EncoderParameters encoderParameters,
+ TokenParameters tokenParameters,
+ CancellationToken cancellationToken)
+ {
+ using (await _semaphore.WaitAsync(cancellationToken))
+ {
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ _logger);
+
+ switch (result.Severity)
+ {
+ case ResultInfoSeverity.Success:
+ _infoBar.Close();
+ _tokenInput.Text(result.Data!.Token!);
+ break;
+
+ case ResultInfoSeverity.Warning:
+ _headerInput.Text(result.Data!.Header!);
+ _payloadInput.Text(result.Data!.Payload!);
+ _infoBar
+ .Description(result.ErrorMessage)
+ .Warning()
+ .Open();
+ break;
+
+ case ResultInfoSeverity.Error:
+ _infoBar
+ .Description(result.ErrorMessage)
+ .Error()
+ .Open();
+ ClearUI();
+ break;
+
+ default:
+ throw new NotImplementedException();
+ }
+ }
+ }
+
+ private void ClearUI()
+ {
+ _infoBar.Close();
+ _tokenInput.Text(string.Empty);
+ }
+
+ private async Task ConfigureUIAsync()
+ {
+ JsonWebTokenAlgorithm tokenAlgorithm = _settingsProvider.GetSetting(tokenAlgorithmSetting);
+ await ConfigureTokenAlgorithmUIAsync(tokenAlgorithm);
+
+ ConfigureSwitch(_settingsProvider.GetSetting(tokenHasIssuerSetting), _encodeTokenHasIssuerSwitch, _encodeTokenIssuerInput);
+ ConfigureSwitch(_settingsProvider.GetSetting(tokenHasAudienceSetting), _encodeTokenHasAudienceSwitch, _encodeTokenAudienceInput);
+ ConfigureSwitch(_settingsProvider.GetSetting(tokenHasExpirationSetting), _encodeTokenHasExpirationSwitch, _encodeTokenExpirationGrid);
+ ConfigureSwitch(_settingsProvider.GetSetting(tokenHasDefaultTimeSetting), _encodeTokenDefaultTimeSwitch);
+ DateTimeOffset expirationDate = _settingsProvider.GetSetting(tokenExpirationSettings);
+ _encodeTokenExpirationYearInputNumber.Value(expirationDate.Year);
+ _encodeTokenExpirationMonthInputNumber.Value(expirationDate.Month);
+ _encodeTokenExpirationDayInputNumber.Value(expirationDate.Day);
+ _encodeTokenExpirationHourInputNumber.Value(expirationDate.Hour);
+ _encodeTokenExpirationMinuteInputNumber.Value(expirationDate.Minute);
+ }
+
+ private async Task ConfigureTokenAlgorithmUIAsync(JsonWebTokenAlgorithm tokenAlgorithm)
+ {
+ if (tokenAlgorithm is JsonWebTokenAlgorithm.HS256 ||
+ tokenAlgorithm is JsonWebTokenAlgorithm.HS384 ||
+ tokenAlgorithm is JsonWebTokenAlgorithm.HS512)
+ {
+ _signatureInput.Show();
+ _privateKeyInput.Hide();
+ }
+ else
+ {
+ _signatureInput.Hide();
+ _privateKeyInput.Show();
+ }
+
+ ResultInfo headerContent = await JsonHelper.FormatAsync(
+ @"{""alg"": """ + tokenAlgorithm.ToString() + @""", ""typ"": ""JWT""}",
+ Indentation.TwoSpaces,
+ false,
+ _logger,
+ CancellationToken.None);
+
+ _headerInput.Text(headerContent.Data!);
+ }
+
+ private static void ConfigureSwitch(bool value, IUISwitch inputSwitch, IUIElementWithChildren? input = null)
+ {
+ if (value)
+ {
+ inputSwitch.On();
+ if (input != null)
+ {
+ input.Enable();
+ }
+ return;
+ }
+ inputSwitch.Off();
+ if (input != null)
+ {
+ input.Disable();
+ }
+ }
+}
diff --git a/src/app/tests/DevToys.UnitTests/DevToys.UnitTests.csproj b/src/app/tests/DevToys.UnitTests/DevToys.UnitTests.csproj
index 35799cf4b0..206281afba 100644
--- a/src/app/tests/DevToys.UnitTests/DevToys.UnitTests.csproj
+++ b/src/app/tests/DevToys.UnitTests/DevToys.UnitTests.csproj
@@ -5,6 +5,20 @@
false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
PreserveNewest
@@ -90,6 +104,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/tests/DevToys.UnitTests/Mocks/MockIFileStorage.cs b/src/app/tests/DevToys.UnitTests/Mocks/MockIFileStorage.cs
index cfc4ff3d8b..5fee948f9c 100644
--- a/src/app/tests/DevToys.UnitTests/Mocks/MockIFileStorage.cs
+++ b/src/app/tests/DevToys.UnitTests/Mocks/MockIFileStorage.cs
@@ -28,13 +28,13 @@ public FileStream OpenWriteFile(string relativeOrAbsoluteFilePath, bool replaceI
throw new NotImplementedException();
}
- public ValueTask PickSaveFileAsync(params string[] fileTypes)
+ public ValueTask PickSaveFileAsync(params string[] fileTypes)
{
// TODO: prompt the user to type in the console a relative or absolute file path that has one of the file types indicated.
throw new NotImplementedException();
}
- public ValueTask PickOpenFileAsync(params string[] fileTypes)
+ public ValueTask PickOpenFileAsync(params string[] fileTypes)
{
// TODO: prompt the user to type in the console a relative or absolute file path that has one of the file types indicated.
throw new NotImplementedException();
@@ -45,7 +45,7 @@ public ValueTask PickOpenFilesAsync(params string[] fileT
throw new NotImplementedException();
}
- public ValueTask PickFolderAsync()
+ public ValueTask PickFolderAsync()
{
throw new NotImplementedException();
}
@@ -53,7 +53,7 @@ public ValueTask PickFolderAsync()
public FileInfo CreateSelfDestroyingTempFile(string desiredFileExtension = null)
{
var assembly = Assembly.GetExecutingAssembly();
- string assemblyDirectory = Path.GetDirectoryName(assembly.Location);
- return FileHelper.CreateTempFile(assemblyDirectory, desiredFileExtension);
+ string? assemblyDirectory = Path.GetDirectoryName(assembly.Location);
+ return FileHelper.CreateTempFile(assemblyDirectory!, desiredFileExtension);
}
}
diff --git a/src/app/tests/DevToys.UnitTests/TestDataProvider.cs b/src/app/tests/DevToys.UnitTests/TestDataProvider.cs
index b93f3ab264..4140e68cf5 100644
--- a/src/app/tests/DevToys.UnitTests/TestDataProvider.cs
+++ b/src/app/tests/DevToys.UnitTests/TestDataProvider.cs
@@ -13,10 +13,17 @@ internal static class TestDataProvider
///
public static async Task GetEmbeddedFileContent(string filePath)
{
- var assembly = Assembly.GetExecutingAssembly();
- using Stream resourceStream = assembly.GetManifestResourceStream(filePath);
- using StreamReader streamReader = new(resourceStream);
- return await streamReader.ReadToEndAsync();
+ try
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ using Stream resourceStream = assembly.GetManifestResourceStream(filePath);
+ using StreamReader streamReader = new(resourceStream);
+ return await streamReader.ReadToEndAsync();
+ }
+ catch
+ {
+ throw new FileNotFoundException(filePath);
+ }
}
///
@@ -26,9 +33,16 @@ public static async Task GetEmbeddedFileContent(string filePath)
///
public static FileInfo GetFile(string filePath)
{
- var assembly = Assembly.GetExecutingAssembly();
- string assemblyDirectory = Path.GetDirectoryName(assembly.Location);
- string resourcePath = Path.Combine(assemblyDirectory, filePath);
- return new(resourcePath);
+ try
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ string assemblyDirectory = Path.GetDirectoryName(assembly.Location);
+ string resourcePath = Path.Combine(assemblyDirectory, filePath);
+ return new(resourcePath);
+ }
+ catch
+ {
+ throw new FileNotFoundException(filePath);
+ }
}
}
diff --git a/src/app/tests/DevToys.UnitTests/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenDecoderGuiToolTests.cs b/src/app/tests/DevToys.UnitTests/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenDecoderGuiToolTests.cs
new file mode 100644
index 0000000000..fce7f90036
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenDecoderGuiToolTests.cs
@@ -0,0 +1,236 @@
+using System.Globalization;
+using System.Threading.Tasks;
+using DevToys.Core.Tools.Metadata;
+using DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+
+namespace DevToys.UnitTests.Tools.EncodersDecoders.JsonWebToken;
+
+public sealed class JsonWebTokenDecoderGuiToolTests : MefBasedTest
+{
+ private const string ToolName = "JsonWebTokenEncoderDecoder";
+ private const string BaseAssembly = "DevToys.UnitTests.Tools.TestData";
+
+ private readonly JsonWebTokenDecoderGuiTool _decodeTool;
+ private readonly JsonWebTokenEncoderDecoderGuiTool _tool;
+ private readonly UIToolView _toolView;
+
+ private readonly IUISwitch _toolMode;
+
+ private readonly IUIInfoBar _infoBar;
+
+ private readonly IUISwitch _validateTokenSwitch;
+ private readonly IUISwitch _validateIssuersSwitch;
+ private readonly IUISwitch _validateIssuerSigningKeySwitch;
+ private readonly IUISwitch _validateAudiencesSwitch;
+ private readonly IUISwitch _validateLifetimeSwitch;
+ private readonly IUISwitch _validateActorsSwitch;
+
+ private readonly IUIMultiLineTextInput _tokenInput;
+ private readonly IUIMultiLineTextInput _headerInput;
+ private readonly IUIMultiLineTextInput _payloadInput;
+ private readonly IUIMultiLineTextInput _signatureInput;
+ private readonly IUIMultiLineTextInput _publicKeyInput;
+
+ private readonly IUISingleLineTextInput _validateIssuersInput;
+ private readonly IUISingleLineTextInput _validateAudiencesInput;
+
+ public JsonWebTokenDecoderGuiToolTests()
+ : base(typeof(JsonWebTokenDecoderGuiTool).Assembly)
+ {
+ _tool = (JsonWebTokenEncoderDecoderGuiTool)MefProvider.ImportMany()
+ .Single(t => t.Metadata.InternalComponentName == "JsonWebTokenEncoderDecoder")
+ .Value;
+
+ _toolView = _tool.View;
+ _toolMode = (IUISwitch)_toolView.GetChildElementById("jwt-token-conversion-mode-switch");
+
+ _decodeTool = _tool.DecoderGuiTool;
+ _infoBar = (IUIInfoBar)_toolView.GetChildElementById("jwt-decode-info-bar");
+ _validateTokenSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-decode-validate-token-switch");
+ _validateIssuerSigningKeySwitch = (IUISwitch)_toolView.GetChildElementById("jwt-decode-validate-token-issuer-signing-key-switch");
+ _validateIssuersSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-decode-validate-token-issuers-switch");
+ _validateAudiencesSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-decode-validate-token-audiences-switch");
+ _validateLifetimeSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-decode-validate-token-lifetime-switch");
+ _validateActorsSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-decode-validate-token-actors-switch");
+ _tokenInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-decode-token-input");
+ _headerInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-decode-header-input");
+ _payloadInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-decode-payload-input");
+ _signatureInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-decode-signature-input");
+ _publicKeyInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-decode-public-key-input");
+ _validateIssuersInput = (IUISingleLineTextInput)_toolView.GetChildElementById("jwt-decode-validate-token-issuers-input");
+ _validateAudiencesInput = (IUISingleLineTextInput)_toolView.GetChildElementById("jwt-decode-validate-token-audiences-input");
+
+ CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+ CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Token should return false")]
+ public async Task DecodeTokenWithInvalidTokenShouldReturnError()
+ {
+ _toolMode.Off();
+ _tokenInput.Text("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ");
+ await _decodeTool.WorkTask;
+ _payloadInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().BeEmpty();
+ _infoBar.Description.Should().StartWith("IDX14100: JWT is not well formed, there are no dots (.).");
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Issuers should return false")]
+ public async Task DecodeTokenWithInvalidIssuersShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ _toolMode.Off();
+ _validateTokenSwitch.On();
+ _validateIssuersSwitch.On();
+ _tokenInput.Text(tokenContent);
+ _signatureInput.Text(signatureContent);
+ await _decodeTool.WorkTask;
+ _payloadInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().BeEmpty();
+ _infoBar.Description.Should().StartWith("IDX10205: Issuer validation failed.");
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Audience should return false")]
+ public async Task DecodeTokenWithInvalidAudienceShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ _toolMode.Off();
+ _validateTokenSwitch.On();
+ _validateAudiencesSwitch.On();
+ _tokenInput.Text(tokenContent);
+ _signatureInput.Text(signatureContent);
+ await _decodeTool.WorkTask;
+ _payloadInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().BeEmpty();
+ _infoBar.Description.Should().StartWith("IDX10214: Audience validation failed.");
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Expired Lifetime should return false")]
+ public async Task DecodeTokenWithInvalidExpiredLifetimeShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ _toolMode.Off();
+ _validateTokenSwitch.On();
+ _validateLifetimeSwitch.On();
+ _tokenInput.Text(tokenContent);
+ _signatureInput.Text(signatureContent);
+ await _decodeTool.WorkTask;
+ _payloadInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().BeEmpty();
+ _infoBar.Description.Should().StartWith("IDX10225: Lifetime validation failed.");
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Valid HS Token should return decoded payload")]
+ public async Task DecodeTokenWithValidHSTokenShouldReturnDecodedPayload()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-ComplexToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ _toolMode.Off();
+ _validateTokenSwitch.On();
+ _validateActorsSwitch.On();
+ _validateIssuersSwitch.On();
+ _validateIssuerSigningKeySwitch.On();
+ _validateAudiencesSwitch.On();
+ _tokenInput.Text(tokenContent);
+ _validateIssuersInput.Text("devtoys");
+ _validateAudiencesInput.Text("devtoys");
+ _signatureInput.Text(signatureContent);
+ await _decodeTool.WorkTask;
+ _infoBar.Description.Should().Be(JsonWebTokenEncoderDecoder.ValidToken);
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Success);
+ _payloadInput.Text.Should().Be(payloadContent);
+ _headerInput.Text.Should().Be(headerContent);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Valid RS Token should return decoded payload")]
+ public async Task DecodeTokenWithValidRSTokenShouldReturnDecodedPayload()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-PublicKey.txt");
+
+ _toolMode.Off();
+ _validateTokenSwitch.On();
+ _validateActorsSwitch.On();
+ _validateIssuersSwitch.On();
+ _validateIssuerSigningKeySwitch.On();
+ _validateAudiencesSwitch.On();
+ _tokenInput.Text(tokenContent);
+ _validateIssuersInput.Text("devtoys");
+ _validateAudiencesInput.Text("devtoys");
+ _publicKeyInput.Text(publicKeyContent);
+ await _decodeTool.WorkTask;
+ _infoBar.Description.Should().Be(JsonWebTokenEncoderDecoder.ValidToken);
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Success);
+ _payloadInput.Text.Should().Be(payloadContent);
+ _headerInput.Text.Should().Be(headerContent);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Valid PS Token should return decoded payload")]
+ public async Task DecodeTokenWithValidPSTokenShouldReturnDecodedPayload()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-PublicKey.txt");
+
+ _toolMode.Off();
+ _validateTokenSwitch.On();
+ _validateActorsSwitch.On();
+ _validateIssuersSwitch.On();
+ _validateIssuerSigningKeySwitch.On();
+ _validateAudiencesSwitch.On();
+ _tokenInput.Text(tokenContent);
+ _validateIssuersInput.Text("devtoys");
+ _validateAudiencesInput.Text("devtoys");
+ _publicKeyInput.Text(publicKeyContent);
+ await _decodeTool.WorkTask;
+ _infoBar.Description.Should().Be(JsonWebTokenEncoderDecoder.ValidToken);
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Success);
+ _payloadInput.Text.Should().Be(payloadContent);
+ _headerInput.Text.Should().Be(headerContent);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Valid ES Token should return decoded payload")]
+ public async Task DecodeTokenWithValidESTokenShouldReturnDecodedPayload()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-PublicKey.txt");
+
+ _toolMode.Off();
+ _validateTokenSwitch.On();
+ _validateActorsSwitch.On();
+ _validateIssuersSwitch.On();
+ _validateIssuerSigningKeySwitch.On();
+ _validateAudiencesSwitch.On();
+ _tokenInput.Text(tokenContent);
+ _validateIssuersInput.Text("devtoys");
+ _validateAudiencesInput.Text("devtoys");
+ _publicKeyInput.Text(publicKeyContent);
+ await _decodeTool.WorkTask;
+ _infoBar.Description.Should().Be(JsonWebTokenEncoderDecoder.ValidToken);
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Success);
+ _payloadInput.Text.Should().Be(payloadContent);
+ _headerInput.Text.Should().Be(headerContent);
+ }
+}
diff --git a/src/app/tests/DevToys.UnitTests/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderGuiToolTests.cs b/src/app/tests/DevToys.UnitTests/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderGuiToolTests.cs
new file mode 100644
index 0000000000..7055143ddc
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/EncodersDecoders/JsonWebToken/JsonWebTokenEncoderGuiToolTests.cs
@@ -0,0 +1,215 @@
+using System.Globalization;
+using System.Threading.Tasks;
+using DevToys.Core.Tools.Metadata;
+using DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+
+namespace DevToys.UnitTests.Tools.EncodersDecoders.JsonWebToken;
+
+public sealed class JsonWebTokenEncoderGuiToolTests : MefBasedTest
+{
+ private const string ToolName = "JsonWebTokenEncoderDecoder";
+ private const string BaseAssembly = "DevToys.UnitTests.Tools.TestData";
+
+ private readonly JsonWebTokenEncoderGuiTool _encodeTool;
+ private readonly JsonWebTokenEncoderDecoderGuiTool _tool;
+ private readonly UIToolView _toolView;
+
+ private readonly IUISwitch _toolMode;
+
+ private readonly IUIInfoBar _infoBar;
+ private readonly IUISelectDropDownList _tokenAlgorithm;
+ private readonly IUIMultiLineTextInput _tokenInput;
+ private readonly IUIMultiLineTextInput _headerInput;
+ private readonly IUIMultiLineTextInput _payloadInput;
+ private readonly IUIMultiLineTextInput _signatureInput;
+ private readonly IUIMultiLineTextInput _privateKeyInput;
+
+ private readonly IUISwitch _tokenIssuersSwitch;
+ private readonly IUISwitch _tokenAudiencesSwitch;
+ private readonly IUISwitch _tokenExpirationSwitch;
+ private readonly IUISingleLineTextInput _tokenIssuersInput;
+ private readonly IUISingleLineTextInput _tokenAudiencesInput;
+
+ public JsonWebTokenEncoderGuiToolTests()
+ : base(typeof(JsonWebTokenEncoderGuiTool).Assembly)
+ {
+ _tool = (JsonWebTokenEncoderDecoderGuiTool)MefProvider.ImportMany()
+ .Single(t => t.Metadata.InternalComponentName == "JsonWebTokenEncoderDecoder")
+ .Value;
+
+ _toolView = _tool.View;
+ _toolMode = (IUISwitch)_toolView.GetChildElementById("jwt-token-conversion-mode-switch");
+
+ _encodeTool = _tool.EncoderGuiTool;
+ _infoBar = (IUIInfoBar)_toolView.GetChildElementById("jwt-encode-info-bar");
+
+ var setting = (IUISetting)_toolView.GetChildElementById("jwt-encode-token-algorithm-setting");
+ _tokenAlgorithm = (IUISelectDropDownList)setting.InteractiveElement;
+ _tokenInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-encode-token-input");
+ _headerInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-encode-header-input");
+ _payloadInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-encode-payload-input");
+ _signatureInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-encode-signature-input");
+ _privateKeyInput = (IUIMultiLineTextInput)_toolView.GetChildElementById("jwt-encode-private-key-input");
+
+ _tokenIssuersSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-encode-token-issuer-switch");
+ _tokenAudiencesSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-encode-token-audience-switch");
+ _tokenExpirationSwitch = (IUISwitch)_toolView.GetChildElementById("jwt-encode-token-expiration-switch");
+ _tokenIssuersInput = (IUISingleLineTextInput)_toolView.GetChildElementById("jwt-encode-token-issuer-input");
+ _tokenAudiencesInput = (IUISingleLineTextInput)_toolView.GetChildElementById("jwt-encode-token-audience-input");
+
+ CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
+ CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Payload should display error")]
+ public async Task EncodeTokenWithInvalidPayloadShouldDisplayError()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Header.json");
+ _toolMode.On();
+ _payloadInput.Text("xxx");
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().Be(headerContent);
+ _infoBar.Description.Should().StartWith("'x' is an invalid start of a value.");
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Signature should display error")]
+ public async Task EncodeTokenWithInvalidSignatureShouldDisplayError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.BasicPayload.json");
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Header.json");
+
+ _toolMode.On();
+ _tokenAlgorithm.Select(0); // HS256
+ _tokenIssuersSwitch.Off();
+ _payloadInput.Text(payloadContent);
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().Be(headerContent);
+ _infoBar.Description.Should().Be(JsonWebTokenEncoderDecoder.InvalidSignature);
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Issuers should display error")]
+ public async Task EncodeTokenWithInvalidIssuersShouldDisplayError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.BasicPayload.json");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Header.json");
+
+ _toolMode.On();
+ _tokenAlgorithm.Select(0); // HS256
+ _tokenIssuersSwitch.On();
+
+ _payloadInput.Text(payloadContent);
+ _signatureInput.Text(signatureContent);
+ _tokenIssuersInput.Text(string.Empty);
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().Be(headerContent);
+ _infoBar.Description.Should().Be(JsonWebTokenEncoderDecoder.ValidIssuersEmptyError);
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Audiences should display error")]
+ public async Task EncodeTokenWithInvalidAudiencesShouldDisplayError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.BasicPayload.json");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Header.json");
+
+ _toolMode.On();
+ _tokenAlgorithm.Select(0); // HS256
+ _tokenAudiencesSwitch.On();
+
+ _payloadInput.Text(payloadContent);
+ _signatureInput.Text(signatureContent);
+ _tokenAudiencesInput.Text(string.Empty);
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().BeEmpty();
+ _headerInput.Text.Should().Be(headerContent);
+ _infoBar.Description.Should().Be(JsonWebTokenEncoderDecoder.ValidAudiencesEmptyError);
+ _infoBar.Severity.Should().Be(UIInfoBarSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Encode HS Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task EncodeHSTokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Header.json");
+
+ _toolMode.On();
+ _tokenAlgorithm.Select(0); // HS256
+ _tokenAudiencesSwitch.On();
+ _tokenAudiencesInput.Text("DevToys");
+ _tokenIssuersSwitch.On();
+ _tokenIssuersInput.Text("DevToys");
+ _payloadInput.Text(payloadContent);
+ _signatureInput.Text(signatureContent);
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().NotBeNullOrWhiteSpace();
+ _headerInput.Text.Should().Be(headerContent);
+ }
+
+ [Fact(DisplayName = "Encode RS Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task EncodeRSTokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-PrivateKey.txt");
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-Header.json");
+
+ _toolMode.On();
+ _tokenAlgorithm.Select(3); // RS256
+ _tokenAudiencesSwitch.On();
+ _tokenAudiencesInput.Text("DevToys");
+ _tokenIssuersSwitch.On();
+ _tokenIssuersInput.Text("DevToys");
+ _payloadInput.Text(payloadContent);
+ _privateKeyInput.Text(privateKeyContent);
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().NotBeNullOrWhiteSpace();
+ _headerInput.Text.Should().Be(headerContent);
+ }
+
+ [Fact(DisplayName = "Encode PS Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task EncodePSTokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-PrivateKey.txt");
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-Header.json");
+
+ _toolMode.On();
+ _tokenAlgorithm.Select(7); // PS384
+ _tokenAudiencesSwitch.On();
+ _tokenAudiencesInput.Text("DevToys");
+ _tokenIssuersSwitch.On();
+ _tokenIssuersInput.Text("DevToys");
+ _payloadInput.Text(payloadContent);
+ _privateKeyInput.Text(privateKeyContent);
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().NotBeNullOrWhiteSpace();
+ _headerInput.Text.Should().Be(headerContent);
+ }
+
+ [Fact(DisplayName = "Encode ES Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task EncodeESTokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-PrivateKey.txt");
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-Header.json");
+
+ _toolMode.On();
+ _tokenAlgorithm.Select(11); // ES512
+ _tokenAudiencesSwitch.On();
+ _tokenAudiencesInput.Text("DevToys");
+ _tokenIssuersSwitch.On();
+ _tokenIssuersInput.Text("DevToys");
+ _payloadInput.Text(payloadContent);
+ _privateKeyInput.Text(privateKeyContent);
+ await _encodeTool.WorkTask;
+ _tokenInput.Text.Should().NotBeNullOrWhiteSpace();
+ _headerInput.Text.Should().Be(headerContent);
+ }
+}
diff --git a/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonHelperTests.cs b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonHelperTests.cs
index 348c40f6fa..21b50dc2ee 100644
--- a/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonHelperTests.cs
+++ b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonHelperTests.cs
@@ -24,57 +24,61 @@ public async Task IsValid(string input, bool expectedResult)
}
[Theory]
- [InlineData(null, "")]
- [InlineData("", "")]
- [InlineData(" ", "")]
- [InlineData(" { } ", "{}")]
- [InlineData(" [ ] ", "[]")]
- [InlineData(" { \"foo\": 123 } ", "{\r\n \"foo\": 123\r\n}")]
- public async Task FormatTwoSpaces(string input, string expectedResult)
+ [InlineData(null, false, "")]
+ [InlineData("", false, "")]
+ [InlineData(" ", false, "")]
+ [InlineData(" { } ", true, "{}")]
+ [InlineData(" [ ] ", true, "[]")]
+ [InlineData(" { \"foo\": 123 } ", true, "{\r\n \"foo\": 123\r\n}")]
+ public async Task FormatTwoSpaces(string input, bool expectedSucceeded, string expectedResult)
{
expectedResult = expectedResult.Replace("\r\n", Environment.NewLine);
ResultInfo result = await JsonHelper.FormatAsync(input, Indentation.TwoSpaces, false, new MockILogger(), CancellationToken.None);
+ result.HasSucceeded.Should().Be(expectedSucceeded);
result.Data.Should().Be(expectedResult);
}
[Theory]
- [InlineData(null, "")]
- [InlineData("", "")]
- [InlineData(" ", "")]
- [InlineData(" { } ", "{}")]
- [InlineData(" [ ] ", "[]")]
- [InlineData(" { \"foo\": 123 } ", "{\r\n \"foo\": 123\r\n}")]
- public async Task FormatFourSpaces(string input, string expectedResult)
+ [InlineData(null, false, "")]
+ [InlineData("", false, "")]
+ [InlineData(" ", false, "")]
+ [InlineData(" { } ", true, "{}")]
+ [InlineData(" [ ] ", true, "[]")]
+ [InlineData(" { \"foo\": 123 } ", true, "{\r\n \"foo\": 123\r\n}")]
+ public async Task FormatFourSpaces(string input, bool expectedSucceeded, string expectedResult)
{
expectedResult = expectedResult.Replace("\r\n", Environment.NewLine);
ResultInfo result = await JsonHelper.FormatAsync(input, Indentation.FourSpaces, false, new MockILogger(), CancellationToken.None);
+ result.HasSucceeded.Should().Be(expectedSucceeded);
result.Data.Should().Be(expectedResult);
}
[Theory]
- [InlineData(null, "")]
- [InlineData("", "")]
- [InlineData(" ", "")]
- [InlineData(" { } ", "{}")]
- [InlineData(" [ ] ", "[]")]
- [InlineData(" { \"foo\": 123 } ", "{\r\n\t\"foo\": 123\r\n}")]
- public async Task FormatOneTab(string input, string expectedResult)
+ [InlineData(null, false, "")]
+ [InlineData("", false, "")]
+ [InlineData(" ", false, "")]
+ [InlineData(" { } ", true, "{}")]
+ [InlineData(" [ ] ", true, "[]")]
+ [InlineData(" { \"foo\": 123 } ", true, "{\r\n\t\"foo\": 123\r\n}")]
+ public async Task FormatOneTab(string input, bool expectedSucceeded, string expectedResult)
{
expectedResult = expectedResult.Replace("\r\n", Environment.NewLine);
ResultInfo result = await JsonHelper.FormatAsync(input, Indentation.OneTab, false, new MockILogger(), CancellationToken.None);
+ result.HasSucceeded.Should().Be(expectedSucceeded);
result.Data.Should().Be(expectedResult);
}
[Theory]
- [InlineData(null, "")]
- [InlineData("", "")]
- [InlineData(" ", "")]
- [InlineData(" { } ", "{}")]
- [InlineData(" [ ] ", "[]")]
- [InlineData(" { \"foo\": 123 } ", "{\"foo\":123}")]
- public async Task FormatMinifiedAsync(string input, string expectedResult)
+ [InlineData(null, false, "")]
+ [InlineData("", false, "")]
+ [InlineData(" ", false, "")]
+ [InlineData(" { } ", true, "{}")]
+ [InlineData(" [ ] ", true, "[]")]
+ [InlineData(" { \"foo\": 123 } ", true, "{\"foo\":123}")]
+ public async Task FormatMinifiedAsync(string input, bool expectedSucceeded, string expectedResult)
{
ResultInfo result = await JsonHelper.FormatAsync(input, Indentation.Minified, false, new MockILogger(), CancellationToken.None);
+ result.HasSucceeded.Should().Be(expectedSucceeded);
result.Data.Should().Be(expectedResult);
}
diff --git a/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonWebTokenDecoderHelperTests.cs b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonWebTokenDecoderHelperTests.cs
new file mode 100644
index 0000000000..594a569259
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonWebTokenDecoderHelperTests.cs
@@ -0,0 +1,666 @@
+using System.Threading;
+using System.Threading.Tasks;
+using DevToys.Tools.Helpers;
+using DevToys.Tools.Helpers.JsonWebToken;
+using DevToys.Tools.Models;
+using Microsoft.Extensions.Logging;
+
+namespace DevToys.UnitTests.Tools.Helpers;
+
+public class JsonWebTokenDecoderHelperTests
+{
+ private readonly ILogger _logger;
+ private const string ToolName = "JsonWebTokenEncoderDecoder";
+ private const string BaseAssembly = "DevToys.UnitTests.Tools.TestData";
+
+ public JsonWebTokenDecoderHelperTests()
+ {
+ _logger = new MockILogger();
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid parameters should throw argument exception")]
+ public async Task DecodeTokenWithInvalidParametersShouldThrowArgumentException()
+ {
+ Func result = () => JsonWebTokenDecoderHelper.DecodeTokenAsync(null, null, _logger, CancellationToken.None).AsTask();
+
+ await result.Should().ThrowAsync();
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Token should return false")]
+ public async Task DecodeTokenWithInvalidTokenShouldReturnError()
+ {
+ var decodeParameters = new DecoderParameters();
+ var tokenParameters = new TokenParameters
+ {
+ Token = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Public Key should return false")]
+ public async Task DecodeTokenWithInvalidPublicKeyShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.RS256
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Signature should return false")]
+ public async Task DecodeTokenWithInvalidSignatureShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Issuers should return false")]
+ public async Task DecodeTokenWithInvalidIssuersShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuers = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ Signature = signatureContent
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Audiences should return false")]
+ public async Task DecodeTokenWithInvalidAudiencesShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ Signature = signatureContent
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Decode Json Web Token with Invalid Expired Lifetime should return false")]
+ public async Task DecodeTokenWithInvalidExpiredLifetimeShouldReturnError()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateLifetime = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ Signature = signatureContent
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ }
+
+ // TODO add other HS tests
+
+ #region HS
+
+ [Fact(DisplayName = "Decode HS256 Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodeHS256TokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-ComplexToken.txt");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.HS256,
+ Signature = signatureContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+ tokenResult.Signature.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ #endregion
+
+ #region RS
+
+ [Fact(DisplayName = "Decode RS256 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodeRS256PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-PublicKey.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.RS256,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ [Fact(DisplayName = "Decode RS384 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodeRS384PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS384-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS384-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS384-PublicKey.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.RS384,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ [Fact(DisplayName = "Decode RS512 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodeRS512PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS512-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS512-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS512-PublicKey.txt");
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.RS512,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ #endregion
+
+ #region PS
+
+ [Fact(DisplayName = "Decode PS256 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodePS256PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS256-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS256-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS256-PublicKey.txt");
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.PS256,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ [Fact(DisplayName = "Decode PS384 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodePS384PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-PublicKey.txt");
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.PS384,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ [Fact(DisplayName = "Decode PS512 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodePS512PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-PublicKey.txt");
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.PS512,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ #endregion
+
+ #region ES
+
+ [Fact(DisplayName = "Decode ES256 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodeES256PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES256-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES256-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES256-PublicKey.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.ES256,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ [Fact(DisplayName = "Decode ES384 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodeES384PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES384-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES384-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES384-PublicKey.txt");
+
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.ES384,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ [Fact(DisplayName = "Decode ES512 Public Key Json Web Token with Valid Token and valid parameters should return decoded token")]
+ public async Task DecodeES512PublicKeyTokenWithValidTokenAndValidParametersShouldReturnDecodedToken()
+ {
+ string headerContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-Header.json");
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-ComplexToken.txt");
+ string publicKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-PublicKey.txt");
+ var decodeParameters = new DecoderParameters()
+ {
+ ValidateSignature = true,
+ ValidateIssuersSigningKey = true,
+ ValidateIssuers = true,
+ ValidateAudiences = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Token = tokenContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.ES512,
+ PublicKey = publicKeyContent,
+ Issuers = ["devtoys"],
+ Audiences = ["devtoys"]
+ };
+
+ ResultInfo result = await JsonWebTokenDecoderHelper.DecodeTokenAsync(
+ decodeParameters,
+ tokenParameters,
+ new MockILogger(), CancellationToken.None);
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ JsonWebTokenResult tokenResult = result.Data;
+ tokenResult.Header.Should().NotBeNull();
+ tokenResult.Payload.Should().NotBeNull();
+
+ ResultInfo formattedHeader = await GetFormattedDataAsync(headerContent);
+ ResultInfo formattedPayload = await GetFormattedDataAsync(payloadContent);
+
+ tokenResult.Header.Should().Be(formattedHeader.Data);
+ tokenResult.Payload.Should().Be(formattedPayload.Data);
+ }
+
+ #endregion
+
+ #region GetTokenAlgorithm
+
+ [Fact(DisplayName = "Get Json Web Token Algorithm with invalid parameters should throw argument exception")]
+ public void GetTokenAlgorithmWithInvalidParametersShouldThrowArgumentException()
+ {
+ Func> result = () => JsonWebTokenDecoderHelper.GetTokenAlgorithm(null, _logger);
+ result.Should().Throw();
+ }
+
+ [Fact(DisplayName = "Get Json Web Token Algorithm with invalid token should return error")]
+ public void GetTokenAlgorithmWithInvalidTokenShouldReturnError()
+ {
+ string tokenContent = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwib2JqZWN0Ijp7Ik9iamVjdF";
+
+ ResultInfo result = JsonWebTokenDecoderHelper.GetTokenAlgorithm(tokenContent, _logger);
+ result.HasSucceeded.Should().BeFalse();
+ }
+
+ [Fact(DisplayName = "Get Json Web Token Algorithm with HS256 token should return HS256")]
+ public async Task GetTokenAlgorithmWithInvalidTokenShouldJwtAlgorithmHS256()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-BasicToken.txt");
+
+ ResultInfo result = JsonWebTokenDecoderHelper.GetTokenAlgorithm(tokenContent, _logger);
+ result.HasSucceeded.Should().BeTrue();
+ result.Data.Should().NotBeNull();
+ result.Data.Should().Be(JsonWebTokenAlgorithm.HS256);
+ }
+
+ [Fact(DisplayName = "Get Json Web Token Algorithm with PS384 token should return PS384")]
+ public async Task GetTokenAlgorithmWithInvalidTokenShouldJwtAlgorithmPS384()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-ComplexToken.txt");
+
+ ResultInfo result = JsonWebTokenDecoderHelper.GetTokenAlgorithm(tokenContent, _logger);
+ result.HasSucceeded.Should().BeTrue();
+ result.Data.Should().NotBeNull();
+ result.Data.Should().Be(JsonWebTokenAlgorithm.PS384);
+ }
+
+ [Fact(DisplayName = "Get Json Web Token Algorithm with RS512 token should return RS512")]
+ public async Task GetTokenAlgorithmWithInvalidTokenShouldJwtAlgorithmRS512()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS512-ComplexToken.txt");
+
+ ResultInfo result = JsonWebTokenDecoderHelper.GetTokenAlgorithm(tokenContent, _logger);
+ result.HasSucceeded.Should().BeTrue();
+ result.Data.Should().NotBeNull();
+ result.Data.Should().Be(JsonWebTokenAlgorithm.RS512);
+ }
+
+ [Fact(DisplayName = "Get Json Web Token Algorithm with ES256 token should return ES256")]
+ public async Task GetTokenAlgorithmWithInvalidTokenShouldJwtAlgorithmES256()
+ {
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES256-ComplexToken.txt");
+
+ ResultInfo result = JsonWebTokenDecoderHelper.GetTokenAlgorithm(tokenContent, _logger);
+ result.HasSucceeded.Should().BeTrue();
+ result.Data.Should().NotBeNull();
+ result.Data.Should().Be(JsonWebTokenAlgorithm.ES256);
+ }
+
+ #endregion
+
+ private async Task> GetFormattedDataAsync(string rawData)
+ => await JsonHelper.FormatAsync(
+ rawData,
+ Indentation.TwoSpaces,
+ false,
+ _logger,
+ CancellationToken.None);
+}
diff --git a/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonWebTokenEncoderHelperTests.cs b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonWebTokenEncoderHelperTests.cs
new file mode 100644
index 0000000000..b8009d4ef8
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JsonWebTokenEncoderHelperTests.cs
@@ -0,0 +1,464 @@
+using System.Threading;
+using System.Threading.Tasks;
+using DevToys.Tools.Helpers;
+using DevToys.Tools.Helpers.JsonWebToken;
+using DevToys.Tools.Models;
+using DevToys.Tools.Tools.EncodersDecoders.JsonWebToken;
+using Microsoft.Extensions.Logging;
+
+namespace DevToys.UnitTests.Tools.Helpers;
+
+public class JsonWebTokenEncoderHelperTests
+{
+ private readonly ILogger _logger;
+ private const string ToolName = "JsonWebTokenEncoderDecoder";
+ private const string BaseAssembly = "DevToys.UnitTests.Tools.TestData";
+
+ public JsonWebTokenEncoderHelperTests()
+ {
+ _logger = new MockILogger();
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with invalid parameters should throw argument exception")]
+ public void GenerateTokenWithInvalidParametersShouldThrowArgumentException()
+ {
+ Action result = () => JsonWebTokenEncoderHelper.GenerateToken(null, null, _logger);
+
+ result.Should().Throw();
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Payload should return false")]
+ public void GenerateTokenWithInvalidPayloadShouldReturnError()
+ {
+ var encoderParameters = new EncoderParameters();
+ var tokenParameters = new TokenParameters
+ {
+ Payload = "xxx"
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Signature should return false")]
+ public async Task GenerateTokenWithInvalidSignatureShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.BasicPayload.json");
+
+ var encoderParameters = new EncoderParameters();
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.HS256
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ result.ErrorMessage.Should().Be(JsonWebTokenEncoderDecoder.InvalidSignature);
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Issuers should return false")]
+ public async Task GenerateTokenWithInvalidIssuersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.BasicPayload.json");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ Signature = signatureContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.HS256
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ result.ErrorMessage.Should().Be(JsonWebTokenEncoderDecoder.ValidIssuersEmptyError);
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Audience should return false")]
+ public async Task GenerateTokenWithInvalidAudiencesShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.BasicPayload.json");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ Signature = signatureContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.HS256
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ result.ErrorMessage.Should().Be(JsonWebTokenEncoderDecoder.ValidAudiencesEmptyError);
+ }
+
+ [Fact(DisplayName = "Encode Json Web Token with Invalid Expiration should return false")]
+ public async Task GenerateTokenWithInvalidExpirationShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.BasicPayload.json");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasExpiration = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ Signature = signatureContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.HS256
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Error);
+ result.ErrorMessage.Should().Be(JsonWebTokenEncoderDecoder.InvalidExpiration);
+ }
+
+ #region HS
+
+ [Fact(DisplayName = "Encode HS256 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodeHS256TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string signatureContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-Signature.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.HS.HS256-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ Signature = signatureContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.HS256,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ #endregion
+
+ #region RS
+
+ [Fact(DisplayName = "Encode RS256 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodeRS256TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-RsaPrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS256-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.RS256,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact(DisplayName = "Encode RS384 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodeRS384TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS384-PrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS384-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.RS384,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact(DisplayName = "Encode RS512 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodeRS512TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS512-PrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.RS.RS512-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.RS512,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ #endregion
+
+ #region PS
+
+ [Fact(DisplayName = "Encode PS256 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodePS256TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS256-RsaPrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS256-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.PS256,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact(DisplayName = "Encode PS384 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodePS384TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-PrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS384-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.PS384,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact(DisplayName = "Encode PS512 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodePS512TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-PrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.PS.PS512-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.PS512,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ #endregion
+
+ #region ES
+
+ [Fact(DisplayName = "Encode ES256 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodeES256TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES256-PrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES256-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.ES256,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact(DisplayName = "Encode ES384 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodeES384TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES384-PrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES384-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.ES384,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact(DisplayName = "Encode ES512 Json Web Token with Valid Payload and Valid Parameters should return token")]
+ public async Task DecodeES512TokenWithValidTokenAndValidParametersShouldReturnError()
+ {
+ string payloadContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ComplexPayload.json");
+ string privateKeyContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-PrivateKey.txt");
+ string tokenContent = await TestDataProvider.GetEmbeddedFileContent($"{BaseAssembly}.{ToolName}.ES.ES512-ComplexToken.txt");
+
+ var encoderParameters = new EncoderParameters()
+ {
+ HasIssuer = true,
+ HasAudience = true
+ };
+ var tokenParameters = new TokenParameters
+ {
+ Payload = payloadContent,
+ PrivateKey = privateKeyContent,
+ TokenAlgorithm = JsonWebTokenAlgorithm.ES512,
+ Issuers = ["DevToys"],
+ Audiences = ["DevToys"]
+ };
+
+ ResultInfo result = JsonWebTokenEncoderHelper.GenerateToken(
+ encoderParameters,
+ tokenParameters,
+ new MockILogger());
+ result.Severity.Should().Be(ResultInfoSeverity.Success);
+ result.Data.Should().NotBeNull();
+ result.Data.Token.Should().NotBeNullOrWhiteSpace();
+ }
+
+ #endregion
+
+ private async Task> GetFormattedDataAsync(string rawData)
+ => await JsonHelper.FormatAsync(
+ rawData,
+ Indentation.TwoSpaces,
+ false,
+ _logger,
+ CancellationToken.None);
+}
diff --git a/src/app/tests/DevToys.UnitTests/Tools/Helpers/JwtHelperTests.cs b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JwtHelperTests.cs
new file mode 100644
index 0000000000..048145e063
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/Helpers/JwtHelperTests.cs
@@ -0,0 +1,21 @@
+using DevToys.Tools.Helpers.JsonWebToken;
+
+namespace DevToys.UnitTests.Tools.Helpers;
+
+public class JwtHelperTests
+{
+ [Theory]
+ [InlineData(null, false)]
+ [InlineData("", false)]
+ [InlineData(" ", false)]
+ [InlineData("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", true)]
+ [InlineData("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", false)]
+ [InlineData("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", true)]
+ [InlineData("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", false)]
+ [InlineData("Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", true)]
+ [InlineData("Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", false)]
+ public void IsValid(string input, bool expectedResult)
+ {
+ JsonWebTokenHelper.IsValid(input, new MockILogger()).Should().Be(expectedResult);
+ }
+}
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/BasicPayload.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/BasicPayload.json
new file mode 100644
index 0000000000..f35d3e8f5d
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/BasicPayload.json
@@ -0,0 +1,22 @@
+{
+ "sub": "1234567890",
+ "name": "John Doe",
+ "object": {
+ "ObjectProperty": "object value"
+ },
+ "objectOfObject": {
+ "object": {
+ "ObjectProperty": "object value"
+ }
+ },
+ "array": [
+ "array value"
+ ],
+ "arrayOfObject": [
+ {
+ "object": {
+ "ObjectProperty": "object value"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ComplexPayload.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ComplexPayload.json
new file mode 100644
index 0000000000..22a3f5c9e2
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ComplexPayload.json
@@ -0,0 +1,31 @@
+{
+ "sub": "1234567890",
+ "name": "John Doe",
+ "true": true,
+ "false": false,
+ "long": 1234567890,
+ "decimal": 1234567890.123,
+ "object": {
+ "ObjectProperty": "object value"
+ },
+ "objectOfObject": {
+ "object": {
+ "ObjectProperty": "object value"
+ }
+ },
+ "array": [
+ "array value"
+ ],
+ "arrayOfObject": [
+ {
+ "object": {
+ "ObjectProperty": "object value"
+ }
+ }
+ ],
+ "nbf": 1661092370,
+ "exp": 1661095970,
+ "iat": 1661092370,
+ "iss": "devtoys",
+ "aud": "devtoys"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-ComplexToken.txt
new file mode 100644
index 0000000000..d7d21c8987
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.3_e8d7otRqRkIm-L0hwgFKsUho3WTfNfIKHmVwJH0I6aUob1pY4mJj6h2nAEJw5XIfDamhYcaAtaGI2p9PS4JA
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-Header.json
new file mode 100644
index 0000000000..e7429b83b1
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "ES256",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-PrivateKey.txt
new file mode 100644
index 0000000000..d3def8d62b
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-PrivateKey.txt
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgclXGo+zWHJe5FiAU
+1tudbbtFAbw+9jbPL/YvJsrXe16hRANCAASEv1LsNlkacvJdr+7cLf8x4+FBaU1g
+4FO8FxWamWGlGK1iT6gCKz5Ge/U7kRTd+L1dBL4kJikllPsQSdOLi+G+
+-----END PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-PublicKey.txt
new file mode 100644
index 0000000000..a50756890e
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES256-PublicKey.txt
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhL9S7DZZGnLyXa/u3C3/MePhQWlN
+YOBTvBcVmplhpRitYk+oAis+Rnv1O5EU3fi9XQS+JCYpJZT7EEnTi4vhvg==
+-----END PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-ComplexToken.txt
new file mode 100644
index 0000000000..a0b4cb0018
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.k-q7Lgj_oZWbq4JD0Lo42N1I-qebKZs6rlJvffFWic0zjBlrVTbQl7UIypjBXqTCxdJHf6tGni8ZFi8rYRRytakGogvnn1VDlLklevLUa7UgxGwv7jzJXrgc0gFkCAB_
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-Header.json
new file mode 100644
index 0000000000..afdb14b116
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "ES384",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-PrivateKey.txt
new file mode 100644
index 0000000000..4682349338
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-PrivateKey.txt
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB071rtVw4pgX+3nOf
+pld4rB5ktQwhlbCvz9mgHSSo2QIA9IgR8YRT/yJZelywZ0dFo5WU+s9JyG3a/GUx
+cFVkvyehgYkDgYYABAHxURk7lO/eDrm6aHMrI1viZNuOzlnq/WJFAloywCuto7vf
+ldPRQf7EFhmP7dWmVPEJ71pmoMPq/cRNm639/83oJwGWA3nK/SFY/9rQtDX/sW+y
+G1pwJtXYLXpn81ezjcWSJ+V14IfDFJGvO1ynRUdF3LYTSdR5DNHAfFWBUiCxQfYA
+qA==
+-----END PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-PublicKey.txt
new file mode 100644
index 0000000000..1b65a09637
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES384-PublicKey.txt
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEO9dpn/Frvw60BjdaO6XzvhwjBR9f1sRi
+1ozSmiRHjlBr+MP0f9V0lUswUsdyRtOidtDMOrpi4ee423eFF5ejj2QPH0s/a+sq
+HsPjoK4pHkMpRvZySAHpAafApxdH9O/U
+-----END PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-ComplexToken.txt
new file mode 100644
index 0000000000..f9d81e3141
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.ACDv2PhgBA6frpNkbY3-G2mRNXOHRJfzmROZGfFYwy5OOtzX5spM94ls5noSeZBvwxIOJQ7IwaQXUXMVlgJyExQfAYvpvYCByc5amJXgEG49MuG6_i5q7VxGIKo1vHmCZN9tGbpbj-rAKZbHrZ-0QNISOPpXQXauh0qtEyafvf99cdVc
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-Header.json
new file mode 100644
index 0000000000..4e65ddfd6d
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "ES512",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-PrivateKey.txt
new file mode 100644
index 0000000000..6736e375fa
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-PrivateKey.txt
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBsy6Ic047Q9FEKpsJ
+6tHrBJ/GIaP1jRl3nJEbSiGzqFzdhVrFhC+QxQWiUgvPSQ4BFNvl1zg3u82eUGnu
+cQQJ/8ahgYkDgYYABABWSTEDxTbp6HQ59NvuQI9YsfV0cYc2OUJPHStoYX54FOKi
+H6C8TlzCmzewyWF8Svu/SdcHfauWD4m9JmqrHpe+6AALLS0UMDd6GFIgZLTvr8sx
+qrcmZ/+Jv9i91vhvqlFRBK6xc/vPLmcZWiazfEthzpZfZG6lqerO935ElOsCKzIR
+3g==
+-----END PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-PublicKey.txt
new file mode 100644
index 0000000000..92521364b1
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/ES/ES512-PublicKey.txt
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB8VEZO5Tv3g65umhzKyNb4mTbjs5Z
+6v1iRQJaMsArraO735XT0UH+xBYZj+3VplTxCe9aZqDD6v3ETZut/f/N6CcBlgN5
+yv0hWP/a0LQ1/7FvshtacCbV2C16Z/NXs43FkifldeCHwxSRrztcp0VHRdy2E0nU
+eQzRwHxVgVIgsUH2AKg=
+-----END PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-BasicToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-BasicToken.txt
new file mode 100644
index 0000000000..d44fc3164a
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-BasicToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwiaWF0IjoxNzAzNzcxNTgxLCJpc3MiOiJkZXZ0b3lzIiwiYXVkIjoiZGV2dG95cyJ9.3COWqfXw4cfUXuz8TPCb1iyqxk38recPQrZ38z2_0uA
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-ComplexToken.txt
new file mode 100644
index 0000000000..8b423b37e6
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.taVhS7pJCx2SPpUE7TVf_A67fHA4Fea4-hHtRkDwoiY
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-Header.json
new file mode 100644
index 0000000000..7275522b4a
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "HS256",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-Signature.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-Signature.txt
new file mode 100644
index 0000000000..b60c0e8716
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/HS/HS256-Signature.txt
@@ -0,0 +1 @@
+Ym7AD3OT2kpuIRcVAXCweYhV64B0Oi9ETAO6XRbqB8LDL3tF4bMk9x/59PljcGbP5v38BSzCjD1VTwuO6iWA8uzDVAjw2fMNfcT2/LyRlMOsynblo3envlivtgHnKkZj6HqRrG5ltgwy5NsCQ7WwwYPkldhLTF+wUYAnq28+QnU=
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-ComplexToken.txt
new file mode 100644
index 0000000000..4f2c0efe45
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.OMeZLP_3fHmJIGeXOx62zh2ACBbMa19kcNw3JVsE2Y4XB4AtcgF6eyIOLxCQF-tkkbJP1uRqsuY8eDz94pyxANTI_vLuwHoDLHtBCzXRsCops9xaEk0AkTsUbSjyYCkb6vY3I4655PKwYwT-HJVtzb_yZjkBZc9gxv7MzWhh5Oi5Wighc-Dj1nfaySD0u5Vq_OaDAWkSpYGwRPjhmsa3fMzZ6j5TmkCnQBa3r59qGlBg86I-nid7YcS28Hzb7Qs5a_Glx8JdWzbB9KsoZ-U_zj2mmspOhd5ETH7Dt5NBTGpPvB5QV8M35s_IlvtsLDA9z172YyPAGsOaFb9OkjVGVw
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-Header.json
new file mode 100644
index 0000000000..5bce20f8dc
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "PS256",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-PrivateKey.txt
new file mode 100644
index 0000000000..de7f532a11
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-PrivateKey.txt
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwX4iDDoRpA5XY6nFqynqHCrD+Zbl+fhRLck9sD27gWBzu19+
+PVcWSyShOJ0F+ItEYkknAQf4bNvBM8GM8cSRB1g8AhnYqt9cmBXq3aEa+PNqodOG
+TR3AHsIBraZHs6QbdVeYaB1D6AOLl/VdYxp7sDOF6bdLITaZ8AADIS4pQ4Jls5zb
+BTHc76bGYWOFkY4siJvoQ+TJCmDpOiup5cpVKRZan4EoVafLAjlXCYJsporwyalv
+EuGdnm2+vKKm6V0i/TIIj19Zwf8Rwv09/utxQWIh4M/J8W4sSufecRkIsq7YgSVJ
+V/FYGebzEORHoiYJoRiCz8AbqNH5TXxf+VRCewIDAQABAoIBAA9SUxTfxkTU4+Oe
+7mma1JBtbH5Hjidom9kBzfI2OTJJ9nAYZDEsQ7YojOGkQXRIZt5Xh5Cj1m6cSiS0
+h1ofpBJOUNYjwM3rCPR8C9CH8NBUyhVNYB2cJPnqh+J9v8DAVnJIwOdBpf5pxXR/
+d+kgkDnIIqr6mibjoXXpMlY+xwKy3dHlEQ/dCFLA4GvL1eJiX0bf9RHQwG3hO+PN
+KA3+Uf2njXzbSi4i4LoKnfu/ouPh6Ds5I1ylURNi2mONpb3xP4JaaUygsSKBRvPp
+2TUzw4fkQ0x4KKUV4TYOYUKSnNAS4vuyhqm7RLdsyCuqpvk1N+bSVjrnGPwnoRWv
+VuOgw1kCgYEA7hdblU+MA0uBJOdZ4FG89IYazUuc9MPZiBLuGGu+FyJ1yxczgh+s
+oasMxqrGc0pOW3TguHLsz1tZLNeC9wxrM6WwJ+9Ld/5n8Wo8FZHYG3/cvzwLwE57
+PFhOedTMahCOtj2LayA7SdycrXni4o0JSeFsbddHmo38bTxOFWyRDuMCgYEA0Av+
+boittYq7Y6Hu7JhHVRo+zt1BoEypk6p0/4AyFFfMVZkvbr4UrAubjlqomuRGcZwM
+V1RGUDAwuVbaaZy3p0oxKlj+NoD/qNWemJOBWtqdny9pMKtiHQOdGhMeJ/W7DZcq
+j5jSRnEcnKLHjE6Y7s7UW4zwYfcDwgTga2fOeYkCgYEArXYhCDgrNt9UmJFhHpgO
+hUwKzNUw5famTogUJYChvnMhxLQDl+MBv8tCBJ+485gIXINbkaZRN4VDA/ODe//1
+PmyRWZbysdqM2gre+YwHGYnfRwg1O4cyK7Xefs51mzSy9L0EDSkvaNGs6IprrUi5
+orT0bVYx2b1mUiNkdCFj0acCgYAL43ORm2vZwOi+Y8WEbZs96LcIGSqj312dW5M9
++nRiJzY8+fj06hXCUxN7igCygTLpURJ/dwhhkq9mF+l5OYRhIeM3WUiFks+JNVPP
+wjYVHAL/0rS/HDwKDmfO0qLZg7hBqY68D+6MKfCJnt+qS99/XgBSjvY5kuT7FE5C
+tPbeiQKBgQCUrzcbKahyLoGDIcrljiGcwzW9J7tUqfMjQxlYvc+pLgWjdCVfkudl
+fpaxhN9g8Wd+bkNboqvke/r/9oHILeimIp60+Q/RkPARHJo9M7EMfS53cDGJgE24
+aUuWhvXrkGckfFZBge2JCrFw1NNDX876Dxp0OS8/LGJAHF8/+a4zig==
+-----END RSA PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-PublicKey.txt
new file mode 100644
index 0000000000..36b98a8160
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-PublicKey.txt
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwX4iDDoRpA5XY6nFqynq
+HCrD+Zbl+fhRLck9sD27gWBzu19+PVcWSyShOJ0F+ItEYkknAQf4bNvBM8GM8cSR
+B1g8AhnYqt9cmBXq3aEa+PNqodOGTR3AHsIBraZHs6QbdVeYaB1D6AOLl/VdYxp7
+sDOF6bdLITaZ8AADIS4pQ4Jls5zbBTHc76bGYWOFkY4siJvoQ+TJCmDpOiup5cpV
+KRZan4EoVafLAjlXCYJsporwyalvEuGdnm2+vKKm6V0i/TIIj19Zwf8Rwv09/utx
+QWIh4M/J8W4sSufecRkIsq7YgSVJV/FYGebzEORHoiYJoRiCz8AbqNH5TXxf+VRC
+ewIDAQAB
+-----END PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-RsaPrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-RsaPrivateKey.txt
new file mode 100644
index 0000000000..f4209d2e7e
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-RsaPrivateKey.txt
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA/TeoYuuKAmwxp9qK/x4zZfL9RtJpF+nS1qmfxcKJwMizzslX
+ovej95MpUlrFpFdVOMryW3DdvQ2Flk9ZFjk58NaRFFPlwfcMRfxmLxQuMMWswbI4
+vI11xb6YIfqqkDd5+ly8lob/n+EvPhL87Ffjm87nVCB//hzHM+CY51ljK7WDCx7P
+37MtSMjGVGUfIoZyc5Q4UKMOSFLttHt/cl+/gQx07ZDDCH090BU6v1Ugy8+3fsX7
+Gt9Mmy5MxeKdVd85v7zL4ZvhJu8KXfe2+qrpJfJ73Irh4qesNf87ccLrlOFl6FQh
+hDf31LCHdH+ankU8URybIfL7DssmJCgrs/p+9wIDA7//AoIBAErz5C3CfGxXKDON
+eYi8PTl8TF4/gK0HtSrUue6WgxVzoVoWn1kvx6qVwMx2USK+OWf3o45fT1E201qo
+p4T0JoF+9p2IhMMQxaqAPBNru/jdFnJNphO1p70Jj0oIN5KKMLtTY5JJ/CGtevlP
+MdmSdE6aFBkwKsTz1owUAT89MK+dSp+UEwPf5rYC+SE5/7ZNctZ/YLb9ajt3g8/0
+DH/4n2ZQ8y+p25w4OIwFiFaOgBiBSOqtMm/Y8pk0ahxtXwiUoQ/EdSSkIB0fhvTq
+CzsGzfyZCam/dJDbnDtVF4h718t6kO4Pf6lQf9W83XBaF9loLbddjh4LA1/K7j/T
+Y3dNwrcCgYEA/tTYonb3WjIyZNg7v/d6qczIQdFw36Qq1QHXgQZD2LCxSwJmHxUa
++Jcm+xqTyjnLM8LYwD7lGpDIfbcmcC24+UwN5OBL8XUXb0MRiZEk0QGAaAxuTPDJ
+LosSy5C9GmLXFoKYhJoOCb0GJNsQDn3ODHGF0o0FWRzHMBaxbiBHUJUCgYEA/mDq
+sr9wjl4gY+S2ywcSIZHr+dKyNts7O/KuPtDtC4Z+BMSNQf7aG7xXfb/L0ofhRUb4
+ML97f+rPXdMx0ICEd+XSHdj+Boe1Bf0in/93ZoBmsNMelh78DwXRBCNOYuQJG0gn
+X68CgT7M7iVUq+A5aXEFSiN7JGcbEMk6xS2k8lsCgYEA3wXjGFz/dXeNImBsd49m
+kJN5WeMaP7PdBMv1IKvUF4fNllJqWXtrVxpqxdb5qfXwHQr5EAy6OsR4QLTjW9kg
+qNqwl3kRsI/asypBU5/qnZLW+dFl0jx8e3deiGS02mnC32ZyE4Kf2mIPRzTfHeX1
+oj1kYHbKlihDFkT3iipjFyMCgYEAqsS6GCb0JQ+dUH2GVXj85DZA05iJV6Xm2zP+
+xfSdfH/Eqyjibd7sRalLWh9p8ELhfZkOCzkfgp/HEfhtgIHXus4rjNQMPCaRn8DY
+8Wn1vhf/5WAiCQjY6Dgfp7NNHFI5sNB5Xa/tgjDRPn8uDgWxA5MuqRmfy7kNsHfn
+wEW5IoMCgYAz5z8OtwnDGCNWjhZ6pJKRu0v1r+opiXkdwHEKByLrpwOv2zJJi/yf
+RNsMxKaOt9fjonHe7rZ3greWefydw538yspW/L+v93QHRdkbig5XTgTDI1jcHFO8
+/kzwRQ0UKkdyJjWAxLeO11HajOge235fxaWa1/Q8YEZdtE8gPNmBAQ==
+-----END RSA PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-RsaPublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-RsaPublicKey.txt
new file mode 100644
index 0000000000..e24581a7cf
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS256-RsaPublicKey.txt
@@ -0,0 +1,8 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEA/TeoYuuKAmwxp9qK/x4zZfL9RtJpF+nS1qmfxcKJwMizzslXovej
+95MpUlrFpFdVOMryW3DdvQ2Flk9ZFjk58NaRFFPlwfcMRfxmLxQuMMWswbI4vI11
+xb6YIfqqkDd5+ly8lob/n+EvPhL87Ffjm87nVCB//hzHM+CY51ljK7WDCx7P37Mt
+SMjGVGUfIoZyc5Q4UKMOSFLttHt/cl+/gQx07ZDDCH090BU6v1Ugy8+3fsX7Gt9M
+my5MxeKdVd85v7zL4ZvhJu8KXfe2+qrpJfJ73Irh4qesNf87ccLrlOFl6FQhhDf3
+1LCHdH+ankU8URybIfL7DssmJCgrs/p+9wIDA7//
+-----END RSA PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-ComplexToken.txt
new file mode 100644
index 0000000000..0562b1ba2a
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.cJLZihnciGWhtn72ejTYvWuA8zv200IgBUw8QTXbsppeIv2HqwE8sKlt7pOjVTvsYMv9pXkN8zlfj-gRpxE5TcPjGbYl2tRb0iPm7CLalxfGLmeywfoXT1xOS4uhni_XGVxU6eQaAjNR5UsIJ_v02irUlaMVNHLenKpGDMKUFwZjz5TQ9NE3CI59tSehlRtiK-MJ1K2Op-yQHsFYIOqi9xQo-vTtadrIEg64IJcingqIljfaz0ESGSLJ2_OODwCjBfiq0X0mlaPdwVIOnqE_6wyS0qR4p6QHuFoG5eWG6WrTG2YfPc4klQ2RGdkGZetboARvFMr-M2PQxhRt3y7u4y8TnmIVDmwKpAC5h53FQCA8Mt6jO0dwSnR-OEXp766hrFIMdrazQvRCeAcbATeqUtPCgWUKuz1eU8UOaVCPLkbk3r2rspGKXgtutbMemqmTsGDtzy9p-Tx_-NS8TGy0I-bc8vcvcgPZ3wDyCeQhBbme5UgqldstZcMBYBaBiufNtRS-V23qJTJfJIarApCSfRv13lufSOwl_n9-aOXmzFRNBjq-kwFt_ZMO6TeZN0PGwxt6Ok_Yr8M70aVhhdOUJEVUcMJaU1XNEvPvIPiDeSVMw142qYe6b-jAeJ5Va4tqqvpChjBI7JR5vwo_xG6JIxlocIJni9RUqgLRsu9DUL0
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-Header.json
new file mode 100644
index 0000000000..782072e3e5
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "PS384",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-PrivateKey.txt
new file mode 100644
index 0000000000..6cc74aeb6d
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-PrivateKey.txt
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAv+JkirePyexeUtrBQqIP7IYrm9pokdygBIy/othuSzKaiqOF
+paAiMHYqBTGQTyF1JZl8Y1PFemPbD/1P/76yo11Q+VLzot4q0re+JeY4PPzUz+rL
+lJ4LuHnQePh/jLMO8oPbdyFVrDZ4sVeyx6Wv2r6FYXty+9LgtDvPBEejSREWrqeA
+Yxy1WEIWSIqJlQb3MyhomXy4oChARGLEz5mfcGzzebOZytCzfGmzVZSbNgP668Hq
+tocQ+fgT8G0cWY5eT7NFElXTl0kx2fNRF8W9nHc30ui0IKvL8XHriVyPLw7u+XB9
+NUJu8MVm1C2MwT9TO0W/i1lcUpZJqXKOWWvKtZpwEr8j86fnzA3HL0Uy7FuIHW9A
+wDOK3TIcNUBJKfTlHESyPRgIiHkvStwuqroZ/9HZL00nPjSrVS5+A8UsU8BVYO5D
+VLZdwx6xVe+Yo259E1LHL/m8xU7nWwSrUPQFGVJFjL8TfHHsDbtOFVpQKEKWIuvt
+RPgHtC1Q/Zeibz+/eMMRu2MSzbWq9kMuddhwlSmpKlEHYFxWpEJvt6Cak3zkOYgj
+HsmXmgNbENFLtn0n/JJuEcNGwMALF64CHVC85AJsDzbfShOn0xopnEUKqD9J+79s
+GMJmzDTMp7ki2LJEaoHlCq9rq/BSBvybbKVRQT7n2CHrDJ4Yuq37XImNR5kCAwEA
+AQKCAgAKdUBxUoaY1OQBrEhI9Uv8okXlb0qQe5exk8m8ioL9YHdNi9LEh4Uhgskw
+wgYpV+uadmJyB+5tCJz3gsD90XMLRhJcGl4qv+TR2f+YMXNaeJWxNMzub0w4fFQl
+Sbbq8FXl2BOpqySQF6JtmPfmm3RJjeV3Xgry6GgEjrduVmiy+mZT889ip32HP0fX
+YYyU1Z26uS61QdfOq87v4zmyjl8DGb8N4KDmzcfghaLHbOtmLuKZv5aHJvahBxkw
+GwRG6cRgX+cJdCnNdqjrxfADJYz7ZoCBg1sQ5kgd40147bVklVHnQK8kTFf/UaT0
+dryTijvjML6bLc5DNotye6rAOyifs50OB8XYfKMfXLlyUiAhWZ1WFmfD1ipc282q
+IW0ejnPw1jBOs/Fo22kqFA+Q+to81UDDLq/udEomrpm7scsbb6Q09Qjt4rbz3ZMe
+ceHIBli6nFrsQw1gfyjWMGk9eP+ozu1V39TSejxRgxaL2siMHBiaEdeuZyAWZULa
+OSOAXx9RwoyRveDU/bIuTFkrKtku5fa7edXk24LyZVXe2yQ7r6cTQRTy79349A31
+Mh7c9v5x3baAOEADZ3bjNOwW0SYaw0PWMzp2y2oIroDE3qK1R6RFMlpaK3fGpfPa
+d6OBPEjvqYKi8Q4My7KoGBYw7aWhWSOXJ8UEdFh1Rdt1nGP40QKCAQEA+93X0SbT
+POotIv8uaQrCXjZQu4KCFK3hGX2ktlgGJ0+Ky4pCrEg0aVqo06LZqvCxQ64ct1be
+GyiK5e5pcsgi/BarDrN6kSpEPU9M4PlHnOMexFieyxaKB4p2+i/mTqQ3xvu3L0i3
+PUNfPFR4uS6xKGulwvQZ+JRdRDxLIMqQC2itKE+xpVOM2zlJOg5UBoQsBuLL6AXS
+UrteFCyRaeAZZPVKeLps0mYmaKA97N/QxPkIamPCPcI7+ymuEHcinOMTQHob23VE
+n4Gji8XMrTq07VOZ92J2MCpOg/coDKYgBIon/zSyFuY9xuRsEZwUf64xeGjs06po
+eLf4mUvIbosacQKCAQEAwwiMfFhmJWiAEJMWxva0K/Iz7UvtjKPX4aVjFXxIDfHu
+dQihQM2lia3FJR+in0kCDBQWuAda18Wks2kfBx/F4yJG35Q60bmWHibzPf+uqUuM
+3Dp4x68WoBg2wimBQk+GCnVYBJV4QPTHTQZtWQKC0bD+nq8YpWDvu6Hxpt+lwFEb
+HXQQ5OasPctvc0E5n17J+IY+1Y+qKEKLBwMka8/2tfMCAYRgc/YFtDKBZYzwxqiS
+VQFmwHotkJ7RYb9/Pbc4/FBlywxGIKx0YCyv5PXqUXvoYTaLVflKgp1wlmxoR154
+jebh2ZurArz4BzBCPHpYBwENfCCkVcMePr13ksGDqQKCAQAP9eHsPhKaxJgxpIwT
+WfwxLi31g/KsTsx56g1bBL73FMJN3agV9Mlld7pFFwfGHlSNXmYxdA7FF8+dfHsw
+K+CTqvgSCWuV8WC7pd/QTdus5HOZq6f8ZKRAkBS0C/8DMsLaGC9mjqwV9MP/Nbck
+vQG69v9dGEzhBL3YSh3UqSFxp6N5b3tpZ58AcWPjiu7mLs//a7XSMTQ/y+HNU3mv
+hOT3EdgzSFUHDDQY4zapYRGAues/mqy/bySP6Pc/0dzepkEdD9/eC1Na7EMp41Hy
+FEtM0sRp+dJVgQjSsgHWs1xKDVs1f+hdLR/zD5yNjR97a9V0tOl1DFJse/QD+tpr
+ruLxAoIBAD0Kz+2bPV039904LEjZrkqS4+3Bdfguz8KwIPL4kxTKQ/gdW1sNAmK1
+IJ8d56gzEZD2OKYgpy4hjgsZO7zfFPCiKcyQeFhr3bBSQI+HFwOlwdCr/GGDS3GQ
+IpNiqjI28Z0UXRgSMDEka7yP5mmVf5xh6U+BPhWE4YO4jAnOtfIbcIEncqIyEHbG
+1XsdTwuyf0RpwADXlkpDiYAIW73iuTT5egc/h3DD78UWialfE30aBbLVXXOgyeIZ
+0y7k4M5QKmXORA3pKYnQcBwp4GOpFE2smm7Sw4mFHO0Emdh61aiAyPqg6M9TCZMx
+0e9uC00ifaqziaTwEn9FMpIwetxT9QECggEBAJvUi4HWxh9ybT1o7k92LKK4YUgk
+G2Zf2AOcxfzMlNyrAZ06DMOjBYHs2gJsj9RI254kcSGVdGxB6IMO19p4RePtdM/F
+XEnyygOcEHYCYshXYIfboSqNCjUMihy/DgYEOumyth7PL79u24nYWG06a49twfio
+Fz0vK3RFFz2qtqbw/szYngPBomnH1sRtLC/ddT3ENei7fH20OCHAs+rvJfBUlisf
+YOBj/akxWcuS8ZNHSfEEwh8EvarR06gRfv9+3bfdrbFaRgXq1Okbhg21CBbnjKkS
+aTtBb/Zfh7mMA7Pl1KX5DvVtVu6GUOTug/bm1Mga8gXyFIrC4O/dqcpGZaQ=
+-----END RSA PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-PublicKey.txt
new file mode 100644
index 0000000000..921d29250a
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS384-PublicKey.txt
@@ -0,0 +1,13 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIICCgKCAgEAv+JkirePyexeUtrBQqIP7IYrm9pokdygBIy/othuSzKaiqOFpaAi
+MHYqBTGQTyF1JZl8Y1PFemPbD/1P/76yo11Q+VLzot4q0re+JeY4PPzUz+rLlJ4L
+uHnQePh/jLMO8oPbdyFVrDZ4sVeyx6Wv2r6FYXty+9LgtDvPBEejSREWrqeAYxy1
+WEIWSIqJlQb3MyhomXy4oChARGLEz5mfcGzzebOZytCzfGmzVZSbNgP668HqtocQ
++fgT8G0cWY5eT7NFElXTl0kx2fNRF8W9nHc30ui0IKvL8XHriVyPLw7u+XB9NUJu
+8MVm1C2MwT9TO0W/i1lcUpZJqXKOWWvKtZpwEr8j86fnzA3HL0Uy7FuIHW9AwDOK
+3TIcNUBJKfTlHESyPRgIiHkvStwuqroZ/9HZL00nPjSrVS5+A8UsU8BVYO5DVLZd
+wx6xVe+Yo259E1LHL/m8xU7nWwSrUPQFGVJFjL8TfHHsDbtOFVpQKEKWIuvtRPgH
+tC1Q/Zeibz+/eMMRu2MSzbWq9kMuddhwlSmpKlEHYFxWpEJvt6Cak3zkOYgjHsmX
+mgNbENFLtn0n/JJuEcNGwMALF64CHVC85AJsDzbfShOn0xopnEUKqD9J+79sGMJm
+zDTMp7ki2LJEaoHlCq9rq/BSBvybbKVRQT7n2CHrDJ4Yuq37XImNR5kCAwEAAQ==
+-----END RSA PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-ComplexToken.txt
new file mode 100644
index 0000000000..4955bf5dd7
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.g2chnq4PLS929mhPUg13z7fneoYLpRWzk5cmWgPHS_EUSN78aJpTDTN81mZ9Ebo1wCrYfpnan1HLvjyoZu9DvLqeWYN0o90EqgxsEoelA-ajozGgCYp_E5lpAJ7Uazhx_g2tKulXbA7zeW_CF4T8yv1jG3GAgy5I9MMw6Jul50BIlV2H4rS3MLh-Wd5rvNt2fkDsxhgs3EhAdd5-R5B8MKov8zAtOLkZedQpPvnPrTYHvoyMS3SAM8V_j1Chbkzqp0k5W8TMpVm2bQFnYfUzp-GAJ7hIQ5plj6sIutLRHsmZT8WuQJeVIXe1cHXw-TZPBWpCnxLVJzCwTyKIEv6D_g_8hYz-KkTZN_wazTtNPAkcgjgapBElFkHlZwvmMMX17VQFghLx4V7xN94v8rDdqPeMnLWOt6BWiPsoJ8E4RjQi4Ec7KjOOhk4TJFFeOYMWqYEw24_HGHcWgc4eLGh0HAgVT2oPUlc-Kxc0_ssfCeEFMmK_kJAOOjgqCPdre_fV
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-Header.json
new file mode 100644
index 0000000000..72253e087d
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "PS512",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-PrivateKey.txt
new file mode 100644
index 0000000000..ffdfba88cb
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-PrivateKey.txt
@@ -0,0 +1,40 @@
+-----BEGIN PRIVATE KEY-----
+MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDg7oQseQHuZtq3
+sWsfFTPObe7oNJRX2uas6rvV9mTwoWaZ3WBhjgd6Jzv0/EEU45ddBxGoK/4pFH8I
+oeSAiOWoBUJU7FkBIBQsmjz+sAzvyPYB7HEG4Dv2dcz1N+VFk7MWgQ0B/SRkxgOL
+3Rj2MphH68sU1h0qAz/pnyG1Mb+Q0DTKL60rANwaml4ZEHPoPFXtpxGTcVy2s4LZ
+b9q2GR61VtnpLSbRvo2UtCefWLCYE5S9maTh1oPHLWlee4QtzwWSLyziSx6lbJsK
+SAeVvvwd5bRgX9SAeSRw+I4V8kMzcSDqXS67ddo97o/0DPeseiEJ7DkLtEklUBni
+jP4liHlq2m7wMtil2FuocLAtY7ACiy7ayqpJw/b2aJO3F5dfpjoh0AGDFVchDisb
+97GNM8LhXyceobs/0/Tm8zhvPKE0DlzSKg0uNQ/4ZJ7Wv5S3MA9djaMjvX15IfBk
+1MeDANjM3KNzBMHVNcaD4enewwIuPKy+ROfQPUvwLONYCkghD4MCAQMCggGAJX0W
+B2mAUmZ5yUg8hS4zTRJSfAjDY/nRHNHJ+P5mKBrmbvo6uu0BPwaJ/ioK2NCZOivY
+RrH/sYNqgXBQwBbQ8VY1uNIO1YVYshm01R1XfUwpAFIS1nq0qROiKN6mNkNIg8As
+1aowu3ZV7KTZfl3EC/yh2M5aMas1UZqFnjL1Qs1eIbKc3IAkrxm6WYK9/AoOUkaC
+7eg6HnNAeZKkc67ac455ptzbzZ/CQ3Nb7+Qdbq3uH5mbevkV9oeRj79Ask0rSGAG
+C4upx1Ib2pTVrqZ2fPNJOcNS0F2yJFk5sPY3kTSigzcavBfW2ClbIk0Ph45e7Py8
+tUw+/eP0v9F62zKZsnYWqs9mYVwbaFLfbIxPRGW/3uq2fJQM6hSFC61V8D6iOSes
+UgQpniCuE1625ea74ORGqcaNAWaIa8ekpDhylCKaOhP00fmQR64+MwhncntRCBhk
+4ZRwDDkU6qwvIGpD0IAUX/1Tk7LBEdPUucNGT8kSWPxHTrrzpz7P4Te1vvLLAoHB
+APE5L3jeTlxhKYPjq+jUJZ02gSxQ6WPxGzrEIWnWEGBtUbg9AOPKr6xps8ID4eFD
+JY/Pb+2czDeGFY3Rv4Rv5kczNwnGGw1koQANM/e+1VeklZX7aRiPsdk5i3QA73QP
+9wyyEZPW+Jrh+ImqMLxbEaXXpfKM4VVyRCfZqTCi1BijXv1ArA8LNVVCSdg0Uz2T
+6nUTaXyr777VCmQYKW851Rw9qp3AUtpZXDesUcAOIsjN9nMA7Wbw44SV/BXhXbMm
+YQKBwQDutdkkJtWdHspm51mVBA+S+3vZevo6VdxcHRKGVuVryAAe1Y1vHCGAjLlk
+Orid4MHsMFzeTgL6ckfV0l196Pj04q/lkFdQSxY2cj+qWv8QhBoLTvpFu/egs+n9
+BqRKHnW+L+YDJWTRaB4eFTdtcEn2LdkxbYvh+n44a7NlRx2im2B4caQzUHj1DN/M
+WNTKuD6359xdlu1w9miAs4NRSWzNnn6VVHmEq+PhPpvEITmGuEg1Wk6Q6rKAHgf6
+2qf0OGMCgcEAoNDKUJQ0PZYbrUJymzgZE3mrcuCbl/YSJy1rm+QK6vOL0CirQocf
+yEZ31q1BQNduX9+f873delljs+EqWEqZhMzPW9lnXkMWAAjNT9SOOm25DqebZbUh
+O3uyTVX0+AqksyFhDTn7Eev7BnF10udhGTpuobNA46GCxTvGIGyNZcI/U4BytLIj
+jiwxOs2M07fxo2JGUx1KfzixmBAbn3vjaCkcaSrh5uY9enLhKrQXMIlO91XzmfXt
+Aw6oDpY+d27rAoHBAJ8j5hgZ474Uhu9E5mNYCmH8/TunUXw5PZK+DFmPQ50wABSO
+XkoSwQBd0O18exPrK/LK6JQ0AfxML+Phk6lF+03sdUO1j4rcuXmhf8bnVLWtZrI0
+ptkn+msim/4EbYa++Sl1RAIY7eDwFBQOJPOgMU7JO3ZJB+v8VCWdIkOEvmxnlaWh
+GCI1pfizP92Qjdx61HqakukPSPX5mwB3rODbnd5pqbji+63H7UDUZ9gWJlnQMCOR
+ibXxzFVpWqc8b/gllwKBwQDZ7XlbcQvUKk3/eo5HYOji3IBt4vGULrnSl66XdtVJ
+54nSbfzlJiF5AcLgLA+TlsRWIRJAaiKHq+k7oprwIoV9uBvdJeCZnEOWVbc3cTfZ
+DuYRLlRZ2bI/aOkFwBHMr65yWlpOE/8LBCZwp2zSWWAsAxQajBR2R3Vq/fQ0HonW
+dScywAJyHvJNt9BTDSYEU+uBvuH6SEWqCu7iZgu+Tz+3gpouJ+QAkSThalG9XADx
+mW5VsiqmYW7Gppcv+Cc6KvE=
+-----END PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-PublicKey.txt
new file mode 100644
index 0000000000..a16e913a8e
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/PS/PS512-PublicKey.txt
@@ -0,0 +1,11 @@
+-----BEGIN PUBLIC KEY-----
+MIIBoDANBgkqhkiG9w0BAQEFAAOCAY0AMIIBiAKCAYEA4O6ELHkB7mbat7FrHxUz
+zm3u6DSUV9rmrOq71fZk8KFmmd1gYY4Heic79PxBFOOXXQcRqCv+KRR/CKHkgIjl
+qAVCVOxZASAULJo8/rAM78j2AexxBuA79nXM9TflRZOzFoENAf0kZMYDi90Y9jKY
+R+vLFNYdKgM/6Z8htTG/kNA0yi+tKwDcGppeGRBz6DxV7acRk3FctrOC2W/athke
+tVbZ6S0m0b6NlLQnn1iwmBOUvZmk4daDxy1pXnuELc8Fki8s4ksepWybCkgHlb78
+HeW0YF/UgHkkcPiOFfJDM3Eg6l0uu3XaPe6P9Az3rHohCew5C7RJJVAZ4oz+JYh5
+atpu8DLYpdhbqHCwLWOwAosu2sqqScP29miTtxeXX6Y6IdABgxVXIQ4rG/exjTPC
+4V8nHqG7P9P05vM4bzyhNA5c0ioNLjUP+GSe1r+UtzAPXY2jI719eSHwZNTHgwDY
+zNyjcwTB1TXGg+Hp3sMCLjysvkTn0D1L8CzjWApIIQ+DAgED
+-----END PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-ComplexToken.txt
new file mode 100644
index 0000000000..1aa68942a3
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.JudxYaw-Gs9AT8uQQCVFuxLTmgcxbMAWsmVj6VBo-2Q0d7KkwLmyNr325CyGDuo6-zZSOVwyMKFleal6Aea8gdPB81izufag2_cogTYWgzQlNbYlDUUITQqziVvh115XTuFx8rjXAr7YHgDjK5iNhfA-0lnpXkgtYeIvl4ejVJpGA_mOimjxOKSV0pgIDBuW1IufS3clPKCNMX-qVI1WD8_28rUrjP05WWWARfX8TlbMcpIkqA5GiZiis99qt2yfdU9yA-MFQYznpy85f_nd9AW2xsQdaxppAYs5pY63EzWdCIKjCggGZ9W3dkLAVSbTpzf6U3prKsZK5MRFBB-ItA
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedComplexToken.txt
new file mode 100644
index 0000000000..0852952d02
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedComplexToken.txt
@@ -0,0 +1 @@
+eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZmFsc2UiOmZhbHNlLCJpc3MiOiJkZXZ0b3lzIiwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibG9uZyI6MTIzNDU2Nzg5MCwiYXVkIjoiZGV2dG95cyIsIm5iZiI6MTY2MTA5MjM3MCwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwib2JqZWN0T2ZPYmplY3QiOnsib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19LCJuYW1lIjoiSm9obiBEb2UiLCJ0cnVlIjp0cnVlLCJkZWNpbWFsIjoxLjIzNDU2Nzg5MDEyM0U5LCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19.QlK_Qqlt96oQ6ozwnH7FvDIDFbG3O_QOjxv5m2s4cD_lsm433U7L4mZgqgbC7nTtB7R3tEcYBNV64YQfs-6ZaLnEcoOWF2ikIElYRK1-gDiND3XisVD-DfbgvZ01_TgI4Egsv3baWrvjG1n5MIJsd2VWb0Qrr_3EW776CqUwYrNJKDRv8JVBA0C-4THavsXkutOouJfSVk7_3zqChqWzv2kOkfi9M0_IrNm8KE4SfWjuNfxRaHuNFyebAil18CWrAFPPx3ajNCeDJC3-Qpqaa1PaZLS1OK2Jg8xeZVKUMV-ziUr0Y0xcpyRzFEig72w89MpsuRXPCMQineC1EclphA
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedPrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedPrivateKey.txt
new file mode 100644
index 0000000000..1944d00978
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedPrivateKey.txt
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6EGXn+EKSwQaul6wd87KjZ2CniIn9c3ThL631wj63w2zpkP5
+FBtJETiY/udTjnFJrNGkfig3osib7i4kv4iwusGKqhRt5mzwrNfPc+XQMe/nd6Ae
+PBmhMXiO7JkKBHmN4YAnnd75Fciwtq8WPCKUZF5jGgqJWRdpwXdfYPSCrJ9WoMPn
+Z4AwIpvRMmfnuL42efSrc95I1AgiayIO4Vw5f9eU97463AklqLJF6IZYwkyUEFy1
+XSbdJXlQNGeMilxgRWUr1MiETHbwB6D+2jbh6ni+DwDRcpzIbNhwlEVRprTy9lwf
+LZSyxz6x2XNzbSXB4AVsBw5F+GdYxK9ra/yuKQIDAQABAoIBAGuHYrGsuN0+SzmZ
++U2eufySvhSEb8fHwXQl8GtoRu3aJCCIO9tpPozwjTUG2JKMNLYEYYnfCNMe2cKO
+LolWuavdS1uvFaqnKU58pBlefuN/1MR3p0ff/dcvn8w+lnq35E5QzQa0YtIJFned
+3Ed/IwDNCkjWM/zBcD8YC1qSGaXdRZfFRK796hCMAc8BBkqeDxqppMWabAoYdmBJ
+rHr884aSz4t8I3Bk5ojHMla57NLkZWQ3qeQpjDP0jaD/R/zYhaLpjGcOaO3zadJ+
+RoT3+GMM5fifF4iex4Fv4iU5TayHwzvb7AH/7jrbMyo4VgTrWyYSC6AU5So1ZBWN
+cioKH+ECgYEA+tN1BptKJBVFUdNpH7ceeig2Zc/XKdkB9vpBOIg2Xvq8Z/xlLCIS
+lJnU+8kmYtdl2/gXR3b7Zw76j7EAin1nmFd/XKz3hYhjo1AkT9q1qfPg2TQmPIbv
+GZIZvjhop6LRr0RIo88i3TsSBUjUpEpMKznbxh6xUboGrGeQqrtsgbUCgYEA7QwS
+wEusrm359LTYOrzbtZBBl7TxUotHO6abCekXJtiuhWaR4eMMmL0TsyHJIAMWtQ1g
+3bFbomMZGs+76ULdgdrnc6hxO9BoWRpv5rTdmA3EjTa+B3LsXrdy85Xlt9HcNoUc
++7crlaNQYUKDb97AJDZgk5TDcCRv6IeQHdtnkyUCgYBU6j+lI+9+tcDJCeR9+zWT
+L4fzAeEa5r+2iFSKOfsGPqaIIbdysHpP2qBzOU8IiVmPlbbz18EWy8hh2w+O0xO+
+TGjuzBPkHh91S41vh2CXD9xgTDphpWDO7FpQvrIXhpSsXFanNlncQcJBDb4HfKu6
+upS/xuZK/8qAlXMxidAbPQKBgQDTQvdTOh5kNs+WL7amYrrNTgunUTPiBQ8vMoTq
+iDqB/ItNiORrFRec6KYt9+ZwCVCre4jhC7hHOVqecpVehqEzogy1H2ILlOnqv40L
+aBY19c8+q5MgwGO45nYkcWuSMA666Fe0XsMRUcPJaqYJhdRoca4Q2Xx07YRvEKJ4
+eZoNoQKBgQCvOKbwUXzeqezKfRn1BbnvJYe/xQbONYHgkj47F3JqcqzeOqezJ7AM
+6cXnSTAXhi2Chw9CdswVB+nEdAW41xznQkSSclxfhVtiT485b3nXdmk/n1u+TAs9
+DGgtJN2RL0hOZzZrbLEtaRSo4E9l+oL1Ke5Ytdb4ossIRWSqzl+XIg==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedPublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedPublicKey.txt
new file mode 100644
index 0000000000..1944d00978
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-EncryptedPublicKey.txt
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6EGXn+EKSwQaul6wd87KjZ2CniIn9c3ThL631wj63w2zpkP5
+FBtJETiY/udTjnFJrNGkfig3osib7i4kv4iwusGKqhRt5mzwrNfPc+XQMe/nd6Ae
+PBmhMXiO7JkKBHmN4YAnnd75Fciwtq8WPCKUZF5jGgqJWRdpwXdfYPSCrJ9WoMPn
+Z4AwIpvRMmfnuL42efSrc95I1AgiayIO4Vw5f9eU97463AklqLJF6IZYwkyUEFy1
+XSbdJXlQNGeMilxgRWUr1MiETHbwB6D+2jbh6ni+DwDRcpzIbNhwlEVRprTy9lwf
+LZSyxz6x2XNzbSXB4AVsBw5F+GdYxK9ra/yuKQIDAQABAoIBAGuHYrGsuN0+SzmZ
++U2eufySvhSEb8fHwXQl8GtoRu3aJCCIO9tpPozwjTUG2JKMNLYEYYnfCNMe2cKO
+LolWuavdS1uvFaqnKU58pBlefuN/1MR3p0ff/dcvn8w+lnq35E5QzQa0YtIJFned
+3Ed/IwDNCkjWM/zBcD8YC1qSGaXdRZfFRK796hCMAc8BBkqeDxqppMWabAoYdmBJ
+rHr884aSz4t8I3Bk5ojHMla57NLkZWQ3qeQpjDP0jaD/R/zYhaLpjGcOaO3zadJ+
+RoT3+GMM5fifF4iex4Fv4iU5TayHwzvb7AH/7jrbMyo4VgTrWyYSC6AU5So1ZBWN
+cioKH+ECgYEA+tN1BptKJBVFUdNpH7ceeig2Zc/XKdkB9vpBOIg2Xvq8Z/xlLCIS
+lJnU+8kmYtdl2/gXR3b7Zw76j7EAin1nmFd/XKz3hYhjo1AkT9q1qfPg2TQmPIbv
+GZIZvjhop6LRr0RIo88i3TsSBUjUpEpMKznbxh6xUboGrGeQqrtsgbUCgYEA7QwS
+wEusrm359LTYOrzbtZBBl7TxUotHO6abCekXJtiuhWaR4eMMmL0TsyHJIAMWtQ1g
+3bFbomMZGs+76ULdgdrnc6hxO9BoWRpv5rTdmA3EjTa+B3LsXrdy85Xlt9HcNoUc
++7crlaNQYUKDb97AJDZgk5TDcCRv6IeQHdtnkyUCgYBU6j+lI+9+tcDJCeR9+zWT
+L4fzAeEa5r+2iFSKOfsGPqaIIbdysHpP2qBzOU8IiVmPlbbz18EWy8hh2w+O0xO+
+TGjuzBPkHh91S41vh2CXD9xgTDphpWDO7FpQvrIXhpSsXFanNlncQcJBDb4HfKu6
+upS/xuZK/8qAlXMxidAbPQKBgQDTQvdTOh5kNs+WL7amYrrNTgunUTPiBQ8vMoTq
+iDqB/ItNiORrFRec6KYt9+ZwCVCre4jhC7hHOVqecpVehqEzogy1H2ILlOnqv40L
+aBY19c8+q5MgwGO45nYkcWuSMA666Fe0XsMRUcPJaqYJhdRoca4Q2Xx07YRvEKJ4
+eZoNoQKBgQCvOKbwUXzeqezKfRn1BbnvJYe/xQbONYHgkj47F3JqcqzeOqezJ7AM
+6cXnSTAXhi2Chw9CdswVB+nEdAW41xznQkSSclxfhVtiT485b3nXdmk/n1u+TAs9
+DGgtJN2RL0hOZzZrbLEtaRSo4E9l+oL1Ke5Ytdb4ossIRWSqzl+XIg==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-Header.json
new file mode 100644
index 0000000000..136c3e7d35
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "RS256",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-PrivateKey.txt
new file mode 100644
index 0000000000..1944d00978
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-PrivateKey.txt
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6EGXn+EKSwQaul6wd87KjZ2CniIn9c3ThL631wj63w2zpkP5
+FBtJETiY/udTjnFJrNGkfig3osib7i4kv4iwusGKqhRt5mzwrNfPc+XQMe/nd6Ae
+PBmhMXiO7JkKBHmN4YAnnd75Fciwtq8WPCKUZF5jGgqJWRdpwXdfYPSCrJ9WoMPn
+Z4AwIpvRMmfnuL42efSrc95I1AgiayIO4Vw5f9eU97463AklqLJF6IZYwkyUEFy1
+XSbdJXlQNGeMilxgRWUr1MiETHbwB6D+2jbh6ni+DwDRcpzIbNhwlEVRprTy9lwf
+LZSyxz6x2XNzbSXB4AVsBw5F+GdYxK9ra/yuKQIDAQABAoIBAGuHYrGsuN0+SzmZ
++U2eufySvhSEb8fHwXQl8GtoRu3aJCCIO9tpPozwjTUG2JKMNLYEYYnfCNMe2cKO
+LolWuavdS1uvFaqnKU58pBlefuN/1MR3p0ff/dcvn8w+lnq35E5QzQa0YtIJFned
+3Ed/IwDNCkjWM/zBcD8YC1qSGaXdRZfFRK796hCMAc8BBkqeDxqppMWabAoYdmBJ
+rHr884aSz4t8I3Bk5ojHMla57NLkZWQ3qeQpjDP0jaD/R/zYhaLpjGcOaO3zadJ+
+RoT3+GMM5fifF4iex4Fv4iU5TayHwzvb7AH/7jrbMyo4VgTrWyYSC6AU5So1ZBWN
+cioKH+ECgYEA+tN1BptKJBVFUdNpH7ceeig2Zc/XKdkB9vpBOIg2Xvq8Z/xlLCIS
+lJnU+8kmYtdl2/gXR3b7Zw76j7EAin1nmFd/XKz3hYhjo1AkT9q1qfPg2TQmPIbv
+GZIZvjhop6LRr0RIo88i3TsSBUjUpEpMKznbxh6xUboGrGeQqrtsgbUCgYEA7QwS
+wEusrm359LTYOrzbtZBBl7TxUotHO6abCekXJtiuhWaR4eMMmL0TsyHJIAMWtQ1g
+3bFbomMZGs+76ULdgdrnc6hxO9BoWRpv5rTdmA3EjTa+B3LsXrdy85Xlt9HcNoUc
++7crlaNQYUKDb97AJDZgk5TDcCRv6IeQHdtnkyUCgYBU6j+lI+9+tcDJCeR9+zWT
+L4fzAeEa5r+2iFSKOfsGPqaIIbdysHpP2qBzOU8IiVmPlbbz18EWy8hh2w+O0xO+
+TGjuzBPkHh91S41vh2CXD9xgTDphpWDO7FpQvrIXhpSsXFanNlncQcJBDb4HfKu6
+upS/xuZK/8qAlXMxidAbPQKBgQDTQvdTOh5kNs+WL7amYrrNTgunUTPiBQ8vMoTq
+iDqB/ItNiORrFRec6KYt9+ZwCVCre4jhC7hHOVqecpVehqEzogy1H2ILlOnqv40L
+aBY19c8+q5MgwGO45nYkcWuSMA666Fe0XsMRUcPJaqYJhdRoca4Q2Xx07YRvEKJ4
+eZoNoQKBgQCvOKbwUXzeqezKfRn1BbnvJYe/xQbONYHgkj47F3JqcqzeOqezJ7AM
+6cXnSTAXhi2Chw9CdswVB+nEdAW41xznQkSSclxfhVtiT485b3nXdmk/n1u+TAs9
+DGgtJN2RL0hOZzZrbLEtaRSo4E9l+oL1Ke5Ytdb4ossIRWSqzl+XIg==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-PublicKey.txt
new file mode 100644
index 0000000000..7126789c07
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-PublicKey.txt
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
+4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
++qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
+kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
+0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
+cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
+mwIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-RsaPrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-RsaPrivateKey.txt
new file mode 100644
index 0000000000..f4209d2e7e
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-RsaPrivateKey.txt
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA/TeoYuuKAmwxp9qK/x4zZfL9RtJpF+nS1qmfxcKJwMizzslX
+ovej95MpUlrFpFdVOMryW3DdvQ2Flk9ZFjk58NaRFFPlwfcMRfxmLxQuMMWswbI4
+vI11xb6YIfqqkDd5+ly8lob/n+EvPhL87Ffjm87nVCB//hzHM+CY51ljK7WDCx7P
+37MtSMjGVGUfIoZyc5Q4UKMOSFLttHt/cl+/gQx07ZDDCH090BU6v1Ugy8+3fsX7
+Gt9Mmy5MxeKdVd85v7zL4ZvhJu8KXfe2+qrpJfJ73Irh4qesNf87ccLrlOFl6FQh
+hDf31LCHdH+ankU8URybIfL7DssmJCgrs/p+9wIDA7//AoIBAErz5C3CfGxXKDON
+eYi8PTl8TF4/gK0HtSrUue6WgxVzoVoWn1kvx6qVwMx2USK+OWf3o45fT1E201qo
+p4T0JoF+9p2IhMMQxaqAPBNru/jdFnJNphO1p70Jj0oIN5KKMLtTY5JJ/CGtevlP
+MdmSdE6aFBkwKsTz1owUAT89MK+dSp+UEwPf5rYC+SE5/7ZNctZ/YLb9ajt3g8/0
+DH/4n2ZQ8y+p25w4OIwFiFaOgBiBSOqtMm/Y8pk0ahxtXwiUoQ/EdSSkIB0fhvTq
+CzsGzfyZCam/dJDbnDtVF4h718t6kO4Pf6lQf9W83XBaF9loLbddjh4LA1/K7j/T
+Y3dNwrcCgYEA/tTYonb3WjIyZNg7v/d6qczIQdFw36Qq1QHXgQZD2LCxSwJmHxUa
++Jcm+xqTyjnLM8LYwD7lGpDIfbcmcC24+UwN5OBL8XUXb0MRiZEk0QGAaAxuTPDJ
+LosSy5C9GmLXFoKYhJoOCb0GJNsQDn3ODHGF0o0FWRzHMBaxbiBHUJUCgYEA/mDq
+sr9wjl4gY+S2ywcSIZHr+dKyNts7O/KuPtDtC4Z+BMSNQf7aG7xXfb/L0ofhRUb4
+ML97f+rPXdMx0ICEd+XSHdj+Boe1Bf0in/93ZoBmsNMelh78DwXRBCNOYuQJG0gn
+X68CgT7M7iVUq+A5aXEFSiN7JGcbEMk6xS2k8lsCgYEA3wXjGFz/dXeNImBsd49m
+kJN5WeMaP7PdBMv1IKvUF4fNllJqWXtrVxpqxdb5qfXwHQr5EAy6OsR4QLTjW9kg
+qNqwl3kRsI/asypBU5/qnZLW+dFl0jx8e3deiGS02mnC32ZyE4Kf2mIPRzTfHeX1
+oj1kYHbKlihDFkT3iipjFyMCgYEAqsS6GCb0JQ+dUH2GVXj85DZA05iJV6Xm2zP+
+xfSdfH/Eqyjibd7sRalLWh9p8ELhfZkOCzkfgp/HEfhtgIHXus4rjNQMPCaRn8DY
+8Wn1vhf/5WAiCQjY6Dgfp7NNHFI5sNB5Xa/tgjDRPn8uDgWxA5MuqRmfy7kNsHfn
+wEW5IoMCgYAz5z8OtwnDGCNWjhZ6pJKRu0v1r+opiXkdwHEKByLrpwOv2zJJi/yf
+RNsMxKaOt9fjonHe7rZ3greWefydw538yspW/L+v93QHRdkbig5XTgTDI1jcHFO8
+/kzwRQ0UKkdyJjWAxLeO11HajOge235fxaWa1/Q8YEZdtE8gPNmBAQ==
+-----END RSA PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-RsaPublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-RsaPublicKey.txt
new file mode 100644
index 0000000000..e24581a7cf
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS256-RsaPublicKey.txt
@@ -0,0 +1,8 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEA/TeoYuuKAmwxp9qK/x4zZfL9RtJpF+nS1qmfxcKJwMizzslXovej
+95MpUlrFpFdVOMryW3DdvQ2Flk9ZFjk58NaRFFPlwfcMRfxmLxQuMMWswbI4vI11
+xb6YIfqqkDd5+ly8lob/n+EvPhL87Ffjm87nVCB//hzHM+CY51ljK7WDCx7P37Mt
+SMjGVGUfIoZyc5Q4UKMOSFLttHt/cl+/gQx07ZDDCH090BU6v1Ugy8+3fsX7Gt9M
+my5MxeKdVd85v7zL4ZvhJu8KXfe2+qrpJfJ73Irh4qesNf87ccLrlOFl6FQhhDf3
+1LCHdH+ankU8URybIfL7DssmJCgrs/p+9wIDA7//
+-----END RSA PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-ComplexToken.txt
new file mode 100644
index 0000000000..a81ed66134
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.k3RqN4SliXC_bJJXOzu7Hmm15Roz2egMurROlA8rh9v0mrbZJlcPzGLD5CRWDPNILMMVHzwErIRpsd9zP4La0gBB1UC3ne1e3F1tjfAL1NloH0axqlKTcOzLsnNFzvK9BKNAzW1yC4XsF7wo_4ZTG3rPqZs6PUhFj4ILwa_Xrzz5CjDRSIAg0YJD-LP1f48z50cpwkRSJJV85DCVvUhF6tEBUzvfPvwTv0lSLTVH5_q43oj_LrcaOwCTw-7wkbtkoOe_hRnB9A5qsOcj1jjJfbPe8Oqcyel31dVxXU3NC2-zGaRrHRmPIJ8Yc9vKfy1zYOYGPghxrUOYK-dGAkgU7va62yYDqVO1gq1x5nEGQ183dRNO9H9en-6yWw42AxxGrqrkVkoHUgF1chm9yQd0A8ik1MJlogouhygW2hueP5aP6lYWsmz6RmakydTNnqO9V2dMmqL4e1zA6kJI2VLtDQkHYalizvS8uxQdyD0slfVd3oqGgm4KkOVZ4e8zklTo
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-Header.json
new file mode 100644
index 0000000000..a6675ce60e
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "RS384",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-PrivateKey.txt
new file mode 100644
index 0000000000..185ba3d39d
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-PrivateKey.txt
@@ -0,0 +1,40 @@
+-----BEGIN PRIVATE KEY-----
+MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCf0OoUfJlwK6yV
+h3dYTfQt/YxewSRFyx+YFdXfTbw9pVdX2kzlktrQKqLmMVipy0juk2eq1S7K3ktj
+GG2bs3Sn1RuxfDlZQ/n/nmCYSOtfpjbyrXFznZrMZiNfbH5CYJwWx9nCM+M0Rw2c
+F8nzGREYTULnEInNX6C++5FBV4QmhYwazprtfo+rCTjMRIgCC88atTd3vE/huQee
+BeBCp5XCKJrDciDysOEXp/S0Ln0u2ZZ3AQhv4siPRjpI37x9kCBCrnoTbGGbjDZE
+prc221VbNqnhmuANULqFI8ode0f+SwxW963v+z/YMk/XfiIEoERewrIEH0A0Lj94
+tfoSUEeU9vqdyqQDUd8w32/s6SCXy9uEnDld014nexAPqWk9cuokJu5OOQ+h6b41
+b/meoLy8x81twdnfCdTK8w4uZEacjIdZMWMwhiD8+QrumF1s+/uYXB2XZJUdoNkG
+z+bJSyVMZxgCfVF/SauDMFr1BkUcPq+9ShInP05VPei60ZuoWF0CAwP/vwKCAYAo
+VjngFJuTC9y61IUY6y2GVCC39wwWgGM4z8KyBvWMSM2Ri6bTZzoT5XuAqne0Xmp/
+FONEqouUZSPDkBKCjgF31tuyusO//RzI5d5xX925S08mgblsDG062VSh2gGRF4A0
+y2ryQLIkrm9n2OC1wtl8dtpodP/g4HLqKE3ZpNbjT9HX0BvR6EndfeCSXWfbEtSF
+9+HRhEExxZYQtIVIa4wXvPpvLY2oVxUtbR/GkRNEMNPm7ft1GLGCmqfzb2eNZjzZ
+Mz+QB1NWU4MgtZ+rHENaebtEjPBe3Vqpb170tzNyWDA2G/vvQQSmj9SlcskQ2mvg
+KnYykbf+i3YyEmPehrfh6AMPrTGybeF9hiaKUmbIXXDu163qm8Ol8juoohws7wpA
+62bK/ubAKya/ONEKTa8vNIbTc2KgD77HSx10d5NhLamexUtxjIymIkff37KeWnq5
+1MwFMjxycDF2RCIPVV9Ud1KQUVlfhnvwQLWak1riwafLyiUNTZnOh9kdxym84k0C
+gcEAzjsmNy0zV3Y9efxtbM9L2oR/PKLU/HILct4obM218yIj7ekDFtqILTBzFg5s
+JXgVN79k+SuxpCgsBOZkGFHVrgtALg1cxEzwintXw3AN95OL/s5HE9gGai85E+Th
+rKrKO68P9rRODwj6T+xNDqQIKTbs/9FiqP6R7ZvZPUfQkeM5MYb1PYRpo0TnYhaq
+EdF+NKAaF6A+Bf1P743RH+ibd6cGdv2ipNtHUPYgBxYHQ6YC6DnTJLPtpcXMJb/C
+r8fvAoHBAMZiRV1lc9u1Krhb06ZXt33LJVCL2c4cUeYcKDaYBT+oSa//g+iX80QU
+sDiDo6ZSiXEReivjWM344pbAxgzp4TOgULzKboOfV8B+gvXdXNuKuCsk7hwwPVe9
+oA6VdfTDjIR2IRgveYBy+R9TXjb/JzdjcABsNmqc2fsjq00APE5HzyeDbW6zYoxx
+f3BENZAVYGRWEnQQK+xZuIxkSYXR5TFbhEWlSTHZ5E4so2ksss5EzCyktf6Gc+zw
+0sTmgR74cwKBwQClLz8tlS1zRb7PXbHVWbkUgsNNFJui6K0acD3Yb2YrpiW3zY/W
+7BJkdIqqIIif3DKdyt4slpbuw5gMMkCrSVVGqHwOUHla4A4eFcZM1UklG0hnusVr
+Nrx6ecdMahOHWNnVh1DcJGLKFPkAEr70Ho5+hmTnkJ8SYsbheCqh5FrNuhZyKux2
+jaDx5k20TU6HgU6oQ7u5gkZ3lmvRMKQim57xMvXsIH8P2l4T2F1pAojp11WCoaqs
+GyZ//sgSFJhlnYkCgcEAmx4P9izsGpLvXAWdjvdCaayHm7GotnhP94bmjonDzwgj
+eHUQbVfi1dUH47m58hSAbjpC9blmAuJ2jpN33dmTotCuhBxYDvp9iPlHFdGggi2U
+aqO4B5b/9YsZR+lN35pjb6bHJnJjS0LHM44a+g+1yPH8XJV3yZMu7LBQ/EoMF4zY
+g9d3M3t+RuHDzbIcdjN6mY9Y78ARKu2/XyK6Po6Dp7/6Vm5Yd/X7uYejTJe12pG+
+5F7zrFIOKbUJ3JwaBZrdAoHAScZAyGm1LYZ38pa+xI+TWVpB0QRxGOs2Ys65BI6H
+8lgmQlE7g8bTN/jjfBxWVt8S9mz8DEugNAUeLBaewjqCanxCnIWMXRD2bRpq+rI3
+FjXAoD5KhuR8WnE70AMFgPbwt3FoWddnyVEs+MlMfMBYKN5Dcvpd47LKc+14iqf9
+clCsidia28Nb3LOuf51zQ6AHS+Mg1UUnv+vT9rE5mPq3o2o464Q8Y14PhtFPFIQ+
+tfgPajnf3fvYR96hkE5q9nT4
+-----END PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-PublicKey.txt
new file mode 100644
index 0000000000..05d85c891d
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS384-PublicKey.txt
@@ -0,0 +1,11 @@
+-----BEGIN PUBLIC KEY-----
+MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAn9DqFHyZcCuslYd3WE30
+Lf2MXsEkRcsfmBXV3028PaVXV9pM5ZLa0Cqi5jFYqctI7pNnqtUuyt5LYxhtm7N0
+p9UbsXw5WUP5/55gmEjrX6Y28q1xc52azGYjX2x+QmCcFsfZwjPjNEcNnBfJ8xkR
+GE1C5xCJzV+gvvuRQVeEJoWMGs6a7X6Pqwk4zESIAgvPGrU3d7xP4bkHngXgQqeV
+wiiaw3Ig8rDhF6f0tC59LtmWdwEIb+LIj0Y6SN+8fZAgQq56E2xhm4w2RKa3NttV
+Wzap4ZrgDVC6hSPKHXtH/ksMVvet7/s/2DJP134iBKBEXsKyBB9ANC4/eLX6ElBH
+lPb6ncqkA1HfMN9v7Okgl8vbhJw5XdNeJ3sQD6lpPXLqJCbuTjkPoem+NW/5nqC8
+vMfNbcHZ3wnUyvMOLmRGnIyHWTFjMIYg/PkK7phdbPv7mFwdl2SVHaDZBs/myUsl
+TGcYAn1Rf0mrgzBa9QZFHD6vvUoSJz9OVT3outGbqFhdAgMD/78=
+-----END PUBLIC KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-ComplexToken.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-ComplexToken.txt
new file mode 100644
index 0000000000..a46edb1b70
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-ComplexToken.txt
@@ -0,0 +1 @@
+eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHJ1ZSI6dHJ1ZSwiZmFsc2UiOmZhbHNlLCJsb25nIjoxMjM0NTY3ODkwLCJkZWNpbWFsIjoxMjM0NTY3ODkwLjEyMywib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn0sIm9iamVjdE9mT2JqZWN0Ijp7Im9iamVjdCI6eyJPYmplY3RQcm9wZXJ0eSI6Im9iamVjdCB2YWx1ZSJ9fSwiYXJyYXkiOlsiYXJyYXkgdmFsdWUiXSwiYXJyYXlPZk9iamVjdCI6W3sib2JqZWN0Ijp7Ik9iamVjdFByb3BlcnR5Ijoib2JqZWN0IHZhbHVlIn19XSwibmJmIjoxNjYxMDkyMzcwLCJleHAiOjE2NjEwOTU5NzAsImlhdCI6MTY2MTA5MjM3MCwiaXNzIjoiZGV2dG95cyIsImF1ZCI6ImRldnRveXMifQ.DD4zAoPOezipuM32HC25ZpypTmcrwKsNohOzLGfZcdidrU2Rk7KVldhB899iacBHSZ2kh9PqSzJjTB3q00EmsOC3JS-tT9356Fm1vm_ZaeqJU1I8NLPCmvK6xXO4edND7pnv02MBvzecYl7s_l5sYOy-s8V8zk46gx6_SJEpV5Gf5hELd_c1oXNCH8uxoNZEY_UaYDl9OhvyrYcXbffB9Mv4PDKirHG5HTe27ZK99z6LYyCOb6eOnMJmRpbb1i5bGUaedDQEo_bZuywTb3La04-gQU859QnJbgGcI4XNaZqdwZ5D0Db74c7sbPXRTc6NrIc3XCTcu35JKx-gWpKzZtLmOFIuTS-azUZW_qIx6wA1Vg4UKWNbzUQE506K6p8Z3AtCOVeMmBUn4Q_U1FvEBz9IoLpKEJJjSb7kHxlrLqRcLUXXLOgT27WBXOO6jZGQaGhLwMgT_mNXnvd0RV43oPHoU1QhUO8qnCWcSAEZBH_ohpN9bnuzBx0_CDDT4N6OuRJYIQf6v9v3psk_a9gg1xZmHj3kPlDYg-3PxxhxH3jvY9X6AT3cq5aaCgK54lbhtm5yA3vd3UdCgjctPzW-MbyE8Al-yjo9NlWQisHJ65mT4Is0NuTOI0Ds_cGN8WOxg4Thfl1aWN2dv3dOCxwTf862tIspswIKfyEv5hmaBgQ
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-Header.json b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-Header.json
new file mode 100644
index 0000000000..7ac2a08ac3
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-Header.json
@@ -0,0 +1,4 @@
+{
+ "alg": "RS512",
+ "typ": "JWT"
+}
\ No newline at end of file
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-PrivateKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-PrivateKey.txt
new file mode 100644
index 0000000000..976a1d85ca
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-PrivateKey.txt
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCgByTfKpM6bomt
+ggnJ7Hh1qh20rs/lG/98DLQXIOHVNCWm7adkunNxl6Ak0oU9ucwBuFy88BdbiFqC
+n+z5KjemPm3yRsl1MLmk0G/ECxx+GFsWdqvPQnGgBxZ3O2DJCfpFK3lQ7ExXvdzP
+IR1ZHfYcBgS46xAApTWGP7RHaR8RWiqH5snE+tNV4lzG1yw3cMRLWtTzmwGr0AHg
+uNykJvnT+ZvJAWHQyTjvSE4yWcHvL1tmNZSyPYXBmpKkBWN2bO01rRtnKuqcjYK/
+AD5UJbjGh/kxa8yxZNgmLqlf2YqgELckSVPA58Z1bI6ig2FZUD5bEfXx6V1Jlyku
+66mK1vFHLtEteD9hkRRuaH8YqBO/ySmc4CkemXvfC20NeK8+l9KHzNSaJaAxI20a
+UZbUXsRJT6Nlw+FDrNTohHCRj6KJjJuztGLPoLlNiRG5C16vL0iNZ0s9FiDSwtVY
+D4W4h0IFFpTWcS9UxuOiUU7iZc05xGc266+exO3JaWgUsBw4w9pnKwVyJqdecBuW
+xgXNQoT2NCI5ve5CJj6Bbof5kaFYEzQI1yxlLzHnAH87ByHDYwAPUyjRGtTl/gP5
+jQ/wADZKJS6vYhC8MLGSE5xEeTT5vxJVLiXgEn8BqEv9bQVnOXBfxKXaO12Gufba
+rMpvlb6h3rDvgvhfXGWhk/ei0CBPvQIDA9//AoICAEzstIavtIX5cPJCyj4THta2
+bCq5pIGDjcB1CdPK/Ij9u+xlPbN26EzljXfwi+fPJZS3LxPllmkulrlW6ztgPddc
+WHEJ6r4WHEawqAxdY885zTj5yQoC6GoBnFT+ZsiUlHx5RzL88d4kdM/v/U9fXs67
+zpxLdwusEmIUjLgJeURn53ZcVK9i2QUUDg4LBEwFaS92LkzPvogpRe2aGfMv40pF
+LxnNLN09KLBYP335kK9nWe7vRuzVyC100yIXRhbpSurh4W/7yjetbiAacl/u7Uiy
+KqhVKJilzKc6yC8dqFQS3JYVVrOd+x7Za6IxdtscEZEZVM6cEkqTSV0ENjLcAwMU
+x3LJ9Qh5ihwOrtraUAW213K/vsXPl3jOyj7M0CuT4mqXofxwwAbr/LhA64X27ffi
+BE72iSpUF/LGEQ1AIJeGqBOIeSjB6MWuKdGMUT2r3BRZLhNbUjkCfzlS8MU1e5bd
+TbueipovbqA3vmA/n0ULWJF5knrPaiF6aftZMh2qFiASsygp7HzJZduGM27wSH+c
+bUmWVAjzOquRbBkmp1b9i2F5lDg566pEyySjKRWEZdhh/aLX/7tx8v8X1C+bm2fs
+ANFL1ZOxUHe/P+3fbbbJOp9RU+6/Ous7p4gEfUZSBJh6hanZiylDu7J4WW1V3s4b
+R69fNs+DAi/gBHeVtVSDAoIBAQDUROdTxTyPpsf8Xl2GS2xwHQQIcK7loF8RZf1Z
+dldmTRjUJzT7JzRwMKJbjVWlk6QtHWZmTcdYvQotRfnEleET5wfVVpE4Vd/l1frd
+5zMpCyAJ69pisOgN5//1nfQLUZ9mknjE7nNCSnW8kwg/mEQFXtZ+wzM6EUiFmQcO
+67WvzYdu6ZkKJY6woUuSysQuggf9kYjmtyhalsGkhwo2tyBTSUYBBK6AANmnFuEz
+rbeKMMmfNOxLSyWXSAR0J+H/DAEmth6YWXD+bv99Y2fs29IYNHo+QXGQ44uRZOGg
+ax8SgbWY/ZYhqL3a0Z6wBVxjsOSxnAqjYuZVNU7Wq0JJbTY3AoIBAQDA/wgheyBR
+laOwtvkg5Aj1Ui8ArQdPvTu8uAuB6hqvn2wuCBvFhU61a628QtANe5vn44Sdl+Lv
+KR/NdZQdKVpLkF2/kO/rPBakpe0NY4xJvCnpknV1wJsdzsAPh5IW9bgsTuPPlAXf
+e098jIfOWUpVaE9iIA+G9bSxuBDQ3Yv9t9LFmh+UbF4HQfRXgkdsecxZAzQuCZQB
+ZikzT67uPGm1bvGCocGThKwjWlGbcmKJg3wbOLzOOIpYNqfl3ejeCh2G73cTzUhy
+KSRBMWJfjSnUGbRKX0JxFCwTZ6dt08hKV0TKLs3y5Os0+cUh7aYrprCRiSgwFEMM
+hICHJNTGDy+rAoIBAQCITIWLT9b8bWcD9SYG+UkR67KQZuDwBvIM4FBwAPhXFZHY
+q1dB6g8674/MTLZO5BfMR1ITbWxQ1Mt8aQklhsFfD+Go5AQt9WNe0PaWA1/KPvmy
+df3TUz0JuYEnJANQJ3zh1d4+XAoBiD7vkdlFXivwz6xwZqGMujJ4iSySmCUBV820
+6MpzyGZTlYoN+OLWosayLem7ZxAYydDbtz/XPEfNQqHGHgkSr6laAadfT291YMYG
+7O75zPwM7WbVO1ob4UsuFaxWoOm2+pJF1HFFt/rHLhDn6rmZnVe3SClUv0mgEDAY
+Yj9+YVasbMsTJkB6Vr5/InVzQslE1BulSd4/SenlAoIBAQCoP5g86eTOavnxmO5S
+ctghhjOyhHCeizqit1aUXh2yEMbcn6wSqjkJRcIRhidm39AE643ps3uf5bHD9Utg
+Ctr9B0NUrkv1R9EfWC0nUDE4E0m9HgAtDAZFELGar1jWHHnyVom+Y9n1W3GnLmA/
+nHJl18knt8ksR9uaTuc7Iz7IqJ0av/2PeUoAFszqCc5MAzIiO7/4IT05X8ZOWo+5
+X576yFTdmzHbPllluFMJNHovq5xVZH4XR1b5fL7VmIfeVz74yp+hQl1Ncj2HvvEC
+sVfy7eg0WwOlw/kqbgj0P/Nl2giSkUE6aKD4Pao5rPQdsFZljqJPbJk739VZQT5X
+VVQxAoIBAQCKfwgzmMsfs34i2QsBpzE5A1pPFbPLv5I8l8g2CzjGYFezlRqiT00c
+HN34w3xK+KuhAk/g0iiJbuSKSdltnczP/H8l1h0Dav9QxXT6QXk9DbKqyEShQp6T
+QUKOW9kPnKBch2LZtww++c+naLlSooY6O/CZiH6I1dw4lTnxxDF2JEl6pYIEjBTd
+/qQjqJCcurl6zra1uW2tvP+xnLjFrUGcVu4g0cNcotulRS0qGgOHNjJAZt3yQncc
+eUyP5kRdRqTzeC+TqOms5+jys9ErUgmRtZw9VXDhXEPi+1aCCYR4W2KtXfQivxdr
+MVStA4H4qqjilXSm4ep6NltkqJuxtdZ0
+-----END PRIVATE KEY-----
diff --git a/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-PublicKey.txt b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-PublicKey.txt
new file mode 100644
index 0000000000..757d99e174
--- /dev/null
+++ b/src/app/tests/DevToys.UnitTests/Tools/TestData/JsonWebTokenEncoderDecoder/RS/RS512-PublicKey.txt
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoAck3yqTOm6JrYIJyex4
+daodtK7P5Rv/fAy0FyDh1TQlpu2nZLpzcZegJNKFPbnMAbhcvPAXW4hagp/s+So3
+pj5t8kbJdTC5pNBvxAscfhhbFnarz0JxoAcWdztgyQn6RSt5UOxMV73czyEdWR32
+HAYEuOsQAKU1hj+0R2kfEVoqh+bJxPrTVeJcxtcsN3DES1rU85sBq9AB4LjcpCb5
+0/mbyQFh0Mk470hOMlnB7y9bZjWUsj2FwZqSpAVjdmztNa0bZyrqnI2CvwA+VCW4
+xof5MWvMsWTYJi6pX9mKoBC3JElTwOfGdWyOooNhWVA+WxH18eldSZcpLuupitbx
+Ry7RLXg/YZEUbmh/GKgTv8kpnOApHpl73wttDXivPpfSh8zUmiWgMSNtGlGW1F7E
+SU+jZcPhQ6zU6IRwkY+iiYybs7Riz6C5TYkRuQtery9IjWdLPRYg0sLVWA+FuIdC
+BRaU1nEvVMbjolFO4mXNOcRnNuuvnsTtyWloFLAcOMPaZysFcianXnAblsYFzUKE
+9jQiOb3uQiY+gW6H+ZGhWBM0CNcsZS8x5wB/Owchw2MAD1Mo0RrU5f4D+Y0P8AA2
+SiUur2IQvDCxkhOcRHk0+b8SVS4l4BJ/AahL/W0FZzlwX8Sl2jtdhrn22qzKb5W+
+od6w74L4X1xloZP3otAgT70CAwPf/w==
+-----END PUBLIC KEY-----