-
@code {
diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor.css b/MidrandBookshop/Components/Layout/MainLayout.razor.css
index 1ec945d..2c6966e 100644
--- a/MidrandBookshop/Components/Layout/MainLayout.razor.css
+++ b/MidrandBookshop/Components/Layout/MainLayout.razor.css
@@ -1,14 +1,10 @@
-/* --- Midrand Books Sliding Cart Extensions --- */
-
-/* Ensure the navbar stays on top while scrolling */
.sticky-top {
- position: -webkit-sticky !important; /* Safari */
+ position: -webkit-sticky !important;
position: sticky !important;
top: 0 !important;
z-index: 1000 !important;
}
-/* Dimmed backdrop background blur styling */
.cart-overlay {
position: fixed;
top: 0;
@@ -24,12 +20,11 @@
transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
- .cart-overlay.is-visible {
- opacity: 1;
- pointer-events: auto;
- }
+.cart-overlay.is-visible {
+ opacity: 1;
+ pointer-events: auto;
+}
-/* Slide-out Sidebar Panel layout specification */
.cart-drawer {
position: fixed;
top: 0;
@@ -38,16 +33,15 @@
max-width: 400px;
height: 100vh;
z-index: 1050;
- pointer-events: none; /* Block layout actions while hidden off-screen */
+ 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; /* Allow complete drawer clicks once slid forward */
- }
+.cart-drawer.is-open {
+ transform: translateX(-420px);
+ pointer-events: auto;
+}
-/* FIXED: Prevent badge rendering from stealing button-down mouse highlights */
.cart-badge {
position: absolute;
top: 2px;
@@ -65,28 +59,135 @@
pointer-events: none;
}
-/* Micro typography utility sizes */
.xx-small {
font-size: 0.68rem;
}
-/* Clean dashed divider lines for item items listing styling */
.border-bottom-dashed {
border-bottom: 1px dashed rgba(0, 0, 0, 0.12);
}
-/* Custom quantity container inline metrics layout structure */
.quantity-picker {
padding: 2px 4px;
}
- .quantity-picker button {
- font-size: 0.85rem;
- font-weight: 600;
- line-height: 1;
- }
+.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%;
- }
+.quantity-picker button:hover {
+ background-color: rgba(0,0,0,0.05);
+ border-radius: 50%;
+}
+
+.custom-site-footer {
+ width: 100%;
+ 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;
+}
+
+.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 {
+ 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: none;
+ 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;
+}
+
+.btn-back-to-top:hover {
+ background-color: #333333;
+ transform: translateY(-2px);
+}
+
+.btn-back-to-top:active {
+ transform: translateY(0);
+}
+
+.scroll-container {
+ scroll-behavior: smooth;
+}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Home.razor b/MidrandBookshop/Components/Pages/Home.razor
index 2f01d63..06a0c4c 100644
--- a/MidrandBookshop/Components/Pages/Home.razor
+++ b/MidrandBookshop/Components/Pages/Home.razor
@@ -2,7 +2,7 @@
@rendermode InteractiveServer
@inject NavigationManager Navigation
-
+
Discover thoughtfully curated
books for every reader.
@@ -208,6 +208,16 @@
}
+
+
+
+
@code {
public enum ViewMode { Grid, List }
private ViewMode CurrentViewMode = ViewMode.Grid;
diff --git a/MidrandBookshop/Components/Pages/Home.razor.css b/MidrandBookshop/Components/Pages/Home.razor.css
index c3c5838..7e5cdc4 100644
--- a/MidrandBookshop/Components/Pages/Home.razor.css
+++ b/MidrandBookshop/Components/Pages/Home.razor.css
@@ -139,3 +139,34 @@
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;
+ }
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Profile.razor b/MidrandBookshop/Components/Pages/Profile.razor
new file mode 100644
index 0000000..0bf0e7a
--- /dev/null
+++ b/MidrandBookshop/Components/Pages/Profile.razor
@@ -0,0 +1,362 @@
+@page "/profile"
+
+
+
My Account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Order History
+
+
+
+
+
+
+ | Order ID |
+ Title |
+ Date |
+ Address |
+ Status |
+ Total |
+ Invoice |
+
+
+
+ @if (orderHistory != null)
+ {
+ @foreach (var order in orderHistory)
+ {
+
+ | @order.OrderId |
+
+
+ @order.DisplayTitle
+
+ |
+ @order.OrderDate.ToString("MMM dd, yyyy") |
+
+
+
+ @order.ShippingAddressName
+
+ |
+
+
+ @order.Status
+
+ |
+ R @order.Total.ToString("N2") |
+
+
+ |
+
+ }
+ }
+ else
+ {
+
+ | Loading order history... |
+
+ }
+
+
+
+
+
+
+
+
Saved Addresses
+ @if (!showAddForm && editingAddress == null)
+ {
+
+ }
+
+
+ @if (showAddForm)
+ {
+
+ }
+
+ @if (editingAddress != null)
+ {
+
+ }
+
+ @foreach (var addr in savedAddresses)
+ {
+
+
+
+
@addr.Name
+
@addr.Street, @addr.City, @addr.PostalCode
+
+ @if (addr.IsBilling)
+ {
+ [Billing]
+ }
+ @if (addr.IsShipping)
+ {
+ [Shipping]
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+
+
+
+
Profile Settings
+
+
+
Manage your password and profile data here.
+
+
+
+
+
+
+
+
+@code {
+ private bool showAddForm = false;
+ private AddressItem? editingAddress = null;
+ private string newAddressName = "";
+ private string newStreetAddress = "";
+ private string newCity = "";
+ private string newPostalCode = "";
+ private bool isBilling, isShipping;
+
+ private List
orderHistory = new()
+ {
+ new OrderItem { OrderId = "#MB-2026-9481", ProductId = "introduction-to-blazor", ProductTitle = "Introduction to Blazor WebAssembly Framework Development", OrderDate = new DateTime(2026, 5, 20), ShippingAddressName = "Home Address", Status = "Shipped", Total = 720.00 },
+ new OrderItem { OrderId = "#MB-2026-8712", ProductId = "mastering-css-isolation", ProductTitle = "Mastering CSS Isolation in Modern .NET Web Applications Architecture", OrderDate = new DateTime(2026, 4, 14), ShippingAddressName = "Midrand Books Warehouse", Status = "Delivered", Total = 890.00 }
+ };
+
+ private List savedAddresses = new()
+ {
+ new AddressItem { Id = 1, Name = "Home Address", Street = "12 Main Road", City = "Midrand", PostalCode = "1685", IsBilling = true, IsShipping = true, IsPrimary = true },
+ new AddressItem { Id = 2, Name = "Corporate Office", Street = "45 Challink Street", City = "Halfway House", PostalCode = "1682", IsBilling = true, IsShipping = false, IsPrimary = false },
+ new AddressItem { Id = 3, Name = "Midrand Books Warehouse", Street = "Unit 8, Corporate Park North", City = "Randjespark", PostalCode = "1683", IsBilling = false, IsShipping = true, IsPrimary = false }
+ };
+
+ private void DownloadInvoice(string orderId)
+ {
+ // Handle invoice downloading logic here
+ }
+
+ private void OpenAddForm()
+ {
+ editingAddress = null;
+ showAddForm = true;
+ }
+
+ private void SaveAddress()
+ {
+ if (!string.IsNullOrWhiteSpace(newAddressName) && !string.IsNullOrWhiteSpace(newStreetAddress))
+ {
+ var nextId = savedAddresses.Any() ? savedAddresses.Max(a => a.Id) + 1 : 1;
+ var newItem = new AddressItem
+ {
+ Id = nextId,
+ Name = newAddressName,
+ Street = newStreetAddress,
+ City = newCity,
+ PostalCode = newPostalCode,
+ IsBilling = isBilling,
+ IsShipping = isShipping,
+ IsPrimary = !savedAddresses.Any()
+ };
+ savedAddresses.Add(newItem);
+ ResetAddForm();
+ }
+ }
+
+ private void ResetAddForm()
+ {
+ newAddressName = "";
+ newStreetAddress = "";
+ newCity = "";
+ newPostalCode = "";
+ isBilling = false;
+ isShipping = false;
+ showAddForm = false;
+ }
+
+ private void StartEditing(AddressItem addr)
+ {
+ showAddForm = false;
+ editingAddress = new AddressItem
+ {
+ Id = addr.Id,
+ Name = addr.Name,
+ Street = addr.Street,
+ City = addr.City,
+ PostalCode = addr.PostalCode,
+ IsBilling = addr.IsBilling,
+ IsShipping = addr.IsShipping,
+ IsPrimary = addr.IsPrimary
+ };
+ }
+
+ private void UpdateAddress()
+ {
+ if (editingAddress != null)
+ {
+ var target = savedAddresses.FirstOrDefault(a => a.Id == editingAddress.Id);
+ if (target != null)
+ {
+ target.Name = editingAddress.Name;
+ target.Street = editingAddress.Street;
+ target.City = editingAddress.City;
+ target.PostalCode = editingAddress.PostalCode;
+ target.IsBilling = editingAddress.IsBilling;
+ target.IsShipping = editingAddress.IsShipping;
+ }
+ editingAddress = null;
+ }
+ }
+
+ private void CancelEditing()
+ {
+ editingAddress = null;
+ }
+
+ private void DeleteAddress(AddressItem addr)
+ {
+ if (editingAddress?.Id == addr.Id)
+ {
+ editingAddress = null;
+ }
+ savedAddresses.Remove(addr);
+ if (addr.IsPrimary && savedAddresses.Any())
+ {
+ savedAddresses.First().IsPrimary = true;
+ }
+ }
+
+ private void SetPrimary(AddressItem target, ChangeEventArgs e)
+ {
+ var isChecked = (bool)(e.Value ?? false);
+ if (isChecked)
+ {
+ foreach (var addr in savedAddresses)
+ {
+ addr.IsPrimary = (addr.Id == target.Id);
+ }
+ }
+ else
+ {
+ target.IsPrimary = false;
+ }
+ }
+
+ public class AddressItem
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } = "";
+ public string Street { get; set; } = "";
+ public string City { get; set; } = "";
+ public string PostalCode { get; set; } = "";
+ public bool IsBilling { get; set; }
+ public bool IsShipping { get; set; }
+ public bool IsPrimary { get; set; }
+ }
+
+ public class OrderItem
+ {
+ public string OrderId { get; set; } = "";
+ public string ProductId { get; set; } = "";
+ public string ProductTitle { get; set; } = "";
+ public DateTime OrderDate { get; set; }
+ public string ShippingAddressName { get; set; } = "";
+ public string Status { get; set; } = "";
+ public double Total { get; set; }
+
+ public string DisplayTitle
+ {
+ get
+ {
+ if (string.IsNullOrWhiteSpace(ProductTitle)) return "";
+ const int maxLength = 21; // Shifted slightly down from 25 to protect bounds against lower resolutions
+ return ProductTitle.Length <= maxLength
+ ? ProductTitle
+ : $"{ProductTitle.Substring(0, maxLength)}...";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Profile.razor.css b/MidrandBookshop/Components/Pages/Profile.razor.css
new file mode 100644
index 0000000..d0d1268
--- /dev/null
+++ b/MidrandBookshop/Components/Pages/Profile.razor.css
@@ -0,0 +1,178 @@
+::deep .container {
+ max-width: 1100px;
+}
+
+/* Navigation Layout overrides */
+.nav-pills .nav-link {
+ color: #6c757d;
+ border-radius: 0;
+ padding: 0.75rem 0;
+ font-weight: 500;
+ transition: all 0.2s ease;
+}
+
+ .nav-pills .nav-link.active {
+ background-color: transparent !important;
+ color: #1A1A1A;
+ border-bottom: 2px solid #1A1A1A;
+ }
+
+ .nav-pills .nav-link:hover:not(.active) {
+ color: #1A1A1A;
+ }
+
+/* Cards layout rules */
+.card {
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ border-radius: 0;
+}
+
+.address-card {
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
+}
+
+ .address-card:hover {
+ border-color: rgba(0, 0, 0, 0.16);
+ }
+
+/* Container Wrapper to Suppress the Scrollbar completely */
+.table-container-fixed {
+ width: 100%;
+ overflow-x: hidden; /* Hard disables horizontal scroll bar activation */
+}
+
+/* Global Table Typography - Reduced uniformly to keep items on a single line */
+.profile-table {
+ font-size: 0.78rem; /* Scaled down further to eliminate overflow bounds */
+ width: 100%;
+ table-layout: fixed; /* Fixes proportions to fit 100% parent container space */
+}
+
+ .profile-table tbody td {
+ padding-top: 0.85rem;
+ padding-bottom: 0.85rem;
+ }
+
+ .profile-table thead th {
+ background-color: #F9F9F9;
+ font-size: 0.7rem;
+ letter-spacing: 0.04rem;
+ }
+
+/* Compact Column Proportions */
+.col-order-id {
+ width: 115px;
+}
+
+.col-date {
+ width: 100px;
+}
+
+.col-total {
+ width: 85px;
+}
+
+.col-status {
+ width: 105px;
+}
+
+.col-invoice {
+ width: 65px;
+}
+
+.col-title {
+ width: auto; /* Takes shared residual space smoothly */
+}
+
+.col-address {
+ width: auto; /* Takes shared residual space smoothly */
+}
+
+/* Product link handling */
+.product-link {
+ color: #1A1A1A;
+ text-decoration: none;
+ border-bottom: 1px dashed transparent;
+ transition: border-color 0.2s ease;
+ display: inline-block;
+}
+
+ .product-link:hover {
+ border-color: #1A1A1A;
+ }
+
+/* Base Badge Settings */
+.badge {
+ font-size: 0.62rem;
+ letter-spacing: 0.5px;
+ padding: 0.4em 0.8em;
+ border-radius: 4px;
+ font-weight: 600;
+}
+
+/* Status Badge Palette Colors */
+.status-shipped {
+ background-color: #e3f2fd !important;
+ color: #0d6efd !important;
+ border: 1px solid rgba(13, 110, 253, 0.15);
+}
+
+.status-delivered {
+ background-color: #e8f5e9 !important;
+ color: #198754 !important;
+ border: 1px solid rgba(25, 135, 84, 0.15);
+}
+
+.badge-tag {
+ background-color: #f0f0f0 !important;
+ color: #4a4a4a !important;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+}
+
+/* Form Buttons */
+.btn-outline-dark {
+ border-radius: 50px;
+ border-width: 1px;
+}
+
+.btn-dark {
+ border-radius: 50px;
+}
+
+/* Action button configurations */
+.action-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ text-decoration: none;
+ transition: background-color 0.15s ease, transform 0.1s ease;
+}
+
+ .action-btn:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ .action-btn.text-danger:hover {
+ background-color: rgba(220, 53, 69, 0.1);
+ }
+
+ .action-btn:active {
+ transform: scale(0.95);
+ }
+
+/* Compact SVG Icons sizing */
+.svg-icon {
+ width: 15px;
+ height: 15px;
+ fill: currentColor;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.pointer-label {
+ cursor: pointer;
+ user-select: none;
+}
diff --git a/MidrandBookshop/wwwroot/app.css b/MidrandBookshop/wwwroot/app.css
index 37a58b9..6fc9a8d 100644
--- a/MidrandBookshop/wwwroot/app.css
+++ b/MidrandBookshop/wwwroot/app.css
@@ -13,8 +13,7 @@
--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 */
+ --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 */
}
/* Global Reset & Core Variables Mapping */
@@ -113,23 +112,3 @@ h1:focus, h2:focus, h3:focus, h4:focus, p:focus, div:focus, span:focus {
[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;
- }
diff --git a/MidrandBookshop/wwwroot/favicon.png b/MidrandBookshop/wwwroot/favicon.png
index 8422b59..7c6c23f 100644
Binary files a/MidrandBookshop/wwwroot/favicon.png and b/MidrandBookshop/wwwroot/favicon.png differ