diff --git a/MidrandBookshop/Components/App.razor b/MidrandBookshop/Components/App.razor index 4e4351f..1ad1d69 100644 --- a/MidrandBookshop/Components/App.razor +++ b/MidrandBookshop/Components/App.razor @@ -6,6 +6,7 @@ + diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor b/MidrandBookshop/Components/Layout/MainLayout.razor index 36ae911..8eb7703 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor +++ b/MidrandBookshop/Components/Layout/MainLayout.razor @@ -1,8 +1,9 @@ @inherits LayoutComponentBase @inject NavigationManager Navigation -
+
+ @* --- CART SYSTEM SIDE PANEL BACKDROP LAYER --- *@
@@ -76,9 +77,10 @@ }
-
- -
+ @* --- TOP FIXED LAYOUT AREA --- *@ + +
+ @* --- MAIN INDEPENDENT SCROLL LAYER --- *@ +
@Body
-
-
+
@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
+
+ +
+ + + + + + + + + + + + + + @if (orderHistory != null) + { + @foreach (var order in orderHistory) + { + + + + + + + + + + } + } + else + { + + + + } + +
Order IDTitleDateAddressStatusTotalInvoice
@order.OrderId + + @order.DisplayTitle + + @order.OrderDate.ToString("MMM dd, yyyy") + + + + + @order.ShippingAddressName + + + + @order.Status + + R @order.Total.ToString("N2") + +
Loading order history...
+
+
+ +
+
+
Saved Addresses
+ @if (!showAddForm && editingAddress == null) + { + + } +
+ + @if (showAddForm) + { +
+
+
New Address
+ +
+ + +
+ + +
+
+ + +
+
+ +
+
+ } + + @if (editingAddress != null) + { +
+
+
Edit Address
+ +
+ + +
+ + +
+
+ + +
+
+ +
+
+ } + + @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