/* 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 (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 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; } /* 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 { 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; 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); 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; } 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-height: calc(100vh - 8rem); overflow-y: auto; } .panel-container[data-side="right"] { 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%); } } }