AMBOSS Width Expander (⇔)

Add a draggable ⇔ button to expand/collapse content width with slider (50-100%), auto-save settings per page

Size

42.6 KB

Version

1.2.79

Created

Jan 23, 2026

Updated

12 days ago

1// ==UserScript==
2// @name		AMBOSS Width Expander (⇔)
3// @description		Add a draggable ⇔ button to expand/collapse content width with slider (50-100%), auto-save settings per page
4// @version		1.2.79
5// @match		https://next.amboss.com/*
6// @match		https://*.amboss.com/*
7// @match		https://amboss.com/*
8// @icon		https://next.amboss.com/us/static/assets/86b15308e0846555.png
9// @grant		GM.getValue
10// @grant		GM.setValue
11// @require		https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
12// @require		https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
13// ==/UserScript==
14(function() {
15    'use strict';
16
17    console.log('AMBOSS Width Expander: Starting initialization');
18
19    // State management
20    let state = {
21        isActive: false,
22        widthPercent: 100,
23        buttonPosition: { x: window.innerWidth - 80, y: window.innerHeight - 80 },
24        targetSelector: null,
25        tablesMarginLeft: 0
26    };
27
28    let elements = {
29        button: null,
30        panel: null,
31        contentContainer: null
32    };
33
34    // Get unique key for current page
35    function getPageKey() {
36        return 'amboss_width_v2_' + window.location.pathname;
37    }
38
39    // Load saved settings for current page
40    async function loadSettings() {
41        try {
42            const pageKey = getPageKey();
43            const saved = await GM.getValue(pageKey, null);
44            
45            if (saved) {
46                const parsed = JSON.parse(saved);
47                // Always start disabled after refresh
48                state.isActive = false;
49                state.widthPercent = parsed.widthPercent || 100;
50                state.buttonPosition = parsed.buttonPosition || state.buttonPosition;
51                state.targetSelector = parsed.targetSelector || null;
52                state.tablesMarginLeft = parsed.tablesMarginLeft || 0;
53                console.log('AMBOSS Width Expander: Loaded settings for page (starting disabled):', parsed);
54            } else {
55                // Default to disabled on first visit
56                state.isActive = false;
57                state.widthPercent = 100;
58                state.tablesMarginLeft = 0;
59                console.log('AMBOSS Width Expander: No saved settings, defaulting to disabled');
60            }
61        } catch (error) {
62            console.error('AMBOSS Width Expander: Error loading settings:', error);
63        }
64    }
65
66    // Save settings for current page
67    async function saveSettings() {
68        try {
69            const pageKey = getPageKey();
70            const toSave = {
71                isActive: state.isActive,
72                widthPercent: state.widthPercent,
73                buttonPosition: state.buttonPosition,
74                targetSelector: state.targetSelector,
75                tablesMarginLeft: state.tablesMarginLeft
76            };
77            await GM.setValue(pageKey, JSON.stringify(toSave));
78            console.log('AMBOSS Width Expander: Saved settings:', toSave);
79        } catch (error) {
80            console.error('AMBOSS Width Expander: Error saving settings:', error);
81        }
82    }
83
84    // Find content container
85    function findContentContainer() {
86        // Try saved selector first
87        if (state.targetSelector) {
88            const saved = document.querySelector(state.targetSelector);
89            if (saved) {
90                console.log('AMBOSS Width Expander: Using saved selector:', state.targetSelector);
91                return saved;
92            }
93        }
94
95        // Target the parent container with max-width constraint
96        const selectors = [
97            'div[class*="articlePageWidth"]',
98            'div[class*="articleContainer"]',
99            'article[class*="article"]',
100            'div[class*="article-content"]',
101            'div[class*="content-container"]',
102            'main[class*="content"]',
103            'div[id*="article"]',
104            'div[class*="main-content"]',
105            '.article-wrapper',
106            'main article',
107            'main > div'
108        ];
109
110        for (const selector of selectors) {
111            const element = document.querySelector(selector);
112            if (element) {
113                console.log('AMBOSS Width Expander: Found content container with selector:', selector);
114                return element;
115            }
116        }
117
118        console.log('AMBOSS Width Expander: Using body as fallback');
119        return document.body;
120    }
121
122    // Apply width to content
123    function applyWidth() {
124        if (!elements.contentContainer) {
125            elements.contentContainer = findContentContainer();
126        }
127
128        if (state.isActive) {
129            const widthValue = state.widthPercent + '%';
130            
131            // Apply to the main container with !important to override site CSS
132            elements.contentContainer.style.setProperty('max-width', widthValue, 'important');
133            elements.contentContainer.style.setProperty('width', widthValue, 'important');
134            elements.contentContainer.style.setProperty('margin', '0 auto', 'important');
135            elements.contentContainer.style.transition = 'max-width 0.3s ease, width 0.3s ease';
136            
137            console.log('AMBOSS Width Expander: Applied width:', widthValue);
138            
139            // Handle wide tables
140            handleWideTables();
141        } else {
142            elements.contentContainer.style.removeProperty('max-width');
143            elements.contentContainer.style.removeProperty('width');
144            elements.contentContainer.style.removeProperty('margin');
145            
146            console.log('AMBOSS Width Expander: Removed width override');
147            
148            // Remove table styling
149            removeTableStyling();
150        }
151    }
152
153    // Handle wide tables to prevent overflow
154    function handleWideTables() {
155        const tables = elements.contentContainer.querySelectorAll('table');
156        
157        tables.forEach(table => {
158            // Skip if already has controls
159            if (table.dataset.hasControls) return;
160            table.dataset.hasControls = 'true';
161            
162            let wrapper = table.parentElement;
163            
164            // Check if parent is a wrapper, if not - wrap the table
165            if (!wrapper.classList.contains('table-wrapper')) {
166                // Find the actual wrapper or use parent
167                while (wrapper && !wrapper.classList.contains('table-wrapper') && wrapper !== elements.contentContainer) {
168                    wrapper = wrapper.parentElement;
169                }
170            }
171            
172            // Apply styles to wrapper if found
173            if (wrapper && wrapper.classList.contains('table-wrapper')) {
174                wrapper.style.setProperty('overflow-x', 'auto', 'important');
175                wrapper.style.setProperty('border', '2px solid #667eea', 'important');
176                wrapper.style.setProperty('border-radius', '8px', 'important');
177                wrapper.style.setProperty('background', '#f8f9ff', 'important');
178                wrapper.style.setProperty('padding', '4px', 'important');
179                wrapper.style.setProperty('max-width', '100%', 'important');
180                wrapper.style.setProperty('position', 'relative', 'important');
181            }
182            
183            // Also style the table itself to ensure it's centered
184            table.style.setProperty('margin', '0 auto', 'important');
185            table.style.setProperty('max-width', '100%', 'important');
186            
187            // Add control buttons to move table left/right
188            const controlsDiv = document.createElement('div');
189            controlsDiv.className = 'amboss-table-controls';
190            controlsDiv.style.cssText = `
191                position: absolute;
192                top: 5px;
193                right: 5px;
194                display: flex;
195                gap: 5px;
196                z-index: 10;
197            `;
198            
199            const leftBtn = document.createElement('button');
200            leftBtn.innerHTML = '←';
201            leftBtn.style.cssText = `
202                width: 30px;
203                height: 30px;
204                background: #667eea;
205                color: white;
206                border: none;
207                border-radius: 50%;
208                cursor: pointer;
209                font-size: 16px;
210                font-weight: bold;
211                box-shadow: 0 2px 6px rgba(0,0,0,0.2);
212            `;
213            leftBtn.title = 'Move ALL tables left';
214            
215            const centerBtn = document.createElement('button');
216            centerBtn.innerHTML = '⊙';
217            centerBtn.style.cssText = `
218                width: 30px;
219                height: 30px;
220                background: #11998e;
221                color: white;
222                border: none;
223                border-radius: 50%;
224                cursor: pointer;
225                font-size: 16px;
226                font-weight: bold;
227                box-shadow: 0 2px 6px rgba(0,0,0,0.2);
228            `;
229            centerBtn.title = 'Center ALL tables';
230            
231            const rightBtn = document.createElement('button');
232            rightBtn.innerHTML = '→';
233            rightBtn.style.cssText = `
234                width: 30px;
235                height: 30px;
236                background: #667eea;
237                color: white;
238                border: none;
239                border-radius: 50%;
240                cursor: pointer;
241                font-size: 16px;
242                font-weight: bold;
243                box-shadow: 0 2px 6px rgba(0,0,0,0.2);
244            `;
245            rightBtn.title = 'Move ALL tables right';
246            
247            leftBtn.addEventListener('click', (e) => {
248                e.stopPropagation();
249                state.tablesMarginLeft -= 50;
250                
251                // Move ALL tables
252                const allTables = elements.contentContainer.querySelectorAll('table');
253                allTables.forEach(t => {
254                    let w = t.parentElement;
255                    while (w && !w.classList.contains('table-wrapper') && w !== elements.contentContainer) {
256                        w = w.parentElement;
257                    }
258                    const elementToMove = (w && w.classList.contains('table-wrapper')) ? w : t.parentElement;
259                    elementToMove.style.setProperty('margin-left', state.tablesMarginLeft + 'px', 'important');
260                    elementToMove.style.setProperty('margin-right', 'auto', 'important');
261                });
262                
263                // Save settings
264                saveSettings();
265                console.log('AMBOSS Width Expander: Moved ALL tables left to', state.tablesMarginLeft);
266            });
267            
268            centerBtn.addEventListener('click', (e) => {
269                e.stopPropagation();
270                state.tablesMarginLeft = 0;
271                
272                // Center ALL tables
273                const allTables = elements.contentContainer.querySelectorAll('table');
274                allTables.forEach(t => {
275                    let w = t.parentElement;
276                    while (w && !w.classList.contains('table-wrapper') && w !== elements.contentContainer) {
277                        w = w.parentElement;
278                    }
279                    const elementToMove = (w && w.classList.contains('table-wrapper')) ? w : t.parentElement;
280                    elementToMove.style.setProperty('margin', '0 auto', 'important');
281                });
282                
283                // Save settings
284                saveSettings();
285                console.log('AMBOSS Width Expander: Centered ALL tables');
286            });
287            
288            rightBtn.addEventListener('click', (e) => {
289                e.stopPropagation();
290                state.tablesMarginLeft += 50;
291                
292                // Move ALL tables
293                const allTables = elements.contentContainer.querySelectorAll('table');
294                allTables.forEach(t => {
295                    let w = t.parentElement;
296                    while (w && !w.classList.contains('table-wrapper') && w !== elements.contentContainer) {
297                        w = w.parentElement;
298                    }
299                    const elementToMove = (w && w.classList.contains('table-wrapper')) ? w : t.parentElement;
300                    elementToMove.style.setProperty('margin-left', state.tablesMarginLeft + 'px', 'important');
301                    elementToMove.style.setProperty('margin-right', 'auto', 'important');
302                });
303                
304                // Save settings
305                saveSettings();
306                console.log('AMBOSS Width Expander: Moved ALL tables right to', state.tablesMarginLeft);
307            });
308            
309            controlsDiv.appendChild(leftBtn);
310            controlsDiv.appendChild(centerBtn);
311            controlsDiv.appendChild(rightBtn);
312            
313            // Add controls to wrapper or table parent
314            const controlParent = (wrapper && wrapper.classList.contains('table-wrapper')) ? wrapper : table.parentElement;
315            if (controlParent) {
316                controlParent.style.position = 'relative';
317                controlParent.appendChild(controlsDiv);
318            }
319        });
320        
321        // Apply saved margin to all tables
322        if (state.tablesMarginLeft !== 0) {
323            const allTables = elements.contentContainer.querySelectorAll('table');
324            allTables.forEach(t => {
325                let w = t.parentElement;
326                while (w && !w.classList.contains('table-wrapper') && w !== elements.contentContainer) {
327                    w = w.parentElement;
328                }
329                const elementToMove = (w && w.classList.contains('table-wrapper')) ? w : t.parentElement;
330                elementToMove.style.setProperty('margin-left', state.tablesMarginLeft + 'px', 'important');
331                elementToMove.style.setProperty('margin-right', 'auto', 'important');
332            });
333            console.log('AMBOSS Width Expander: Applied saved table margin:', state.tablesMarginLeft);
334        }
335    }
336    
337    // Remove table styling
338    function removeTableStyling() {
339        const wrappers = document.querySelectorAll('.table-wrapper');
340        wrappers.forEach(wrapper => {
341            wrapper.style.removeProperty('overflow-x');
342            wrapper.style.removeProperty('border');
343            wrapper.style.removeProperty('border-radius');
344            wrapper.style.removeProperty('background');
345            wrapper.style.removeProperty('padding');
346            wrapper.style.removeProperty('margin');
347            wrapper.style.removeProperty('max-width');
348            wrapper.style.removeProperty('position');
349        });
350        
351        // Remove control buttons
352        const controls = document.querySelectorAll('.amboss-table-controls');
353        controls.forEach(control => control.remove());
354        
355        // Also remove table styling
356        const tables = document.querySelectorAll('table');
357        tables.forEach(table => {
358            table.style.removeProperty('margin');
359            table.style.removeProperty('margin-left');
360            table.style.removeProperty('max-width');
361            table.dataset.hasControls = '';
362        });
363    }
364
365    // Create floating button
366    function createButton() {
367        const button = document.createElement('div');
368        button.id = 'amboss-width-button';
369        button.innerHTML = '⇔';
370        button.style.cssText = `
371            position: fixed;
372            width: 50px;
373            height: 50px;
374            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
375            color: white;
376            border-radius: 50%;
377            display: flex;
378            align-items: center;
379            justify-content: center;
380            font-size: 24px;
381            font-weight: bold;
382            cursor: move;
383            z-index: 999999;
384            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
385            user-select: none;
386            transition: transform 0.2s ease, box-shadow 0.2s ease;
387            left: ${state.buttonPosition.x}px;
388            top: ${state.buttonPosition.y}px;
389        `;
390
391        // Hover effect
392        button.addEventListener('mouseenter', () => {
393            button.style.transform = 'scale(1.1)';
394            button.style.boxShadow = '0 6px 16px rgba(0,0,0,0.4)';
395        });
396
397        button.addEventListener('mouseleave', () => {
398            button.style.transform = 'scale(1)';
399            button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
400        });
401
402        // Make draggable
403        let isDragging = false;
404        let dragStartX, dragStartY;
405        let clickStartTime;
406
407        button.addEventListener('mousedown', (e) => {
408            isDragging = true;
409            clickStartTime = Date.now();
410            dragStartX = e.clientX - state.buttonPosition.x;
411            dragStartY = e.clientY - state.buttonPosition.y;
412            button.style.cursor = 'grabbing';
413            e.preventDefault();
414        });
415
416        document.addEventListener('mousemove', (e) => {
417            if (isDragging) {
418                state.buttonPosition.x = e.clientX - dragStartX;
419                state.buttonPosition.y = e.clientY - dragStartY;
420                
421                // Keep button within viewport
422                state.buttonPosition.x = Math.max(0, Math.min(window.innerWidth - 50, state.buttonPosition.x));
423                state.buttonPosition.y = Math.max(0, Math.min(window.innerHeight - 50, state.buttonPosition.y));
424                
425                button.style.left = state.buttonPosition.x + 'px';
426                button.style.top = state.buttonPosition.y + 'px';
427            }
428        });
429
430        document.addEventListener('mouseup', (e) => {
431            if (isDragging) {
432                isDragging = false;
433                button.style.cursor = 'move';
434                
435                // If it was a quick click (not a drag), toggle panel
436                const clickDuration = Date.now() - clickStartTime;
437                const dragDistance = Math.sqrt(
438                    Math.pow(e.clientX - (state.buttonPosition.x + dragStartX), 2) +
439                    Math.pow(e.clientY - (state.buttonPosition.y + dragStartY), 2)
440                );
441                
442                if (clickDuration < 200 && dragDistance < 5) {
443                    togglePanel();
444                } else {
445                    saveSettings();
446                }
447            }
448        });
449
450        document.body.appendChild(button);
451        elements.button = button;
452        console.log('AMBOSS Width Expander: Button created');
453    }
454
455    // Create control panel
456    function createPanel() {
457        const panel = document.createElement('div');
458        panel.id = 'amboss-width-panel';
459        panel.style.cssText = `
460            position: fixed;
461            background: white;
462            border-radius: 12px;
463            padding: 20px;
464            box-shadow: 0 8px 32px rgba(0,0,0,0.2);
465            z-index: 1000000;
466            display: none;
467            min-width: 280px;
468            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
469        `;
470
471        panel.innerHTML = `
472            <div style="margin-bottom: 15px; font-size: 16px; font-weight: 600; color: #333; border-bottom: 2px solid #667eea; padding-bottom: 10px;">
473                ⇔ Width Control
474            </div>
475            
476            <div style="margin-bottom: 15px;">
477                <button id="amboss-toggle-btn" style="
478                    width: 100%;
479                    padding: 12px;
480                    border: none;
481                    border-radius: 8px;
482                    font-size: 14px;
483                    font-weight: 600;
484                    cursor: pointer;
485                    transition: all 0.3s ease;
486                    color: white;
487                ">
488                    Enable
489                </button>
490            </div>
491            
492            <div style="margin-bottom: 15px;">
493                <label style="display: block; margin-bottom: 8px; font-size: 13px; color: #666; font-weight: 500;">
494                    Width: <span id="amboss-width-value">100</span>%
495                </label>
496                <input type="range" id="amboss-width-slider" min="50" max="100" value="100" style="
497                    width: 100%;
498                    height: 6px;
499                    border-radius: 3px;
500                    background: linear-gradient(to right, #667eea 0%, #764ba2 100%);
501                    outline: none;
502                    -webkit-appearance: none;
503                ">
504            </div>
505            
506            <div style="margin-bottom: 15px; padding: 12px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px; font-size: 12px; color: #856404; line-height: 1.6;">
507                <strong style="color: #856404;">⚠️ Important Instructions:</strong><br>
508                1. Click <strong>Expand All</strong> on the page first<br>
509                2. Then click <strong>Enable</strong> here<br>
510                <br>
511                <span style="font-size: 11px; color: #856404;">If you don't follow this order, tables will appear off-center</span>
512            </div>
513            
514            <div style="margin-bottom: 15px; padding: 12px; background: #e8eeff; border-left: 4px solid #667eea; border-radius: 4px; font-size: 12px; color: #333; line-height: 1.5;">
515                <strong style="color: #667eea;">How to Use:</strong><br>
516                • Click Enable to activate width expansion<br>
517                • Drag the slider to adjust content width<br>
518                • Settings are saved automatically per page<br>
519                • You can drag the ⇔ button anywhere
520            </div>
521            
522            <div style="margin-bottom: 15px;">
523                <button id="amboss-screenshot-btn" style="
524                    width: 100%;
525                    padding: 12px;
526                    border: none;
527                    border-radius: 8px;
528                    font-size: 14px;
529                    font-weight: 600;
530                    cursor: pointer;
531                    transition: all 0.3s ease;
532                    color: white;
533                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
534                ">
535                    📸 Long Screenshot
536                </button>
537            </div>
538            
539            <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee; font-size: 11px; color: #999; text-align: center;">
540                Settings auto-save per page
541            </div>
542        `;
543
544        // Position panel near button
545        function positionPanel() {
546            const buttonRect = elements.button.getBoundingClientRect();
547            let panelX = buttonRect.left - 300;
548            let panelY = buttonRect.top;
549
550            if (panelX < 10) panelX = buttonRect.right + 10;
551            if (panelY + 250 > window.innerHeight) panelY = window.innerHeight - 250;
552            if (panelY < 10) panelY = 10;
553
554            panel.style.left = panelX + 'px';
555            panel.style.top = panelY + 'px';
556        }
557
558        document.body.appendChild(panel);
559        elements.panel = panel;
560
561        // Setup controls
562        const toggleBtn = document.getElementById('amboss-toggle-btn');
563        const slider = document.getElementById('amboss-width-slider');
564        const widthValue = document.getElementById('amboss-width-value');
565        const screenshotBtn = document.getElementById('amboss-screenshot-btn');
566
567        // Update toggle button appearance
568        function updateToggleButton() {
569            if (state.isActive) {
570                toggleBtn.textContent = '✓ Active';
571                toggleBtn.style.background = 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)';
572            } else {
573                toggleBtn.textContent = '✕ Disabled';
574                toggleBtn.style.background = 'linear-gradient(135deg, #eb3349 0%, #f45c43 100%)';
575            }
576        }
577
578        // Toggle button click
579        toggleBtn.addEventListener('click', () => {
580            state.isActive = !state.isActive;
581            updateToggleButton();
582            applyWidth();
583            saveSettings();
584        });
585
586        // Slider change
587        slider.addEventListener('input', () => {
588            state.widthPercent = parseInt(slider.value);
589            widthValue.textContent = state.widthPercent;
590            applyWidth();
591        });
592
593        slider.addEventListener('change', () => {
594            saveSettings();
595        });
596
597        // Long Screenshot button click
598        screenshotBtn.addEventListener('click', async () => {
599            console.log('AMBOSS Width Expander: Taking screenshots by sections');
600            
601            // Show loading message
602            screenshotBtn.textContent = '⏳ Preparing...';
603            screenshotBtn.disabled = true;
604            
605            try {
606                // Find all sections
607                const sections = document.querySelectorAll('[data-e2e-test-id="section-with-header"]');
608                
609                if (sections.length === 0) {
610                    alert('❌ No sections found! Make sure the page is loaded and expanded.');
611                    return;
612                }
613                
614                console.log('AMBOSS Width Expander: Found', sections.length, 'sections');
615                
616                // Hide extension UI
617                elements.button.style.display = 'none';
618                elements.panel.style.display = 'none';
619                
620                const screenshots = [];
621                
622                // Capture each section
623                for (let i = 0; i < sections.length; i++) {
624                    const section = sections[i];
625                    
626                    // Get section title
627                    const titleElement = section.querySelector('[data-e2e-test-id="section-header-title"]');
628                    const sectionTitle = titleElement ? titleElement.textContent.trim() : `Section ${i + 1}`;
629                    
630                    screenshotBtn.textContent = `${i + 1}/${sections.length}: ${sectionTitle}...`;
631                    
632                    // Scroll section into view
633                    section.scrollIntoView({ behavior: 'instant', block: 'start' });
634                    await new Promise(resolve => setTimeout(resolve, 800));
635                    
636                    console.log('AMBOSS Width Expander: Capturing section', i + 1, '-', sectionTitle);
637                    
638                    try {
639                        // Capture screenshot of this section
640                        const canvas = await html2canvas(section, {
641                            useCORS: true,
642                            allowTaint: false,
643                            backgroundColor: '#ffffff',
644                            logging: false,
645                            scale: 1.5,
646                            windowWidth: section.scrollWidth,
647                            windowHeight: section.scrollHeight,
648                            ignoreElements: (element) => {
649                                // Skip extension UI
650                                if (element.id === 'amboss-width-button' || 
651                                    element.id === 'amboss-width-panel' ||
652                                    element.classList.contains('amboss-table-controls')) {
653                                    return true;
654                                }
655                                return false;
656                            }
657                        });
658                        
659                        screenshots.push({
660                            title: sectionTitle,
661                            canvas: canvas,
662                            data: canvas.toDataURL('image/jpeg', 0.95)
663                        });
664                        
665                        console.log('AMBOSS Width Expander: Captured section', i + 1, 'successfully');
666                    } catch (err) {
667                        console.error('AMBOSS Width Expander: Failed to capture section', i + 1, err);
668                        // Continue with next section
669                    }
670                }
671                
672                if (screenshots.length === 0) {
673                    alert('❌ Failed to capture any sections!');
674                    return;
675                }
676                
677                // Create PDF
678                screenshotBtn.textContent = '⏳ Creating PDF...';
679                console.log('AMBOSS Width Expander: Creating PDF with', screenshots.length, 'sections');
680                
681                const { jsPDF } = window.jspdf;
682                const pdf = new jsPDF({
683                    orientation: 'portrait',
684                    unit: 'mm',
685                    format: 'a4',
686                    compress: true
687                });
688                
689                const pageWidth = 210; // A4 width in mm
690                const pageHeight = 297; // A4 height in mm
691                const margin = 5; // 5mm margin
692                const maxWidth = pageWidth - (2 * margin);
693                const maxHeight = pageHeight - (2 * margin);
694                
695                // Add each screenshot to PDF
696                for (let i = 0; i < screenshots.length; i++) {
697                    const screenshot = screenshots[i];
698                    
699                    screenshotBtn.textContent = `⏳ Adding ${i + 1}/${screenshots.length} to PDF...`;
700                    
701                    // Calculate dimensions to fit A4 with margins
702                    let imgWidth = maxWidth;
703                    let imgHeight = (screenshot.canvas.height * imgWidth) / screenshot.canvas.width;
704                    
705                    // If image is too tall, split it across multiple pages
706                    if (imgHeight > maxHeight) {
707                        const numPages = Math.ceil(imgHeight / maxHeight);
708                        console.log('AMBOSS Width Expander: Section', screenshot.title, 'needs', numPages, 'pages');
709                        
710                        for (let page = 0; page < numPages; page++) {
711                            if (i > 0 || page > 0) {
712                                pdf.addPage();
713                            }
714                            
715                            // Calculate the portion of the image to show on this page
716                            const sourceY = page * (screenshot.canvas.height / numPages);
717                            const sourceHeight = screenshot.canvas.height / numPages;
718                            
719                            // Create a temporary canvas for this page
720                            const tempCanvas = document.createElement('canvas');
721                            tempCanvas.width = screenshot.canvas.width;
722                            tempCanvas.height = sourceHeight;
723                            const tempCtx = tempCanvas.getContext('2d');
724                            
725                            // Draw the portion of the image
726                            tempCtx.drawImage(
727                                screenshot.canvas,
728                                0, sourceY, screenshot.canvas.width, sourceHeight,
729                                0, 0, screenshot.canvas.width, sourceHeight
730                            );
731                            
732                            const pageData = tempCanvas.toDataURL('image/jpeg', 0.95);
733                            const pageImgHeight = (sourceHeight * imgWidth) / screenshot.canvas.width;
734                            
735                            pdf.addImage(pageData, 'JPEG', margin, margin, imgWidth, pageImgHeight);
736                        }
737                    } else {
738                        // Image fits in one page
739                        if (i > 0) {
740                            pdf.addPage();
741                        }
742                        
743                        pdf.addImage(screenshot.data, 'JPEG', margin, margin, imgWidth, imgHeight);
744                    }
745                    
746                    console.log('AMBOSS Width Expander: Added section', i + 1, '-', screenshot.title);
747                }
748                
749                // Save PDF
750                screenshotBtn.textContent = '⏳ Saving PDF...';
751                const pageTitle = document.title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
752                pdf.save(`amboss-${pageTitle}.pdf`);
753                
754                console.log('AMBOSS Width Expander: PDF saved');
755                alert(`✅ Created PDF with ${screenshots.length} sections!`);
756                
757            } catch (error) {
758                console.error('AMBOSS Width Expander: Screenshot error:', error);
759                alert('❌ Failed to take screenshot. Error: ' + error.message);
760            } finally {
761                elements.button.style.display = 'flex';
762                screenshotBtn.textContent = '📸 Long Screenshot';
763                screenshotBtn.disabled = false;
764            }
765        });
766
767        // Initialize controls
768        slider.value = state.widthPercent;
769        widthValue.textContent = state.widthPercent;
770        updateToggleButton();
771
772        // Position panel when shown
773        const originalDisplay = panel.style.display;
774        const observer = new MutationObserver(() => {
775            if (panel.style.display !== 'none' && panel.style.display !== originalDisplay) {
776                positionPanel();
777            }
778        });
779        observer.observe(panel, { attributes: true, attributeFilter: ['style'] });
780
781        console.log('AMBOSS Width Expander: Panel created');
782    }
783
784    // Toggle panel visibility
785    function togglePanel() {
786        if (elements.panel.style.display === 'none') {
787            elements.panel.style.display = 'block';
788            // Position panel near button
789            const buttonRect = elements.button.getBoundingClientRect();
790            let panelX = buttonRect.left - 300;
791            let panelY = buttonRect.top;
792
793            if (panelX < 10) panelX = buttonRect.right + 10;
794            if (panelY + 250 > window.innerHeight) panelY = window.innerHeight - 250;
795            if (panelY < 10) panelY = 10;
796
797            elements.panel.style.left = panelX + 'px';
798            elements.panel.style.top = panelY + 'px';
799        } else {
800            elements.panel.style.display = 'none';
801        }
802    }
803
804    // Close panel when clicking outside
805    document.addEventListener('click', (e) => {
806        if (elements.panel && elements.button) {
807            if (!elements.panel.contains(e.target) && !elements.button.contains(e.target)) {
808                elements.panel.style.display = 'none';
809            }
810        }
811    });
812
813    // Add custom styles for slider
814    function addStyles() {
815        const style = document.createElement('style');
816        style.textContent = `
817            #amboss-width-slider::-webkit-slider-thumb {
818                -webkit-appearance: none;
819                appearance: none;
820                width: 18px;
821                height: 18px;
822                border-radius: 50%;
823                background: white;
824                cursor: pointer;
825                box-shadow: 0 2px 6px rgba(0,0,0,0.3);
826                transition: transform 0.2s ease;
827            }
828            
829            #amboss-width-slider::-webkit-slider-thumb:hover {
830                transform: scale(1.2);
831            }
832            
833            #amboss-width-slider::-moz-range-thumb {
834                width: 18px;
835                height: 18px;
836                border-radius: 50%;
837                background: white;
838                cursor: pointer;
839                border: none;
840                box-shadow: 0 2px 6px rgba(0,0,0,0.3);
841                transition: transform 0.2s ease;
842            }
843            
844            #amboss-width-slider::-moz-range-thumb:hover {
845                transform: scale(1.2);
846            }
847            
848            /* Allow screenshots - remove any blocking */
849            * {
850                -webkit-user-select: text !important;
851                -moz-user-select: text !important;
852                -ms-user-select: text !important;
853                user-select: text !important;
854            }
855            
856            @media print {
857                /* Force all content to be visible */
858                body, body * {
859                    visibility: visible !important;
860                    display: block !important;
861                    overflow: visible !important;
862                    max-height: none !important;
863                    height: auto !important;
864                    opacity: 1 !important;
865                }
866                
867                /* Hide extension UI */
868                #amboss-width-button,
869                #amboss-width-panel {
870                    display: none !important;
871                }
872                
873                /* Ensure sections are expanded */
874                [data-e2e-test-id="section-content-is-shown"],
875                [class*="contentContainer"],
876                [class*="content"] {
877                    display: block !important;
878                    visibility: visible !important;
879                    max-height: none !important;
880                    height: auto !important;
881                }
882                
883                /* Remove any hidden classes */
884                [class*="hidden"],
885                [class*="collapsed"] {
886                    display: block !important;
887                    visibility: visible !important;
888                }
889                
890                /* Page break settings */
891                h1, h2, h3, h4, h5, h6 {
892                    page-break-after: avoid !important;
893                    break-after: avoid !important;
894                }
895                
896                p, div, section {
897                    page-break-inside: avoid !important;
898                    break-inside: avoid !important;
899                }
900                
901                table {
902                    page-break-inside: auto !important;
903                    break-inside: auto !important;
904                }
905                
906                tr {
907                    page-break-inside: avoid !important;
908                    break-inside: avoid !important;
909                }
910                
911                @page {
912                    size: A4;
913                    margin: 1cm;
914                }
915            }
916        `;
917        document.head.appendChild(style);
918        
919        // Remove any screenshot blocking scripts
920        const blockingScripts = document.querySelectorAll('script[src*="screenshot"], script[src*="capture"], script[src*="protect"]');
921        blockingScripts.forEach(script => script.remove());
922        
923        // Override any screenshot blocking functions
924        if (window.document) {
925            document.addEventListener('contextmenu', function(e) {
926                e.stopPropagation();
927            }, true);
928            
929            document.addEventListener('keydown', function(e) {
930                e.stopPropagation();
931            }, true);
932            
933            document.addEventListener('keyup', function(e) {
934                e.stopPropagation();
935            }, true);
936        }
937        
938        console.log('AMBOSS Width Expander: Removed screenshot blocking');
939    }
940
941    // Initialize extension
942    async function init() {
943        console.log('AMBOSS Width Expander: Initializing...');
944        
945        // Wait for page to be ready
946        if (document.readyState === 'loading') {
947            await new Promise(resolve => {
948                document.addEventListener('DOMContentLoaded', resolve);
949            });
950        }
951
952        // Wait for article content to be present
953        let attempts = 0;
954        while (attempts < 20) {
955            const article = document.querySelector('article[data-e2e-test-id="learningCardContent"]') || 
956                           document.querySelector('div[class*="articlePageWidth"]');
957            
958            if (article) {
959                console.log('AMBOSS Width Expander: Content loaded, found article');
960                break;
961            }
962            
963            console.log('AMBOSS Width Expander: Waiting for content... attempt', attempts + 1);
964            await new Promise(resolve => setTimeout(resolve, 500));
965            attempts++;
966        }
967
968        // Load settings
969        await loadSettings();
970
971        // Find content container
972        elements.contentContainer = findContentContainer();
973
974        // Create UI
975        addStyles();
976        createButton();
977        createPanel();
978
979        // Apply saved state
980        if (state.isActive) {
981            applyWidth();
982        }
983
984        // Watch for DOM changes (collapse/expand all)
985        const observer = new MutationObserver(debounce(() => {
986            if (state.isActive) {
987                console.log('AMBOSS Width Expander: DOM changed, reapplying styles');
988                handleWideTables();
989            }
990        }, 500));
991        
992        observer.observe(elements.contentContainer, {
993            childList: true,
994            subtree: true,
995            attributes: true,
996            attributeFilter: ['class', 'style']
997        });
998
999        // Listen for collapse/expand all button clicks
1000        document.addEventListener('click', (e) => {
1001            const target = e.target;
1002            // Check if clicked on collapse/expand all buttons
1003            if (target.matches('[data-e2e-test-id="keyKnowledgeToggle"]') ||
1004                target.closest('[data-e2e-test-id="keyKnowledgeToggle"]') ||
1005                target.matches('[data-collapse-all], [data-expand-all], button[class*="collapse"], button[class*="expand"]') ||
1006                target.closest('[data-collapse-all], [data-expand-all], button[class*="collapse"], button[class*="expand"]')) {
1007                console.log('AMBOSS Width Expander: Collapse/Expand all clicked, refreshing extension');
1008                
1009                if (state.isActive) {
1010                    // Turn off
1011                    console.log('AMBOSS Width Expander: Turning OFF');
1012                    elements.contentContainer.style.removeProperty('max-width');
1013                    elements.contentContainer.style.removeProperty('width');
1014                    elements.contentContainer.style.removeProperty('margin');
1015                    removeTableStyling();
1016                    
1017                    // Wait for DOM to update, then turn back on
1018                    setTimeout(() => {
1019                        console.log('AMBOSS Width Expander: Turning ON');
1020                        const widthValue = state.widthPercent + '%';
1021                        elements.contentContainer.style.setProperty('max-width', widthValue, 'important');
1022                        elements.contentContainer.style.setProperty('width', widthValue, 'important');
1023                        elements.contentContainer.style.setProperty('margin', '0 auto', 'important');
1024                        handleWideTables();
1025                    }, 1000);
1026                }
1027            }
1028        }, true);
1029
1030        console.log('AMBOSS Width Expander: Initialization complete');
1031    }
1032    
1033    // Debounce function to prevent too many calls
1034    function debounce(func, wait) {
1035        let timeout;
1036        return function executedFunction(...args) {
1037            const later = () => {
1038                clearTimeout(timeout);
1039                func(...args);
1040            };
1041            clearTimeout(timeout);
1042            timeout = setTimeout(later, wait);
1043        };
1044    }
1045
1046    // Start the extension
1047    init();
1048})();
AMBOSS Width Expander (⇔) | Robomonkey