flipbookengine 0.0.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,160 @@
1
- # flipbookengine
2
-
3
- This package name is currently reserved for an active project in development. The initial version will be published soon.
1
+ # FlipbookEngine
2
+
3
+ FlipbookEngine is a modern, lightweight, and embeddable HTML flipbook viewer for PDF-backed catalogs, brochures, and digital publications. Written in TypeScript, it features single-page and spread modes, a thumbnail rail, smooth zoom/pan, full internationalization (i18n), and robust theme customization.
4
+
5
+ ## Highlights
6
+
7
+ - **Dual-Mode Layout**: Seamlessly switches between single-page (mobile-optimized) and spread (book/catalog layout) modes.
8
+ - **Rich Interaction**: Built-in thumbnail navigation rail, smooth zoom & pan, and HTML5 fullscreen support.
9
+ - **Theme Engine**: Styled with compiled CSS and CSS Custom Properties, featuring full support for light/dark modes (auto-responsive to Bootstrap 5 or tailorable per instance).
10
+ - **Multi-Language (i18n)**: Out-of-the-box support for English (`en`) and Turkish (`tr`) with customizable overrides.
11
+ - **Framework Wrappers**: Direct React and Vue wrapper exports for seamless modern integration.
12
+
13
+ ## Installation
14
+
15
+ Install via npm:
16
+
17
+ ```bash
18
+ npm install flipbookengine
19
+ ```
20
+
21
+ For direct CDN usage in the browser (Vanilla HTML/JS):
22
+
23
+ ```html
24
+ <link rel="stylesheet" href="https://unpkg.com/flipbookengine/dist/flipbook-engine.css" />
25
+ <script src="https://unpkg.com/flipbookengine/dist/flipbook-engine.iife.js"></script>
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### Vanilla HTML / JS
31
+
32
+ Include the styles and script, then initialize the engine:
33
+
34
+ ```html
35
+ <div id="viewer" style="width: 100%; height: 600px;"></div>
36
+
37
+ <script>
38
+ const engine = new FlipbookEngine('#viewer', {
39
+ allowDownload: true,
40
+ showThumbs: true,
41
+ primaryColor: '#7367f0',
42
+ theme: 'auto',
43
+ locale: 'en'
44
+ });
45
+
46
+ engine.init('/files/catalog.pdf', [
47
+ { kind: 'cover', pageNumber: 1, normal: '/img/1.jpg', low: '/img/1-low.jpg', thumb: '/img/1-thumb.jpg' },
48
+ { kind: 'spread', pageNumbers: [2, 3], normal: '/img/2-3.jpg', low: '/img/2-3-low.jpg', thumb: '/img/2-3-thumb.jpg' }
49
+ ]);
50
+ </script>
51
+ ```
52
+
53
+ ### React
54
+
55
+ ```tsx
56
+ import React, { useRef } from 'react';
57
+ import { Flipbook, FlipbookRef } from 'flipbookengine/react';
58
+ import 'flipbookengine/dist/flipbook-engine.css';
59
+
60
+ function CatalogViewer() {
61
+ const flipbookRef = useRef<FlipbookRef>(null);
62
+
63
+ const handleNextPage = () => {
64
+ flipbookRef.current?.goToPage(flipbookRef.current.getCurrentPage() + 1);
65
+ };
66
+
67
+ return (
68
+ <Flipbook
69
+ ref={flipbookRef}
70
+ pdfUrl="/files/catalog.pdf"
71
+ pages={[
72
+ { kind: 'cover', pageNumber: 1, normal: '/img/1.jpg', low: '/img/1-low.jpg', thumb: '/img/1-thumb.jpg' },
73
+ { kind: 'spread', pageNumbers: [2, 3], normal: '/img/2-3.jpg', low: '/img/2-3-low.jpg', thumb: '/img/2-3-thumb.jpg' }
74
+ ]}
75
+ allowDownload={true}
76
+ showThumbs={true}
77
+ primaryColor="#7367f0"
78
+ />
79
+ );
80
+ }
81
+ ```
82
+
83
+ ### Vue 3
84
+
85
+ ```vue
86
+ <template>
87
+ <Flipbook
88
+ pdfUrl="/files/catalog.pdf"
89
+ :pages="pages"
90
+ :allowDownload="true"
91
+ :showThumbs="true"
92
+ primaryColor="#7367f0"
93
+ />
94
+ </template>
95
+
96
+ <script setup>
97
+ import { Flipbook } from 'flipbookengine/vue';
98
+ import 'flipbookengine/dist/flipbook-engine.css';
99
+
100
+ const pages = [
101
+ { kind: 'cover', pageNumber: 1, normal: '/img/1.jpg', low: '/img/1-low.jpg', thumb: '/img/1-thumb.jpg' },
102
+ { kind: 'spread', pageNumbers: [2, 3], normal: '/img/2-3.jpg', low: '/img/2-3-low.jpg', thumb: '/img/2-3-thumb.jpg' }
103
+ ];
104
+ </script>
105
+ ```
106
+
107
+ ## Public API Reference
108
+
109
+ The `FlipbookEngine` class exposes the following public methods:
110
+
111
+ - **`init(pdfUrl: string, imageList?: FlipbookPageAsset[])`**: Initializes the viewer and loads the flipbook with pages.
112
+ - **`goToPage(pageIndex: number)`**: Navigates programmatically to a 0-based page index.
113
+ - **`getCurrentPage()`**: Returns the 0-based index of the currently active page.
114
+ - **`getTotalPages()`**: Returns the total number of pages.
115
+ - **`getZoom()`**: Returns the current zoom level (default is `1`).
116
+ - **`setZoom(zoomLevel: number)`**: Programmatically sets the zoom level (accepts values between `0.5` and `5`).
117
+ - **`setSingleMode(isSingle: boolean)`**: Programmatically toggles between single-page mode (`true`) and double-page spread mode (`false`).
118
+ - **`toggleFullscreen()`**: Programmatically toggles fullscreen mode.
119
+ - **`updateOptions(options: Partial<FlipbookEngineOptions>)`**: Updates instance options at runtime.
120
+ - **`setLocale(locale: string, messages?: PartialFlipbookMessages)`**: Updates the locale programmatically.
121
+ - **`destroy(clearMarkup = true)`**: Tears down the instance and clean up DOM listeners.
122
+
123
+ ### Subscribing to Events
124
+
125
+ You can subscribe to events using `.on()`:
126
+
127
+ ```ts
128
+ const unsubscribe = engine.on('pageChange', ({ currentPage, totalPages, isSingle }) => {
129
+ console.log('Page Changed:', currentPage, 'Total:', totalPages);
130
+ });
131
+
132
+ // To unsubscribe:
133
+ unsubscribe();
134
+ ```
135
+
136
+ Supported events: `pageChange`, `zoomChange`, `singlePageModeChange`, `thumbsToggle`, `destroy`.
137
+
138
+ ## Styling and Theming
139
+
140
+ FlipbookEngine features fully custom-property-based styling compatible with modern styling systems.
141
+
142
+ ### Global CSS Variables
143
+
144
+ ```css
145
+ :root {
146
+ --mainbgcolor: #f3f6fb;
147
+ --panelbgcolor: #172033;
148
+ --accentcolor: #7367f0;
149
+ --thumbrailbgcolor: #0f172a;
150
+ }
151
+ ```
152
+
153
+ ## Licensing
154
+
155
+ FlipbookEngine is distributed under a dual-license model:
156
+
157
+ - **Open Source**: GNU Affero General Public License v3.0 or later (see [LICENSE](./LICENSE)).
158
+ - **Commercial**: For use in closed-source projects, proprietary SaaS platforms, or agency client deliveries (see [COMMERCIAL_LICENSE.md](./COMMERCIAL_LICENSE.md)).
159
+
160
+ For commercial licenses and inquiries, contact: **murat.dogan@hotmail.com.tr**
@@ -0,0 +1,61 @@
1
+ # Third-Party Notices
2
+
3
+ This file contains the licenses of the third-party libraries used in the FlipbookEngine project.
4
+
5
+ ## Runtime Dependencies
6
+
7
+ ### 1. PDF.js (pdfjs-dist)
8
+ - **Website:** https://mozilla.github.io/pdf.js/
9
+ - **Repository:** https://github.com/mozilla/pdf.js
10
+ - **License:** Apache License 2.0
11
+
12
+ ```text
13
+ Copyright 2012 Mozilla Foundation
14
+
15
+ Licensed under the Apache License, Version 2.0 (the "License");
16
+ you may not use this file except in compliance with the License.
17
+ You may obtain a copy of the License at
18
+
19
+ http://www.apache.org/licenses/LICENSE-2.0
20
+
21
+ Unless required by applicable law or agreed to in writing, software
22
+ distributed under the License is distributed on an "AS IS" BASIS,
23
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
+ See the License for the specific language governing permissions and
25
+ limitations under the License.
26
+ ```
27
+
28
+ ---
29
+
30
+ ### 2. page-flip
31
+ - **Website:** https://nodlik.github.io/StPageFlip/
32
+ - **Repository:** https://github.com/Nodlik/StPageFlip
33
+ - **License:** MIT License
34
+
35
+ ```text
36
+ The MIT License (MIT)
37
+ Copyright (c) 2020 Oleg
38
+
39
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
40
+ this software and associated documentation files (the "Software"), to deal in
41
+ the Software without restriction, including without limitation the rights to
42
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
43
+ the Software, and to permit persons to whom the Software is furnished to do so,
44
+ subject to the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be included in all
47
+ copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
51
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
52
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
53
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
54
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Development Dependencies
60
+
61
+ Development dependencies such as `vite`, `typescript`, and their respective plugins are licensed under the MIT License. These tools are used during the build process and are not distributed as part of the compiled FlipbookEngine bundle.
@@ -0,0 +1,50 @@
1
+ # FlipbookEngine Trademark Policy
2
+
3
+ This policy governs use of the FlipbookEngine name, logos, and brand assets.
4
+
5
+ ## Covered Marks
6
+
7
+ The following are treated as protected brand assets:
8
+
9
+ - FlipbookEngine
10
+ - FlipbookEngine logos
11
+ - any official product lockups, icons, and visual identity files distributed with the project
12
+
13
+ ## What the Open Source License Does Not Grant
14
+
15
+ The AGPL license applies to source code, not to trademark rights.
16
+
17
+ Using the FlipbookEngine code does not automatically grant permission to:
18
+
19
+ - present your product as the official FlipbookEngine product
20
+ - use FlipbookEngine logos in your own branding
21
+ - imply sponsorship, certification, or endorsement
22
+ - register confusingly similar names, domains, or product marks
23
+
24
+ ## Allowed Uses
25
+
26
+ Unless otherwise stated, you may:
27
+
28
+ - refer to FlipbookEngine by name in factual descriptions
29
+ - state that your project uses or is powered by FlipbookEngine
30
+ - link to the official project and documentation
31
+
32
+ ## Uses That Require Permission
33
+
34
+ You must get permission before:
35
+
36
+ - using official logos in a product UI or marketing page
37
+ - using FlipbookEngine as part of your own product or company name
38
+ - presenting your fork as the official project
39
+ - using the marks in paid advertising or marketplace listings
40
+
41
+ ## Open Source Attribution
42
+
43
+ Attribution is appreciated in open source projects, but trademark permission is still separate from code licensing.
44
+
45
+ ## Contact
46
+
47
+ Trademark and brand permission inquiries:
48
+
49
+ - murat.dogan@hotmail.com.tr
50
+
@@ -0,0 +1,128 @@
1
+ import { FlipbookThemeMode } from './theme/theme';
2
+ import { FlipbookLocale, PartialFlipbookMessages } from './i18n/service';
3
+ import { FlipbookPageAsset } from './model/pages';
4
+
5
+ export interface FlipbookEngineOptions {
6
+ allowDownload?: boolean;
7
+ showThumbs?: boolean;
8
+ primaryColor?: string;
9
+ soundEnabled?: boolean;
10
+ soundUrl?: string;
11
+ backgroundColor?: string;
12
+ backgroundImage?: string;
13
+ whiteLabel?: boolean;
14
+ watermarkUrl?: string;
15
+ theme?: FlipbookThemeMode;
16
+ locale?: FlipbookLocale | string;
17
+ messages?: Partial<Record<string, PartialFlipbookMessages>>;
18
+ className?: string;
19
+ cssVariables?: Record<string, string>;
20
+ onDownload?: (url: string) => void;
21
+ }
22
+ export interface PageImages extends FlipbookPageAsset {
23
+ pageNumber: number;
24
+ normal: string;
25
+ low: string;
26
+ thumb: string;
27
+ }
28
+ export interface FlipbookEngineEventMap {
29
+ init: {
30
+ totalPages: number;
31
+ };
32
+ destroy: undefined;
33
+ pageChange: {
34
+ currentPage: number;
35
+ pageNumber: number;
36
+ totalPages: number;
37
+ isSingle: boolean;
38
+ };
39
+ zoomChange: {
40
+ zoom: number;
41
+ isActive: boolean;
42
+ };
43
+ thumbsToggle: {
44
+ showThumbs: boolean;
45
+ };
46
+ singlePageModeChange: {
47
+ isSingle: boolean;
48
+ };
49
+ orientationChange: {
50
+ orientation: 'landscape' | 'portrait';
51
+ };
52
+ }
53
+ export type FlipbookEngineEventName = keyof FlipbookEngineEventMap;
54
+ type FlipbookEventHandler<T extends FlipbookEngineEventName> = (payload: FlipbookEngineEventMap[T]) => void;
55
+ export declare class FlipbookEngine {
56
+ private selector;
57
+ private options;
58
+ private static readonly DEFAULT_PAGE_VIEWPORT;
59
+ private container;
60
+ private ui;
61
+ private pageFlip;
62
+ private sourceAssets;
63
+ private pages;
64
+ private downloadUrl;
65
+ private totalPages;
66
+ private pageViewport;
67
+ private pdfDoc;
68
+ private pdfPagesCache;
69
+ private currentZoom;
70
+ private zoomState;
71
+ private messages;
72
+ private listeners;
73
+ private domEventController;
74
+ private orientationMediaQuery;
75
+ private orientationChangeHandler;
76
+ private initialized;
77
+ private state;
78
+ private touchStartX;
79
+ constructor(selector: string | HTMLElement, options?: FlipbookEngineOptions);
80
+ init(pdfUrl: string, imageList?: Array<PageImages | FlipbookPageAsset>): Promise<void>;
81
+ on<T extends FlipbookEngineEventName>(eventName: T, handler: FlipbookEventHandler<T>): () => void;
82
+ off<T extends FlipbookEngineEventName>(eventName: T, handler: FlipbookEventHandler<T>): void;
83
+ getCurrentPage(): number;
84
+ getTotalPages(): number;
85
+ getLocale(): string;
86
+ goToPage(targetIdx: number): void;
87
+ getZoom(): number;
88
+ setZoom(zoomLevel: number): void;
89
+ toggleFullscreen(): void;
90
+ destroy(clearMarkup?: boolean): void;
91
+ updateOptions(nextOptions: Partial<FlipbookEngineOptions>): void;
92
+ setLocale(locale: FlipbookLocale | string, messages?: PartialFlipbookMessages): void;
93
+ setPages(imageList: Array<PageImages | FlipbookPageAsset>, pdfUrl?: string): void;
94
+ private buildUI;
95
+ private mountViewer;
96
+ private renderEmptyState;
97
+ private teardownMountedViewer;
98
+ private setResolvedPages;
99
+ private resolvePageViewport;
100
+ private loadImageSize;
101
+ private setupOrientationHandling;
102
+ private teardownRuntimeBindings;
103
+ private getEventSignal;
104
+ private playSound;
105
+ private applyThemeConfiguration;
106
+ private renderPagesIntoDOM;
107
+ private initPageFlip;
108
+ private createPageImage;
109
+ private createThumbnailImage;
110
+ private ensurePageSrc;
111
+ private getPageImageClassName;
112
+ private getThumbnailImageClassName;
113
+ setSingleMode(isSingle: boolean): void;
114
+ private updateSingleView;
115
+ private syncUI;
116
+ private goNext;
117
+ private goPrev;
118
+ private loadHighResForVisiblePages;
119
+ private bindEvents;
120
+ private handleDragStart;
121
+ private handleDragMove;
122
+ private handleDragEnd;
123
+ private updateTransform;
124
+ private emit;
125
+ private emitPageChange;
126
+ private emitZoomChange;
127
+ }
128
+ export {};
@@ -0,0 +1 @@
1
+ :root{--flipbook-bg-main: var(--bs-body-bg, #f8f9fa);--flipbook-bg-surface: var(--bs-secondary-bg, #ffffff);--flipbook-bg-surface-alt: var(--bs-tertiary-bg, #f1f3f5);--flipbook-bg-panel: var(--bs-dark, #212529);--flipbook-bg-panel-soft: color-mix(in srgb, var(--flipbook-bg-panel) 88%, white 12%);--flipbook-text-main: var(--bs-body-color, #212529);--flipbook-text-muted: var(--bs-secondary-color, #6c757d);--flipbook-toolbar-text: #f8f9fa;--flipbook-toolbar-button-text: #f8f9fa;--flipbook-border: var(--bs-border-color, rgba(0, 0, 0, .15));--flipbook-accent: var(--bs-primary, #0d6efd);--flipbook-accent-text: #ffffff;--flipbook-thumbrail-bg: color-mix(in srgb, var(--flipbook-bg-panel) 95%, black 5%);--flipbook-thumb-bg: #ffffff;--flipbook-thumb-text: #ffffff;--flipbook-thumbscroll-track: rgba(0, 0, 0, .18);--flipbook-thumbscroll-thumb: rgba(255, 255, 255, .22);--flipbook-navbutton-bg: rgba(0, 0, 0, .6);--flipbook-navbutton-text: #ffffff;--flipbook-shadow: rgba(0, 0, 0, .35);--flipbook-bookpage-bg: #ffffff;--flipbook-emptycover-bg: var(--flipbook-bg-panel);--flipbook-download-bg: #198754;--flipbook-download-border: #198754;--flipbook-download-text: #ffffff;--flipbook-settings-bg: var(--flipbook-bg-surface);--flipbook-settings-text: var(--flipbook-text-main);--flipbook-radius-sm: .375rem;--flipbook-radius-md: .5rem;--flipbook-toolbar-height: 60px;--flipbook-thumbs-height: 110px;--flipbook-navbutton-size: 50px;--flipbook-page-padding: 20px;--flipbook-font-family: var(--bs-body-font-family, system-ui, -apple-system, "Segoe UI", sans-serif)}[data-bs-theme=dark]{--flipbook-bg-main: var(--bs-body-bg, #121826);--flipbook-bg-surface: var(--bs-secondary-bg, #1b2232);--flipbook-bg-surface-alt: var(--bs-tertiary-bg, #222b3c);--flipbook-bg-panel: #141927;--flipbook-bg-panel-soft: #1d2434;--flipbook-text-main: var(--bs-body-color, #f8f9fa);--flipbook-text-muted: var(--bs-secondary-color, #adb5bd);--flipbook-toolbar-text: #f8f9fa;--flipbook-toolbar-button-text: #f8f9fa;--flipbook-border: var(--bs-border-color, rgba(255, 255, 255, .12));--flipbook-thumbrail-bg: #101522;--flipbook-thumb-bg: #ffffff;--flipbook-settings-bg: #1d2434;--flipbook-settings-text: #f8f9fa;--flipbook-emptycover-bg: #101522;--flipbook-shadow: rgba(0, 0, 0, .5)}.bk-theme-light{color-scheme:light;--flipbook-bg-main: #f8fafc;--flipbook-bg-surface: #ffffff;--flipbook-bg-surface-alt: #f1f5f9;--flipbook-bg-panel: #e2e8f0;--flipbook-bg-panel-soft: #f1f5f9;--flipbook-text-main: #0f172a;--flipbook-text-muted: #64748b;--flipbook-toolbar-text: #0f172a;--flipbook-toolbar-button-text: #334155;--flipbook-border: rgba(15, 23, 42, .1);--flipbook-accent: #4f46e5;--flipbook-accent-text: #ffffff;--flipbook-thumbrail-bg: #f8fafc;--flipbook-thumb-bg: #ffffff;--flipbook-thumb-text: #0f172a;--flipbook-thumbscroll-track: rgba(15, 23, 42, .08);--flipbook-thumbscroll-thumb: rgba(15, 23, 42, .18);--flipbook-navbutton-bg: rgba(15, 23, 42, .65);--flipbook-navbutton-text: #ffffff;--flipbook-shadow: rgba(15, 23, 42, .08);--flipbook-bookpage-bg: #ffffff;--flipbook-emptycover-bg: #e2e8f0;--flipbook-download-bg: #10b981;--flipbook-download-border: #10b981;--flipbook-download-text: #ffffff;--flipbook-settings-bg: #ffffff;--flipbook-settings-text: #0f172a}.bk-theme-dark{color-scheme:dark;--flipbook-bg-main: #0b0f19;--flipbook-bg-surface: #111827;--flipbook-bg-surface-alt: #1f2937;--flipbook-bg-panel: #111827;--flipbook-bg-panel-soft: #1f2937;--flipbook-text-main: #f9fafb;--flipbook-text-muted: #9ca3af;--flipbook-toolbar-text: #f9fafb;--flipbook-toolbar-button-text: #e5e7eb;--flipbook-border: rgba(255, 255, 255, .08);--flipbook-accent: #7367f0;--flipbook-accent-text: #ffffff;--flipbook-thumbrail-bg: #0b0f19;--flipbook-thumb-bg: #1f2937;--flipbook-thumb-text: #f9fafb;--flipbook-thumbscroll-track: rgba(255, 255, 255, .08);--flipbook-thumbscroll-thumb: rgba(255, 255, 255, .18);--flipbook-navbutton-bg: rgba(0, 0, 0, .6);--flipbook-navbutton-text: #ffffff;--flipbook-shadow: rgba(0, 0, 0, .5);--flipbook-bookpage-bg: #ffffff;--flipbook-emptycover-bg: #101522;--flipbook-download-bg: #10b981;--flipbook-download-border: #10b981;--flipbook-download-text: #ffffff;--flipbook-settings-bg: #1f2937;--flipbook-settings-text: #f9fafb}.bk-container,.bk-container *{box-sizing:border-box}.bk-container{color:var(--flipbook-text-main);font-family:var(--flipbook-font-family)}.bk-wrapper{position:relative;width:100%;height:100%;display:flex;flex-direction:column;background:var(--flipbook-bg-main);color:var(--flipbook-text-main);overflow:hidden;font-family:var(--flipbook-font-family);border-radius:var(--flipbook-radius-md)}.bk-main-area{flex:1;position:relative;overflow:hidden;width:100%;min-height:0;background:var(--flipbook-bg-main)}#bk-book-wrapper{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--flipbook-page-padding);display:flex;justify-content:center;align-items:center;transition:transform .3s ease;transform-origin:center center}#bk-book-wrapper.zoomed{transition:none;cursor:grab}#bk-book-wrapper.zoomed:active{cursor:grabbing}#bk-book-wrapper.zoomed #bk-book{pointer-events:none}#bk-book{width:100%;height:100%;box-shadow:0 0 20px var(--flipbook-shadow)}#bk-single-view{display:none;width:100%;height:100%;justify-content:center;align-items:center;overflow:hidden}#bk-single-img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;box-shadow:0 5px 20px var(--flipbook-shadow);background:var(--flipbook-bookpage-bg);transition:transform .3s ease,opacity .3s ease;pointer-events:none}.slide-left{transform:translate(-100%);opacity:0}.slide-right{transform:translate(100%);opacity:0}.bz-page{background-color:var(--flipbook-bg-main);overflow:hidden}.page-content{width:100%;height:100%;object-fit:contain;pointer-events:none;background:var(--flipbook-bookpage-bg);display:block}.page-content--split{width:100%;max-width:none;height:100%;object-fit:cover;object-position:left center}.page-content--left{object-position:left center}.page-content--right{object-position:right center}#bk-single-img.page-content--split{width:100%;height:100%;max-width:none;max-height:none;object-fit:cover}.bk-toolbar{height:var(--flipbook-toolbar-height);background:var(--flipbook-bg-panel-soft);color:var(--flipbook-toolbar-text);display:flex;align-items:center;justify-content:space-between;padding:0 15px;z-index:10;flex-shrink:0;border-top:1px solid var(--flipbook-border);gap:.75rem}.bk-btn-group{display:flex;gap:8px;align-items:center}.bk-btn-group--center{flex-grow:1;justify-content:center}.bk-btn{background:color-mix(in srgb,var(--flipbook-bg-surface) 18%,transparent 82%);border:1px solid color-mix(in srgb,var(--flipbook-border) 80%,transparent 20%);color:var(--flipbook-toolbar-button-text);padding:0 15px;height:38px;border-radius:var(--flipbook-radius-sm);cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center;transition:background-color .2s ease,border-color .2s ease,color .2s ease}.bk-btn:hover,.bk-btn.active{background:var(--flipbook-accent);border-color:var(--flipbook-accent);color:var(--flipbook-accent-text)}.bk-btn:focus-visible,.thumb-item:focus-visible,.nav-float:focus-visible{outline:2px solid var(--flipbook-accent);outline-offset:2px}.bk-btn--download{background:var(--flipbook-download-bg);border-color:var(--flipbook-download-border);color:var(--flipbook-download-text)}#bk-page-info{color:var(--flipbook-toolbar-text);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:14px;min-width:70px;text-align:center;font-weight:700}.bk-thumbs{height:var(--flipbook-thumbs-height);background:var(--flipbook-thumbrail-bg);overflow-x:auto;padding:15px;gap:15px;z-index:10;flex-shrink:0;align-items:center;border-top:1px solid var(--flipbook-border)}.bk-thumbs::-webkit-scrollbar{height:6px}.bk-thumbs::-webkit-scrollbar-track{background:var(--flipbook-thumbscroll-track);border-radius:999px}.bk-thumbs::-webkit-scrollbar-thumb{background:var(--flipbook-thumbscroll-thumb);border-radius:999px;transition:background-color .2s ease}.bk-thumbs::-webkit-scrollbar-thumb:hover{background:var(--flipbook-accent)}.thumb-item{display:flex;flex-direction:column;align-items:center;cursor:pointer;opacity:.68;transition:opacity .2s ease,transform .2s ease;flex-shrink:0;color:var(--flipbook-thumb-text)}.thumb-item:hover,.thumb-item.active{opacity:1}.thumb-item.active img{border-color:var(--flipbook-accent);transform:scale(1.05);box-shadow:0 0 15px color-mix(in srgb,var(--flipbook-accent) 45%,transparent 55%)}.thumb-img{height:60px;width:auto;border:2px solid transparent;border-radius:var(--flipbook-radius-sm);background:var(--flipbook-thumb-bg);transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease}.thumb-img--split{width:96px;max-width:none;object-fit:cover}.thumb-img--left{object-position:left center}.thumb-img--right{object-position:right center}.thumb-item span{color:var(--flipbook-thumb-text);font-size:11px;margin-top:6px;font-weight:700}.nav-float{position:absolute;z-index:10;background:var(--flipbook-navbutton-bg);border:none;border-radius:999px;width:var(--flipbook-navbutton-size);height:var(--flipbook-navbutton-size);color:var(--flipbook-navbutton-text);font-size:20px;cursor:pointer;transition:background-color .2s ease;top:50%;transform:translateY(-50%)}.nav-float:hover{background:var(--flipbook-accent)}#nav-prev{left:20px}#nav-next{right:20px}.bk-settings-panel{position:absolute;bottom:70px;right:15px;background:var(--flipbook-settings-bg);color:var(--flipbook-settings-text);border:1px solid var(--flipbook-border);border-radius:var(--flipbook-radius-md);padding:15px;display:none;z-index:20;min-width:200px;box-shadow:0 10px 30px var(--flipbook-shadow)}.setting-item{display:flex;align-items:center;justify-content:space-between;gap:1rem;font-size:14px;margin-bottom:10px}.setting-item:last-child{margin-bottom:0}.bk-empty-state{display:grid;place-items:center;width:100%;min-height:240px;padding:1.5rem;background:var(--flipbook-bg-main);color:var(--flipbook-text-main);border:1px dashed var(--flipbook-border);border-radius:var(--flipbook-radius-md)}.bk-empty-state__title{margin:0;font-size:1.125rem;font-weight:600}.empty-cover{background-color:var(--flipbook-emptycover-bg)!important;box-shadow:none!important}.bk-attribution{position:absolute;bottom:15px;left:15px;background:#0f172abf;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#fff;padding:6px 12px;border-radius:var(--flipbook-radius-sm);font-size:11px;text-decoration:none;z-index:20;display:flex;align-items:center;gap:6px;box-shadow:0 4px 12px #0006;border:1px solid rgba(255,255,255,.15);transition:all .2s ease;opacity:.85}.bk-attribution:hover{opacity:1;transform:translateY(-2px);box-shadow:0 6px 16px #0009;border-color:var(--flipbook-accent);color:#fff}.bk-attribution-icon{font-size:13px}.bk-attribution-img{height:24px;width:auto;object-fit:contain;display:block}@media (max-width: 768px){:root{--flipbook-toolbar-height: 50px;--flipbook-thumbs-height: 85px;--flipbook-navbutton-size: 35px;--flipbook-page-padding: 12px}.bk-toolbar{padding:0 5px}.bk-btn{padding:0 8px;font-size:12px;height:32px}.bk-btn-group{gap:4px}#bk-page-info{min-width:50px;font-size:12px}.nav-float{font-size:16px}#nav-prev{left:10px}#nav-next{right:10px}.bk-thumbs{padding:10px}.thumb-img{height:45px}.bk-settings-panel{bottom:60px}}