PageSourceSearch

https://www.simkunaites-fondas.lt/wp-content/plugins/smartembed-ad…/assets/frontend.js?ver=1786346466

js simkunaites-fondas.lt collected 2026-10-03 00:18:08 UTC 101,206 bytes, 1,887 lines download raw bytes

1(function() {
2    'use strict';
3
4    // We check for pdfjsLib inside the initialization functions instead of at the root level
5
6    function initSmartEmbedViewer(container) {
7        let options;
8        try {
9            options = JSON.parse(container.getAttribute('data-options'));
10        } catch (e) {
11            console.error('Invalid options', e);
12            return;
13        }
14
15        // Thumbnail Mode Check
16        if (options.thumbnailMode && options.thumbnailUrl) {
17            container.innerHTML = `
18                <div class="smartembed-thumbnail-wrapper" style="position:relative; cursor:pointer; text-align:center;">
19                    <img src="${options.thumbnailUrl}" style="max-width:100%; height:auto; display:block; margin:0 auto; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius:4px;" alt="PDF Cover">
20                    <div class="smartembed-play-btn" style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); background:rgba(0,0,0,0.6); color:#fff; border-radius:50%; width:64px; height:64px; display:flex; align-items:center; justify-content:center; box-shadow: 0 4px 10px rgba(0,0,0,0.3); transition: background 0.3s;">
21                        <svg viewBox="0 0 24 24" width="32" height="32" stroke="currentColor" stroke-width="2" fill="white" style="margin-left: 4px;"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
22                    </div>
23                </div>
24            `;
25            
26            const wrapper = container.querySelector('.smartembed-thumbnail-wrapper');
27            const playBtn = container.querySelector('.smartembed-play-btn');
28            wrapper.addEventListener('mouseenter', () => playBtn.style.background = 'rgba(0,0,0,0.8)');
29            wrapper.addEventListener('mouseleave', () => playBtn.style.background = 'rgba(0,0,0,0.6)');
30            
31            wrapper.addEventListener('click', () => {
32                container.innerHTML = '<div style="padding: 40px; text-align: center; color: #666;">Loading PDF...</div>';
33                options.thumbnailMode = false;
34                container.setAttribute('data-options', JSON.stringify(options));
35                initSmartEmbedViewer(container);
36            });
37            return;
38        }
39
40        // Force Single Page Mode (Continuous) on mobile/tablet devices
41        if (window.innerWidth <= 850) {
42            options.mode = 'continuous';
43        }
44
45        // Don't override container dimensions - keep the grey background from shortcode HTML
46        // Only apply protection styles
47        if (options.fullscreen) container.style.position = 'relative';
48
49        // Apply Protection
50        if (options.noSelect) {
51            container.style.userSelect = 'none';
52            container.style.webkitUserSelect = 'none';
53        }
54        if (options.noRightClick) {
55            container.addEventListener('contextmenu', e => e.preventDefault());
56        }
57
58        // Build Template Selector UI if enabled via Elementor
59        let templatesHtml = '';
60        if (options.allowedTemplates) {
61            const allowedArr = options.allowedTemplates.split(',').filter(Boolean);
62            if (allowedArr.length > 1) { // Only show if more than 1 enabled
63                if (!options.currentTemplate) {
64                    options.currentTemplate = allowedArr[0]; // Set default
65                }
66                const activeTpl = options.currentTemplate;
67                
68                // Map templates to viewer modes
69                if (activeTpl === 'vertical_reader') options.mode = 'continuous';
70                else if (activeTpl === 'slider') options.mode = 'presentation';
71                else options.mode = 'flipbook';
72
73                templatesHtml = `
74                    <div class="smartembed-template-selector">
75                        <div class="smartembed-template-title">Select Flipbook Template</div>
76                        <div class="smartembed-template-grid">
77                            ${allowedArr.includes('3d_flipbook') ? `<div class="smartembed-tpl-card ${activeTpl==='3d_flipbook'?'active':''}" data-tpl="3d_flipbook">
78                                <div class="smartembed-tpl-icon">
79                                    <img src="${window.smartEmbedFront ? window.smartEmbedFront.pluginUrl : ''}assets/Images/3d%20book%20icon%20image.png" alt="3D Flipbook" class="smartembed-tpl-img" />
80                                </div>
81                                <div class="smartembed-tpl-name">3D Flipbook</div>
82                                <div class="smartembed-tpl-desc">3D Flip Depth</div>
83                            </div>` : ''}
84                            ${allowedArr.includes('2d_flipbook') ? `<div class="smartembed-tpl-card ${activeTpl==='2d_flipbook'?'active':''}" data-tpl="2d_flipbook">
85                                <div class="smartembed-tpl-icon">
86                                    <img src="${window.smartEmbedFront ? window.smartEmbedFront.pluginUrl : ''}assets/Images/2d%20book%20icon%20image.png" alt="2D Flipbook" class="smartembed-tpl-img" />
87                                </div>
88                                <div class="smartembed-tpl-name">
882D Flipbook</div>
89                                <div class="smartembed-tpl-desc">Flat 2D Page Flip</div>
90                            </div>` : ''}
91                            ${allowedArr.includes('hard_cover') ? `<div class="smartembed-tpl-card ${activeTpl==='hard_cover'?'active':''}" data-tpl="hard_cover">
92                                <div class="smartembed-tpl-icon">
93                                    <img src="${window.smartEmbedFront ? window.smartEmbedFront.pluginUrl : ''}assets/Images/Hard%20Cover%20book%20icon%20image.png" alt="Hard Cover" class="smartembed-tpl-img" />
94                                </div>
95                                <div class="smartembed-tpl-name">Hard Cover</div>
96                                <div class="smartembed-tpl-desc">Realistic Book</div>
97                            </div>` : ''}
98                            ${allowedArr.includes('binder') ? `<div class="smartembed-tpl-card ${activeTpl==='binder'?'active':''}" data-tpl="binder">
99                                <div class="smartembed-tpl-icon">
100                                    <img src="${window.smartEmbedFront ? window.smartEmbedFront.pluginUrl : ''}assets/Images/Binding%20Cover%20book%20icon%20image.png" alt="Binder" class="smartembed-tpl-img" />
101                                </div>
102                                <div class="smartembed-tpl-name">Binder Flipbook</div>
103                                <div class="smartembed-tpl-desc">3D Spiral Binding</div>
104                            </div>` : ''}
105
106                            ${allowedArr.includes('vertical_reader') ? `<div class="smartembed-tpl-card ${activeTpl==='vertical_reader'?'active':''}" data-tpl="vertical_reader">
107                                <div class="smartembed-tpl-icon">
108                                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>
109                                </div>
110                                <div class="smartembed-tpl-name">Vertical Reader</div>
111                                <div class="smartembed-tpl-desc">Scroll Pages</div>
112                            </div>` : ''}
113                        </div>
114                    </div>
115                `;
116            } else if (allowedArr.length === 1) {
117                // If only 1 template enabled, just set the mode automatically without showing UI
118                options.currentTemplate = allowedArr[0];
119                if (options.currentTemplate === 'vertical_reader') options.mode = 'continuous';
120                else if (options.currentTemplate === 'slider') options.mode = 'presentation';
121                else options.mode = 'flipbook';
122            }
123        }
124
125        // Build UI
126        container.innerHTML = `
127            ${templatesHtml}
128            <div class="smartembed-pdf-viewer-ui custom-premium-layout ${options.darkMode ? 'smartembed-dark-mode' : ''}">
129                <div class="smartembed-pdf-scroll-area ${options.mode === 'flipbook' ? 'flipbook-mode' : ''}">
130                    <div class="smartembed-pdf-pages-container"></div>
131                </div>
132                
133                <!-- Thumbnails Sidebar -->
134                <div class="smartembed-thumbnails-sidebar">
135                    <div class="smartembed-thumbnails-header">
136                        <span>THUMBNAILS</span>
137                        <button class="smartembed-thumbnails-close">✕</button>
138                    </div>
139                    <div class="smartembed-thumbnails-content"></div>
140                </div>
141
142                ${options.toc ? `
143                <div class="smartembed-toc-sidebar">
144                    <div class="smartembed-toc-header">
145                        <span>TABLE OF CONTENTS</span>
146                        <button class="smartembed-toc-close">✕</button>
147                    </div>
148                    <div class="smartembed-toc-content"></div>
149                </div>
150                ` : ''}
151
152                <!-- Navigation Arrows -->
153                <button class="smartembed-nav-arrow smartembed-prev-page" title="Previous Page">
154                    <svg viewBox="0 0 24 24" width="40" height="40" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
155                </button>
156                <button class="smartembed-nav-arrow smartembed-next-page" title="Next Page">
157                    <svg viewBox="0 0 24 24" width="40" height="40" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
158                </button>
159
160                <!-- Floating Bottom Toolbar -->
161                <div class="smartembed-toolbar-wrapper">
162                    <div class="smartembed-bottom-toolbar">
163                        <div class="smartembed-page-info">
164                        <input type="text" class="smartembed-current-page" value="1" />/<span class="smartembed-page-count">--</span>
165                    </div>
166                    <button class="smartembed-tool-btn smartembed-toggle-thumbnails" title="Thumbn
166ails">
167                        <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
168                    </button>
169                    ${options.toc ? `
170                    <button class="smartembed-tool-btn smartembed-toggle-toc" title="Table of Contents">
171                        <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
172                    </button>
173                    ` : ''}
174                    <button class="smartembed-tool-btn smartembed-zoom-in" title="Zoom In"><svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg></button>
175                    <button class="smartembed-tool-btn smartembed-zoom-out" title="Zoom Out"><svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg></button>
176                    ${options.fullscreen ? '<button class="smartembed-tool-btn smartembed-fullscreen-btn" title="Fullscreen"><svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg></button>' : ''}
177                    ${options.print ? '<button class="smartembed-tool-btn smartembed-print-btn" title="Print"><svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg></button>' : ''}
178                    
179                    ${options.share ? '<div class="smartembed-options-menu-container smartembed-share-container"><button class="smartembed-tool-btn smartembed-share-btn" title="Share"><svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg></button><div class="smartembed-options-dropdown smartembed-share-dropdown" style="right: auto; left: 0; transform: translateX(-40%);"><button class="smartembed-dropdown-item smartembed-share-fb"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg> Facebook</button><button class="smartembed-dropdown-item smartembed-share-tw"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg> Twitter / X</button><button class="smartembed-dropdown-item smartembed-share-wa"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg> WhatsApp</button><button class="smartembed-dropdown-item smartembed-share-li"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg> LinkedIn</button><button class="smartembed-dropdown-item smartembed-share-copy"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> Copy Link</button></div></div>' : ''}
180
181                    <div class="smartembed-options-menu-container">
182                        <button class="smartembed-tool-btn smartembed-options-btn" title="More Options">
183                            <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
184                        </button>
185                        <div class="smartembed-options-dropdown">
186                            <!-- Premium Features injected here -->
187                            <button class="smartembed-dropdown-item smartembed-toggle-mode"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg> Single Page Mode</button>
188                            <button class="smartembed-dropdown-item smartembed-goto-first"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg> Goto First Page</button>
189                            <button class="smartembed-dropdown-item smartembed-goto-last"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg> Goto Last Page</button>
190                            <button class="smartembed-dropdown-item smartembed-tts-btn"><svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg> <span>Turn on/off Sound</span></button>
191                        </div>
192                    </div>
193                </div>
194            </div>
195
196                ${options.watermark ? '<div class="smartembed-watermark-overlay">' + options.watermarkText + '</div>' : ''}
197            </div>
198        `;
199        
200        // ---------- Layout Setup ----------
201        // Measure the template selector (if rendered) to compute the correct heights.
202        // We do this synchronously right after innerHTML is set so the layout is clean.
203        const templateSelector = container.querySelector('.smartembed-template-selector');
204        let templateSelectorH = 0;
205        if (templateSelector) {
206            // Force a layout so offsetHeight is accurate
207            templateSelector.style.display = 'block';
208            templateSelectorH = templateSelector.offsetHeight || 160;
209        }
210        
211        // The PDF viewer height = total container height - template selector height
212        const ui = container.querySelector('.smartembed-pdf-viewer-ui');
213        
214        // Ensure the container itself is tall enough: base is options.height (e.g. 750px)
215        // we do NOT add templateSelectorH here; we just let the UI shrink.
216        // The UI uses absolute height = containerHeight - templateSelectorH.
217        const baseViewerH = parseInt(options.height || 750);
218        const totalContainerH = baseViewerH + templateSelectorH;
219        container.style.setProperty('height', totalContainerH + 'px', 'important');
220        container.style.setProperty('min-height', totalContainerH + 'px', 'important');
221
222        // Set the viewer UI to fill only the PDF viewer portion
223        ui.style.height = baseViewerH + 'px';
224        ui.style.minHeight = baseViewerH + 'px';
225        ui.style.flexShrink = '0';
226        
227        const scrollArea = container.querySelector('.smartembed-pdf-scroll-area');
228        const pagesContainer = container.querySelector('.smartembed-pdf-pages-container');
229        const pageNumEl = container.querySelector('.smartembed-current-page');
230        const pageCountEl = container.querySelector('.smartembed-page-count');
231        const prevBtn = container.querySelector('.smartembed-prev-page');
232        const nextBtn = container.querySelector('.smartembed-next-page');
233        const zoomInBtn = container.querySelector('.smartembed-zoom-in');
234        const zoomOutBtn = container.querySelector('.smartembed-zoom-out');
235        const fullscreenBtn = container.querySelector('.smartembed-fullscreen-btn');
236        const printBtn = container.querySelector('.smartembed-print-btn');
237        const shareBtn = container.querySelector('.smartembed-share-btn');
238        const ttsBtn = container.querySelector('.smartembed-tts-btn');
239        const optionsBtn = container.querySelector('.smartembed-options-btn');
240        const toggleThumbnailsBtn = container.querySelector('.smartembed-toggle-thumbnails');
241        const closeThumbnailsBtn = container.querySelector('.smartembed-thumbnails-close');
242        const thumbnailsSidebar = container.querySelector('.smartembed-thumbnails-sidebar');
243        const thumbnailsContent = container.querySelector('.smartembed-thumbnails-content');
244        
245        const toggleTocBtn = container.querySelector('.smartembed-toggle-toc');
246        const closeTocBtn = container.querySelector('.smartembed-toc-close');
247        const tocSidebar = container.querySelector('.smartembed-toc-sidebar');
248        const tocContent = container.querySelector('.smartembed-toc-content');
249        const gotoLastBtn = container.querySelector('.smartembed-goto-last');
250        const gotoFirstBtn = container.querySelector('.smartembed-goto-first');
251        const toggleModeBtn = container.querySelector('.smartembed-toggle-mode');
252
253        if (toggleModeBtn && options.mode === 'continuous') {
254            toggleModeBtn.innerHTML = toggleModeBtn.innerHTML.replace('Single Page Mode', 'Flipbook Mode');
255        }
256
257        let pdfDoc = null;
258        let numPages = 0;
259        let scale = 1.0;
260        let renderTask = null;
261        let pageRendering = false;
262        let pageNumPending = null;
263        let currentPage = 1;
264        let pageFlipInstance = null;
265        let flipZoom = 1.0;
266        let flipPanX = 0;
267        let flipPanY = 0;
268        let flipPageDivs = [];  // hoisted so zoom re-render can access canvases
269        let cachedCanvases = [];
270        let cachedFitScale = 0;
271        let baseFitScale = 1;   // the fitScale calculated at init time
272        let soundEnabled = options.enablePaginationSound !== false && options.enablePaginationSound !== 'false' && options.enablePaginationSound !== 0 && options.enablePaginationSound !== '0'; // pagination sound toggle state
273
274        // --- Pagination Sound Setup ---
275        let pageFlipAudio = null;
276        if (window.smartEmbedFront && window.smartEmbedFront.pluginUrl) {
277            pageFlipAudio = new Audio(window.smartEmbedFront.pluginUrl + 'assets/audio/Pagination sounds.mp3');
278            pageFlipAudio.preload = 'auto';
279            pageFlipAudio.volume = 0.7;
280        }
281
282        // Apply combined centering + pan + scale to the flipbook container
283        function applyFlipbookTransform(centerXPct) {
284            const coverWrapper = pagesContainer.querySelector('.smartembed-book-cover-3d');
285            const flipContainer = pagesContainer.querySelector('.smartembed-flipbook-container');
286            const targetEl = coverWrapper || flipContainer;
287            if (!targetEl) return;
288            if (centerXPct === null || centerXPct === undefined) {
289                centerXPct = 0;
290            }
291            const zoomFactor = (flipZoom && flipZoom !== 1.0) ? flipZoom : 1;
292            targetEl.style.transform =
293                `translate(calc(${centerXPct}px + ${flipPanX}px), ${flipPanY}px) scale(${zoomFactor})`;
294        }
295
296        // Re-render all flipbook canvases at baseFitScale * flipZoom for crisp quality
297        let _reRenderTimer = null;
298        function reRenderFlipbookPages() {
299            if (!pdfDoc || flipPageDivs.length === 0) return;
300            clearTimeout(_reRenderTimer);
301            _reRenderTimer = setTimeout(() => {
302                const renderDpr = Math.max(window.devicePixelRatio || 1, 2.5); // Slightly higher for zoom
303                const renderScale = baseFitScale * flipZoom * renderDpr;
304                flipPageDivs.forEach(({ canvas, index }) => {
305                    pdfDoc.getPage(index).then(pdfPage => {
306                        const vp = pdfPage.getViewport({ scale: renderScale });
307                        const ctx = canvas.getContext('2d');
308                        canvas.width = vp.width;
309                        canvas.height = vp.height;
310                        const cssW = Math.round(vp.width / renderDpr);
311                        const cssH = Math.round(vp.height / renderDpr);
312                        canvas.style.width = cssW + 'px';
313                        canvas.style.height = cssH + 'px';
314                        ctx.setTransform(1, 0, 0, 1, 0, 0);
315                        pdfPage.render({
316                            canvasContext: ctx,
317                            viewport: vp
318                        });
319                    });
320                });
321            }, 150); // debounce: wait for rapid zoom clicks to settle
322        }
323
324        // Handle direct page input navigation
325        if (pageNumEl) {
326            pageNumEl.addEventListener('change', () => {
327                const rawVal = pageNumEl.value.split('-')[0].trim();
328                let val = parseInt(rawVal, 10);
329                if (!val || isNaN(val)) {
330                    val = 1;
331                }
332                if (val < 1) val = 1;
333                if (val > numPages) val = numPages;
334
335                currentPage = val;
336                queueRenderPage(currentPage);
337            });
338
339            pageNumEl.addEventListener('keydown', (e) => {
340                if (e.key === 'Enter') {
341                    pageNumEl.blur();
342                }
343            });
344
345            pageNumEl.addEventListener('click', () => {
346                pageNumEl.select();
347            });
348        }
349
350        // Restore resume page
351        if (options.resume) {
352            const savedPage = localStorage.getItem('smartembed_page_' + options.url);
353            if (savedPage) currentPage = parseInt(savedPage, 10);
354        }
355
356        let imageGallery = null;
357        if (options.images) {
358            imageGallery = options.images.split(',').map(s => s.trim()).filter(s => s);
359        }
360
361        function updateNavigationArrows() {
362            if (options.mode === 'continuous') {
363                if (prevBtn) prevBtn.style.display = 'none';
364                if (nextBtn) nextBtn.style.display = 'none';
365            } else {
366                if (prevBtn) prevBtn.style.display = 'flex';
367                if (nextBtn) nextBtn.style.display = 'flex';
368            }
369        }
370
371        function initViewerWithDoc(pdf) {
372            pdfDoc = pdf;
373            numPages = pdf.numPages || pdfDoc.numPages;
374            pageCountEl.textContent = numPages;
375            buildThumbnails();
376            buildToc();
377
378            updateNavigationArrows();
379
380            if (options.mode === 'continuous') {
381                scrollArea.classList.remove('flipbook-mode');
382                renderAllPages();
383            } else if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
384                scrollArea.classList.add('flipbook-mode');
385                renderFlipbook();
386            } else {
387                scrollArea.classList.remove('flipbook-mode');
388                renderPage(currentPage);
389            }
390            
391            // Apply Watermark Overlay
392            if (options.watermark && options.watermarkText) {
393                const watermarkDiv = document.createElement('div');
394                watermarkDiv.className = 'smartembed-watermark-overlay';
395                watermarkDiv.textContent = options.watermarkText;
396                watermarkDiv.style.position = 'absolute';
397                watermarkDiv.style.pointerEvents = 'none';
398                watermarkDiv.style.zIndex = '9999';
399                watermarkDiv.style.color = 'rgba(150, 150, 150, 0.3)';
400                watermarkDiv.style.fontWeight = 'bold';
401                watermarkDiv.style.whiteSpace = 'nowrap';
402                
403                if (options.watermarkPlacement === 'tiled') {
404                    watermarkDiv.style.top = '0';
405                    watermarkDiv.style.left = '0';
406                    watermarkDiv.style.width = '100%';
407                    watermarkDiv.style.height = '100%';
408                    watermarkDiv.style.backgroundImage = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='200' width='200'><text x='50%' y='50%' transform='translate(-50%, -50%) rotate(-45, 100, 100)' fill='rgba(150, 150, 150, 0.2)' font-size='24' font-family='Arial' text-anchor='middle'>${encodeURIComponent(options.watermarkText)}</text></svg>")`;
409                    watermarkDiv.textContent = ''; // Use SVG background instead
410                } else if (options.watermarkPlacement === 'diagonal') {
411                    watermarkDiv.style.top = '50%';
412                    watermarkDiv.style.left = '50%';
413                    watermarkDiv.style.transform = 'translate(-50%, -50%) rotate(-45deg)';
414                    watermarkDiv.style.fontSize = '60px';
415                } else {
416                    // Center
417                    watermarkDiv.style.top = '50%';
418                    watermarkDiv.style.left = '50%';
419                    watermarkDiv.style.transform = 'translate(-50%, -50%)';
420                    watermarkDiv.style.fontSize = '40px';
421                }
422                
423                scrollArea.appendChild(watermarkDiv);
424                scrollArea.style.position = 'relative';
425            }
426            
427            // Analytics: Track View
428            if (options.bookId && options.ajaxUrl) {
429                var formData = new FormData();
430                formData.append('action', 'smartembed_track_event');
431                formData.append('event_type', 'view');
432                formData.append('book_id', options.bookId);
433                fetch(options.ajaxUrl, { method: 'POST', body: formData }).catch(e => {});
434
435                // Analytics: Track Max Page Scrolled
436                let maxPageReached = 1;
437                function updateMaxPage(p) {
438                    if (p > maxPageReached) maxPageReached = p;
439                }
440                
441                // Hook into pageNumEl changes
442                if (pageNumEl) {
443                    const observer = new MutationObserver(() => {
444                        let current = parseInt(pageNumEl.value || pageNumEl.textContent);
445                        if (!isNaN(current)) updateMaxPage(current);
446                    });
447                    observer.observe(pageNumEl, { attributes: true, childList: true, characterData: true, subtree: true });
448                    pageNumEl.addEventListener('change', () => {
449                        let current = parseInt(pageNumEl.value);
450                        if (!isNaN(current)) updateMaxPage(current);
451                    });
452                }
453
454                // Send beacon on unload
455                window.addEventListener('beforeunload', () => {
456                    if (maxPageReached > 1) {
457                        var fd = new FormData();
458                        fd.append('action', 'smartembed_track_event');
459                        fd.append('event_type', 'scroll');
460                        fd.append('book_id', options.bookId);
461                        fd.append('max_page', maxPageReached);
462                        fd.append('total_pages', numPages);
463                        navigator.sendBeacon(options.ajaxUrl, fd);
464                    }
465                });
466            }
467        }
468
469        if (imageGallery && imageGallery.length > 0) {
470            const mockPdfDoc = {
471                numPages: imageGallery.length,
472                getPage: function(num) {
473                    return new Promise((resolve) => {
474                        const imgUrl = imageGallery[num - 1];
475                        const img = new Image();
476                        img.src = imgUrl;
477                        img.onload = () => {
478                            resolve({
479                                getViewport: ({scale}) => ({ width: img.naturalWidth * scale, height: img.naturalHeight * scale, scale }),
480                                render: ({canvasContext, viewport}) => {
481                                    canvasContext.drawImage(img, 0, 0, viewport.width, viewport.height);
482                                    return { promise: Promise.resolve() };
483                                },
484                                getAnnotations: () => Promise.resolve([]),
485                                getTextContent: () => Promise.resolve({ items: [] }),
486                            });
487                        };
488                        img.onerror = () => {
489                            resolve({
490                                getViewport: ({scale}) => ({ width: 800 * scale, height: 1100 * scale, scale }),
491                                render: ({canvasContext, viewport}) => {
492                                    canvasContext.fillStyle = '#f0f0f0';
493                                    canvasContext.fillRect(0, 0, viewport.width, viewport.height);
494                                    return { promise: Promise.resolve() };
495                                },
496                                getAnnotations: () => Promise.resolve([]),
497                                getTextContent: () => Promise.resolve({ items: [] }),
498                            });
499                        };
500                    });
501                },
502                getOutline: () => Promise.resolve([]),
503                getDestination: () => Promise.resolve(null),
504                getPageIndex: () => Promise.resolve(0)
505            };
506            initViewerWithDoc(mockPdfDoc);
507        } else {
508            const loadingTask = pdfjsLib.getDocument(options.url);
509            
510            loadingTask.onPassword = function(updatePassword, reason) {
511                const password = prompt(reason === pdfjsLib.PasswordResponses.NEED_PASSWORD 
512                    ? 'Please enter the password to view this PDF:' 
513                    : 'Incorrect password. Please try again:');
514                if (password !== null) updatePassword(password);
515                else titleEl.textContent = 'Password required';
516            };
517
518            loadingTask.promise.then(pdf => {
519                initViewerWithDoc(pdf);
520            }).catch(err => {
521                console.error('Error loading PDF:', err);
522                container.innerHTML = '<div style="padding:40px; color:#fff; text-align:center;">Failed to load PDF file.</div>';
523            });
524        }
525
526        // Save original height for restoring when switching modes
527        const originalHeight = container.style.height || (options.height ? options.height : '750px');
528
529        // Handle Window Resize to make viewer dynamically adjustable
530        let resizeTimeout;
531        window.addEventListener('resize', () => {
532            clearTimeout(resizeTimeout);
533            resizeTimeout = setTimeout(() => {
534                if (!pdfDoc) return; // Not fully loaded yet
535                
536                const wasContinuous = options.mode === 'continuous';
537                
538                // Enforce continuous on smaller screens
539                if (window.innerWidth <= 850) {
540                    options.mode = 'continuous';
541                }
542                
543                // If mode changed due to resize
544                if (wasContinuous !== (options.mode === 'continuous')) {
545                    if (pageFlipInstance) {
546                        try { pageFlipInstance.destroy(); } catch(e){}
547                        pageFlipInstance = null;
548                    }
549                    if (options.mode === 'continuous') {
550                        scrollArea.classList.remove('flipbook-mode');
551                    } else {
552                        scrollArea.classList.add('flipbook-mode');
553                    }
554                    updateNavigationArrows();
555                    
556                    if (toggleModeBtn) {
557                        const currentText = toggleModeBtn.innerHTML;
558                        if (options.mode === 'continuous' && currentText.includes('Single Page Mode')) {
559                            toggleModeBtn.innerHTML = currentText.replace('Single Page Mode', 'Flipbook Mode');
560                        } else if (options.mode !== 'continuous' && currentText.includes('Flipbook Mode')) {
561                            toggleModeBtn.innerHTML = currentText.replace('Flipbook Mode', 'Single Page Mode');
562                        }
563                    }
564                }
565
566                // Re-render to adapt to new container dimensions
567                if (options.mode === 'continuous') {
568                    scrollArea.classList.remove('flipbook-mode');
569                    renderAllPages();
570                } else if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
571                    scrollArea.classList.add('flipbook-mode');
572                    if (pageFlipInstance) {
573                        try { pageFlipInstance.destroy(); } catch(e){}
574                        pageFlipInstance = null;
575                    }
576                    renderFlipbook();
577                } else {
578                    scrollArea.classList.remove('flipbook-mode');
579                    renderPage(currentPage);
580                }
581            }, 300);
582        });
583
584        function buildThumbnails() {
585            thumbnailsContent.innerHTML = '';
586            for (let i = 1; i <= numPages; i++) {
587                const thumb = document.createElement('div');
588                thumb.className = 'smartembed-thumbnail';
589                const canvas = document.createElement('canvas');
590                const pageLabel = document.createElement('div');
591                pageLabel.className = 'smartembed-thumbnail-label';
592                pageLabel.textContent = i;
593                thumb.appendChild(canvas);
594                thumb.appendChild(pageLabel);
595                thumbnailsContent.appendChild(thumb);
596                
597                thumb.addEventListener('click', () => {
598                    currentPage = i;
599                    queueRenderPage(currentPage);
600                    thumbnailsSidebar.classList.remove('open');
601                });
602
603                pdfDoc.getPage(i).then(page => {
604                    const viewport = page.getViewport({ scale: 0.2 });
605                    canvas.height = viewport.height;
606                    canvas.width = viewport.width;
607                    const renderTask = page.render({
608                        canvasContext: canvas.getContext('2d'),
609                        viewport: viewport
610                    });
611                    renderTask.promise.then(() => {
612                        document.dispatchEvent(new CustomEvent('smartembed_page_rendered', {
613                            detail: {
614                                pageIndex: i,
615                                pdfPage: page,
616                                canvas: canvas,
617                                viewport: viewport,
618                                pageDiv: null, // No pageDiv in thumbnails
619                                container: container,
620                                options: options,
621                                isThumbnail: true
622                            }
623                        }));
624                    });
625                });
626            }
627        }
628
629        function buildToc() {
630            if (!options.toc || !tocContent) return;
631            pdfDoc.getOutline().then(outline => {
632                if (!outline || outline.length === 0) {
633                    tocContent.innerHTML = '<div class="smartembed-toc-empty" style="padding: 15px; color: #888;">No Table of Contents available.</div>';
634                    return;
635                }
636                
637                function renderOutline(items, containerEl) {
638                    const ul = document.createElement('ul');
639                    ul.className = 'smartembed-toc-list';
640                    ul.style.listStyle = 'none';
641                    ul.style.paddingLeft = containerEl === tocContent ? '0' : '15px';
642                    
643                    items.forEach(item => {
644                        const li = document.createElement('li');
645                        li.className = 'smartembed-toc-item';
646                        li.style.marginBottom = '8px';
647                        
648                        const a = document.createElement('a');
649                        a.textContent = item.title;
650                        a.href = '#';
651                        a.style.textDecoration = 'none';
652                        a.style.color = 'inherit';
653                        a.style.fontSize = '14px';
654                        
655                        a.addEventListener('click', (e) => {
656                            e.preventDefault();
657                            if (typeof item.dest === 'string') {
658                                pdfDoc.getDestination(item.dest).then(destArray => {
659                                    if (destArray) {
660                                        pdfDoc.getPageIndex(destArray[0]).then(pageIndex => {
661                                            currentPage = pageIndex + 1;
662                                            queueRenderPage(currentPage);
663                                            tocSidebar.classList.remove('open');
664                                        });
665                                    }
666                                });
667                            } else if (Array.isArray(item.dest)) {
668                                pdfDoc.getPageIndex(item.dest[0]).then(pageIndex => {
669                                    currentPage = pageIndex + 1;
670                                    queueRenderPage(currentPage);
671                                    tocSidebar.classList.remove('open');
672                                });
673                            }
674                        });
675                        li.appendChild(a);
676                        if (item.items && item.items.length > 0) {
677                            renderOutline(item.items, li);
678                        }
679                        ul.appendChild(li);
680                    });
681                    containerEl.appendChild(ul);
682                }
683                tocContent.innerHTML = '';
684                renderOutline(outline, tocContent);
685            }).catch(err => {
686                console.error('Error loading outline:', err);
687                tocContent.innerHTML = '<div class="smartembed-toc-empty" style="padding: 15px; color: #888;">Failed to load outline.</div>';
688            });
689        }
690
691
692        function getFitScale(page) {
693            const containerW = container.offsetWidth || 650;
694            const viewport = page.getViewport({ scale: 1.0 });
695            const padW = window.innerWidth <= 850 ? 20 : 60;
696            const scaleW = (containerW - padW) / viewport.width;
697
698            if (options.mode !== 'continuous') {
699                const ui = container.querySelector('.smartembed-pdf-viewer-ui');
700                const containerH = ui ? (ui.offsetHeight || parseInt(ui.style.height) || 750) : 750;
701                const scaleH = (containerH - 80) / viewport.height;
702                return Math.min(scaleW, Math.max(scaleH, 0.1));
703            } else {
704                // Prevent continuous mode pages from becoming overwhelmingly large on wide screens
705                // 850px is a comfortable maximum reading width for most documents
706                const maxReadingScale = 850 / viewport.width;
707                return Math.min(scaleW, maxReadingScale);
708            }
709        }
710
711        function renderPage(num) {
712            pageRendering = true;
713            pdfDoc.getPage(num).then(page => {
714                const fitScale = getFitScale(page) * scale;
715                const viewport = page.getViewport({ scale: fitScale });
716                const dpr = Math.max(window.devicePixelRatio || 1, 2);
717                pagesContainer.innerHTML = ''; // clear
718                const canvas = document.createElement('canvas');
719                const ctx = canvas.getContext('2d');
720                canvas.width = viewport.width * dpr;
721                canvas.height = viewport.height * dpr;
722                canvas.style.width = viewport.width + 'px';
723                canvas.style.height = viewport.height + 'px';
724                ctx.scale(dpr, dpr);
725                canvas.className = 'smartembed-pdf-page-canvas';
726                pagesContainer.appendChild(canvas);
727
728                const renderContext = {
729                    canvasContext: ctx,
730                    viewport: viewport
731                };
732                renderTask = page.render(renderContext);
733
734                renderTask.promise.then(() => {
735                    pageRendering = false;
736                    document.dispatchEvent(new CustomEvent('smartembed_page_rendered', {
737                        detail: {
738                            pageIndex: num,
739                            pdfPage: page,
740                            canvas: canvas,
741                            viewport: viewport,
742                            pageDiv: null,
743                            container: container,
744                            options: options,
745                            isThumbnail: false
746                        }
747                    }));
748                    if (pageNumPending !== null) {
749                        renderPage(pageNumPending);
750                        pageNumPending = null;
751                    }
752                });
753            });
754            if (pageNumEl) pageNumEl.value = num;
755            if (options.resume) localStorage.setItem('smartembed_page_' + options.url, num);
756        }
757
758        function renderAllPages() {
759            pagesContainer.innerHTML = '';
760            
761            if (!document.fullscreenElement) {
762                container.style.setProperty('height', originalHeight, 'important');
763                container.style.setProperty('min-height', originalHeight, 'important');
764            }
765            
766            pdfDoc.getPage(1).then(firstPage => {
767                const baseScale = getFitScale(firstPage);
768                const finalScale = baseScale * scale;
769                
770                for (let i = 1; i <= numPages; i++) {
771                const wrapper = document.createElement('div');
772                wrapper.className = 'smartembed-page-wrapper';
773                wrapper.id = `page-${i}`;
774                if (options.pagesGap && !isNaN(options.pagesGap)) {
775                    wrapper.style.marginBottom = options.pagesGap + 'px';
776                }
777                
778                const canvas = document.createElement('canvas');
779                canvas.className = 'smartembed-pdf-page-canvas';
780                wrapper.appendChild(canvas);
781                pagesContainer.appendChild(wrapper);
782
783                    pdfDoc.getPage(i).then(page => {
784                        const viewport = page.getViewport({ scale: finalScale });
785                        const dpr = Math.max(window.devicePixelRatio || 1, 2);
786                        const ctx = canvas.getContext('2d');
787                        canvas.width = viewport.width * dpr;
788                        canvas.height = viewport.height * dpr;
789                    canvas.style.width = viewport.width + 'px';
790                    canvas.style.height = viewport.height + 'px';
791                    ctx.scale(dpr, dpr);
792                        const renderTask = page.render({
793                            canvasContext: ctx,
794                            viewport: viewport
795                        });
796                        renderTask.promise.then(() => {
797                            document.dispatchEvent(new CustomEvent('smartembed_page_rendered', {
798                                detail: {
799                                    pageIndex: i,
800                                    pdfPage: page,
801                                    canvas: canvas,
802                                    viewport: viewport,
803                                    pageDiv: wrapper,
804                                    container: container,
805                                    options: options,
806                                    isThumbnail: false
807                                }
808                            }));
809                        });
810                    });
811                }
812            });
813        }
814
815        scrollArea.addEventListener('scroll', () => {
816            if (options.mode !== 'continuous') return;
817            const wrappers = pagesContainer.querySelectorAll('.smartembed-page-wrapper');
818            let current = 1;
819            let minDiff = Infinity;
820            const scrollCenter = scrollArea.scrollTop + scrollArea.offsetHeight / 2;
821            
822            wrappers.forEach((wrap, index) => {
823                const diff = Math.abs((wrap.offsetTop + wrap.offsetHeight / 2) - scrollCenter);
824                if (diff < minDiff) {
825                    minDiff = diff;
826                    current = index + 1;
827                }
828            });
829            
830            if (current !== currentPage) {
831                currentPage = current;
832                if (pageNumEl) pageNumEl.value = currentPage;
833                if (options.resume) localStorage.setItem('smartembed_page_' + options.url, currentPage);
834            }
835        });
836
837        function renderFlipbook() {
838            // Create the flipbook without the external cover wrapper
839            pagesContainer.innerHTML = `<div class="smartembed-flipbook-container" id="flipbook-wrapper-${options.uid}"></div>`;
840            const flipContainer = pagesContainer.querySelector('.smartembed-flipbook-container');
841
842            // Get page 1 dimensions to seed PageFlip, then render all pages
843            pdfDoc.getPage(1).then(p1 => {
844                const containerW = container.offsetWidth || 650;
845                const naturalVp = p1.getViewport({ scale: 1.0 });
846                
847                // Use the explicitly set ui height (baseViewerH) for reliable calculations
848                const parentW = containerW;
849                const parentH = ui.offsetHeight || parseInt(ui.style.height) || 750;
850                
851                // Always render single page for shortcode flipbook (portrait mode)
852                const padX = 40; // horizontal padding inside grey area
853                const padY = 80; // vertical padding (room for toolbar + nav arrows)
854                
855                const availW = parentW - padX;
856                const availH = parentH - padY;
857                
858                // Calculate scale to fit page within available space while preserving aspect ratio
859                const scaleW = availW / naturalVp.width;
860                const scaleH = availH / naturalVp.height;
861                // Multiply by 0.88 to shrink the book slightly and guarantee space on all sides
862                let fitScale = Math.min(scaleW, scaleH) * 0.88;
863                
864                // Clamp scale to prevent extreme values
865                if (fitScale < 0.1) fitScale = 0.1;
866                if (fitScale > 3.0) fitScale = 3.0;
867                
868                const pgW = Math.round(naturalVp.width * fitScale);
869                const pgH = Math.round(naturalVp.height * fitScale);
870                
871                // Recalculate container height based on actual page height
872                if (!document.fullscreenElement) {
873                    const tplSelectorH = templateSelectorH || 0;
874                    const newViewerH = Math.min(pgH + 80, parentH);
875                    const newTotalH = newViewerH + tplSelectorH;
876                    
877                    // Update container total height — only shrink if page is smaller than viewer
878                    if (pgH + 80 < parentH) {
879                        container.style.setProperty('height', newTotalH + 'px', 'important');
880                        container.style.setProperty('min-height', newTotalH + 'px', 'important');
881                        ui.style.height = newViewerH + 'px';
882                        ui.style.minHeight = newViewerH + 'px';
883                    }
884                }
885
886                // Set container width and height explicitly — single page mode keeps book centered
887                // with constrained width so it doesn't fill the entire grey area
888                flipContainer.style.width = pgW + 'px';
889                flipContainer.style.height = pgH + 'px';
890                
891                if (options.tilt) {
892                    flipContainer.classList.add('smartembed-tilt-enabled');
893                }
894                if (options.rtl) {
895                    flipContainer.classList.add('smartembed-rtl-enabled');
896                }
897
898                baseFitScale = fitScale; // save for zoom re-renders
899                
900                // Configure PageFlip based on selected template
901                let useShadow = true;
902                let useCover = true;
903                let tplPortrait = true; // Always portrait for shortcode flipbook (single page at a time)
904                
905                if (options.mode === 'flipbook_single') {
906                    tplPortrait = true;
907                }
908                
909                if (options.currentTemplate === '2d_flipbook') {
910                    useShadow = true;
911                    useCover = true;
912                    flipContainer.classList.add('smartembed-tpl-2d');
913                } else if (options.currentTemplate === 'hard_cover') {
914                    useCover = true;
915                    useShadow = true;
916                    flipContainer.classList.add('smartembed-tpl-hardcover');
917                } else if (options.currentTemplate === 'binder') {
918                    useCover = true;
919                    useShadow = true;
920                    flipContainer.classList.add('smartembed-tpl-binder');
921                } else if (options.currentTemplate === 'calendar') {
922                    tplPortrait = true;
923                    flipContainer.classList.add('smartembed-tpl-calendar');
924                } else if (options.currentTemplate === '3d_flipbook') {
925                    useCover = true;
926                    useShadow = true;
927                    flipContainer.classList.add('smartembed-tpl-3d');
928                } else {
929                    // Default Plain Flipbook (Free version) — no spine shadow
930                    useCover = true;
931                    useShadow = false;
932                }
933
934                // Initialize 
935                try {
936                    let flipOptions = {
937                        width: naturalVp.width,
938                        height: naturalVp.height,
939                        size: 'stretch',
940                        minWidth: 200,
941                        maxWidth: 3000,
942                        minHeight: 300,
943                        maxHeight: 3000,
944                        showCover: useCover,
945                        usePortrait: tplPortrait, // Portrait mode for mobile or calendar
946                        maxShadowOpacity: useShadow ? 0.3 : 0,
947                        mobileScrollSupport: true,
948                        drawShadow: useShadow,
949                        flippingTime: 450, // Faster, snappier animation
950                        swipeDistance: 30, // Make it easier to turn pages
951                        useMouseEvents: true,
952                        clickEventForward: true
953                    };
954                    
955                    if (options.mode === 'flipbook_single') {
956                        flipOptions.usePortrait = true;
957                    }
958
959                    pageFlipInstance = new St.PageFlip(flipContainer, flipOptions);
960                    
961                    if (options.mode === 'flipbook_single') {
962                        flipOptions.usePortrait = true;
963                    }
964
965                    pageFlipInstance = new St.PageFlip(flipContainer, flipOptions);
966                    
967                } catch(e) {
968                    console.error('PageFlip init failed', e);
969                    options.mode = 'continuous';
970                    renderAllPages();
971                    return;
972                }
973
974                // Use cached canvases if available and scale is identical
975                let useCache = (cachedCanvases.length === numPages && cachedFitScale === fitScale);
976
977                // Pre-build all page divs with canvases
978                flipPageDivs = []; // save to outer scope for zoom re-renders
979                const dprInit = Math.max(window.devicePixelRatio || 1, 2);
980                const isRTL = options.rtl;
981                for (let i = 1; i <= numPages; i++) {
982                    const pageDiv = document.createElement('div');
983                    pageDiv.className = 'my-page';
984                    
985                    // Assign side attribute for LTR vs RTL (for page stacking styles)
986                    let side = (i % 2 === 0) ? 'left' : 'right';
987                    if (isRTL) {
988                        side = (i % 2 === 0) ? 'right' : 'left';
989                    }
990                    pageDiv.setAttribute('data-side', side);
991                    
992                    // Assign Hard Cover density if it's the first or last page
993                    if (options.currentTemplate === 'hard_cover' || options.currentTemplate === 'binder') {
994                        if (i === 1 || i === numPages) {
995                            pageDiv.setAttribute('data-density', 'hard');
996                        }
997                    }
998
999                    let canvas;
1000                    if (useCache) {
1001                        canvas = cachedCanvases[i - 1];
1002                    } else {
1003                        canvas = document.createElement('canvas');
1004                        // Render at high DPI but display at CSS pixel size
1005                        canvas.width = pgW * dprInit;
1006                        canvas.height = pgH * dprInit;
1007                    }
1008                    
1009                    canvas.style.width = pgW + 'px';
1010                    canvas.style.height = pgH + 'px';
1011                    pageDiv.appendChild(canvas);
1012                    flipContainer.appendChild(pageDiv);
1013                    flipPageDivs.push({ pageDiv, canvas, index: i });
1014                    
1015                    if (!useCache) {
1016                        cachedCanvases.push(canvas);
1017                    }
1018                }
1019
1020                // Load structure into PageFlip immediately so it renders the shell
1021                pageFlipInstance.loadFromHTML(flipContainer.querySelectorAll('.my-p
1021age'));
1022
1023                if (!useCache) {
1024                    cachedFitScale = fitScale;
1025                    // Now async-render each PDF page into its canvas (correct closure per page)
1026                    flipPageDivs.forEach(({ canvas, index }) => {
1027                        pdfDoc.getPage(index).then(pdfPage => {
1028                            const renderDpr = Math.max(window.devicePixelRatio || 1, 2);
1029                            const vp = pdfPage.getViewport({ scale: fitScale * renderDpr });
1030                            const ctx = canvas.getContext('2d');
1031                            canvas.width = vp.width;
1032                            canvas.height = vp.height;
1033                            canvas.style.width = pgW + 'px';
1034                            canvas.style.height = pgH + 'px';
1035                            ctx.setTransform(1, 0, 0, 1, 0, 0);
1036                            const renderTask = pdfPage.render({
1037                                canvasContext: ctx,
1038                                viewport: vp
1039                            });
1040                            renderTask.promise.then(() => {
1041                                document.dispatchEvent(new CustomEvent('smartembed_page_rendered', {
1042                                    detail: {
1043                                        pageIndex: index,
1044                                        pdfPage: pdfPage,
1045                                        canvas: canvas,
1046                                        viewport: vp,
1047                                        pageDiv: canvas.parentElement,
1048                                        container: container,
1049                                        options: options,
1050                                        isThumbnail: false
1051                                    }
1052                                }));
1053                            });
1054                        });
1055                    });
1056                }
1057
1058                // ==========================================
1059                // Custom Hardcover Intro (3D Animation)
1060                // ==========================================
1061                if (options.currentTemplate === 'hard_cover' && currentPage === 1 && !document.fullscreenElement) {
1062                    const introEl = document.createElement('div');
1063                    introEl.className = 'smartembed-custom-hardcover-intro';
1064                    
1065                    const hcBook = document.createElement('div');
1066                    hcBook.className = 'smartembed-hc-book';
1067                    hcBook.style.width = pgW + 'px';
1068                    hcBook.style.height = pgH + 'px';
1069                    
1070                    const frontCover = document.createElement('div');
1071                    frontCover.className = 'smartembed-hc-part smartembed-hc-front';
1072                    const introCanvas = document.createElement('canvas');
1073                    
1074                    // Render page 1 to intro canvas
1075                    pdfDoc.getPage(1).then(page => {
1076                        const vp = page.getViewport({ scale: fitScale * (window.devicePixelRatio || 1) });
1077                        introCanvas.width = vp.width;
1078                        introCanvas.height = vp.height;
1079                        const ctx = introCanvas.getContext('2d');
1080                        page.render({ canvasContext: ctx, viewport: vp });
1081                    });
1082                    
1083                    frontCover.appendChild(introCanvas);
1084                    
1085                    const spine = document.createElement('div');
1086                    spine.className = 'smartembed-hc-part smartembed-hc-spine';
1087                    const backCover = document.createElement('div');
1088                    backCover.className = 'smartembed-hc-part smartembed-hc-back';
1089                    
1090                    const pTop = document.createElement('div');
1091                    pTop.className = 'smartembed-hc-part smartembed-hc-pages-top';
1092                    const pBottom = document.createElement('div');
1093                    pBottom.className = 'smartembed-hc-part smartembed-hc-pages-bottom';
1094                    const pRight = document.createElement('div');
1095                    pRight.className = 'smartembed-hc-part smartembed-hc-pages-right';
1096                    
1097                    const shadow = document.createElement('div');
1098                    shadow.className = 'smartembed-hc-shadow';
1099                    
1100                    const hint = document.createElement('div');
1101                    hint.className = 'smartembed-open-hint';
1102                    hint.textContent = 'Click to Open Book';
1103                    
1104                    hcBook.appendChild(shadow);
1105                    hcBook.appendChild(backCover);
1106                    hcBook.appendChild(spine);
1107                    hcBook.appendChild(pTop);
1108                    hcBook.appendChild(pBottom);
1109                    hcBook.appendChild(pRight);
1110                    hcBook.appendChild(frontCover);
1111                    introEl.appendChild(hcBook);
1112                    introEl.appendChild(hint);
1113                    
1114                    // Append to scrollArea so the overlay covers the full viewer
1115                    scrollArea.style.position = 'relative';
1116                    scrollArea.appendChild(introEl);
1117                    
1118                    // Hide actual flip container while intro is active
1119                    flipContainer.style.opacity = '0';
1120                    flipContainer.style.pointerEvents = 'none';
1121                    flipContainer.style.transition = 'opacity 0.8s ease';
1122                    
1123                    let introOpened = false;
1124                    const openBook = () => {
1125                        if (introOpened) return;
1126                        introOpened = true;
1127                        introEl.classList.add('is-opening');
1128                        setTimeout(() => {
1129                            introEl.style.transition = 'opacity 0.5s ease';
1130                            introEl.classList.add('hidden');
1131                            flipContainer.style.opacity = '1';
1132                            flipContainer.style.pointerEvents = 'auto';
1133                            if (pageFlipInstance && numPages > 1) {
1134                                pageFlipInstance.turnToPage(1);
1135                            }
1136                            // Remove intro DOM after fade
1137                            setTimeout(() => { if (introEl.parentNode) introEl.parentNode.removeChild(introEl); }, 600);
1138                        }, 1500);
1139                    };
1140                    
1141                    hcBook.addEventListener('click', openBook);
1142                    // Also support touch for mobile
1143                    hcBook.addEventListener('touchend', (e) => { e.preventDefault(); openBook(); }, { passive: false });
1144                }
1145
1146                // Restore resume position
1147                if (currentPage > 1) {
1148                    setTimeout(() => pageFlipInstance.turnToPage(currentPage - 1), 100);
1149                }
1150
1151                // Sync orientation class
1152                const updateOrientationClass = () => {
1153                    const orient = pageFlipInstance.getOrientation();
1154                    if (orient === 'landscape') {
1155                        flipContainer.classList.add('se-orientation-landscape');
1156                        flipContainer.classList.remove('se-orientation-portrait');
1157                    } else {
1158                        flipContainer.classList.add('se-orientation-portrait');
1159                        flipContainer.classList.remove('se-orientation-landscape');
1160                    }
1161                };
1162                updateOrientationClass();
1163
1164                // Add transition style to targetEl for smooth centering slide
1165                const coverWrapper = pagesContainer.querySelector('.smartembed-book-cover-3d');
1166                const targetEl = coverWrapper || flipContainer;
1167                if (targetEl) {
1168                    targetEl.style.transition = 'transform 0.45s ease-out';
1169                }
1170
1171                let initialShift = 0;
1172                if (pageFlipInstance.getOrientation() === 'landscape') {
1173                    initialShift = -(pgW / 4) - 20;
1174                }
1175                applyFlipbookTransform(initialShift);
1176
1177                // Sync page counter on flip
1178                pageFlipInstance.on('flip', (e) => {
1179                    const idx = e.data;
1180                    currentPage = idx + 1;
1181
1182                    if (pageNumEl) {
1183                        if (pageFlipInstance.getOrientation() === 'landscape' && idx > 0 && idx < numPages - 1) {
1184                            pageNumEl.value = `${idx + 1}-${idx + 2}`;
1185                        } else {
1186                            pageNumEl.value = idx + 1;
1187                        }
1188                    }
1189                    
1190                    let shiftX = 0;
1191                    if (pageFlipInstance.getOrientation() === 'landscape') {
1192                        if (idx === 0) {
1193                            shiftX = -(pgW / 4) - 20;
1194                        } else if (idx === numPages - 1 && numPages % 2 === 0) {
1195                            shiftX = (pgW / 4) + 20;
1196                        }
1197                    }
1198                    applyFlipbookTransform(shiftX);
1199                    
1200                    if (options.resume) localStorage.setItem('smartembed_page_' + options.url, currentPage);
1201                });
1202
1203                // Handle orientation changes (e.g. window resize/rotation)
1204                pageFlipInstance.on('changeOrientation', (e) => {
1205                    updateOrientationClass();
1206                    applyFlipbookTransform(0);
1207                });
1208
1209                // Play sound immediately on flip start
1210                pageFlipInstance.on('changeState', (e) => {
1211                    if (e.data === 'flipping' && soundEnabled && pageFlipAudio) {
1212                        pageFlipAudio.currentTime = 0;
1213                        pageFlipAudio.play().catch(() => {});
1214                    }
1215                });
1216            });
1217        }
1218
1219        // Navigation
1220        const queueRenderPage = num => {
1221            if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1222                if (pageFlipInstance) pageFlipInstance.turnToPage(num - 1);
1223                return;
1224            }
1225            if (options.mode === 'continuous') {
1226                const targetPage = pagesContainer.querySelector(`#page-${num}`);
1227                if (targetPage) {
1228                    scrollArea.scrollTo({
1229                        top: targetPage.offsetTop - 10,
1230                        behavior: 'smooth'
1231                    });
1232                }
1233                return;
1234            }
1235            if (soundEnabled && pageFlipAudio) {
1236                pageFlipAudio.currentTime = 0;
1237                pageFlipAudio.play().catch(() => {});
1238            }
1239            if (pageRendering) pageNumPending = num;
1240            else renderPage(num);
1241        };
1242
1243        const onPrevPage = () => {
1244            if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1245                if (pageFlipInstance) pageFlipInstance.flipPrev();
1246                return;
1247            }
1248            if (currentPage <= 1) return;
1249            currentPage--;
1250            queueRenderPage(currentPage);
1251        };
1252
1253        const onNextPage = () => {
1254            if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1255                if (pageFlipInstance) pageFlipInstance.flipNext();
1256                return;
1257            }
1258            if (currentPage >= numPages) return;
1259            currentPage++;
1260            queueRenderPage(currentPage);
1261        };
1262
1263        if (prevBtn) prevBtn.addEventListener('click', onPrevPage);
1264        if (nextBtn) nextBtn.addEventListener('click', onNextPage);
1265
1266        if (toggleThumbnailsBtn) toggleThumbnailsBtn.addEventListener('click', () => {
1267            thumbnailsSidebar.classList.toggle('open');
1268            if (tocSidebar && tocSidebar.classList.contains('open')) tocSidebar.classList.remove('open');
1269        });
1270        if (closeThumbnailsBtn) closeThumbnailsBtn.addEventListener('click', () => {
1271            thumbnailsSidebar.classList.remove('open');
1272        });
1273
1274        if (toggleTocBtn) toggleTocBtn.addEventListener('click', () => {
1275            tocSidebar.classList.toggle('open');
1276            if (thumbnailsSidebar && thumbnailsSidebar.classList.contains('open')) thumbnailsSidebar.classList.remove('open');
1277        });
1278        if (closeTocBtn) closeTocBtn.addEventListener('click', () => {
1279            tocSidebar.classList.remove('open');
1280        });
1281        
1282        if (optionsBtn) {
1283            optionsBtn.addEventListener('click', (e) => {
1284                e.stopPropagation();
1285                container.querySelectorAll('.smartembed-options-dropdown').forEach(dd => {
1286                    if (!dd.classList.contains('smartembed-share-dropdown')) dd.classList.remove('show');
1287                });
1288                const menu = container.querySelector('.smartembed-options-menu-container > .smartembed-options-dropdown:not(.smartembed-share-dropdown)');
1289                if (menu) menu.classList.toggle('show');
1290            });
1291        }
1292        
1293        document.addEventListener('click', () => {
1294            container.querySelectorAll('.smartembed-options-dropdown').forEach(dd => dd.classList.remove('show'));
1295        });
1296        
1297        if (gotoFirstBtn) gotoFirstBtn.addEventListener('click', () => {
1298            currentPage = 1; queueRenderPage(currentPage);
1299            container.querySelector('.smartembed-options-dropdown').classList.remove('show');
1300        });
1301        if (gotoLastBtn) gotoLastBtn.addEventListener('click', () => {
1302            currentPage = numPages; queueRenderPage(currentPage);
1303            container.querySelector('.smartembed-options-dropdown').classList.remove('show');
1304        });
1305        if (toggleModeBtn) toggleModeBtn.addEventListener('click', () => {
1306            const isFlip = options.mode === 'flipbook' || options.mode === 'flipbook_single';
1307            options.mode = isFlip ? 'continuous' : 'flipbook';
1308            
1309            if (pageFlipInstance) {
1310                try { pageFlipInstance.destroy(); } catch(e){}
1311                pageFlipInstance = null;
1312            }
1313            
1314            if (options.mode === 'continuous') {
1315                scrollArea.classList.remove('flipbook-mode');
1316                renderAllPages();
1317            } else {
1318                scrollArea.classList.add('flipbook-mode');
1319                renderFlipbook();
1320            }
1321            
1322            updateNavigationArrows();
1323            
1324            toggleModeBtn.innerHTML = options.mode === 'continuous' ? 
1325                toggleModeBtn.innerHTML.replace('Single Page Mode', 'Flipbook Mode') : 
1326                toggleModeBtn.innerHTML.replace('Flipbook Mode', 'Single Page Mode');
1327                
1328            container.querySelector('.smartembed-options-dropdown').classList.remove('show');
1329        });
1330
1331        if (printBtn) {
1332            printBtn.addEventListener('click', () => {
1333                const iframe = document.createElement('iframe');
1334                iframe.style.display = 'none';
1335                iframe.src = options.url;
1336                document.body.appendChild(iframe);
1337                iframe.onload = () => {
1338                    iframe.contentWindow.print();
1339                    setTimeout(() => iframe.remove(), 5000);
1340                };
1341            });
1342        }
1343
1344        if (shareBtn) {
1345            shareBtn.addEventListener('click', (e) => {
1346                e.stopPropagation();
1347                container.querySelectorAll('.smartembed-options-dropdown').forEach(dd => {
1348                    if (!dd.classList.contains('smartembed-share-dropdown')) dd.classList.remove('show');
1349                });
1350                const shareMenu = container.querySelector('.smartembed-share-dropdown');
1351                if (shareMenu) shareMenu.classList.toggle('show');
1352            });
1353            
1354            const fbShare = container.querySelector('.smartembed-share-fb');
1355            const twShare = container.querySelector('.smartembed-share-tw');
1356            const waShare = container.querySelector('.smartembed-share-wa');
1357            const liShare = container.querySelector('.smartembed-share-li');
1358            const cpShare = container.querySelector('.smartembed-share-copy');
1359            
1360            if (fbShare) fbShare.addEventListener('click', () => window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(options.url)}`, '_blank'));
1361            if (twShare) twShare.addEventListener('click', () => window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(options.url)}&text=${encodeURIComponent(document.title)}`, '_blank'));
1362            if (waShare) waShare.addEventListener('click', () =>
1362 window.open(`https://api.whatsapp.com/send?text=${encodeURIComponent(document.title + " " + options.url)}`, '_blank'));
1363            if (liShare) liShare.addEventListener('click', () => window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(options.url)}`, '_blank'));
1364            if (cpShare) cpShare.addEventListener('click', () => {
1365                navigator.clipboard.writeText(options.url);
1366                alert('Link copied to clipboard!');
1367            });
1368        }
1369
1370        // Template Selector Click Handler
1371        const templateCards = container.querySelectorAll('.smartembed-tpl-card');
1372        templateCards.forEach(card => {
1373            card.addEventListener('click', () => {
1374                const tpl = card.getAttribute('data-tpl');
1375                if (tpl === options.currentTemplate) return;
1376                
1377                // Update active UI
1378                templateCards.forEach(c => c.classList.remove('active'));
1379                card.classList.add('active');
1380                
1381                // Set new template and mode
1382                options.currentTemplate = tpl;
1383                if (tpl === 'vertical_reader') options.mode = 'continuous';
1384                else if (tpl === 'slider') options.mode = 'presentation';
1385                else options.mode = 'flipbook';
1386                
1387                // Re-render
1388                if (options.mode === 'continuous') {
1389                    scrollArea.classList.remove('flipbook-mode');
1390                    renderAllPages();
1391                } else if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1392                    scrollArea.classList.add('flipbook-mode');
1393                    if (pageFlipInstance) {
1394                        try { pageFlipInstance.destroy(); } catch(e){}
1395                        pageFlipInstance = null;
1396                    }
1397                    renderFlipbook();
1398                } else {
1399                    scrollArea.classList.remove('flipbook-mode');
1400                    renderPage(currentPage);
1401                }
1402                
1403                updateNavigationArrows();
1404                if (toggleModeBtn) {
1405                    toggleModeBtn.innerHTML = options.mode === 'continuous' ? 
1406                        toggleModeBtn.innerHTML.replace('Single Page Mode', 'Flipbook Mode') : 
1407                        toggleModeBtn.innerHTML.replace('Flipbook Mode', 'Single Page Mode');
1408                }
1409            });
1410        });
1411
1412        // Zoom
1413        if (zoomInBtn) zoomInBtn.addEventListener('click', () => {
1414            if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1415                flipZoom = Math.min(flipZoom + 0.15, 3.0);
1416                applyFlipbookTransform(0);
1417                reRenderFlipbookPages();
1418                // Enable pan cursor when zoomed in
1419                const cw = pagesContainer.querySelector('.smartembed-book-cover-3d');
1420                const fc = pagesContainer.querySelector('.smartembed-flipbook-container');
1421                if (cw) cw.classList.toggle('se-flipbook-zoomed', flipZoom > 1.0);
1422                if (fc) fc.classList.toggle('se-flipbook-zoomed', flipZoom > 1.0);
1423            } else {
1424                scale += 0.25;
1425                if (options.mode === 'continuous') renderAllPages();
1426                else renderPage(currentPage);
1427            }
1428        });
1429        if (zoomOutBtn) zoomOutBtn.addEventListener('click', () => {
1430            if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1431                flipZoom = Math.max(flipZoom - 0.15, 0.4);
1432                if (flipZoom <= 1.0) { flipPanX = 0; flipPanY = 0; } // reset pan at/below 1x
1433                applyFlipbookTransform(0);
1434                reRenderFlipbookPages();
1435                const cw = pagesContainer.querySelector('.smartembed-book-cover-3d');
1436                const fc = pagesContainer.querySelector('.smartembed-flipbook-container');
1437                if (cw) cw.classList.toggle('se-flipbook-zoomed', flipZoom > 1.0);
1438                if (fc) fc.classList.toggle('se-flipbook-zoomed', flipZoom > 1.0);
1439            } else {
1440                scale -= 0.25;
1441                if (scale < 0.5) scale = 0.5;
1442                if (options.mode === 'continuous') renderAllPages();
1443                else renderPage(currentPage);
1444                console.log('Images array is empty.');
1445            }
1446        });
1447
1448    // ==========================================
1449    // PRO FEATURES (Merged)
1450    // ==========================================
1451        if (options.isPro) {
1452            
1453        if (options.download) {
1454            // Find the main toolbar instead of the dropdown
1455            const toolbar = container.querySelector('.smartembed-bottom-toolbar');
1456            if (toolbar) {
1457                const downloadBtn = document.createElement('button');
1458                downloadBtn.className = 'smartembed-tool-btn smartembed-download-btn';
1459                downloadBtn.title = 'Download';
1460                downloadBtn.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>';
1461                
1462                // Insert before the print button if it exists, otherwise before the share container, or just append
1463                const printBtn = toolbar.querySelector('.smartembed-print-btn');
1464                const shareContainer = toolbar.querySelector('.smartembed-share-container');
1465                const optionsMenu = toolbar.querySelector('.smartembed-options-menu-container:last-child');
1466                
1467                if (printBtn) {
1468                    toolbar.insertBefore(downloadBtn, printBtn);
1469                } else if (shareContainer) {
1470                    toolbar.insertBefore(downloadBtn, shareContainer);
1471                } else if (optionsMenu) {
1472                    toolbar.insertBefore(downloadBtn, optionsMenu);
1473                } else {
1474                    toolbar.appendChild(downloadBtn);
1475                }
1476
1477        downloadBtn.addEventListener('click', async (evt) => {
1478            evt.preventDefault();
1479            downloadBtn.style.opacity = '0.5';
1480            const originalHtml = downloadBtn.innerHTML;
1481            downloadBtn.innerHTML = 'Preparing...';
1482            
1483            try {
1484                if (options.bookId && options.ajaxUrl) {
1485                    var formData = new FormData();
1486                    formData.append('action', 'smartembed_track_event');
1487                    formData.append('event_type', 'download');
1488                    formData.append('book_id', options.bookId);
1489                    fetch(options.ajaxUrl, { method: 'POST', body: formData }).catch(e => {});
1490                }
1491
1492                if (options.watermarkLogoUrl && typeof window.PDFLib !== 'undefined') {
1493                    const PDFLib = window.PDFLib;
1494                    const pdfBytes = await fetch(options.url).then(res => res.arrayBuffer());
1495                    const pdfDocLib = await PDFLib.PDFDocument.load(pdfBytes);
1496                    
1497                    const logoBytes = await fetch(options.watermarkLogoUrl).then(res => res.arrayBuffer());
1498                    let logoImage;
1499                    if (options.watermarkLogoUrl.toLowerCase().endsWith('.png')) {
1500                        logoImage = await pdfDocLib.embedPng(logoBytes);
1501                    } else {
1502                        logoImage = await pdfDocLib.embedJpg(logoBytes);
1503                    }
1504                    
1505                    const pages = pdfDocLib.getPages();
1506                    const maxLogoWidth = 200;
1507                    const scaleFactor = Math.min(1, maxLogoWidth / logoImage.width);
1508                    const logoWidth = logoImage.width * scaleFactor;
1509                    const logoHeight = logoImage.height * scaleFactor;
1510                    
1511                    for (const page of pages) {
1512                        const { width: pageWidth, height: pageHeight } = page.getSize();
1513                        page.drawImage(logoImage, {
1514                            x: pageWidth / 2 - logoWidth / 2,
1515                            y: pageHeight / 2 - logoHeight / 2,
1516                            width: logoWidth,
1517                            height: logoHeight,
1518                            opacity: 0.3,
1519                        });
1520                    }
1521                    
1522                    const modifiedPdfBytes = await pdfDocLib.save();
1523                    const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
1524                    const url = URL.createObjectURL(blob);
1525                    const a = document.createElement('a');
1526                    a.href = url;
1527                    a.download = options.url.split('/').pop() || 'download.pdf';
1528                    a.click();
1529                    URL.revokeObjectURL(url);
1530                } else {
1531                    const a = document.createElement('a');
1532                    a.href = options.url;
1533                    a.download = options.url.split('/').pop() || 'download.pdf';
1534                    a.click();
1535                }
1536            } catch (err) {
1537                console.error('Download error', err);
1538                alert('Error downloading PDF.');
1539            }
1540            
1541            downloadBtn.style.opacity = '1';
1542            downloadBtn.innerHTML = originalHtml;
1543            container.querySelectorAll('.smartembed-options-dropdown').forEach(dd => dd.classList.remove('show'));
1544        });
1545            } // end if dropdown
1546        } // end if download
1547
1548        // Zoom Scroll
1549        container.addEventListener('wheel', (e) => {
1550            if (e.ctrlKey || e.metaKey) {
1551                e.preventDefault();
1552                if (e.deltaY < 0) {
1553                    const zoomInBtn = container.querySelector('.smartembed-zoom-in');
1554                    if (zoomInBtn) zoomInBtn.click();
1555                } else {
1556                    const zoomOutBtn = container.querySelector('.smartembed-zoom-out');
1557                    if (zoomOutBtn) zoomOutBtn.click();
1558                }
1559            }
1560        }, { passive: false });
1561        } // end if options.isPro
1562    // PDF Links & Text Search
1563    document.addEventListener('smartembed_page_rendered', async function(e) {
1564        const { pdfPage, canvas, viewport, pageDiv, isThumbnail, options } = e.detail;
1565        if (!options.isPro) return;
1566        if (isThumbnail || !pageDiv) return;
1567
1568        pageDiv.style.position = 'relative';
1569
1570        const pdfjsViewer = window.pdfjsViewer;
1571        if (!pdfjsViewer) return;
1572
1573        // --- TextLayer (for Search / Selection) ---
1574        let textLayerDiv = pageDiv.querySelector('.textLayer');
1575        if (textLayerDiv) textLayerDiv.remove();
1576
1577        textLayerDiv = document.createElement('div');
1578        textLayerDiv.className = 'textLayer';
1579        textLayerDiv.style.position = 'absolute';
1580        textLayerDiv.style.top = '0';
1581        textLayerDiv.style.left = '0';
1582        textLayerDiv.style.right = '0';
1583        textLayerDiv.style.bottom = '0';
1584        textLayerDiv.style.overflow = 'hidden';
1585        textLayerDiv.style.lineHeight = '1.0';
1586        
1587        // Ensure --scale-factor is set for pdf_viewer.css
1588        textLayerDiv.style.setProperty('--scale-factor', viewport.scale);
1589        
1590        pageDiv.appendChild(textLayerDiv);
1591
1592        try {
1593            const textContent = await pdfPage.getTextContent();
1594            pdfjsViewer.renderTextLayer({
1595                textContentSource: textContent,
1596                container: textLayerDiv,
1597                viewport: viewport,
1598                textDivs: []
1599            });
1600        } catch (err) {
1601            console.error('Error rendering text layer', err);
1602        }
1603
1604        // --- AnnotationLayer (for PDF Links) ---
1605        if (options.showAnnotations !== false) {
1606            let annoLayerDiv = pageDiv.querySelector('.annotationLayer');
1607            if (annoLayerDiv) annoLayerDiv.remove();
1608
1609            annoLayerDiv = document.createElement('div');
1610            annoLayerDiv.className = 'annotationLayer';
1611            annoLayerDiv.style.position = 'absolute';
1612            annoLayerDiv.style.top = '0';
1613            annoLayerDiv.style.left = '0';
1614            annoLayerDiv.style.right = '0';
1615            annoLayerDiv.style.bottom = '0';
1616            
1617            // Ensure --scale-factor is set for pdf_viewer.css
1618            annoLayerDiv.style.setProperty('--scale-factor', viewport.scale);
1619            
1620            pageDiv.appendChild(annoLayerDiv);
1621
1622            try {
1623                const annotations = await pdfPage.getAnnotations();
1624                if (annotations.length > 0) {
1625                    pdfjsViewer.AnnotationLayer.render({
1626                        viewport: viewport,
1627                        div: annoLayerDiv,
1628                        annotations: annotations,
1629                        page: pdfPage,
1630                        linkService: new pdfjsViewer.SimpleLinkService(),
1631                        downloadManager: null
1632                    });
1633                }
1634            } catch (err) {
1635                console.error('Error rendering annotation layer', err);
1636            }
1637        }
1638    });
1639
1640        // --- Flipbook Pan/Drag (click-and-hold to move when zoomed) ---
1641        if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1642            let _isDragging = false;
1643            let _dragStartX = 0;
1644            let _dragStartY = 0;
1645            let _panAtDragStart = { x: 0, y: 0 };
1646
1647            const onDragStart = (clientX, clientY) => {
1648                if (flipZoom <= 1.0) return;
1649                _isDragging = true;
1650                _dragStartX = clientX;
1651                _dragStartY = clientY;
1652                _panAtDragStart = { x: flipPanX, y: flipPanY };
1653                const cw = pagesContainer.querySelector('.smartembed-book-cover-3d');
1654                const fc = pagesContainer.querySelector('.smartembed-flipbook-container');
1655                if (cw) cw.classList.add('se-flipbook-dragging');
1656                if (fc) fc.classList.add('se-flipbook-dragging');
1657                // Disable PageFlip mouse events while panning
1658                if (pageFlipInstance) pageFlipInstance.getSettings().useMouseEvents = false;
1659            };
1660
1661            const onDragMove = (clientX, clientY) => {
1662                if (!_isDragging) return;
1663                flipPanX = _panAtDragStart.x + (clientX - _dragStartX);
1664                flipPanY = _panAtDragStart.y + (clientY - _dragStartY);
1665                applyFlipbookTransform(null);
1666            };
1667
1668            const onDragEnd = () => {
1669                if (!_isDragging) return;
1670                _isDragging = false;
1671                const cw = pagesContainer.querySelector('.smartembed-book-cover-3d');
1672                const fc = pagesContainer.querySelector('.smartembed-flipbook-container');
1673                if (cw) cw.classList.remove('se-flipbook-dragging');
1674                if (fc) fc.classList.remove('se-flipbook-dragging');
1675                // Re-enable PageFlip mouse events after panning
1676                if (pageFlipInstance) pageFlipInstance.getSettings().useMouseEvents = true;
1677            };
1678
1679            // Mouse events
1680            scrollArea.addEventListener('mousedown', (e) => {
1681                if (e.button !== 0) return;
1682                onDragStart(e.clientX, e.clientY);
1683            });
1684            window.addEventListener('mousemove', (e) => onDragMove(e.clientX, e.clientY));
1685            window.addEventListener('mouseup', onDragEnd);
1686
1687            // Touch events
1688            scrollArea.addEventListener('touchstart', (e) => {
1689                if (e.touches.length !== 1) return;
1690                onDragStart(e.touches[0].clientX, e.touches[0].clientY);
1691            }, { passive: true });
1692            scrollArea.addEventListener('touchmove', (e) => {
1693                if (e.touches.length !== 1) return;
1694                onDragMove(e.touches[0].clientX, e.touches[0].clientY);
1695            }, { passive: true });
1696            scrollArea.addEventListener('touchend', onDragEnd);
1697        }
1698
1699
1700        // Fullscreen
1701        if (fullscreenBtn) {
1702            fullscreenBtn.addEventListener('click', () => {
1703                if (!document.fullscreenElement) {
1704                    container.requestFullscreen().catch(err => {
1705                        console.error(`Error attempting to enable full-screen mode: ${err.message}`);
1706                    });
1707                } else {
1708                    document.exitFullscreen();
1709                }
1710            });
1711        }
1712
1713        // Re-render content when entering/exiting fullscreen so the book fills the screen
1714        container.addEventListener('fullscreenchange', () => {
1715            if (!pdfDoc) return;
1716            const isFs = !!document.fullscreenElement;
1717
1718            if (isFs) {
1719                setTimeout(() => {
1720                    const fsW = window.innerWidth;
1721                    const fsH = window.innerHeight;
1722
1723                    // Expand container to full screen
1724                    container.style.setProperty('width',        '100vw', 'important');
1725                    container.style.setProperty('height',       '100vh', 'important');
1726                    container.style.setProperty('max-width',    '100vw', 'important');
1727                    container.style.setProperty('min-height',   '100vh', 'important');
1728                    container.style.setProperty('border-radius','0',        'important');
1729
1730                    // CRITICAL: update ui height so renderFlipbook calculates at new size
1731                    const templateSelectorEl = container.querySelector('.smartembed-template-selector');
1732                    const currentTemplateSelectorH = templateSelectorEl ? templateSelectorEl.offsetHeight : 0;
1733                    const uiH = fsH - currentTemplateSelectorH;
1734                    ui.style.height    = uiH + 'px';
1735                    ui.style.minHeight = uiH + 'px';
1736
1737                    // Reset zoom/pan so book is centred
1738                    flipZoom = 1.0;
1739                    flipPanX = 0;
1740                    flipPanY = 0;
1741
1742                    if (pageFlipInstance) {
1743                        try { pageFlipInstance.destroy(); } catch(e){}
1744                        pageFlipInstance = null;
1745                    }
1746                    cachedCanvases = [];
1747                    cachedFitScale = 0;
1748
1749                    if (options.mode === 'continuous') {
1750                        renderAllPages();
1751                    } else if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1752                        scrollArea.classList.add('flipbook-mode');
1753                        renderFlipbook();
1754                    } else {
1755                        renderPage(currentPage);
1756                    }
1757                }, 200);
1758            } else {
1759                // Restore original container + ui dimensions
1760                container.style.setProperty('height',     originalHeight, 'important');
1761                container.style.setProperty('min-height', originalHeight, 'important');
1762                container.style.removeProperty('width');
1763                container.style.removeProperty('max-width');
1764                container.style.removeProperty('border-radius');
1765
1766                ui.style.height    = baseViewerH + 'px';
1767                ui.style.minHeight = baseViewerH + 'px';
1768
1769                flipZoom = 1.0;
1770                flipPanX = 0;
1771                flipPanY = 0;
1772
1773                if (pageFlipInstance) {
1774                    try { pageFlipInstance.destroy(); } catch(e){}
1775                    pageFlipInstance = null;
1776                }
1777                cachedCanvases = [];
1778                cachedFitScale = 0;
1779
1780                if (options.mode === 'continuous') {
1781                    renderAllPages();
1782                } else if (options.mode === 'flipbook' || options.mode === 'flipbook_single') {
1783                    scrollArea.classList.add('flipbook-mode');
1784                    renderFlipbook();
1785                } else {
1786                    renderPage(currentPage);
1787                }
1788            }
1789        });
1790
1791        // TTS (Text to Speech)
1792        if (ttsBtn) {
1793            let speaking = false;
1794            ttsBtn.addEventListener('click', () => {
1795                if (speaking) {
1796                    window.speechSynthesis.cancel();
1797                    speaking = false;
1798                    ttsBtn.querySelector('span').textContent = 'Turn on/off Sound';
1799                    return;
1800                }
1801                pdfDoc.getPage(currentPage).then(page => {
1802                    page.getTextContent().then(textContent => {
1803                        let text = textContent.items.map(item => item.str).join(' ');
1804                        const utterance = new SpeechSynthesisUtterance(text);
1805                        window.speechSynthesis.speak(utterance);
1806                        speaking = true;
1807                        ttsBtn.querySelector('span').textContent = 'Stop Sound';
1808                        utterance.onend = () => {
1809                            speaking = false;
1810                            ttsBtn.querySelector('span').textContent = 'Turn on/off Sound';
1811                        };
1812                    });
1813                });
1814            });
1815        }
1816    }
1817
1818    window.smartEmbedInitAll = function() {
1819        if (typeof pdfjsLib === 'undefined') {
1820            if (!document.getElementById('smartembed-pdfjs-fallback')) {
1821                const script = document.createElement('script');
1822                script.id = 'smartembed-pdfjs-fallback';
1823                script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
1824                document.head.appendChild(script);
1825            }
1826            setTimeout(window.smartEmbedInitAll, 500);
1827            return;
1828        }
1829        
1830        if (typeof St === 'undefined' && typeof PageFlip === 'undefined') {
1831            if (!document.getElementById('smartembed-pageflip-fallback')) {
1832                const script = document.createElement('script');
1833                script.id = 'smartembed-pageflip-fallback';
1834                script.src = 'https://cdn.jsdelivr.net/npm/page-flip/dist/js/page-flip.browser.min.js';
1835                document.head.appendChild(script);
1836            }
1837            setTimeout(window.smartEmbedInitAll, 500);
1838            return;
1839        }
1840        
1841        if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1842            pdfjsLib.GlobalWorkerOptions.workerSrc = (window.smartEmbedFront && window.smartEmbedFront.workerSrc) 
1843                ? window.smartEmbedFront.workerSrc 
1844                : 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
1845        }
1846
1847        document.querySelectorAll('.smartembed-pdf-app-container:not(.smartembed-initialized)').forEach(container => {
1848            container.classList.add('smartembed-initialized');
1849            initSmartEmbedViewer(container);
1850        });
1851
1852        // Dispatch event for Pro addon to hook into
1853        document.dispatchEvent(new CustomEvent('smartembed_viewer_initialized'));
1854    };
1855
1856    document.addEventListener('DOMContentLoaded', window.smartEmbedInitAll);
1857
1858    // Support for Elementor Editor
1859    if (typeof window.jQuery !== 'undefined') {
1860        window.jQuery(window).on('elementor/frontend/init', function() {
1861            if (window.elementorFrontend && window.elementorFrontend.hooks) {
1862                window.elementorFrontend.hooks.addAction('frontend/element_ready/widget', function() {
1863                    window.smartEmbedInitAll();
1864                });
1865            }
1866        });
1867    }
1868
1869    // Fallback for other dynamic content loads (e.g., AJAX, Gutenberg preview)
1870    const observer = new MutationObserver((mutations) => {
1871        for (let mutation of mutations) {
1872            if (mutation.addedNodes.length) {
1873                const hasViewer = Array.from(mutation.addedNodes).some(node => 
1874                    node.nodeType === 1 && (node.classList.contains('smartembed-pdf-app-container') || node.querySelector('.smartembed-pdf-app-container'))
1875                );
1876                if (hasViewer) {
1877                    window.smartEmbedInitAll();
1878                }
1879            }
1880        }
1881    });
1882    observer.observe(document.body, { childList: true, subtree: true });
1883
1884    // Execute immediately in case script was loaded dynamically after DOMContentLoaded
1885    window.smartEmbedInitAll();
1886
1887})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.