Adds authentication and pricing pages for user onboarding
Introduces comprehensive user authentication flow with plan selection and registration Includes: - Pricing page with plan details and selection - Payment page with plan summary and card information - Signup page for different plan tiers - Shared authentication layout and design system improvements Enhances user onboarding experience with clear plan information and streamlined signup process
This commit is contained in:
parent
cd092f8290
commit
217a9cd95c
18 changed files with 2312 additions and 165 deletions
|
|
@ -3,7 +3,8 @@
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(cd:*)",
|
"Bash(cd:*)",
|
||||||
"Bash(ls:*)",
|
"Bash(ls:*)",
|
||||||
"Bash(dotnet build:*)"
|
"Bash(dotnet build:*)",
|
||||||
|
"Bash(find:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"liveServer.settings.port": 5501
|
"liveServer.settings.port": 5501,
|
||||||
|
"liveServer.settings.multiRootWorkspaceName": "Calendar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
101
PlanTempus.Application/Features/Accounts/Models/PlanInfo.cs
Normal file
101
PlanTempus.Application/Features/Accounts/Models/PlanInfo.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
namespace PlanTempus.Application.Features.Accounts.Models;
|
||||||
|
|
||||||
|
public record PlanInfo(
|
||||||
|
string Key,
|
||||||
|
string Name,
|
||||||
|
string UserRange,
|
||||||
|
decimal? PricePerMonth,
|
||||||
|
string BadgeText,
|
||||||
|
string BadgeClass,
|
||||||
|
string BadgeIcon,
|
||||||
|
IReadOnlyList<string> Features,
|
||||||
|
bool RequiresPayment,
|
||||||
|
bool IsContactSales
|
||||||
|
)
|
||||||
|
{
|
||||||
|
public bool IsFree => PricePerMonth == 0;
|
||||||
|
public string PriceDisplay => PricePerMonth.HasValue ? $"{PricePerMonth:0}" : "Kontakt os";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlanCatalog
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, PlanInfo> Plans = new()
|
||||||
|
{
|
||||||
|
["basis"] = new PlanInfo(
|
||||||
|
Key: "basis",
|
||||||
|
Name: "Basis",
|
||||||
|
UserRange: "1-3 brugere",
|
||||||
|
PricePerMonth: 0,
|
||||||
|
BadgeText: "Gratis",
|
||||||
|
BadgeClass: "free",
|
||||||
|
BadgeIcon: "ph-gift",
|
||||||
|
Features: new List<string>
|
||||||
|
{
|
||||||
|
"Op til 3 brugere",
|
||||||
|
"Online booking",
|
||||||
|
"Kalender & aftalestyring",
|
||||||
|
"Kundekartotek",
|
||||||
|
"SMS-påmindelser"
|
||||||
|
},
|
||||||
|
RequiresPayment: false,
|
||||||
|
IsContactSales: false
|
||||||
|
),
|
||||||
|
|
||||||
|
["pro"] = new PlanInfo(
|
||||||
|
Key: "pro",
|
||||||
|
Name: "Pro",
|
||||||
|
UserRange: "4-8 brugere",
|
||||||
|
PricePerMonth: 599,
|
||||||
|
BadgeText: "Mest populære",
|
||||||
|
BadgeClass: "popular",
|
||||||
|
BadgeIcon: "ph-star",
|
||||||
|
Features: new List<string>
|
||||||
|
{
|
||||||
|
"Op til 8 brugere",
|
||||||
|
"Alt fra Basis",
|
||||||
|
"Lagerstyring",
|
||||||
|
"Avancerede rapporter",
|
||||||
|
"Gavekort & klippekort",
|
||||||
|
"Prioriteret support"
|
||||||
|
},
|
||||||
|
RequiresPayment: true,
|
||||||
|
IsContactSales: false
|
||||||
|
),
|
||||||
|
|
||||||
|
["enterprise"] = new PlanInfo(
|
||||||
|
Key: "enterprise",
|
||||||
|
Name: "Enterprise",
|
||||||
|
UserRange: "8+ brugere",
|
||||||
|
PricePerMonth: null,
|
||||||
|
BadgeText: "Enterprise",
|
||||||
|
BadgeClass: "enterprise",
|
||||||
|
BadgeIcon: "ph-buildings",
|
||||||
|
Features: new List<string>
|
||||||
|
{
|
||||||
|
"Ubegrænset antal brugere",
|
||||||
|
"Alt fra Pro",
|
||||||
|
"Flere lokationer",
|
||||||
|
"Tilpasset integration",
|
||||||
|
"Dedikeret kontaktperson",
|
||||||
|
"SLA & uptime garanti"
|
||||||
|
},
|
||||||
|
RequiresPayment: false,
|
||||||
|
IsContactSales: true
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
public static PlanInfo GetPlan(string key)
|
||||||
|
{
|
||||||
|
if (Plans.TryGetValue(key.ToLowerInvariant(), out var plan))
|
||||||
|
return plan;
|
||||||
|
|
||||||
|
// Default to Pro if invalid key
|
||||||
|
return Plans["pro"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<PlanInfo> GetAllPlans() => Plans.Values;
|
||||||
|
|
||||||
|
public static PlanInfo Basis => Plans["basis"];
|
||||||
|
public static PlanInfo Pro => Plans["pro"];
|
||||||
|
public static PlanInfo Enterprise => Plans["enterprise"];
|
||||||
|
}
|
||||||
219
PlanTempus.Application/Features/Accounts/Pages/Payment.cshtml
Normal file
219
PlanTempus.Application/Features/Accounts/Pages/Payment.cshtml
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
@page "/payment"
|
||||||
|
@model PlanTempus.Application.Features.Accounts.Pages.PaymentModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Betaling";
|
||||||
|
Layout = "/Features/_Shared/Pages/_AuthLayout.cshtml";
|
||||||
|
|
||||||
|
var plan = Model.SelectedPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
<swp-auth-layout>
|
||||||
|
<!-- Order Summary Panel -->
|
||||||
|
<swp-auth-plan-panel>
|
||||||
|
<swp-auth-logo>
|
||||||
|
<i class="ph ph-calendar-check"></i>
|
||||||
|
<span>PlanTempus</span>
|
||||||
|
</swp-auth-logo>
|
||||||
|
|
||||||
|
<swp-selected-plan-header>Din ordre</swp-selected-plan-header>
|
||||||
|
|
||||||
|
<swp-plan-card class="selected">
|
||||||
|
<swp-plan-badge class="@plan.BadgeClass">
|
||||||
|
<i class="ph @plan.BadgeIcon"></i>
|
||||||
|
@plan.BadgeText
|
||||||
|
</swp-plan-badge>
|
||||||
|
<swp-plan-name>@plan.Name</swp-plan-name>
|
||||||
|
<swp-plan-users>@plan.UserRange</swp-plan-users>
|
||||||
|
@if (plan.PricePerMonth.HasValue)
|
||||||
|
{
|
||||||
|
<swp-plan-price>
|
||||||
|
<swp-plan-price-amount>@plan.PriceDisplay</swp-plan-price-amount>
|
||||||
|
<swp-plan-price-period>kr/md</swp-plan-price-period>
|
||||||
|
</swp-plan-price>
|
||||||
|
}
|
||||||
|
|
||||||
|
<swp-plan-features>
|
||||||
|
@foreach (var feature in plan.Features)
|
||||||
|
{
|
||||||
|
<swp-plan-feature>
|
||||||
|
<i class="ph ph-check-circle"></i>
|
||||||
|
@feature
|
||||||
|
</swp-plan-feature>
|
||||||
|
}
|
||||||
|
</swp-plan-features>
|
||||||
|
|
||||||
|
<swp-order-summary>
|
||||||
|
<swp-order-line>
|
||||||
|
<span>@plan.Name abonnement</span>
|
||||||
|
<span>@(plan.PricePerMonth.HasValue ? $"{plan.PriceDisplay} kr" : "–")</span>
|
||||||
|
</swp-order-line>
|
||||||
|
<swp-order-line class="discount">
|
||||||
|
<span>14 dages gratis prøve</span>
|
||||||
|
<span>-@(plan.PricePerMonth.HasValue ? $"{plan.PriceDisplay} kr" : "–")</span>
|
||||||
|
</swp-order-line>
|
||||||
|
<swp-order-divider></swp-order-divider>
|
||||||
|
<swp-order-line class="total">
|
||||||
|
<span>At betale i dag</span>
|
||||||
|
<span>0 kr</span>
|
||||||
|
</swp-order-line>
|
||||||
|
</swp-order-summary>
|
||||||
|
</swp-plan-card>
|
||||||
|
|
||||||
|
<swp-change-plan-link>
|
||||||
|
<a href="/pricing">
|
||||||
|
<i class="ph ph-arrow-left"></i>
|
||||||
|
Skift abonnement
|
||||||
|
</a>
|
||||||
|
</swp-change-plan-link>
|
||||||
|
</swp-auth-plan-panel>
|
||||||
|
|
||||||
|
<!-- Payment Form Panel -->
|
||||||
|
<swp-auth-form-panel>
|
||||||
|
<swp-auth-form-container>
|
||||||
|
<swp-auth-header>
|
||||||
|
<swp-auth-title>Betalingsoplysninger</swp-auth-title>
|
||||||
|
<swp-auth-description>Dit kort debiteres først efter prøveperioden</swp-auth-description>
|
||||||
|
</swp-auth-header>
|
||||||
|
|
||||||
|
<swp-auth-form id="paymentForm">
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Kortnummer</swp-form-label>
|
||||||
|
<swp-form-input class="has-icon">
|
||||||
|
<input type="text"
|
||||||
|
id="cardNumber"
|
||||||
|
placeholder="1234 5678 9012 3456"
|
||||||
|
maxlength="19"
|
||||||
|
autocomplete="cc-number" />
|
||||||
|
<span class="input-icon">
|
||||||
|
<i class="ph ph-credit-card"></i>
|
||||||
|
</span>
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-row>
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Udløbsdato</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="text"
|
||||||
|
id="expiry"
|
||||||
|
placeholder="MM/ÅÅ"
|
||||||
|
maxlength="5"
|
||||||
|
autocomplete="cc-exp" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>CVV</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="text"
|
||||||
|
id="cvv"
|
||||||
|
placeholder="123"
|
||||||
|
maxlength="4"
|
||||||
|
autocomplete="cc-csc" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
</swp-form-row>
|
||||||
|
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Navn på kort</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="text"
|
||||||
|
id="cardName"
|
||||||
|
placeholder="Som det står på kortet"
|
||||||
|
autocomplete="cc-name" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-payment-secure>
|
||||||
|
<i class="ph ph-lock"></i>
|
||||||
|
<span>Sikker betaling med SSL-kryptering</span>
|
||||||
|
</swp-payment-secure>
|
||||||
|
|
||||||
|
<swp-btn class="primary full-width lg" id="submitPayment">
|
||||||
|
<span>Start gratis prøveperiode</span>
|
||||||
|
<i class="ph ph-arrow-right"></i>
|
||||||
|
</swp-btn>
|
||||||
|
</swp-auth-form>
|
||||||
|
|
||||||
|
<!-- Processing State -->
|
||||||
|
<swp-payment-processing id="processingState" style="display: none;">
|
||||||
|
<swp-spinner></swp-spinner>
|
||||||
|
<swp-processing-text>Behandler betaling...</swp-processing-text>
|
||||||
|
</swp-payment-processing>
|
||||||
|
|
||||||
|
<!-- Success State -->
|
||||||
|
<swp-payment-success id="successState" style="display: none;">
|
||||||
|
<swp-success-icon>
|
||||||
|
<i class="ph ph-check-circle"></i>
|
||||||
|
</swp-success-icon>
|
||||||
|
<swp-success-title>Betaling godkendt!</swp-success-title>
|
||||||
|
<swp-success-text>Omdirigerer til oprettelse af konto...</swp-success-text>
|
||||||
|
</swp-payment-success>
|
||||||
|
|
||||||
|
<swp-auth-footer>
|
||||||
|
<swp-auth-footer-text>
|
||||||
|
Ved at fortsætte accepterer du vores <a href="/terms">vilkår</a>
|
||||||
|
</swp-auth-footer-text>
|
||||||
|
</swp-auth-footer>
|
||||||
|
</swp-auth-form-container>
|
||||||
|
</swp-auth-form-panel>
|
||||||
|
</swp-auth-layout>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
// Format card number with spaces
|
||||||
|
const cardNumber = document.getElementById('cardNumber');
|
||||||
|
cardNumber.addEventListener('input', function(e) {
|
||||||
|
let value = e.target.value.replace(/\s/g, '').replace(/\D/g, '');
|
||||||
|
let formatted = value.match(/.{1,4}/g)?.join(' ') || '';
|
||||||
|
e.target.value = formatted;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format expiry date
|
||||||
|
const expiry = document.getElementById('expiry');
|
||||||
|
expiry.addEventListener('input', function(e) {
|
||||||
|
let value = e.target.value.replace(/\D/g, '');
|
||||||
|
if (value.length >= 2) {
|
||||||
|
value = value.substring(0, 2) + '/' + value.substring(2, 4);
|
||||||
|
}
|
||||||
|
e.target.value = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// CVV - numbers only
|
||||||
|
const cvv = document.getElementById('cvv');
|
||||||
|
cvv.addEventListener('input', function(e) {
|
||||||
|
e.target.value = e.target.value.replace(/\D/g, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate payment processing
|
||||||
|
const submitBtn = document.getElementById('submitPayment');
|
||||||
|
const form = document.getElementById('paymentForm');
|
||||||
|
const processingState = document.getElementById('processingState');
|
||||||
|
const successState = document.getElementById('successState');
|
||||||
|
|
||||||
|
submitBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Simple validation
|
||||||
|
if (!cardNumber.value || !expiry.value || !cvv.value || !document.getElementById('cardName').value) {
|
||||||
|
alert('Udfyld venligst alle felter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show processing
|
||||||
|
form.style.display = 'none';
|
||||||
|
processingState.style.display = 'flex';
|
||||||
|
|
||||||
|
// Simulate processing delay
|
||||||
|
setTimeout(function() {
|
||||||
|
processingState.style.display = 'none';
|
||||||
|
successState.style.display = 'flex';
|
||||||
|
|
||||||
|
// Redirect to signup after success
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.href = '/signup?plan=@Model.SelectedPlan.Key&payment=complete';
|
||||||
|
}, 1500);
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using PlanTempus.Application.Features.Accounts.Models;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Accounts.Pages;
|
||||||
|
|
||||||
|
public class PaymentModel : PageModel
|
||||||
|
{
|
||||||
|
public PlanInfo SelectedPlan { get; private set; } = PlanCatalog.Pro;
|
||||||
|
|
||||||
|
public void OnGet(string plan = "pro")
|
||||||
|
{
|
||||||
|
SelectedPlan = PlanCatalog.GetPlan(plan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
@page "/pricing"
|
||||||
|
@model PlanTempus.Application.Features.Accounts.Pages.PricingModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Vælg abonnement";
|
||||||
|
Layout = "/Features/_Shared/Pages/_AuthLayout.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
<swp-pricing-page>
|
||||||
|
<swp-pricing-header>
|
||||||
|
<swp-auth-logo>
|
||||||
|
<i class="ph ph-calendar-check"></i>
|
||||||
|
<span>PlanTempus</span>
|
||||||
|
</swp-auth-logo>
|
||||||
|
|
||||||
|
<swp-pricing-title>Vælg dit abonnement</swp-pricing-title>
|
||||||
|
<swp-pricing-subtitle>Start med 14 dages gratis prøveperiode. Ingen binding.</swp-pricing-subtitle>
|
||||||
|
</swp-pricing-header>
|
||||||
|
|
||||||
|
<swp-pricing-grid>
|
||||||
|
@foreach (var plan in Model.Plans)
|
||||||
|
{
|
||||||
|
var cardClass = plan.Key switch
|
||||||
|
{
|
||||||
|
"pro" => "popular",
|
||||||
|
"enterprise" => "enterprise",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionUrl = plan.RequiresPayment
|
||||||
|
? $"/payment?plan={plan.Key}"
|
||||||
|
: $"/signup?plan={plan.Key}";
|
||||||
|
|
||||||
|
var buttonText = plan.Key switch
|
||||||
|
{
|
||||||
|
"basis" => "Kom i gang gratis",
|
||||||
|
"enterprise" => "Kontakt salg",
|
||||||
|
_ => $"Vælg {plan.Name}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttonClass = plan.IsContactSales ? "outline" : "primary";
|
||||||
|
|
||||||
|
<swp-plan-card class="@cardClass">
|
||||||
|
<swp-plan-badge class="@plan.BadgeClass">
|
||||||
|
<i class="ph @plan.BadgeIcon"></i>
|
||||||
|
@plan.BadgeText
|
||||||
|
</swp-plan-badge>
|
||||||
|
<swp-plan-name>@plan.Name</swp-plan-name>
|
||||||
|
<swp-plan-users>@plan.UserRange</swp-plan-users>
|
||||||
|
<swp-plan-price>
|
||||||
|
@if (plan.PricePerMonth.HasValue)
|
||||||
|
{
|
||||||
|
<swp-plan-price-amount>@plan.PriceDisplay</swp-plan-price-amount>
|
||||||
|
<swp-plan-price-period>kr/md</swp-plan-price-period>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<swp-plan-price-amount class="contact">Kontakt os</swp-plan-price-amount>
|
||||||
|
}
|
||||||
|
</swp-plan-price>
|
||||||
|
<swp-plan-features>
|
||||||
|
@foreach (var feature in plan.Features)
|
||||||
|
{
|
||||||
|
<swp-plan-feature>
|
||||||
|
<i class="ph ph-check-circle"></i>
|
||||||
|
@feature
|
||||||
|
</swp-plan-feature>
|
||||||
|
}
|
||||||
|
</swp-plan-features>
|
||||||
|
<swp-plan-action>
|
||||||
|
<a href="@actionUrl" class="swp-btn @buttonClass full-width">
|
||||||
|
@buttonText
|
||||||
|
</a>
|
||||||
|
</swp-plan-action>
|
||||||
|
</swp-plan-card>
|
||||||
|
}
|
||||||
|
</swp-pricing-grid>
|
||||||
|
|
||||||
|
<swp-pricing-footer>
|
||||||
|
<swp-pricing-footer-text>
|
||||||
|
Har du allerede en konto? <a href="/login">Log ind</a>
|
||||||
|
</swp-pricing-footer-text>
|
||||||
|
</swp-pricing-footer>
|
||||||
|
</swp-pricing-page>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using PlanTempus.Application.Features.Accounts.Models;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Accounts.Pages;
|
||||||
|
|
||||||
|
public class PricingModel : PageModel
|
||||||
|
{
|
||||||
|
public IEnumerable<PlanInfo> Plans { get; private set; } = Enumerable.Empty<PlanInfo>();
|
||||||
|
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
Plans = PlanCatalog.GetAllPlans();
|
||||||
|
}
|
||||||
|
}
|
||||||
283
PlanTempus.Application/Features/Accounts/Pages/Signup.cshtml
Normal file
283
PlanTempus.Application/Features/Accounts/Pages/Signup.cshtml
Normal file
|
|
@ -0,0 +1,283 @@
|
||||||
|
@page "/signup"
|
||||||
|
@model PlanTempus.Application.Features.Accounts.Pages.SignupModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = Model.SelectedPlan.IsContactSales ? "Kontakt salg" : "Opret konto";
|
||||||
|
Layout = "/Features/_Shared/Pages/_AuthLayout.cshtml";
|
||||||
|
|
||||||
|
var plan = Model.SelectedPlan;
|
||||||
|
var badgeClass = plan.IsContactSales ? plan.BadgeClass : (plan.IsFree ? plan.BadgeClass : "selected");
|
||||||
|
var badgeIcon = plan.IsContactSales ? plan.BadgeIcon : (plan.IsFree ? plan.BadgeIcon : "ph-check");
|
||||||
|
var badgeText = plan.IsContactSales ? plan.BadgeText : (plan.IsFree ? plan.BadgeText : "Valgt");
|
||||||
|
}
|
||||||
|
|
||||||
|
<swp-auth-layout>
|
||||||
|
<!-- Selected Plan Panel -->
|
||||||
|
<swp-auth-plan-panel>
|
||||||
|
<swp-auth-logo>
|
||||||
|
<i class="ph ph-calendar-check"></i>
|
||||||
|
<span>PlanTempus</span>
|
||||||
|
</swp-auth-logo>
|
||||||
|
|
||||||
|
<swp-selected-plan-header>@(plan.IsContactSales ? "Enterprise løsning" : "Dit valgte abonnement")</swp-selected-plan-header>
|
||||||
|
|
||||||
|
<swp-plan-card class="selected">
|
||||||
|
<swp-plan-badge class="@badgeClass">
|
||||||
|
<i class="ph @badgeIcon"></i>
|
||||||
|
@badgeText
|
||||||
|
</swp-plan-badge>
|
||||||
|
<swp-plan-name>@plan.Name</swp-plan-name>
|
||||||
|
<swp-plan-users>@plan.UserRange</swp-plan-users>
|
||||||
|
@if (plan.PricePerMonth.HasValue)
|
||||||
|
{
|
||||||
|
<swp-plan-price>
|
||||||
|
<swp-plan-price-amount>@plan.PriceDisplay</swp-plan-price-amount>
|
||||||
|
<swp-plan-price-period>kr/md</swp-plan-price-period>
|
||||||
|
</swp-plan-price>
|
||||||
|
}
|
||||||
|
<swp-plan-features>
|
||||||
|
@foreach (var feature in plan.Features)
|
||||||
|
{
|
||||||
|
<swp-plan-feature>
|
||||||
|
<i class="ph ph-check-circle"></i>
|
||||||
|
@feature
|
||||||
|
</swp-plan-feature>
|
||||||
|
}
|
||||||
|
</swp-plan-features>
|
||||||
|
</swp-plan-card>
|
||||||
|
|
||||||
|
<swp-change-plan-link>
|
||||||
|
<a href="/pricing">
|
||||||
|
<i class="ph ph-arrows-clockwise"></i>
|
||||||
|
Skift abonnement
|
||||||
|
</a>
|
||||||
|
</swp-change-plan-link>
|
||||||
|
</swp-auth-plan-panel>
|
||||||
|
|
||||||
|
<!-- Form Panel -->
|
||||||
|
<swp-auth-form-panel>
|
||||||
|
<swp-auth-form-container>
|
||||||
|
@if (plan.IsContactSales)
|
||||||
|
{
|
||||||
|
<!-- Enterprise Contact Form -->
|
||||||
|
<swp-auth-header>
|
||||||
|
<swp-auth-title>Kontakt vores salgsteam</swp-auth-title>
|
||||||
|
<swp-auth-description>Vi vender tilbage inden for 24 timer</swp-auth-description>
|
||||||
|
</swp-auth-header>
|
||||||
|
|
||||||
|
<swp-auth-form>
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Virksomhedsnavn</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="text"
|
||||||
|
name="companyName"
|
||||||
|
placeholder="F.eks. Salon Group ApS"
|
||||||
|
autocomplete="organization" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Antal ansatte</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<select name="employeeCount">
|
||||||
|
<option value="">Vælg antal</option>
|
||||||
|
<option value="8-15">8-15 ansatte</option>
|
||||||
|
<option value="16-30">16-30 ansatte</option>
|
||||||
|
<option value="31-50">31-50 ansatte</option>
|
||||||
|
<option value="50+">Mere end 50 ansatte</option>
|
||||||
|
</select>
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Kontaktperson</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="text"
|
||||||
|
name="contactName"
|
||||||
|
placeholder="Dit fulde navn"
|
||||||
|
autocomplete="name" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-row>
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Email</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="din@email.dk"
|
||||||
|
autocomplete="email" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Telefon</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="tel"
|
||||||
|
name="phone"
|
||||||
|
placeholder="+45 12 34 56 78"
|
||||||
|
autocomplete="tel" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
</swp-form-row>
|
||||||
|
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Besked <span class="optional">(valgfrit)</span></swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<textarea name="message"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Fortæl os om jeres behov..."></textarea>
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-btn class="primary full-width lg">
|
||||||
|
<span>Send forespørgsel</span>
|
||||||
|
<i class="ph ph-paper-plane-tilt"></i>
|
||||||
|
</swp-btn>
|
||||||
|
</swp-auth-form>
|
||||||
|
|
||||||
|
<swp-auth-footer>
|
||||||
|
<swp-auth-footer-text>
|
||||||
|
Eller ring til os på <a href="tel:+4570123456">70 12 34 56</a>
|
||||||
|
</swp-auth-footer-text>
|
||||||
|
</swp-auth-footer>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Standard Signup Form -->
|
||||||
|
<swp-auth-header>
|
||||||
|
<swp-auth-title>Opret din konto</swp-auth-title>
|
||||||
|
<swp-auth-description>
|
||||||
|
@(plan.IsFree ? "Kom i gang gratis - ingen kreditkort påkrævet" : "Start din gratis 14-dages prøveperiode")
|
||||||
|
</swp-auth-description>
|
||||||
|
</swp-auth-header>
|
||||||
|
|
||||||
|
<!-- User Info (already known) -->
|
||||||
|
<swp-user-info-card>
|
||||||
|
<swp-user-avatar>MJ</swp-user-avatar>
|
||||||
|
<swp-user-details>
|
||||||
|
<swp-user-name>Maria Jensen</swp-user-name>
|
||||||
|
<swp-user-email>maria@example.dk</swp-user-email>
|
||||||
|
</swp-user-details>
|
||||||
|
<swp-user-verified>
|
||||||
|
<i class="ph ph-check-circle"></i>
|
||||||
|
</swp-user-verified>
|
||||||
|
</swp-user-info-card>
|
||||||
|
|
||||||
|
<swp-auth-form>
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Virksomhedsnavn</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<input type="text"
|
||||||
|
name="companyName"
|
||||||
|
placeholder="F.eks. Salon Copenhagen"
|
||||||
|
autocomplete="organization" />
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-group>
|
||||||
|
<swp-form-label>Vælg adgangskode</swp-form-label>
|
||||||
|
<swp-form-input class="has-icon">
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="Mindst 8 tegn"
|
||||||
|
autocomplete="new-password" />
|
||||||
|
<span class="input-icon" id="togglePassword">
|
||||||
|
<i class="ph ph-eye"></i>
|
||||||
|
</span>
|
||||||
|
</swp-form-input>
|
||||||
|
<swp-password-strength>
|
||||||
|
<swp-strength-bar>
|
||||||
|
<swp-strength-fill id="strengthFill"></swp-strength-fill>
|
||||||
|
</swp-strength-bar>
|
||||||
|
<swp-strength-text id="strengthText"></swp-strength-text>
|
||||||
|
</swp-password-strength>
|
||||||
|
</swp-form-group>
|
||||||
|
|
||||||
|
<swp-form-checkbox>
|
||||||
|
<input type="checkbox" name="acceptTerms" id="acceptTerms" />
|
||||||
|
<span>
|
||||||
|
Jeg accepterer <a href="/terms">vilkår og betingelser</a>
|
||||||
|
samt <a href="/privacy">privatlivspolitikken</a>
|
||||||
|
</span>
|
||||||
|
</swp-form-checkbox>
|
||||||
|
|
||||||
|
<swp-btn class="primary full-width lg">
|
||||||
|
<span>Opret konto</span>
|
||||||
|
<i class="ph ph-arrow-right"></i>
|
||||||
|
</swp-btn>
|
||||||
|
</swp-auth-form>
|
||||||
|
|
||||||
|
<swp-auth-footer>
|
||||||
|
<swp-auth-footer-text>
|
||||||
|
Har du allerede en konto? <a href="/login">Log ind</a>
|
||||||
|
</swp-auth-footer-text>
|
||||||
|
</swp-auth-footer>
|
||||||
|
}
|
||||||
|
</swp-auth-form-container>
|
||||||
|
</swp-auth-form-panel>
|
||||||
|
</swp-auth-layout>
|
||||||
|
|
||||||
|
@if (!Model.SelectedPlan.IsContactSales)
|
||||||
|
{
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
// Toggle password visibility
|
||||||
|
const togglePassword = document.getElementById('togglePassword');
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
const toggleIcon = togglePassword.querySelector('i');
|
||||||
|
|
||||||
|
togglePassword.addEventListener('click', function() {
|
||||||
|
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||||
|
passwordInput.setAttribute('type', type);
|
||||||
|
toggleIcon.classList.toggle('ph-eye');
|
||||||
|
toggleIcon.classList.toggle('ph-eye-slash');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Password strength indicator
|
||||||
|
const strengthFill = document.getElementById('strengthFill');
|
||||||
|
const strengthText = document.getElementById('strengthText');
|
||||||
|
|
||||||
|
passwordInput.addEventListener('input', function() {
|
||||||
|
const password = this.value;
|
||||||
|
const strength = calculateStrength(password);
|
||||||
|
|
||||||
|
strengthFill.className = '';
|
||||||
|
strengthText.className = '';
|
||||||
|
|
||||||
|
if (password.length === 0) {
|
||||||
|
strengthText.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strength < 2) {
|
||||||
|
strengthFill.classList.add('weak');
|
||||||
|
strengthText.classList.add('weak');
|
||||||
|
strengthText.textContent = 'Svag';
|
||||||
|
} else if (strength < 3) {
|
||||||
|
strengthFill.classList.add('fair');
|
||||||
|
strengthText.classList.add('fair');
|
||||||
|
strengthText.textContent = 'Middel';
|
||||||
|
} else if (strength < 4) {
|
||||||
|
strengthFill.classList.add('good');
|
||||||
|
strengthText.classList.add('good');
|
||||||
|
strengthText.textContent = 'God';
|
||||||
|
} else {
|
||||||
|
strengthFill.classList.add('strong');
|
||||||
|
strengthText.classList.add('strong');
|
||||||
|
strengthText.textContent = 'Stærk';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function calculateStrength(password) {
|
||||||
|
let strength = 0;
|
||||||
|
if (password.length >= 8) strength++;
|
||||||
|
if (password.length >= 12) strength++;
|
||||||
|
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
|
||||||
|
if (/\d/.test(password)) strength++;
|
||||||
|
if (/[^a-zA-Z0-9]/.test(password)) strength++;
|
||||||
|
return strength;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using PlanTempus.Application.Features.Accounts.Models;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Accounts.Pages;
|
||||||
|
|
||||||
|
public class SignupModel : PageModel
|
||||||
|
{
|
||||||
|
public PlanInfo SelectedPlan { get; private set; } = PlanCatalog.Pro;
|
||||||
|
|
||||||
|
public void OnGet(string plan = "pro")
|
||||||
|
{
|
||||||
|
SelectedPlan = PlanCatalog.GetPlan(plan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="da">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>@ViewData["Title"] - PlanTempus</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@phosphor-icons/web@@2.1.2/src/regular/style.css" />
|
||||||
|
<!-- Design System -->
|
||||||
|
<link rel="stylesheet" href="~/css/design-tokens.css">
|
||||||
|
<link rel="stylesheet" href="~/css/design-system.css">
|
||||||
|
<link rel="stylesheet" href="~/css/base.css">
|
||||||
|
<!-- Auth Styles -->
|
||||||
|
<link rel="stylesheet" href="~/css/auth.css">
|
||||||
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
@RenderBody()
|
||||||
|
|
||||||
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1143
PlanTempus.Application/wwwroot/css/auth.css
Normal file
1143
PlanTempus.Application/wwwroot/css/auth.css
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,163 +1,104 @@
|
||||||
/**
|
/**
|
||||||
* SWP Design System - CSS Variables
|
* SWP Design System
|
||||||
*
|
*
|
||||||
* Dette er den centrale definition af alle design tokens.
|
* Entry point for all design tokens and base styles.
|
||||||
* Alle farver, fonts og layout-variabler defineres her.
|
* Import this file in your layout to get the complete design system.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import url('design-tokens.css');
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
COLOR PALETTE - Light Mode (Default)
|
BASE RESETS & DEFAULTS
|
||||||
=========================================== */
|
=========================================== */
|
||||||
:root {
|
*, *::before, *::after {
|
||||||
/* Surfaces */
|
box-sizing: border-box;
|
||||||
--color-surface: #fff;
|
margin: 0;
|
||||||
--color-background: #f5f5f5;
|
padding: 0;
|
||||||
--color-background-hover: #f0f0f0;
|
}
|
||||||
--color-background-alt: #fafafa;
|
|
||||||
|
|
||||||
/* Borders */
|
html {
|
||||||
--color-border: #e0e0e0;
|
font-size: var(--font-size-base);
|
||||||
--color-border-light: #f0f0f0;
|
line-height: var(--line-height-normal);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
/* Text */
|
body {
|
||||||
--color-text: #333;
|
font-family: var(--font-family);
|
||||||
--color-text-secondary: #666;
|
color: var(--color-text);
|
||||||
--color-text-muted: #999;
|
background-color: var(--color-background);
|
||||||
|
|
||||||
/* Brand Colors */
|
|
||||||
--color-teal: #00897b;
|
|
||||||
--color-teal-light: color-mix(in srgb, var(--color-teal) 10%, transparent);
|
|
||||||
|
|
||||||
/* Semantic Colors */
|
|
||||||
--color-blue: #1976d2;
|
|
||||||
--color-green: #43a047;
|
|
||||||
--color-amber: #f59e0b;
|
|
||||||
--color-red: #e53935;
|
|
||||||
--color-purple: #8b5cf6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
COLOR PALETTE - Dark Mode (System)
|
TYPOGRAPHY DEFAULTS
|
||||||
=========================================== */
|
=========================================== */
|
||||||
@media (prefers-color-scheme: dark) {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
:root:not(.light-mode) {
|
font-weight: var(--font-weight-semibold);
|
||||||
--color-surface: #1e1e1e;
|
line-height: var(--line-height-tight);
|
||||||
--color-background: #121212;
|
color: var(--color-text);
|
||||||
--color-background-hover: #2a2a2a;
|
|
||||||
--color-background-alt: #1a1a1a;
|
|
||||||
|
|
||||||
--color-border: #333;
|
|
||||||
--color-border-light: #2a2a2a;
|
|
||||||
|
|
||||||
--color-text: #e0e0e0;
|
|
||||||
--color-text-secondary: #999;
|
|
||||||
--color-text-muted: #666;
|
|
||||||
|
|
||||||
--color-teal: #26a69a;
|
|
||||||
--color-blue: #42a5f5;
|
|
||||||
--color-green: #66bb6a;
|
|
||||||
--color-amber: #ffb74d;
|
|
||||||
--color-red: #ef5350;
|
|
||||||
--color-purple: #a78bfa;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 { font-size: var(--font-size-3xl); }
|
||||||
|
h2 { font-size: var(--font-size-2xl); }
|
||||||
|
h3 { font-size: var(--font-size-xl); }
|
||||||
|
h4 { font-size: var(--font-size-lg); }
|
||||||
|
h5 { font-size: var(--font-size-base); }
|
||||||
|
h6 { font-size: var(--font-size-md); }
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: var(--spacing-8);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-teal);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
COLOR PALETTE - Dark Mode (Manual)
|
FOCUS STATES
|
||||||
=========================================== */
|
=========================================== */
|
||||||
:root.dark-mode {
|
:focus-visible {
|
||||||
--color-surface: #1e1e1e;
|
outline: 2px solid var(--color-teal);
|
||||||
--color-background: #121212;
|
outline-offset: 2px;
|
||||||
--color-background-hover: #2a2a2a;
|
|
||||||
--color-background-alt: #1a1a1a;
|
|
||||||
|
|
||||||
--color-border: #333;
|
|
||||||
--color-border-light: #2a2a2a;
|
|
||||||
|
|
||||||
--color-text: #e0e0e0;
|
|
||||||
--color-text-secondary: #999;
|
|
||||||
--color-text-muted: #666;
|
|
||||||
|
|
||||||
--color-teal: #26a69a;
|
|
||||||
--color-blue: #42a5f5;
|
|
||||||
--color-green: #66bb6a;
|
|
||||||
--color-amber: #ffb74d;
|
|
||||||
--color-red: #ef5350;
|
|
||||||
--color-purple: #a78bfa;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
TYPOGRAPHY
|
SCROLLBAR STYLING
|
||||||
=========================================== */
|
=========================================== */
|
||||||
:root {
|
::-webkit-scrollbar {
|
||||||
--font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
width: 8px;
|
||||||
--font-mono: 'JetBrains Mono', monospace;
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Font Sizes */
|
::-webkit-scrollbar-track {
|
||||||
--font-size-xs: 11px;
|
background: var(--color-background);
|
||||||
--font-size-sm: 12px;
|
}
|
||||||
--font-size-base: 14px;
|
|
||||||
--font-size-md: 13px;
|
|
||||||
--font-size-lg: 16px;
|
|
||||||
--font-size-xl: 22px;
|
|
||||||
|
|
||||||
/* Line Heights */
|
::-webkit-scrollbar-thumb {
|
||||||
--line-height-tight: 1.25;
|
background: var(--color-border);
|
||||||
--line-height-normal: 1.5;
|
border-radius: var(--radius-md);
|
||||||
--line-height-relaxed: 1.75;
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--color-border) var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
SPACING
|
SELECTION
|
||||||
=========================================== */
|
=========================================== */
|
||||||
:root {
|
::selection {
|
||||||
--spacing-1: 4px;
|
background: color-mix(in srgb, var(--color-teal) 30%, transparent);
|
||||||
--spacing-2: 8px;
|
color: var(--color-text);
|
||||||
--spacing-3: 12px;
|
|
||||||
--spacing-4: 16px;
|
|
||||||
--spacing-5: 20px;
|
|
||||||
--spacing-6: 24px;
|
|
||||||
--spacing-8: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================================
|
|
||||||
LAYOUT
|
|
||||||
=========================================== */
|
|
||||||
:root {
|
|
||||||
--side-menu-width: 240px;
|
|
||||||
--side-menu-width-collapsed: 64px;
|
|
||||||
--topbar-height: 56px;
|
|
||||||
--page-max-width: 1400px;
|
|
||||||
--border-radius: 6px;
|
|
||||||
--border-radius-lg: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================================
|
|
||||||
TRANSITIONS
|
|
||||||
=========================================== */
|
|
||||||
:root {
|
|
||||||
--transition-fast: 150ms ease;
|
|
||||||
--transition-normal: 200ms ease;
|
|
||||||
--transition-slow: 300ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================================
|
|
||||||
Z-INDEX LAYERS
|
|
||||||
=========================================== */
|
|
||||||
:root {
|
|
||||||
--z-dropdown: 100;
|
|
||||||
--z-sticky: 200;
|
|
||||||
--z-overlay: 900;
|
|
||||||
--z-drawer: 1000;
|
|
||||||
--z-modal: 1100;
|
|
||||||
--z-tooltip: 1200;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===========================================
|
|
||||||
SHADOWS
|
|
||||||
=========================================== */
|
|
||||||
:root {
|
|
||||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
311
PlanTempus.Application/wwwroot/css/design-tokens.css
Normal file
311
PlanTempus.Application/wwwroot/css/design-tokens.css
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
/**
|
||||||
|
* SWP Design System - Unified Design Tokens
|
||||||
|
*
|
||||||
|
* Konsolideret fra Calendar POC og PlanTempus Application
|
||||||
|
* Brug disse tokens konsistent på tværs af alle projekter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
COLOR PALETTE - Light Mode (Default)
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
/* -------- Surfaces & Backgrounds -------- */
|
||||||
|
--color-surface: #fff;
|
||||||
|
--color-background: #f5f5f5;
|
||||||
|
--color-background-hover: #f0f0f0;
|
||||||
|
--color-background-alt: #fafafa;
|
||||||
|
|
||||||
|
/* -------- Borders -------- */
|
||||||
|
--color-border: #e0e0e0;
|
||||||
|
--color-border-light: #f0f0f0;
|
||||||
|
|
||||||
|
/* -------- Text -------- */
|
||||||
|
--color-text: #333;
|
||||||
|
--color-text-secondary: #666;
|
||||||
|
--color-text-muted: #999;
|
||||||
|
|
||||||
|
/* -------- Primary Brand -------- */
|
||||||
|
--color-primary: #1976d2;
|
||||||
|
--color-teal: #00897b;
|
||||||
|
--color-teal-light: color-mix(in srgb, var(--color-teal) 10%, transparent);
|
||||||
|
|
||||||
|
/* -------- Semantic / Status -------- */
|
||||||
|
--color-blue: #1976d2;
|
||||||
|
--color-green: #43a047;
|
||||||
|
--color-amber: #f59e0b;
|
||||||
|
--color-red: #e53935;
|
||||||
|
--color-purple: #8b5cf6;
|
||||||
|
|
||||||
|
/* -------- Team/Resource -------- */
|
||||||
|
--color-team-bg: #e3f2fd;
|
||||||
|
--color-team-text: #1565c0;
|
||||||
|
|
||||||
|
/* -------- Grid Lines -------- */
|
||||||
|
--color-hour-line: rgba(0, 0, 0, 0.2);
|
||||||
|
--color-grid-line-light: rgba(0, 0, 0, 0.05);
|
||||||
|
--color-unavailable: rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
COLOR PALETTE - Dark Mode (System)
|
||||||
|
=========================================== */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root:not(.light-mode) {
|
||||||
|
--color-surface: #1e1e1e;
|
||||||
|
--color-background: #121212;
|
||||||
|
--color-background-hover: #2a2a2a;
|
||||||
|
--color-background-alt: #1a1a1a;
|
||||||
|
|
||||||
|
--color-border: #333;
|
||||||
|
--color-border-light: #2a2a2a;
|
||||||
|
|
||||||
|
--color-text: #e0e0e0;
|
||||||
|
--color-text-secondary: #999;
|
||||||
|
--color-text-muted: #666;
|
||||||
|
|
||||||
|
--color-teal: #26a69a;
|
||||||
|
--color-blue: #42a5f5;
|
||||||
|
--color-green: #66bb6a;
|
||||||
|
--color-amber: #ffb74d;
|
||||||
|
--color-red: #ef5350;
|
||||||
|
--color-purple: #a78bfa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
COLOR PALETTE - Dark Mode (Manual Toggle)
|
||||||
|
=========================================== */
|
||||||
|
:root.dark-mode {
|
||||||
|
--color-surface: #1e1e1e;
|
||||||
|
--color-background: #121212;
|
||||||
|
--color-background-hover: #2a2a2a;
|
||||||
|
--color-background-alt: #1a1a1a;
|
||||||
|
|
||||||
|
--color-border: #333;
|
||||||
|
--color-border-light: #2a2a2a;
|
||||||
|
|
||||||
|
--color-text: #e0e0e0;
|
||||||
|
--color-text-secondary: #999;
|
||||||
|
--color-text-muted: #666;
|
||||||
|
|
||||||
|
--color-teal: #26a69a;
|
||||||
|
--color-blue: #42a5f5;
|
||||||
|
--color-green: #66bb6a;
|
||||||
|
--color-amber: #ffb74d;
|
||||||
|
--color-red: #ef5350;
|
||||||
|
--color-purple: #a78bfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
EVENT COLOR PALETTE (18 Colors)
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
--b-color-red: #e53935;
|
||||||
|
--b-color-pink: #d81b60;
|
||||||
|
--b-color-magenta: #c200c2;
|
||||||
|
--b-color-purple: #8e24aa;
|
||||||
|
--b-color-violet: #5e35b1;
|
||||||
|
--b-color-deep-purple: #4527a0;
|
||||||
|
--b-color-indigo: #3949ab;
|
||||||
|
--b-color-blue: #1e88e5;
|
||||||
|
--b-color-light-blue: #03a9f4;
|
||||||
|
--b-color-cyan: #3bc9db;
|
||||||
|
--b-color-teal: #00897b;
|
||||||
|
--b-color-green: #43a047;
|
||||||
|
--b-color-light-green: #8bc34a;
|
||||||
|
--b-color-lime: #c0ca33;
|
||||||
|
--b-color-yellow: #fdd835;
|
||||||
|
--b-color-amber: #ffb300;
|
||||||
|
--b-color-orange: #fb8c00;
|
||||||
|
--b-color-deep-orange: #f4511e;
|
||||||
|
|
||||||
|
/* For color-mix calculations */
|
||||||
|
--b-mix: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TYPOGRAPHY
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
/* -------- Font Families -------- */
|
||||||
|
--font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
--font-mono: 'JetBrains Mono', monospace;
|
||||||
|
|
||||||
|
/* -------- Font Sizes -------- */
|
||||||
|
--font-size-xs: 11px;
|
||||||
|
--font-size-sm: 12px;
|
||||||
|
--font-size-md: 13px;
|
||||||
|
--font-size-base: 14px;
|
||||||
|
--font-size-lg: 16px;
|
||||||
|
--font-size-xl: 18px;
|
||||||
|
--font-size-2xl: 20px;
|
||||||
|
--font-size-3xl: 22px;
|
||||||
|
|
||||||
|
/* -------- Font Weights -------- */
|
||||||
|
--font-weight-light: 300;
|
||||||
|
--font-weight-regular: 400;
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--font-weight-semibold: 600;
|
||||||
|
--font-weight-bold: 700;
|
||||||
|
|
||||||
|
/* -------- Line Heights -------- */
|
||||||
|
--line-height-tight: 1.25;
|
||||||
|
--line-height-snug: 1.3;
|
||||||
|
--line-height-normal: 1.5;
|
||||||
|
--line-height-relaxed: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
SPACING
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
--spacing-0: 0;
|
||||||
|
--spacing-1: 2px;
|
||||||
|
--spacing-2: 4px;
|
||||||
|
--spacing-3: 6px;
|
||||||
|
--spacing-4: 8px;
|
||||||
|
--spacing-5: 10px;
|
||||||
|
--spacing-6: 12px;
|
||||||
|
--spacing-7: 14px;
|
||||||
|
--spacing-8: 16px;
|
||||||
|
--spacing-10: 20px;
|
||||||
|
--spacing-12: 24px;
|
||||||
|
--spacing-16: 32px;
|
||||||
|
--spacing-24: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
LAYOUT
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
/* -------- Sidebars & Navigation -------- */
|
||||||
|
--side-menu-width: 240px;
|
||||||
|
--side-menu-width-collapsed: 64px;
|
||||||
|
--topbar-height: 56px;
|
||||||
|
|
||||||
|
/* -------- Content Containers -------- */
|
||||||
|
--page-max-width: 1400px;
|
||||||
|
--container-max-width-sm: 900px;
|
||||||
|
--container-max-width-md: 1200px;
|
||||||
|
--container-max-width-lg: 1400px;
|
||||||
|
|
||||||
|
/* -------- Calendar Grid -------- */
|
||||||
|
--hour-height: 64px;
|
||||||
|
--time-axis-width: 60px;
|
||||||
|
--grid-columns: 7;
|
||||||
|
--day-column-min-width: 200px;
|
||||||
|
--day-start-hour: 6;
|
||||||
|
--day-end-hour: 18;
|
||||||
|
--header-height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
BORDERS & RADIUS
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
/* -------- Border Radius -------- */
|
||||||
|
--radius-sm: 3px;
|
||||||
|
--radius-md: 4px;
|
||||||
|
--radius-lg: 6px;
|
||||||
|
--radius-xl: 8px;
|
||||||
|
--radius-2xl: 12px;
|
||||||
|
--radius-pill: 20px;
|
||||||
|
--radius-full: 50%;
|
||||||
|
|
||||||
|
/* Legacy aliases */
|
||||||
|
--border-radius: 6px;
|
||||||
|
--border-radius-lg: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
SHADOWS
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
|
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
--shadow-xl: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
/* Specific use-cases */
|
||||||
|
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
|
--shadow-dropdown: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
--shadow-drawer: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-dragging: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TRANSITIONS
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
--transition-fast: 150ms ease;
|
||||||
|
--transition-normal: 200ms ease;
|
||||||
|
--transition-slow: 300ms ease;
|
||||||
|
|
||||||
|
/* Specific timing functions */
|
||||||
|
--ease-out: ease-out;
|
||||||
|
--ease-in-out: ease-in-out;
|
||||||
|
--ease-spring: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Z-INDEX LAYERS
|
||||||
|
=========================================== */
|
||||||
|
:root {
|
||||||
|
--z-base: 1;
|
||||||
|
--z-grid: 2;
|
||||||
|
--z-event: 10;
|
||||||
|
--z-event-hover: 20;
|
||||||
|
--z-resize-handle: 25;
|
||||||
|
--z-dropdown: 100;
|
||||||
|
--z-sticky: 200;
|
||||||
|
--z-overlay: 900;
|
||||||
|
--z-drawer: 1000;
|
||||||
|
--z-modal: 1100;
|
||||||
|
--z-tooltip: 1200;
|
||||||
|
--z-dragging: 999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
BREAKPOINTS (as CSS custom properties)
|
||||||
|
For reference - use @media queries in CSS
|
||||||
|
=========================================== */
|
||||||
|
/*
|
||||||
|
--breakpoint-sm: 600px;
|
||||||
|
--breakpoint-md: 768px;
|
||||||
|
--breakpoint-lg: 1024px;
|
||||||
|
--breakpoint-xl: 1100px;
|
||||||
|
--breakpoint-2xl: 1400px;
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
UTILITY CLASSES - Event Colors
|
||||||
|
=========================================== */
|
||||||
|
.is-red { --b-primary: var(--b-color-red); }
|
||||||
|
.is-pink { --b-primary: var(--b-color-pink); }
|
||||||
|
.is-magenta { --b-primary: var(--b-color-magenta); }
|
||||||
|
.is-purple { --b-primary: var(--b-color-purple); }
|
||||||
|
.is-violet { --b-primary: var(--b-color-violet); }
|
||||||
|
.is-deep-purple { --b-primary: var(--b-color-deep-purple); }
|
||||||
|
.is-indigo { --b-primary: var(--b-color-indigo); }
|
||||||
|
.is-blue { --b-primary: var(--b-color-blue); }
|
||||||
|
.is-light-blue { --b-primary: var(--b-color-light-blue); }
|
||||||
|
.is-cyan { --b-primary: var(--b-color-cyan); }
|
||||||
|
.is-teal { --b-primary: var(--b-color-teal); }
|
||||||
|
.is-green { --b-primary: var(--b-color-green); }
|
||||||
|
.is-light-green { --b-primary: var(--b-color-light-green); }
|
||||||
|
.is-lime { --b-primary: var(--b-color-lime); }
|
||||||
|
.is-yellow { --b-primary: var(--b-color-yellow); }
|
||||||
|
.is-amber { --b-primary: var(--b-color-amber); }
|
||||||
|
.is-orange { --b-primary: var(--b-color-orange); }
|
||||||
|
.is-deep-orange { --b-primary: var(--b-color-deep-orange); }
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
UTILITY CLASSES - Status Badges
|
||||||
|
=========================================== */
|
||||||
|
.status-confirmed { background: color-mix(in srgb, var(--color-green) 15%, transparent); color: var(--color-green); }
|
||||||
|
.status-pending { background: color-mix(in srgb, var(--color-amber) 15%, transparent); color: var(--color-amber); }
|
||||||
|
.status-inprogress { background: color-mix(in srgb, var(--color-blue) 15%, transparent); color: var(--color-blue); }
|
||||||
|
.status-error { background: color-mix(in srgb, var(--color-red) 15%, transparent); color: var(--color-red); }
|
||||||
|
.status-active { background: color-mix(in srgb, var(--color-green) 15%, transparent); color: var(--color-green); }
|
||||||
|
.status-inactive { background: var(--color-background); color: var(--color-text-muted); }
|
||||||
|
|
@ -101,11 +101,11 @@ swp-profile-section {
|
||||||
swp-profile-avatar-large {
|
swp-profile-avatar-large {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
border-radius: 50%;
|
border-radius: var(--radius-full);
|
||||||
background: var(--color-teal);
|
background: var(--color-teal);
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 24px;
|
font-size: var(--font-size-3xl);
|
||||||
font-weight: 600;
|
font-weight: var(--font-weight-semibold);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -114,7 +114,7 @@ swp-profile-avatar-large {
|
||||||
|
|
||||||
swp-profile-name-large {
|
swp-profile-name-large {
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 600;
|
font-weight: var(--font-weight-semibold);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
margin-bottom: var(--spacing-1);
|
margin-bottom: var(--spacing-1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ swp-page-header {
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-page-title h1 {
|
swp-page-title h1 {
|
||||||
font-size: 24px;
|
font-size: var(--font-size-2xl);
|
||||||
font-weight: 600;
|
font-weight: var(--font-weight-semibold);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
margin-bottom: var(--spacing-1);
|
margin-bottom: var(--spacing-1);
|
||||||
}
|
}
|
||||||
|
|
@ -65,12 +65,12 @@ swp-card-title {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-2);
|
gap: var(--spacing-2);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
font-weight: 600;
|
font-weight: var(--font-weight-semibold);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-card-title i {
|
swp-card-title i {
|
||||||
font-size: 20px;
|
font-size: var(--font-size-xl);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,12 +127,12 @@ swp-ai-header {
|
||||||
gap: var(--spacing-2);
|
gap: var(--spacing-2);
|
||||||
margin-bottom: var(--spacing-2);
|
margin-bottom: var(--spacing-2);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 500;
|
font-weight: var(--font-weight-medium);
|
||||||
color: var(--color-purple);
|
color: var(--color-purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-ai-header i {
|
swp-ai-header i {
|
||||||
font-size: 16px;
|
font-size: var(--font-size-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-ai-text {
|
swp-ai-text {
|
||||||
|
|
@ -172,7 +172,7 @@ swp-quick-action-btn:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-quick-action-btn i {
|
swp-quick-action-btn i {
|
||||||
font-size: 18px;
|
font-size: var(--font-size-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ swp-side-menu {
|
||||||
swp-side-menu-header {
|
swp-side-menu-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: var(--spacing-5);
|
||||||
height: var(--topbar-height);
|
height: var(--topbar-height);
|
||||||
padding: 0 var(--spacing-4);
|
padding: 0 var(--spacing-4);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
|
@ -100,7 +100,7 @@ a[is="swp-side-menu-item"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-3);
|
gap: var(--spacing-3);
|
||||||
padding: 10px var(--spacing-4);
|
padding: var(--spacing-5) var(--spacing-4);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
|
|
@ -146,7 +146,7 @@ swp-side-menu-action {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--spacing-2);
|
gap: var(--spacing-2);
|
||||||
padding: 10px;
|
padding: var(--spacing-5);
|
||||||
font-size: var(--font-size-md);
|
font-size: var(--font-size-md);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,8 @@ swp-stat-box {
|
||||||
=========================================== */
|
=========================================== */
|
||||||
swp-stat-value {
|
swp-stat-value {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 22px;
|
font-size: var(--font-size-3xl);
|
||||||
font-weight: 600;
|
font-weight: var(--font-weight-semibold);
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
line-height: var(--line-height-tight);
|
line-height: var(--line-height-tight);
|
||||||
|
|
@ -59,8 +59,8 @@ swp-stat-value {
|
||||||
/* Larger variant for emphasis */
|
/* Larger variant for emphasis */
|
||||||
swp-stat-card swp-stat-value,
|
swp-stat-card swp-stat-value,
|
||||||
swp-stat-box swp-stat-value {
|
swp-stat-box swp-stat-value {
|
||||||
font-size: 22px;
|
font-size: var(--font-size-3xl);
|
||||||
font-weight: 600;
|
font-weight: var(--font-weight-semibold);
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
@ -76,8 +76,8 @@ swp-stat-label {
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-stat-box swp-stat-label {
|
swp-stat-box swp-stat-label {
|
||||||
font-size: 11px;
|
font-size: var(--font-size-xs);
|
||||||
font-weight: 500;
|
font-weight: var(--font-weight-medium);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
|
@ -88,7 +88,7 @@ swp-stat-box swp-stat-label {
|
||||||
=========================================== */
|
=========================================== */
|
||||||
swp-stat-subtitle {
|
swp-stat-subtitle {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 11px;
|
font-size: var(--font-size-xs);
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
margin-top: var(--spacing-1);
|
margin-top: var(--spacing-1);
|
||||||
}
|
}
|
||||||
|
|
@ -201,11 +201,11 @@ swp-quick-stat {
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-quick-stat swp-stat-value {
|
swp-quick-stat swp-stat-value {
|
||||||
font-size: 18px;
|
font-size: var(--font-size-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-quick-stat swp-stat-label {
|
swp-quick-stat swp-stat-label {
|
||||||
font-size: 11px;
|
font-size: var(--font-size-xs);
|
||||||
margin-top: var(--spacing-1);
|
margin-top: var(--spacing-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,15 +215,15 @@ swp-quick-stat swp-stat-label {
|
||||||
swp-stat-item {
|
swp-stat-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: var(--spacing-1);
|
||||||
padding: var(--spacing-2) var(--spacing-3);
|
padding: var(--spacing-2) var(--spacing-3);
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-stat-item swp-stat-value {
|
swp-stat-item swp-stat-value {
|
||||||
font-size: 16px;
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 600;
|
font-weight: var(--font-weight-semibold);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-stat-item swp-stat-value.mono {
|
swp-stat-item swp-stat-value.mono {
|
||||||
|
|
@ -231,7 +231,7 @@ swp-stat-item swp-stat-value.mono {
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-stat-item swp-stat-label {
|
swp-stat-item swp-stat-label {
|
||||||
font-size: 11px;
|
font-size: var(--font-size-xs);
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ swp-app-topbar {
|
||||||
swp-topbar-search {
|
swp-topbar-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: var(--spacing-5);
|
||||||
padding: var(--spacing-2) var(--spacing-3);
|
padding: var(--spacing-2) var(--spacing-3);
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
|
|
@ -133,8 +133,8 @@ swp-topbar-divider {
|
||||||
swp-topbar-profile {
|
swp-topbar-profile {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: var(--spacing-5);
|
||||||
padding: 6px var(--spacing-3) 6px 6px;
|
padding: var(--spacing-3) var(--spacing-3) var(--spacing-3) var(--spacing-3);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue