Removed vulnerabilities

This commit is contained in:
2026-07-18 10:25:41 +02:00
parent 1efe1ff21e
commit c9a5d3a79c
10 changed files with 2139 additions and 1268 deletions
@@ -1,299 +1,53 @@
@using Blazored.Toast
@inherits LayoutComponentBase
@inject NavigationManager Navigation
@inherits LayoutComponentBase
<div class="position-relative vh-100 d-flex flex-column justify-content-between overflow-hidden" style="background-color: #F9F9F9;">
<div class="cart-overlay @(IsCartOpen ? "is-visible" : "")" @onclick="ToggleCart"></div>
<div class="cart-drawer @(IsCartOpen ? "is-open" : "") d-flex flex-column bg-white shadow-lg">
<div class="cart-header d-flex align-items-center justify-content-between p-4 border-bottom">
<h5 class="fw-bold m-0 text-dark tracking-tight" style="font-family: 'Inter', sans-serif; font-size: 1.1rem;">
YOUR CART (@ShoppingCart.Items.Count())
</h5>
<button class="btn btn-sm text-dark p-1 border-0" @onclick="ToggleCart" type="button">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="water-background">
<div class="cart-body flex-grow-1 overflow-y-auto p-4">
@if (!ShoppingCart.Items.Any())
{
<div class="h-100 d-flex flex-column align-items-center justify-content-center text-muted py-5">
<svg class="mb-3 opacity-50" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z M3 6h18 M16 10a4 4 0 0 1-8 0" />
</svg>
<span class="small tracking-wide">Your collection is empty.</span>
</div>
}
else
{
<div class="d-flex flex-column gap-4">
@foreach (var item in ShoppingCart.Items)
{
<div class="cart-item d-flex gap-3 align-items-start pb-3 border-bottom-dashed">
<div class="cart-item-thumb bg-dark text-white-50 d-flex align-items-center justify-content-center px-2 text-center" style="width: 54px; height: 74px; font-size: 0.45rem; letter-spacing: 0.5px;">
@if (!string.IsNullOrWhiteSpace(item.Product!.ImageUrl))
{
<img src="@item.Product!.ImageUrl" class="img-fluid book-shadow" style="max-height: 240px; object-fit: contain;" alt="@item.Product.Name" />
}
else
{
@:[COVER]
}
</div>
<div class="flex-grow-1">
<h6 class="text-dark small fw-bold mb-0 text-truncate" style="max-width: 180px;">@item.Product!.Name</h6>
<p class="text-muted xx-small mb-2">by @($"{item.Author!.Name} {item.Author.LastName}")</p>
<div class="d-flex align-items-center justify-content-between">
<div class="quantity-picker d-flex align-items-center border rounded-pill bg-light">
<button class="btn btn-sm py-0 px-2 text-dark border-0" @onclick="() => ChangeQuantity(item, -1)" type="button">-</button>
<span class="px-1 text-dark fw-medium" style="font-size: 0.75rem;">@ShoppingCart.Items.FirstOrDefault(i => i.Price!.Id == item.Price!.Id)!.Quantity</span>
<button class="btn btn-sm py-0 px-2 text-dark border-0" @onclick="() => ChangeQuantity(item, 1)" type="button">+</button>
</div>
<span class="small fw-semibold text-dark">R @(item.Price!.Amount * item.Quantity)</span>
</div>
</div>
<button class="btn text-muted p-0 border-0 mt-1 align-self-start" style="background: none;" @onclick="() => RemoveFromCart(item)" type="button">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
}
</div>
}
</div>
<div class="blotch blotch-light-main"></div>
<div class="blotch blotch-dark-main"></div>
<div class="blotch blotch-accent-1"></div>
<div class="blotch blotch-accent-2"></div>
@if (ShoppingCart.Items.Any())
{
<div class="cart-footer p-4 bg-light border-top mt-auto">
<div class="d-flex align-items-center justify-content-between mb-4">
<span class="text-secondary small fw-medium tracking-wider">TOTAL DUE</span>
<span class="text-dark h5 fw-bold mb-0">R @GetCartTotal()</span>
</div>
<div class="d-flex flex-column gap-2">
<button class="btn btn-dark w-100 rounded-pill py-2.5 fw-medium shadow-sm small tracking-wide" @onclick="RedirectToCheckout" type="button">
Proceed to Checkout
</button>
<button class="btn btn-outline-dark w-100 rounded-pill py-2.5 fw-medium bg-white small tracking-wide" @onclick="RedirectToCart" type="button">
Go to Cart Page
</button>
</div>
</div>
}
</div>
<div class="noise-overlay"></div>
<div class="w-100 position-relative flex-shrink-0" style="z-index: 1020;">
<div class="position-absolute top-0 start-0 overflow-hidden d-none d-md-block" style="z-index: -1; pointer-events: none; opacity: 0.08; transform: translate(-10%, -10%);">
<svg width="480" height="480" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="98" stroke="#1A1A1A" stroke-width="0.25" stroke-dasharray="0.5 1.5" />
<circle cx="100" cy="100" r="92" stroke="#1A1A1A" stroke-width="0.4" />
<circle cx="100" cy="100" r="86" stroke="#1A1A1A" stroke-width="0.2" stroke-dasharray="2 1" />
<circle cx="100" cy="100" r="80" stroke="#1A1A1A" stroke-width="0.3" />
<path d="M100 20 A80 80 0 0 1 180 100 A80 80 0 0 1 100 180 A80 80 0 0 1 20 100 A80 80 0 0 1 100 20" stroke="#1A1A1A" stroke-width="0.15" />
<path d="M100 20 A80 80 0 0 0 180 100 A80 80 0 0 0 100 180 A80 80 0 0 0 20 100 A80 80 0 0 0 100 20" stroke="#1A1A1A" stroke-width="0.15" />
<line x1="40" y1="100" x2="160" y2="100" stroke="#1A1A1A" stroke-width="0.2" stroke-dasharray="1 4" />
<line x1="100" y1="40" x2="100" y2="160" stroke="#1A1A1A" stroke-width="0.2" stroke-dasharray="1 4" />
<g transform="translate(65, 82)" stroke="#1A1A1A" stroke-width="0.6" stroke-linecap="round" stroke-linejoin="round" fill="none">
<path d="M16 18 C 16 10, 24 10, 24 18" stroke-width="0.5" />
<line x1="20" y1="10" x2="20" y2="4" />
<path d="M28 18 C 28 14, 34 14, 34 18" stroke-width="0.5" />
<line x1="31" y1="14" x2="31" y2="7" />
<rect x="3" y="8" width="5" height="10" stroke-width="0.5" />
<rect x="11" y="5" width="5" height="13" stroke-width="0.5" />
<rect x="38" y="3" width="7" height="15" stroke-width="0.5" />
<polygon points="38,3 41.5,0 45,3" stroke-width="0.4" />
<rect x="49" y="9" width="6" height="9" stroke-width="0.5" />
<line x1="0" y1="18" x2="70" y2="18" stroke-width="0.75" />
</g>
</svg>
</div>
<div class="glass-panel">
<nav class="navbar navbar-expand-lg py-3" style="pointer-events: auto;">
<div class="container-fluid px-md-5">
<a class="navbar-brand d-flex align-items-center" href="/" style="transform: scale(1.6); transform-origin: left center; margin-right: 4rem;">
<svg class="me-2" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 17c-2.5 0-4.5-1.8-4.5-4s2-4 4.5-4 4.5 1.8 4.5 4-2 4-4.5 4Z" />
<path d="M12 9c2.2 0 4-1.6 4-3.5S14.2 2 12 2 8 3.6 8 5.5 9.8 9 12 9Z" />
<path d="M2 12h20" stroke-width="1" stroke-dasharray="1 2" opacity="0.5" />
</svg>
<span class="fw-bold tracking-tight text-dark" style="font-size: 0.65rem; letter-spacing: 0.8px; font-family: 'Inter', sans-serif;">MIDRAND BOOKS</span>
</a>
<header class="top-bar">
<span class="brand-text">midrandbooks.co.za</span>
<div class="collapse navbar-collapse justify-content-center" id="navbarNav">
<div class="nav-pill-wrapper d-flex align-items-center">
<a class="nav-pill-link active" href="/">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" /></svg>
Books
</a>
<a class="nav-pill-link" href="/audiobooks">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 18v-6a9 9 0 0 1 18 0v6" /><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" /></svg>
Audiobooks
</a>
<a class="nav-pill-link" href="/ebooks">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2" /><line x1="12" y1="18" x2="12.01" y2="18" /></svg>
E-Books
</a>
</div>
</div>
<div class="d-flex align-items-center gap-2 position-relative">
<div class="search-input-container d-flex align-items-center @(IsSearchActive ? "is-active" : "")">
<input type="text"
class="form-control form-control-sm rounded-pill border-light-subtle px-3 text-dark bg-light custom-search-field"
placeholder="Search by ISBN, Author, Title..."
@bind="SearchInputBuffer"
@bind:event="oninput"
@onkeydown="HandleSearchKeyDown" />
</div>
<button class="btn btn-link text-dark p-1 me-2 border-0 header-action-btn"
@onclick="ToggleGlobalSearch"
type="button"
aria-label="Toggle Search">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
<nav class="top-nav-icons">
<button class="icon-btn" title="Ebooks" aria-label="Ebooks">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20" />
</svg>
</button>
<button class="btn d-flex align-items-center justify-content-center p-2 position-relative border-0"
style="background: transparent; color: #1A1A1A; width: 36px; height: 36px;"
@onclick="ToggleCart"
type="button">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z M3 6h18 M16 10a4 4 0 0 1-8 0" />
<button class="icon-btn" title="Audiobooks" aria-label="Audiobooks">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 18v-6a9 9 0 0 1 18 0v6" />
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" />
</svg>
@if (ShoppingCart.Items.Any())
{
<span class="cart-badge">@ShoppingCart.Items.Count</span>
}
</button>
<div class="dropdown header-account-dropdown">
<button class="btn btn-sm d-flex align-items-center gap-1.5 p-1 text-dark border-0 dropdown-toggle no-caret"
type="button"
id="accountDropdownMenu"
data-bs-toggle="dropdown"
aria-expanded="false">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="flex-shrink-0">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
<button class="icon-btn" title="Library" aria-label="Library">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7" />
<rect x="14" y="3" width="7" height="7" />
<rect x="14" y="14" width="7" height="7" />
<rect x="3" y="14" width="7" height="7" />
</svg>
<span class="d-none d-md-inline font-monospace text-uppercase" style="font-size: 0.8rem; letter-spacing: 0.05em;">Account</span>
</button>
<ul class="dropdown-menu dropdown-menu-end brand-dropdown-pane p-0 m-0" aria-labelledby="accountDropdownMenu">
<AuthorizeView>
<Authorized>
<li class="dropdown-header-identity p-3 border-bottom bg-light">
<span class="d-block text-muted text-uppercase font-monospace xx-small" style="font-size: 0.6rem; letter-spacing: 0.1em;">Active Identity</span>
<span class="fw-bold text-dark text-truncate d-block" style="font-size: 0.85rem;">@(context.User.FindFirst("given_name")?.Value ?? context.User.Identity?.Name ?? "Reader")</span>
</li>
<li>
<a class="dropdown-item p-2.5 font-monospace text-uppercase text-dark d-flex align-items-center gap-2" href="/account">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
My Orders
</a>
</li>
<li>
<a class="dropdown-item p-2.5 font-monospace text-uppercase text-dark d-flex align-items-center gap-2" href="https://sts.security.khongisa.co.za/Manage/Index?returnUrl=https://midrandbooks.co.za/account" target="_blank">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
Security Center
</a>
</li>
<li><hr class="dropdown-divider m-0" /></li>
<li>
<a class="dropdown-item p-2.5 font-monospace text-uppercase text-danger d-flex align-items-center gap-2" href="javascript:void(0)" @onclick:preventDefault @onclick="HandleLogout">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
<button class="icon-btn" title="My Collection" aria-label="My Collection">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" />
</svg>
Logout
</a>
</li>
</Authorized>
<NotAuthorized>
<li>
<a class="dropdown-item p-2.5 font-monospace text-uppercase text-dark d-flex align-items-center gap-2" href="javascript:void(0)" @onclick:preventDefault @onclick="HandleLogin">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>
Sign In
</a>
</li>
<li>
<a class="dropdown-item p-2.5 font-monospace text-uppercase text-muted d-flex align-items-center gap-2" href="javascript:void(0)" @onclick:preventDefault @onclick="RedirectToAccount">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
Order History
</a>
</li>
</NotAuthorized>
</AuthorizeView>
</ul>
</div>
</div>
</div>
</button>
</nav>
</div>
</header>
<div class="w-100 flex-grow-1 overflow-y-auto d-flex flex-column justify-content-between">
<main class="position-relative" style="z-index: 5;">
<CascadingValue Value="GlobalSearchQuery">
<main class="content-area">
@Body
</CascadingValue>
</main>
<footer class="custom-site-footer border-top mt-auto">
<div class="container-fluid px-md-5">
<div class="row g-4 pb-4 justify-content-between footer-content-section">
<div class="col-12 col-md-5">
<span class="footer-brand-title d-block mb-2">MIDRAND BOOKS</span>
<p class="footer-desc mb-3">An architectural destination for curated print, thoughtful reading culture, and global design perspectives.</p>
<div class="d-flex flex-column gap-2">
<a href="mailto:info@midrandbooks.co.za" class="footer-contact-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></svg>
info@midrandbooks.co.za
</a>
<a href="tel:+27872650198" class="footer-contact-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 .2.81.7A2 2 0 0 1 22 16.92z" /></svg>
+27 87 265 9463
</a>
</div>
</div>
<div class="col-12 col-md-6 col-lg-5 text-start">
<div class="row row-cols-2 justify-content-md-end g-3">
<div style="max-width: 160px;">
<span class="footer-section-heading d-block mb-2">PLATFORM</span>
<ul class="list-unstyled d-flex flex-column gap-1.5 m-0">
<li><a href="/about" class="footer-nav-link">About Us</a></li>
<li><a href="/" class="footer-nav-link">Browse Catalog</a></li>
<li><a href="/contact" class="footer-nav-link">Contact Us</a></li>
</ul>
</div>
<div style="max-width: 210px;">
<span class="footer-section-heading d-block mb-2">ENTERPRISE</span>
<ul class="list-unstyled d-flex flex-column gap-1.5 m-0">
<li class="footer-meta-item d-flex align-items-center gap-1.5">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></svg>
Lite Charms (PTY) Ltd
</li>
<li><a href="/terms" class="footer-nav-link">Terms & Conditions</a></li>
<li><a href="/privacy" class="footer-nav-link">Privacy Policy</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="row align-items-center pt-3 footer-copyright-section">
<div class="col-12 text-center text-md-start">
<span class="footer-copyright">
&copy; @DateTime.Now.Year Midrand Books. All rights reserved.
</span>
</div>
</div>
</div>
</footer>
</div>
</div>
<BlazoredToasts />
@@ -6,6 +6,7 @@ namespace MidrandBookshop.Components.Layout;
public partial class MainLayout(CartService cartService) : IDisposable
{
[Inject] public IToastService ToastService { get; set; } = default!;
[Inject] private NavigationManager Navigation { get; set; } = default!;
private Cart ShoppingCart => cartService.ShoppingCart;
@@ -16,13 +17,13 @@ public partial class MainLayout(CartService cartService) : IDisposable
protected override async Task OnInitializedAsync()
{
Navigation.LocationChanged += OnLocationChanged;
cartService.OnCartChanged += CartService_OnCartChanged;
//Navigation.LocationChanged += OnLocationChanged;
//cartService.OnCartChanged += CartService_OnCartChanged;
if (cartService.ShoppingCart.Items.Count == 0)
await cartService.LoadCartFromStorageAsync();
//if (cartService.ShoppingCart.Items.Count == 0)
// await cartService.LoadCartFromStorageAsync();
SyncSearchQueryFromUrl();
//SyncSearchQueryFromUrl();
}
private async void CartService_OnCartChanged() => await InvokeAsync(StateHasChanged);
@@ -1,225 +1,199 @@
.sticky-top {
position: -webkit-sticky !important;
position: sticky !important;
top: 0 !important;
z-index: 1000 !important;
}
/* MainLayout.razor.css */
.cart-overlay {
position: fixed;
top: 0;
left: 0;
.water-background {
height: 100vh;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.25);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
z-index: 1040;
opacity: 0;
pointer-events: none;
transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.cart-overlay.is-visible {
opacity: 1;
pointer-events: auto;
}
.cart-drawer {
position: fixed;
top: 0;
right: -420px;
width: 100%;
max-width: 400px;
height: 100vh;
z-index: 1050;
pointer-events: none;
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.cart-drawer.is-open {
transform: translateX(-420px);
pointer-events: auto;
}
.cart-badge {
position: absolute;
top: 2px;
right: 2px;
background-color: #1A1A1A;
color: #FFF;
font-size: 0.62rem;
font-weight: 700;
border-radius: 50%;
width: 15px;
height: 15px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.xx-small {
font-size: 0.68rem;
}
.border-bottom-dashed {
border-bottom: 1px dashed rgba(0, 0, 0, 0.12);
}
.quantity-picker {
padding: 2px 4px;
}
.quantity-picker button {
font-size: 0.85rem;
font-weight: 600;
line-height: 1;
}
.quantity-picker button:hover {
background-color: rgba(0,0,0,0.05);
border-radius: 50%;
}
.custom-site-footer {
width: 100%;
overflow: hidden;
position: relative;
z-index: 10;
background-color: #1A1A1A !important;
border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
padding: 3rem 0 2rem 0;
background-color: #1e2d4a; /* Base slate */
}
.footer-content-section {
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
}
.footer-brand-title {
font-family: 'Inter', sans-serif;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 1px;
color: #FFFFFF !important;
}
.footer-desc {
font-size: 0.8rem;
line-height: 1.6;
max-width: 360px;
color: #A0AEC0 !important;
}
.footer-contact-link {
text-decoration: none;
font-size: 0.8rem;
color: #A0AEC0 !important;
display: inline-flex;
align-items: center;
gap: 8px;
transition: color 0.2s ease;
}
.footer-contact-link:hover {
color: #FFFFFF !important;
}
.footer-section-heading {
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 1.2px;
color: #FFFFFF !important;
}
.footer-nav-link {
text-decoration: none;
font-size: 0.8rem;
color: #A0AEC0 !important;
transition: color 0.2s ease;
}
.footer-nav-link:hover {
color: #FFFFFF !important;
}
.footer-meta-item {
font-size: 0.8rem;
font-weight: 500;
color: #FFFFFF !important;
}
.footer-copyright-section {
color: #718096 !important;
}
.footer-copyright {
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.2px;
}
.btn-back-to-top {
/* Base class for all floating organic shapes */
.blotch {
position: absolute;
bottom: 2rem;
right: 2rem;
width: 42px !important;
min-width: 42px !important;
max-width: 42px !important;
height: 42px !important;
border-radius: 50% !important;
background-color: #1A1A1A;
color: #FFFFFF;
border-radius: 50%;
filter: blur(80px);
z-index: 0;
}
/* 1. Main Light Blotch - Now anchored Top-Left */
.blotch-light-main {
width: 60vw;
height: 60vh;
background: #8892b0;
top: -10%;
left: -10%;
/* Sped up by ~0.9% from 25s to 24.7s */
animation: driftLight 24.7s infinite ease-in-out alternate;
}
/* 2. Main Dark Blotch - Now anchored Bottom-Right */
.blotch-dark-main {
width: 70vw;
height: 70vh;
background: #0a192f;
bottom: -10%;
right: -10%;
/* Sped up by ~0.9% from 20s to 19.8s */
animation: driftDark 19.8s infinite ease-in-out alternate;
}
/* 3. New Accent Blotch - Mid-size, mid-navy */
.blotch-accent-1 {
width: 40vw;
height: 40vh;
background: #172a45;
top: 30%;
right: 20%;
animation: driftAccentOne 22s infinite ease-in-out alternate;
}
/* 4. New Accent Blotch - Smaller, lighter shade for depth */
.blotch-accent-2 {
width: 30vw;
height: 35vh;
background: #2c3e50;
bottom: 20%;
left: 20%;
animation: driftAccentTwo 28s infinite ease-in-out alternate;
}
/* Denser Pixel Packing (SVG Noise) */
.noise-overlay {
position: absolute;
inset: 0;
z-index: 1; /* Sits above the blotches, below the glass */
opacity: 0.06; /* Keep this low; just enough to texture the gradient */
/* An inline SVG generating fractal noise */
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
pointer-events: none; /* Ensures clicks pass through to the UI */
}
/* The Glass Layer */
.glass-panel {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.03);
box-shadow: inset 0 0 80px rgba(255, 255, 255, 0.05);
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
z-index: 2;
display: flex;
flex-direction: column;
}
.content-area {
z-index: 3;
flex-grow: 1;
height: 100%;
width: 100%;
overflow-y: hidden;
padding: 2rem;
}
/* --- Animations --- */
@keyframes driftLight {
0% {
transform: translate(0, 0) scale(1);
}
100% {
transform: translate(10%, 15%) scale(1.15);
}
/* Pushes down and right */
}
@keyframes driftDark {
0% {
transform: translate(0, 0) scale(1);
}
100% {
transform: translate(-15%, -10%) scale(1.1);
}
/* Pushes up and left */
}
@keyframes driftAccentOne {
0% {
transform: translate(0, 0) scale(0.9);
}
100% {
transform: translate(-20%, 20%) scale(1.2);
}
}
@keyframes driftAccentTwo {
0% {
transform: translate(0, 0) scale(1);
}
100% {
transform: translate(25%, -15%) scale(0.85);
}
}
/* --- Floating Top Bar --- */
.top-bar {
display: flex;
align-items: center;
height: 60px;
padding: 0 2rem;
/* The Floating Magic */
margin: 1rem 1rem 0 1rem; /* 1.5rem top gap, 2rem left/right gap, 0 bottom gap */
border-radius: 10px; /* Smooth, rounded corners for the island effect */
/* The Glass Effect */
background: rgba(255, 255, 255, 0.04);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.2);
/* A subtle frosted edge all the way around to define the shape */
border: 1px solid rgba(255, 255, 255, 0.08);
z-index: 10;
}
.brand-text {
color: #f8f9fa;
font-size: 1.25rem;
font-weight: 600;
letter-spacing: 0.5px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
/* --- Top Navigation Icons --- */
.top-nav-icons {
display: flex;
align-items: center;
gap: 1.5rem; /* Even spacing between icons */
margin-left: auto; /* Pushes the nav block to the right */
}
/* The transparent button wrappers */
.icon-btn {
background: transparent;
border: none;
padding: 0.5rem;
cursor: pointer;
/* Set the icon color to a slightly muted off-white */
color: rgba(255, 255, 255, 0.6);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1010;
cursor: pointer;
transition: transform 0.2s ease, background-color 0.2s ease;
border-radius: 8px; /* For a subtle hover background if needed later */
/* Smooth out the hover animations */
transition: color 0.2s ease, transform 0.2s ease;
}
.btn-back-to-top:hover {
background-color: #333333;
/* Hover interactions */
.icon-btn:hover {
/* Brightens to pure white and floats up slightly when moused over */
color: #ffffff;
transform: translateY(-2px);
}
.btn-back-to-top:active {
transform: translateY(0);
/* Ensure the SVG scales neatly */
.icon-btn svg {
width: 22px;
height: 22px;
/* This ensures the stroke/fill matches the .icon-btn color */
stroke: currentColor;
}
.scroll-container {
scroll-behavior: smooth;
}
.no-caret::after {
display: none !important;
}
.brand-dropdown-pane {
border-radius: 0px !important;
border: 1px solid rgba(0, 0, 0, 0.15) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05) !important;
min-width: 190px;
z-index: 1050;
}
.brand-dropdown-pane .dropdown-item {
font-size: 0.72rem;
letter-spacing: 0.02em;
transition: background-color 0.15s ease, color 0.15s ease;
}
.brand-dropdown-pane .dropdown-item:hover {
background-color: #1A1A1A !important;
color: #FFFFFF !important;
}
.brand-dropdown-pane .dropdown-item.text-danger:hover {
background-color: #DC3545 !important;
color: #FFFFFF !important;
}
.dropdown-header-identity {
user-select: none;
}
@@ -3,32 +3,31 @@
<dialog id="components-reconnect-modal" data-nosnippet>
<div class="glassine-page-jacket"></div>
<div class="literary-sync-strip">
<div class="strip-container">
<div class="reconnect-content">
<div class="sync-status-indicator">
<svg class="literary-helix-loader" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path class="book-base-spine" d="M12 5v14M12 5c-1.5-2-4.5-2-7-2H2v14h3c2.5 0 5.5 0 7 2M12 5c1.5-2 4.5-2 7-2h3v14h-3c-2.5 0-5.5 0-7 2" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" />
<path class="flipping-leaf-vector" d="M12 5c-1-1.5-3-2-5-2H3v14h4c2 0 4 .5 5 2" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" />
<svg class="waveform-svg" viewBox="0 0 54 50" width="60" height="60" xmlns="http://www.w3.org/2000/svg">
<rect class="bar bar-1" x="2" y="15" width="4" height="20" rx="2" fill="currentColor" />
<rect class="bar bar-2" x="14" y="5" width="4" height="40" rx="2" fill="currentColor" />
<rect class="bar bar-3" x="26" y="20" width="4" height="10" rx="2" fill="currentColor" />
<rect class="bar bar-4" x="38" y="10" width="4" height="30" rx="2" fill="currentColor" />
<rect class="bar bar-5" x="50" y="18" width="4" height="14" rx="2" fill="currentColor" />
</svg>
<span class="font-monospace text-uppercase small-tracking">Catalog Circuit Sync</span>
</div>
<div class="sync-message-body">
<div class="sync-message-body font-monospace text-uppercase small-tracking">
<span class="components-reconnect-first-attempt-visible">
Connection interrupted. Re-indexing active reading stack...
Losing signal...
</span>
<span class="components-reconnect-repeated-attempt-visible">
Sync delayed. Re-aligning database archives in <span id="components-seconds-to-next-attempt" class="fw-bold font-monospace">0</span>s...
reconnecting in <span id="components-seconds-to-next-attempt" class="fw-bold">0</span>s...
</span>
<span class="components-reconnect-failed-visible text-crimson">
Archival path blocked. Automated sync offline.
Playback disconnected.
</span>
<span class="components-pause-visible">
Reading layout paused by host environment node.
Reading layout paused.
</span>
<span class="components-resume-failed-visible text-crimson">
@@ -37,15 +36,15 @@
</div>
<div class="sync-action-node">
<button id="components-reconnect-button" class="btn-strip-action components-reconnect-failed-visible">
<button id="components-reconnect-button" class="btn-glass-action components-reconnect-failed-visible">
<span>Retry Sync</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67" />
</svg>
</button>
<button id="components-resume-button" class="btn-strip-action components-pause-visible components-resume-failed-visible">
<button id="components-resume-button" class="btn-glass-action components-pause-visible components-resume-failed-visible">
<span>Reload Session</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
@@ -54,5 +53,4 @@
</div>
</div>
</div>
</dialog>
@@ -1,5 +1,5 @@
/* ==========================================================================
Midrand Books — Glassine Architectural Veil & Ribbon Strip
Midrand Books — Disconnect Overlay (Media Player Aesthetic)
========================================================================== */
/* --- Native Dialog Element Layout Overrides --- */
@@ -17,165 +17,151 @@
background: transparent;
z-index: 99999;
overflow: hidden;
/* Center the content */
display: flex;
align-items: center;
justify-content: center;
/* Blazor handles visibility, but we handle the fade */
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease-in-out;
}
/* Remove default browser modal backdrop blockout to allow custom layering below */
#components-reconnect-modal::backdrop {
background: transparent;
}
/* --- Glassine Page Jacket Layer ---
Frosted translucent shield that preserves context visibility while blocking mouse actions
*/
/* Show the modal smoothly when Blazor attaches state classes */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
opacity: 1;
pointer-events: all;
}
#components-reconnect-modal.components-reconnect-hide {
display: none !important;
}
/* --- Dark Frosted Jacket Layer --- */
.glassine-page-jacket {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(251, 251, 250, 0.4); /* Premium warm paper tint */
backdrop-filter: blur(5px); /* Elegant frosted glass sweep */
inset: 0;
background-color: rgba(10, 25, 47, 0.4); /* Deep navy tint */
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
cursor: wait;
animation: glassFadeIn 0.35s ease-out forwards;
z-index: 1;
}
@keyframes glassFadeIn {
from {
opacity: 0;
backdrop-filter: blur(0px);
}
to {
opacity: 1;
backdrop-filter: blur(5px);
}
}
/* --- The Sliding Ribbon Banner --- */
.literary-sync-strip {
position: absolute;
top: 0;
left: 0;
width: 100%;
background-color: #FFFFFF;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.02);
padding: 1.1rem 2rem;
box-sizing: border-box;
/* --- Centered Content Wrapper --- */
.reconnect-content {
position: relative;
z-index: 2;
/* Animation kinematics: smooth physical drop slide */
transform: translateY(-100%);
animation: stripSlideDown 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes stripSlideDown {
to {
transform: translateY(0);
}
}
/* --- Ribbon Layout Matrix Grid --- */
.strip-container {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 2.5rem;
}
/* Left Node Ticker Elements & Animated Vector */
.sync-status-indicator {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.85rem;
border-right: 1px solid rgba(0, 0, 0, 0.08);
padding-right: 2rem;
gap: 1.5rem;
color: #f8f9fa;
text-align: center;
}
.small-tracking {
font-family: var(--bs-font-monospace);
font-size: 0.7rem;
letter-spacing: 0.12em;
color: #111111;
font-weight: 600;
/* --- SVG Waveform Animation --- */
.waveform-svg {
shape-rendering: geometricPrecision;
color: #f8f9fa;
}
/* Animated Book Helix SVG */
.literary-helix-loader {
width: 18px;
height: 18px;
color: #111111;
.bar {
transform-origin: center;
animation: wavePulse 1.2s ease-in-out infinite alternate;
}
.flipping-leaf-vector {
transform-origin: 12px 12px;
animation: svgLeafFlip 1.6s infinite cubic-bezier(0.4, 0, 0.2, 1);
.bar-1 {
animation-delay: 0.0s;
}
@keyframes svgLeafFlip {
.bar-2 {
animation-delay: 0.3s;
}
.bar-3 {
animation-delay: 0.6s;
}
.bar-4 {
animation-delay: 0.2s;
}
.bar-5 {
animation-delay: 0.5s;
}
@keyframes wavePulse {
0% {
transform: scaleX(1);
opacity: 1;
}
50% {
transform: scaleX(0);
opacity: 0.3;
transform: scaleY(0.3);
opacity: 0.5;
}
100% {
transform: scaleX(-1);
opacity: 0;
transform: scaleY(1);
opacity: 1;
}
}
/* Center Node Text Content */
.sync-message-body {
font-family: Georgia, 'Times New Roman', serif;
font-size: 0.95rem;
color: #333333;
font-style: italic;
/* Stop the waveform if the connection completely fails */
#components-reconnect-modal.components-reconnect-failed .bar,
#components-reconnect-modal.components-reconnect-rejected .bar,
#components-reconnect-modal.components-reconnect-paused .bar {
animation: none;
transform: scaleY(0.1);
opacity: 0.3;
}
/* --- Typography --- */
.small-tracking {
font-size: 0.85rem;
letter-spacing: 0.15em;
font-weight: 500;
color: rgba(255, 255, 255, 0.7);
}
.text-crimson {
color: #A34843;
font-style: normal;
font-weight: 500;
color: #ff6b6b; /* Softened red for dark mode */
font-weight: 600;
}
/* Right Node Fine-Press Button Trigger */
.btn-strip-action {
background: #111111;
color: #FFFFFF;
border: 1px solid #111111;
padding: 0.45rem 1.25rem;
/* --- Glass Buttons --- */
.sync-action-node {
margin-top: 1rem;
display: flex;
gap: 1rem;
}
.btn-glass-action {
background: rgba(255, 255, 255, 0.05);
color: #f8f9fa;
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 0.6rem 1.5rem;
font-family: var(--bs-font-monospace);
font-size: 0.7rem;
letter-spacing: 0.08em;
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
border-radius: 4px;
transition: all 0.2s ease;
border-radius: 8px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.5rem;
transition: all 0.2s ease;
}
.btn-strip-action:hover {
background: transparent;
color: #111111;
.btn-glass-action:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.btn-strip-action svg {
transition: transform 0.2s ease;
}
.btn-strip-action:hover svg {
transform: rotate(45deg);
}
/* --- Display Mechanics Matrix Controllers --- */
/* --- Display Mechanics Matrix Controllers (Preserved) --- */
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
@@ -192,27 +178,8 @@
display: inline-block !important;
}
#components-reconnect-modal.components-reconnect-failed .btn-strip-action,
#components-reconnect-modal.components-reconnect-paused .btn-strip-action,
#components-reconnect-modal.components-reconnect-resume-failed .btn-strip-action {
#components-reconnect-modal.components-reconnect-failed .btn-glass-action,
#components-reconnect-modal.components-reconnect-paused .btn-glass-action,
#components-reconnect-modal.components-reconnect-resume-failed .btn-glass-action {
display: inline-flex !important;
}
/* Tablet Parameters Response Collapse Matrix */
@media (max-width: 768px) {
.strip-container {
grid-template-columns: 1fr;
gap: 0.65rem;
text-align: center;
}
.sync-status-indicator {
border-right: none;
padding-right: 0;
justify-content: center;
}
.sync-action-node {
margin-top: 0.25rem;
}
}
File diff suppressed because it is too large Load Diff
+11 -8
View File
@@ -101,20 +101,23 @@ public partial class Home : ComponentBase
protected override async Task OnInitializedAsync()
{
if (CartService.ShoppingCart.Items.Count == 0)
await CartService.LoadCartFromStorageAsync();
//if (CartService.ShoppingCart.Items.Count == 0)
// await CartService.LoadCartFromStorageAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender == false && HydrationService.CartHydrated == false)
{
if(!CartService.ShoppingCart.CustomerId.HasValue)
await HydrationService.EnsureCustomerExistsAsync(CancellationToken);
}
//if (firstRender == false && HydrationService.CartHydrated == false)
//{
// if(!CartService.ShoppingCart.CustomerId.HasValue)
// await HydrationService.EnsureCustomerExistsAsync(CancellationToken);
//}
}
protected override async Task OnParametersSetAsync() => await LoadCatalogDataAsync();
protected override async Task OnParametersSetAsync()
{
//await LoadCatalogDataAsync();
}
private async Task LoadCatalogDataAsync()
{
+61 -161
View File
@@ -1,177 +1,77 @@
/* Expanding Search Input Controls */
.search-input-container {
max-width: 0;
opacity: 0;
overflow: hidden;
transition: max-width 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s ease;
/* --- Centering Container --- */
.home-portal-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.search-input-container.is-active {
max-width: 280px;
opacity: 1;
/* --- Invisible Wrapper --- */
.artistic-portal {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
/* Make the whole block interactive */
cursor: pointer;
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.artistic-portal:hover {
transform: translateY(-5px) scale(1.02);
}
.custom-search-field {
font-size: 0.8rem;
height: 32px;
box-shadow: none !important;
/* --- The Illustration --- */
.portal-illustration {
width: 100%;
max-width: 450px; /* Scales nicely on larger screens */
height: auto;
/* Adds a beautiful glass-like shadow behind the vector lines */
filter: drop-shadow(0 15px 25px rgba(0, 0, 0, 0.3));
transition: filter 0.4s ease;
}
/* Structural Layout Typography */
.branding-logo {
font-size: 1.1rem;
letter-spacing: -0.5px;
.artistic-portal:hover .portal-illustration {
filter: drop-shadow(0 25px 35px rgba(0, 0, 0, 0.4)) drop-shadow(0 0 15px rgba(255, 255, 255, 0.1));
}
.text-hero-wrapper {
margin-top: 5rem;
margin-bottom: 3.5rem;
/* --- Ambient Animations inside the SVG --- */
.float-slow {
animation: floatVertical 6s ease-in-out infinite;
transform-origin: center;
}
.master-headline {
letter-spacing: -1.5px;
font-weight: 400;
line-height: 1.15;
.float-medium {
animation: floatVertical 4s ease-in-out infinite reverse;
transform-origin: center;
}
.sub-headline {
max-width: 520px;
font-size: 0.95rem;
@keyframes floatVertical {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-12px);
}
}
/* --- The Text --- */
.read-text {
color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 2rem;
font-weight: 300;
line-height: 1.6;
letter-spacing: 0.35em;
margin-left: 0.35em; /* Compensates for the extra tracking on the right side */
/* Soft glow */
text-shadow: 0 4px 15px rgba(248, 249, 250, 0.2);
transition: all 0.4s ease;
}
/* Filtering Dropdown Drawer Settings */
.filter-dropdown-panel {
border-radius: 12px;
}
.panel-section-heading {
letter-spacing: 0.5px;
font-size: 0.7rem;
}
.reset-link-btn {
font-size: 0.7rem;
}
/* Custom Minimal Separator Rule Layout */
.custom-milled-line {
height: 35px;
margin-top: 5px;
margin-bottom: 25px;
z-index: 0;
pointer-events: none;
}
.center-bloom-shadow {
bottom: 15px;
width: 55%;
height: 50px;
background: radial-gradient(ellipse at bottom, rgba(20, 20, 20, 0.07) 0%, rgba(40, 40, 40, 0.02) 60%, rgba(255, 255, 255, 0) 100%);
filter: blur(10px);
}
.core-horizontal-rule {
bottom: 15px;
height: 1px;
background: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.15) 50%, rgba(0,0,0,0) 100%);
}
/* View Engine Layout Presentation Variants: Grid Cards */
.book-grid-card {
background-color: #F1F1F1;
border-radius: 12px;
min-height: 380px;
}
.badge-new-arrival {
background-color: #E63946;
font-size: 0.75rem;
}
.book-spine-fallback {
width: 130px;
height: 185px;
font-size: 0.65rem;
letter-spacing: 1px;
border-radius: 2px;
}
.product-card-title {
font-size: 0.95rem;
font-weight: 500;
}
/* View Engine Layout Presentation Variants: List Rows */
.list-row-item {
border-bottom: 1px solid rgba(0,0,0,0.06);
transition: background-color 0.2s ease;
}
.list-row-item:hover {
background-color: rgba(0,0,0,0.01);
}
.structural-list-left {
flex: 2;
}
.list-item-title {
font-size: 0.95rem;
min-width: 260px;
}
.list-item-author {
min-width: 160px;
}
.list-item-tag {
font-size: 0.65rem;
}
.list-new-badge {
font-size: 0.65rem;
}
.list-item-price {
font-size: 0.9rem;
min-width: 80px;
text-align: right;
}
/* Enable native smooth scrolling page-wide */
html {
scroll-behavior: smooth;
}
/* Back to Top Icon Layout Presentation */
.back-to-top-btn {
position: fixed !important;
bottom: 32px;
right: 32px;
width: 42px;
height: 42px;
border-radius: 50% !important;
text-decoration: none !important;
/* Dual-tone contrast visibility profile */
background-color: #ffffff !important;
color: #1a1a1a !important;
border: 2px solid #1a1a1a !important;
box-shadow: 0 0 0 2px #ffffff, 0 4px 12px rgba(0, 0, 0, 0.15) !important;
z-index: 2147483647 !important;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.back-to-top-btn:hover {
transform: translateY(-4px);
background-color: #1a1a1a !important;
color: #ffffff !important;
border-color: #ffffff !important;
box-shadow: 0 0 0 2px #1a1a1a, 0 6px 16px rgba(0, 0, 0, 0.25) !important;
}
/* Direct the smooth scroll action straight to the layout viewport boundary */
#top-target {
scroll-margin-top: 100vh;
.artistic-portal:hover .read-text {
color: #ffffff;
letter-spacing: 0.4em;
text-shadow: 0 4px 20px rgba(248, 249, 250, 0.5);
}
+2
View File
@@ -18,6 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="KubernetesClient" Version="19.0.2" />
<PackageReference Include="LiteCharms.Features" Version="1.137.0" />
</ItemGroup>
@@ -25,6 +26,7 @@
<ItemGroup>
<PackageReference Include="ANM.Blazored.Toast" Version="0.1.1" />
<PackageReference Include="LiteCharms.Features.MidrandBooks" Version="1.137.0" />
<PackageReference Include="Microsoft.OpenApi" Version="2.11.0" />
<!-- Global Usings -->
<Using Include="Blazored.Toast.Services" />
+9 -219
View File
@@ -1,224 +1,14 @@
@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600&display=swap');
:root {
/* Fonts */
--font-heading: 'Instrument Serif', serif;
--font-ui: 'Inter', sans-serif;
/* Color Palette */
--mb-bg: #F9F9F9;
--mb-card-bg: #FFFFFF;
--mb-text-dark: #1A1A1A;
--mb-text-muted: #666666;
--mb-accent-red: #E63946;
--mb-radius: 12px;
/* High-Visibility Machined White Metal Core Surface Definition */
/* Sharp linear reflection channels paired with micro-milled density grids */
--brushed-metal-bg: linear-gradient(90deg, rgba(255, 255, 255, 1) 0%, rgba(240, 241, 245, 0.95) 25%, rgba(255, 255, 255, 1) 50%, rgba(238, 240, 244, 0.95) 75%, rgba(255, 255, 255, 1) 100% ), repeating-linear-gradient( 0deg, rgba(0, 0, 0, 0.012) 0px, rgba(0, 0, 0, 0.012) 1px, transparent 1px, transparent 2px ), repeating-linear-gradient( 90deg, rgba(255, 255, 255, 0.9) 0px, rgba(255, 255, 255, 0.9) 1px, transparent 1px, transparent 3px ), #EDEFF4; /* Definitive satin platinum breakout base fallback */
html, body {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #ffffff;
}
/* Global Reset & Core Variables Mapping */
body {
background-color: var(--mb-bg);
color: var(--mb-text-dark);
font-family: var(--font-ui);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, .display-font {
font-family: var(--font-heading);
font-weight: 400;
}
/* Custom Navigation Pill Styling */
.nav-pill-wrapper {
background-color: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.06);
padding: 6px;
border-radius: 50px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02);
}
.nav-pill-link {
font-size: 0.9rem;
font-weight: 500;
color: var(--mb-text-muted);
padding: 8px 20px;
border-radius: 50px;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.nav-pill-link i {
width: 16px;
height: 16px;
stroke-width: 1.5;
}
/* Navigation Pill States */
.nav-pill-link:hover {
color: var(--mb-text-dark);
background-color: #F1F1F1;
}
.nav-pill-link.active {
color: #FFFFFF !important;
background-color: var(--mb-text-dark);
}
.nav-pill-link.active svg {
stroke: #FFFFFF;
}
/* Utilities & Component Support Layouts */
.cart-badge {
position: absolute;
top: -5px;
right: -5px;
background-color: var(--mb-text-dark);
color: #FFFFFF;
font-size: 0.7rem;
font-weight: 600;
width: 18px;
height: 18px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--mb-bg);
}
.transition-smooth {
transition: color 0.2s ease;
}
.transition-smooth:hover {
color: var(--mb-text-dark) !important;
}
.py-1-5 {
padding-top: 0.35rem !important;
padding-bottom: 0.35rem !important;
}
/* Global Focus Ring Corrections */
h1:focus, h2:focus, h3:focus, h4:focus, p:focus, div:focus, span:focus {
outline: none !important;
box-shadow: none !important;
}
[tabindex="-1"]:focus {
outline: none !important;
}
/* ==========================================================================
Global Toast Notification Framework Extensions
========================================================================== */
.blazored-toast-container {
position: fixed;
/* 🛠️ Shift anchors from top-right to bottom-left */
bottom: 24px;
left: 24px;
top: auto;
right: auto;
z-index: 2000 !important;
display: flex;
flex-direction: column-reverse; /* 💡 Newest toasts will now stack cleanly on top of old ones */
gap: 12px;
max-width: 400px;
width: 100%;
pointer-events: none;
}
.blazored-toast {
display: flex;
align-items: center;
padding: 16px 20px;
border-radius: var(--mb-radius);
background-color: var(--mb-card-bg);
color: var(--mb-text-dark);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.05);
font-family: var(--font-ui);
font-size: 0.9rem;
font-weight: 500;
animation: toastFadeIn 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.125) forwards;
}
/* Success Toast Core Variants */
.blazored-toast-success {
border-left: 4px solid var(--mb-text-dark);
}
/* Error Toast Core Variants */
.blazored-toast-error {
border-left: 4px solid var(--mb-accent-red);
color: var(--mb-accent-red);
}
.blazored-toast-icon {
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 12px;
flex-shrink: 0;
}
/* Entry Transition Keyframes */
@keyframes toastFadeIn {
from {
opacity: 0;
transform: translateY(-12px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.book-shadow {
filter: drop-shadow(5px 10px 15px rgba(0, 0, 0, 0.15)) drop-shadow(1px 2px 4px rgba(0, 0, 0, 0.1));
}
.sm-icon {
width: 14px;
height: 14px;
vertical-align: middle;
}
/* 🛠️ Micro-interactions for the header icon placement */
.btn-cart-icon:hover {
transform: scale(1.08);
background-color: var(--mb-text-dark) !important;
}
.btn-cart-icon:hover svg {
stroke: #FFFFFF !important;
}
@keyframes toastFadeIn {
from {
opacity: 0;
transform: translateX(-24px) scale(0.95); /* Slide rightward into view */
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
.blazored-toast button.blazored-toast-close,
.blazored-toast-close-icon {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
padding: 0 !important;
margin: 0 !important;
-moz-osx-font-smoothing: grayscale;
}