@@ -60,7 +60,16 @@ export class GraphRenderer {
6060 titleColor : options . titleColor ,
6161 } ) ;
6262
63- const { WIDTH : width , HEIGHT : height } = this . DIMENSIONS ;
63+ const SIZE_PRESETS : Record < string , { WIDTH : number ; HEIGHT : number } > = {
64+ small : { WIDTH : 800 , HEIGHT : 400 } ,
65+ medium : { WIDTH : 1000 , HEIGHT : 500 } ,
66+ default : { WIDTH : 1200 , HEIGHT : 600 } ,
67+ large : { WIDTH : 1400 , HEIGHT : 700 } ,
68+ } ;
69+ const { WIDTH : width , HEIGHT : height } = SIZE_PRESETS [ options . size ?? 'default' ] ?? SIZE_PRESETS . default ;
70+ const scale = width / 1200 ;
71+ const titleFontSize = Math . round ( 42 * scale ) ;
72+ const contribFontSize = Math . round ( 18 * scale ) ;
6473 const fontName = theme . fontName || 'Orbitron' ;
6574 const fontFamily = theme . fontFamily || `'${ fontName } ', 'Ubuntu', 'sans-serif'` ;
6675 const fontUrl = theme . fontUrl || '/fonts/orbitron.woff2' ;
@@ -76,8 +85,8 @@ export class GraphRenderer {
7685 this . adjustColor ( theme . iconColor , 25 ) ,
7786 ] ;
7887
79- const cellSize = 14 ;
80- const gap = 4 ;
88+ const cellSize = Math . round ( 14 * scale ) ;
89+ const gap = Math . max ( 2 , Math . round ( 4 * scale ) ) ;
8190 const step = cellSize + gap ;
8291 const gridWidth = data . weeks . length * step ;
8392 const gridHeight = 7 * step ;
@@ -90,27 +99,27 @@ export class GraphRenderer {
9099 const svgWidth = showBackground ? width : gridWidth + bgMargin * 2 ;
91100 const startX = showBackground ? ( width - gridWidth ) / 2 : bgMargin ;
92101
93- const titleY = 90 ;
94- const titleFrameY1 = 52 ;
95- const titleFrameY2 = 100 ;
102+ const titleY = Math . round ( 90 * scale ) ;
103+ const titleFrameY1 = Math . round ( 52 * scale ) ;
104+ const titleFrameY2 = Math . round ( 100 * scale ) ;
96105
97106 const svgRenderHeight = showContrib
98107 ? height
99108 : showTitle
100- ? Math . max ( 300 , 158 + gridHeight + 90 )
101- : Math . max ( 240 , 48 + gridHeight + 90 ) ;
109+ ? Math . max ( Math . round ( 300 * scale ) , Math . round ( 158 * scale ) + gridHeight + Math . round ( 90 * scale ) )
110+ : Math . max ( Math . round ( 240 * scale ) , Math . round ( 48 * scale ) + gridHeight + Math . round ( 90 * scale ) ) ;
102111
103112 const startY = ( showTitle && showContrib )
104- ? Math . round ( ( height - gridHeight ) / 2 + 60 )
113+ ? Math . round ( ( height - gridHeight ) / 2 + Math . round ( 60 * scale ) )
105114 : ( ! showTitle && showContrib )
106115 ? Math . round ( ( height - gridHeight ) / 2 )
107116 : showTitle
108- ? Math . round ( svgRenderHeight * 0.15 ) + titleFrameY2 + 28
109- : Math . round ( svgRenderHeight * 0.15 ) + 28 ;
117+ ? Math . round ( svgRenderHeight * 0.15 ) + titleFrameY2 + Math . round ( 28 * scale )
118+ : Math . round ( svgRenderHeight * 0.15 ) + Math . round ( 28 * scale ) ;
110119
111- const contribBaseY = showTitle ? 125 : Math . round ( startY / 2 ) ;
112- const contribFrameTop = contribBaseY - 28 ;
113- const contribFrameBot = contribBaseY + 14 ;
120+ const contribBaseY = showTitle ? Math . round ( 125 * scale ) : Math . round ( startY / 2 ) ;
121+ const contribFrameTop = contribBaseY - Math . round ( 28 * scale ) ;
122+ const contribFrameBot = contribBaseY + Math . round ( 14 * scale ) ;
114123
115124 // Stars only needed when background is visible; cache uses full canvas dims
116125 const stars = showBackground ? this . getStarfield ( width , height , theme . textColor ) : '' ;
@@ -164,7 +173,7 @@ export class GraphRenderer {
164173 const month = new Date ( day0 . date ) . getMonth ( ) ;
165174 if ( month !== currentMonth ) {
166175 currentMonth = month ;
167- monthParts . push ( `<text x="${ ( startX + i * step ) . toFixed ( 1 ) } " y="${ monthY } " fill="${ theme . textColor } " font-size="10 " opacity="0.5" font-family="${ fontFamily } ">${ MONTHS [ month ] } </text>` ) ;
176+ monthParts . push ( `<text x="${ ( startX + i * step ) . toFixed ( 1 ) } " y="${ monthY } " fill="${ theme . textColor } " font-size="${ Math . round ( 10 * scale ) } " opacity="0.5" font-family="${ fontFamily } ">${ MONTHS [ month ] } </text>` ) ;
168177 }
169178 }
170179
@@ -181,25 +190,25 @@ export class GraphRenderer {
181190 if ( ! showTitle ) return '' ;
182191 const cx = svgWidth / 2 ;
183192 const titleText = data . username + "'s Activity " + data . year ;
184- const tfw = ( titleText . length * 24 ) / 2 ;
193+ const tfw = ( titleText . length * Math . round ( 24 * scale ) ) / 2 ;
185194 return [
186- `<text x="50%" y="${ titleY } " text-anchor="middle" fill="${ theme . titleColor } " font-size="42 " font-family="${ fontFamily } " font-weight="700" style="filter:drop-shadow(0 0 12px ${ theme . titleColor } 66)">${ titleText } </text>` ,
187- `<g fill="none" stroke="${ theme . titleColor } " stroke-width="1.5" opacity="0.25">${ buildCornerPaths ( cx - tfw - 12 , titleFrameY1 , cx + tfw + 12 , titleFrameY2 , 16 ) } </g>` ,
195+ `<text x="50%" y="${ titleY } " text-anchor="middle" fill="${ theme . titleColor } " font-size="${ titleFontSize } " font-family="${ fontFamily } " font-weight="700" style="filter:drop-shadow(0 0 12px ${ theme . titleColor } 66)">${ titleText } </text>` ,
196+ `<g fill="none" stroke="${ theme . titleColor } " stroke-width="1.5" opacity="0.25">${ buildCornerPaths ( cx - tfw - Math . round ( 12 * scale ) , titleFrameY1 , cx + tfw + Math . round ( 12 * scale ) , titleFrameY2 , Math . round ( 16 * scale ) ) } </g>` ,
188197 ] . join ( '\n ' ) ;
189198 } ) ( ) ;
190199
191200 const contribSection = ( ( ) => {
192201 if ( ! showContrib ) return '' ;
193202 const cx = svgWidth / 2 ;
194203 const contribText = data . totalContributions . toLocaleString ( ) + ' total contributions' ;
195- const tfw = ( contribText . length * 11 ) / 2 ;
204+ const tfw = ( contribText . length * Math . round ( 11 * scale ) ) / 2 ;
196205 return [
197- `<text x="50%" y="${ contribBaseY } " text-anchor="middle" fill="${ theme . textColor } " font-size="18 " opacity="0.8" font-family="${ fontFamily } " filter="url(#textGlow)">${ contribText } </text>` ,
198- `<g fill="none" stroke="${ theme . iconColor } " stroke-width="1.5" opacity="0.35">${ buildCornerPaths ( cx - tfw - 9 , contribFrameTop , cx + tfw + 9 , contribFrameBot , 12 ) } </g>` ,
206+ `<text x="50%" y="${ contribBaseY } " text-anchor="middle" fill="${ theme . textColor } " font-size="${ contribFontSize } " opacity="0.8" font-family="${ fontFamily } " filter="url(#textGlow)">${ contribText } </text>` ,
207+ `<g fill="none" stroke="${ theme . iconColor } " stroke-width="1.5" opacity="0.35">${ buildCornerPaths ( cx - tfw - Math . round ( 9 * scale ) , contribFrameTop , cx + tfw + Math . round ( 9 * scale ) , contribFrameBot , Math . round ( 12 * scale ) ) } </g>` ,
199208 ] . join ( '\n ' ) ;
200209 } ) ( ) ;
201210
202- const gridCorners = `<g fill="none" stroke="${ theme . iconColor } " stroke-width="1.5" opacity="0.35">${ buildCornerPaths ( startX - 24 , startY - 28 , startX + gridWidth + 24 , startY + gridHeight + 24 , 12 ) } </g>` ;
211+ const gridCorners = `<g fill="none" stroke="${ theme . iconColor } " stroke-width="1.5" opacity="0.35">${ buildCornerPaths ( startX - Math . round ( 24 * scale ) , startY - Math . round ( 28 * scale ) , startX + gridWidth + Math . round ( 24 * scale ) , startY + gridHeight + Math . round ( 24 * scale ) , Math . round ( 12 * scale ) ) } </g>` ;
203212
204213 const gridLines = showBackground ? ( ( ) => {
205214 const vLines = Array . from ( { length : 24 } , ( _ , i ) => {
@@ -214,10 +223,14 @@ export class GraphRenderer {
214223 } ) ( ) : '' ;
215224
216225 const legend = ( ( ) => {
217- const lx = ( startX + gridWidth - 100 ) . toFixed ( 1 ) ;
218- const ly = ( startY + gridHeight + 25 ) . toFixed ( 1 ) ;
219- const rects = levelColors . map ( ( c , i ) => `<rect x="${ i * 15 } " y="0" width="12" height="12" fill="${ c } " rx="2" opacity="${ i === 0 ? 0.3 : 0.9 } "/>` ) . join ( '' ) ;
220- return `<g transform="translate(${ lx } ,${ ly } )"><text x="-35" y="10" fill="${ theme . textColor } " font-size="10" opacity="0.5" font-family="${ fontFamily } ">Less</text>${ rects } <text x="${ levelColors . length * 15 + 5 } " y="10" fill="${ theme . textColor } " font-size="10" opacity="0.5" font-family="${ fontFamily } ">More</text></g>` ;
226+ const lgCell = Math . round ( 12 * scale ) ;
227+ const lgStep = Math . round ( 15 * scale ) ;
228+ const lgFont = Math . round ( 10 * scale ) ;
229+ const lgTextY = Math . round ( 10 * scale ) ;
230+ const lx = ( startX + gridWidth - Math . round ( 100 * scale ) ) . toFixed ( 1 ) ;
231+ const ly = ( startY + gridHeight + Math . round ( 25 * scale ) ) . toFixed ( 1 ) ;
232+ const rects = levelColors . map ( ( c , i ) => `<rect x="${ i * lgStep } " y="0" width="${ lgCell } " height="${ lgCell } " fill="${ c } " rx="2" opacity="${ i === 0 ? 0.3 : 0.9 } "/>` ) . join ( '' ) ;
233+ return `<g transform="translate(${ lx } ,${ ly } )"><text x="${ - Math . round ( 35 * scale ) } " y="${ lgTextY } " fill="${ theme . textColor } " font-size="${ lgFont } " opacity="0.5" font-family="${ fontFamily } ">Less</text>${ rects } <text x="${ levelColors . length * lgStep + Math . round ( 5 * scale ) } " y="${ lgTextY } " fill="${ theme . textColor } " font-size="${ lgFont } " opacity="0.5" font-family="${ fontFamily } ">More</text></g>` ;
221234 } ) ( ) ;
222235
223236 const divClipX = ( svgWidth / 2 - ( width - 160 ) / 4 ) . toFixed ( 1 ) ;
0 commit comments