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 | #!/usr/bin/env tsx /** * Generate a single day-of-month favicon * Usage: npx tsx scripts/generateDayIcon.tsx <day> * Example: npx tsx scripts/generateDayIcon.tsx 15 */ import React from 'react' import { renderToStaticMarkup } from 'react-dom/server' 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] } // Get day from command line argument const day = parseInt(process.argv[2], 10) if (!day || day < 1 || day > 31) { console.error('Usage: npx tsx scripts/generateDayIcon.tsx <day>') console.error('Example: npx tsx scripts/generateDayIcon.tsx 15') process.exit(1) } // Render 2-column abacus showing day of month // Using AbacusStatic for server-side rendering 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}` const svg = `<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> ` // Output to stdout so parent process can capture it process.stdout.write(svg) |