diff --git a/Iceshrimp.Backend/Controllers/Mastodon/MastodonAuthController.cs b/Iceshrimp.Backend/Controllers/Mastodon/MastodonAuthController.cs index db17e27d..a5ece7ab 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/MastodonAuthController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/MastodonAuthController.cs @@ -6,6 +6,7 @@ using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Middleware; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; +using Microsoft.EntityFrameworkCore; namespace Iceshrimp.Backend.Controllers.Mastodon; @@ -13,9 +14,8 @@ namespace Iceshrimp.Backend.Controllers.Mastodon; [Tags("Mastodon")] [EnableRateLimiting("sliding")] [Produces("application/json")] -[Route("/api/v1")] public class MastodonAuthController(DatabaseContext db) : Controller { - [HttpGet("verify_credentials")] + [HttpGet("/api/v1/apps/verify_credentials")] [AuthenticateOauth] [Produces("application/json")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MastodonAuth.VerifyCredentialsResponse))] @@ -32,7 +32,7 @@ public class MastodonAuthController(DatabaseContext db) : Controller { return Ok(res); } - [HttpPost("apps")] + [HttpPost("/api/v1/apps")] [EnableRateLimiting("strict")] [ConsumesHybrid] [Produces("application/json")] @@ -78,4 +78,53 @@ public class MastodonAuthController(DatabaseContext db) : Controller { return Ok(res); } + + + [HttpPost("/oauth/token")] + [ConsumesHybrid] + [Produces("application/json")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MastodonAuth.OauthTokenResponse))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(MastodonErrorResponse))] + public async Task GetOauthToken([FromHybrid] MastodonAuth.OauthTokenRequest request) { + //TODO: app-level access (grant_type = "client_credentials") + if (request.GrantType != "code") + throw GracefulException.BadRequest("Invalid grant_type"); + var token = await db.OauthTokens.FirstOrDefaultAsync(p => p.Code == request.Code && + p.App.ClientId == request.ClientId && + p.App.ClientSecret == request.ClientSecret); + if (token == null) + throw GracefulException + .Unauthorized("Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."); + + if (token.Active) + throw GracefulException + .BadRequest("The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."); + + if (MastodonOauthHelpers.ExpandScopes(request.Scopes) + .Except(MastodonOauthHelpers.ExpandScopes(token.Scopes)).Any()) + throw GracefulException.BadRequest("The requested scope is invalid, unknown, or malformed."); + + token.Scopes = request.Scopes; + token.Active = true; + await db.SaveChangesAsync(); + + var res = new MastodonAuth.OauthTokenResponse { + CreatedAt = token.CreatedAt, + Scopes = token.Scopes, + AccessToken = token.Token + }; + + return Ok(res); + } + + /* +[HttpPost("/oauth/revoke")] +[ConsumesHybrid] +[Produces("application/json")] +//[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MastodonAuth.RegisterAppResponse))] +[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(MastodonErrorResponse))] +[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))] +public async Task RegisterApp([FromHybrid] ) { } + +*/ } \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/MastodonAuth.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/MastodonAuth.cs index 96245164..085c1301 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/MastodonAuth.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/MastodonAuth.cs @@ -24,11 +24,15 @@ public abstract class MastodonAuth { [B(Name = "scopes")] [J("scopes")] [JC(typeof(EnsureArrayConverter))] - public List Scopes { + public List? Scopes { get => _scopes; - set => _scopes = value.Count == 1 - ? value[0].Split(' ').ToList() - : value; + set => _scopes = value == null + ? ["read"] + : value.Count == 1 + ? value.Contains(" ") + ? value[0].Split(' ').ToList() + : value[0].Split(',').ToList() + : value; } [B(Name = "client_name")] @@ -59,4 +63,55 @@ public abstract class MastodonAuth { [J("vapid_key")] public required string? VapidKey { get; set; } } + + public class OauthTokenRequest { + public List Scopes = ["read"]; + + [B(Name = "scope")] + [J("scope")] + [JC(typeof(EnsureArrayConverter))] + public List? ScopesInternal { + get => Scopes; + set => Scopes = value == null + ? ["read"] + : value.Count == 1 + ? value.Contains(" ") + ? value[0].Split(' ').ToList() + : value[0].Split(',').ToList() + : value; + } + + [B(Name = "redirect_uri")] + [J("redirect_uri")] + [JR] + public string RedirectUri { get; set; } = null!; + + [B(Name = "grant_type")] + [J("grant_type")] + [JR] + public string GrantType { get; set; } = null!; + + [B(Name = "client_id")] + [J("client_id")] + [JR] + public string ClientId { get; set; } = null!; + + [B(Name = "client_secret")] + [J("client_secret")] + [JR] + public string ClientSecret { get; set; } = null!; + + [B(Name = "code")] [J("code")] public string? Code { get; set; } = null!; + } + + public class OauthTokenResponse { + public required DateTime CreatedAt; + + public required List Scopes; + [J("access_token")] public required string AccessToken { get; set; } + + [J("token_type")] public string TokenType => "Bearer"; + [J("scope")] public string Scope => string.Join(' ', Scopes); + [J("created_at")] public long CreatedAtInternal => (long)(CreatedAt - DateTime.UnixEpoch).TotalSeconds; + } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs index 340c4297..274a6afb 100644 --- a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs @@ -32,6 +32,7 @@ public static class ServiceExtensions { services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Singleton = instantiated once across application lifetime services.AddSingleton(); @@ -41,6 +42,7 @@ public static class ServiceExtensions { services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Hosted services = long running background tasks // Note: These need to be added as a singleton as well to ensure data consistency diff --git a/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs b/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs index 75b8fe93..5c801a23 100644 --- a/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs @@ -13,6 +13,8 @@ public static class WebApplicationExtensions { .UseMiddleware() .UseMiddleware() .UseMiddleware() + .UseMiddleware() + .UseMiddleware() .UseMiddleware(); } diff --git a/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml b/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml new file mode 100644 index 00000000..0aa5d85b --- /dev/null +++ b/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml @@ -0,0 +1,45 @@ +@page "/oauth/authorize" +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using Microsoft.AspNetCore.WebUtilities +@model AuthorizeModel + +

Iceshrimp.NET OAuth

+ +@if (Model.Token == null) { +
+ The app @Model.App.Name requests the following permissions: + +
    + @foreach (var scope in Model.Scopes) { +
  • @scope
  • + } +
+ + Log in below to confirm this: + +
+
+ + + +
+
+
+} +else if (Model.Token.RedirectUri == "urn:ietf:wg:oauth:2.0:oob") { +
+ Your code is:
@Model.Token.Code
+
+} +else { + var uri = new Uri(Model.Token.RedirectUri); + var query = QueryHelpers.ParseQuery(uri.Query); + query.Add("code", Model.Token.Code); + if (Request.Query.ContainsKey("state")) + query.Add("state", Request.Query["state"]); + uri = new Uri(QueryHelpers.AddQueryString(Model.Token.RedirectUri, query)); + Response.Redirect(uri.ToString()); +
+ Click here to be redirected back to your application +
+} \ No newline at end of file diff --git a/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml.cs b/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml.cs new file mode 100644 index 00000000..7cba29b8 --- /dev/null +++ b/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml.cs @@ -0,0 +1,71 @@ +using System.Diagnostics.CodeAnalysis; +using Iceshrimp.Backend.Core.Database; +using Iceshrimp.Backend.Core.Database.Tables; +using Iceshrimp.Backend.Core.Helpers; +using Iceshrimp.Backend.Core.Middleware; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.EntityFrameworkCore; + +namespace Iceshrimp.Backend.Pages.OAuth; + +public class AuthorizeModel(DatabaseContext db) : PageModel { + [FromQuery(Name = "response_type")] public required string ResponseType { get; set; } + [FromQuery(Name = "client_id")] public required string ClientId { get; set; } + [FromQuery(Name = "redirect_uri")] public required string RedirectUri { get; set; } + [FromQuery(Name = "force_login")] public bool ForceLogin { get; set; } = false; + [FromQuery(Name = "lang")] public string? Language { get; set; } + + [FromQuery(Name = "scope")] + public string Scope { + get => string.Join(' ', Scopes); + [MemberNotNull(nameof(Scopes))] set => Scopes = value.Split(' ').ToList(); + } + + public List Scopes = []; + public OauthApp App = null!; + public OauthToken? Token = null; + + public async Task OnGet() { + App = await db.OauthApps.FirstOrDefaultAsync(p => p.ClientId == ClientId) + ?? throw GracefulException.BadRequest("Invalid client_id"); + if (MastodonOauthHelpers.ExpandScopes(Scopes).Except(MastodonOauthHelpers.ExpandScopes(App.Scopes)).Any()) + throw GracefulException.BadRequest("Cannot request more scopes than app"); + if (ResponseType != "code") + throw GracefulException.BadRequest("Invalid response_type"); + if (!App.RedirectUris.Contains(RedirectUri)) + throw GracefulException.BadRequest("Cannot request redirect_uri not sent during app registration"); + } + + public async Task OnPost([FromForm] string username, [FromForm] string password) { + // Validate query parameters first + await OnGet(); + + var user = await db.Users.FirstOrDefaultAsync(p => p.Host == null && + p.UsernameLower == username.ToLowerInvariant()); + if (user == null) + throw GracefulException.Forbidden("Invalid username or password"); + var userProfile = await db.UserProfiles.FirstOrDefaultAsync(p => p.User == user); + if (userProfile?.Password == null) + throw GracefulException.Forbidden("Invalid username or password"); + if (AuthHelpers.ComparePassword(password, userProfile.Password) == false) + throw GracefulException.Forbidden("Invalid username or password"); + + var token = new OauthToken { + Id = IdHelpers.GenerateSlowflakeId(), + Active = false, + Code = CryptographyHelpers.GenerateRandomString(32), + Token = CryptographyHelpers.GenerateRandomString(32), + App = App, + User = user, + CreatedAt = DateTime.UtcNow, + Scopes = Scopes, + RedirectUri = RedirectUri + }; + + await db.AddAsync(token); + await db.SaveChangesAsync(); + + Token = token; + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml.css b/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml.css new file mode 100644 index 00000000..80555239 --- /dev/null +++ b/Iceshrimp.Backend/Pages/OAuth/Authorize.cshtml.css @@ -0,0 +1,7 @@ +.app_name { + color: #9a92ff; +} + +.form-wrapper { + padding-top: 5px; +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Pages/Shared/FrontendSPA.cshtml b/Iceshrimp.Backend/Pages/Shared/FrontendSPA.cshtml index 4246ca1f..b4620271 100644 --- a/Iceshrimp.Backend/Pages/Shared/FrontendSPA.cshtml +++ b/Iceshrimp.Backend/Pages/Shared/FrontendSPA.cshtml @@ -2,6 +2,10 @@ @addTagHelper *, Vite.AspNetCore @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@{ + Layout = null; +} + diff --git a/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml b/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml index ab874f60..e354788a 100644 --- a/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml +++ b/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml @@ -3,9 +3,11 @@ Iceshrimp + @* ReSharper disable once Html.PathError *@ + + -
@RenderBody() \ No newline at end of file diff --git a/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml.css b/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml.css index 947d5b5d..5f282702 100644 --- a/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml.css +++ b/Iceshrimp.Backend/Pages/Shared/_Layout.cshtml.css @@ -1,49 +1 @@ -/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification -for details on configuring this project to bundle and minify static web assets. */ - -a.navbar-brand { - white-space: normal; - text-align: center; - word-break: break-all; -} - -a { - color: #0077cc; -} - -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} - -.nav-pills .nav-link.active, .nav-pills .show > .nav-link { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} - -.border-top { - border-top: 1px solid #e5e5e5; -} - -.border-bottom { - border-bottom: 1px solid #e5e5e5; -} - -.box-shadow { - box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); -} - -button.accept-policy { - font-size: 1rem; - line-height: inherit; -} - -.footer { - position: absolute; - bottom: 0; - width: 100%; - white-space: nowrap; - line-height: 60px; -} + \ No newline at end of file diff --git a/Iceshrimp.Backend/wwwroot/css/default.css b/Iceshrimp.Backend/wwwroot/css/default.css new file mode 100644 index 00000000..4d60e43b --- /dev/null +++ b/Iceshrimp.Backend/wwwroot/css/default.css @@ -0,0 +1,1675 @@ +/** + * From https://watercss.kognise.dev/ + */ + +:root { + --background-body: #fff; + --background: #efefef; + --background-alt: #f7f7f7; + --selection: #9e9e9e; + --text-main: #363636; + --text-bright: #000; + --text-muted: #70777f; + --links: #0076d1; + --focus: #0096bfab; + --border: #dbdbdb; + --code: #000; + --animation-duration: 0.1s; + --button-base: #d0cfcf; + --button-hover: #9b9b9b; + --scrollbar-thumb: rgb(170, 170, 170); + --scrollbar-thumb-hover: var(--button-hover); + --form-placeholder: #949494; + --form-text: #1d1d1d; + --variable: #39a33c; + --highlight: #ff0; + --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); +} + +@media (prefers-color-scheme: dark) { + :root { + --background-body: #202b38; + --background: #161f27; + --background-alt: #1a242f; + --selection: #1c76c5; + --text-main: #dbdbdb; + --text-bright: #fff; + --text-muted: #a9b1ba; + --links: #41adff; + --focus: #0096bfab; + --border: #526980; + --code: #ffbe85; + --animation-duration: 0.1s; + --button-base: #0c151c; + --button-hover: #040a0f; + --scrollbar-thumb: var(--button-hover); + --scrollbar-thumb-hover: rgb(0, 0, 0); + --form-placeholder: #a9a9a9; + --form-text: #fff; + --variable: #d941e2; + --highlight: #efdb43; + --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); + } +} + +html { + scrollbar-color: rgb(170, 170, 170) #fff; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + scrollbar-width: thin; +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif; + line-height: 1.4; + max-width: 800px; + margin: 20px auto; + padding: 0 10px; + word-wrap: break-word; + color: #363636; + color: var(--text-main); + background: #fff; + background: var(--background-body); + text-rendering: optimizeLegibility; +} + +@media (prefers-color-scheme: dark) { + + body { + background: #202b38; + background: var(--background-body); + } +} + +@media (prefers-color-scheme: dark) { + + body { + color: #dbdbdb; + color: var(--text-main); + } +} + +button { + transition: background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +@media (prefers-color-scheme: dark) { + + button { + transition: background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } +} + +input { + transition: background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +@media (prefers-color-scheme: dark) { + + input { + transition: background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } +} + +textarea { + transition: background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +@media (prefers-color-scheme: dark) { + + textarea { + transition: background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } +} + +h1 { + font-size: 2.2em; + margin-top: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 12px; + margin-top: 24px; +} + +h1 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h1 { + color: #fff; + color: var(--text-bright); + } +} + +h2 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h2 { + color: #fff; + color: var(--text-bright); + } +} + +h3 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h3 { + color: #fff; + color: var(--text-bright); + } +} + +h4 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h4 { + color: #fff; + color: var(--text-bright); + } +} + +h5 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h5 { + color: #fff; + color: var(--text-bright); + } +} + +h6 { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + h6 { + color: #fff; + color: var(--text-bright); + } +} + +strong { + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + strong { + color: #fff; + color: var(--text-bright); + } +} + +h1, +h2, +h3, +h4, +h5, +h6, +b, +strong, +th { + font-weight: 600; +} + +q::before { + content: none; +} + +q::after { + content: none; +} + +blockquote { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; +} + +@media (prefers-color-scheme: dark) { + + blockquote { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + } +} + +q { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; +} + +@media (prefers-color-scheme: dark) { + + q { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + } +} + +blockquote > footer { + font-style: normal; + border: 0; +} + +blockquote cite { + font-style: normal; +} + +address { + font-style: normal; +} + +a[href^='mailto\:']::before { + content: '📧 '; +} + +a[href^='tel\:']::before { + content: '📞 '; +} + +a[href^='sms\:']::before { + content: '💬 '; +} + +mark { + background-color: #ff0; + background-color: var(--highlight); + border-radius: 2px; + padding: 0 2px 0 2px; + color: #000; +} + +@media (prefers-color-scheme: dark) { + + mark { + background-color: #efdb43; + background-color: var(--highlight); + } +} + +a > code, +a > strong { + color: inherit; +} + +button, +select, +input[type='submit'], +input[type='reset'], +input[type='button'], +input[type='checkbox'], +input[type='range'], +input[type='radio'] { + cursor: pointer; +} + +input, +select { + display: block; +} + +[type='checkbox'], +[type='radio'] { + display: initial; +} + +input { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + input { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + input { + color: #fff; + color: var(--form-text); + } +} + +button { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + button { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + button { + color: #fff; + color: var(--form-text); + } +} + +textarea { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + textarea { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + textarea { + color: #fff; + color: var(--form-text); + } +} + +select { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + select { + background-color: #161f27; + background-color: var(--background); + } +} + +@media (prefers-color-scheme: dark) { + + select { + color: #fff; + color: var(--form-text); + } +} + +button { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + button { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +input[type='submit'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + input[type='submit'] { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +input[type='reset'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + input[type='reset'] { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +input[type='button'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +@media (prefers-color-scheme: dark) { + + input[type='button'] { + background-color: #0c151c; + background-color: var(--button-base); + } +} + +button:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + button:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='submit']:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + input[type='submit']:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='reset']:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + input[type='reset']:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='button']:hover { + background: #9b9b9b; + background: var(--button-hover); +} + +@media (prefers-color-scheme: dark) { + + input[type='button']:hover { + background: #040a0f; + background: var(--button-hover); + } +} + +input[type='color'] { + min-height: 2rem; + padding: 8px; + cursor: pointer; +} + +input[type='checkbox'], +input[type='radio'] { + height: 1em; + width: 1em; +} + +input[type='radio'] { + border-radius: 100%; +} + +input { + vertical-align: top; +} + +label { + vertical-align: middle; + margin-bottom: 4px; + display: inline-block; +} + +input:not([type='checkbox']):not([type='radio']), +input[type='range'], +select, +button, +textarea { + -webkit-appearance: none; +} + +textarea { + display: block; + margin-right: 0; + box-sizing: border-box; + resize: vertical; +} + +textarea:not([cols]) { + width: 100%; +} + +textarea:not([rows]) { + min-height: 40px; + height: 140px; +} + +select { + background: #efefef url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + padding-right: 35px; +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +@media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } +} + +select::-ms-expand { + display: none; +} + +select[multiple] { + padding-right: 10px; + background-image: none; + overflow-y: auto; +} + +input:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + input:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +select:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + select:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +button:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + button:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +textarea:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +@media (prefers-color-scheme: dark) { + + textarea:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } +} + +input[type='checkbox']:active, +input[type='radio']:active, +input[type='submit']:active, +input[type='reset']:active, +input[type='button']:active, +input[type='range']:active, +button:active { + transform: translateY(2px); +} + +input:disabled, +select:disabled, +button:disabled, +textarea:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +::-moz-placeholder { + color: #949494; + color: var(--form-placeholder); +} + +:-ms-input-placeholder { + color: #949494; + color: var(--form-placeholder); +} + +::-ms-input-placeholder { + color: #949494; + color: var(--form-placeholder); +} + +::placeholder { + color: #949494; + color: var(--form-placeholder); +} + +@media (prefers-color-scheme: dark) { + + ::-moz-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + :-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + ::-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + ::placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } +} + +fieldset { + border: 1px #0096bfab solid; + border: 1px var(--focus) solid; + border-radius: 6px; + margin: 0; + margin-bottom: 12px; + padding: 10px; +} + +@media (prefers-color-scheme: dark) { + + fieldset { + border: 1px #0096bfab solid; + border: 1px var(--focus) solid; + } +} + +legend { + font-size: 0.9em; + font-weight: 600; +} + +input[type='range'] { + margin: 10px 0; + padding: 10px 0; + background: transparent; +} + +input[type='range']:focus { + outline: none; +} + +input[type='range']::-webkit-slider-runnable-track { + width: 100%; + height: 9.5px; + -webkit-transition: 0.2s; + transition: 0.2s; + background: #efefef; + background: var(--background); + border-radius: 3px; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-webkit-slider-runnable-track { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-webkit-slider-thumb { + box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); + -webkit-appearance: none; + margin-top: -7px; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-webkit-slider-thumb { + background: #526980; + background: var(--border); + } +} + +input[type='range']:focus::-webkit-slider-runnable-track { + background: #efefef; + background: var(--background); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']:focus::-webkit-slider-runnable-track { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-moz-range-track { + width: 100%; + height: 9.5px; + -moz-transition: 0.2s; + transition: 0.2s; + background: #efefef; + background: var(--background); + border-radius: 3px; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-moz-range-track { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-moz-range-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-moz-range-thumb { + background: #526980; + background: var(--border); + } +} + +input[type='range']::-ms-track { + width: 100%; + height: 9.5px; + background: transparent; + border-color: transparent; + border-width: 16px 0; + color: transparent; +} + +input[type='range']::-ms-fill-lower { + background: #efefef; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-ms-fill-lower { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-ms-fill-upper { + background: #efefef; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-ms-fill-upper { + background: #161f27; + background: var(--background); + } +} + +input[type='range']::-ms-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + border: 1px solid #000; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']::-ms-thumb { + background: #526980; + background: var(--border); + } +} + +input[type='range']:focus::-ms-fill-lower { + background: #efefef; + background: var(--background); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']:focus::-ms-fill-lower { + background: #161f27; + background: var(--background); + } +} + +input[type='range']:focus::-ms-fill-upper { + background: #efefef; + background: var(--background); +} + +@media (prefers-color-scheme: dark) { + + input[type='range']:focus::-ms-fill-upper { + background: #161f27; + background: var(--background); + } +} + +a { + text-decoration: none; + color: #0076d1; + color: var(--links); +} + +@media (prefers-color-scheme: dark) { + + a { + color: #41adff; + color: var(--links); + } +} + +a:hover { + text-decoration: underline; +} + +code { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +@media (prefers-color-scheme: dark) { + + code { + color: #ffbe85; + color: var(--code); + } +} + +@media (prefers-color-scheme: dark) { + + code { + background: #161f27; + background: var(--background); + } +} + +samp { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +@media (prefers-color-scheme: dark) { + + samp { + color: #ffbe85; + color: var(--code); + } +} + +@media (prefers-color-scheme: dark) { + + samp { + background: #161f27; + background: var(--background); + } +} + +time { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +@media (prefers-color-scheme: dark) { + + time { + color: #ffbe85; + color: var(--code); + } +} + +@media (prefers-color-scheme: dark) { + + time { + background: #161f27; + background: var(--background); + } +} + +pre > code { + padding: 10px; + display: block; + overflow-x: auto; +} + +var { + color: #39a33c; + color: var(--variable); + font-style: normal; + font-family: monospace; +} + +@media (prefers-color-scheme: dark) { + + var { + color: #d941e2; + color: var(--variable); + } +} + +kbd { + background: #efefef; + background: var(--background); + border: 1px solid #dbdbdb; + border: 1px solid var(--border); + border-radius: 2px; + color: #363636; + color: var(--text-main); + padding: 2px 4px 2px 4px; +} + +@media (prefers-color-scheme: dark) { + + kbd { + color: #dbdbdb; + color: var(--text-main); + } +} + +@media (prefers-color-scheme: dark) { + + kbd { + border: 1px solid #526980; + border: 1px solid var(--border); + } +} + +@media (prefers-color-scheme: dark) { + + kbd { + background: #161f27; + background: var(--background); + } +} + +img, +video { + max-width: 100%; + height: auto; +} + +hr { + border: none; + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); +} + +@media (prefers-color-scheme: dark) { + + hr { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } +} + +table { + border-collapse: collapse; + margin-bottom: 10px; + width: 100%; + table-layout: fixed; +} + +table caption { + text-align: left; +} + +td, +th { + padding: 6px; + text-align: left; + vertical-align: top; + word-wrap: break-word; +} + +thead { + border-bottom: 1px solid #dbdbdb; + border-bottom: 1px solid var(--border); +} + +@media (prefers-color-scheme: dark) { + + thead { + border-bottom: 1px solid #526980; + border-bottom: 1px solid var(--border); + } +} + +tfoot { + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); +} + +@media (prefers-color-scheme: dark) { + + tfoot { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } +} + +tbody tr:nth-child(even) { + background-color: #efefef; + background-color: var(--background); +} + +@media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) { + background-color: #161f27; + background-color: var(--background); + } +} + +tbody tr:nth-child(even) button { + background-color: #f7f7f7; + background-color: var(--background-alt); +} + +@media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) button { + background-color: #1a242f; + background-color: var(--background-alt); + } +} + +tbody tr:nth-child(even) button:hover { + background-color: #fff; + background-color: var(--background-body); +} + +@media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) button:hover { + background-color: #202b38; + background-color: var(--background-body); + } +} + +::-webkit-scrollbar { + height: 10px; + width: 10px; +} + +::-webkit-scrollbar-track { + background: #efefef; + background: var(--background); + border-radius: 6px; +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-track { + background: #161f27; + background: var(--background); + } +} + +::-webkit-scrollbar-thumb { + background: rgb(170, 170, 170); + background: var(--scrollbar-thumb); + border-radius: 6px; +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb { + background: #040a0f; + background: var(--scrollbar-thumb); + } +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb { + background: #040a0f; + background: var(--scrollbar-thumb); + } +} + +::-webkit-scrollbar-thumb:hover { + background: #9b9b9b; + background: var(--scrollbar-thumb-hover); +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb:hover { + background: rgb(0, 0, 0); + background: var(--scrollbar-thumb-hover); + } +} + +@media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb:hover { + background: rgb(0, 0, 0); + background: var(--scrollbar-thumb-hover); + } +} + +::-moz-selection { + background-color: #9e9e9e; + background-color: var(--selection); + color: #000; + color: var(--text-bright); +} + +::selection { + background-color: #9e9e9e; + background-color: var(--selection); + color: #000; + color: var(--text-bright); +} + +@media (prefers-color-scheme: dark) { + + ::-moz-selection { + color: #fff; + color: var(--text-bright); + } + + ::selection { + color: #fff; + color: var(--text-bright); + } +} + +@media (prefers-color-scheme: dark) { + + ::-moz-selection { + background-color: #1c76c5; + background-color: var(--selection); + } + + ::selection { + background-color: #1c76c5; + background-color: var(--selection); + } +} + +details { + display: flex; + flex-direction: column; + align-items: flex-start; + background-color: #f7f7f7; + background-color: var(--background-alt); + padding: 10px 10px 0; + margin: 1em 0; + border-radius: 6px; + overflow: hidden; +} + +@media (prefers-color-scheme: dark) { + + details { + background-color: #1a242f; + background-color: var(--background-alt); + } +} + +details[open] { + padding: 10px; +} + +details > :last-child { + margin-bottom: 0; +} + +details[open] summary { + margin-bottom: 10px; +} + +summary { + display: list-item; + background-color: #efefef; + background-color: var(--background); + padding: 10px; + margin: -10px -10px 0; + cursor: pointer; + outline: none; +} + +@media (prefers-color-scheme: dark) { + + summary { + background-color: #161f27; + background-color: var(--background); + } +} + +summary:hover, +summary:focus { + text-decoration: underline; +} + +details > :not(summary) { + margin-top: 0; +} + +summary::-webkit-details-marker { + color: #363636; + color: var(--text-main); +} + +@media (prefers-color-scheme: dark) { + + summary::-webkit-details-marker { + color: #dbdbdb; + color: var(--text-main); + } +} + +dialog { + background-color: #f7f7f7; + background-color: var(--background-alt); + color: #363636; + color: var(--text-main); + border: none; + border-radius: 6px; + border-color: #dbdbdb; + border-color: var(--border); + padding: 10px 30px; +} + +@media (prefers-color-scheme: dark) { + + dialog { + border-color: #526980; + border-color: var(--border); + } +} + +@media (prefers-color-scheme: dark) { + + dialog { + color: #dbdbdb; + color: var(--text-main); + } +} + +@media (prefers-color-scheme: dark) { + + dialog { + background-color: #1a242f; + background-color: var(--background-alt); + } +} + +dialog > header:first-child { + background-color: #efefef; + background-color: var(--background); + border-radius: 6px 6px 0 0; + margin: -10px -30px 10px; + padding: 10px; + text-align: center; +} + +@media (prefers-color-scheme: dark) { + + dialog > header:first-child { + background-color: #161f27; + background-color: var(--background); + } +} + +dialog::-webkit-backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +dialog::backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +footer { + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); + padding-top: 10px; + color: #70777f; + color: var(--text-muted); +} + +@media (prefers-color-scheme: dark) { + + footer { + color: #a9b1ba; + color: var(--text-muted); + } +} + +@media (prefers-color-scheme: dark) { + + footer { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } +} + +body > footer { + margin-top: 40px; +} + +@media print { + body, + pre, + code, + summary, + details, + button, + input, + textarea { + background-color: #fff; + } + + button, + input, + textarea { + border: 1px solid #000; + } + + body, + h1, + h2, + h3, + h4, + h5, + h6, + pre, + code, + button, + input, + textarea, + footer, + summary, + strong { + color: #000; + } + + summary::marker { + color: #000; + } + + summary::-webkit-details-marker { + color: #000; + } + + tbody tr:nth-child(even) { + background-color: #f2f2f2; + } + + a { + color: #00f; + text-decoration: underline; + } +} \ No newline at end of file