about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-03-15 22:51:37 -0400
committerelioat <elioat@tilde.institute>2025-03-15 22:51:37 -0400
commita78e5d4db2d220b0f19280c4723539d9c1d564c0 (patch)
tree8b7ffae2dac98982994b5cc9915f5548f688e360
parent4c788c1865e52b61a94922328c245bc1c22e8659 (diff)
downloadtour-a78e5d4db2d220b0f19280c4723539d9c1d564c0.tar.gz
*
-rw-r--r--html/immoral/bookmarklet.js451
-rw-r--r--html/immoral/index.html59
2 files changed, 490 insertions, 20 deletions
diff --git a/html/immoral/bookmarklet.js b/html/immoral/bookmarklet.js
new file mode 100644
index 0000000..a7b929b
--- /dev/null
+++ b/html/immoral/bookmarklet.js
@@ -0,0 +1,451 @@
+(function() {
+    // Prevent multiple instances
+    if (window.immoralFontVacuum) {
+        alert('Font Vacuum is already running!');
+        return;
+    }
+    window.immoralFontVacuum = true;
+
+    const style = document.createElement('style');
+    style.textContent = `
+        .fv-container {
+            position: fixed;
+            top: 20px;
+            right: 20px;
+            width: 400px;
+            max-height: 90vh;
+            color: #333333;
+            background: #f5f5f5;
+            border: 2px solid #333;
+            border-radius: 8px;
+            z-index: 999999;
+            overflow-y: auto;
+            font-family: system-ui, -apple-system, sans-serif;
+            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+        }
+        .fv-header {
+            padding: 1rem;
+            background: #333333;
+            color: #f5f5f5;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            cursor: move;
+        }
+        .fv-close {
+            background: none;
+            border: none;
+            color: #f5f5f5;
+            cursor: pointer;
+            font-size: 1.5rem;
+            padding: 0;
+        }
+        .fv-content {
+            padding: 1rem;
+        }
+        .fv-font-item {
+            margin-bottom: 1rem;
+            padding: 1rem;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+        }
+        .fv-preview {
+            margin: 1rem 0;
+            padding: 1rem;
+            border: 1px dashed #333;
+        }
+        .fv-button {
+            background: #333;
+            color: #f5f5f5;
+            border: none;
+            padding: 0.5rem 1rem;
+            border-radius: 4px;
+            cursor: pointer;
+            margin: 0.25rem 0.5rem 0.25rem 0;
+        }
+        .fv-button:last-child {
+            margin-right: 0;
+        }
+        .fv-button:hover {
+            background: #444;
+        }
+    `;
+    document.head.appendChild(style);
+
+    // Create UI container
+    const container = document.createElement('div');
+    container.className = 'fv-container';
+    
+    // Make the container draggable
+    let isDragging = false;
+    let currentX;
+    let currentY;
+    let initialX;
+    let initialY;
+    let xOffset = 0;
+    let yOffset = 0;
+
+    // Create header with drag handle
+    const header = document.createElement('div');
+    header.className = 'fv-header';
+    header.innerHTML = `
+        <div>Font Vacuum</div>
+        <button class="fv-close">&times;</button>
+    `;
+    
+    // Add drag functionality
+    header.addEventListener('mousedown', dragStart);
+    document.addEventListener('mousemove', drag);
+    document.addEventListener('mouseup', dragEnd);
+
+    function dragStart(e) {
+        initialX = e.clientX - xOffset;
+        initialY = e.clientY - yOffset;
+        if (e.target === header) {
+            isDragging = true;
+        }
+    }
+
+    function drag(e) {
+        if (isDragging) {
+            e.preventDefault();
+            currentX = e.clientX - initialX;
+            currentY = e.clientY - initialY;
+            xOffset = currentX;
+            yOffset = currentY;
+            container.style.transform = `translate(${currentX}px, ${currentY}px)`;
+        }
+    }
+
+    function dragEnd() {
+        isDragging = false;
+    }
+
+    // Add close button functionality
+    header.querySelector('.fv-close').addEventListener('click', () => {
+        document.body.removeChild(container);
+        window.immoralFontVacuum = false;
+    });
+
+    // Create content area
+    const content = document.createElement('div');
+    content.className = 'fv-content';
+
+    // Helper to extract font URLs from CSS text
+    function extractFontUrls(cssText, baseUrl) {
+        const fontUrls = [];
+        const fontFaceRegex = /@font-face\s*{[^}]*}/g;
+        const urlRegex = /url\(['"]?([^'"\)]+)['"]?\)/g;
+        const fontFamilyRegex = /font-family\s*:\s*['"]?([^'";]*)['"]?/;
+        
+        function resolveUrl(url, base) {
+            try {
+                // Handle protocol-relative URLs
+                if (url.startsWith('//')) {
+                    return `${location.protocol}${url}`;
+                }
+                // Handle absolute URLs
+                if (url.match(/^https?:\/\//)) {
+                    return url;
+                }
+                // Handle root-relative URLs
+                if (url.startsWith('/')) {
+                    return `${location.origin}${url}`;
+                }
+                // Handle relative URLs - use stylesheet URL as base if available
+                return new URL(url, base || location.href).href;
+            } catch (e) {
+                console.warn('Failed to resolve URL:', url, e);
+                return url;
+            }
+        }
+        
+        let fontFaceMatch;
+        while ((fontFaceMatch = fontFaceRegex.exec(cssText)) !== null) {
+            const fontFaceBlock = fontFaceMatch[0];
+            const familyMatch = fontFaceBlock.match(fontFamilyRegex);
+            const fontFamily = familyMatch ? familyMatch[1].trim() : 'Unknown Font';
+            
+            let urlMatch;
+            while ((urlMatch = urlRegex.exec(fontFaceBlock)) !== null) {
+                let fontUrl = urlMatch[1].trim();
+                
+                // Skip data: URLs
+                if (fontUrl.startsWith('data:')) continue;
+                
+                // Only process known font file types
+                if (!fontUrl.match(/\.(woff2?|ttf|otf|eot)(\?.*)?$/i)) continue;
+                
+                // Resolve the URL relative to the stylesheet's URL
+                fontUrl = resolveUrl(fontUrl, baseUrl);
+                
+                fontUrls.push({
+                    family: fontFamily,
+                    url: fontUrl,
+                    filename: fontUrl.split('/').pop().split('?')[0],
+                    cssRule: fontFaceBlock
+                });
+            }
+        }
+        return fontUrls;
+    }
+
+    // Function to find all fonts on the page
+    function findFonts() {
+        const fonts = new Map();
+        console.group('Font Vacuum: Scanning Stylesheets');
+        
+        // Log total number of stylesheets
+        console.log(`Found ${document.styleSheets.length} stylesheets`);
+        
+        for (const sheet of document.styleSheets) {
+            try {
+                const baseUrl = sheet.href;
+                console.group(`Stylesheet: ${baseUrl || 'inline'}`);
+                const cssRules = sheet.cssRules || sheet.rules;
+                console.log(`- Rules found: ${cssRules.length}`);
+                
+                let cssText = '';
+                let fontFaceCount = 0;
+                for (const rule of cssRules) {
+                    if (rule.type === CSSRule.FONT_FACE_RULE) {
+                        fontFaceCount++;
+                    }
+                    cssText += rule.cssText + '\n';
+                }
+                console.log(`- @font-face rules found: ${fontFaceCount}`);
+                
+                const fontUrls = extractFontUrls(cssText, baseUrl);
+                console.log(`- Font URLs extracted: ${fontUrls.length}`);
+                fontUrls.forEach(font => {
+                    console.log(`  • ${font.family}: ${font.url}`);
+                    if (!fonts.has(font.family)) {
+                        fonts.set(font.family, {
+                            variants: [],
+                            cssRule: font.cssRule
+                        });
+                    }
+                    fonts.get(font.family).variants.push(font);
+                });
+                console.groupEnd();
+            } catch (e) {
+                console.warn(`Could not access stylesheet:`, sheet.href, e);
+                console.groupEnd();
+            }
+        }
+        
+        const results = Array.from(fonts.entries()).map(([family, data]) => ({
+            family,
+            variants: data.variants,
+            cssRule: data.cssRule
+        }));
+        
+        console.log('Final Results:', {
+            totalFamilies: results.length,
+            families: results.map(f => ({
+                family: f.family,
+                variants: f.variants.length,
+                urls: f.variants.map(v => v.url)
+            }))
+        });
+        console.groupEnd();
+        
+        return results;
+    }
+
+    async function downloadFont(url, filename) {
+        try {
+            console.group(`Font Vacuum: Downloading ${filename} from ${url}`);
+            
+            // Try to get the font from an already loaded stylesheet first
+            console.log('Searching for existing font-face rule...');
+            const existingFontRule = Array.from(document.styleSheets)
+                .flatMap(sheet => {
+                    try {
+                        return Array.from(sheet.cssRules);
+                    } catch (e) {
+                        return [];
+                    }
+                })
+                .find(rule => 
+                    rule.type === CSSRule.FONT_FACE_RULE && 
+                    rule.cssText.includes(url)
+                );
+
+            console.log('Existing font-face rule found:', !!existingFontRule);
+            let response;
+            
+            if (existingFontRule) {
+                console.log('Attempting to fetch using existing rule credentials...');
+                const fontBlob = await fetch(url, {
+                    mode: 'cors',
+                    credentials: 'include',
+                    headers: {
+                        'Origin': window.location.origin
+                    }
+                }).then(r => r.blob());
+                response = new Response(fontBlob);
+            } else {
+                console.log('No existing rule found, attempting direct fetch...');
+                response = await fetch(url, {
+                    mode: 'cors',
+                    credentials: 'include',
+                    headers: {
+                        'Origin': window.location.origin
+                    }
+                });
+            }
+
+            if (!response.ok) {
+                throw new Error(`Network response was not ok. Status: ${response.status}`);
+            }
+            
+            console.log('Font fetched successfully, preparing download...');
+            const blob = await response.blob();
+            console.log('Font blob size:', blob.size, 'bytes');
+            
+            const objectUrl = URL.createObjectURL(blob);
+            const link = document.createElement('a');
+            link.href = objectUrl;
+            link.download = filename;
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            
+            setTimeout(() => URL.revokeObjectURL(objectUrl), 100);
+            console.log('Download initiated successfully');
+            console.groupEnd();
+            return true;
+        } catch (error) {
+            console.error('Error downloading font:', error);
+            console.groupEnd();
+            alert(`Error downloading font: ${error.message}\n\nTroubleshooting tips:\n1. Check the console for detailed logs\n2. Try using your browser's developer tools Network tab to find and download the font file directly\n3. Some sites may block direct font downloads`);
+            return false;
+        }
+    }
+
+    async function previewFont(url, fontFamily) {
+        try {
+            console.group(`Font Vacuum: Previewing ${fontFamily} from ${url}`);
+            
+            const existingFontRule = Array.from(document.styleSheets)
+                .flatMap(sheet => {
+                    try {
+                        return Array.from(sheet.cssRules);
+                    } catch (e) {
+                        return [];
+                    }
+                })
+                .find(rule => 
+                    rule.type === CSSRule.FONT_FACE_RULE && 
+                    rule.cssText.includes(url)
+                );
+
+            if (existingFontRule) {
+                console.log('Using existing font-face rule for preview');
+                console.groupEnd();
+                return true;
+            }
+
+            console.log('No existing rule found, attempting to load font...');
+            const response = await fetch(url, {
+                mode: 'cors',
+                credentials: 'include',
+                headers: {
+                    'Origin': window.location.origin
+                }
+            });
+            
+            if (!response.ok) {
+                throw new Error(`Network response was not ok. Status: ${response.status}`);
+            }
+            
+            const blob = await response.blob();
+            console.log('Font blob size:', blob.size, 'bytes');
+            const fontUrl = URL.createObjectURL(blob);
+            
+            const fontFace = new FontFace(fontFamily, `url(${fontUrl})`, {
+                style: 'normal',
+                weight: '400',
+                display: 'swap'
+            });
+            
+            const loadedFont = await fontFace.load();
+            document.fonts.add(loadedFont);
+            URL.revokeObjectURL(fontUrl);
+            
+            console.log('Font loaded successfully');
+            console.groupEnd();
+            return true;
+        } catch (error) {
+            console.error('Error loading font:', error);
+            console.groupEnd();
+            return false;
+        }
+    }
+
+    const fonts = findFonts();
+    
+    if (fonts.length === 0) {
+        content.innerHTML = '<p>No web fonts found on this page.</p>';
+    } else {
+        fonts.forEach(fontData => {
+            const fontItem = document.createElement('div');
+            fontItem.className = 'fv-font-item';
+            
+            // Font family name
+            const fontName = document.createElement('h3');
+            fontName.style.margin = '0 0 1rem 0';
+            fontName.textContent = fontData.family;
+            fontItem.appendChild(fontName);
+            
+            // Preview section
+            const preview = document.createElement('div');
+            preview.className = 'fv-preview';
+            preview.innerHTML = 'Society for me my misery<br>Since Gift of Thee --';
+            fontItem.appendChild(preview);
+            
+            // Download buttons for unique files
+            const uniqueDownloads = new Map();
+            fontData.variants.forEach(variant => {
+                if (!uniqueDownloads.has(variant.url)) {
+                    uniqueDownloads.set(variant.url, {
+                        filename: variant.filename,
+                        url: variant.url
+                    });
+                }
+            });
+            
+            const buttonContainer = document.createElement('div');
+            buttonContainer.style.marginTop = '1rem';
+            
+            uniqueDownloads.forEach(({filename, url}) => {
+                const downloadBtn = document.createElement('button');
+                downloadBtn.className = 'fv-button';
+                downloadBtn.textContent = `⬇ Download ${filename}`;
+                downloadBtn.addEventListener('click', () => downloadFont(url, filename));
+                buttonContainer.appendChild(downloadBtn);
+            });
+            
+            fontItem.appendChild(buttonContainer);
+            
+            fontData.variants.forEach(async (variant) => {
+                if (await previewFont(variant.url, fontData.family)) {
+                    preview.style.fontFamily = fontData.family;
+                    if (variant.cssRule) {
+                        const fontStyle = variant.cssRule.match(/font-style:\s*([^;]+)/);
+                        const fontWeight = variant.cssRule.match(/font-weight:\s*([^;]+)/);
+                        if (fontStyle) preview.style.fontStyle = fontStyle[1];
+                        if (fontWeight) preview.style.fontWeight = fontWeight[1];
+                    }
+                }
+            });
+            
+            content.appendChild(fontItem);
+        });
+    }
+
+    container.appendChild(header);
+    container.appendChild(content);
+    document.body.appendChild(container);
+})(); 
\ No newline at end of file
diff --git a/html/immoral/index.html b/html/immoral/index.html
index b7c089d..c2f4b26 100644
--- a/html/immoral/index.html
+++ b/html/immoral/index.html
@@ -145,26 +145,45 @@
     <main class="container">
         <h1><span class="immoral">Immoral</span> Web Font Vacuum</h1>
         <p>Enter a URL to find, preview, and download web fonts (WOFF/TTF/WOFF2/OTF) present on the page.</p>
-        
-        <form class="input-group" role="search" aria-label="Website URL search form" onsubmit="event.preventDefault();">
-            <label for="urlInput" class="sr-only">Website URL</label>
-            <input 
-                type="url" 
-                id="urlInput" 
-                name="urlInput"
-                placeholder="Enter website URL (e.g., https://example.com)" 
-                required
-                aria-required="true"
-                aria-describedby="urlHint"
-            >
-            <span id="urlHint" class="sr-only">Enter the full website URL including https:// or http://</span>
-            <button 
-                id="analyzeBtn" 
-                type="submit"
-                aria-label="Analyze website for fonts"
-            >Analyze Fonts</button>
-        </form>
-
+        <div id="urlForm">
+            <form class="input-group" role="search" aria-label="Website URL search form" onsubmit="event.preventDefault();">
+                <label for="urlInput" class="sr-only">Website URL</label>
+                <input 
+                    type="url" 
+                    id="urlInput" 
+                    name="urlInput"
+                    placeholder="Enter website URL (e.g., https://example.com)" 
+                    required
+                    aria-required="true"
+                    aria-describedby="urlHint"
+                >
+                <span id="urlHint" class="sr-only">Enter the full website URL including https:// or http://</span>
+                <button 
+                    id="analyzeBtn" 
+                    type="submit"
+                    aria-label="Analyze website for fonts"
+                >Analyze Fonts</button>
+            </form>
+        </div>
+        <div class="bookmarklet-section" style="margin: 2rem 0; padding: 1rem; border: 2px dashed var(--dark); background: var(--beige);">
+            <h2>Font Vacuum Bookmarklet</h2>
+            <p>Drag this link to your bookmarks bar to vacuum fonts from any webpage:</p>
+            <p style="text-align: center;">
+                <a href="javascript:(function(){
+                    const script = document.createElement('script');
+                    script.src = 'https://smallandnearlysilent.com/immoral/bookmarklet.js';
+                    document.body.appendChild(script);
+                })()" 
+                class="bookmarklet-link" 
+                style="display: inline-block; padding: 0.5rem 1rem; background: var(--dark); color: var(--beige); text-decoration: none; border-radius: 4px; font-weight: bold;"
+                onclick="event.preventDefault(); alert('Drag this to your bookmarks bar!');">
+                    Font Vacuum
+                </a>
+            </p>
+            <p style="font-size: 0.9rem; color: var(--dark); margin-top: 1rem;">
+                How to: Click the bookmarklet on any webpage to find and download its fonts directly.
+            </p>
+        </div>
         <div id="error" class="error" role="alert" aria-live="polite"></div>
         <div id="results" role="region" aria-label="Font analysis results"></div>
     </main>