Files
bookhoard/web/static/input.css
T
john-okeefe ba95cc3e8b fix(reader): stabilize chrome panels, bookmarks end-to-end, dead UI removal
Phase 0 of the reader redesign:

- Panels no longer render under the top/bottom bars: sidebars get
  measured insets (same resize/safe-area mechanism as the viewport);
  panel max-height now derives from the bounded sidebar instead of a
  100vh guess; right-side border targets the actual sidebar.
- Bookmarks work end-to-end for the first time: REST CRUD under
  /api/media-items/:id/bookmarks (create/delete route through
  AnnotationService for dedup/LWW/tombstones), fix UpdateMediaBookmark
  referencing nonexistent updated_at column, frontend posts to the
  real API with per-format position (CFI vs page), live list with
  jump + delete instead of SSR-only snapshot.
- Fix chapter matching in progress saves: boundaries were compared by
  a nonexistent tocItem property, so chapter was never persisted.
- Remove dead UI: Navigator panel stub, empty dictionary popup shell,
  unwired Chrome Behavior select; purge 160 stale build artifacts.
- Reader chrome now follows the user's app theme instead of hardcoded
  theme-tokyo-night.
2026-08-14 09:05:33 -04:00

948 lines
24 KiB
CSS

/* Tailwind CSS directives - required for framework */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/*
* Default :root mirrors the Tokyo Night theme so the palette is correct
* on first paint, before the theme class is applied to <body> (kills the
* theme-flash on load).
*/
:root {
--bg-primary: #1a1b26;
--bg-secondary: #16161e;
--text-primary: #c0caf5;
--text-secondary: #9aa5ce;
--accent: #7aa2f7;
--border: #2a2e42;
/* status */
--status-success: #9ece6a;
--status-warning: #e0af68;
--status-danger: #f7768e;
--status-info: #7aa2f7;
}
.theme-tokyo-night {
--bg-primary: #1a1b26;
--bg-secondary: #16161e;
--text-primary: #c0caf5;
--text-secondary: #9aa5ce;
--accent: #7aa2f7;
--border: #2a2e42;
}
.theme-dracula {
--bg-primary: #282a36;
--bg-secondary: #21222c;
--text-primary: #f8f8f2;
--text-secondary: #bfbfbf;
--accent: #bd93f9;
--border: #3a3d4d;
}
.theme-nord {
--bg-primary: #2e3440;
--bg-secondary: #3b4252;
--text-primary: #eceff4;
--text-secondary: #b7c0d2;
--accent: #88c0d0;
--border: #46577a;
}
.theme-solarized-dark {
--bg-primary: #002b36;
--bg-secondary: #073642;
--text-primary: #c5d3d3;
--text-secondary: #93a1a1;
--accent: #2aa198;
--border: #1d4b58;
}
.theme-monokai {
--bg-primary: #272822;
--bg-secondary: #1e1f1c;
--text-primary: #f8f8f2;
--text-secondary: #c8c8b8;
--accent: #a6e22e;
--border: #3e3d32;
}
.theme-one-dark-pro {
--bg-primary: #282c34;
--bg-secondary: #21252b;
--text-primary: #d7dbe0;
--text-secondary: #9da5b4;
--accent: #61dafb;
--border: #3a3f4b;
}
.theme-material-dark {
--bg-primary: #263238;
--bg-secondary: #1e282d;
--text-primary: #eeffff;
--text-secondary: #b0bec5;
--accent: #80cbc4;
--border: #37474f;
}
.theme-catppuccin-mocha {
--bg-primary: #1e1e2e;
--bg-secondary: #181825;
--text-primary: #cdd6f4;
--text-secondary: #a6adc8;
--accent: #f38ba8;
--border: #313244;
}
.theme-catppuccin-macchiato {
--bg-primary: #24273a;
--bg-secondary: #1e2030;
--text-primary: #cad3f5;
--text-secondary: #a5adcb;
--accent: #c6a0f6;
--border: #363a4f;
}
.theme-catppuccin-frappe {
--bg-primary: #303446;
--bg-secondary: #292c3c;
--text-primary: #c6d0f5;
--text-secondary: #a5adce;
--accent: #ca9ee6;
--border: #414559;
}
.theme-catppuccin-latte {
--bg-primary: #eff1f5;
--bg-secondary: #ffffff;
--text-primary: #4c4f69;
--text-secondary: #6c6f85;
--accent: #d20f39;
--border: #dcdfe6;
}
/*
* Derived tokens — computed once on <body> from the active theme vars so
* they adapt automatically to every theme without 11x repetition.
*/
body {
background-color: var(--bg-primary);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* hover/overlay surfaces */
--surface-hover: color-mix(in srgb, var(--text-primary) 9%, transparent);
--surface-overlay: rgba(0, 0, 0, 0.55);
/* stronger hairline */
--border-strong: color-mix(in srgb, var(--text-primary) 24%, var(--border));
/* soft accent tint (chips, active nav, subtle highlights) */
--accent-muted: color-mix(in srgb, var(--accent) 14%, var(--bg-secondary));
/* elevation */
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.10), 0 1px 3px rgba(0, 0, 0, 0.08);
--shadow-card-hover: 0 6px 16px rgba(0, 0, 0, 0.22), 0 2px 6px rgba(0, 0, 0, 0.12);
--shadow-pop: 0 12px 40px rgba(0, 0, 0, 0.34), 0 4px 12px rgba(0, 0, 0, 0.20);
--shadow-bar: 0 1px 0 rgba(0, 0, 0, 0.18), 0 4px 16px rgba(0, 0, 0, 0.16);
}
/* Slightly softer shadows on the lone light theme */
.theme-catppuccin-latte {
--shadow-card: 0 1px 2px rgba(76, 79, 105, 0.08), 0 1px 3px rgba(76, 79, 105, 0.06);
--shadow-card-hover: 0 6px 16px rgba(76, 79, 105, 0.16), 0 2px 6px rgba(76, 79, 105, 0.08);
--shadow-pop: 0 12px 40px rgba(76, 79, 105, 0.22), 0 4px 12px rgba(76, 79, 105, 0.12);
--shadow-bar: 0 1px 0 rgba(76, 79, 105, 0.08), 0 4px 16px rgba(76, 79, 105, 0.10);
}
/* App-wide keyboard focus affordance (accessibility) */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Hide Alpine-controlled elements until Alpine initialises */
[x-cloak] {
display: none !important;
}
/*
* 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;
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;
}
/* Grouped panels for the collapsible footer menus (account, appearance).
Each menu lives in its own subtly lifted, bordered box so that expanded
submenus stay visually contained and one menu never blends into another. */
.sidebar-panel {
background-color: color-mix(in srgb, var(--text-primary) 4%, var(--bg-secondary));
border: 1px solid var(--border);
border-radius: 0.75rem;
padding: 0.375rem;
}
/* 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 */
* {
scrollbar-color: color-mix(in srgb, var(--text-primary) 22%, transparent) transparent;
}
}
@layer components {
/* ---------- Surfaces ---------- */
.card {
background-color: var(--bg-secondary);
border: 1px solid color-mix(in srgb, var(--text-primary) 6%, transparent);
border-radius: 1rem;
box-shadow: var(--shadow-card);
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.625rem;
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease,
transform 0.15s ease, border-color 0.15s ease;
cursor: pointer;
white-space: nowrap;
}
.btn:active {
transform: translateY(1px);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background-color: var(--accent);
color: var(--bg-primary);
box-shadow: var(--shadow-card);
}
.btn-primary:hover {
background-color: color-mix(in srgb, var(--accent) 88%, white);
box-shadow: var(--shadow-card-hover);
}
.btn-secondary {
background-color: transparent;
color: var(--text-primary);
border: 1px solid var(--border-strong);
}
.btn-secondary:hover {
background-color: var(--surface-hover);
border-color: var(--accent);
color: var(--text-primary);
}
.btn-ghost {
background-color: transparent;
color: var(--text-secondary);
}
.btn-ghost:hover {
background-color: var(--surface-hover);
color: var(--text-primary);
}
.btn-danger {
background-color: var(--status-danger);
color: #fff;
}
.btn-danger:hover {
background-color: color-mix(in srgb, var(--status-danger) 88%, white);
}
/* ---------- Icon button (square, subtle) ---------- */
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.625rem;
color: var(--text-secondary);
transition: background-color 0.15s ease, color 0.15s ease;
cursor: pointer;
}
.icon-btn:hover {
background-color: var(--surface-hover);
color: var(--text-primary);
}
/* ---------- Inputs ---------- */
.input {
width: 100%;
background-color: var(--bg-primary);
color: var(--text-primary);
border: 1px solid var(--border);
border-radius: 0.625rem;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.input::placeholder {
color: color-mix(in srgb, var(--text-secondary) 75%, transparent);
}
.input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
outline: none;
}
/* ---------- Chips & badges ---------- */
.chip {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
background-color: var(--accent-muted);
color: var(--text-primary);
}
.badge {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.5rem;
border-radius: 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.01em;
}
/* ---------- Book card (the atomic unit) ---------- */
.book-card {
position: relative;
background-color: var(--bg-secondary);
border: 1px solid color-mix(in srgb, var(--text-primary) 6%, transparent);
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(-4px);
box-shadow: var(--shadow-card-hover);
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;
top: 0;
left: 0;
right: 0;
aspect-ratio: 2 / 3;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
z-index: 2;
transition: opacity 0.18s ease;
}
.book-card:hover .book-card-action,
.book-card:focus-within .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);
pointer-events: auto;
text-decoration: none;
}
.book-card-meta {
background-color: var(--bg-secondary);
padding: 0.5rem 0.625rem;
}
/* On touch devices there is no hover, so the quick-action stays visible and
only the circular button itself is clickable (the container passes clicks
through to the detail link). */
@media (hover: none) {
.book-card-action {
opacity: 1;
}
}
/* ---------- 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);
border: 1px solid var(--border);
}
.tristate-btn.state-null {
color: var(--text-secondary);
}
.tristate-btn.state-true {
background-color: var(--accent-muted);
border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
color: var(--accent);
}
.tristate-btn.state-false {
background-color: var(--surface-hover);
color: var(--text-secondary);
}
.loading-spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 3px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ---------- Status / priority semantics ---------- */
.status-pending {
background-color: color-mix(in srgb, var(--status-warning) 20%, transparent);
color: var(--status-warning);
}
.status-processing {
background-color: color-mix(in srgb, var(--status-info) 20%, transparent);
color: var(--status-info);
}
.status-completed {
background-color: color-mix(in srgb, var(--status-success) 20%, transparent);
color: var(--status-success);
}
.status-failed {
background-color: color-mix(in srgb, var(--status-danger) 20%, transparent);
color: var(--status-danger);
}
.priority-1 {
background-color: color-mix(in srgb, var(--status-danger) 22%, transparent);
color: var(--status-danger);
}
.priority-2 {
background-color: color-mix(in srgb, #f97316 22%, transparent);
color: #fb923c;
}
.priority-3 {
background-color: color-mix(in srgb, var(--status-warning) 22%, transparent);
color: var(--status-warning);
}
.priority-5 {
background-color: color-mix(in srgb, var(--status-success) 22%, transparent);
color: var(--status-success);
}
.priority-7 {
background-color: var(--surface-hover);
color: var(--text-secondary);
}
.priority-10 {
background-color: var(--surface-hover);
color: var(--text-secondary);
}
/* ---------- Docs / highlight.js overrides ---------- */
.prose pre code.hljs {
background-color: transparent !important;
}
.prose code.hljs {
background-color: var(--bg-primary) !important;
padding: 0.2em 0.4em;
}
.prose pre {
background-color: var(--bg-secondary) !important;
}
.prose .hljs {
background-color: transparent !important;
color: var(--text-primary) !important;
}
/* ---------- Theme / wood picker active states ---------- */
.bg-theme-active,
.bg-wood-active {
background-color: var(--bg-primary) !important;
box-shadow: inset 0 0 0 2px var(--accent);
}
.bg-theme-inactive,
.bg-wood-inactive {
background-color: var(--bg-secondary) !important;
}
/* ---------- Wood paneling (optional bookshelf background) ---------- */
.bg-wood-dark,
.bg-wood-light,
.bg-wood-mahogany {
background-attachment: fixed;
background-position: center;
background-size: cover;
}
.bg-wood-dark {
background-image: url(/static/textures/wood-dark.png);
}
.bg-wood-light {
background-image: url(/static/textures/wood-light.png);
}
.bg-wood-mahogany {
background-image: url(/static/textures/wood-mahogany.png);
}
body.bg-wood-dark,
body.bg-wood-light,
body.bg-wood-mahogany,
#collections-container.bg-wood-dark,
#collections-container.bg-wood-light,
#collections-container.bg-wood-mahogany {
background-attachment: fixed;
background-position: center;
background-size: cover;
}
/* Smart text colors for wood paneling backgrounds */
#collections-container[data-wood="wood-light"] {
--wood-text-primary: #1a1a1a;
--wood-text-secondary: #4a4a4a;
--wood-border: #8b5a2b;
}
#collections-container[data-wood="wood-dark"] {
--wood-text-primary: #f5f5f5;
--wood-text-secondary: #c0c0c0;
--wood-border: #7a5228;
}
#collections-container[data-wood="wood-mahogany"] {
--wood-text-primary: #f5f5f5;
--wood-text-secondary: #c0c0c0;
--wood-border: #8b3a3a;
}
#collections-container[data-wood] h1,
#collections-container[data-wood] h2,
#collections-container[data-wood] h3,
#collections-container[data-wood] p,
#collections-container[data-wood] span,
#collections-container[data-wood] a,
#collections-container[data-wood] button {
color: var(--wood-text-primary) !important;
}
#collections-container[data-wood] .text-secondary {
color: var(--wood-text-secondary) !important;
}
#collections-container[data-wood="wood-light"] a {
color: #0066cc !important;
}
#collections-container[data-wood="wood-dark"] a,
#collections-container[data-wood="wood-mahogany"] a {
color: #66ccff !important;
}
#collections-container[data-wood="wood-light"] .carousel-nav-left {
background: linear-gradient(to right, rgba(0, 0, 0, 0.6), transparent);
}
#collections-container[data-wood="wood-light"] .carousel-nav-right {
background: linear-gradient(to left, rgba(0, 0, 0, 0.6), transparent);
}
#collections-container[data-wood="wood-dark"] .carousel-nav-left,
#collections-container[data-wood="wood-mahogany"] .carousel-nav-left {
background: linear-gradient(to right, rgba(0, 0, 0, 0.3), transparent);
}
#collections-container[data-wood="wood-dark"] .carousel-nav-right,
#collections-container[data-wood="wood-mahogany"] .carousel-nav-right {
background: linear-gradient(to left, rgba(0, 0, 0, 0.3), transparent);
}
#collections-container[data-wood] .book-card {
border: 1px solid var(--wood-border);
}
/* ---------- Reader ---------- */
.reader-icon {
display: block;
fill: none;
stroke: currentColor;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
}
.dockable-panel {
background-color: var(--bg-secondary);
border-bottom: 1px solid var(--border);
transition: max-height 0.2s ease-out;
overflow: hidden;
}
.dockable-panel.panel-collapsed .panel-content {
display: none;
}
.panel-header {
user-select: none;
}
.panel-header:hover {
background-color: var(--surface-hover);
}
.panel-container {
background-color: var(--bg-secondary);
border-right: 1px solid var(--border);
width: 320px;
max-width: calc(100vw - 1rem);
max-height: 100%;
overflow-y: auto;
}
#right-sidebar .panel-container {
border-right: none;
border-left: 1px solid var(--border);
}
/* ---------- Series stacked covers ---------- */
.stacked-covers {
position: relative;
width: 100%;
aspect-ratio: 2 / 3;
overflow: hidden;
}
.stacked-covers .cover-img {
position: absolute;
width: 70%;
height: 90%;
object-fit: cover;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.stacked-covers.cover-count-1 .cover-0 {
top: 5%;
left: 15%;
}
.stacked-covers.cover-count-2 .cover-0 {
top: 8%;
left: 5%;
transform: rotate(-6deg);
z-index: 1;
}
.stacked-covers.cover-count-2 .cover-1 {
top: 2%;
left: 25%;
transform: rotate(3deg);
z-index: 2;
}
.stacked-covers.cover-count-3 .cover-0 {
top: 12%;
left: 2%;
transform: rotate(-8deg);
z-index: 1;
}
.stacked-covers.cover-count-3 .cover-1 {
top: 6%;
left: 15%;
transform: rotate(-2deg);
z-index: 2;
}
.stacked-covers.cover-count-3 .cover-2 {
top: 0;
left: 30%;
transform: rotate(4deg);
z-index: 3;
}
.stacked-covers.cover-count-4 .cover-0 {
top: 14%;
left: 0;
width: 65%;
height: 85%;
transform: rotate(-10deg);
z-index: 1;
}
.stacked-covers.cover-count-4 .cover-1 {
top: 10%;
left: 12%;
width: 65%;
height: 85%;
transform: rotate(-4deg);
z-index: 2;
}
.stacked-covers.cover-count-4 .cover-2 {
top: 5%;
left: 22%;
width: 65%;
height: 85%;
transform: rotate(2deg);
z-index: 3;
}
.stacked-covers.cover-count-4 .cover-3 {
top: 0;
left: 32%;
width: 65%;
height: 85%;
transform: rotate(6deg);
z-index: 4;
}
.stacked-covers.cover-count-5 .cover-0,
.stacked-covers.cover-count-6 .cover-0,
.stacked-covers.cover-count-7 .cover-0 {
top: 16%;
left: -2%;
width: 62%;
height: 82%;
transform: rotate(-12deg);
z-index: 1;
}
.stacked-covers.cover-count-5 .cover-1,
.stacked-covers.cover-count-6 .cover-1,
.stacked-covers.cover-count-7 .cover-1 {
top: 12%;
left: 8%;
width: 62%;
height: 82%;
transform: rotate(-6deg);
z-index: 2;
}
.stacked-covers.cover-count-5 .cover-2,
.stacked-covers.cover-count-6 .cover-2,
.stacked-covers.cover-count-7 .cover-2 {
top: 8%;
left: 17%;
width: 62%;
height: 82%;
transform: rotate(-1deg);
z-index: 3;
}
.stacked-covers.cover-count-5 .cover-3,
.stacked-covers.cover-count-6 .cover-3,
.stacked-covers.cover-count-7 .cover-3 {
top: 4%;
left: 26%;
width: 62%;
height: 82%;
transform: rotate(4deg);
z-index: 4;
}
.stacked-covers.cover-count-5 .cover-4,
.stacked-covers.cover-count-6 .cover-4,
.stacked-covers.cover-count-7 .cover-4 {
top: 0;
left: 35%;
width: 62%;
height: 82%;
transform: rotate(9deg);
z-index: 5;
}
.stacked-covers.cover-count-6 .cover-5,
.stacked-covers.cover-count-7 .cover-5 {
top: -2%;
left: 30%;
width: 60%;
height: 80%;
transform: rotate(13deg);
z-index: 6;
}
.stacked-covers.cover-count-7 .cover-6 {
top: -4%;
left: 25%;
width: 58%;
height: 78%;
transform: rotate(16deg);
z-index: 7;
}
.series-card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.series-card:hover {
transform: translateY(-4px);
}
/* ---------- Skeleton loading shimmer ---------- */
.skeleton {
position: relative;
overflow: hidden;
background-color: var(--surface-hover);
border-radius: 0.5rem;
}
.skeleton::after {
content: "";
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(
90deg,
transparent,
color-mix(in srgb, var(--text-primary) 8%, transparent),
transparent
);
animation: skeleton-shimmer 1.4s infinite;
}
@keyframes skeleton-shimmer {
100% {
transform: translateX(100%);
}
}
}