Implemented main home design and cart viewer
This commit is contained in:
@@ -11,13 +11,14 @@
|
|||||||
<link rel="stylesheet" href="@Assets["MidrandBookshop.styles.css"]" />
|
<link rel="stylesheet" href="@Assets["MidrandBookshop.styles.css"]" />
|
||||||
<ImportMap />
|
<ImportMap />
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<HeadOutlet />
|
<HeadOutlet @rendermode="InteractiveServer" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<Routes />
|
<Routes @rendermode="InteractiveServer" />
|
||||||
<ReconnectModal />
|
<ReconnectModal />
|
||||||
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="col-12 col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card border-0 h-100 p-4 position-relative" style="background-color: #F1F1F1; border-radius: var(--mb-radius);">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<span class="badge rounded-pill px-3 py-2" style="background-color: var(--mb-accent-red); font-weight: 500;">New</span>
|
||||||
|
<button class="btn bg-white rounded-circle d-flex align-items-center justify-content-center p-2 shadow-sm border-0" style="width: 32px; height: 32px;">
|
||||||
|
<i data-lucide="arrow-up-right" style="width: 16px; height: 16px; color: var(--mb-text-dark);"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center align-items-center flex-grow-1 my-3" style="min-height: 260px;">
|
||||||
|
<img src="@BookImageUrl" class="img-fluid book-shadow" style="max-height: 240px; object-fit: contain;" alt="@Title" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-3 px-2">
|
||||||
|
<span class="fw-medium text-dark" style="font-size: 0.9rem;">@Title</span>
|
||||||
|
<span class="text-muted fw-semibold" style="font-size: 0.9rem;">$@Price</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string Title { get; set; } = "";
|
||||||
|
[Parameter] public decimal Price { get; set; }
|
||||||
|
[Parameter] public string BookImageUrl { get; set; } = "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
@@ -1,13 +1,318 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<div class="page">
|
<div class="position-relative min-vh-100 d-flex flex-column justify-content-between overflow-hidden" style="background-color: #F9F9F9;">
|
||||||
<main>
|
|
||||||
<div class="top-row px-4">
|
<div class="cart-overlay @(IsCartOpen ? "is-visible" : "")" @onclick="ToggleCart"></div>
|
||||||
<h2>Midrand Books</h2>
|
<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 (@CartItems.Sum(i => i.Quantity))
|
||||||
|
</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>
|
||||||
|
|
||||||
<article class="content px-4">
|
<div class="cart-body flex-grow-1 overflow-y-auto p-4">
|
||||||
@Body
|
@if (!CartItems.Any())
|
||||||
</article>
|
{
|
||||||
</main>
|
<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 CartItems)
|
||||||
|
{
|
||||||
|
<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;">
|
||||||
|
[ COVER ]
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h6 class="text-dark small fw-bold mb-0 text-truncate" style="max-width: 180px;">@item.Title</h6>
|
||||||
|
<p class="text-muted xx-small mb-2">by @item.Author</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;">@item.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 * 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>
|
||||||
|
|
||||||
|
@if (CartItems.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="w-100 flex-grow-1 position-relative">
|
||||||
|
|
||||||
|
<div class="position-absolute top-0 start-0 overflow-hidden d-none d-md-block" style="z-index: 0; 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>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg py-4 bg-transparent position-relative" style="z-index: 100; 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>
|
||||||
|
|
||||||
|
<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..."
|
||||||
|
value="@GlobalSearchQuery"
|
||||||
|
@oninput="OnSearchInput" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-sm rounded-circle d-flex align-items-center justify-content-center border-0 p-0 transition-smooth @(IsSearchActive ? "bg-dark text-white" : "bg-light text-dark")"
|
||||||
|
style="width: 32px; height: 32px;"
|
||||||
|
@onclick="ToggleGlobalSearch"
|
||||||
|
type="button">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
|
</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" />
|
||||||
|
</svg>
|
||||||
|
@if (CartItems.Any())
|
||||||
|
{
|
||||||
|
<span class="cart-badge">@CartItems.Sum(i => i.Quantity)</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a href="/login" class="btn btn-dark rounded-pill px-3 py-1 d-inline-flex align-items-center gap-2 btn-sm fw-medium shadow-sm">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||||
|
<circle cx="12" cy="7" r="4" />
|
||||||
|
</svg>
|
||||||
|
Log In
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="position-relative" style="z-index: 5;">
|
||||||
|
<CascadingValue Value="GlobalSearchQuery">
|
||||||
|
@Body
|
||||||
|
</CascadingValue>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="w-100 position-relative overflow-hidden"
|
||||||
|
style="z-index: 10; padding: 5.5rem 0 3.5rem 0; background-color: #F2F4F7 !important; background-image: linear-gradient(90deg, #FFF 0%, rgba(243,245,249,0.98) 22%, #FFF 48%, rgba(239,242,247,0.98) 73%, #FFF 100%), repeating-linear-gradient(15deg, rgba(0,0,0,0.005) 0px, rgba(0,0,0,0.005) 1px, transparent 1px, transparent 41px), repeating-linear-gradient(-25deg, rgba(255,255,255,0.6) 0px, rgba(255,255,255,0.6) 2px, transparent 2px, transparent 73px), repeating-linear-gradient(65deg, rgba(0,0,0,0.003) 0px, rgba(0,0,0,0.003) 1px, transparent 1px, transparent 127px) !important;">
|
||||||
|
<div class="position-absolute top-0 start-0 w-100 h-100" style="background: radial-gradient(circle at 50% 30%, rgba(255,255,255,0.45) 0%, rgba(242,244,248,0.05) 100%); pointer-events: none; z-index: 1; backdrop-filter: blur(1.5px); -webkit-backdrop-filter: blur(1.5px);"></div>
|
||||||
|
<div class="position-absolute top-0 start-0 w-100 h-100" style="z-index: 2; opacity: 0.95; pointer-events: none; mix-blend-mode: multiply;">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 1440 400" preserveAspectRatio="none" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<ellipse cx="15%" cy="35%" rx="750" ry="400" fill="url(#randomMetal_1)" />
|
||||||
|
<ellipse cx="60%" cy="80%" rx="950" ry="350" fill="url(#randomMetal_2)" />
|
||||||
|
<ellipse cx="85%" cy="20%" rx="600" ry="250" fill="url(#randomMetal_3)" />
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="randomMetal_1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(216 140) rotate(15) scale(350 750)">
|
||||||
|
<stop offset="0%" stop-color="#FFFFFF" />
|
||||||
|
<stop offset="60%" stop-color="#EBF0F7" />
|
||||||
|
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="randomMetal_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(864 320) rotate(-15) scale(300 950)">
|
||||||
|
<stop offset="0%" stop-color="#FFFFFF" />
|
||||||
|
<stop offset="70%" stop-color="#E2E7F2" />
|
||||||
|
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="randomMetal_3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1224 80) rotate(45) scale(200 600)">
|
||||||
|
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.8" />
|
||||||
|
<stop offset="50%" stop-color="#E9EDF5" stop-opacity="0.3" />
|
||||||
|
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="position-absolute top-0 start-0 w-100" style="height: 2px; z-index: 5;">
|
||||||
|
<div class="w-100" style="height: 1px; background: rgba(0, 0, 0, 0.055);"></div>
|
||||||
|
<div class="w-100" style="height: 1px; background: rgba(255, 255, 255, 0.95);"></div>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid px-md-5 position-relative" style="z-index: 4;">
|
||||||
|
<div class="row g-4 pb-5 justify-content-between" style="border-bottom: 1px solid rgba(0, 0, 0, 0.05);">
|
||||||
|
<div class="col-12 col-md-5">
|
||||||
|
<span class="fw-bold tracking-tight text-dark d-block mb-3" style="font-size: 0.75rem; letter-spacing: 1px; font-family: 'Inter', sans-serif;">MIDRAND BOOKS</span>
|
||||||
|
<p class="text-muted small mb-4" style="line-height: 1.6; max-width: 360px;">An architectural destination for curated print, thoughtful reading culture, and global design perspectives.</p>
|
||||||
|
<div class="d-flex flex-column gap-2.5">
|
||||||
|
<a href="mailto:info@midrandshop.co.za" class="text-decoration-none text-muted d-inline-flex align-items-center gap-2 small">
|
||||||
|
<svg width="13" height="13" 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@midrandshop.co.za
|
||||||
|
</a>
|
||||||
|
<a href="tel:+27872650198" class="text-decoration-none text-muted d-inline-flex align-items-center gap-2 small">
|
||||||
|
<svg width="13" height="13" 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 0198
|
||||||
|
</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="text-dark fw-bold tracking-wider d-block mb-3" style="font-size: 0.65rem; letter-spacing: 1.2px;">PLATFORM</span>
|
||||||
|
<ul class="list-unstyled d-flex flex-column gap-2 m-0">
|
||||||
|
<li><a href="/about" class="text-decoration-none text-muted small">About Us</a></li>
|
||||||
|
<li><a href="/" class="text-decoration-none text-muted small">Browse Catalog</a></li>
|
||||||
|
<li><a href="/contact" class="text-decoration-none text-muted small">Contact Us</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div style="max-width: 210px;">
|
||||||
|
<span class="text-dark fw-bold tracking-wider d-block mb-3" style="font-size: 0.65rem; letter-spacing: 1.2px;">ENTERPRISE</span>
|
||||||
|
<ul class="list-unstyled d-flex flex-column gap-2 m-0">
|
||||||
|
<li class="small text-muted d-flex align-items-center gap-1.5 mb-1" style="font-weight: 500;">
|
||||||
|
<svg width="12" height="12" 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="text-decoration-none text-muted small">Terms & Conditions</a></li>
|
||||||
|
<li><a href="/privacy" class="text-decoration-none text-muted small">Privacy Policy</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row align-items-center pt-4">
|
||||||
|
<div class="col-12 text-center text-md-start">
|
||||||
|
<span class="text-muted tracking-wide" style="font-size: 0.75rem; font-weight: 500;">
|
||||||
|
© @DateTime.Now.Year Midrand Books. All rights reserved.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string GlobalSearchQuery { get; set; } = string.Empty;
|
||||||
|
private bool IsSearchActive { get; set; } = false;
|
||||||
|
private bool IsCartOpen { get; set; } = false;
|
||||||
|
|
||||||
|
private List<CartItem> CartItems = new()
|
||||||
|
{
|
||||||
|
new CartItem { Id = 1, Title = "Letters from M/M (Paris)", Author = "M/M Paris", Price = 720, Quantity = 1 },
|
||||||
|
new CartItem { Id = 2, Title = "Daan Paans: Floating Signifiers", Author = "Daan Paans", Price = 540, Quantity = 1 },
|
||||||
|
new CartItem { Id = 3, Title = "Album Architectures, Maputo", Author = "Guedes Archive", Price = 350, Quantity = 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
private void ToggleGlobalSearch() => IsSearchActive = !IsSearchActive;
|
||||||
|
private void ToggleCart() => IsCartOpen = !IsCartOpen;
|
||||||
|
|
||||||
|
private void OnSearchInput(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
GlobalSearchQuery = e.Value?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangeQuantity(CartItem item, int delta)
|
||||||
|
{
|
||||||
|
item.Quantity += delta;
|
||||||
|
if (item.Quantity <= 0)
|
||||||
|
{
|
||||||
|
CartItems.Remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveFromCart(CartItem item) => CartItems.Remove(item);
|
||||||
|
private int GetCartTotal() => CartItems.Sum(item => item.Price * item.Quantity);
|
||||||
|
|
||||||
|
private void RedirectToCart()
|
||||||
|
{
|
||||||
|
IsCartOpen = false;
|
||||||
|
Navigation.NavigateTo("/cart");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RedirectToCheckout()
|
||||||
|
{
|
||||||
|
IsCartOpen = false;
|
||||||
|
Navigation.NavigateTo("/checkout");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CartItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Author { get; set; } = string.Empty;
|
||||||
|
public int Price { get; set; }
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,98 +1,84 @@
|
|||||||
.page {
|
/* --- Midrand Books Sliding Cart Extensions --- */
|
||||||
position: relative;
|
|
||||||
display: flex;
|
/* Dimmed backdrop background blur styling */
|
||||||
flex-direction: column;
|
.cart-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
.cart-overlay.is-visible {
|
||||||
flex: 1;
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide-out Sidebar Panel layout specification */
|
||||||
|
.cart-drawer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: -420px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 1050;
|
||||||
|
pointer-events: none; /* Block layout actions while hidden off-screen */
|
||||||
|
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.cart-drawer.is-open {
|
||||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
transform: translateX(-420px);
|
||||||
}
|
pointer-events: auto; /* Allow complete drawer clicks once slid forward */
|
||||||
|
}
|
||||||
|
|
||||||
.top-row {
|
/* FIXED: Prevent badge rendering from stealing button-down mouse highlights */
|
||||||
background-color: #f7f7f7;
|
.cart-badge {
|
||||||
border-bottom: 1px solid #d6d5d5;
|
position: absolute;
|
||||||
justify-content: flex-end;
|
top: 2px;
|
||||||
height: 3.5rem;
|
right: 2px;
|
||||||
|
background-color: #1A1A1A;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 0.62rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
/* Micro typography utility sizes */
|
||||||
white-space: nowrap;
|
.xx-small {
|
||||||
margin-left: 1.5rem;
|
font-size: 0.68rem;
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row ::deep a:first-child {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640.98px) {
|
|
||||||
.top-row {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 641px) {
|
/* Clean dashed divider lines for item items listing styling */
|
||||||
.page {
|
.border-bottom-dashed {
|
||||||
flex-direction: row;
|
border-bottom: 1px dashed rgba(0, 0, 0, 0.12);
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
width: 250px;
|
|
||||||
height: 100vh;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row.auth ::deep a:first-child {
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-row, article {
|
|
||||||
padding-left: 2rem !important;
|
|
||||||
padding-right: 1.5rem !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#blazor-error-ui {
|
/* Custom quantity container inline metrics layout structure */
|
||||||
color-scheme: light only;
|
.quantity-picker {
|
||||||
background: lightyellow;
|
padding: 2px 4px;
|
||||||
bottom: 0;
|
|
||||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: none;
|
|
||||||
left: 0;
|
|
||||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#blazor-error-ui .dismiss {
|
.quantity-picker button {
|
||||||
cursor: pointer;
|
font-size: 0.85rem;
|
||||||
position: absolute;
|
font-weight: 600;
|
||||||
right: 0.75rem;
|
line-height: 1;
|
||||||
top: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quantity-picker button:hover {
|
||||||
|
background-color: rgba(0,0,0,0.05);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,325 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
<PageTitle>Midrand Books</PageTitle>
|
<div class="container text-center text-hero-wrapper">
|
||||||
|
<h1 class="display-3 text-dark mb-3 px-2 master-headline">
|
||||||
|
Discover thoughtfully curated<br>books for every reader.
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mx-auto mb-0 sub-headline">
|
||||||
|
Explorations into books, reading culture, and the art of thoughtful curation from Midrand to the world.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>midrandbooks.co.za</h3>
|
<div class="container mb-5 px-md-5">
|
||||||
|
<div class="row align-items-center justify-content-between pb-3 g-3">
|
||||||
|
|
||||||
Welcome to the Midrand Books online bookstore! We are passionate about providing a wide selection of books to readers of all ages and interests. Whether you're looking for the latest bestsellers, classic literature, or educational resources, we have something for everyone.
|
<div class="col-12 col-md-8">
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-start">
|
||||||
|
@foreach (var category in MainCategories)
|
||||||
|
{
|
||||||
|
var catName = category;
|
||||||
|
<button class="btn btn-sm rounded-pill px-3 py-1-5 fw-medium transition-smooth
|
||||||
|
@(ActiveCategory == catName ? "btn-dark" : "btn-outline-secondary text-dark border-light-subtle bg-white")"
|
||||||
|
@onclick="() => SelectCategory(catName)">
|
||||||
|
@catName
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<button class="btn btn-link text-muted btn-sm text-decoration-none fw-medium transition-smooth d-inline-flex align-items-center gap-1 p-1 ms-1"
|
||||||
|
@onclick="ToggleExtraCategories">
|
||||||
|
<span>@(ShowExpandedCategories ? "Show Less" : "See More")</span>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
|
||||||
|
style="transform: @(ShowExpandedCategories ? "rotate(180deg)" : "rotate(0deg)"); transition: transform 0.2s ease;">
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="d-flex align-items-center justify-content-start justify-content-md-end gap-2">
|
||||||
|
|
||||||
|
<button class="btn btn-sm rounded-pill px-3 py-1-5 fw-medium transition-smooth border border-light-subtle bg-white text-dark d-inline-flex align-items-center gap-2"
|
||||||
|
style="height: 32px; font-size: 0.8rem;"
|
||||||
|
@onclick="ToggleFilterMenu">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
||||||
|
</svg>
|
||||||
|
<span>Filter & Sort</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center bg-light p-1 rounded-pill border border-light-subtle" style="height: 32px;">
|
||||||
|
<button class="btn btn-sm rounded-circle p-0 d-flex align-items-center justify-content-center transition-smooth
|
||||||
|
@(CurrentViewMode == ViewMode.Grid ? "bg-white text-dark shadow-sm" : "text-muted border-0 bg-transparent")"
|
||||||
|
style="width: 24px; height: 24px;"
|
||||||
|
@onclick="() => SetViewMode(ViewMode.Grid)">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect width="7" height="7" x="3" y="3" rx="1" /><rect width="7" height="7" x="14" y="3" rx="1" /><rect width="7" height="7" x="14" y="14" rx="1" /><rect width="7" height="7" x="3" y="14" rx="1" /></svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm rounded-circle p-0 d-flex align-items-center justify-content-center transition-smooth
|
||||||
|
@(CurrentViewMode == ViewMode.List ? "bg-white text-dark shadow-sm" : "text-muted border-0 bg-transparent")"
|
||||||
|
style="width: 24px; height: 24px; margin-left: 2px;"
|
||||||
|
@onclick="() => SetViewMode(ViewMode.List)">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (ShowExpandedCategories)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-start pt-3 pb-2 border-top border-light-subtle mt-2 animate-fade-in">
|
||||||
|
@foreach (var category in DynamicExtendedCategories)
|
||||||
|
{
|
||||||
|
var catName = category;
|
||||||
|
<button class="btn btn-sm rounded-pill px-3 py-1-5 fw-medium transition-smooth
|
||||||
|
@(ActiveCategory == catName ? "btn-dark" : "btn-outline-secondary text-dark border-light-subtle bg-white")"
|
||||||
|
@onclick="() => SelectCategory(catName)">
|
||||||
|
@catName
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (ShowFilterMenu)
|
||||||
|
{
|
||||||
|
<div class="p-4 bg-light border border-light-subtle mt-3 animate-fade-in filter-dropdown-panel">
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<p class="text-dark font-monospace small fw-bold mb-2 opacity-50 panel-section-heading">SORT ORDER</p>
|
||||||
|
<div class="d-flex flex-column gap-1">
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(SelectedSortOption == "default" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangeSort("default")'>Curated Default</button>
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(SelectedSortOption == "price-low" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangeSort("price-low")'>Price: Low to High</button>
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(SelectedSortOption == "price-high" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangeSort("price-high")'>Price: High to Low</button>
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(SelectedSortOption == "title-asc" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangeSort("title-asc")'>Title: A-Z</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<p class="text-dark font-monospace small fw-bold mb-2 opacity-50 panel-section-heading">PRICE RANGE</p>
|
||||||
|
<div class="d-flex flex-column gap-1">
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(ActivePriceFilter == "all" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangePriceFilter("all")'>All Prices</button>
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(ActivePriceFilter == "under-500" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangePriceFilter("under-500")'>Under R 500</button>
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(ActivePriceFilter == "500-1000" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangePriceFilter("500-1000")'>R 500 – R 1,000</button>
|
||||||
|
<button type="button" class="btn btn-sm text-start py-1 px-2 rounded @(ActivePriceFilter == "over-1000" ? "fw-bold text-dark bg-white shadow-sm" : "text-muted bg-transparent border-0")" @onclick='() => ChangePriceFilter("over-1000")'>Over R 1,000</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<p class="text-dark font-monospace small fw-bold mb-2 opacity-50 panel-section-heading">RELEASE AVAILABILITY</p>
|
||||||
|
<div class="form-check form-switch pt-1 ms-1">
|
||||||
|
<input class="form-check-input style-track-switch" type="checkbox" id="newArrivalsToggle" checked="@OnlyShowNew" @onchange="ToggleNewArrivalsOnly" style="cursor: pointer;">
|
||||||
|
<label class="form-check-label small text-dark fw-medium ps-1" for="newArrivalsToggle" style="cursor: pointer;">Only New Acquisitions</label>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-link text-danger text-decoration-none mt-3 p-0 font-monospace reset-link-btn" @onclick="ResetFilters">RESET ALL FILTERS</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="position-relative w-100 custom-milled-line">
|
||||||
|
<div class="position-absolute start-50 translate-middle-x center-bloom-shadow"></div>
|
||||||
|
<div class="position-absolute w-100 core-horizontal-rule"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container px-md-5 pb-5">
|
||||||
|
|
||||||
|
@if (!PaginatedBooks.Any())
|
||||||
|
{
|
||||||
|
<div class="text-center text-muted py-5">
|
||||||
|
<p class="mb-0 small style-track" style="letter-spacing: 1px;">NO PRODUCTS MATCH YOUR TARGET SELECTION SPECIFICATIONS</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (CurrentViewMode == ViewMode.Grid)
|
||||||
|
{
|
||||||
|
<div class="row g-4 animate-fade-in">
|
||||||
|
@foreach (var book in PaginatedBooks)
|
||||||
|
{
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="card border-0 p-4 d-flex flex-column position-relative justify-content-between book-grid-card">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
@if (book.IsNew)
|
||||||
|
{
|
||||||
|
<span class="badge rounded-pill px-3 py-2 badge-new-arrival">New</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div></div>
|
||||||
|
}
|
||||||
|
<button class="btn bg-white rounded-circle d-flex align-items-center justify-content-center p-0 shadow-sm border-0" style="width: 32px; height: 32px;">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2.5"><line x1="7" y1="17" x2="17" y2="7" /><polyline points="7,7 17,7 17,17" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-center align-items-center my-auto py-3">
|
||||||
|
<div class="book-spine-fallback bg-dark d-flex align-items-center justify-content-center text-center p-4 text-white-50">
|
||||||
|
@book.Category.ToUpper()<br><span class="opacity-50" style="font-size:0.55rem;">EDITION</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-start mt-3 px-2">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-dark m-0 lh-sm product-card-title">@book.Title</h3>
|
||||||
|
<p class="text-muted m-0 mt-1 small" style="font-size: 0.8rem;">by @book.Author</p>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted fw-semibold" style="font-size: 0.95rem;">R @book.Price.ToString("N0")</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-column border-top border-light-subtle animate-fade-in">
|
||||||
|
@foreach (var book in PaginatedBooks)
|
||||||
|
{
|
||||||
|
<div class="d-flex align-items-center justify-content-between py-3 px-2 list-row-item">
|
||||||
|
<div class="d-flex align-items-center gap-4 structural-list-left">
|
||||||
|
<span class="text-dark fw-medium list-item-title">@book.Title</span>
|
||||||
|
<span class="text-muted small list-item-author">by @book.Author</span>
|
||||||
|
<span class="badge bg-light text-secondary border rounded-pill px-2.5 py-1 font-monospace list-item-tag">@book.Category.ToUpper()</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center gap-4">
|
||||||
|
@if (book.IsNew)
|
||||||
|
{
|
||||||
|
<span class="badge rounded-pill bg-danger-subtle text-danger px-2.5 py-1 list-new-badge">NEW</span>
|
||||||
|
}
|
||||||
|
<span class="text-dark font-monospace fw-medium list-item-price">R @book.Price.ToString("N0")</span>
|
||||||
|
<button class="btn btn-link text-dark p-1">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="7" y1="17" x2="17" y2="7" /><polyline points="7,7 17,7 17,17" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (HasMoreItems)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-column align-items-center justify-content-center mt-5 pt-4">
|
||||||
|
<button class="btn btn-outline-dark rounded-pill px-5 py-2.5 fw-medium shadow-sm" style="font-size: 0.85rem;" @onclick="LoadNextPage">
|
||||||
|
<span>Show More Artifacts</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public enum ViewMode { Grid, List }
|
||||||
|
private ViewMode CurrentViewMode = ViewMode.Grid;
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
public string SharedSearchQuery { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private string ActiveCategory = "All";
|
||||||
|
private bool ShowExpandedCategories = false;
|
||||||
|
private bool ShowFilterMenu = false;
|
||||||
|
|
||||||
|
private string SelectedSortOption = "default";
|
||||||
|
private string ActivePriceFilter = "all";
|
||||||
|
private bool OnlyShowNew = false;
|
||||||
|
|
||||||
|
private List<string> MainCategories = new() { "All", "Graphic Design", "Product Design", "Architecture" };
|
||||||
|
private List<string> DynamicExtendedCategories = new();
|
||||||
|
|
||||||
|
private int ItemsPerPage = 12;
|
||||||
|
private int VisibleCount = 12;
|
||||||
|
|
||||||
|
public class BookItem
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Author { get; set; } = string.Empty;
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
public string Category { get; set; } = string.Empty;
|
||||||
|
public bool IsNew { get; set; }
|
||||||
|
public string Isbn { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BookItem> BooksCollection = new();
|
||||||
|
|
||||||
|
private IEnumerable<BookItem> FilteredData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = BooksCollection.AsEnumerable();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(SharedSearchQuery))
|
||||||
|
{
|
||||||
|
var q = SharedSearchQuery.Trim();
|
||||||
|
data = data.Where(b =>
|
||||||
|
b.Title.Contains(q, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
b.Author.Contains(q, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
b.Isbn.Contains(q, StringComparison.OrdinalIgnoreCase)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActiveCategory != "All")
|
||||||
|
{
|
||||||
|
data = data.Where(b => b.Category.Equals(ActiveCategory, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OnlyShowNew) { data = data.Where(b => b.IsNew); }
|
||||||
|
|
||||||
|
data = ActivePriceFilter switch
|
||||||
|
{
|
||||||
|
"under-500" => data.Where(b => b.Price < 500),
|
||||||
|
"500-1000" => data.Where(b => b.Price >= 500 && b.Price <= 1000),
|
||||||
|
"over-1000" => data.Where(b => b.Price > 1000),
|
||||||
|
_ => data
|
||||||
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<BookItem> SortedAndFilteredBooks => SelectedSortOption switch
|
||||||
|
{
|
||||||
|
"price-low" => FilteredData.OrderBy(b => b.Price),
|
||||||
|
"price-high" => FilteredData.OrderByDescending(b => b.Price),
|
||||||
|
"title-asc" => FilteredData.OrderBy(b => b.Title),
|
||||||
|
_ => FilteredData
|
||||||
|
};
|
||||||
|
|
||||||
|
private IEnumerable<BookItem> PaginatedBooks => SortedAndFilteredBooks.Take(VisibleCount);
|
||||||
|
private int TotalFilteredCount => FilteredData.Count();
|
||||||
|
private bool HasMoreItems => VisibleCount < TotalFilteredCount;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
var extraSourceCategories = new[] { "Fine Arts", "Science", "Photography", "Typography", "Interior Design", "Industrialism", "Fashion", "Curation Studies" };
|
||||||
|
DynamicExtendedCategories.AddRange(extraSourceCategories);
|
||||||
|
|
||||||
|
BooksCollection.Add(new BookItem { Title = "Letters from M/M (Paris)", Author = "M/M Paris", Price = 720, Category = "Graphic Design", IsNew = true, Isbn = "9782915173" });
|
||||||
|
BooksCollection.Add(new BookItem { Title = "Daan Paans: Floating Signifiers", Author = "Daan Paans", Price = 540, Category = "Product Design", IsNew = true, Isbn = "9789492051" });
|
||||||
|
BooksCollection.Add(new BookItem { Title = "Album Architectures, Maputo", Author = "Guedes Archive", Price = 350, Category = "Architecture", IsNew = true, Isbn = "9780620751" });
|
||||||
|
|
||||||
|
var designPrefixes = new[] { "Minimalist", "Monolithic", "Architectural", "Japanese", "Scandinavian" };
|
||||||
|
var designNouns = new[] { "Structures", "Typologies", "Forms & Spaces", "Systems Matrix", "Graphic Ephemera" };
|
||||||
|
var designers = new[] { "J. Morrison", "K. Fujita", "Studio Bouroullec", "Es Devlin", "Kenya Hara" };
|
||||||
|
|
||||||
|
var entireCategoryPool = MainCategories.Concat(DynamicExtendedCategories).Where(c => c != "All").ToArray();
|
||||||
|
var random = new Random(42);
|
||||||
|
|
||||||
|
for (int i = 4; i <= 60; i++)
|
||||||
|
{
|
||||||
|
BooksCollection.Add(new BookItem
|
||||||
|
{
|
||||||
|
Title = $"{designPrefixes[random.Next(designPrefixes.Length)]} {designNouns[random.Next(designNouns.Length)]} (Vol. {random.Next(1, 4)})",
|
||||||
|
Author = designers[random.Next(designers.Length)],
|
||||||
|
Price = random.Next(25, 135) * 10,
|
||||||
|
Category = entireCategoryPool[random.Next(entireCategoryPool.Length)],
|
||||||
|
IsNew = random.NextDouble() > 0.7,
|
||||||
|
Isbn = $"978000000{i}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetViewMode(ViewMode targetMode) => CurrentViewMode = targetMode;
|
||||||
|
private void SelectCategory(string categoryName) { ActiveCategory = categoryName; VisibleCount = ItemsPerPage; }
|
||||||
|
private void ToggleExtraCategories() => ShowExpandedCategories = !ShowExpandedCategories;
|
||||||
|
private void ToggleFilterMenu() => ShowFilterMenu = !ShowFilterMenu;
|
||||||
|
private void ChangeSort(string sortOption) => SelectedSortOption = sortOption;
|
||||||
|
private void ChangePriceFilter(string priceBracket) { ActivePriceFilter = priceBracket; VisibleCount = ItemsPerPage; }
|
||||||
|
private void ToggleNewArrivalsOnly(ChangeEventArgs e) { OnlyShowNew = e.Value is bool b && b; VisibleCount = ItemsPerPage; }
|
||||||
|
private void ResetFilters() { SelectedSortOption = "default"; ActivePriceFilter = "all"; OnlyShowNew = false; VisibleCount = ItemsPerPage; }
|
||||||
|
private void LoadNextPage() { if (HasMoreItems) VisibleCount += ItemsPerPage; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-container.is-active {
|
||||||
|
max-width: 280px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-field {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
height: 32px;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Structural Layout Typography */
|
||||||
|
.branding-logo {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-hero-wrapper {
|
||||||
|
margin-top: 5rem;
|
||||||
|
margin-bottom: 3.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-headline {
|
||||||
|
letter-spacing: -1.5px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-headline {
|
||||||
|
max-width: 520px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
+119
-44
@@ -1,60 +1,135 @@
|
|||||||
html, body {
|
@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600&display=swap');
|
||||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
|
: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 */
|
||||||
}
|
}
|
||||||
|
|
||||||
a, .btn-link {
|
/* Global Reset & Core Variables Mapping */
|
||||||
color: #006bb7;
|
body {
|
||||||
|
background-color: var(--mb-bg);
|
||||||
|
color: var(--mb-text-dark);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
h1, h2, h3, .display-font {
|
||||||
color: #fff;
|
font-family: var(--font-heading);
|
||||||
background-color: #1b6ec2;
|
font-weight: 400;
|
||||||
border-color: #1861ac;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
/* Custom Navigation Pill Styling */
|
||||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.nav-pill-link {
|
||||||
padding-top: 1.1rem;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1:focus {
|
.nav-pill-link i {
|
||||||
outline: none;
|
width: 16px;
|
||||||
}
|
height: 16px;
|
||||||
|
stroke-width: 1.5;
|
||||||
.valid.modified:not([type=checkbox]) {
|
|
||||||
outline: 1px solid #26b050;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invalid {
|
|
||||||
outline: 1px solid #e50000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-message {
|
|
||||||
color: #e50000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blazor-error-boundary {
|
|
||||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
|
||||||
padding: 1rem 1rem 1rem 3.7rem;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blazor-error-boundary::after {
|
|
||||||
content: "An error has occurred."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.darker-border-checkbox.form-check-input {
|
/* Navigation Pill States */
|
||||||
border-color: #929292;
|
.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
.transition-smooth {
|
||||||
color: var(--bs-secondary-color);
|
transition: color 0.2s ease;
|
||||||
text-align: end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
.transition-smooth:hover {
|
||||||
text-align: start;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Polished White Metal Footer Enhancements */
|
||||||
|
footer .text-muted {
|
||||||
|
color: #555555 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .text-dark {
|
||||||
|
color: #1A1A1A !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer span.text-muted,
|
||||||
|
footer a.text-muted {
|
||||||
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||||
|
transition: color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a.text-muted:hover {
|
||||||
|
color: #1A1A1A !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user