diff options
author | elioat <elioat@tilde.institute> | 2025-03-15 21:44:31 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-03-15 21:44:31 -0400 |
commit | 777ea6a90d2484131c4ce31ffe49cb8cd1375740 (patch) | |
tree | 6b9e0cdf2bf83061748e26e1fd4c07c85bcde76b | |
parent | 08bc2eccd539f1bd30d9807e76de79fd30b389c0 (diff) | |
download | tour-777ea6a90d2484131c4ce31ffe49cb8cd1375740.tar.gz |
*
-rw-r--r-- | html/immoral/app.js | 6 | ||||
-rw-r--r-- | html/web-font-vacuum/app.js | 730 | ||||
-rw-r--r-- | html/web-font-vacuum/index.html | 167 |
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> |