All files / web/scripts generateDayIcon.tsx

0% Statements 0/118
0% Branches 0/1
0% Functions 0/1
0% Lines 0/118

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)