diff --git a/MidrandBookshop/Components/App.razor b/MidrandBookshop/Components/App.razor index c39d234..1ad1d69 100644 --- a/MidrandBookshop/Components/App.razor +++ b/MidrandBookshop/Components/App.razor @@ -6,18 +6,20 @@ + - + - + + diff --git a/MidrandBookshop/Components/BookCard.razor b/MidrandBookshop/Components/BookCard.razor new file mode 100644 index 0000000..c3aec62 --- /dev/null +++ b/MidrandBookshop/Components/BookCard.razor @@ -0,0 +1,26 @@ +
+
+ +
+ New + +
+ +
+ @Title +
+ +
+
+ @Title + $@Price +
+
+ +@code { + [Parameter] public string Title { get; set; } = ""; + [Parameter] public decimal Price { get; set; } + [Parameter] public string BookImageUrl { get; set; } = ""; +} \ No newline at end of file diff --git a/MidrandBookshop/Components/BookCard.razor.css b/MidrandBookshop/Components/BookCard.razor.css new file mode 100644 index 0000000..49a2369 --- /dev/null +++ b/MidrandBookshop/Components/BookCard.razor.css @@ -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; +} diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor b/MidrandBookshop/Components/Layout/MainLayout.razor index b7d6acb..8eb7703 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor +++ b/MidrandBookshop/Components/Layout/MainLayout.razor @@ -1,13 +1,293 @@ @inherits LayoutComponentBase +@inject NavigationManager Navigation -
-
-
-

Midrand Books

+
+ + @* --- CART SYSTEM SIDE PANEL BACKDROP LAYER --- *@ +
+
+
+
+ YOUR CART (@CartItems.Sum(i => i.Quantity)) +
+
-
- @Body -
-
+
+ @if (!CartItems.Any()) + { +
+ + + + Your collection is empty. +
+ } + else + { +
+ @foreach (var item in CartItems) + { +
+
+ [ COVER ] +
+
+
@item.Title
+

by @item.Author

+
+
+ + @item.Quantity + +
+ R @(item.Price * item.Quantity) +
+
+ +
+ } +
+ } +
+ + @if (CartItems.Any()) + { + + } +
+ + @* --- TOP FIXED LAYOUT AREA --- *@ +
+ @* Decorative Background SVG Watermark Line Graphic *@ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + @* --- MAIN INDEPENDENT SCROLL LAYER --- *@ +
+
+ + @Body + +
+ + +
+ +@code { + private string GlobalSearchQuery { get; set; } = string.Empty; + private bool IsSearchActive { get; set; } = false; + private bool IsCartOpen { get; set; } = false; + + private List 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; } + } +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor.css b/MidrandBookshop/Components/Layout/MainLayout.razor.css index 38d1f25..2c6966e 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor.css +++ b/MidrandBookshop/Components/Layout/MainLayout.razor.css @@ -1,98 +1,193 @@ -.page { - position: relative; - display: flex; - flex-direction: column; +.sticky-top { + position: -webkit-sticky !important; + position: sticky !important; + top: 0 !important; + z-index: 1000 !important; } -main { - flex: 1; +.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); } -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +.cart-overlay.is-visible { + opacity: 1; + pointer-events: auto; } -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; +.cart-drawer { + position: fixed; + top: 0; + right: -420px; + width: 100%; + max-width: 400px; + height: 100vh; + z-index: 1050; + pointer-events: none; + transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); +} + +.cart-drawer.is-open { + transform: translateX(-420px); + pointer-events: auto; +} + +.cart-badge { + position: absolute; + top: 2px; + right: 2px; + background-color: #1A1A1A; + color: #FFF; + font-size: 0.62rem; + font-weight: 700; + border-radius: 50%; + width: 15px; + height: 15px; display: flex; align-items: center; + justify-content: center; + pointer-events: none; } - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - 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; - } +.xx-small { + font-size: 0.68rem; } -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .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; - } +.border-bottom-dashed { + border-bottom: 1px dashed rgba(0, 0, 0, 0.12); } -#blazor-error-ui { - color-scheme: light only; - background: lightyellow; - 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; +.quantity-picker { + padding: 2px 4px; +} + +.quantity-picker button { + font-size: 0.85rem; + font-weight: 600; + line-height: 1; +} + +.quantity-picker button:hover { + background-color: rgba(0,0,0,0.05); + border-radius: 50%; +} + +.custom-site-footer { width: 100%; - z-index: 1000; + 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; } - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } +.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/Cart.razor b/MidrandBookshop/Components/Pages/Cart.razor new file mode 100644 index 0000000..a3de54b --- /dev/null +++ b/MidrandBookshop/Components/Pages/Cart.razor @@ -0,0 +1,106 @@ +@page "/cart" + +
+
+

Your Cart

+ CONTINUE SHOPPING +
+ + @if (!CartItems.Any()) + { +
+

Your collection is currently empty.

+ Browse Catalog +
+ } + else + { +
+ @foreach (var item in CartItems) + { +
+ +
+
+ [COVER] +
+
+
@item.Title
+

by @item.Author

+
+
+ + +
+
+ + @item.Quantity + +
+
+ + +
+ R @(item.Price * item.Quantity) + +
+
+ } +
+ + +
+
+
+
+ Subtotal + R @Subtotal.ToString("F2") +
+
+ VAT (15%) + R @VatAmount.ToString("F2") +
+
+
+ Total + R @Total.ToString("F2") +
+ Proceed to Checkout +
+
+
+ } +
+ +@code { + 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; } + } + + private List 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 } + }; + + // Computed Properties for Calculations + private decimal Subtotal => CartItems.Sum(i => (decimal)i.Price * i.Quantity); + private decimal VatAmount => Subtotal * 0.15m; + private decimal Total => Subtotal + VatAmount; + + 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); +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Checkout.razor b/MidrandBookshop/Components/Pages/Checkout.razor new file mode 100644 index 0000000..1b307ee --- /dev/null +++ b/MidrandBookshop/Components/Pages/Checkout.razor @@ -0,0 +1,110 @@ +@page "/checkout" +@inject NavigationManager Navigation + +
+

Checkout

+
+ + +
+ +
+
Your Items
+ @foreach (var item in CartItems) + { +
+
@item.Title
@item.Author
+
+
+ + @item.Quantity + +
+ +
+
+ } +
+ + +
+
Shipping Method
+
+ + +
+
+ + +
+
+ + +
+
Shipping Address
+
+ + +
+ +
+
+ + +
+
+
Order Summary
+
SubtotalR @Subtotal.ToString("F2")
+
VAT (15%)R @VatAmount.ToString("F2")
+
ShippingR @ShippingCost.ToString("F2")
+
+
+ Total Due +

R @((Subtotal + VatAmount + ShippingCost).ToString("F2"))

+
+ +
+
+
+
+ +@code { + private decimal ShippingCost = 0; + private bool IsSameAddress = true; + + // Calculations + private decimal Subtotal => CartItems.Sum(i => (decimal)i.Price * i.Quantity); + private decimal VatAmount => Subtotal * 0.15m; + + // Assuming your CartItems list is managed via a Service or cascading parameter + // Here it is locally mocked for this example + private List 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 } + }; + + 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(i => i.Price * i.Quantity); + + public class CartItem + { + public int Id { get; set; } + public string Title { get; set; } = ""; + public string Author { get; set; } = ""; + public int Price { get; set; } + public int Quantity { get; set; } + } + private void CompletePurchase(MouseEventArgs args) + { + Navigation.NavigateTo("/payment-confirmation"); + } +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Error.razor b/MidrandBookshop/Components/Pages/Error.razor index 576cc2d..a796fd0 100644 --- a/MidrandBookshop/Components/Pages/Error.razor +++ b/MidrandBookshop/Components/Pages/Error.razor @@ -1,36 +1,49 @@ @page "/Error" @using System.Diagnostics +@inject NavigationManager Navigation -Error +System Exception | Midrand Books -

Error.

-

An error occurred while processing your request.

+
+
+
-@if (ShowRequestId) -{ -

- Request ID: @RequestId -

-} +
+ + + + + + +
-

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

+
+
System Exception // Critical Halt
-@code{ - [CascadingParameter] - private HttpContext? HttpContext { get; set; } +

An unexpected
interruption.

+

+ We are experiencing a technical disruption in our processing stream. Our team has been notified of this structural anomaly. +

+ + @if (ShowRequestId) + { +

+ Ref: @RequestId +

+ } + + +
+
+
+
+ +@code { + [CascadingParameter] private HttpContext? HttpContext { get; set; } private string? RequestId { get; set; } private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); protected override void OnInitialized() => RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; -} +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Error.razor.css b/MidrandBookshop/Components/Pages/Error.razor.css new file mode 100644 index 0000000..c56ee2c --- /dev/null +++ b/MidrandBookshop/Components/Pages/Error.razor.css @@ -0,0 +1,37 @@ +.artistic-error-container { + background-color: #ffffff; + min-height: 70vh; + display: flex; + align-items: center; + padding: 5rem 0; +} + +.editorial-headline { + font-family: 'Playfair Display', serif; + font-size: 3.5rem; + line-height: 1.1; + letter-spacing: -0.02em; + color: #111111; +} + +.body-manifesto-text { + font-size: 1.1rem; + max-width: 400px; + line-height: 1.7; +} + +.meta-tag { + font-size: 0.75rem; + letter-spacing: 0.2em; + color: #dc3545; /* Subtle red for system status */ +} + +.btn-outline-dark { + border: 1px solid #111; + transition: all 0.3s ease; +} + + .btn-outline-dark:hover { + background-color: #111; + color: #fff; + } diff --git a/MidrandBookshop/Components/Pages/Home.razor b/MidrandBookshop/Components/Pages/Home.razor index a4ad784..06a0c4c 100644 --- a/MidrandBookshop/Components/Pages/Home.razor +++ b/MidrandBookshop/Components/Pages/Home.razor @@ -1,7 +1,349 @@ @page "/" +@rendermode InteractiveServer +@inject NavigationManager Navigation -Midrand Books +
+

+ Discover thoughtfully curated
books for every reader. +

+

+ Explorations into books, reading culture, and the art of thoughtful curation from Midrand to the world. +

+
-

midrandbooks.co.za

+
+
-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. +
+
+ @foreach (var category in MainCategories) + { + var catName = category; + + } + + +
+
+ +
+
+ + + +
+ + +
+ +
+
+
+ + @if (ShowExpandedCategories) + { +
+ @foreach (var category in DynamicExtendedCategories) + { + var catName = category; + + } +
+ } + + @if (ShowFilterMenu) + { +
+
+
+

SORT ORDER

+
+ + + + +
+
+
+

PRICE RANGE

+
+ + + + +
+
+
+

RELEASE AVAILABILITY

+
+ + +
+ +
+
+
+ } + +
+
+
+
+
+ +
+ + @if (!PaginatedBooks.Any()) + { +
+

NO PRODUCTS MATCH YOUR TARGET SELECTION SPECIFICATIONS

+
+ } + else if (CurrentViewMode == ViewMode.Grid) + { +
+ @foreach (var book in PaginatedBooks) + { +
+
+
+ @if (book.IsNew) + { + New + } + else + { +
+ } + +
+
+
+ @book.Category.ToUpper()
EDITION +
+
+
+
+
+

@book.Title

+

by @book.Author

+
+ R @book.Price.ToString("N0") +
+
+ } +
+ } + else + { +
+ @foreach (var book in PaginatedBooks) + { +
+
+ @book.Title + by @book.Author + @book.Category.ToUpper() +
+
+ @if (book.IsNew) + { + NEW + } + R @book.Price.ToString("N0") + +
+
+ } +
+ } + + @if (HasMoreItems) + { +
+ +
+ } +
+ + + + + + + + +@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 MainCategories = new() { "All", "Graphic Design", "Product Design", "Architecture" }; + private List DynamicExtendedCategories = new(); + + private int ItemsPerPage = 12; + private int VisibleCount = 12; + + public class BookItem + { + public long Id { get; set; } // Refactored to hold unique record indices of type long + 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 BooksCollection = new(); + + private IEnumerable 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 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 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); + + // Updated mock items to supply long IDs matching your screenshot items + BooksCollection.Add(new BookItem { Id = 1L, Title = "Letters from M/M (Paris)", Author = "M/M Paris", Price = 720, Category = "Graphic Design", IsNew = true, Isbn = "9782915173" }); + BooksCollection.Add(new BookItem { Id = 2L, Title = "Daan Paans: Floating Signifiers", Author = "Daan Paans", Price = 540, Category = "Product Design", IsNew = true, Isbn = "9789492051" }); + BooksCollection.Add(new BookItem { Id = 3L, 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 + { + Id = (long)i, + 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}" + }); + } + } + + // Handles the explicit page transition routing + private void NavigateToProduct(long id) + { + Navigation.NavigateTo($"/product/{id}"); + } + + 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; } +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Home.razor.css b/MidrandBookshop/Components/Pages/Home.razor.css new file mode 100644 index 0000000..7e5cdc4 --- /dev/null +++ b/MidrandBookshop/Components/Pages/Home.razor.css @@ -0,0 +1,172 @@ +/* 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; +} + +/* 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/NotFound.razor b/MidrandBookshop/Components/Pages/NotFound.razor index 917ada1..ce1740d 100644 --- a/MidrandBookshop/Components/Pages/NotFound.razor +++ b/MidrandBookshop/Components/Pages/NotFound.razor @@ -1,5 +1,35 @@ @page "/not-found" -@layout MainLayout +@inject NavigationManager Navigation -

Not Found

-

Sorry, the content you are looking for does not exist.

\ No newline at end of file +
+
+
+ +
+
+ + + + + + + + + + + + +
+
+ +
+
System Index // Uncharted Territory
+

Nothing was found
in this search.

+

+ Even with careful scrutiny, the requested volume remains elusive. It appears the shelf you are exploring is currently vacant. +

+ +
+
+
+
\ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/NotFound.razor.css b/MidrandBookshop/Components/Pages/NotFound.razor.css new file mode 100644 index 0000000..a372b49 --- /dev/null +++ b/MidrandBookshop/Components/Pages/NotFound.razor.css @@ -0,0 +1,76 @@ +.artistic-404-container { + background-color: #ffffff; + min-height: 70vh; + display: flex; + align-items: center; + padding: 5rem 0; +} + +.terrain-wrapper { + display: flex; + justify-content: center; + align-items: center; + /* Static, grounded look */ + border-bottom: 2px solid #f0f0f0; + padding-bottom: 2rem; +} + +.editorial-headline { + font-family: 'Playfair Display', serif; + font-size: 3.5rem; + line-height: 1.1; + letter-spacing: -0.02em; + color: #111111; +} + +.body-manifesto-text { + font-size: 1.1rem; + max-width: 400px; + line-height: 1.7; +} + +.meta-tag { + font-size: 0.75rem; + letter-spacing: 0.2em; +} + +.btn-dark { + transition: background 0.3s ease; +} + + .btn-dark:hover { + background: #444; + } + +.artistic-404-container { + background-color: #ffffff; + min-height: 70vh; + display: flex; + align-items: center; + padding: 5rem 0; +} + +.artistic-svg-wrapper { + display: flex; + justify-content: center; + align-items: center; +} + +.editorial-headline { + font-family: 'Playfair Display', serif; + font-size: 3.5rem; + line-height: 1.1; + letter-spacing: -0.02em; + color: #111111; +} + +.body-manifesto-text { + font-size: 1.1rem; + max-width: 400px; + line-height: 1.7; +} + +.meta-tag { + font-size: 0.75rem; + letter-spacing: 0.2em; +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/PaymentConfirmation.razor b/MidrandBookshop/Components/Pages/PaymentConfirmation.razor new file mode 100644 index 0000000..555af5f --- /dev/null +++ b/MidrandBookshop/Components/Pages/PaymentConfirmation.razor @@ -0,0 +1,40 @@ +@page "/payment-confirmation" + +
+
+
+ + +
+
+ + + + +
+

Order Confirmed

+

Thank you for shopping with Midrand Books. Your order has been received and is being processed.

+
+

Order Number

+
#MB-2026-8834
+
+
+ + + + + +

You will receive a confirmation email shortly at user@email.com.

+
+
+
\ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Product.razor b/MidrandBookshop/Components/Pages/Product.razor new file mode 100644 index 0000000..aca17d0 --- /dev/null +++ b/MidrandBookshop/Components/Pages/Product.razor @@ -0,0 +1,137 @@ +@page "/product/{BookId:long}" +@inject NavigationManager Navigation + +
+ + +
+ + +
+
+ @AuthorName +
+ @for (int i = 1; i <= 5; i++) + { + + } + (@CurrentRating.ToString("F1")) +
+
+ +

@BookTitle

+
R @Price.ToString("N2")
+ +
+
+ + @Quantity + +
+ +
+ +
+ +
+

Description

+

@BookDescription

+
+ +
+

About the Author

+

@AuthorBio

+ +
+
+
+
+ +@code { + [Parameter] public long BookId { get; set; } + + // Mock State - In production, pull these via a Service using BookId inside OnInitialized + private string BookTitle { get; set; } = "Letters from M/M (Paris)"; + private string AuthorName { get; set; } = "M/M Paris"; + private string AuthorBio { get; set; } = "M/M Paris is an art and design partnership consisting of Michaël Amzalag and Mathias Augustyniak, established in 1992. Renowned globally for their influence on fashion, music, and contemporary art layout structures."; + private string BookDescription { get; set; } = "An exquisite archive tracking visual graphics, typography, and structural design curation over three decades. Beautifully bound with matte-coated plates and custom layouts."; + private decimal Price { get; set; } = 720.00m; + + // Dynamic Ratings State + private double CurrentRating { get; set; } = 4.7; + + // Formats supported + private bool IsPhysicalBook { get; set; } = true; + private bool IsEBook { get; set; } = true; + private bool CanReadOnline { get; set; } = false; + + // Image Caching Gallery State + private string ActiveImageUrl { get; set; } = "images/book-cover-large.png"; + private List Thumbnails { get; set; } = new() + { + "images/book-cover-large.png", + "images/book-inside-1.png", + "images/book-inside-2.png" + }; + + private int Quantity { get; set; } = 1; + + protected override void OnInitialized() + { + // Default the gallery viewer context logic + if (Thumbnails.Any()) + { + ActiveImageUrl = Thumbnails.First(); + } + } + + private void IncreaseQty() => Quantity++; + private void DecreaseQty() { if (Quantity > 1) Quantity--; } + + private void HandleAddToCart() + { + // Event logic hooked into your structural state layout + } + + private void ViewAllAuthorBooks() + { + Navigation.NavigateTo($"/catalog?author={Uri.EscapeDataString(AuthorName)}"); + } +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Product.razor.css b/MidrandBookshop/Components/Pages/Product.razor.css new file mode 100644 index 0000000..1e64af7 --- /dev/null +++ b/MidrandBookshop/Components/Pages/Product.razor.css @@ -0,0 +1,294 @@ +.product-container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem 1.5rem; + font-family: system-ui, -apple-system, sans-serif; +} + +/* Breadcrumbs */ +.breadcrumb { + font-size: 0.85rem; + margin-bottom: 2.5rem; + color: #8c8c8c; +} + +.crumb-link { + cursor: pointer; + transition: color 0.2s; +} + + .crumb-link:hover { + color: #111; + } + +.crumb-separator { + margin: 0 0.5rem; +} + +.crumb-current { + color: #111; + font-weight: 500; +} + +/* Two-Column Grid Layout Structure */ +.product-layout { + display: grid; + grid-template-columns: 1.1fr 0.9fr; + gap: 4rem; +} + +@media (max-width: 992px) { + .product-layout { + grid-template-columns: 1fr; + gap: 2.5rem; + } +} + +/* Left Section: Visual Images/Thumbnails Layout */ +.gallery-section { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.main-image-wrapper { + position: relative; + background-color: #f9f9f9; + border-radius: 8px; + padding: 3rem; + display: flex; + justify-content: center; + align-items: center; + min-height: 480px; +} + +.main-image { + max-height: 450px; + object-fit: contain; + mix-blend-mode: multiply; +} + +/* Format Badges Overlay Styles */ +.format-badges { + position: absolute; + top: 1rem; + left: 1rem; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.badge { + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 0.35rem 0.75rem; + border-radius: 100px; + font-weight: 600; + border: 1px solid transparent; +} + +.badge-physical { + background-color: #111; + color: #fff; +} + +.badge-ebook { + background-color: #fff; + color: #111; + border-color: #e5e5e5; +} + +.badge-online { + background-color: #f0fdf4; + color: #166534; + border-color: #bbf7d0; +} + +/* Thumbnails container */ +.thumbnail-grid { + display: flex; + gap: 1rem; +} + +.thumbnail-wrapper { + width: 80px; + height: 80px; + border: 1px solid #e5e5e5; + border-radius: 6px; + padding: 0.5rem; + cursor: pointer; + background: #f9f9f9; + transition: all 0.2s ease; +} + + .thumbnail-wrapper:hover, .thumbnail-wrapper.active { + border-color: #111; + } + + .thumbnail-wrapper img { + width: 100%; + height: 100%; + object-fit: contain; + mix-blend-mode: multiply; + } + +/* Right Section: Core Text Typography Controls */ +.details-section { + display: flex; + flex-direction: column; +} + +.meta-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} + +.author-name { + font-size: 1.1rem; + color: #666; +} + +/* Dynamic Stars C# rendering mapping */ +.rating-stars { + display: flex; + align-items: center; + gap: 0.2rem; +} + +.star { + color: #e5e5e5; + font-size: 1.1rem; +} + + .star.filled { + color: #111; /* Solid black stars fits your clean aesthetic perfectly */ + } + +.rating-text { + font-size: 0.85rem; + color: #666; + margin-left: 0.4rem; +} + +.product-title { + font-size: 2.5rem; + font-family: 'Playfair Display', serif, Georgia; /* Fits the luxury typography tone */ + font-weight: 400; + line-height: 1.2; + margin-bottom: 1rem; + color: #111; +} + +.product-price { + font-size: 1.5rem; + font-weight: 500; + margin-bottom: 2rem; + color: #111; +} + +/* Standard E-commerce Action Bar Block */ +.purchase-actions { + display: flex; + gap: 1rem; + margin-bottom: 2.5rem; +} + +.quantity-picker { + display: flex; + align-items: center; + border: 1px solid #e5e5e5; + border-radius: 100px; + padding: 0.25rem 0.5rem; +} + +.qty-btn { + background: none; + border: none; + width: 36px; + height: 36px; + font-size: 1.2rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background-color 0.2s; +} + + .qty-btn:hover { + background-color: #f5f5f5; + } + +.qty-val { + min-width: 30px; + text-align: center; + font-weight: 500; +} + +.btn-add-to-cart { + flex-grow: 1; + background-color: #111; + color: #fff; + border: none; + border-radius: 100px; + font-weight: 500; + letter-spacing: 0.02em; + cursor: pointer; + transition: background-color 0.2s; +} + + .btn-add-to-cart:hover { + background-color: #2a2a2a; + } + +.divider { + border: 0; + border-top: 1px solid #eee; + margin-bottom: 2rem; +} + +/* General Layout Text and Cross-Selling */ +.info-block { + margin-bottom: 2rem; +} + + .info-block h3 { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #8c8c8c; + margin-bottom: 0.75rem; + } + +.description-text, .author-bio { + font-size: 0.95rem; + line-height: 1.6; + color: #444; +} + +.author-bio-card { + background-color: #fafafa; + padding: 1.5rem; + border-radius: 8px; + border: 1px solid #f0f0f0; +} + +.btn-text-link { + background: none; + border: none; + padding: 0; + margin-top: 1rem; + font-weight: 600; + font-size: 0.9rem; + color: #111; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 4px; +} + + .btn-text-link:hover { + color: #444; + } 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/Components/Routes.razor b/MidrandBookshop/Components/Routes.razor index 105855d..71c38b1 100644 --- a/MidrandBookshop/Components/Routes.razor +++ b/MidrandBookshop/Components/Routes.razor @@ -1,6 +1,12 @@ - +@using MidrandBookshop.Components.Pages + - - + + - + + + + + + \ No newline at end of file diff --git a/MidrandBookshop/wwwroot/app.css b/MidrandBookshop/wwwroot/app.css index 73a69d6..6fc9a8d 100644 --- a/MidrandBookshop/wwwroot/app.css +++ b/MidrandBookshop/wwwroot/app.css @@ -1,60 +1,114 @@ -html, body { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600&display=swap'); + +:root { + /* Fonts */ + --font-heading: 'Instrument Serif', serif; + --font-ui: 'Inter', sans-serif; + /* Color Palette */ + --mb-bg: #F9F9F9; + --mb-card-bg: #FFFFFF; + --mb-text-dark: #1A1A1A; + --mb-text-muted: #666666; + --mb-accent-red: #E63946; + --mb-radius: 12px; + /* High-Visibility Machined White Metal Core Surface Definition */ + /* Sharp linear reflection channels paired with micro-milled density grids */ + --brushed-metal-bg: linear-gradient(90deg, rgba(255, 255, 255, 1) 0%, rgba(240, 241, 245, 0.95) 25%, rgba(255, 255, 255, 1) 50%, rgba(238, 240, 244, 0.95) 75%, rgba(255, 255, 255, 1) 100% ), repeating-linear-gradient( 0deg, rgba(0, 0, 0, 0.012) 0px, rgba(0, 0, 0, 0.012) 1px, transparent 1px, transparent 2px ), repeating-linear-gradient( 90deg, rgba(255, 255, 255, 0.9) 0px, rgba(255, 255, 255, 0.9) 1px, transparent 1px, transparent 3px ), #EDEFF4; /* Definitive satin platinum breakout base fallback */ } -a, .btn-link { - color: #006bb7; +/* Global Reset & Core Variables Mapping */ +body { + background-color: var(--mb-bg); + color: var(--mb-text-dark); + font-family: var(--font-ui); + -webkit-font-smoothing: antialiased; } -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; +h1, h2, h3, .display-font { + font-family: var(--font-heading); + font-weight: 400; } -.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { - box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +/* Custom Navigation Pill Styling */ +.nav-pill-wrapper { + background-color: #FFFFFF; + border: 1px solid rgba(0, 0, 0, 0.06); + padding: 6px; + border-radius: 50px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02); } -.content { - padding-top: 1.1rem; +.nav-pill-link { + font-size: 0.9rem; + font-weight: 500; + color: var(--mb-text-muted); + padding: 8px 20px; + border-radius: 50px; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + display: inline-flex; + align-items: center; + gap: 8px; + text-decoration: none; } -h1:focus { - outline: none; -} - -.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." + .nav-pill-link i { + width: 16px; + height: 16px; + stroke-width: 1.5; } -.darker-border-checkbox.form-check-input { - border-color: #929292; + /* Navigation Pill States */ + .nav-pill-link:hover { + color: var(--mb-text-dark); + background-color: #F1F1F1; + } + + .nav-pill-link.active { + color: #FFFFFF !important; + background-color: var(--mb-text-dark); + } + + .nav-pill-link.active svg { + stroke: #FFFFFF; + } + +/* Utilities & Component Support Layouts */ +.cart-badge { + position: absolute; + top: -5px; + right: -5px; + background-color: var(--mb-text-dark); + color: #FFFFFF; + font-size: 0.7rem; + font-weight: 600; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid var(--mb-bg); } -.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { - color: var(--bs-secondary-color); - text-align: end; +.transition-smooth { + transition: color 0.2s ease; } -.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { - text-align: start; -} \ No newline at end of file + .transition-smooth:hover { + color: var(--mb-text-dark) !important; + } + +.py-1-5 { + padding-top: 0.35rem !important; + padding-bottom: 0.35rem !important; +} + +/* Global Focus Ring Corrections */ +h1:focus, h2:focus, h3:focus, h4:focus, p:focus, div:focus, span:focus { + outline: none !important; + box-shadow: none !important; +} + +[tabindex="-1"]:focus { + outline: none !important; +} 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