mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-25 01:52:12 -04:00
Add password strength indicator to change password screen (#773)
This commit is contained in:
@@ -19,16 +19,18 @@
|
||||
</div>
|
||||
<div>
|
||||
<label for="new-password" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">New password</label>
|
||||
<InputText type="password" @bind-Value="Input.NewPassword" id="new-password" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password."/>
|
||||
<input type="password" @bind="Input.NewPassword" @oninput="OnPasswordInput" id="new-password" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password."/>
|
||||
<ValidationMessage For="() => Input.NewPassword" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="confirm-password" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">Confirm password</label>
|
||||
<InputText type="password" @bind-Value="Input.ConfirmPassword" id="confirm-password" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/>
|
||||
<input type="password" @bind="Input.ConfirmPassword" @oninput="OnConfirmPasswordInput" id="confirm-password" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/>
|
||||
<ValidationMessage For="() => Input.ConfirmPassword" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
|
||||
</div>
|
||||
<div>
|
||||
<SubmitButton>Update password</SubmitButton>
|
||||
<button type="submit" disabled="@(!IsSubmitEnabled)" class="w-full px-4 py-2 text-white bg-primary-600 hover:bg-primary-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition duration-150 ease-in-out @(IsSubmitEnabled ? "" : "opacity-50 cursor-not-allowed")">
|
||||
Update password
|
||||
</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
</div>
|
||||
@@ -38,6 +40,34 @@
|
||||
|
||||
[SupplyParameterFromForm] private InputModel Input { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the submit button should be enabled.
|
||||
/// </summary>
|
||||
private bool IsSubmitEnabled =>
|
||||
!string.IsNullOrWhiteSpace(Input.OldPassword) &&
|
||||
!string.IsNullOrWhiteSpace(Input.NewPassword) &&
|
||||
!string.IsNullOrWhiteSpace(Input.ConfirmPassword) &&
|
||||
Input.NewPassword == Input.ConfirmPassword &&
|
||||
Input.NewPassword.Length >= 10;
|
||||
|
||||
/// <summary>
|
||||
/// Handles password input to update state.
|
||||
/// </summary>
|
||||
private void OnPasswordInput(ChangeEventArgs e)
|
||||
{
|
||||
Input.NewPassword = e.Value?.ToString() ?? string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles confirm password input to update button state.
|
||||
/// </summary>
|
||||
private void OnConfirmPasswordInput(ChangeEventArgs e)
|
||||
{
|
||||
Input.ConfirmPassword = e.Value?.ToString() ?? string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Input ??= new();
|
||||
@@ -45,6 +75,7 @@
|
||||
|
||||
private async Task OnValidSubmitAsync()
|
||||
{
|
||||
|
||||
var user = await UserManager.FindByIdAsync(UserService.User().Id);
|
||||
if (user == null)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@using System.Timers
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using AliasVault.Client.Main.Components.Shared
|
||||
@using AliasVault.Client.Main.Constants
|
||||
|
||||
<div class="w-full mx-auto">
|
||||
@if (_isLoading)
|
||||
@@ -158,7 +159,7 @@
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
if (Password.Length < 10)
|
||||
if (Password.Length < PasswordStrengthConstants.MinimumGoodPasswordLength)
|
||||
{
|
||||
_errorMessage = Localizer["PasswordTooShortError"];
|
||||
await OnPasswordChange.InvokeAsync(string.Empty);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using AliasVault.Client.Main.Constants
|
||||
@inject IStringLocalizerFactory LocalizerFactory
|
||||
|
||||
@if (!string.IsNullOrEmpty(Password))
|
||||
@@ -88,10 +89,10 @@
|
||||
return _passwordStrength switch
|
||||
{
|
||||
0 => "bg-orange-400 dark:bg-orange-500",
|
||||
1 => "bg-orange-500 dark:bg-orange-600",
|
||||
2 => "bg-yellow-500 dark:bg-yellow-600",
|
||||
3 => "bg-blue-500 dark:bg-blue-600",
|
||||
4 => "bg-green-500 dark:bg-green-600",
|
||||
1 => "bg-yellow-500 dark:bg-yellow-600",
|
||||
2 => "bg-green-500 dark:bg-green-600",
|
||||
3 => "bg-green-600 dark:bg-green-700",
|
||||
4 => "bg-green-700 dark:bg-green-800",
|
||||
_ => "bg-gray-300 dark:bg-gray-600"
|
||||
};
|
||||
}
|
||||
@@ -101,10 +102,10 @@
|
||||
return _passwordStrength switch
|
||||
{
|
||||
0 => "text-orange-600 dark:text-orange-400",
|
||||
1 => "text-orange-600 dark:text-orange-400",
|
||||
2 => "text-yellow-600 dark:text-yellow-400",
|
||||
3 => "text-blue-600 dark:text-blue-400",
|
||||
4 => "text-green-600 dark:text-green-400",
|
||||
1 => "text-yellow-600 dark:text-yellow-400",
|
||||
2 => "text-green-600 dark:text-green-400",
|
||||
3 => "text-green-700 dark:text-green-300",
|
||||
4 => "text-green-800 dark:text-green-200",
|
||||
_ => "text-gray-500 dark:text-gray-400"
|
||||
};
|
||||
}
|
||||
@@ -113,4 +114,22 @@
|
||||
{
|
||||
return (_passwordStrength + 1) * 20;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current password strength level.
|
||||
/// </summary>
|
||||
/// <returns>The password strength level (0-4).</returns>
|
||||
public int GetPasswordStrength()
|
||||
{
|
||||
return _passwordStrength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current password meets the minimum required strength.
|
||||
/// </summary>
|
||||
/// <returns>True if the password meets minimum requirements, false otherwise.</returns>
|
||||
public bool MeetsMinimumRequirement()
|
||||
{
|
||||
return _passwordStrength >= PasswordStrengthConstants.MinimumRequiredStrength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="PasswordStrengthConstants.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Main.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Constants for password strength validation and requirements.
|
||||
/// </summary>
|
||||
public static class PasswordStrengthConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum password strength level required for account creation and password changes.
|
||||
/// Level 2 corresponds to "Good" (12-15 characters).
|
||||
/// </summary>
|
||||
public const int MinimumRequiredStrength = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum password length for "Good" strength level.
|
||||
/// </summary>
|
||||
public const int MinimumGoodPasswordLength = 12;
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace AliasVault.Client.Main.Models.Validation;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using AliasVault.Client.Main.Constants;
|
||||
using AliasVault.Client.Resources;
|
||||
using AliasVault.Shared.Models.WebApi.PasswordChange;
|
||||
|
||||
@@ -26,7 +27,7 @@ public class PasswordChangeFormModel : PasswordChangeModel
|
||||
/// Gets or sets the new password.
|
||||
/// </summary>
|
||||
[Required(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.PasswordRequired))]
|
||||
[MinLength(10, ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.PasswordMinLength))]
|
||||
[MinLength(PasswordStrengthConstants.MinimumGoodPasswordLength, ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.PasswordMinLength))]
|
||||
public new string NewPassword { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
@using AliasVault.Shared.Models.WebApi.Vault;
|
||||
@using AliasVault.Cryptography.Client
|
||||
@using AliasVault.Client.Services.JsInterop.RustCore
|
||||
@using AliasVault.Client.Main.Components.Shared
|
||||
@using AliasVault.Client.Main.Constants
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inherits MainBase
|
||||
@inject HttpClient Http
|
||||
@@ -40,20 +42,29 @@ else
|
||||
|
||||
<div>
|
||||
<label for="newPassword" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["NewPasswordLabel"]</label>
|
||||
<InputText type="password" id="newPassword" @bind-Value="PasswordChangeFormModel.NewPassword"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||
required />
|
||||
<input type="password" id="newPassword" @bind="PasswordChangeFormModel.NewPassword" @oninput="OnPasswordInput"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||
required />
|
||||
<PasswordStrengthIndicator @ref="PasswordStrengthIndicatorRef" Password="@PasswordChangeFormModel.NewPassword" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="newPasswordConfirm" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ConfirmNewPasswordLabel"]</label>
|
||||
<InputText type="password" id="newPasswordConfirm" @bind-Value="PasswordChangeFormModel.NewPasswordConfirm"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||
required />
|
||||
<input type="password" id="newPasswordConfirm" @bind="PasswordChangeFormModel.NewPasswordConfirm" @oninput="OnConfirmPasswordInput"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||
required />
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(PasswordStrengthError))
|
||||
{
|
||||
<div class="p-3 text-sm text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 rounded-lg">
|
||||
@PasswordStrengthError
|
||||
</div>
|
||||
}
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-primary-500 text-white py-2 px-4 rounded-md hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition duration-150 ease-in-out">
|
||||
disabled="@(!IsSubmitEnabled)"
|
||||
class="w-full bg-primary-500 text-white py-2 px-4 rounded-md hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition duration-150 ease-in-out @(IsSubmitEnabled ? "" : "opacity-50 cursor-not-allowed")">
|
||||
@Localizer["ChangePasswordButton"]
|
||||
</button>
|
||||
</EditForm>
|
||||
@@ -104,6 +115,46 @@ else
|
||||
private SrpSession ClientSession = new();
|
||||
private string PrivateKey = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the password strength indicator component.
|
||||
/// </summary>
|
||||
private PasswordStrengthIndicator? PasswordStrengthIndicatorRef { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message for password strength validation.
|
||||
/// </summary>
|
||||
private string PasswordStrengthError { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the submit button should be enabled.
|
||||
/// </summary>
|
||||
private bool IsSubmitEnabled =>
|
||||
!string.IsNullOrWhiteSpace(PasswordChangeFormModel.CurrentPassword) &&
|
||||
!string.IsNullOrWhiteSpace(PasswordChangeFormModel.NewPassword) &&
|
||||
!string.IsNullOrWhiteSpace(PasswordChangeFormModel.NewPasswordConfirm) &&
|
||||
PasswordChangeFormModel.NewPassword == PasswordChangeFormModel.NewPasswordConfirm &&
|
||||
PasswordStrengthIndicatorRef != null &&
|
||||
PasswordStrengthIndicatorRef.MeetsMinimumRequirement();
|
||||
|
||||
/// <summary>
|
||||
/// Handles real-time password input to update the strength indicator.
|
||||
/// </summary>
|
||||
private void OnPasswordInput(ChangeEventArgs e)
|
||||
{
|
||||
PasswordChangeFormModel.NewPassword = e.Value?.ToString() ?? string.Empty;
|
||||
PasswordStrengthError = string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles real-time confirm password input to update button state.
|
||||
/// </summary>
|
||||
private void OnConfirmPasswordInput(ChangeEventArgs e)
|
||||
{
|
||||
PasswordChangeFormModel.NewPasswordConfirm = e.Value?.ToString() ?? string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -157,6 +208,17 @@ else
|
||||
/// </summary>
|
||||
private async Task InitiatePasswordChange()
|
||||
{
|
||||
// Clear any previous errors
|
||||
PasswordStrengthError = string.Empty;
|
||||
|
||||
// Validate password strength before proceeding
|
||||
if (PasswordStrengthIndicatorRef == null || !PasswordStrengthIndicatorRef.MeetsMinimumRequirement())
|
||||
{
|
||||
PasswordStrengthError = Localizer["PasswordStrengthTooWeakError"];
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalLoadingSpinner.Show(Localizer["ChangingPasswordMessage"]);
|
||||
GlobalNotificationService.ClearMessages();
|
||||
StateHasChanged();
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<comment>Success message for valid password</comment>
|
||||
</data>
|
||||
<data name="PasswordTooShortError">
|
||||
<value>Master password must be at least 10 characters long.</value>
|
||||
<value>Master password must be at least 12 characters long (Good strength or higher).</value>
|
||||
<comment>Error message for password too short</comment>
|
||||
</data>
|
||||
<data name="ConfirmPasswordPrompt">
|
||||
|
||||
@@ -116,4 +116,8 @@
|
||||
<value>Failed to change password. Please refresh the page and try again.</value>
|
||||
<comment>Error message when password change fails</comment>
|
||||
</data>
|
||||
<data name="PasswordStrengthTooWeakError" xml:space="preserve">
|
||||
<value>Your new password must be at least 12 characters long (Good strength or higher).</value>
|
||||
<comment>Error message when password strength is too weak</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
<!-- Password validation messages -->
|
||||
<data name="PasswordMinLength" xml:space="preserve">
|
||||
<value>The new password must be at least 10 characters long.</value>
|
||||
<value>The new password must be at least 12 characters long (Good strength or higher).</value>
|
||||
<comment>Error message for password minimum length validation</comment>
|
||||
</data>
|
||||
<data name="PasswordsDoNotMatch" xml:space="preserve">
|
||||
@@ -69,7 +69,7 @@
|
||||
<comment>Error message when password confirmation doesn't match</comment>
|
||||
</data>
|
||||
<data name="PasswordMinLengthGeneric" xml:space="preserve">
|
||||
<value>Password must be at least 10 characters long.</value>
|
||||
<value>Password must be at least 12 characters long (Good strength or higher).</value>
|
||||
<comment>Generic error message for password minimum length validation</comment>
|
||||
</data>
|
||||
<data name="PasswordsDoNotMatchGeneric" xml:space="preserve">
|
||||
|
||||
@@ -1539,6 +1539,12 @@ video {
|
||||
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
@@ -1715,6 +1721,11 @@ video {
|
||||
border-color: rgb(251 191 36 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-blue-200 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(191 219 254 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-blue-700 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(29 78 216 / var(--tw-border-opacity));
|
||||
@@ -1894,6 +1905,11 @@ video {
|
||||
background-color: rgb(240 253 244 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
||||
@@ -2008,6 +2024,11 @@ video {
|
||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-orange-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(251 146 60 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
@@ -2584,6 +2605,12 @@ video {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-inner {
|
||||
--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
|
||||
--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.outline-0 {
|
||||
outline-width: 0px;
|
||||
}
|
||||
@@ -2664,6 +2691,10 @@ video {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.duration-500 {
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
@@ -2672,6 +2703,10 @@ video {
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.ease-out {
|
||||
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.file\:mr-4::file-selector-button {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
@@ -3154,6 +3189,11 @@ video {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disabled\:bg-gray-400:disabled {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.disabled\:opacity-50:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -3181,6 +3221,11 @@ video {
|
||||
border-color: rgb(59 130 246 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:border-blue-800:is(.dark *) {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(30 64 175 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:border-gray-400:is(.dark *) {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(156 163 175 / var(--tw-border-opacity));
|
||||
@@ -3285,6 +3330,10 @@ video {
|
||||
background-color: rgb(30 58 138 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-blue-900\/20:is(.dark *) {
|
||||
background-color: rgb(30 58 138 / 0.2);
|
||||
}
|
||||
|
||||
.dark\:bg-gray-500:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
||||
@@ -3428,6 +3477,26 @@ video {
|
||||
background-color: rgb(113 63 18 / 0.2);
|
||||
}
|
||||
|
||||
.dark\:bg-orange-600:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(234 88 12 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-yellow-600:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-orange-500:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(249 115 22 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-green-700:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-opacity-75:is(.dark *) {
|
||||
--tw-bg-opacity: 0.75;
|
||||
}
|
||||
@@ -3456,6 +3525,11 @@ video {
|
||||
color: rgb(251 191 36 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-blue-200:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(191 219 254 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-blue-400:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(96 165 250 / var(--tw-text-opacity));
|
||||
@@ -3599,6 +3673,16 @@ video {
|
||||
color: rgb(250 204 21 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-green-300:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(134 239 172 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-green-200:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(187 247 208 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:placeholder-gray-400:is(.dark *)::-moz-placeholder {
|
||||
--tw-placeholder-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
|
||||
|
||||
Reference in New Issue
Block a user