about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-03-15 21:44:31 -0400
committerelioat <elioat@tilde.institute>2025-03-15 21:44:31 -0400
commit777ea6a90d2484131c4ce31ffe49cb8cd1375740 (patch)
tree6b9e0cdf2bf83061748e26e1fd4c07c85bcde76b
parent08bc2eccd539f1bd30d9807e76de79fd30b389c0 (diff)
downloadtour-777ea6a90d2484131c4ce31ffe49cb8cd1375740.tar.gz
*
-rw-r--r--html/immoral/app.js6
-rw-r--r--html/web-font-vacuum/app.js730
-rw-r--r--html/web-font-vacuum/index.html167
3 files changed, 3 insertions, 900 deletions
diff --git a/html/immoral/app.js b/html/immoral/app.js
index f90a6f3..1c3bb16 100644
--- a/html/immoral/app.js
+++ b/html/immoral/app.js
@@ -143,7 +143,7 @@ function getFontMimeType(url) {
 }
 
 /**
- * Sets up event listeners and initializes the app.
+ * Event listeners and initialization.
  * 
  * 1. User enters URL and clicks Analyze
  * 2. Fetch the HTML through a CORS proxy
@@ -593,13 +593,13 @@ document.addEventListener('DOMContentLoaded', () => {
             const previewLabel = document.createElement('div');
             previewLabel.style.fontWeight = 'bold';
             previewLabel.style.marginBottom = '0.5rem';
-            previewLabel.textContent = 'Preview:';
+            previewLabel.textContent = 'Preview';
             previewContainer.appendChild(previewLabel);
 
             const preview = document.createElement('div');
             preview.style.marginBottom = '1rem';
             preview.id = `preview-${fontData.filename}`;
-            preview.textContent = 'The quick brown fox jumps over the lazy dog 0123456789';
+            preview.innerHTML = 'Society for me my misery<br>Since Gift of Thee --';
             previewContainer.appendChild(preview);
 
             // Add CSS Rule section
diff --git a/html/web-font-vacuum/app.js b/html/web-font-vacuum/app.js
deleted file mode 100644
index f90a6f3..0000000
--- a/html/web-font-vacuum/app.js
+++ /dev/null
@@ -1,730 +0,0 @@
-/**
- * An Immoral Web Font Vacuum
- * A tool to find and extract web fonts from any website
- * 
- * We sort of set up a pipeline where each step processes the data and passes it to the next:
- * 1. URL Input > 2. Fetch HTML > 3. Parse & Extract > 4. Process CSS > 5. Display Results
- * 
- */
-
-/**
- * Proxy List
- * Proxies are buggy, and temperamental...so why not use a whole lot of them!
- * List of CORS proxies we can try if the main one fails.
- * Keeps track of the last working proxy to optimize future requests.
- * @type {Array<{name: string, url: string, urlFormatter: (url: string) => string}>}
- */
-const CORS_PROXIES = [
-    {
-        name: 'allorigins',
-        url: 'https://api.allorigins.win/raw?url=',
-        urlFormatter: url => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`
-    },
-    {
-        name: 'corsproxy.io',
-        url: 'https://corsproxy.io/?',
-        urlFormatter: url => `https://corsproxy.io/?${encodeURIComponent(url)}`
-    },
-    {
-        name: 'cors.sh',
-        url: 'https://cors.sh/',
-        urlFormatter: url => `https://cors.sh/${url}`
-    },
-    {
-        name: 'corsanywhere',
-        url: 'https://cors-anywhere.herokuapp.com/', // FIXME: pretty certain this one doesn't work without human intervention
-        urlFormatter: url => `https://cors-anywhere.herokuapp.com/${url}`
-    },
-    {
-        name: 'thingproxy',
-        url: 'https://thingproxy.freeboard.io/fetch/',
-        urlFormatter: url => `https://thingproxy.freeboard.io/fetch/${url}`
-    }
-];
-
-// Keep track of which proxy worked last
-let lastWorkingProxyIndex = 0;
-let proxyFailureCount = 0;
-const MAX_PROXY_FAILURES = 3;
-
-async function fetchWithProxies(url, attempt = 0, isBinary = false) {
-    // Start with the last working proxy
-    const startIndex = lastWorkingProxyIndex;
-    
-    for (let i = 0; i < CORS_PROXIES.length; i++) {
-        // Calculate the current proxy index, wrapping around if necessary
-        const proxyIndex = (startIndex + i) % CORS_PROXIES.length;
-        const proxy = CORS_PROXIES[proxyIndex];
-        
-        try {
-            console.log(`Trying proxy: ${proxy.name} for URL: ${url}`);
-            
-            const fetchOptions = {
-                headers: {
-                    'Accept': isBinary ? '*/*' : 'text/html,application/xhtml+xml,text/css',
-                    'Origin': window.location.origin
-                },
-                mode: 'cors'
-            };
-            
-            const response = await fetch(proxy.urlFormatter(url), fetchOptions);
-            
-            if (response.ok) {
-                lastWorkingProxyIndex = proxyIndex;
-                proxyFailureCount = 0;
-                return response;
-            }
-        } catch (error) {
-            console.log(`Proxy ${proxy.name} failed:`, error);
-            proxyFailureCount++;
-            
-            // If we've had too many failures, wait a bit before continuing
-            if (proxyFailureCount >= MAX_PROXY_FAILURES) {
-                await new Promise(resolve => setTimeout(resolve, 1000));
-                proxyFailureCount = 0;
-            }
-        }
-    }
-    
-    throw new Error('All proxies failed to fetch the resource');
-}
-
-async function downloadFont(url, filename) {
-    try {
-        console.log('Downloading font from:', url);
-        const response = await fetchWithProxies(url, 0, true);
-        
-        const arrayBuffer = await response.arrayBuffer();
-        if (arrayBuffer.byteLength === 0) {
-            throw new Error('Received empty font file');
-        }
-        
-        // Convert ArrayBuffer to Blob with proper MIME type
-        const blob = new Blob([arrayBuffer], { type: getFontMimeType(url) });
-        
-        // Temporary link to trigger the download
-        const objectUrl = URL.createObjectURL(blob);
-        const link = document.createElement('a');
-        link.href = objectUrl;
-        link.download = filename;
-        
-        document.body.appendChild(link);
-        link.click();
-        
-        // Small delay to ensure download starts before cleanup
-        setTimeout(() => {
-            document.body.removeChild(link);
-            URL.revokeObjectURL(objectUrl);
-        }, 100);
-        
-        return true;
-    } catch (error) {
-        console.error('Error downloading font:', error);
-        alert(`Error downloading font: ${error.message}`);
-        return false;
-    }
-}
-
-// Assume the MIME type based on the file's extension
-function getFontMimeType(url) {
-    const extension = url.split('.').pop().toLowerCase().split('?')[0];
-    switch (extension) {
-        case 'woff':
-            return 'font/woff';
-        case 'woff2':
-            return 'font/woff2';
-        case 'ttf':
-            return 'font/ttf';
-        case 'otf':
-            return 'font/otf';
-        default:
-            return 'application/octet-stream';
-    }
-}
-
-/**
- * Sets up event listeners and initializes the app.
- * 
- * 1. User enters URL and clicks Analyze
- * 2. Fetch the HTML through a CORS proxy
- * 3. Parse HTML to find:
- *    - Direct font links
- *    - Stylesheet links
- *    - Inline styles
- * 4. Process each CSS source to find @font-face rules
- * 5. Display results with preview/download options
- */
-document.addEventListener('DOMContentLoaded', () => {
-    const urlInput = document.getElementById('urlInput');
-    const analyzeBtn = document.getElementById('analyzeBtn');
-    const resultsDiv = document.getElementById('results');
-    const errorDiv = document.getElementById('error');
-    
-    /**
-     * Two different methods to load the font:
-     * - Using a data URL
-     * - Fallback: Using a blob URL if data URL fails
-     * 
-     * @param {string} url - The URL of the font file to preview
-     * @param {string} fontFamily - The font-family name to use
-     * @returns {Promise<boolean>} - Whether the font was successfully loaded
-     */
-    async function previewFont(url, fontFamily) {
-        try {
-            console.log('Loading font from:', url);
-            const response = await fetchWithProxies(url, 0, true);
-            
-            const arrayBuffer = await response.arrayBuffer();
-            if (arrayBuffer.byteLength === 0) {
-                throw new Error('Received empty font file');
-            }
-
-            // Convert ArrayBuffer to Blob
-            const blob = new Blob([arrayBuffer], { type: getFontMimeType(url) });
-            
-            // Try using a data URL instead of a blob URL
-            const reader = new FileReader();
-            
-            return new Promise((resolve, reject) => {
-                reader.onload = async function() {
-                    try {
-                        const dataUrl = reader.result;
-                        
-                        const fontFace = new FontFace(fontFamily, `url(${dataUrl})`, {
-                            style: 'normal',
-                            weight: '400',
-                            display: 'swap'
-                        });
-                        
-                        const loadedFont = await fontFace.load();
-                        document.fonts.add(loadedFont);
-                        
-                        resolve(true);
-                    } catch (loadError) {
-                        console.error('Font load error:', loadError);
-                        
-                        // Try fallback method with blob URL
-                        try {
-                            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);
-                            resolve(true);
-                        } catch (fallbackError) {
-                            console.error('Font fallback load error:', fallbackError);
-                            reject(fallbackError);
-                        }
-                    }
-                };
-                
-                reader.onerror = function() {
-                    reject(new Error('Failed to read font file'));
-                };
-                
-                reader.readAsDataURL(blob);
-            });
-        } catch (error) {
-            console.error('Error loading font for preview:', error);
-            return false;
-        }
-    }
-    
-    /**
-     * Click handler for the Analyze button.
-     * 
-     * 1. Validate input URL
-     * 2. Fetch and parse webpage
-     * 3. Look for fonts in:
-     *    - Direct links (preload, regular links)
-     *    - CSS files (external and inline)
-     *    - Common font directories
-     * 4. Display results
-     */
-    analyzeBtn.addEventListener('click', async () => {
-        const url = urlInput.value.trim();
-        if (!url) {
-            showError('Please enter a valid URL');
-            return;
-        }
-
-        try {
-            // Clear previous results and errors
-            resultsDiv.innerHTML = '';
-            errorDiv.style.display = 'none';
-
-            // Show loading state
-            resultsDiv.innerHTML = '<p>Analyzing webpage. Sometimes this takes a while.</p>';
-
-            // Fetch the target webpage through the proxy system
-            const response = await fetchWithProxies(url);
-            const html = await response.text();
-
-            // Create a temporary DOM to parse the HTML
-            const parser = new DOMParser();
-            const doc = parser.parseFromString(html, 'text/html');
-
-            // Find all potential font sources
-            const fontUrls = new Set();
-            
-            // Get the base URL (domain)
-            const baseUrlObj = new URL(url);
-            const domain = baseUrlObj.origin;
-            
-            // Brute force common font paths, for scenarios where the font is not found in the css
-            const commonFontPaths = [
-                '/assets/fonts/',
-                '/fonts/',
-                '/assets/',
-                '/css/fonts/',
-                '/wp-content/themes/*/fonts/',
-                '/static/fonts/'
-            ];
-            
-            console.log('Checking common font paths for:', domain);
-
-            // Check for direct font links in the HTML
-            extractDirectFontLinks(doc, url, fontUrls);
-
-            // Check stylesheet links
-            const cssPromises = Array.from(doc.querySelectorAll('link[rel="stylesheet"]')).map(link => {
-                const href = link.getAttribute('href');
-                if (!href) return Promise.resolve();
-                
-                const cssUrl = new URL(href, url).href;
-                console.log('Processing CSS URL:', cssUrl);
-                return processCssUrl(cssUrl, fontUrls, url);
-            });
-
-            // Wait for all CSS files to be processed
-            await Promise.all(cssPromises.filter(Boolean));
-
-            // Check style tags
-            doc.querySelectorAll('style').forEach(style => {
-                extractFontUrlsFromCss(style.textContent, url, fontUrls);
-            });
-
-            // If no fonts found, try checking common paths
-            if (fontUrls.size === 0) {
-                console.log('No fonts found in CSS, checking common paths...');
-                
-                const commonPathPromises = commonFontPaths.map(async path => {
-                    try {
-                        // Try to access the directory
-                        const directoryUrl = new URL(path, domain).href;
-                        console.log('Checking directory:', directoryUrl);
-                        
-                        // We can't list directory contents directly, but we can try common font names
-                        const commonFontNames = ['font', 'fonts', 'webfont', 'custom-font', 'main'];
-                        const fontExtensions = ['woff', 'woff2', 'ttf', 'otf'];
-                        
-                        for (const name of commonFontNames) {
-                            for (const ext of fontExtensions) {
-                                const fontUrl = `${directoryUrl}${name}.${ext}`;
-                                try {
-                                    const fontResponse = await fetchWithProxies(fontUrl, 0, true);
-                                    if (fontResponse.ok) {
-                                        console.log('Found font at common path:', fontUrl);
-                                        fontUrls.add({
-                                            url: fontUrl,
-                                            family: 'Unknown Font',
-                                            filename: `${name}.${ext}`
-                                        });
-                                    }
-                                } catch (error) {
-                                    // Ignore errors for common path checks
-                                }
-                            }
-                        }
-                    } catch (error) {
-                        // Ignore errors for common path checks
-                    }
-                });
-                
-                await Promise.all(commonPathPromises);
-            }
-
-            // Display results
-            if (fontUrls.size === 0) {
-                resultsDiv.innerHTML = '<p>No web fonts (WOFF/TTF/WOFF2/OTF) were found on this page.</p>';
-            } else {
-                displayFontUrls(fontUrls);
-            }
-
-        } catch (error) {
-            showError(`Error analyzing the webpage: ${error.message}`);
-            console.error('Full error:', error);
-        }
-    });
-
-    /**
-     * Processes a the URL of a CSS file to extract font information.
-     * 
-     * @param {string} cssUrl - The URL of the CSS file to process
-     * @param {Set} fontUrls - Set to store found font URLs
-     * @param {string} baseUrl - Base URL for resolving relative paths
-     */
-    async function processCssUrl(cssUrl, fontUrls, baseUrl) {
-        try {
-            console.log('Fetching CSS from:', cssUrl);
-            const response = await fetchWithProxies(cssUrl);
-            const css = await response.text();
-            
-            // Extract font URLs from the CSS content
-            extractFontUrlsFromCss(css, cssUrl, fontUrls);
-        } catch (error) {
-            console.error(`Error processing CSS from ${cssUrl}:`, error);
-        }
-    }
-
-    /**
-     * Extract font URLs from CSS content
-     * 
-     * @param {string} css - The CSS content to process
-     * @param {string} cssUrl - The URL of the CSS file (for resolving relative paths)
-     * @param {Set} fontUrls - Set to store found font URLs
-     */
-    function extractFontUrlsFromCss(css, cssUrl, fontUrls) {
-        // Get the base URL for resolving relative paths
-        const baseUrl = new URL(cssUrl).origin;
-        
-        // Match @font-face blocks
-        const fontFaceRegex = /@font-face\s*{[^}]*}/g;
-        const urlRegex = /url\(['"]?([^'"\)]+)['"]?\)/g;
-        const fontFamilyRegex = /font-family\s*:\s*['"]?([^'";]*)['"]?/;
-        
-        let fontFaceMatch;
-        while ((fontFaceMatch = fontFaceRegex.exec(css)) !== null) {
-            const fontFaceBlock = fontFaceMatch[0];
-            
-            // Extract font-family name
-            const familyMatch = fontFaceBlock.match(fontFamilyRegex);
-            const fontFamily = familyMatch ? familyMatch[1].trim() : 'Unknown Font';
-            
-            // Clean up the CSS rule for display
-            const cleanRule = fontFaceBlock.replace(/\s+/g, ' ').trim();
-            
-            // Extract all URLs from this @font-face block
-            let urlMatch;
-            while ((urlMatch = urlRegex.exec(fontFaceBlock)) !== null) {
-                try {
-                    let fontUrl = urlMatch[1].trim();
-                    
-                    // Skip data: URLs
-                    if (fontUrl.startsWith('data:')) {
-                        console.log('Skipping data: URL font');
-                        continue;
-                    }
-                    
-                    // Only process known font file types
-                    if (!fontUrl.match(/\.(woff2?|ttf|otf|eot)(\?.*)?$/i)) {
-                        continue;
-                    }
-                    
-                    // Resolve relative URLs
-                    if (fontUrl.startsWith('//')) {
-                        fontUrl = 'https:' + fontUrl;
-                    } else if (!fontUrl.startsWith('http')) {
-                        fontUrl = new URL(fontUrl, cssUrl).href;
-                    }
-                    
-                    const filename = fontUrl.split('/').pop().split('?')[0];
-                    console.log(`Found font in CSS: ${fontUrl} (${fontFamily})`);
-                    
-                    fontUrls.add({
-                        url: fontUrl,
-                        family: fontFamily,
-                        filename: filename,
-                        cssRule: cleanRule
-                    });
-                } catch (error) {
-                    console.error('Error processing font URL:', urlMatch[1], error);
-                }
-            }
-        }
-    }
-
-    /**
-     * Find direct font links in HTML.
-     * Checks two types of links:
-     * 1. Preload links with as="font"
-     * 2. Regular <a> tags pointing to font files
-     * 
-     * @param {Document} doc - Parsed HTML document
-     * @param {string} baseUrl - Base URL for resolving relative paths
-     * @param {Set} fontUrls - Set to store found font URLs
-     */
-    function extractDirectFontLinks(doc, baseUrl, fontUrls) {
-        // Check for preload links
-        doc.querySelectorAll('link[rel="preload"][as="font"], link[rel="stylesheet"]').forEach(link => {
-            const href = link.getAttribute('href');
-            if (href && href.match(/\.(woff|woff2|ttf|otf|css)(\?.*)?$/i)) {
-                try {
-                    const absoluteUrl = new URL(href, baseUrl).href;
-                    if (href.match(/\.css(\?.*)?$/i)) {
-                        processCssUrl(absoluteUrl, fontUrls, baseUrl);
-                    } else {
-                        const filename = href.split('/').pop().split('?')[0];
-                        let fontFamilyName = 'Unknown Font';
-                        if (link.dataset.fontFamily) {
-                            fontFamilyName = link.dataset.fontFamily;
-                        }
-                        
-                        console.log(`Found preloaded font: ${absoluteUrl} (${fontFamilyName})`);
-                        fontUrls.add({
-                            url: absoluteUrl,
-                            family: fontFamilyName,
-                            filename: filename,
-                            cssRule: `@font-face { font-family: "${fontFamilyName}"; src: url("${absoluteUrl}") format("${getFormatFromFilename(filename)}"); }`
-                        });
-                    }
-                } catch (error) {
-                    console.error('Error resolving font URL:', href, error);
-                }
-            }
-        });
-    }
-
-    /**
-     * Get the format string for a font file based on its filename
-     * @param {string} filename 
-     * @returns {string}
-     */
-    function getFormatFromFilename(filename) {
-        const ext = filename.split('.').pop().toLowerCase();
-        switch (ext) {
-            case 'woff2':
-                return 'woff2';
-            case 'woff':
-                return 'woff';
-            case 'ttf':
-                return 'truetype';
-            case 'otf':
-                return 'opentype';
-            default:
-                return ext;
-        }
-    }
-
-    /**
-     * The V of MVC
-     *  
-     * - Auto-preview for 3 or fewer fonts
-     * - Manual preview toggle for 4+ fonts
-     * - Download buttons
-     * - Font information display
-     * - CSS rule display
-     * - Preview with multiple sizes
-     * 
-     * @param {Array} urls - Array of font data objects to display
-     */
-    async function displayFontUrls(urls) {
-        const resultsDiv = document.getElementById('results');
-        resultsDiv.innerHTML = '';
-
-        // Convert urls to array if it's a Set, or use as is if already array
-        const fontsArray = Array.isArray(urls) ? urls : Array.from(urls);
-        
-        if (fontsArray.length === 0) {
-            resultsDiv.innerHTML = '<p>No fonts found on this webpage.</p>';
-            return;
-        }
-
-        const container = document.createElement('div');
-        container.style.display = 'flex';
-        container.style.flexDirection = 'column';
-        container.style.gap = '2rem';
-        container.style.maxWidth = '800px';
-        container.style.margin = '0 auto';
-
-        const shouldAutoPreview = fontsArray.length < 4;
-
-        for (let fontData of fontsArray) {
-            const fontItem = document.createElement('div');
-            fontItem.style.border = '2px solid var(--dark)';
-            fontItem.style.padding = '1rem';
-            fontItem.style.background = 'var(--beige)';
-            fontItem.style.position = 'relative';
-
-            const accentBar = document.createElement('div');
-            accentBar.style.position = 'absolute';
-            accentBar.style.top = '0';
-            accentBar.style.left = '0';
-            accentBar.style.right = '0';
-            accentBar.style.height = '4px';
-            accentBar.style.background = 'var(--accent)';
-            fontItem.appendChild(accentBar);
-
-            const fontInfo = document.createElement('div');
-            fontInfo.style.marginTop = '0.5rem';
-            
-            const fontName = document.createElement('h3');
-            fontName.style.margin = '0';
-            fontName.style.textTransform = 'uppercase';
-            fontName.textContent = fontData.family;
-            fontInfo.appendChild(fontName);
-
-            const fontType = document.createElement('div');
-            fontType.style.display = 'inline-block';
-            fontType.style.background = 'var(--dark)';
-            fontType.style.color = 'var(--beige)';
-            fontType.style.padding = '0.2rem 0.5rem';
-            fontType.style.marginTop = '0.5rem';
-            fontType.style.fontSize = '0.8rem';
-            fontType.textContent = fontData.filename.split('.').pop().toUpperCase();
-            fontInfo.appendChild(fontType);
-
-            const previewContainer = document.createElement('div');
-            previewContainer.style.marginTop = '1rem';
-            previewContainer.style.padding = '1rem';
-            previewContainer.style.border = '1px dashed var(--dark)';
-            
-            // Hide preview container initially if not auto-previewing
-            if (!shouldAutoPreview) {
-                previewContainer.style.display = 'none';
-            }
-            
-            const previewLabel = document.createElement('div');
-            previewLabel.style.fontWeight = 'bold';
-            previewLabel.style.marginBottom = '0.5rem';
-            previewLabel.textContent = 'Preview:';
-            previewContainer.appendChild(previewLabel);
-
-            const preview = document.createElement('div');
-            preview.style.marginBottom = '1rem';
-            preview.id = `preview-${fontData.filename}`;
-            preview.textContent = 'The quick brown fox jumps over the lazy dog 0123456789';
-            previewContainer.appendChild(preview);
-
-            // Add CSS Rule section
-            if (fontData.cssRule) {
-                const cssContainer = document.createElement('div');
-                cssContainer.style.marginTop = '1rem';
-                cssContainer.style.marginBottom = '1rem';
-                cssContainer.style.padding = '1rem';
-                cssContainer.style.background = 'var(--dark)';
-                cssContainer.style.color = 'var(--beige)';
-                cssContainer.style.borderRadius = '4px';
-                cssContainer.style.position = 'relative';
-                
-                const cssLabel = document.createElement('div');
-                cssLabel.style.position = 'absolute';
-                cssLabel.style.top = '-10px';
-                cssLabel.style.left = '10px';
-                cssLabel.style.background = 'var(--accent)';
-                cssLabel.style.color = 'var(--dark)';
-                cssLabel.style.padding = '0 0.5rem';
-                cssLabel.style.fontSize = '0.8rem';
-                cssLabel.style.fontWeight = 'bold';
-                cssLabel.textContent = '@font-face';
-                cssContainer.appendChild(cssLabel);
-                
-                const cssContent = document.createElement('pre');
-                cssContent.style.margin = '0';
-                cssContent.style.fontFamily = 'monospace';
-                cssContent.style.fontSize = '0.9rem';
-                cssContent.style.whiteSpace = 'pre-wrap';
-                cssContent.style.wordBreak = 'break-all';
-                
-                // Format the CSS rule nicely
-                const formattedCss = fontData.cssRule
-                    .replace(/{/, ' {\n    ')
-                    .replace(/;/g, ';\n    ')
-                    .replace(/}/g, '\n}')
-                    .replace(/\s+}/g, '}')
-                    .trim();
-                
-                cssContent.textContent = formattedCss;
-                cssContainer.appendChild(cssContent);
-                
-                fontInfo.appendChild(cssContainer);
-            }
-
-            const sizeVariations = document.createElement('div');
-            sizeVariations.style.borderTop = '1px solid var(--dark)';
-            sizeVariations.style.paddingTop = '0.5rem';
-            sizeVariations.style.marginTop = '0.5rem';
-            
-            [12, 18, 24].forEach(size => {
-                const sizePreview = document.createElement('div');
-                sizePreview.style.fontSize = `${size}px`;
-                sizePreview.textContent = `${size}px - The quick brown fox jumps over the lazy dog 0123456789`;
-                sizeVariations.appendChild(sizePreview);
-            });
-            
-            previewContainer.appendChild(sizeVariations);
-
-            const buttonContainer = document.createElement('div');
-            buttonContainer.style.display = 'flex';
-            buttonContainer.style.gap = '0.5rem';
-            buttonContainer.style.marginTop = '1rem';
-
-            const downloadBtn = document.createElement('button');
-            downloadBtn.textContent = '⬇ Download';
-            downloadBtn.style.flex = '1';
-            downloadBtn.addEventListener('click', () => downloadFont(fontData.url, fontData.filename));
-            buttonContainer.appendChild(downloadBtn);
-
-            if (!shouldAutoPreview) {
-                const previewBtn = document.createElement('button');
-                previewBtn.textContent = '👁 Preview';
-                previewBtn.style.flex = '1';
-                
-                let isPreviewVisible = false;
-                previewBtn.addEventListener('click', async () => {
-                    if (!isPreviewVisible) {
-                        previewContainer.style.display = 'block';
-                        const previewElement = document.getElementById(`preview-${fontData.filename}`);
-                        if (await previewFont(fontData.url, fontData.family)) {
-                            previewElement.style.fontFamily = fontData.family;
-                            sizeVariations.querySelectorAll('div').forEach(div => {
-                                div.style.fontFamily = fontData.family;
-                            });
-                            previewBtn.textContent = '👁 Hide Preview';
-                            isPreviewVisible = true;
-                        } else {
-                            // If preview fails, hide the container again
-                            previewContainer.style.display = 'none';
-                        }
-                    } else {
-                        previewContainer.style.display = 'none';
-                        previewBtn.textContent = '👁 Preview';
-                        isPreviewVisible = false;
-                    }
-                });
-                buttonContainer.appendChild(previewBtn);
-            }
-
-            fontItem.appendChild(fontInfo);
-            fontItem.appendChild(previewContainer);
-            fontItem.appendChild(buttonContainer);
-            container.appendChild(fontItem);
-
-            if (shouldAutoPreview) {
-                // Use setTimeout to ensure the DOM is ready
-                setTimeout(async () => {
-                    const previewElement = document.getElementById(`preview-${fontData.filename}`);
-                    if (await previewFont(fontData.url, fontData.family)) {
-                        previewElement.style.fontFamily = fontData.family;
-                        sizeVariations.querySelectorAll('div').forEach(div => {
-                            div.style.fontFamily = fontData.family;
-                        });
-                    }
-                }, 100);
-            }
-        }
-
-        resultsDiv.appendChild(container);
-    }
-
-    function showError(message) {
-        errorDiv.textContent = message;
-        errorDiv.style.display = 'block';
-    }
-}); 
\ No newline at end of file
diff --git a/html/web-font-vacuum/index.html b/html/web-font-vacuum/index.html
deleted file mode 100644
index b6eee24..0000000
--- a/html/web-font-vacuum/index.html
+++ /dev/null
@@ -1,167 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>An Immoral Web Font Vacuum</title>
-    <meta name="description" content="Enter a URL to find, preview, and download web fonts (WOFF/TTF/WOFF2/OTF) present on the page.">
-    <style>
-        :root {
-            --beige: #f5f2e9;
-            --dark: #111111;
-            --accent: #ff4d00;
-            --grid-line: #ccbea7;
-            --container-bg: #ffffff;
-            --focus-outline: #2563eb;
-        }
-        
-        body {
-            font-family: 'Courier New', monospace;
-            max-width: 900px;
-            margin: 0 auto;
-            padding: 1rem;
-            line-height: 1.5;
-            background: var(--beige);
-            color: var(--dark);
-        }
-        
-        h1, h2 {
-            text-transform: uppercase;
-            letter-spacing: 2px;
-            border-bottom: 3px solid var(--accent);
-            padding-bottom: 0.5rem;
-            font-weight: 900;
-        }
-        
-        .container {
-            background: var(--container-bg);
-            padding: 2rem;
-            border: 3px solid var(--dark);
-            box-shadow: 8px 8px 0 var(--dark);
-            margin-top: 2rem;
-        }
-        
-        .input-group {
-            display: flex;
-            gap: 1rem;
-            margin-bottom: 2rem;
-            border: 2px solid var(--dark);
-            padding: 1rem;
-            background: var(--beige);
-        }
-        
-        .sr-only {
-            position: absolute;
-            width: 1px;
-            height: 1px;
-            padding: 0;
-            margin: -1px;
-            overflow: hidden;
-            clip: rect(0, 0, 0, 0);
-            white-space: nowrap;
-            border: 0;
-        }
-        
-        input[type="url"] {
-            flex: 1;
-            padding: 0.75rem;
-            font-size: 1rem;
-            border: 2px solid var(--dark);
-            background: var(--beige);
-            font-family: 'Courier New', monospace;
-        }
-        
-        input[type="url"]:focus {
-            outline: 3px solid var(--focus-outline);
-            outline-offset: 2px;
-        }
-        
-        button {
-            padding: 0.75rem 1.5rem;
-            font-size: 1rem;
-            background: var(--dark);
-            color: var(--beige);
-            border: 2px solid var(--dark);
-            cursor: pointer;
-            text-transform: uppercase;
-            font-weight: bold;
-            font-family: 'Courier New', monospace;
-            transition: all 0.2s;
-        }
-        
-        button:hover,
-        button:focus-visible {
-            background: var(--accent);
-            transform: translateY(-2px);
-            outline: 3px solid var(--focus-outline);
-            outline-offset: 2px;
-        }
-        
-        button:focus:not(:focus-visible) {
-            outline: none;
-        }
-        
-        .error {
-            color: var(--accent);
-            padding: 1rem;
-            background: rgba(255, 77, 0, 0.1);
-            border: 2px solid var(--accent);
-            margin-top: 1rem;
-            display: none;
-            font-weight: bold;
-            role: "alert";
-        }
-        
-        #results {
-            margin-top: 1rem;
-        }
-
-        /* Skip link for keyboard users */
-        .skip-link {
-            position: absolute;
-            top: -40px;
-            left: 0;
-            background: var(--dark);
-            color: var(--beige);
-            padding: 8px;
-            z-index: 100;
-            transition: top 0.2s;
-        }
-
-        .skip-link:focus {
-            top: 0;
-            outline: 3px solid var(--focus-outline);
-        }
-    </style>
-</head>
-<body>
-    <a href="#main-content" class="skip-link">Skip to main content</a>
-    <div class="container" id="main-content">
-        <h1>Immoral 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="error" class="error" role="alert" aria-live="polite"></div>
-        <div id="results" role="region" aria-label="Font analysis results"></div>
-    </div>
-    <script src="app.js"></script>
-</body>
-</html>