feat: Refactor dashboard screens to Modern Deep Dark style #74

Merged
mjasin merged 2 commits from feature/modern-dark-dashboard into develop 2026-06-06 07:59:00 +00:00
13 changed files with 959 additions and 272 deletions
@@ -57,10 +57,10 @@
</div>
<div class="empty-text">
<h3>Brak aktywnych lektur</h3>
<p>Przejdź do biblioteki, aby rozpocząć przygodę z Nexus Reader.</p>
<p>Przejdź do katalogu lub swoich książek, aby rozpocząć przygodę z Nexus Reader.</p>
</div>
<button class="btn-nexus primary" @onclick='() => NavigationManager.NavigateTo("/library")'>
Przejdź do Biblioteki
<button class="btn-nexus primary" @onclick='() => NavigationManager.NavigateTo("/my-books")'>
Przejdź do Moich Książek
</button>
</div>
}
@@ -2,6 +2,17 @@
width: 100%;
padding: 2rem;
overflow: hidden;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.current-reading-card:hover {
background: #1e1e24;
border-color: rgba(16, 185, 129, 0.2);
transform: translateY(-4px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.card-layout {
@@ -55,7 +66,7 @@
.author-name {
font-size: 0.9rem;
color: #A0A0A0;
color: #a1a1aa;
font-weight: 500;
}
@@ -80,7 +91,7 @@
}
.percentage {
color: var(--nexus-neon);
color: #10b981;
}
.progress-bar-container {
@@ -92,8 +103,8 @@
.progress-bar-fill {
height: 100%;
background: var(--nexus-neon);
box-shadow: 0 0 10px rgba(0, 255, 153, 0.4);
background: #10b981;
box-shadow: 0 0 10px rgba(16, 185, 129, 0.4);
border-radius: 100px;
transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
}
@@ -101,7 +112,7 @@
.book-excerpt {
font-size: 0.95rem;
line-height: 1.6;
color: #B0B0B0;
color: #a1a1aa;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 3;
@@ -134,26 +145,26 @@
.btn-nexus.outline {
background: transparent;
color: var(--nexus-neon);
border: 1px solid rgba(0, 255, 153, 0.3);
color: #10b981;
border: 1px solid rgba(16, 185, 129, 0.3);
}
.btn-nexus.outline:hover {
background: rgba(0, 255, 153, 0.05);
border-color: var(--nexus-neon);
background: rgba(16, 185, 129, 0.05);
border-color: #10b981;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 255, 153, 0.1);
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.1);
}
.btn-nexus.primary {
background: var(--nexus-neon);
background: #10b981;
color: #000;
}
.btn-nexus.primary:hover {
transform: translateY(-2px);
filter: brightness(1.1);
box-shadow: 0 5px 15px rgba(0, 255, 153, 0.2);
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.2);
}
/* Empty State */
@@ -168,9 +179,9 @@
}
.empty-icon {
color: var(--nexus-neon);
color: #10b981;
opacity: 0.3;
filter: drop-shadow(0 0 10px rgba(0, 255, 153, 0.2));
filter: drop-shadow(0 0 10px rgba(16, 185, 129, 0.2));
}
.empty-text h3 {
@@ -6,9 +6,9 @@
@if (!_isFullyLoaded)
{
<div class="app-preloader" style="backdrop-filter: blur(15px); background: rgba(18, 18, 18, 0.95); z-index: 100000;">
<div class="app-preloader" style="backdrop-filter: blur(15px); background: rgba(18, 18, 18, 0.95); z-index: 100000; color: #ffffff;">
<div class="preloader-spinner"></div>
Review

Add a higher‑contrast text color for the preloader message (e.g., color: #fff).

Add a higher‑contrast text color for the preloader message (e.g., `color: #fff`).
<div class="preloader-text">Synchronizing Secure Session...</div>
<div class="preloader-text" style="color: #ffffff;">Synchronizing Secure Session...</div>
</div>
}
@@ -46,49 +46,54 @@
</div>
<nav class="sidebar-nav">
<NavLink class="nav-item" href="/" Match="NavLinkMatch.All" @onclick="CloseMobileMenu">
<NavLink class="nav-item" href="/dashboard" Match="NavLinkMatch.All" @onclick="CloseMobileMenu" title="Pulpit" aria-label="Pulpit">
<div class="nav-icon">
<NexusIcon Name="home" Size="18" />
<NexusIcon Name="home" Size="20" />
</div>
<span class="nav-text">Dashboard</span>
<span class="nav-text">Pulpit</span>
</NavLink>
<NavLink class="nav-item" href="/library" @onclick="CloseMobileMenu">
<NavLink class="nav-item" href="/catalog" @onclick="CloseMobileMenu" title="Katalog" aria-label="Katalog">
<div class="nav-icon">
<NexusIcon Name="book-open" Size="18" />
<NexusIcon Name="layout" Size="20" />
</div>
<span class="nav-text">Library</span>
<span class="nav-text">Katalog</span>
</NavLink>
<NavLink class="nav-item" href="/concepts-map" @onclick="CloseMobileMenu">
<NavLink class="nav-item" href="/my-books" @onclick="CloseMobileMenu" title="Moje" aria-label="Moje">
<div class="nav-icon">
<NexusIcon Name="map" Size="18" />
<NexusIcon Name="book-open" Size="20" />
</div>
Review

Provide a visible focus style for navigation items (CSS :focus-visible).

Provide a visible focus style for navigation items (CSS `:focus-visible`).
<span class="nav-text">Concepts Map</span>
<span class="nav-text">Moje</span>
</NavLink>
<NavLink class="nav-item" href="/intelligence" @onclick="CloseMobileMenu">
<NavLink class="nav-item" href="/profile" @onclick="CloseMobileMenu" title="Konto" aria-label="Konto">
<div class="nav-icon">
<NexusIcon Name="cpu" Size="18" />
<NexusIcon Name="user" Size="20" />
</div>
<span class="nav-text">Global AI Q&A</span>
<span class="nav-text">Konto</span>
</NavLink>
<NavLink class="nav-item" href="/profile" @onclick="CloseMobileMenu">
<NavLink class="nav-item" href="/concepts-map" @onclick="CloseMobileMenu" title="Mapa Pojęć" aria-label="Mapa Pojęć">
<div class="nav-icon">
<NexusIcon Name="message-square" Size="18" />
<NexusIcon Name="map" Size="20" />
</div>
<span class="nav-text">Profile</span>
<span class="nav-text">Mapa Pojęć</span>
</NavLink>
<NavLink class="nav-item" href="/settings" @onclick="CloseMobileMenu">
<NavLink class="nav-item" href="/intelligence" @onclick="CloseMobileMenu" title="Globalne AI" aria-label="Globalne AI">
<div class="nav-icon">
<NexusIcon Name="settings" Size="18" />
<NexusIcon Name="robot" Size="20" />
</div>
<span class="nav-text">Settings</span>
<span class="nav-text">Globalne AI</span>
</NavLink>
<NavLink class="nav-item" href="/concenters" @onclick="CloseMobileMenu">
<NavLink class="nav-item" href="/settings" @onclick="CloseMobileMenu" title="Ustawienia" aria-label="Ustawienia">
<div class="nav-icon">
<NexusIcon Name="target" Size="18" />
<NexusIcon Name="settings" Size="20" />
</div>
<span class="nav-text">Concenters</span>
<span class="nav-text">Ustawienia</span>
</NavLink>
<NavLink class="nav-item" href="/concenters" @onclick="CloseMobileMenu" title="Koncentry" aria-label="Koncentry">
<div class="nav-icon">
<NexusIcon Name="target" Size="20" />
</div>
<span class="nav-text">Koncentry</span>
</NavLink>
</nav>
<div class="sidebar-footer">
@@ -100,8 +105,8 @@
<span class="user-name">@context.User.Identity?.Name</span>
</div>
</div>
<button class="logout-btn" @onclick="HandleLogout" title="Logout">
<NexusIcon Name="log-out" Size="18" />
<button class="logout-btn" @onclick="HandleLogout" title="Wyloguj">
<NexusIcon Name="log-out" Size="20" />
</button>
</div>
</aside>
@@ -2,163 +2,162 @@
display: flex;
width: 100vw;
height: 100vh;
background: #121212;
color: #e0e0e0;
background: #121214;
color: #e4e4e7;
overflow: hidden;
}
::deep .hub-sidebar {
width: 260px;
width: 80px;
height: 100%;
background: #161616;
background: #0d0d0d;
border-right: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
flex-direction: column;
z-index: 100;
flex-shrink: 0;
transition: width 0.2s ease;
}
::deep .sidebar-header {
padding: 2.5rem 1.5rem;
padding: 2rem 0;
display: flex;
justify-content: center;
}
::deep .logo {
display: flex;
align-items: center;
gap: 0.75rem;
justify-content: center;
}
::deep .logo-icon {
color: var(--nexus-neon);
filter: drop-shadow(0 0 10px rgba(0, 255, 153, 0.4));
color: #10b981;
filter: drop-shadow(0 0 8px rgba(16, 185, 129, 0.3));
}
::deep .logo-text {
font-family: var(--nexus-font-serif);
font-size: 1.5rem;
font-weight: 700;
color: #ffffff;
letter-spacing: -0.01em;
display: none;
}
::deep .sidebar-nav {
flex: 1;
padding: 0;
padding: 0.5rem 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
::deep .nav-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.5rem;
color: #A0A0A0;
justify-content: center;
width: 100%;
height: 54px;
color: #8b8273;
text-decoration: none;
transition: all 0.2s ease;
border-left: 3px solid transparent;
font-family: var(--nexus-font-sans);
font-size: 0.9rem;
font-weight: 500;
transition: color 0.2s ease, background-color 0.2s ease;
position: relative;
}
::deep .nav-item:hover {
Review

Add focus outline for .nav-item:focus-visible (e.g., outline: 2px solid #10b981;).

Add focus outline for `.nav-item:focus-visible` (e.g., `outline: 2px solid #10b981;`).
background: rgba(255, 255, 255, 0.02);
color: #ffffff;
color: #10b981;
background: rgba(255, 255, 255, 0.01);
}
::deep .nav-item:focus-visible {
outline: 2px solid #10b981;
outline-offset: -2px;
}
::deep .nav-item.active {
color: #ffffff;
background: rgba(0, 255, 153, 0.03);
border-left: 3px solid var(--nexus-neon);
color: #10b981;
background: rgba(16, 185, 129, 0.04);
}
::deep .nav-item.active .nav-icon {
color: var(--nexus-neon);
::deep .nav-item.active::before {
content: '';
position: absolute;
left: 0;
top: 15%;
height: 70%;
width: 3px;
background: #10b981;
border-radius: 0 4px 4px 0;
box-shadow: 0 0 10px rgba(16, 185, 129, 0.6);
}
::deep .nav-icon {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
opacity: 0.7;
transition: opacity 0.2s;
width: 24px;
height: 24px;
transition: color 0.2s ease;
}
::deep .nav-item:hover .nav-icon,
::deep .nav-item.active .nav-icon {
opacity: 1;
::deep .nav-text {
display: none;
}
::deep .sidebar-footer {
padding: 1.25rem 1.5rem;
padding: 1.5rem 0;
border-top: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
gap: 1.25rem;
}
::deep .user-brief {
display: flex;
align-items: center;
gap: 0.75rem;
overflow: hidden;
justify-content: center;
width: 100%;
}
::deep .user-avatar {
width: 32px;
height: 32px;
background: #222;
border: 1px solid rgba(255, 255, 255, 0.1);
width: 36px;
height: 36px;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-size: 0.9rem;
font-weight: 600;
color: #A0A0A0;
color: #e4e4e7;
flex-shrink: 0;
}
::deep .user-details {
display: flex;
flex-direction: column;
overflow: hidden;
}
::deep .user-name {
font-size: 0.85rem;
font-weight: 500;
color: #A0A0A0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: none;
}
::deep .logout-btn {
background: transparent;
border: none;
color: #666;
color: #8b8273;
cursor: pointer;
padding: 0.4rem;
border-radius: 6px;
transition: all 0.2s;
padding: 0.5rem;
border-radius: 8px;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
::deep .logout-btn:hover {
background: rgba(255, 255, 255, 0.05);
color: #ffffff;
background: rgba(239, 68, 68, 0.08);
color: #ef4444;
}
.hub-main {
flex: 1;
height: 100%;
overflow-y: auto;
background: radial-gradient(circle at center, #1a1a1a 0%, #121212 100%);
background: #121214;
}
.hub-content {
@@ -179,11 +178,11 @@
::deep .nexus-loader {
width: 32px;
height: 32px;
border: 2px solid rgba(0, 255, 153, 0.1);
border-top-color: var(--nexus-neon);
border: 2px solid rgba(16, 185, 129, 0.1);
border-top-color: #10b981;
border-radius: 50%;
animation: spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
filter: drop-shadow(0 0 5px var(--nexus-neon));
filter: drop-shadow(0 0 5px #10b981);
}
@keyframes spin {
@@ -41,7 +41,7 @@
<!-- Intelligence Card -->
<div class="metric-card glass-panel">
<div class="card-header">
<NexusIcon Name="robot" Size="24" Color="var(--nexus-neon)" />
<NexusIcon Name="robot" Size="24" Color="#10b981" />
<h3>Interfejs AI</h3>
</div>
<div class="card-body">
@@ -62,7 +62,7 @@
<!-- Sync Card -->
<div class="metric-card glass-panel">
<div class="card-header">
<NexusIcon Name="activity" Size="24" Color="var(--nexus-neon)" />
<NexusIcon Name="activity" Size="24" Color="#10b981" />
<h3>Wydajność Nauki</h3>
</div>
<div class="card-body">
@@ -80,7 +80,7 @@
<!-- Account Status Card -->
<div class="metric-card glass-panel full-width">
<div class="card-header">
<NexusIcon Name="shield" Size="24" Color="var(--nexus-neon)" />
<NexusIcon Name="shield" Size="24" Color="#10b981" />
<h3>Status Autoryzacji</h3>
</div>
<div class="card-body status-layout">
@@ -2,8 +2,8 @@
position: relative;
width: 100%;
min-height: 100vh;
background-color: #0a0c10;
color: #e0e6ed;
background-color: #121214;
color: #e4e4e7;
overflow-x: hidden;
display: flex;
justify-content: center;
@@ -18,7 +18,7 @@
transform: translate(-50%, -50%);
width: 800px;
height: 800px;
background: radial-gradient(circle, rgba(0, 255, 153, 0.05) 0%, transparent 70%);
background: radial-gradient(circle, rgba(16, 185, 129, 0.03) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}
@@ -63,17 +63,17 @@
.avatar-inner {
width: 120px;
height: 120px;
background: #151921;
border: 2px solid var(--nexus-neon);
background: #1a1a1e;
border: 2px solid #10b981;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 3.5rem;
font-weight: 800;
color: var(--nexus-neon);
color: #10b981;
z-index: 2;
box-shadow: 0 0 30px rgba(0, 255, 153, 0.2), inset 0 0 20px rgba(0, 255, 153, 0.1);
box-shadow: 0 0 30px rgba(16, 185, 129, 0.2), inset 0 0 20px rgba(16, 185, 129, 0.1);
position: relative;
}
@@ -81,7 +81,7 @@
position: absolute;
width: 140px;
height: 140px;
border: 1px solid rgba(0, 255, 153, 0.3);
border: 1px solid rgba(16, 185, 129, 0.3);
border-radius: 50%;
animation: pulse-ring 3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@@ -104,7 +104,7 @@
.system-rank {
font-family: 'Inter', 'Courier New', Courier, monospace;
font-size: 0.9rem;
color: var(--nexus-neon);
color: #10b981;
text-transform: uppercase;
letter-spacing: 0.2em;
opacity: 0.8;
@@ -120,11 +120,17 @@
.glass-panel {
padding: 32px;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
transition: all 0.3s ease;
}
.glass-panel:hover {
border-color: rgba(0, 255, 153, 0.2);
border-color: rgba(16, 185, 129, 0.2);
transform: translateY(-4px);
background: #1e1e24;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.metric-card {
@@ -185,8 +191,8 @@
.progress-bar {
height: 100%;
background: var(--nexus-neon);
box-shadow: 0 0 15px rgba(0, 255, 153, 0.5);
background: #10b981;
box-shadow: 0 0 15px rgba(16, 185, 129, 0.5);
border-radius: 10px;
}
@@ -206,7 +212,7 @@
.score-value {
font-size: 2.5rem;
font-weight: 800;
color: var(--nexus-neon);
color: #10b981;
line-height: 1;
}
@@ -223,7 +229,8 @@
align-items: center;
gap: 8px;
padding: 10px 16px;
background: rgba(0, 255, 153, 0.05);
background: rgba(16, 185, 129, 0.05);
border: 1px solid rgba(16, 185, 129, 0.1);
border-radius: 12px;
font-size: 0.85rem;
color: #cbd5e0;
@@ -259,10 +266,16 @@
width: fit-content;
}
.plan-badge.pro {
background: rgba(0, 255, 153, 0.1);
color: var(--nexus-neon);
border: 1px solid rgba(0, 255, 153, 0.2);
.plan-badge.pro, .plan-badge.enterprise {
background: rgba(16, 185, 129, 0.15);
color: #10b981;
border: 1px solid rgba(16, 185, 129, 0.3);
}
.plan-badge.free {
background: rgba(255, 255, 255, 0.05);
color: #a1a1aa;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.tenant-tag {
@@ -318,11 +331,11 @@
.nexus-loader {
width: 60px;
height: 60px;
border: 4px solid rgba(0, 255, 153, 0.1);
border-top-color: var(--nexus-neon);
border: 4px solid rgba(16, 185, 129, 0.1);
border-top-color: #10b981;
border-radius: 50%;
animation: spin 1.5s cubic-bezier(0.5, 0, 0.5, 1) infinite;
filter: drop-shadow(0 0 10px var(--nexus-neon));
filter: drop-shadow(0 0 10px #10b981);
}
@keyframes spin { to { transform: rotate(360deg); } }
@@ -0,0 +1,234 @@
@page "/catalog"
@attribute [Authorize]
@using NexusReader.UI.Shared.Components.Organisms
@using NexusReader.Application.DTOs.User
@using NexusReader.UI.Shared.Services
@using System.Net.Http.Json
@inject HttpClient Http
@inject IReaderNavigationService ReaderNavigation
@inject NavigationManager NavigationManager
<div class="catalog-page">
<header class="catalog-header">
<div class="header-title-section">
<h1>Katalog Kursów</h1>
<p class="subtitle">Rozwijaj swoje kompetencje techniczne z interaktywnymi kursami zintegrowanymi z asystentem Nexus AI</p>
</div>
</header>
<div class="catalog-content">
@if (_isLoading)
{
<div class="catalog-loading-container">
<div class="loader-card">
<div class="spinner-glow small"></div>
<span class="loader-text">Wczytywanie katalogu...</span>
</div>
<div class="loading-grid">
@for (int i = 0; i < 3; i++)
{
<div class="skeleton-card">
<div class="skeleton-cover"></div>
<div class="skeleton-details">
<div class="skeleton-line title"></div>
<div class="skeleton-line author"></div>
<div class="skeleton-line button"></div>
</div>
</div>
}
</div>
</div>
}
else
{
<div class="catalog-grid">
@* Render real books first *@
@if (_books != null && _books.Any())
{
@foreach (var book in _books)
{
<div class="course-card" @onclick="() => OpenBook(book.Id)">
<div class="card-cover-container">
<img src="@(book.CoverUrl ?? "https://api.dicebear.com/7.x/identicon/svg?seed=" + book.Title)" alt="@book.Title" class="card-cover" />
<div class="cover-overlay">
<span class="start-action">Uruchom kurs</span>
</div>
</div>
<div class="card-details">
<span class="category-badge">E-Book</span>
<h3 class="course-title" title="@book.Title">@book.Title</h3>
<p class="course-author">Autor: @book.Author.Name</p>
<p class="course-desc">
@(string.IsNullOrEmpty(book.Description) ? "Rozpocznij naukę i buduj strukturę pojęć w oparciu o autorskie algorytmy ekstrakcji wiedzy Nexus AI." : book.Description)
</p>
<div class="card-footer-info">
<span class="meta-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
Rozdziały: Wiele
</span>
<span class="meta-item text-success">Dostępny</span>
</div>
<div class="card-actions">
<button class="btn-nexus start-course-btn" @onclick="() => OpenBook(book.Id)" @onclick:stopPropagation="true">
ZACZNIJ KURS
</button>
</div>
</div>
</div>
}
}
@* Curated Showcase Mock Courses to look extremely premium *@
<div class="course-card">
<div class="card-cover-container">
<div class="mock-cover dotnet-gradient">
<span class="cover-code-text">&lt;.NET 10&gt;</span>
</div>
<div class="cover-overlay">
<span class="start-action">Zarejestruj się</span>
</div>
</div>
<div class="card-details">
<span class="category-badge architecture">Architektura</span>
<h3 class="course-title">.NET 10 &amp; Blazor SaaS Architecture</h3>
<p class="course-author">Autor: Nexus Architect</p>
<p class="course-desc">
Zaawansowany kurs budowania skalowalnych SaaS z Native AOT, CQRS, MediatR, FluentResults i izolowanym systemem stylów Blazor CSS.
</p>
<div class="card-footer-info">
<span class="meta-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
12 Modułów
</span>
<span class="meta-item text-premium">Premium</span>
</div>
<div class="card-actions">
<button class="btn-nexus start-course-btn mock-btn" @onclick="ShowPremiumAlert">
ZACZNIJ KURS
</button>
</div>
</div>
</div>
<div class="course-card">
<div class="card-cover-container">
<div class="mock-cover blazor-gradient">
<span class="cover-code-text">BLAZOR</span>
</div>
<div class="cover-overlay">
<span class="start-action">Zarejestruj się</span>
</div>
</div>
<div class="card-details">
<span class="category-badge design">Performance</span>
<h3 class="course-title">Blazor State &amp; Rendering Masterclass</h3>
<p class="course-author">Autor: Nexus Architect</p>
<p class="course-desc">
Techniki optymalizacji renderowania, zarządzanie stanem w aplikacjach rozproszonych oraz głęboka integracja JavaScript Interop.
</p>
<div class="card-footer-info">
<span class="meta-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
8 Modułów
</span>
<span class="meta-item text-premium">Premium</span>
</div>
<div class="card-actions">
<button class="btn-nexus start-course-btn mock-btn" @onclick="ShowPremiumAlert">
ZACZNIJ KURS
</button>
</div>
</div>
</div>
<div class="course-card">
<div class="card-cover-container">
<div class="mock-cover graph-gradient">
<span class="cover-code-text">D3.JS GRAPH</span>
</div>
<div class="cover-overlay">
<span class="start-action">Zarejestruj się</span>
</div>
</div>
<div class="card-details">
<span class="category-badge analytics">Wizualizacja</span>
<h3 class="course-title">D3.js &amp; interactive Knowledge Graphs</h3>
<p class="course-author">Autor: Nexus Architect</p>
<p class="course-desc">
Projektowanie interaktywnych grafów pojęć i dynamicznych map myśli 2D/3D zsynchronizowanych z modelem językowym AI.
</p>
<div class="card-footer-info">
<span class="meta-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
10 Modułów
</span>
<span class="meta-item text-premium">Premium</span>
</div>
<div class="card-actions">
<button class="btn-nexus start-course-btn mock-btn" @onclick="ShowPremiumAlert">
ZACZNIJ KURS
</button>
</div>
</div>
</div>
</div>
}
</div>
</div>
@code {
private bool _isLoading = true;
private List<LastReadBookDto>? _books;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadBooksAsync();
}
}
private async Task LoadBooksAsync()
{
_isLoading = true;
StateHasChanged();
try
{
_books = await Http.GetFromJsonAsync<List<LastReadBookDto>>("api/library/books");
_isLoading = false;
}
catch (Exception ex)
{
Console.WriteLine($"[Catalog] Failed to load books: {ex.Message}");
if (OperatingSystem.IsBrowser())
{
_isLoading = false;
}
}
finally
{
StateHasChanged();
}
}
private void OpenBook(Guid bookId)
{
ReaderNavigation.NavigateToBook(bookId);
}
private void ShowPremiumAlert()
{
// Showcase callback
NavigationManager.NavigateTo("/profile");
}
}
@@ -0,0 +1,358 @@
.catalog-page {
padding: 3rem 2rem;
max-width: 1200px;
margin: 0 auto;
animation: fadeIn 0.6s ease-out;
}
.catalog-header {
margin-bottom: 3rem;
}
.catalog-header h1 {
font-family: var(--nexus-font-serif);
font-size: 2.5rem;
font-weight: 700;
margin: 0 0 0.5rem 0;
color: #ffffff;
letter-spacing: -0.5px;
}
.catalog-header .subtitle {
font-size: 1rem;
color: #a1a1aa;
margin: 0;
}
/* Catalog Grid */
.catalog-grid, .loading-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 2.5rem;
}
.course-card {
cursor: pointer;
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
border-radius: 12px;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.course-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
border-color: rgba(16, 185, 129, 0.2);
}
.card-cover-container {
position: relative;
height: 200px;
background: rgba(0, 0, 0, 0.2);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.card-cover {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.course-card:hover .card-cover {
transform: scale(1.04);
}
/* Gradients for Mock Course Covers */
.mock-cover {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.cover-code-text {
font-family: var(--nexus-font-sans, "Outfit", sans-serif);
font-size: 1.5rem;
font-weight: 800;
color: #ffffff;
letter-spacing: 1px;
text-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.dotnet-gradient {
background: linear-gradient(135deg, #512bd4 0%, #10b981 100%);
}
.blazor-gradient {
background: linear-gradient(135deg, #f05a28 0%, #10b981 100%);
}
.graph-gradient {
background: linear-gradient(135deg, #0d9488 0%, #10b981 100%);
}
.cover-overlay {
position: absolute;
inset: 0;
background: rgba(13, 13, 15, 0.6);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
backdrop-filter: blur(4px);
}
.course-card:hover .cover-overlay {
opacity: 1;
}
.start-action {
color: #ffffff;
font-weight: 600;
font-size: 1rem;
padding: 0.6rem 1.25rem;
border: 2px solid #ffffff;
border-radius: 30px;
transform: translateY(10px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.course-card:hover .start-action {
transform: translateY(0);
background: #ffffff;
color: #121214;
}
.card-details {
padding: 1.5rem;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.category-badge {
align-self: flex-start;
font-size: 0.7rem;
font-weight: 700;
color: #a1a1aa;
background: rgba(255, 255, 255, 0.05);
padding: 0.2rem 0.5rem;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.75rem;
}
.category-badge.architecture {
color: #f43f5e;
background: rgba(244, 63, 94, 0.1);
}
.category-badge.design {
color: #0ea5e9;
background: rgba(14, 165, 233, 0.1);
}
.category-badge.analytics {
color: #a855f7;
background: rgba(168, 85, 247, 0.1);
}
.course-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0 0 0.4rem 0;
color: #ffffff;
line-height: 1.3;
font-family: var(--nexus-font-sans, "Outfit", sans-serif);
}
.course-author {
font-size: 0.85rem;
color: #a1a1aa;
margin: 0 0 1rem 0;
}
.course-desc {
font-size: 0.88rem;
line-height: 1.5;
color: #a1a1aa;
margin: 0 0 1.5rem 0;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.card-footer-info {
margin-top: auto;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8rem;
color: #a1a1aa;
border-top: 1px solid rgba(255, 255, 255, 0.05);
padding-top: 0.75rem;
}
.meta-item {
display: flex;
align-items: center;
gap: 0.35rem;
}
.text-success {
color: #10b981;
font-weight: 600;
}
.text-premium {
color: #10b981;
font-weight: 600;
}
.card-actions {
margin-top: 1.25rem;
}
.start-course-btn {
width: 100%;
background: #10b981;
color: #0d0d0d;
border: none;
padding: 0.75rem 1.5rem;
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.08em;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
text-transform: uppercase;
}
.start-course-btn:hover {
background: #059669;
color: #ffffff;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.2);
}
/* Skeleton Loading */
.skeleton-card {
border-radius: 12px;
overflow: hidden;
height: 440px;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
opacity: 0.6;
}
.skeleton-cover {
height: 200px;
background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.06) 50%, rgba(255,255,255,0.02) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
.skeleton-details {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.skeleton-line {
background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.06) 50%, rgba(255,255,255,0.02) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
.skeleton-line.title {
height: 20px;
width: 80%;
}
.skeleton-line.author {
height: 14px;
width: 50%;
}
.skeleton-line.button {
height: 36px;
width: 100%;
margin-top: auto;
}
.catalog-loading-container {
position: relative;
width: 100%;
}
.catalog-loading-container .loader-card {
position: absolute;
top: 100px;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1.25rem 2.25rem;
border-radius: 40px;
background: rgba(13, 13, 15, 0.85);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.08);
animation: scaleIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.spinner-glow {
width: 28px;
height: 28px;
border: 2px solid rgba(16, 185, 129, 0.1);
border-radius: 50%;
border-top-color: #10b981;
animation: spin 1s cubic-bezier(0.55, 0.055, 0.675, 0.19) infinite;
box-shadow: 0 0 15px rgba(16, 185, 129, 0.2);
}
.loader-text {
font-weight: 500;
color: #ffffff;
font-size: 0.95rem;
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(15px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes scaleIn {
from { transform: translate(-50%, -50%) scale(0.9); opacity: 0; }
to { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}
@@ -44,7 +44,7 @@
<NexusIcon Name="book-open" Size="64" Class="dim-icon" />
<h2>Brak Aktywnych Książek</h2>
<p>Nie wybrano żadnej książki lub ta książka nie ma jeszcze wygenerowanej mapy pojęć przez Nexus AI.</p>
<a href="/library" class="btn-nexus btn-nexus-primary">Przejdź do Biblioteki</a>
<a href="/my-books" class="btn-nexus btn-nexus-primary">Przejdź do Moich Książek</a>
</div>
}
else
@@ -53,7 +53,7 @@
<div class="header-back">
<button class="btn-nexus btn-nexus-secondary btn-back" @onclick="GoBackToLibrary">
<NexusIcon Name="arrow-left" Size="16" />
<span>Biblioteka</span>
<span>Moje Książki</span>
</button>
</div>
<div class="header-title">
@@ -257,7 +257,7 @@
private void GoBackToLibrary()
{
NavigationManager.NavigateTo("/library");
NavigationManager.NavigateTo("/my-books");
}
private void GoToReader()
@@ -1,4 +1,5 @@
@page "/"
@page "/dashboard"
@using Microsoft.AspNetCore.Authorization
@using NexusReader.UI.Shared.Components.Atoms
@using NexusReader.UI.Shared.Components.Organisms
@@ -138,6 +139,26 @@
</section>
</div>
</div>
<!-- Detailed Content Block Showcase -->
<section class="architecture-guide-panel glass-panel">
<div class="panel-header">
<h4>Architektura Systemu Nexus</h4>
<NexusIcon Name="book" Size="16" />
</div>
<div class="architecture-content">
<h3>.NET 10 &amp; Blazor Hybrid Architecture</h3>
<p>
Nasza platforma została zaprojektowana w oparciu o najnowszy stos technologiczny <strong>.NET 10</strong> oraz model komponentowy <strong>Blazor</strong>, zapewniając pełną kompatybilność z kompilacją <strong>Native AOT</strong> (Ahead-Of-Time). Dzięki temu aplikacja charakteryzuje się błyskawicznym czasu uruchamiania i minimalnym zużyciem pamięci, co jest kluczowe w scenariuszach mobilnych i hybrydowych.
</p>
<p>
Wykorzystanie wzorca <strong>CQRS</strong> (Command Query Responsibility Segregation) wraz z biblioteką <strong>MediatR</strong> oddziela operacje odczytu od zapisu, gwarantując skalowalność i przejrzystość kodu. Wszystkie operacje biznesowe są reprezentowane przez niezależne procedury obsługi (handlers) zwracające unifikowany typ wyniku <code>Result&lt;T&gt;</code>, eliminując rzucanie wyjątków dla przepływów sterowania.
</p>
<p>
Warstwa prezentacji opiera się na izolowanych komponentach Razor z dedykowanymi arkuszami stylów CSS, co ułatwia zarządzanie modularnym i rozszerzalnym interfejsem użytkownika w duchu Modern Deep Dark.
</p>
</div>
</section>
</main>
</div>
@@ -17,7 +17,8 @@
display: flex;
justify-content: center;
overflow: hidden;
background: #0D0D0D;
background: #0d0d0d;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.header-grid-bg {
@@ -61,7 +62,7 @@
position: absolute;
inset: -5px;
border-radius: 50%;
background: var(--nexus-neon);
background: #10b981;
filter: blur(20px);
opacity: 0.4;
z-index: 1;
@@ -102,13 +103,13 @@
.status-pill {
padding: 0.6rem 1.25rem;
background: rgba(0, 255, 153, 0.05);
border: 1px solid rgba(0, 255, 153, 0.3);
background: rgba(16, 185, 129, 0.05);
border: 1px solid rgba(16, 185, 129, 0.3);
border-radius: 100px;
display: flex;
gap: 0.5rem;
font-size: 0.9rem;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.1);
box-shadow: 0 0 15px rgba(16, 185, 129, 0.1);
}
.pill-label { color: #A0A0A0; }
@@ -136,25 +137,18 @@
}
.glass-panel {
background: rgba(20, 20, 20, 0.8); /* Fallback for browsers without backdrop-filter */
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 20px;
border-radius: 12px;
padding: 1.5rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@supports (backdrop-filter: blur(10px)) {
.glass-panel {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(10px);
}
}
.glass-panel:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(0, 255, 153, 0.2);
background: #1e1e24;
border-color: rgba(16, 185, 129, 0.2);
transform: translateY(-4px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
/* Reading Card */
@@ -373,7 +367,7 @@
}
.btn-nexus.primary {
background: var(--nexus-neon);
background: #10b981;
color: #000;
}
@@ -606,3 +600,42 @@
}
}
/* --- Architecture Guide Block --- */
.architecture-guide-panel {
margin-top: 2.5rem;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 2rem;
}
.architecture-content {
max-width: 800px;
margin: 0 auto;
font-family: var(--nexus-font-sans, "Outfit", sans-serif);
}
.architecture-content h3 {
font-size: 1.5rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 1.25rem;
letter-spacing: -0.01em;
}
.architecture-content p {
font-size: 0.95rem;
line-height: 1.6;
color: #e4e4e7;
margin-bottom: 1.25rem;
}
.architecture-content code {
background: rgba(255, 255, 255, 0.05);
color: #10b981;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-size: 0.85rem;
}
@@ -1,4 +1,4 @@
@page "/library"
@page "/my-books"
@attribute [Authorize]
@using NexusReader.UI.Shared.Components.Organisms
@using NexusReader.Application.DTOs.User
@@ -7,26 +7,26 @@
@inject HttpClient Http
@inject IReaderNavigationService ReaderNavigation
<div class="library-page">
<header class="library-header">
<div class="my-books-page">
<header class="my-books-header">
<div class="header-title-section">
<h1>Moja Biblioteka</h1>
<p class="subtitle">Zarządzaj swoją kolekcją e-booków i rozwijaj strukturę wiedzy z Nexus AI</p>
<h1>Moje Książki</h1>
<p class="subtitle">Twoje aktywne lektury i postępy w nauce z Nexus AI</p>
</div>
<AuthorizeView Roles="Admin, ContentManager">
<NexusButton Class="add-book-trigger" OnClick="() => _isModalOpen = true">
<button class="btn-nexus add-book-trigger" @onclick="() => _isModalOpen = true">
<span class="btn-icon">+</span> Dodaj E-book
</NexusButton>
</button>
</AuthorizeView>
</header>
<BookIngestionModal @bind-IsOpen="_isModalOpen" @bind-IsOpen:after="RefreshLibrary" />
<div class="library-content">
<div class="my-books-content">
@if (_isLoading)
{
<div class="library-loading-container">
<div class="loader-card glass-panel">
<div class="my-books-loading-container">
<div class="loader-card">
<div class="spinner-glow small"></div>
<span class="loader-text">Wczytywanie biblioteki...</span>
</div>
@@ -34,7 +34,7 @@
<div class="loading-grid">
@for (int i = 0; i < 3; i++)
{
<div class="skeleton-card glass-panel">
<div class="skeleton-card">
<div class="skeleton-cover"></div>
<div class="skeleton-details">
<div class="skeleton-line title"></div>
@@ -48,7 +48,7 @@
}
else if (_books == null || !_books.Any())
{
<div class="empty-state-container glass-panel">
<div class="empty-state-container">
<div class="empty-icon-pulse">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
@@ -56,17 +56,8 @@
</svg>
</div>
<h3>Pusta biblioteka</h3>
<p>Nie masz jeszcze żadnych książek w swojej kolekcji.</p>
<AuthorizeView Roles="Admin, ContentManager">
<Authorized>
<button class="btn-nexus btn-nexus-primary" @onclick="() => _isModalOpen = true">
Prześlij pierwszą książkę
</button>
</Authorized>
<NotAuthorized>
<p class="restricted-info">Skontaktuj się z administratorem, aby dodać książki do swojego konta.</p>
</NotAuthorized>
</AuthorizeView>
<p>Nie masz jeszcze żadnych książek na swojej półce. Przejdź do katalogu, aby rozpocząć kurs.</p>
<a href="/catalog" class="btn-nexus catalog-btn">Przeglądaj Katalog</a>
</div>
}
else
@@ -74,7 +65,7 @@
<div class="books-grid">
@foreach (var book in _books)
{
<div class="book-card glass-panel" @onclick="() => OpenBook(book.Id)">
<div class="book-card" @onclick="() => OpenBook(book.Id)">
<div class="book-cover-container">
<img src="@(book.CoverUrl ?? "https://api.dicebear.com/7.x/identicon/svg?seed=" + book.Title)" alt="@book.Title" class="book-cover" />
<div class="cover-overlay">
@@ -98,6 +89,12 @@
{
<span class="new-badge">Nowa</span>
}
<div class="card-actions">
<button class="btn-nexus primary-accent-btn">
KONTYNUUJ KURS
</button>
</div>
</div>
</div>
}
@@ -131,7 +128,7 @@
}
catch (Exception ex)
{
Console.WriteLine($"[Library] Failed to load books: {ex.Message}");
Console.WriteLine($"[MyBooks] Failed to load books: {ex.Message}");
if (OperatingSystem.IsBrowser())
{
_isLoading = false;
@@ -145,7 +142,6 @@
private async Task RefreshLibrary()
{
// Refresh when modal closes or when a book is successfully ingested
await LoadBooksAsync();
}
@@ -1,11 +1,11 @@
.library-page {
.my-books-page {
padding: 3rem 2rem;
max-width: 1200px;
margin: 0 auto;
animation: fadeIn 0.6s ease-out;
}
.library-header {
.my-books-header {
display: flex;
justify-content: space-between;
align-items: center;
@@ -16,35 +16,36 @@
.header-title-section h1 {
font-family: var(--nexus-font-serif);
font-size: 2.8rem;
font-size: 2.5rem;
font-weight: 700;
margin: 0 0 0.5rem 0;
background: linear-gradient(135deg, var(--nexus-text) 0%, rgba(255, 255, 255, 0.7) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: #ffffff;
letter-spacing: -0.5px;
}
.header-title-section .subtitle {
font-size: 1rem;
color: rgba(255, 255, 255, 0.6);
color: #a1a1aa;
margin: 0;
}
.add-book-trigger {
background: var(--nexus-neon) !important;
color: #000000 !important;
border: none !important;
box-shadow: 0 4px 15px var(--nexus-primary-glow) !important;
font-weight: 600 !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
border-radius: var(--radius-md) !important;
background: transparent;
color: #10b981;
border: 1px solid rgba(16, 185, 129, 0.3);
padding: 0.75rem 1.5rem;
font-size: 0.9rem;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease-out;
}
.add-book-trigger:hover {
transform: translateY(-2px) !important;
box-shadow: 0 8px 20px rgba(0, 255, 153, 0.5) !important;
filter: brightness(1.1);
background: rgba(16, 185, 129, 0.05);
border-color: #10b981;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.1);
}
.btn-icon {
@@ -56,7 +57,7 @@
.books-grid, .loading-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 2rem;
gap: 2.5rem;
}
.book-card {
@@ -65,34 +66,22 @@
flex-direction: column;
height: 100%;
overflow: hidden;
border-radius: var(--radius-lg);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 12px;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.book-card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(800px circle at var(--x, 0) var(--y, 0), rgba(255, 255, 255, 0.06), transparent 40%);
opacity: 0;
transition: opacity 0.5s;
pointer-events: none;
}
.book-card:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 2px rgba(255, 255, 255, 0.1) inset;
border-color: rgba(0, 255, 153, 0.2);
}
.book-card:hover::before {
opacity: 1;
transform: translateY(-4px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
border-color: rgba(16, 185, 129, 0.2);
}
.book-cover-container {
position: relative;
height: 380px;
height: 360px;
background: rgba(0, 0, 0, 0.2);
overflow: hidden;
display: flex;
@@ -105,17 +94,17 @@
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.book-card:hover .book-cover {
transform: scale(1.08);
transform: scale(1.04);
}
.cover-overlay {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.6);
background: rgba(13, 13, 15, 0.6);
display: flex;
align-items: center;
justify-content: center;
@@ -131,8 +120,8 @@
.read-action {
color: #ffffff;
font-weight: 600;
font-size: 1.1rem;
padding: 0.75rem 1.5rem;
font-size: 1rem;
padding: 0.6rem 1.25rem;
border: 2px solid #ffffff;
border-radius: 30px;
transform: translateY(10px);
@@ -142,7 +131,7 @@
.book-card:hover .read-action {
transform: translateY(0);
background: #ffffff;
color: #0f172a;
color: #121214;
}
.book-details {
@@ -150,35 +139,34 @@
display: flex;
flex-direction: column;
flex-grow: 1;
background: rgba(15, 23, 42, 0.3);
}
.book-title {
font-size: 1.25rem;
font-size: 1.2rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
color: var(--nexus-text);
margin: 0 0 0.4rem 0;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--nexus-font-sans);
font-family: var(--nexus-font-sans, "Outfit", sans-serif);
}
.book-author {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.5);
margin: 0 0 1rem 0;
color: #a1a1aa;
margin: 0 0 1.25rem 0;
}
.new-badge {
align-self: flex-start;
font-size: 0.75rem;
font-weight: 600;
color: var(--nexus-primary);
background: rgba(0, 255, 153, 0.15);
color: #10b981;
background: rgba(16, 185, 129, 0.1);
padding: 0.25rem 0.75rem;
border-radius: 20px;
border: 1px solid rgba(0, 255, 153, 0.3);
border: 1px solid rgba(16, 185, 129, 0.2);
}
/* Book Progress Bar */
@@ -191,25 +179,51 @@
.progress-bar {
height: 6px;
background: rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--nexus-neon) 0%, #00ccff 100%);
background: #10b981;
border-radius: 3px;
}
.progress-text {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.4);
color: #a1a1aa;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-actions {
margin-top: 1.25rem;
}
.primary-accent-btn {
width: 100%;
background: #10b981;
color: #0d0d0d;
border: none;
padding: 0.75rem 1.5rem;
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.08em;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
text-transform: uppercase;
}
.primary-accent-btn:hover {
background: #059669;
color: #ffffff;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.2);
}
/* Empty State */
.empty-state-container {
display: flex;
@@ -218,12 +232,14 @@
justify-content: center;
padding: 5rem 2rem;
text-align: center;
border-radius: var(--radius-lg);
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
}
.empty-icon-pulse {
margin-bottom: 2rem;
color: rgba(255, 255, 255, 0.2);
color: #a1a1aa;
animation: pulse 3s infinite alternate;
}
@@ -231,35 +247,45 @@
font-family: var(--nexus-font-serif);
font-size: 1.8rem;
margin: 0 0 0.5rem 0;
color: var(--nexus-text);
color: #ffffff;
}
.empty-state-container p {
color: rgba(255, 255, 255, 0.5);
color: #a1a1aa;
max-width: 400px;
margin: 0 0 2rem 0;
}
.restricted-info {
font-size: 0.85rem;
font-style: italic;
color: rgba(255, 255, 255, 0.35) !important;
.catalog-btn {
background: #10b981;
color: #0d0d0d;
padding: 0.75rem 2rem;
font-size: 0.9rem;
font-weight: 600;
border-radius: 8px;
text-decoration: none;
transition: all 0.2s ease;
}
.catalog-btn:hover {
background: #059669;
color: #ffffff;
transform: translateY(-2px);
}
/* Skeleton Loading */
.skeleton-card {
border-radius: var(--radius-lg);
border-radius: 12px;
overflow: hidden;
height: 480px;
background: rgba(255, 255, 255, 0.02) !important;
border: 1px solid rgba(255, 255, 255, 0.05) !important;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15) !important;
opacity: 0.5;
background: #1a1a1e;
border: 1px solid rgba(255, 255, 255, 0.05);
opacity: 0.6;
}
.skeleton-cover {
height: 380px;
background: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.12) 50%, rgba(255,255,255,0.04) 75%) !important;
height: 360px;
background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.06) 50%, rgba(255,255,255,0.02) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@@ -272,7 +298,7 @@
}
.skeleton-line {
background: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.12) 50%, rgba(255,255,255,0.04) 75%) !important;
background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.06) 50%, rgba(255,255,255,0.02) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
@@ -294,12 +320,12 @@
margin-top: auto;
}
.library-loading-container {
.my-books-loading-container {
position: relative;
width: 100%;
}
.library-loading-container .loader-card {
.my-books-loading-container .loader-card {
position: absolute;
top: 180px;
left: 50%;
@@ -310,35 +336,26 @@
gap: 1.25rem;
padding: 1.25rem 2.25rem;
border-radius: 40px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), 0 0 1px rgba(255, 255, 255, 0.15) inset;
background: rgba(15, 23, 42, 0.75);
background: rgba(13, 13, 15, 0.85);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.08);
animation: scaleIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.spinner-glow {
width: 60px;
height: 60px;
border: 3px solid rgba(0, 255, 153, 0.1);
border-radius: 50%;
border-top-color: var(--nexus-neon);
animation: spin 1s cubic-bezier(0.55, 0.055, 0.675, 0.19) infinite;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.2);
}
.spinner-glow.small {
width: 28px;
height: 28px;
border-width: 2px;
border: 2px solid rgba(16, 185, 129, 0.1);
border-radius: 50%;
border-top-color: #10b981;
animation: spin 1s cubic-bezier(0.55, 0.055, 0.675, 0.19) infinite;
box-shadow: 0 0 15px rgba(16, 185, 129, 0.2);
}
.loader-text {
font-family: var(--nexus-font-sans);
font-weight: 500;
color: #ffffff;
font-size: 0.95rem;
letter-spacing: 0.2px;
}
/* Animations */