Files
bookorbit/client/src/assets/main.css
T
neon 870abecb01 feat(reader): polish the PDF reading experience
Make long PDF reading sessions easier to navigate across desktop and mobile. Keep document loading recoverable when EmbedPDF initializes or tears down out of order, without enabling document-editing capabilities.
2026-07-11 15:29:34 +00:00

770 lines
22 KiB
CSS

@import '@fontsource-variable/inter';
@import '@fontsource-variable/fraunces';
@import 'tailwindcss';
@import 'tw-animate-css';
@import './theme/tokens.css';
@import './theme/accents.css';
@import './theme/radius.css';
@import './theme/bridge.css';
@import './theme/cover-effects.css';
@custom-variant dark (&:is(.dark *));
.pattern-preview {
position: relative;
overflow: hidden;
}
.pattern-preview[class*='pattern-']::before {
position: absolute;
z-index: 1;
}
.pattern-dots {
background-image: radial-gradient(circle, var(--dot-color) 1px, transparent 1px);
background-size: 24px 24px;
}
.pattern-cross {
background-image: radial-gradient(circle, var(--dot-color) 1px, transparent 1px), radial-gradient(circle, var(--dot-color) 1px, transparent 1px);
background-size: 24px 24px;
background-position:
0 0,
12px 12px;
}
.pattern-aurora {
background-image:
radial-gradient(ellipse 60% 45% at 0% 50%, color-mix(in oklch, var(--primary) 15%, transparent), transparent 100%),
radial-gradient(ellipse 50% 40% at 100% 50%, color-mix(in oklch, var(--primary) 10%, transparent), transparent 100%),
radial-gradient(ellipse 40% 35% at 50% 0%, color-mix(in oklch, var(--primary) 8%, transparent), transparent 100%);
}
.pattern-horizon {
background-image: linear-gradient(180deg, transparent 0%, color-mix(in oklch, var(--primary) 8%, transparent) 50%, transparent 100%);
}
.pattern-topo {
background-image:
repeating-radial-gradient(
circle at 0% 0%,
transparent 0,
transparent 40px,
color-mix(in oklch, var(--dot-color) 50%, transparent) 40px,
color-mix(in oklch, var(--dot-color) 50%, transparent) 41px,
transparent 41px,
transparent 80px
),
repeating-radial-gradient(
circle at 100% 100%,
transparent 0,
transparent 40px,
color-mix(in oklch, var(--dot-color) 50%, transparent) 40px,
color-mix(in oklch, var(--dot-color) 50%, transparent) 41px,
transparent 41px,
transparent 80px
);
}
.pattern-mesh {
background-image:
radial-gradient(at 0% 0%, color-mix(in oklch, var(--primary) 20%, transparent) 0px, transparent 50%),
radial-gradient(at 100% 0%, color-mix(in oklch, var(--primary) 15%, transparent) 0px, transparent 50%),
radial-gradient(at 100% 100%, color-mix(in oklch, var(--primary) 20%, transparent) 0px, transparent 50%),
radial-gradient(at 0% 100%, color-mix(in oklch, var(--primary) 15%, transparent) 0px, transparent 50%),
radial-gradient(at 50% 50%, color-mix(in oklch, var(--primary) 10%, transparent) 0px, transparent 50%);
}
.pattern-glow {
background-image: radial-gradient(ellipse 80% 50% at 50% -10%, color-mix(in oklch, var(--primary) 20%, transparent) 0%, transparent 100%);
}
.pattern-brushed {
background-image: repeating-linear-gradient(
45deg,
transparent,
transparent 1px,
color-mix(in oklch, var(--primary) 6%, transparent) 1px,
color-mix(in oklch, var(--primary) 6%, transparent) 2px
);
}
.pattern-prism::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(135deg, rgba(255, 0, 0, 0.03) 0%, transparent 50%), linear-gradient(225deg, rgba(0, 0, 255, 0.03) 0%, transparent 50%),
linear-gradient(45deg, rgba(0, 255, 0, 0.03) 0%, transparent 50%);
filter: contrast(150%) brightness(110%);
}
.pattern-spectrum::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(135deg, oklch(0.7 0.15 var(--tint-h) / 0.08) 0%, transparent 50%),
linear-gradient(225deg, oklch(0.7 0.15 calc(var(--tint-h) + 120) / 0.08) 0%, transparent 50%),
linear-gradient(45deg, oklch(0.7 0.15 calc(var(--tint-h) + 240) / 0.08) 0%, transparent 50%);
filter: contrast(160%) brightness(110%);
}
.pattern-spectrum-x::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(135deg, oklch(0.7 0.15 var(--tint-h) / 0.08) 0%, transparent 50%),
linear-gradient(225deg, oklch(0.7 0.15 calc(var(--tint-h) + 90) / 0.08) 0%, transparent 50%),
linear-gradient(315deg, oklch(0.7 0.15 calc(var(--tint-h) + 180) / 0.08) 0%, transparent 50%),
linear-gradient(45deg, oklch(0.7 0.15 calc(var(--tint-h) + 270) / 0.08) 0%, transparent 50%);
filter: contrast(160%) brightness(110%);
}
.pattern-spectrum-plus::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(to bottom, oklch(0.7 0.15 var(--tint-h) / 0.08) 0%, transparent 50%),
linear-gradient(to left, oklch(0.7 0.15 calc(var(--tint-h) + 90) / 0.08) 0%, transparent 50%),
linear-gradient(to top, oklch(0.7 0.15 calc(var(--tint-h) + 180) / 0.08) 0%, transparent 50%),
linear-gradient(to right, oklch(0.7 0.15 calc(var(--tint-h) + 270) / 0.08) 0%, transparent 50%);
filter: contrast(160%) brightness(110%);
}
.pattern-eclipse::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
radial-gradient(at 0% 0%, oklch(0.7 0.15 var(--tint-h) / 0.12) 0%, transparent 65%),
radial-gradient(at 100% 100%, oklch(0.7 0.15 calc(var(--tint-h) + 180) / 0.12) 0%, transparent 65%);
filter: contrast(150%) brightness(110%);
}
.pattern-millimeter::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(to right, color-mix(in oklch, var(--primary) 5%, transparent) 1px, transparent 1px),
linear-gradient(to bottom, color-mix(in oklch, var(--primary) 5%, transparent) 1px, transparent 1px);
background-size: 2px 2px;
}
.pattern-elevation::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(to bottom, color-mix(in oklch, var(--primary) 5%, transparent), transparent 40%),
linear-gradient(to top, color-mix(in oklch, var(--primary) 3%, transparent), transparent 30%);
}
.pattern-terminal::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
opacity: 0.05;
background-image: repeating-linear-gradient(90deg, var(--primary), var(--primary) 1px, transparent 1px, transparent 40px);
}
.pattern-scanlines::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background: linear-gradient(to bottom, transparent 50%, color-mix(in oklch, var(--primary) 5%, transparent) 50%);
background-size: 100% 4px;
}
.pattern-vinyl::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image: repeating-radial-gradient(
circle at 50% -25%,
transparent,
transparent 3px,
color-mix(in oklch, var(--primary) 8%, transparent) 3.75px,
transparent 4.5px
);
}
.pattern-carbon::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(45deg, color-mix(in oklch, var(--primary) 5%, transparent) 25%, transparent 25%),
linear-gradient(-45deg, color-mix(in oklch, var(--primary) 5%, transparent) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, color-mix(in oklch, var(--primary) 5%, transparent) 75%),
linear-gradient(-45deg, transparent 75%, color-mix(in oklch, var(--primary) 5%, transparent) 75%);
background-size: 8px 8px;
}
.pattern-perforated::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image: radial-gradient(circle at center, color-mix(in oklch, var(--primary) 8%, transparent) 1.5px, transparent 2px);
background-size: 12px 12px;
}
.pattern-blueprint::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
linear-gradient(to right, color-mix(in oklch, var(--primary) 4%, transparent) 1px, transparent 1px),
linear-gradient(to bottom, color-mix(in oklch, var(--primary) 4%, transparent) 1px, transparent 1px);
background-size: 4px 4px;
}
.pattern-wireframe::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
opacity: 0.1;
background-color: var(--primary);
-webkit-mask-image: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 50l50-50 50 50-50 50z' fill='none' stroke='black' stroke-width='0.5'/%3E%3Cpath d='M50 0v100M0 50h100' stroke='black' stroke-width='0.5'/%3E%3Ccircle cx='50' cy='50' r='2' fill='black'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 50l50-50 50 50-50 50z' fill='none' stroke='black' stroke-width='0.5'/%3E%3Cpath d='M50 0v100M0 50h100' stroke='black' stroke-width='0.5'/%3E%3Ccircle cx='50' cy='50' r='2' fill='black'/%3E%3C/svg%3E");
mask-size: 150px 150px;
}
.glow-wrapper {
background-image: radial-gradient(50% 50% at center -25px, color-mix(in oklch, var(--primary) 35%, transparent) 0%, transparent 100%);
background-blend-mode: hard-light, color-dodge;
background-position: top;
background-repeat: no-repeat;
background-size: auto 12rem;
}
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-color: var(--scrollbar-thumb) transparent;
}
html,
body,
#app,
.app-shell,
.app-shell-inset,
.app-shell-scroll {
max-width: 100%;
overflow-x: clip;
overscroll-behavior-x: none;
}
@supports not (overflow: clip) {
html,
body,
#app,
.app-shell,
.app-shell-inset,
.app-shell-scroll {
overflow-x: hidden;
}
}
body {
@apply bg-background text-foreground font-sans antialiased;
}
@supports (-webkit-touch-callout: none) {
@media (pointer: coarse) {
/* iOS Safari auto-zooms focused inputs below 16px. Keep editable controls at 16px on touch devices. */
input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='color']):not([type='file']):not([type='button']):not(
[type='submit']
):not([type='reset']):not([data-ios-zoom-exempt]),
textarea:not([data-ios-zoom-exempt]),
select:not([data-ios-zoom-exempt]) {
font-size: 16px;
}
}
}
@media (pointer: coarse) {
.app-shell-inset,
.app-shell-scroll {
touch-action: pan-y;
}
/* Prevent double-tap zoom on tappable controls while preserving normal taps. */
a,
button,
[role='button'] {
touch-action: manipulation;
}
}
/* Webkit scrollbar styles */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border: 2px solid transparent;
background-clip: padding-box;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover);
border: 2px solid transparent;
background-clip: padding-box;
}
}
@layer components {
.viewer-btn {
@apply flex items-center justify-center w-8 h-8 rounded text-foreground/60 hover:text-foreground hover:bg-muted transition-colors shrink-0 disabled:opacity-30 disabled:cursor-not-allowed;
}
.viewer-sep {
@apply w-px h-5 mx-1 bg-border shrink-0;
}
/* ── Settings typography scale ──────────────────────────────
Change these once to update every settings panel at once.
────────────────────────────────────────────────────────────── */
.settings-title {
@apply font-serif font-semibold text-foreground text-2xl tracking-tight;
}
.settings-subtitle {
@apply text-sm text-muted-foreground mt-1;
}
.settings-group-label {
@apply text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3;
}
.settings-label {
@apply text-sm font-medium text-foreground;
}
.settings-hint {
@apply text-xs text-muted-foreground mt-0.5;
}
.settings-value {
@apply text-sm font-medium tabular-nums text-foreground;
}
/* ── Settings form controls ─────────────────────────────────
Use .input-field on <input> and .select-field on <select>
to get consistent sizing and focus rings across all tabs.
────────────────────────────────────────────────────────────── */
.input-field {
@apply h-9 rounded-md border border-input bg-background px-3 text-sm text-foreground
placeholder:text-muted-foreground/70
focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary
disabled:opacity-50 transition-colors;
}
.select-field {
@apply h-9 rounded-md border border-input bg-background px-3 text-sm text-foreground cursor-pointer
focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary
disabled:opacity-50 transition-colors;
}
/* ── Settings action buttons ─────────────────────────────────
Use these on <button> elements in settings panels (headers,
card rows, inline forms). Matches the Kobo "Add device" size.
────────────────────────────────────────────────────────────── */
.settings-btn-primary {
@apply inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium shrink-0
bg-primary text-primary-foreground hover:bg-primary/90
disabled:opacity-50 transition-colors;
}
.settings-btn-outline {
@apply inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium shrink-0
border border-border bg-background text-foreground hover:bg-muted
disabled:opacity-50 transition-colors;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
}
/* ── Micro-animation keyframes ───────────────────────────────
Shared motion vocabulary used across all components.
Wrapped in prefers-reduced-motion so users who opt out
get instant transitions instead.
──────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes shimmer {
from {
background-position: -400px 0;
}
to {
background-position: 400px 0;
}
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-3px);
}
75% {
transform: translateX(3px);
}
}
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-4px);
}
}
@keyframes count-pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
@keyframes progress-stripes {
from {
background-position: 1rem 0;
}
to {
background-position: 0 0;
}
}
.animate-fade-in {
animation: fade-in 0.25s ease-out both;
}
.animate-fade-up {
animation: fade-up 0.3s ease-out both;
}
.animate-scale-in {
animation: scale-in 0.25s ease-out both;
}
.animate-shimmer {
background: linear-gradient(90deg, transparent 0%, color-mix(in oklch, var(--foreground) 6%, transparent) 50%, transparent 100%);
background-size: 400px 100%;
animation: shimmer 1.5s infinite linear;
}
.animate-shake {
animation: shake 0.3s ease-in-out;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
.animate-count-pulse {
animation: count-pulse 0.2s ease-out;
}
.animate-progress-stripes {
background-image: linear-gradient(
45deg,
rgba(255, 255, 255, 0.1) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.1) 75%,
transparent 75%,
transparent
);
background-size: 1rem 1rem;
animation: progress-stripes 0.5s linear infinite;
}
}
/* Page route transitions */
.page-enter-active {
transition: opacity 0.2s ease-out;
}
.page-leave-active {
transition: opacity 0.15s ease-in;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
}
/* Tab content transitions */
.tab-enter-active {
transition:
opacity 0.15s ease-out,
transform 0.15s ease-out;
}
.tab-leave-active {
transition:
opacity 0.1s ease-in,
transform 0.1s ease-in;
}
.tab-enter-from {
opacity: 0;
transform: translateX(4px);
}
.tab-leave-to {
opacity: 0;
transform: translateX(-4px);
}
/* Content crossfade (skeleton → content) */
.content-enter-active {
transition: opacity 0.25s ease-out;
}
.content-leave-active {
transition: opacity 0.15s ease-in;
}
.content-enter-from,
.content-leave-to {
opacity: 0;
}
/* Sidebar section collapse/expand */
.section-enter-active {
transition:
max-height 0.25s ease-out,
opacity 0.25s ease-out;
overflow: hidden;
}
.section-leave-active {
transition:
max-height 0.2s ease-in,
opacity 0.2s ease-in;
overflow: hidden;
}
.section-enter-from,
.section-leave-to {
max-height: 0;
opacity: 0;
}
.section-enter-to,
.section-leave-from {
max-height: 100dvh;
opacity: 1;
}
/* Search dropdown entrance */
.search-drop-enter-active {
transition:
opacity 0.2s ease-out,
transform 0.2s ease-out;
}
.search-drop-leave-active {
transition: opacity 0.15s ease-in;
}
.search-drop-enter-from {
opacity: 0;
transform: translateY(-4px);
}
.search-drop-leave-to {
opacity: 0;
}
/* ── Driver.js tour ─────────────────────────────────────────── */
.driver-popover {
background-color: var(--card) !important;
color: var(--card-foreground) !important;
border: 1px solid var(--border) !important;
border-radius: var(--radius) !important;
box-shadow: var(--elevation-xl) !important;
padding: 1rem 1.125rem !important;
min-width: 260px !important;
max-width: 310px !important;
font-family: inherit !important;
}
.driver-popover-title {
font-size: 0.875rem !important;
font-weight: 600 !important;
color: var(--card-foreground) !important;
margin-bottom: 0.25rem !important;
line-height: 1.4 !important;
letter-spacing: -0.01em !important;
}
.driver-popover-description {
font-size: 0.8125rem !important;
line-height: 1.55 !important;
color: var(--muted-foreground) !important;
}
.driver-popover-footer {
margin-top: 0.875rem !important;
gap: 1rem !important;
padding-top: 0.625rem !important;
border-top: 1px solid var(--border) !important;
}
.driver-popover-progress-text {
font-size: 0.6875rem !important;
color: var(--muted-foreground) !important;
opacity: 0.65 !important;
font-variant-numeric: tabular-nums !important;
}
.driver-popover-prev-btn,
.driver-popover-prev-btn:focus {
gap: 1rem !important;
all: unset !important;
display: inline-flex !important;
align-items: center !important;
padding: 0.3125rem 0.625rem !important;
border-radius: calc(var(--radius) - 0.125rem) !important;
border: 1px solid var(--border) !important;
background: transparent !important;
color: var(--foreground) !important;
font-size: 0.8125rem !important;
font-weight: 500 !important;
font-family: inherit !important;
cursor: pointer !important;
transition: background-color 0.15s ease !important;
text-shadow: none !important;
}
.driver-popover-prev-btn:hover {
background: var(--muted) !important;
}
.driver-popover-next-btn,
.driver-popover-next-btn:focus {
all: unset !important;
display: inline-flex !important;
align-items: center !important;
padding: 0.3125rem 0.75rem !important;
border-radius: calc(var(--radius) - 0.125rem) !important;
background: var(--primary) !important;
color: var(--primary-foreground) !important;
border: none !important;
font-size: 0.8125rem !important;
font-weight: 500 !important;
font-family: inherit !important;
cursor: pointer !important;
transition: opacity 0.15s ease !important;
text-shadow: none !important;
}
.driver-popover-next-btn:hover {
opacity: 0.88 !important;
}
.driver-popover-navigation-btns {
gap: 0.5rem !important;
}
.driver-popover-close-btn,
.driver-popover-close-btn:focus {
all: unset !important;
position: absolute !important;
top: 0.625rem !important;
right: 0.75rem !important;
color: var(--muted-foreground) !important;
font-size: 1rem !important;
line-height: 1 !important;
cursor: pointer !important;
transition: color 0.15s ease !important;
opacity: 0.7 !important;
}
.driver-popover-close-btn:hover {
color: var(--foreground) !important;
opacity: 1 !important;
}
/* Arrow: match popover card background on all sides */
.driver-popover-arrow-side-left.driver-popover-arrow {
border-right-color: var(--card) !important;
}
.driver-popover-arrow-side-right.driver-popover-arrow {
border-left-color: var(--card) !important;
}
.driver-popover-arrow-side-top.driver-popover-arrow {
border-bottom-color: var(--card) !important;
}
.driver-popover-arrow-side-bottom.driver-popover-arrow {
border-top-color: var(--card) !important;
}