my website at ewancroft.uk
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: remove dynamic OG image generation and simplify meta tags

- Deleted custom OG image generation system (fonts, satori, resvg, etc.)
- Removed API routes for dynamic OG images (main, blog, now, site-meta, test, warmup, cron)
- Removed utils (textExtractor, OG server helpers, font assets)
- Updated <meta> tags in pages (home, blog, now, site meta) to reference static `/og/*.png` images
- Simplified PostHead excerpt logic (manual HTML strip + truncation)
- Cleaned up Atom/RSS feeds to use static OG images
- Removed workflow for OG cache warmup

+31 -1576
-23
.github/workflows/warmup-og.yml
··· 1 - name: Warmup OG Cache 2 - 3 - on: 4 - deployment_status: 5 - 6 - jobs: 7 - warmup: 8 - runs-on: ubuntu-latest 9 - if: github.event.deployment_status.state == 'success' 10 - steps: 11 - - name: Wait for deployment 12 - run: sleep 10 13 - 14 - - name: Warmup OG cache 15 - run: | 16 - echo "Warming up OG cache..." 17 - curl -f https://ewancroft.uk/api/og/warmup || echo "⚠️ Warmup failed but continuing" 18 - 19 - - name: Test OG generation 20 - run: | 21 - echo "Testing OG image generation..." 22 - curl -f -o /dev/null https://ewancroft.uk/api/og/main.png || echo "⚠️ OG test failed" 23 - echo "✅ Warmup complete"
+17 -6
src/lib/components/post/PostHead.svelte
··· 3 3 const { page } = getStores(); 4 4 import type { Post } from "$components/shared"; 5 5 import { env } from "$env/dynamic/public"; 6 - import { extractTextFromHTML } from "$utils/textExtractor"; 7 6 8 7 export let post: Post | undefined; 9 8 10 9 // Generate a clean excerpt from HTML content for meta tags 11 - $: metaExcerpt = post?.content 12 - ? extractTextFromHTML(post.content, 50) // Shorter excerpt for meta tags 13 - : post?.excerpt || ''; 10 + $: metaExcerpt = post?.content 11 + ? (() => { 12 + const clean = post.content 13 + .replace(/<[^>]+>/g, '') // Remove HTML tags 14 + .replace(/\s+/g, ' ') // Collapse whitespace 15 + .trim(); 16 + 17 + if (clean.length <= 160) return clean; 18 + 19 + // Cut at nearest word boundary before 160 chars 20 + const truncated = clean.slice(0, 160); 21 + const lastSpace = truncated.lastIndexOf(' '); 22 + return truncated.slice(0, lastSpace) + '…'; 23 + })() 24 + : "Read this blog post on Ewan's Corner."; 14 25 </script> 15 26 16 27 <svelte:head> ··· 33 44 <meta property="og:site_name" content="Blog - Ewan's Corner" /> 34 45 <meta 35 46 property="og:image" 36 - content={`${$page.url.origin}/api/og/blog/${post.rkey}.png`} 47 + content={`${$page.url.origin}/og/blog.png`} 37 48 /> 38 49 <meta property="og:image:width" content="1200" /> 39 50 <meta property="og:image:height" content="630" /> ··· 58 69 <meta name="twitter:description" content={metaExcerpt} /> 59 70 <meta 60 71 name="twitter:image" 61 - content={`${$page.url.origin}/api/og/blog/${post.rkey}.png`} 72 + content={`${$page.url.origin}/og/blog.png`} 62 73 /> 63 74 {:else} 64 75 <title>Post Not Found - Blog - Ewan's Corner</title>
-83
src/lib/server/og/fonts.ts
··· 1 - import { dev } from '$app/environment'; 2 - 3 - const FONT_BASE_URL = '/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF'; 4 - const FONT_FILES = { 5 - regular: 'RecursiveSansCslSt-Regular.ttf', 6 - bold: 'RecursiveSansCslSt-Bold.ttf', 7 - italic: 'RecursiveSansCslSt-Italic.ttf', 8 - }; 9 - 10 - // Global cache that persists across requests 11 - let fontCache: { regular?: Buffer; bold?: Buffer; italic?: Buffer } = {}; 12 - let fontLoadPromise: Promise<{ regular: Buffer; bold: Buffer; italic: Buffer }> | null = null; 13 - 14 - async function loadSingleFont(fileName: string, baseUrl?: string): Promise<Buffer> { 15 - const timeout = 3000; // 3 second timeout per font 16 - 17 - if (dev) { 18 - const fs = await import('fs/promises'); 19 - const path = await import('path'); 20 - const fontPath = path.resolve(`static${FONT_BASE_URL}/${fileName}`); 21 - return await fs.readFile(fontPath); 22 - } else { 23 - const fontUrl = `${baseUrl || ''}${FONT_BASE_URL}/${fileName}`; 24 - 25 - const controller = new AbortController(); 26 - const timeoutId = setTimeout(() => controller.abort(), timeout); 27 - 28 - try { 29 - const res = await fetch(fontUrl, { signal: controller.signal }); 30 - clearTimeout(timeoutId); 31 - 32 - if (!res.ok) throw new Error(`Failed to fetch font: ${res.status} ${res.statusText}`); 33 - const ab = await res.arrayBuffer(); 34 - return Buffer.from(ab); 35 - } catch (error) { 36 - clearTimeout(timeoutId); 37 - if (error instanceof Error && error.name === 'AbortError') { 38 - throw new Error(`Font fetch timeout: ${fileName}`); 39 - } 40 - throw error; 41 - } 42 - } 43 - } 44 - 45 - export async function loadFonts(baseUrl?: string) { 46 - // Return cached fonts if available 47 - if (fontCache.regular && fontCache.bold && fontCache.italic) { 48 - return fontCache as { regular: Buffer; bold: Buffer; italic: Buffer }; 49 - } 50 - 51 - // If already loading, wait for that promise 52 - if (fontLoadPromise) { 53 - return fontLoadPromise; 54 - } 55 - 56 - // Start loading fonts in parallel 57 - fontLoadPromise = (async () => { 58 - try { 59 - const [regular, bold, italic] = await Promise.all([ 60 - loadSingleFont(FONT_FILES.regular, baseUrl), 61 - loadSingleFont(FONT_FILES.bold, baseUrl), 62 - loadSingleFont(FONT_FILES.italic, baseUrl), 63 - ]); 64 - 65 - // Cache the fonts 66 - fontCache = { regular, bold, italic }; 67 - 68 - return { regular, bold, italic }; 69 - } catch (error) { 70 - // Clear the promise on error so we can retry 71 - fontLoadPromise = null; 72 - throw error; 73 - } 74 - })(); 75 - 76 - return fontLoadPromise; 77 - } 78 - 79 - // Clear cache (useful for testing) 80 - export function clearFontCache() { 81 - fontCache = {}; 82 - fontLoadPromise = null; 83 - }
-458
src/lib/server/og/generateOgImage.ts
··· 1 - import satori from 'satori'; 2 - import { Resvg } from '@resvg/resvg-js'; 3 - import { loadFonts } from './fonts'; 4 - import { resolveImageSrc } from './images'; 5 - import { 6 - calculateTitleFontSize, 7 - calculateSubtitleFontSize, 8 - estimateContentHeight, 9 - estimateTextWidth, 10 - } from './text'; 11 - import type { OgImageOptions } from './types'; 12 - 13 - // In-memory cache for generated OG images 14 - const imageCache = new Map<string, { data: Uint8Array; timestamp: number }>(); 15 - const CACHE_TTL = 1000 * 60 * 60; // 1 hour cache 16 - 17 - function getCacheKey(options: OgImageOptions): string { 18 - return JSON.stringify({ 19 - title: options.title, 20 - subtitle: options.subtitle, 21 - metaLine: options.metaLine, 22 - author: options.author, 23 - extraMeta: options.extraMeta, 24 - }); 25 - } 26 - 27 - export async function generateOgImage(options: OgImageOptions, baseUrl?: string): Promise<Uint8Array> { 28 - const startTime = Date.now(); 29 - 30 - // Check cache first 31 - const cacheKey = getCacheKey(options); 32 - const cached = imageCache.get(cacheKey); 33 - 34 - if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) { 35 - console.log('Returning cached OG image'); 36 - return cached.data; 37 - } 38 - 39 - // Set overall timeout for the entire generation process 40 - const timeoutDuration = 8000; // 8 seconds total timeout 41 - const timeoutPromise = new Promise<never>((_, reject) => { 42 - setTimeout(() => reject(new Error('OG image generation timeout')), timeoutDuration); 43 - }); 44 - 45 - try { 46 - const result = await Promise.race([ 47 - generateImageInternal(options, baseUrl), 48 - timeoutPromise 49 - ]); 50 - 51 - const elapsedTime = Date.now() - startTime; 52 - console.log(`OG image generated in ${elapsedTime}ms`); 53 - 54 - // Cache the result 55 - imageCache.set(cacheKey, { 56 - data: result, 57 - timestamp: Date.now() 58 - }); 59 - 60 - // Clean up old cache entries (keep cache size manageable) 61 - if (imageCache.size > 100) { 62 - const now = Date.now(); 63 - for (const [key, value] of imageCache.entries()) { 64 - if (now - value.timestamp > CACHE_TTL) { 65 - imageCache.delete(key); 66 - } 67 - } 68 - } 69 - 70 - return result; 71 - } catch (error) { 72 - const elapsedTime = Date.now() - startTime; 73 - console.error(`OG image generation failed after ${elapsedTime}ms:`, error); 74 - throw error; 75 - } 76 - } 77 - 78 - async function generateImageInternal(options: OgImageOptions, baseUrl?: string): Promise<Uint8Array> { 79 - // Load fonts and images in parallel with timeouts 80 - const [fonts, avatarDataUrl, bannerDataUrl] = await Promise.all([ 81 - loadFonts(baseUrl).catch(err => { 82 - console.error('Font loading failed:', err); 83 - throw new Error('Failed to load fonts'); 84 - }), 85 - resolveImageSrc(options.author?.avatar || null, 'profile.svg', baseUrl).catch(err => { 86 - console.warn('Avatar loading failed, using fallback:', err); 87 - return `data:image/svg+xml;base64,${Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><circle cx="32" cy="32" r="32" fill="#2d4839"/></svg>').toString('base64')}`; 88 - }), 89 - resolveImageSrc(options.banner || null, 'banner.svg', baseUrl).catch(err => { 90 - console.warn('Banner loading failed, using fallback:', err); 91 - return `data:image/svg+xml;base64,${Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630"><rect width="1200" height="630" fill="#121c17"/></svg>').toString('base64')}`; 92 - }) 93 - ]); 94 - 95 - const titleFontSize = calculateTitleFontSize(options.title); 96 - const subtitleFontSize = options.subtitle ? calculateSubtitleFontSize(options.subtitle, titleFontSize) : 0; 97 - 98 - const contentEstimate = estimateContentHeight(options); 99 - const availableHeight = 630 - 96; 100 - let titleMarginBottom = 32; 101 - let subtitleMarginBottom = 24; 102 - let metaMarginBottom = 24; 103 - 104 - if (contentEstimate.totalContentHeight > availableHeight) { 105 - const compressionRatio = availableHeight / contentEstimate.totalContentHeight; 106 - titleMarginBottom = Math.max(8, Math.floor(titleMarginBottom * compressionRatio)); 107 - subtitleMarginBottom = Math.max(6, Math.floor(subtitleMarginBottom * compressionRatio)); 108 - metaMarginBottom = Math.max(8, Math.floor(metaMarginBottom * compressionRatio)); 109 - } 110 - 111 - let dateString: string | null = null; 112 - if (options.extraMeta && options.extraMeta.length > 0) { 113 - const raw = options.extraMeta[0]; 114 - let dateObj: Date | null = null; 115 - 116 - if (typeof raw === 'string' || raw instanceof Date) { 117 - dateObj = new Date(raw); 118 - } 119 - 120 - if (dateObj && !isNaN(dateObj.getTime())) { 121 - dateString = new Intl.DateTimeFormat('en-GB', { 122 - day: '2-digit', 123 - month: 'short', 124 - year: 'numeric', 125 - }).format(dateObj); 126 - } else { 127 - dateString = String(raw); 128 - } 129 - } 130 - 131 - const children: any[] = []; 132 - children.push({ 133 - type: 'h1', 134 - props: { 135 - style: { 136 - fontSize: `${titleFontSize}px`, 137 - fontWeight: 700, 138 - margin: `0 0 ${titleMarginBottom}px 0`, 139 - textAlign: 'center', 140 - lineHeight: 1.25, 141 - maxWidth: '1000px', 142 - paddingLeft: '48px', 143 - paddingRight: '48px', 144 - }, 145 - children: options.title, 146 - }, 147 - }); 148 - 149 - if (options.subtitle) { 150 - children.push({ 151 - type: 'div', 152 - props: { 153 - style: { 154 - fontSize: `${subtitleFontSize}px`, 155 - fontWeight: 400, 156 - margin: `0 0 ${subtitleMarginBottom}px 0`, 157 - textAlign: 'center', 158 - opacity: 0.8, 159 - maxWidth: '900px', 160 - lineHeight: 1.3, 161 - paddingLeft: '48px', 162 - paddingRight: '48px', 163 - }, 164 - children: options.subtitle, 165 - }, 166 - }); 167 - } 168 - 169 - if (options.metaLine) { 170 - children.push({ 171 - type: 'div', 172 - props: { 173 - style: { 174 - fontSize: '24px', 175 - opacity: 0.75, 176 - margin: `0 0 ${metaMarginBottom}px 0`, 177 - display: 'flex', 178 - justifyContent: 'center', 179 - }, 180 - children: options.metaLine, 181 - }, 182 - }); 183 - } 184 - 185 - if (options.customChildren) children.push(options.customChildren); 186 - 187 - if (!options.author && !dateString && !options.customChildren) { 188 - children.push({ 189 - type: 'div', 190 - props: { 191 - style: { 192 - width: '120px', 193 - height: '4px', 194 - background: '#2d4839', 195 - borderRadius: '2px', 196 - margin: '0 auto', 197 - }, 198 - }, 199 - }); 200 - } 201 - 202 - const safeAuthor = options.author ?? { name: 'Anonymous' }; 203 - if (options.author && !options.author.handle && !options.author.did) { 204 - throw new Error('OG image generation error: author is missing both handle and DID.'); 205 - } 206 - 207 - let identifierNode: any = null; 208 - if (safeAuthor.handle) { 209 - identifierNode = { 210 - type: 'span', 211 - props: { 212 - style: { 213 - fontSize: '16px', 214 - color: '#8fd0a0', 215 - fontWeight: 400, 216 - fontStyle: 'italic', 217 - }, 218 - children: '@' + safeAuthor.handle, 219 - }, 220 - }; 221 - } else if (safeAuthor.did) { 222 - const containerWidth = 1200; 223 - const paddingTotal = 48 + 48; 224 - const avatarWidth = 64; 225 - const avatarGap = 18; 226 - const nameFontSize = 22; 227 - const didFontSize = 15; 228 - const dateFontSize = 18; 229 - const safetyBuffer = 8; 230 - 231 - const nameText = safeAuthor.name || 'Anonymous'; 232 - const nameWidth = estimateTextWidth(nameText, nameFontSize, 0.56); 233 - const leftBase = avatarWidth + avatarGap + Math.max(120, nameWidth); 234 - 235 - const dateBlockWidth = dateString 236 - ? estimateTextWidth(dateString, dateFontSize, 0.55) + estimateTextWidth('Published', 14, 0.55) + 24 237 - : 0; 238 - 239 - const monoCharFactor = 0.6; 240 - const didFullWidth = estimateTextWidth(safeAuthor.did, didFontSize, monoCharFactor); 241 - 242 - const availableForLeft = containerWidth - paddingTotal - dateBlockWidth - safetyBuffer; 243 - const leftNeededIfFull = leftBase + 12 + didFullWidth; 244 - 245 - let didToRender = safeAuthor.did; 246 - if (leftNeededIfFull > availableForLeft) { 247 - const allowedDidWidth = Math.max(24, availableForLeft - leftBase - 12); 248 - const avgCharPx = didFontSize * monoCharFactor; 249 - let allowedChars = Math.floor(allowedDidWidth / avgCharPx); 250 - 251 - if (allowedChars <= 6) { 252 - const head = safeAuthor.did.slice(0, 4); 253 - const tail = safeAuthor.did.slice(-1); 254 - didToRender = `${head}…${tail}`; 255 - } else { 256 - const headLen = Math.ceil((allowedChars - 1) / 2); 257 - const tailLen = Math.floor((allowedChars - 1) / 2); 258 - const head = safeAuthor.did.slice(0, headLen); 259 - const tail = safeAuthor.did.slice(-tailLen); 260 - didToRender = `${head}…${tail}`; 261 - } 262 - } 263 - 264 - identifierNode = { 265 - type: 'code', 266 - props: { 267 - style: { 268 - fontSize: `${didFontSize}px`, 269 - fontFamily: 'monospace', 270 - background: '#1e2c23', 271 - color: '#8fd0a0', 272 - padding: '2px 6px', 273 - borderRadius: '6px', 274 - }, 275 - children: didToRender, 276 - }, 277 - }; 278 - } 279 - 280 - const footerContent = 281 - options.author || dateString 282 - ? { 283 - type: 'div', 284 - props: { 285 - style: { 286 - width: '100%', 287 - display: 'flex', 288 - flexDirection: 'row', 289 - alignItems: 'center', 290 - justifyContent: 'space-between', 291 - paddingLeft: '48px', 292 - paddingRight: '48px', 293 - opacity: 0.85, 294 - flexShrink: 0, 295 - }, 296 - children: [ 297 - { 298 - type: 'div', 299 - props: { 300 - style: { 301 - display: 'flex', 302 - flexDirection: 'row', 303 - alignItems: 'center', 304 - gap: '18px', 305 - textAlign: 'left', 306 - }, 307 - children: [ 308 - { 309 - type: 'img', 310 - props: { 311 - src: avatarDataUrl, 312 - width: 64, 313 - height: 64, 314 - style: { 315 - borderRadius: '50%', 316 - border: '3px solid #2d4839', 317 - boxShadow: '0 2px 12px rgba(0,0,0,0.18)', 318 - background: '#1e2c23', 319 - flexShrink: 0, 320 - }, 321 - }, 322 - }, 323 - { 324 - type: 'div', 325 - props: { 326 - style: { 327 - display: 'flex', 328 - flexDirection: 'column', 329 - alignItems: 'flex-start', 330 - gap: '2px', 331 - }, 332 - children: [ 333 - { 334 - type: 'span', 335 - props: { 336 - style: { 337 - fontSize: '22px', 338 - fontWeight: 700, 339 - lineHeight: 1.1, 340 - }, 341 - children: safeAuthor.name || 'Anonymous', 342 - }, 343 - }, 344 - identifierNode, 345 - ].filter(Boolean), 346 - }, 347 - }, 348 - ], 349 - }, 350 - }, 351 - dateString 352 - ? { 353 - type: 'div', 354 - props: { 355 - style: { 356 - display: 'flex', 357 - flexDirection: 'column', 358 - alignItems: 'flex-end', 359 - textAlign: 'right', 360 - gap: '4px', 361 - }, 362 - children: [ 363 - { 364 - type: 'span', 365 - props: { 366 - style: { 367 - fontSize: '18px', 368 - fontWeight: 600, 369 - color: '#d8e8d8', 370 - lineHeight: 1.2, 371 - }, 372 - children: dateString, 373 - }, 374 - }, 375 - { 376 - type: 'span', 377 - props: { 378 - style: { 379 - fontSize: '14px', 380 - color: '#8fd0a0', 381 - fontWeight: 400, 382 - fontStyle: 'italic', 383 - opacity: 0.8, 384 - }, 385 - children: 'Published', 386 - }, 387 - }, 388 - ], 389 - }, 390 - } 391 - : { type: 'div', props: {} }, 392 - ], 393 - }, 394 - } 395 - : null; 396 - 397 - const mainContainer = { 398 - type: 'div', 399 - props: { 400 - style: { 401 - width: '1200px', 402 - height: '630px', 403 - display: 'flex', 404 - flexDirection: 'column', 405 - alignItems: 'center', 406 - justifyContent: 'space-between', 407 - background: '#121c17', 408 - color: '#d8e8d8', 409 - fontFamily: 'Recursive', 410 - position: 'relative', 411 - padding: footerContent ? '60px 0 48px 0' : '48px 0', 412 - boxSizing: 'border-box', 413 - }, 414 - children: [ 415 - { 416 - type: 'div', 417 - props: { 418 - style: { 419 - display: 'flex', 420 - flexDirection: 'column', 421 - alignItems: 'center', 422 - justifyContent: 'center', 423 - width: '100%', 424 - flex: '1', 425 - }, 426 - children, 427 - }, 428 - }, 429 - footerContent, 430 - ].filter(Boolean), 431 - }, 432 - }; 433 - 434 - // Generate SVG with Satori 435 - const svg = await satori(mainContainer, { 436 - width: 1200, 437 - height: 630, 438 - fonts: [ 439 - { name: 'Recursive', data: fonts.regular, weight: 400, style: 'normal' }, 440 - { name: 'Recursive', data: fonts.bold, weight: 700, style: 'normal' }, 441 - { name: 'Recursive', data: fonts.italic, weight: 400, style: 'italic' }, 442 - ], 443 - }); 444 - 445 - // Convert SVG to PNG with Resvg 446 - const resvg = new Resvg(svg, { 447 - fitTo: { mode: 'width', value: 1200 }, 448 - background: '#121c17', 449 - }); 450 - 451 - const pngData = resvg.render(); 452 - return pngData.asPng(); 453 - } 454 - 455 - // Clear cache (useful for testing) 456 - export function clearOgImageCache() { 457 - imageCache.clear(); 458 - }
-123
src/lib/server/og/images.ts
··· 1 - import { dev } from '$app/environment'; 2 - 3 - // Cache for loaded images 4 - const imageCache = new Map<string, string>(); 5 - 6 - async function loadFallbackSvg(fileName: string, baseUrl?: string): Promise<string> { 7 - const cacheKey = `fallback:${fileName}`; 8 - 9 - if (imageCache.has(cacheKey)) { 10 - return imageCache.get(cacheKey)!; 11 - } 12 - 13 - const timeout = 2000; // 2 second timeout 14 - 15 - if (dev) { 16 - const fs = await import('fs/promises'); 17 - const path = await import('path'); 18 - const filePath = path.resolve(`static/fallback/${fileName}`); 19 - const svg = await fs.readFile(filePath, 'utf-8'); 20 - const dataUrl = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`; 21 - imageCache.set(cacheKey, dataUrl); 22 - return dataUrl; 23 - } else { 24 - const url = `${baseUrl || ''}/fallback/${fileName}`; 25 - 26 - const controller = new AbortController(); 27 - const timeoutId = setTimeout(() => controller.abort(), timeout); 28 - 29 - try { 30 - const res = await fetch(url, { signal: controller.signal }); 31 - clearTimeout(timeoutId); 32 - 33 - if (!res.ok) throw new Error(`Failed to fetch fallback SVG: ${res.status} ${res.statusText}`); 34 - const svgText = await res.text(); 35 - const dataUrl = `data:image/svg+xml;base64,${Buffer.from(svgText).toString('base64')}`; 36 - imageCache.set(cacheKey, dataUrl); 37 - return dataUrl; 38 - } catch (error) { 39 - clearTimeout(timeoutId); 40 - // Return a simple fallback SVG on error 41 - const simpleFallback = `data:image/svg+xml;base64,${Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="#2d4839"/></svg>').toString('base64')}`; 42 - imageCache.set(cacheKey, simpleFallback); 43 - return simpleFallback; 44 - } 45 - } 46 - } 47 - 48 - export async function resolveImageSrc( 49 - src: string | undefined | null, 50 - fallbackFileName: string, 51 - baseUrl?: string 52 - ): Promise<string> { 53 - // Use cache key for external images too 54 - const cacheKey = src || `fallback:${fallbackFileName}`; 55 - 56 - if (imageCache.has(cacheKey)) { 57 - return imageCache.get(cacheKey)!; 58 - } 59 - 60 - if (!src) { 61 - return await loadFallbackSvg(fallbackFileName, baseUrl); 62 - } 63 - 64 - const trimmed = src.trim(); 65 - 66 - // Data URLs are ready to use 67 - if (trimmed.startsWith('data:')) { 68 - imageCache.set(cacheKey, trimmed); 69 - return trimmed; 70 - } 71 - 72 - // For external HTTP(S) URLs, return them directly 73 - // Satori will handle fetching them, but we add timeout protection 74 - if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { 75 - // Don't fetch external images server-side - let Satori handle it 76 - // Just return the URL and cache it 77 - imageCache.set(cacheKey, trimmed); 78 - return trimmed; 79 - } 80 - 81 - const timeout = 2000; // 2 second timeout 82 - 83 - if (dev) { 84 - try { 85 - const fs = await import('fs/promises'); 86 - const path = await import('path'); 87 - const rel = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed; 88 - const filePath = path.resolve(`static/${rel}`); 89 - const svg = await fs.readFile(filePath); 90 - const dataUrl = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`; 91 - imageCache.set(cacheKey, dataUrl); 92 - return dataUrl; 93 - } catch (error) { 94 - console.warn(`Failed to load image ${trimmed}, using fallback`, error); 95 - return await loadFallbackSvg(fallbackFileName, baseUrl); 96 - } 97 - } else { 98 - const url = `${baseUrl || ''}${trimmed.startsWith('/') ? trimmed : `/${trimmed}`}`; 99 - 100 - const controller = new AbortController(); 101 - const timeoutId = setTimeout(() => controller.abort(), timeout); 102 - 103 - try { 104 - const res = await fetch(url, { signal: controller.signal }); 105 - clearTimeout(timeoutId); 106 - 107 - if (!res.ok) throw new Error(`Failed to fetch image ${url}: ${res.status}`); 108 - const text = await res.text(); 109 - const dataUrl = `data:image/svg+xml;base64,${Buffer.from(text).toString('base64')}`; 110 - imageCache.set(cacheKey, dataUrl); 111 - return dataUrl; 112 - } catch (error) { 113 - clearTimeout(timeoutId); 114 - console.warn(`Failed to load image ${url}, using fallback`, error); 115 - return await loadFallbackSvg(fallbackFileName, baseUrl); 116 - } 117 - } 118 - } 119 - 120 - // Clear cache (useful for testing) 121 - export function clearImageCache() { 122 - imageCache.clear(); 123 - }
-6
src/lib/server/og/index.ts
··· 1 - export * from './types'; 2 - export * from './fonts'; 3 - export * from './images'; 4 - export * from './text'; 5 - export * from './generateOgImage'; 6 - export * from './monitoring';
-66
src/lib/server/og/monitoring.ts
··· 1 - interface OgMetrics { 2 - cacheHits: number; 3 - cacheMisses: number; 4 - generationTimes: number[]; 5 - errors: number; 6 - timeouts: number; 7 - } 8 - 9 - const metrics: OgMetrics = { 10 - cacheHits: 0, 11 - cacheMisses: 0, 12 - generationTimes: [], 13 - errors: 0, 14 - timeouts: 0 15 - }; 16 - 17 - export function recordCacheHit() { 18 - metrics.cacheHits++; 19 - } 20 - 21 - export function recordCacheMiss() { 22 - metrics.cacheMisses++; 23 - } 24 - 25 - export function recordGenerationTime(ms: number) { 26 - metrics.generationTimes.push(ms); 27 - // Keep only last 100 measurements 28 - if (metrics.generationTimes.length > 100) { 29 - metrics.generationTimes.shift(); 30 - } 31 - } 32 - 33 - export function recordError() { 34 - metrics.errors++; 35 - } 36 - 37 - export function recordTimeout() { 38 - metrics.timeouts++; 39 - } 40 - 41 - export function getMetrics() { 42 - const times = metrics.generationTimes; 43 - const avg = times.length > 0 44 - ? times.reduce((a, b) => a + b, 0) / times.length 45 - : 0; 46 - const max = times.length > 0 ? Math.max(...times) : 0; 47 - const min = times.length > 0 ? Math.min(...times) : 0; 48 - 49 - return { 50 - ...metrics, 51 - averageGenerationTime: Math.round(avg), 52 - maxGenerationTime: max, 53 - minGenerationTime: min, 54 - cacheHitRate: metrics.cacheHits + metrics.cacheMisses > 0 55 - ? ((metrics.cacheHits / (metrics.cacheHits + metrics.cacheMisses)) * 100).toFixed(2) + '%' 56 - : 'N/A' 57 - }; 58 - } 59 - 60 - export function resetMetrics() { 61 - metrics.cacheHits = 0; 62 - metrics.cacheMisses = 0; 63 - metrics.generationTimes = []; 64 - metrics.errors = 0; 65 - metrics.timeouts = 0; 66 - }
-54
src/lib/server/og/text.ts
··· 1 - import type { OgImageOptions } from './types'; 2 - 3 - export function calculateTitleFontSize(title: string, maxWidth = 1000) { 4 - const baseSize = 64; 5 - const charThreshold = 45; 6 - const minSize = 36; 7 - if (title.length <= charThreshold) return baseSize; 8 - const scaleFactor = Math.max(0.5, 1 - ((title.length - charThreshold) * 0.012)); 9 - return Math.max(minSize, Math.floor(baseSize * scaleFactor)); 10 - } 11 - 12 - export function calculateSubtitleFontSize(subtitle: string, titleSize: number) { 13 - const baseRatio = 0.57; 14 - const charThreshold = 80; 15 - const minSize = 20; 16 - let size = Math.floor(titleSize * baseRatio); 17 - if (subtitle.length > charThreshold) { 18 - const scaleFactor = Math.max(0.7, 1 - ((subtitle.length - charThreshold) * 0.01)); 19 - size = Math.floor(size * scaleFactor); 20 - } 21 - return Math.max(minSize, size); 22 - } 23 - 24 - export function estimateContentHeight(options: OgImageOptions) { 25 - const titleFontSize = calculateTitleFontSize(options.title); 26 - const titleLines = Math.ceil(options.title.length / 40); 27 - const titleHeight = titleLines * titleFontSize * 1.25 + 32; 28 - 29 - let subtitleHeight = 0; 30 - if (options.subtitle) { 31 - const subtitleFontSize = calculateSubtitleFontSize(options.subtitle, titleFontSize); 32 - const subtitleLines = Math.ceil(options.subtitle.length / 60); 33 - subtitleHeight = subtitleLines * subtitleFontSize * 1.2 + 24; 34 - } 35 - 36 - let metaHeight = 0; 37 - if (options.metaLine || (options.extraMeta && options.extraMeta.length > 0)) { 38 - metaHeight = 32 + 24; 39 - } 40 - 41 - const authorHeight = options.author ? 120 : 28; 42 - 43 - return { 44 - titleHeight, 45 - subtitleHeight, 46 - metaHeight, 47 - authorHeight, 48 - totalContentHeight: titleHeight + subtitleHeight + metaHeight + authorHeight, 49 - }; 50 - } 51 - 52 - export function estimateTextWidth(text: string, fontSize: number, charWidthFactor = 0.55) { 53 - return Math.ceil(text.length * fontSize * charWidthFactor); 54 - }
-14
src/lib/server/og/types.ts
··· 1 - export interface OgImageOptions { 2 - title: string; 3 - subtitle?: string; 4 - metaLine?: string; 5 - author?: { 6 - name?: string; 7 - handle?: string; 8 - did?: string; 9 - avatar?: string; 10 - }; 11 - extraMeta?: (string | Date)[]; 12 - banner?: string; 13 - customChildren?: any; 14 - }
-49
src/lib/utils/textExtractor.ts
··· 1 - /** 2 - * Extracts plain text from HTML content and limits by word count. 3 - * 4 - * @param html - The HTML string to strip down 5 - * @param maxWords - Maximum number of words in the result 6 - * @returns Clean plain text excerpt 7 - */ 8 - export function extractTextFromHTML(html: string, maxWords: number = 200): string { 9 - if (!html) return ''; 10 - 11 - // SSR fallback: regex-based stripping 12 - if (typeof document === 'undefined') { 13 - let text = html 14 - .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') 15 - .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '') 16 - .replace(/<[^>]+>/g, ' ') 17 - .replace(/\s+/g, ' ') 18 - .trim(); 19 - 20 - const words = text.split(/\s+/); 21 - if (words.length <= maxWords) { 22 - return text; 23 - } 24 - return words.slice(0, maxWords).join(' ') + '...'; 25 - } 26 - 27 - // Browser environment: use DOM parsing 28 - const temp = document.createElement('div'); 29 - temp.innerHTML = html; 30 - 31 - // Remove script and style elements 32 - const scripts = temp.querySelectorAll('script, style'); 33 - scripts.forEach(el => el.remove()); 34 - 35 - // Get text content 36 - let text = temp.textContent || temp.innerText || ''; 37 - 38 - // Clean up whitespace 39 - text = text.replace(/\s+/g, ' ').trim(); 40 - 41 - // Split into words and truncate 42 - const words = text.split(/\s+/); 43 - if (words.length <= maxWords) { 44 - return text; 45 - } 46 - 47 - return words.slice(0, maxWords).join(' ') + '...'; 48 - } 49 -
-20
src/routes/+layout.server.ts
··· 1 - import { loadFonts } from '$lib/server/og/fonts'; 2 - 3 - let fontsWarmedUp = false; 4 - 5 - export const load = async ({ url }) => { 6 - // Warm up fonts on first request (fire and forget) 7 - if (!fontsWarmedUp) { 8 - fontsWarmedUp = true; 9 - const baseUrl = url.origin.includes('localhost') 10 - ? url.origin 11 - : 'https://ewancroft.uk'; 12 - 13 - // Don't await - let it warm up in background 14 - loadFonts(baseUrl) 15 - .then(() => console.log('✓ Fonts pre-loaded on first request')) 16 - .catch(err => console.error('Font pre-load failed:', err)); 17 - } 18 - 19 - return {}; 20 - };
+2 -2
src/routes/+page.svelte
··· 157 157 content="Welcome to Ewan's Corner - A personal space where I share my thoughts on coding, technology, and life." 158 158 /> 159 159 <meta property="og:site_name" content="Ewan's Corner" /> 160 - <meta property="og:image" content={`${$page.url.origin}/api/og/main.png`} /> 160 + <meta property="og:image" content={`${$page.url.origin}/og/main.png`} /> 161 161 <meta property="og:image:width" content="1200" /> 162 162 <meta property="og:image:height" content="630" /> 163 163 ··· 169 169 name="twitter:description" 170 170 content="Welcome to Ewan's Corner - A personal space where I share my thoughts on coding, technology, and life." 171 171 /> 172 - <meta name="twitter:image" content={`${$page.url.origin}/api/og/main.png`} /> 172 + <meta name="twitter:image" content={`${$page.url.origin}/og/main.png`} /> 173 173 </svelte:head> 174 174 175 175 <!-- Latest Blog Post section -->
-38
src/routes/api/cron/warmup/+server.ts
··· 1 - import { loadFonts } from '$lib/server/og/fonts'; 2 - import { dev } from '$app/environment'; 3 - 4 - export const GET = async ({ request, url }) => { 5 - // Verify cron secret (optional but recommended) 6 - const authHeader = request.headers.get('authorization'); 7 - const cronSecret = process.env.CRON_SECRET; 8 - 9 - if (cronSecret && authHeader !== `Bearer ${cronSecret}`) { 10 - return new Response('Unauthorized', { status: 401 }); 11 - } 12 - 13 - const baseUrl = dev ? url.origin : 'https://ewancroft.uk'; 14 - 15 - try { 16 - const startTime = Date.now(); 17 - await loadFonts(baseUrl); 18 - const elapsedTime = Date.now() - startTime; 19 - 20 - return new Response(JSON.stringify({ 21 - success: true, 22 - message: 'Cache warmed via cron', 23 - elapsedTime: `${elapsedTime}ms`, 24 - timestamp: new Date().toISOString() 25 - }), { 26 - headers: { 'Content-Type': 'application/json' } 27 - }); 28 - } catch (error) { 29 - console.error('Cron warmup failed:', error); 30 - return new Response(JSON.stringify({ 31 - success: false, 32 - error: error instanceof Error ? error.message : 'Unknown error' 33 - }), { 34 - status: 500, 35 - headers: { 'Content-Type': 'application/json' } 36 - }); 37 - } 38 - };
-19
src/routes/api/og/blog.png/+server.ts
··· 1 - import { generateOgImage } from '$lib/server/og'; 2 - import type { OgImageOptions } from '$lib/server/og'; 3 - import { dev } from '$app/environment'; 4 - 5 - export const GET = async ({ url }) => { 6 - const baseUrl = dev ? url.origin : 'https://ewancroft.uk'; 7 - 8 - const pngBuffer = await generateOgImage({ 9 - title: "Blog – Ewan's Corner", 10 - subtitle: 'Personal blog, coding, technology, and life', 11 - }, baseUrl); 12 - 13 - return new Response(new Uint8Array(pngBuffer), { 14 - headers: { 15 - 'Content-Type': 'image/png', 16 - 'Cache-Control': 'public, max-age=86400', 17 - }, 18 - }); 19 - };
-47
src/routes/api/og/blog/[rkey].png/+server.ts
··· 1 - import { loadAllPosts } from '$lib/services/blogService'; 2 - import { generateOgImage } from '$lib/server/og'; 3 - import type { OgImageOptions } from '$lib/server/og'; 4 - import { dev } from '$app/environment'; 5 - 6 - export const GET = async (event) => { 7 - const rkey = event.params.rkey; 8 - const baseUrl = dev ? event.url.origin : 'https://ewancroft.uk'; 9 - 10 - if (!rkey) { 11 - return new Response('Missing rkey', { status: 400 }); 12 - } 13 - 14 - const { getPost, profile } = await loadAllPosts(event.fetch); 15 - const post = getPost(rkey); 16 - 17 - if (!post) { 18 - return new Response('Post not found', { status: 404 }); 19 - } 20 - 21 - const wordCount = post.wordCount || 0; 22 - const readingTime = Math.ceil(wordCount / 200); 23 - 24 - // just pass the raw createdAt as a Date or ISO string 25 - const createdAt = post.createdAt ? new Date(post.createdAt) : new Date(); 26 - 27 - const metaLine = `${readingTime} min read • ${wordCount} words`; 28 - 29 - const pngBuffer = await generateOgImage({ 30 - title: post.title, 31 - subtitle: undefined, 32 - metaLine, 33 - extraMeta: [createdAt.toDateString()], 34 - author: { 35 - name: profile.displayName, 36 - handle: profile.handle, 37 - avatar: profile.avatar, 38 - }, 39 - }, baseUrl); 40 - 41 - return new Response(new Uint8Array(pngBuffer), { 42 - headers: { 43 - 'Content-Type': 'image/png', 44 - 'Cache-Control': 'public, max-age=86400', 45 - }, 46 - }); 47 - };
-78
src/routes/api/og/main.png/+server.ts
··· 1 - import { generateOgImage } from '$lib/server/og'; 2 - import type { OgImageOptions } from '$lib/server/og'; 3 - import { dev } from '$app/environment'; 4 - import { createFileDebugger } from '$lib/utils/debug.js'; 5 - 6 - const debug = createFileDebugger('api/og/main.png/+server.ts'); 7 - 8 - export const GET = async ({ url }) => { 9 - debug.enter('GET', { 10 - url: url.toString(), 11 - userAgent: url.searchParams.get('user-agent') || 'unknown', 12 - referer: url.searchParams.get('referer') || 'none' 13 - }); 14 - 15 - const timer = debug.time('mainOgImageGeneration'); 16 - 17 - try { 18 - debug.info('Processing main OG image request', { 19 - dev, 20 - urlOrigin: url.origin, 21 - baseUrl: dev ? url.origin : 'https://ewancroft.uk' 22 - }); 23 - 24 - const baseUrl = dev ? url.origin : 'https://ewancroft.uk'; 25 - 26 - debug.debug('Calling generateOgImage with main site options'); 27 - const pngBuffer = await generateOgImage({ 28 - title: "Ewan's Corner", 29 - subtitle: 'personal site, blog, and digital garden', 30 - }, baseUrl); 31 - 32 - debug.info('OG image generated successfully', { 33 - bufferLength: pngBuffer?.length, 34 - isBuffer: Buffer.isBuffer(pngBuffer), 35 - contentType: 'image/png' 36 - }); 37 - 38 - const response = new Response(new Uint8Array(pngBuffer), { 39 - headers: { 40 - 'Content-Type': 'image/png', 41 - 'Cache-Control': 'public, max-age=86400', 42 - }, 43 - }); 44 - 45 - debug.debug('Response created successfully', { 46 - status: response.status, 47 - headers: Object.fromEntries(response.headers.entries()) 48 - }); 49 - 50 - timer(); 51 - debug.exit('GET', { 52 - success: true, 53 - responseStatus: response.status, 54 - bufferLength: pngBuffer?.length 55 - }); 56 - 57 - return response; 58 - } catch (error) { 59 - debug.errorWithContext('Failed to generate main OG image', error as Error, { 60 - function: 'GET', 61 - url: url.toString() 62 - }); 63 - 64 - timer(); 65 - debug.exit('GET', { 66 - success: false, 67 - error: error 68 - }); 69 - 70 - // Return an error response 71 - return new Response('Failed to generate OG image', { 72 - status: 500, 73 - headers: { 74 - 'Content-Type': 'text/plain', 75 - }, 76 - }); 77 - } 78 - };
-19
src/routes/api/og/now.png/+server.ts
··· 1 - import { generateOgImage } from '$lib/server/og'; 2 - import type { OgImageOptions } from '$lib/server/og'; 3 - import { dev } from '$app/environment'; 4 - 5 - export const GET = async ({ url }) => { 6 - const baseUrl = dev ? url.origin : 'https://ewancroft.uk'; 7 - 8 - const pngBuffer = await generateOgImage({ 9 - title: 'What I\'m Doing Now', 10 - subtitle: 'My current focus, projects, and life updates', 11 - }, baseUrl); 12 - 13 - return new Response(new Uint8Array(pngBuffer), { 14 - headers: { 15 - 'Content-Type': 'image/png', 16 - 'Cache-Control': 'public, max-age=86400', 17 - }, 18 - }); 19 - };
-19
src/routes/api/og/site/meta.png/+server.ts
··· 1 - import { generateOgImage } from '$lib/server/og'; 2 - import type { OgImageOptions } from '$lib/server/og'; 3 - import { dev } from '$app/environment'; 4 - 5 - export const GET = async ({ url }) => { 6 - const baseUrl = dev ? url.origin : 'https://ewancroft.uk'; 7 - 8 - const pngBuffer = await generateOgImage({ 9 - title: 'Site Meta', 10 - subtitle: 'About this site, colophon, and technical details', 11 - }, baseUrl); 12 - 13 - return new Response(new Uint8Array(pngBuffer), { 14 - headers: { 15 - 'Content-Type': 'image/png', 16 - 'Cache-Control': 'public, max-age=86400', 17 - }, 18 - }); 19 - };
-59
src/routes/api/og/test.png/+server.ts
··· 1 - // Go to http://localhost:5173/api/og/test.png to see the result. 2 - // Note: In production, this would be at https://ewancroft.uk/api/og/test.png 3 - // This is primarily for testing the OG image generation locally and in production. 4 - // Particularly since this is entirely dynamic and not based on any content. 5 - 6 - import { generateOgImage } from '$lib/server/og'; 7 - import { dev } from '$app/environment'; 8 - 9 - // simple counter (module-level, persists across dev requests) 10 - let counter = 0; 11 - 12 - export const GET = async () => { 13 - if (!dev) { 14 - // In production, redirect to the index page 15 - return new Response(null, { 16 - status: 302, 17 - headers: { Location: '/' } 18 - }); 19 - } 20 - 21 - const now = new Date(); 22 - const dateCases = [ 23 - now, // 0: direct Date object 24 - now.toISOString(), // 1: ISO string 25 - now.toLocaleDateString('en-GB', { 26 - day: '2-digit', 27 - month: 'short', 28 - year: 'numeric' 29 - }), // 2: preformatted string 30 - ]; 31 - 32 - // Pick case by counter (cycles through 0,1,2,...) 33 - const caseIndex = counter % dateCases.length; 34 - const selectedCase = dateCases[caseIndex]; 35 - counter++; 36 - 37 - const png = await generateOgImage({ 38 - title: 'Test OG Image', 39 - subtitle: `This is a subtitle to check sizing and wrapping. [${caseIndex}]`, 40 - metaLine: '5 min read · 1200 words', 41 - author: { 42 - name: 'ewan', 43 - handle: 'ewancroft.uk', 44 - did: 'did:plc:ofrbh253gwicbkc5nktqepol', 45 - avatar: '', // force fallback 46 - }, 47 - extraMeta: [selectedCase], 48 - banner: '', // force fallback 49 - }); 50 - 51 - console.log(`OG Test: using case [${caseIndex}] ->`, selectedCase); 52 - 53 - return new Response(new Uint8Array(png), { 54 - headers: { 55 - 'Content-Type': 'image/png', 56 - 'Cache-Control': 'no-store', 57 - }, 58 - }); 59 - };
-35
src/routes/api/og/warmup/+server.ts
··· 1 - import { loadFonts } from '$lib/server/og/fonts'; 2 - import { dev } from '$app/environment'; 3 - 4 - export const GET = async ({ url }) => { 5 - const baseUrl = dev ? url.origin : 'https://ewancroft.uk'; 6 - 7 - try { 8 - const startTime = Date.now(); 9 - await loadFonts(baseUrl); 10 - const elapsedTime = Date.now() - startTime; 11 - 12 - return new Response(JSON.stringify({ 13 - success: true, 14 - message: 'Fonts pre-loaded successfully', 15 - elapsedTime: `${elapsedTime}ms` 16 - }), { 17 - headers: { 18 - 'Content-Type': 'application/json', 19 - 'Cache-Control': 'no-cache' 20 - } 21 - }); 22 - } catch (error) { 23 - console.error('Font warmup failed:', error); 24 - return new Response(JSON.stringify({ 25 - success: false, 26 - error: error instanceof Error ? error.message : 'Unknown error' 27 - }), { 28 - status: 500, 29 - headers: { 30 - 'Content-Type': 'application/json', 31 - 'Cache-Control': 'no-cache' 32 - } 33 - }); 34 - } 35 - };
+2 -2
src/routes/blog/+page.svelte
··· 139 139 content="Welcome to Blog - Ewan's Corner - A personal blog where I share my thoughts on coding, technology, and life." 140 140 /> 141 141 <meta property="og:site_name" content="Blog - Ewan's Corner" /> 142 - <meta property="og:image" content={`${$page.url.origin}/api/og/blog.png`} /> 142 + <meta property="og:image" content={`${$page.url.origin}/og/blog.png`} /> 143 143 <meta property="og:image:width" content="1200" /> 144 144 <meta property="og:image:height" content="630" /> 145 145 ··· 151 151 name="twitter:description" 152 152 content="Welcome to Blog - Ewan's Corner - A personal blog where I share my thoughts on coding, technology, and life." 153 153 /> 154 - <meta name="twitter:image" content={`${$page.url.origin}/api/og/blog.png`} /> 154 + <meta name="twitter:image" content={`${$page.url.origin}/og/blog.png`} /> 155 155 </svelte:head> 156 156 157 157 {#if isLoading}
+3 -3
src/routes/blog/atom/+server.ts
··· 35 35 <name>${profile.displayName || profile.handle}</name> 36 36 <uri>${baseUrl}/blog</uri> 37 37 </author> 38 - <icon>${baseUrl}/api/og/blog.png</icon> 39 - <logo>${baseUrl}/api/og/blog.png</logo> 38 + <icon>${baseUrl}/og/blog.png</icon> 39 + <logo>${baseUrl}/og/blog.png</logo> 40 40 ${sortedPosts 41 41 .map( 42 42 (post) => ` ··· 52 52 <name>${profile.displayName || profile.handle}</name> 53 53 <uri>https://bsky.app/profile/${profile.handle}</uri> 54 54 </author> 55 - <link href="${baseUrl}/api/og/blog/${post.rkey}.png" rel="enclosure" type="image/png" /> 55 + <link href="${baseUrl}/og/blog.png" rel="enclosure" type="image/png" /> 56 56 </entry>` 57 57 ) 58 58 .join("")}
+2 -2
src/routes/blog/rss/+server.ts
··· 68 68 <link>${baseUrl}/blog</link> 69 69 <atom:link href="${baseUrl}/blog/rss" rel="self" type="application/rss+xml" /> 70 70 <image> 71 - <url>${baseUrl}/api/og/blog.png</url> 71 + <url>${baseUrl}/og/blog.png</url> 72 72 <title>Blog - Ewan's Corner</title> 73 73 <link>${baseUrl}/blog</link> 74 74 </image> ··· 86 86 <author>${profileData.displayName || profileData.handle} (${ 87 87 profileData.handle 88 88 })</author> 89 - <media:content url="${baseUrl}/api/og/blog/${post.rkey}.png" medium="image" /> 89 + <media:content url="${baseUrl}/og/blog.png" medium="image" /> 90 90 </item>` 91 91 ) 92 92 .join("")}
+2 -2
src/routes/now/atom/+server.ts
··· 64 64 <n>${profileData.displayName || profileData.handle}</n> 65 65 <uri>${baseUrl}/now</uri> 66 66 </author> 67 - <icon>${baseUrl}/api/og/now.png</icon> 68 - <logo>${baseUrl}/api/og/now.png</logo> 67 + <icon>${baseUrl}/og/now.png</icon> 68 + <logo>${baseUrl}/og/now.png</logo> 69 69 ${sortedUpdates 70 70 .map( 71 71 (status) => `
+1 -1
src/routes/now/rss/+server.ts
··· 61 61 <link>${baseUrl}/now</link> 62 62 <atom:link href="${baseUrl}/now/rss" rel="self" type="application/rss+xml" /> 63 63 <image> 64 - <url>${baseUrl}/api/og/now.png</url> 64 + <url>${baseUrl}/og/now.png</url> 65 65 <title>Now - ${profileData.displayName || profileData.handle}'s Status Updates</title> 66 66 <link>${baseUrl}/now</link> 67 67 </image>
+2 -2
src/routes/site/meta/+page.svelte
··· 27 27 content="Learn about Ewan's Corner - including technology stack, website purpose, and privacy information." 28 28 /> 29 29 <meta property="og:site_name" content="Ewan's Corner" /> 30 - <meta property="og:image" content={`${$page.url.origin}/api/og/site/meta.png`} /> 30 + <meta property="og:image" content={`${$page.url.origin}/og/site-meta.png`} /> 31 31 <meta property="og:image:width" content="1200" /> 32 32 <meta property="og:image:height" content="630" /> 33 33 ··· 39 39 name="twitter:description" 40 40 content="Learn about Ewan's Corner - including technology stack, website purpose, and privacy information." 41 41 /> 42 - <meta name="twitter:image" content={`${$page.url.origin}/api/og/site/meta.png`} /> 42 + <meta name="twitter:image" content={`${$page.url.origin}/og/site-meta.png`} /> 43 43 </svelte:head> 44 44 45 45 <div class="max-w-2xl mx-auto px-4 py-8">
-93
static/fonts/ArrowType-Recursive-1.085/LICENSE.txt
··· 1 - Copyright 2020 The Recursive Project Authors (https://github.com/arrowtype/recursive) 2 - 3 - This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 - This license is copied below, and is also available with a FAQ at: 5 - http://scripts.sil.org/OFL 6 - 7 - 8 - ----------------------------------------------------------- 9 - SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 - ----------------------------------------------------------- 11 - 12 - PREAMBLE 13 - The goals of the Open Font License (OFL) are to stimulate worldwide 14 - development of collaborative font projects, to support the font creation 15 - efforts of academic and linguistic communities, and to provide a free and 16 - open framework in which fonts may be shared and improved in partnership 17 - with others. 18 - 19 - The OFL allows the licensed fonts to be used, studied, modified and 20 - redistributed freely as long as they are not sold by themselves. The 21 - fonts, including any derivative works, can be bundled, embedded, 22 - redistributed and/or sold with any software provided that any reserved 23 - names are not used by derivative works. The fonts and derivatives, 24 - however, cannot be released under any other type of license. The 25 - requirement for fonts to remain under this license does not apply 26 - to any document created using the fonts or their derivatives. 27 - 28 - DEFINITIONS 29 - "Font Software" refers to the set of files released by the Copyright 30 - Holder(s) under this license and clearly marked as such. This may 31 - include source files, build scripts and documentation. 32 - 33 - "Reserved Font Name" refers to any names specified as such after the 34 - copyright statement(s). 35 - 36 - "Original Version" refers to the collection of Font Software components as 37 - distributed by the Copyright Holder(s). 38 - 39 - "Modified Version" refers to any derivative made by adding to, deleting, 40 - or substituting -- in part or in whole -- any of the components of the 41 - Original Version, by changing formats or by porting the Font Software to a 42 - new environment. 43 - 44 - "Author" refers to any designer, engineer, programmer, technical 45 - writer or other person who contributed to the Font Software. 46 - 47 - PERMISSION & CONDITIONS 48 - Permission is hereby granted, free of charge, to any person obtaining 49 - a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 - redistribute, and sell modified and unmodified copies of the Font 51 - Software, subject to the following conditions: 52 - 53 - 1) Neither the Font Software nor any of its individual components, 54 - in Original or Modified Versions, may be sold by itself. 55 - 56 - 2) Original or Modified Versions of the Font Software may be bundled, 57 - redistributed and/or sold with any software, provided that each copy 58 - contains the above copyright notice and this license. These can be 59 - included either as stand-alone text files, human-readable headers or 60 - in the appropriate machine-readable metadata fields within text or 61 - binary files as long as those fields can be easily viewed by the user. 62 - 63 - 3) No Modified Version of the Font Software may use the Reserved Font 64 - Name(s) unless explicit written permission is granted by the corresponding 65 - Copyright Holder. This restriction only applies to the primary font name as 66 - presented to the users. 67 - 68 - 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 - Software shall not be used to promote, endorse or advertise any 70 - Modified Version, except to acknowledge the contribution(s) of the 71 - Copyright Holder(s) and the Author(s) or with their explicit written 72 - permission. 73 - 74 - 5) The Font Software, modified or unmodified, in part or in whole, 75 - must be distributed entirely under this license, and must not be 76 - distributed under any other license. The requirement for fonts to 77 - remain under this license does not apply to any document created 78 - using the Font Software. 79 - 80 - TERMINATION 81 - This license becomes null and void if any of the above conditions are 82 - not met. 83 - 84 - DISCLAIMER 85 - THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 - OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 - COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 - INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 - DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 - FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 - OTHER DEALINGS IN THE FONT SOFTWARE.
-32
static/fonts/ArrowType-Recursive-1.085/README.md
··· 1 - # Recursive Mono & Sans, by Arrow Type 2 - 3 - Recursive is a typographic palette for code & UI. It includes sans-serif & mono fonts designed specifically for use in interactive applications and code editors. See https://recursive.design for more information. 4 - 5 - Recursive is an open-source project. Follow along and contribute at https://github.com/arrowtype/recursive. It is licensed under the OFL, so (in summary) you are free to make derivative versions, but these must also adopt the same OFL license (e.g. you cannot sell proprietary licenses for derivative fonts). See `LICENSE.txt` for full details. 6 - 7 - Do you have a question or have you found a bug? Please file an issue at https://github.com/arrowtype/recursive/issues. Thanks! 8 - 9 - ## Recommendations 10 - 11 - For design & usage recommendations, please see the project README at https://github.com/arrowtype/recursive. 12 - 13 - Fonts in `Recursive_Desktop` and `Recursive_Code` are made so that it is possible to install all of these without experiencing conflicts in font menus. However, you may want to pick-and-choose which font files you wish to install, based on your needs. 14 - 15 - ### General Desktop use (Word, PowerPoint, Keynote, InDesign, Illustrator, PhotoShop, Figma, etc) 16 - 17 - - On Windows, install `Recursive_Desktop/recursive-statics.ttc` (This is a collection of all 64 static instances in TTF format) 18 - - On Mac, install `Recursive_Desktop/recursive-statics.otc` (This is a collection of all 64 static instances in OTF format) 19 - 20 - ### Desktop web design (Sketch) & experimental use in Adobe apps 21 - 22 - - Install `Recursive_Desktop/Recursive_VF_1.0XX.ttf` (this is the full Recursive variable font) 23 - - It may also be beneficial to install static fonts, as OS & app support of variable fonts is still growing 24 - 25 - ### Code (code editors such as VS Code, Atom, Sublime, etc etc) 26 - 27 - - Install fonts in `Recursive_Code` (These are specifically simplified families for use in code editors. See README in that directory for further advice) 28 - 29 - ### Web 30 - 31 - - Use the woff2 font files in `Recursive_Web`. This includes a few useful subsets for variable fonts, along with some starter `@font-face` CSS for the `woff2_variable_subsets`. 32 - - If you only need a style or two on a site, it may be practical to just use static instances, but you may want to figure out subsetting with `pyftsubset` to make those even smaller.
-46
static/fonts/ArrowType-Recursive-1.085/Recursive_Code/README.md
··· 1 - # Rec Mono for Code 2 - 3 - This folder includes four pre-customized packages specifically made for code editors, each featuring: 4 - - Regular, Italic, Bold, & Bold Italic static fonts 5 - - Frozen-in Code Ligatures 6 - - An abbreviated family name to enable italic themes on macOS 7 - - Reduced-slant italics for easier readability in code (normal Recursive Italics have slnt=-15, which is pretty intense) 8 - - Frozen-in OpenType features to enhance legibility for code (e.g. making 1 and l instantly recognizable) 9 - - `ss03` # simplified f 10 - - `ss05` # simplified l 11 - - `ss08` # serifless L and Z 12 - - `ss09` # simplified 6 and 9 13 - - `ss12` # simplified @ 14 - 15 - NOTE: If you would rather customize your own version of Rec Mono for Code (for instance, without Code Ligatures or with different stylistic sets frozen-in), check out https://github.com/arrowtype/recursive-code-config. 16 - 17 - 18 - ## Packages 19 - 20 - Download the zip in this folder for an easy way to download these fonts. Then, install the fonts then call them from your favorite code editor with their relevant family name, e.g. `Rec Mono Duotone`. 21 - 22 - **`Rec Mono Duotone`** 23 - - A personal favorite – this use the Linear style for Regular text and Casual styles for Bold, Italic, & Bold Italic text. In many themes that use italic styles, this will give most code a utilitarian look, but set comments, some keywords, and certain headlines in the more-handwritten Casual style. 24 - 25 - **`Rec Mono Linear`** 26 - - An everyday workhorse for code. Slightly-boxy shapes maximize legibility while maintaining a standard monospace width, while a few quirks add a little bit of extra personality and differentiation between similarly-shaped characters. 27 - 28 - **`Rec Mono Casual`** 29 - - A party in a font. Fun & wacky shapes, simplified enough for small sizes but curvy enough to have plenty of character. Best in casual coding & non-primary terminals. 30 - 31 - **`Rec Mono SemiCasual`** 32 - - Sets the CASL axis at `0.5` for font that is serious but softened a little bit. This isn't the best choice for text at large sizes (like headlines on a website), but can be a really nice balance in code. 33 - 34 - Want to customize your own version? Check out https://github.com/arrowtype/recursive-code-config! 35 - 36 - ## Code Ligatures 37 - 38 - By popular demand, these fonts shift the code ligature feature from their usual OpenType feature of `dlig` to the feature `calt`, to act more like preexisting code-ligature fonts such as Fira Code & Hasklig. 39 - 40 - In the Desktop & Web fonts, code ligatures are still controlled by the `dlig` feature, so that they are *not* on by default for contexts in which they may not be well-understood by many users. 41 - 42 - ## And again... 43 - 44 - If you would rather customize your own version of Rec Mono for Code, use the scripts at https://github.com/arrowtype/recursive-code-config. 45 - 46 - Happy coding!
static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoCasual/RecMonoCasual-Bold-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoCasual/RecMonoCasual-BoldItalic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoCasual/RecMonoCasual-Italic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoCasual/RecMonoCasual-Regular-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoDuotone/RecMonoDuotone-Bold-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoDuotone/RecMonoDuotone-BoldItalic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoDuotone/RecMonoDuotone-Italic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoDuotone/RecMonoDuotone-Regular-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoLinear/RecMonoLinear-Bold-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoLinear/RecMonoLinear-BoldItalic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoLinear/RecMonoLinear-Italic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoLinear/RecMonoLinear-Regular-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoSemicasual/RecMonoSemicasual-Bold-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoSemicasual/RecMonoSemicasual-BoldItalic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoSemicasual/RecMonoSemicasual-Italic-1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Code/RecMonoSemicasual/RecMonoSemicasual-Regular-1.085.ttf

This is a binary file and will not be displayed.

-24
static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/README.md
··· 1 - # Installation recommendations 2 - 3 - It is possible to install both static & variable fonts without font menu conflicts, as these are given distinct font family names. 4 - 5 - However, it is useful to install only the specific static fonts you need from this folder, or you may experience conflicts in use. 6 - 7 - ## General Desktop use (Word, PowerPoint, Keynote, InDesign, Illustrator, PhotoShop, etc) 8 - 9 - - On **Windows**, install `Recursive_Desktop/recursive-static-TTFs.ttc` (This is a collection of all 64 static instances in TTF format) *OR* the separate TTFs from the `separate_statics` folder. 10 - - On **Mac**, install `Recursive_Desktop/recursive-static-OTFs.otc` (This is a collection of all 64 static instances in OTF format) *OR* the separate OTFs from the `separate_statics` folder. 11 - - On **Linux** and other operating systems, you are probably safest to install the TTFs from the `separate_statics` folder. 12 - 13 - ### Usage-specific recommendations 14 - 15 - - For **design** in Figma, and for general usage in older systems/applications, you should use separate static fonts from within the `separate_statics` folder 16 - - For **printing**, you may get better results from the OTFs. 17 - - For the **web**, *don’t* use the fonts in this folder. You will get better results (and smaller file sizes) by using the `woff2` files in the adjacent `Recursive_Web` folder. 18 - 19 - ## Desktop web design (Sketch) & experimental use in Adobe apps 20 - 21 - - Install `Recursive_Desktop/Recursive_VF_1.0XX.ttf` (this is the full Recursive variable font) 22 - - It may also be beneficial to install static fonts, as OS & app support of variable fonts is still growing. 23 - 24 - **NOTE: Currently, variable fonts do not export to PDFs cleanly from Adobe apps.** This is something Adobe is working on, and it is not an issue in Recursive. So, if you are designing for print, it is recommended that you use static OTFs rather than variable fonts. If you do use variable fonts in print design, be sure to check the output PDFs to ensure that styles are not mixed in unexpected ways (e.g. letters within the same words may have inconsistent weights, etc).
static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/Recursive_VF_1.085.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/recursive-static-OTFs.otc

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/recursive-static-TTFs.ttc

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-BdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-Black.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-BlkItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-Bold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-ExtraBd.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-Italic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-Light.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-LtItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-Med.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-MedItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-Regular.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-SemiBd.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-SmBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-XBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-XBlk.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoCslSt-XBlkItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-Black.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-BlackItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-Bold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-BoldItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-ExBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-ExtraBold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-Italic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-Light.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-LightItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-Med.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-MedItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-Regular.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-SemiBold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-SmBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-XBlk.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveMonoLnrSt-XBlkItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-BdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-Black.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-BlkItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-Bold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-ExtraBd.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-Italic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-Light.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-LtItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-Med.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-MedItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-Regular.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-SemiBd.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-SmBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-XBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-XBlk.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansCslSt-XBlkItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-Black.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-BlackItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-Bold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-BoldItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-ExBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-ExtraBold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-Italic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-Light.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-LightItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-Med.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-MedItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-Regular.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-SemiBold.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-SmBdItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-XBlk.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/OTF/RecursiveSansLnrSt-XBlkItalic.otf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-BdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-Black.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-BlkItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-Bold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-ExtraBd.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-Italic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-Light.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-LtItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-Med.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-MedItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-Regular.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-SemiBd.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-SmBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-XBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-XBlk.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoCslSt-XBlkItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-Black.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-BlackItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-Bold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-BoldItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-ExBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-ExtraBold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-Italic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-Light.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-LightItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-Med.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-MedItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-Regular.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-SemiBold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-SmBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-XBlk.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveMonoLnrSt-XBlkItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-BdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-Black.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-BlkItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-Bold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-ExtraBd.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-Italic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-Light.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-LtItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-Med.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-MedItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-Regular.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-SemiBd.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-SmBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-XBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-XBlk.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansCslSt-XBlkItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-Black.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-BlackItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-Bold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-BoldItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-ExBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-ExtraBold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-Italic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-Light.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-LightItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-Med.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-MedItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-Regular.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-SemiBold.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-SmBdItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-XBlk.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Desktop/separate_statics/TTF/RecursiveSansLnrSt-XBlkItalic.ttf

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-BdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-Black.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-BlkItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-Bold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-ExtraBd.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-Italic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-Light.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-LtItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-Med.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-MedItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-Regular.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-SemiBd.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-SmBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-XBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-XBlk.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoCslSt-XBlkItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-Black.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-BlackItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-Bold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-BoldItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-ExBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-ExtraBold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-Italic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-Light.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-LightItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-Med.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-MedItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-Regular.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-SemiBold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-SmBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-XBlk.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveMonoLnrSt-XBlkItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-BdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-Black.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-BlkItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-Bold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-ExtraBd.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-Italic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-Light.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-LtItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-Med.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-MedItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-Regular.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-SemiBd.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-SmBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-XBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-XBlk.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansCslSt-XBlkItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-Black.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-BlackItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-Bold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-BoldItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-ExBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-ExtraBold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-Italic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-Light.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-LightItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-Med.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-MedItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-Regular.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-SemiBold.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-SmBdItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-XBlk.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_static/RecursiveSansLnrSt-XBlkItalic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable/Recursive_VF_1.085--subset-GF_latin_basic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable/Recursive_VF_1.085.woff2

This is a binary file and will not be displayed.

-61
static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/fonts.css
··· 1 - 2 - /* The bare minimum English subset, plus copyright & arrows (← ↑ → ↓) & quotes (“ ” ‘ ’) & bullet (•) */ 3 - @font-face { 4 - font-family: 'RecVar'; 5 - font-style: oblique 0deg 15deg; 6 - font-weight: 300 1000; 7 - font-display: swap; 8 - src: url('./fonts/Recursive_VF_1.085--subset_range_english_basic.woff2') format('woff2'); 9 - unicode-range: U+0020-007F,U+00A9,U+2190-2193,U+2018,U+2019,U+201C,U+201D,U+2022; 10 - } 11 - 12 - /* unicode latin-1 letters, basic european diacritics */ 13 - @font-face { 14 - font-family: 'RecVar'; 15 - font-style: oblique 0deg 15deg; 16 - font-weight: 300 1000; 17 - font-display: swap; 18 - src: url('./fonts/Recursive_VF_1.085--subset_range_latin_1.woff2') format('woff2'); 19 - unicode-range: U+00C0-00FF; 20 - } 21 - 22 - /* unicode latin-1, punc/symbols & arrows (↔ ↕ ↖ ↗ ↘ ↙) */ 23 - @font-face { 24 - font-family: 'RecVar'; 25 - font-style: oblique 0deg 15deg; 26 - font-weight: 300 1000; 27 - font-display: swap; 28 - src: url('./fonts/Recursive_VF_1.085--subset_range_latin_1_punc.woff2') format('woff2'); 29 - unicode-range: U+00A0-00A8,U+00AA-00BF,U+2194-2199; 30 - } 31 - 32 - /* unicode latin A extended */ 33 - @font-face { 34 - font-family: 'RecVar'; 35 - font-style: oblique 0deg 15deg; 36 - font-weight: 300 1000; 37 - font-display: swap; 38 - src: url('./fonts/Recursive_VF_1.085--subset_range_latin_ext.woff2') format('woff2'); 39 - unicode-range: U+0100-017F; 40 - } 41 - 42 - /* unicodes for vietnamese */ 43 - @font-face { 44 - font-family: 'RecVar'; 45 - font-style: oblique 0deg 15deg; 46 - font-weight: 300 1000; 47 - font-display: swap; 48 - src: url('./fonts/Recursive_VF_1.085--subset_range_vietnamese.woff2') format('woff2'); 49 - unicode-range: U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB; 50 - } 51 - 52 - /* remaining Unicodes */ 53 - @font-face { 54 - font-family: 'RecVar'; 55 - font-style: oblique 0deg 15deg; 56 - font-weight: 300 1000; 57 - font-display: swap; 58 - src: url('./fonts/Recursive_VF_1.085--subset_range_remaining.woff2') format('woff2'); 59 - unicode-range: U+2007,U+2008,U+2009,U+200A,U+200B,U+D,U+2010,U+2012,U+2013,U+2014,U+2015,U+201A,U+201E,U+2020,U+2021,U+2026,U+2030,U+2032,U+2033,U+2039,U+203A,U+203E,U+2044,U+2052,U+2070,U+2074,U+2075,U+2076,U+2077,U+2078,U+2079,U+207B,U+2080,U+2081,U+2082,U+2083,U+2084,U+2085,U+2086,U+2087,U+2088,U+2089,U+20A1,U+20A6,U+20A8,U+20A9,U+20AA,U+20AC,U+20AD,U+20B1,U+20B2,U+20B4,U+20B5,U+20B8,U+20B9,U+20BA,U+20BC,U+20BD,U+20BF,U+F8FF,U+2113,U+2116,U+2122,U+2126,U+212E,U+E132,U+E133,U+2153,U+2154,U+215B,U+215C,U+215D,U+215E,U+18F,U+192,U+19D,U+1C4,U+1C5,U+1C6,U+1C7,U+1C8,U+1C9,U+1CA,U+1CB,U+1CC,U+1E6,U+1E7,U+1EA,U+1EB,U+1F1,U+1F2,U+1F3,U+1FA,U+1FB,U+1FC,U+1FD,U+1FE,U+1FF,U+200,U+201,U+202,U+203,U+204,U+205,U+206,U+207,U+208,U+209,U+20A,U+20B,U+20C,U+20D,U+20E,U+20F,U+210,U+211,U+212,U+213,U+214,U+215,U+216,U+217,U+218,U+219,U+21A,U+21B,U+2215,U+2219,U+221E,U+221A,U+22A,U+22B,U+22C,U+22D,U+222B,U+230,U+231,U+232,U+233,U+2236,U+237,U+2248,U+259,U+2260,U+2261,U+2264,U+2265,U+272,U+2B9,U+2BA,U+2BB,U+2BC,U+2BE,U+2BF,U+2C6,U+2C7,U+2C8,U+2C9,U+2CA,U+2CB,U+2D8,U+2D9,U+2DA,U+2DB,U+2DC,U+2DD,U+300,U+301,U+FB02,U+FB03,U+302,U+303,U+304,U+FB01,U+306,U+307,U+308,U+309,U+30A,U+30B,U+30C,U+30F,U+311,U+312,U+315,U+31B,U+2202,U+323,U+324,U+325,U+326,U+327,U+328,U+329,U+2205,U+32E,U+2206,U+331,U+335,U+220F,U+2211,U+2212,U+391,U+392,U+393,U+394,U+398,U+39B,U+39C,U+39D,U+3A0,U+3A6,U+3B1,U+3B2,U+3B3,U+3B4,U+3B8,U+3BB,U+3BC,U+3BD,U+3C0,U+3C6,U+25A0,U+25A1,U+25B2,U+25B3,U+25B6,U+25B7,U+25BC,U+25BD,U+25C0,U+25C1,U+25C6,U+25C7,U+25CA,U+1E08,U+1E09,U+1E0C,U+1E0D,U+1E0E,U+1E0F,U+2610,U+2611,U+1E14,U+1E15,U+1E16,U+1E17,U+1E1C,U+1E1D,U+1E20,U+1E21,U+1E24,U+1E25,U+1E2A,U+1E2B,U+1E2E,U+1E2F,U+1E36,U+1E37,U+1E3A,U+1E3B,U+E3F,U+1E42,U+1E43,U+1E44,U+1E45,U+1E46,U+1E47,U+1E48,U+1E49,U+1E4C,U+1E4D,U+1E4E,U+1E4F,U+1E50,U+1E51,U+1E52,U+1E53,U+1E5A,U+1E5B,U+1E5E,U+1E5F,U+1E60,U+2661,U+1E61,U+1E62,U+1E63,U+1E64,U+1E65,U+1E66,U+1E67,U+1E68,U+1E69,U+2665,U+1E6C,U+1E6D,U+1E6E,U+1E6F,U+1E78,U+1E79,U+1E7A,U+1E7B,U+1E80,U+1E81,U+1E82,U+1E83,U+1E84,U+1E85,U+1E8E,U+1E8F,U+1E92,U+1E93,U+1E97,U+1E9E,U+2713,U+27E8,U+27E9; 60 - } 61 -
static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/fonts/Recursive_VF_1.085--subset_range_english_basic.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/fonts/Recursive_VF_1.085--subset_range_latin_1.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/fonts/Recursive_VF_1.085--subset_range_latin_1_punc.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/fonts/Recursive_VF_1.085--subset_range_latin_ext.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/fonts/Recursive_VF_1.085--subset_range_remaining.woff2

This is a binary file and will not be displayed.

static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/fonts/Recursive_VF_1.085--subset_range_vietnamese.woff2

This is a binary file and will not be displayed.

-90
static/fonts/ArrowType-Recursive-1.085/Recursive_Web/woff2_variable_subsets/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 - <title>About these subsets</title> 8 - <link rel="stylesheet" href="./fonts.css"> 9 - <style> 10 - body { 11 - font-family: "RecVar"; 12 - max-width: 64rem; 13 - margin: 2rem auto; 14 - } 15 - p { 16 - font-size: 32px; 17 - line-height: 2; 18 - font-variation-settings:"MONO" 1; 19 - } 20 - .intro { 21 - margin: 2rem 0 4rem 0; 22 - } 23 - .intro p, .intro blockquote, .intro li { 24 - font-size: 16px; 25 - line-height: 1.5; 26 - max-width: 80ch; 27 - font-variation-settings:"MONO" 0; 28 - margin-bottom: 1rem; 29 - } 30 - blockquote{ 31 - border-left: 1px black solid; 32 - margin: 0; 33 - padding-left: 1rem; 34 - } 35 - code { 36 - font-family: "RecVar"; 37 - font-variation-settings:"MONO" 1; 38 - background-color: #eee; 39 - padding: 0 0.05em; 40 - } 41 - </style> 42 - </head> 43 - <body> 44 - <h1>Example HTML for Recursive Variable Subsets</h1> 45 - 46 - <div class="intro"> 47 - <p> 48 - The subset fonts in this example use <a href="">CSS unicode-range</a> to load progressively, as needed. To quote MDN: 49 - </p> 50 - <blockquote> 51 - The unicode-range CSS descriptor sets the specific range of characters to be used from a font defined by @font-face and made available for use on the current page. If the page doesn't use any character in this range, the font is not downloaded; if it uses at least one, the whole font is downloaded. 52 - </blockquote> 53 - <p> 54 - See Appendix below for information on further font optimization strategies. 55 - </p> 56 - </div> 57 - 58 - <h2>subset_range_english_basic</h2> 59 - <p> 60 - A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 _ - ( ) [ ] { } # % \' " ‘ ’ “ ” * . , : ; ! ? / \ | @ & • + = < > ← ↑ → ↓ $ ^ ~ © 61 - </p> 62 - <h2>subset_range_latin_1_punc</h2> 63 - <p> 64 - ª º ¹ ² ³ ¼ ½ ¾ « » ¡ ¿ ¦ § ¶ · ± ¬ ↔ ¢ £ ¤ ¥ ´ ¯ ¨ ¸ ® ° ↕ ↖ ↗ ↘ ↙ µ 65 - </p> 66 - <h2>subset_range_latin_ext</h2> 67 - <p> 68 - Ā Ă Ą Ć Ĉ Ċ Č Ď Ē Ĕ Ė Ę Ě Ĝ Ğ Ġ Ģ Ĥ Ĩ Ī Ĭ Į İ Ĵ Ķ Ĺ Ļ Ľ Ń Ņ Ň Ō Ŏ Ő Ŕ Ŗ Ř Ś Ŝ Ş Š Ţ Ť Ũ Ū Ŭ Ů Ű Ų Ŵ Ŷ Ÿ Ź Ż Ž Đ Ħ IJ Ŀ Ł Ŋ Œ Ŧ ā ă ą ć ĉ ċ č ď ē ĕ ė ę ě ĝ ğ ġ ģ ĥ ĩ ī ĭ į ĵ ķ ĺ ļ ľ ń ņ ň ō ŏ ő ŕ ŗ ř ś ŝ ş š ţ ť ũ ū ŭ ů ű ų ŵ ŷ ź ż ž đ ħ ı ij ĸ ŀ ł ʼn ŋ œ ŧ 69 - </p> 70 - <h2>subset_range_vietnamese</h2> 71 - <p> 72 - Ă Ạ Ả Ấ Ầ Ẩ Ẫ Ậ Ắ Ằ Ẳ Ẵ Ặ Ẹ Ẻ Ẽ Ế Ề Ể Ễ Ệ Ĩ Ỉ Ị Ơ Ọ Ỏ Ố Ồ Ổ Ỗ Ộ Ớ Ờ Ở Ỡ Ợ Ũ Ư Ụ Ủ Ứ Ừ Ử Ữ Ự Ỳ Ỵ Ỷ Ỹ Đ ă ạ ả ấ ầ ẩ ẫ ậ ắ ằ ẳ ẵ ặ ẹ ẻ ẽ ế ề ể ễ ệ ĩ ỉ ị ơ ọ ỏ ố ồ ổ ỗ ộ ớ ờ ở ỡ ợ ũ ư ụ ủ ứ ừ ử ữ ự ỳ ỵ ỷ ỹ đ ₫ 73 - </p> 74 - <h2>subset_range_remaining</h2> 75 - <p> 76 - Ǻ Ȁ Ȃ Ḉ Ḍ Ḏ Ȅ Ȇ Ḕ Ḗ Ḝ Ǧ Ḡ Ḥ Ḫ Ȉ Ȋ Ḯ Ḷ Ḻ Ṃ Ṅ Ṇ Ṉ Ǫ Ȍ Ȏ Ȫ Ȭ Ȱ Ṍ Ṏ Ṑ Ṓ Ȑ Ȓ Ṛ Ṟ Ș Ṡ Ṣ Ṥ Ṧ Ṩ Ț Ṭ Ṯ Ȕ Ȗ Ṹ Ṻ Ẁ Ẃ Ẅ Ȳ Ẏ Ẓ Ǽ Ǿ Ə Ɲ DŽ LJ NJ DZ ẞ Ω ǻ ȁ ȃ ḉ ḍ ḏ ȅ ȇ ḕ ḗ ḝ ǧ ḡ ḥ ḫ ȉ ȋ ḯ ḷ ḻ ṃ ṅ ṇ ṉ ǫ ȍ ȏ ȫ ȭ ȱ ṍ ṏ ṑ ṓ ȑ ȓ ṛ ṟ ș ṡ ṣ ṥ ṧ ṩ ț ṭ ṯ ẗ ȕ ȗ ṹ ṻ ẁ ẃ ẅ ȳ ẏ ẓ ǽ ǿ dž lj nj dz ȷ ə ɲ π Dž Lj Nj Dz ʹ ʺ ʻ ʼ ʾ ʿ ˈ ˉ ˊ ˋ ˌ ̀ ́ ̂ ̃ ̄ ̆ ̇ ̈ ̉ ̊ ̋ ̌ ̏ ̑ ̒ ̕ ̛ ̣ ̤ ̥ ̦ ̧ ̨ ̮ ̱ ̵ ⁰ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ⅓ ⅔ ⅛ ⅜ ⅝ ⅞ ‐ ‒ – — ― ⟨ ⟩ ‰ ‚ „ ‹ › † ‡ … ⁄ ℓ № ′ ″ ‾ − ≤ ≥ ≈ ≠ ⁒ ∂ ∅ ∆ ∏ ∑ ∕ ∙ √ ∞ ∫ ≡ ▷ ◁ ฿ ₡ ₦ ₨ ₩ ₪ € ƒ ₭ ₱ ₲ ₴ ₵ ₸ ₹ ₺ ₼ ₽ ₿ ˝ ˆ ˇ ˘ ˜ ˙ ˚ ˛ ™ ℮ ■ □ ▲ △ ▶ ▼ ▽ ◀ ◆ ◇ ◊ ☐ ☑ ♡ ♥ ✓ 77 - </p> 78 - 79 - <div class="intro"> 80 - <h2>Appendix</h2> 81 - <p>There are ways to further optimize Recursive:</p> 82 - <ol> 83 - <li>If you only need part of the stylistic range (e.g. Monospace <code>0</code>, Casual <code>1</code>, Weight <code>300-800</code>), variable fonts can be “instanced” or “partially instanced” with the <a href="https://fonttools.readthedocs.io/en/latest/varLib/instancer.html" rel="nofollow">FontTools Instancer</a>. You can do it locally on the command line or in a Python script. Alternatively, the <a href="https://css-tricks.com/getting-the-most-out-of-variable-fonts-on-google-fonts/" rel="nofollow">Google Fonts API allows you to do this by requesting a special URL</a>, and the “Get Recursive” section of the <a href="https://recursive.design/" rel="nofollow">Recursive Minisite</a> has a UI to help construct these URLs.</li> 84 - <li>You can tailor subsets and CSS <code>unicode-range</code> to your specific use case. E.g. if you are specifically writing in only English &amp; Spanish, you could make a subset for this, using the FontTools Subsetter or by adapting <a href="https://github.com/arrowtype/recursive/blob/4d6fb032b722968cdc12b7d6b7da1994e10e4f70/src/build-scripts/make-release/make-variable-woff2s_and_subsets.sh">my Recursive subsetting script</a> if you want.</li> 85 - <li>You can combine instancing and subsetting. For instance, if you want a particular font style for just a website’s logo, you could make a font file that just includes characters in the single style (or style range) you need for that, and load it separately. For example, the website <a href="https://sia.codes/" rel="nofollow">https://sia.codes/</a> does something like this for the name <code>Sia</code> on the homepage.</li> 86 - </ol> 87 - <p>If you want to optimize these fonts but don’t want to use fonts from Google Fonts and also don’t want to dig into code to optimize them on your own, please consider reaching out to <a href="https://arrowtype.com">Arrow Type</a> to commission custom subsetting & instancing.</p> 88 - </div> 89 - </body> 90 - </html>
static/og/blog.png

This is a binary file and will not be displayed.

static/og/main.png

This is a binary file and will not be displayed.

static/og/now.png

This is a binary file and will not be displayed.

static/og/site-meta.png

This is a binary file and will not be displayed.