feat(ui): sidebar app shell and bold design system
Replace the top-nav with a fixed left sidebar + slim content topbar (the Komga/Audiobookshelf layout), the signature change versus the conservative tighten-ui branch. - App shell: .app-sidebar (off-canvas on mobile via Alpine mobileMenuOpen, pinned at 16rem on lg), .app-topbar (fixed, blurred, 4rem), and .app-subbar for in-page sticky bars (parks under the topbar). Content is auto-offset via body:has(.app-sidebar) so reader.templ/error.templ (which have no sidebar) are untouched. - Header rebuilt as the sidebar: logo + vertical nav (activeClass), an inline Appearance picker driven by ThemeOptions/WoodOptions, and an inline account menu / sign-in (preserving the inline htmx login). The search lives in the topbar so its dropdown still anchors correctly. - Bolder primitives: .card -> rounded-2xl, cinematic .book-card-cover hover overlay with a quick-action affordance, .brand-gradient hero surface, .hero-backdrop (blurred cover) and .stat-card utilities.
This commit is contained in:
+186
-9
@@ -169,11 +169,107 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Header is sticky on every authenticated page, plus docs. */
|
||||
.header-nav {
|
||||
position: sticky;
|
||||
/*
|
||||
* App shell — a fixed left sidebar with a content area to its right.
|
||||
* The sidebar is off-canvas on mobile (toggled via the `is-open` class,
|
||||
* which Alpine flips through `mobileMenuOpen`) and pinned on >= lg.
|
||||
*/
|
||||
:root {
|
||||
--sidebar-w: 16rem;
|
||||
--topbar-h: 4rem;
|
||||
}
|
||||
|
||||
.app-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: var(--sidebar-w);
|
||||
z-index: 60;
|
||||
background-color: var(--bg-secondary);
|
||||
border-right: 1px solid color-mix(in srgb, var(--text-primary) 7%, transparent);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
.app-sidebar.is-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.app-sidebar-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 55;
|
||||
background: var(--surface-overlay);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
.app-sidebar-backdrop.is-open {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Desktop: pin the sidebar, offset body content, drop the backdrop. */
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
padding-left: var(--sidebar-w);
|
||||
}
|
||||
.app-sidebar {
|
||||
transform: none;
|
||||
}
|
||||
.app-sidebar-backdrop {
|
||||
display: none !important;
|
||||
}
|
||||
.app-mobile-only {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1023.98px) {
|
||||
.app-desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Slim fixed topbar that spans the content area (right of the sidebar). */
|
||||
.app-topbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 40;
|
||||
height: var(--topbar-h);
|
||||
background-color: color-mix(in srgb, var(--bg-secondary) 82%, transparent);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 6%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Content offsets — applied only on pages whose Header renders the
|
||||
* fixed sidebar (reader.templ / error.templ have no sidebar, so they are
|
||||
* left untouched).
|
||||
*/
|
||||
body:has(.app-sidebar) {
|
||||
padding-top: var(--topbar-h);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
body:has(.app-sidebar) {
|
||||
padding-left: var(--sidebar-w);
|
||||
}
|
||||
/* On desktop the topbar starts right of the pinned sidebar. */
|
||||
.app-topbar {
|
||||
left: var(--sidebar-w);
|
||||
}
|
||||
}
|
||||
|
||||
/* A secondary sticky bar (e.g. library switcher) parks under the topbar. */
|
||||
.app-subbar {
|
||||
position: sticky;
|
||||
top: var(--topbar-h);
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
/* Smooth, theme-aware scrollbars */
|
||||
@@ -187,8 +283,8 @@
|
||||
|
||||
.card {
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--text-primary) 7%, transparent);
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid color-mix(in srgb, var(--text-primary) 6%, transparent);
|
||||
border-radius: 1rem;
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
@@ -318,21 +414,102 @@
|
||||
.book-card {
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--text-primary) 6%, transparent);
|
||||
border-radius: 0.625rem;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-card);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
.book-card:hover {
|
||||
transform: translateY(-3px);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-card-hover);
|
||||
border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
|
||||
border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
|
||||
}
|
||||
/* Cinematic cover overlay: a centered quick-action that fades in on hover. */
|
||||
.book-card-cover {
|
||||
position: relative;
|
||||
}
|
||||
.book-card-cover::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.55), transparent 55%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.book-card:hover .book-card-cover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
.book-card-action {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
.book-card:hover .book-card-action {
|
||||
opacity: 1;
|
||||
}
|
||||
.book-card-action-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border-radius: 9999px;
|
||||
background-color: color-mix(in srgb, var(--accent) 92%, white);
|
||||
color: #fff;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.book-card-meta {
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 0.5rem 0.625rem;
|
||||
}
|
||||
|
||||
/* ---------- Bold accents ---------- */
|
||||
|
||||
/* Signature gradient surface for heroes / primary panels. */
|
||||
.brand-gradient {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
color-mix(in srgb, var(--accent) 28%, var(--bg-secondary)),
|
||||
var(--bg-secondary) 62%
|
||||
);
|
||||
}
|
||||
|
||||
/* Blurred cover backdrop used behind the book-detail hero. */
|
||||
.hero-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
filter: blur(28px) saturate(1.4);
|
||||
transform: scale(1.15);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.hero-backdrop::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
color-mix(in srgb, var(--bg-primary) 35%, transparent),
|
||||
var(--bg-primary) 92%
|
||||
);
|
||||
}
|
||||
|
||||
/* Stat tile for analytics / dashboard summaries. */
|
||||
.stat-card {
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--text-primary) 6%, transparent);
|
||||
border-radius: 1rem;
|
||||
padding: 1.25rem;
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
/* ---------- TriState Button (has_cover filter) ---------- */
|
||||
.tristate-btn {
|
||||
background-color: var(--bg-primary);
|
||||
|
||||
Reference in New Issue
Block a user