Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | #!/usr/bin/env tsx /** * Pre-generate all 31 day-of-month favicons for production * * This script generates icon-day-01.svg through icon-day-31.svg * Production route handler will read these pre-generated files */ // biome-ignore lint/correctness/noUnusedImports: React is required for JSX transform import React from 'react' import { renderToStaticMarkup } from 'react-dom/server' import { writeFileSync, mkdirSync } from 'fs' import { join } from 'path' import { AbacusStatic } from '@soroban/abacus-react' // Extract just the SVG element from rendered output function extractSvgElement(markup: string): string { const svgMatch = markup.match(/<svg[^>]*>[\s\S]*?<\/svg>/) if (!svgMatch) { throw new Error('No SVG element found in rendered output') } return svgMatch[0] } // Generate a single day icon function generateDayIcon(day: number): string { // Render 2-column abacus showing day of month const abacusMarkup = renderToStaticMarkup( <AbacusStatic value={day} columns={2} scaleFactor={1.8} showNumbers={false} hideInactiveBeads={true} frameVisible={true} cropToActiveBeads={{ padding: { top: 8, bottom: 2, left: 5, right: 5, }, }} customStyles={{ columnPosts: { fill: '#1c1917', stroke: '#0c0a09', strokeWidth: 2, }, reckoningBar: { fill: '#1c1917', stroke: '#0c0a09', strokeWidth: 3, }, columns: { 0: { // Ones place - Gold (royal theme) heavenBeads: { fill: '#fbbf24', stroke: '#f59e0b', strokeWidth: 2 }, earthBeads: { fill: '#fbbf24', stroke: '#f59e0b', strokeWidth: 2 }, }, 1: { // Tens place - Purple (royal theme) heavenBeads: { fill: '#a855f7', stroke: '#7e22ce', strokeWidth: 2 }, earthBeads: { fill: '#a855f7', stroke: '#7e22ce', strokeWidth: 2 }, }, }, }} /> ) // Extract the cropped SVG let croppedSvg = extractSvgElement(abacusMarkup) // Remove !important from CSS (production code policy) croppedSvg = croppedSvg.replace(/\s*!important/g, '') // Parse width and height from the cropped SVG const widthMatch = croppedSvg.match(/width="([^"]+)"/) const heightMatch = croppedSvg.match(/height="([^"]+)"/) if (!widthMatch || !heightMatch) { throw new Error('Could not parse dimensions from cropped SVG') } const croppedWidth = parseFloat(widthMatch[1]) const croppedHeight = parseFloat(heightMatch[1]) // Calculate scale to fit cropped region into 96x96 (leaving room for border) const targetSize = 96 const scale = Math.min(targetSize / croppedWidth, targetSize / croppedHeight) // Center in 100x100 canvas const scaledWidth = croppedWidth * scale const scaledHeight = croppedHeight * scale const offsetX = (100 - scaledWidth) / 2 const offsetY = (100 - scaledHeight) / 2 // Wrap in 100x100 SVG canvas for favicon // Extract viewBox from cropped SVG to preserve it const viewBoxMatch = croppedSvg.match(/viewBox="([^"]+)"/) const viewBox = viewBoxMatch ? viewBoxMatch[1] : `0 0 ${croppedWidth} ${croppedHeight}` return `<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <!-- Abacus showing day ${day.toString().padStart(2, '0')} (US Central Time) - cropped to active beads --> <svg x="${offsetX}" y="${offsetY}" width="${scaledWidth}" height="${scaledHeight}" viewBox="${viewBox}"> ${croppedSvg.match(/<svg[^>]*>([\s\S]*?)<\/svg>/)?.[1] || ''} </svg> </svg> ` } // Main execution const publicDir = join(__dirname, '..', 'public') const iconsDir = join(publicDir, 'icons') try { // Ensure icons directory exists mkdirSync(iconsDir, { recursive: true }) console.log('Generating all 31 day-of-month favicons...\n') // Generate all 31 days for (let day = 1; day <= 31; day++) { const svg = generateDayIcon(day) const filename = `icon-day-${day.toString().padStart(2, '0')}.svg` const filepath = join(iconsDir, filename) writeFileSync(filepath, svg) console.log(`ā Generated ${filename}`) } console.log('\nā All day icons generated successfully!') console.log(` Location: public/icons/icon-day-*.svg`) } catch (error) { console.error('ā Error generating day icons:', error) process.exit(1) } |