From bb0c03613551da247970436ccab9442216e1ac11 Mon Sep 17 00:00:00 2001 From: Piyush-jangid-01 Date: Thu, 29 Oct 2026 01:14:33 +0530 Subject: [PATCH 1/3] feat(templates): add blog benchmark card --- src/templates/blog/benchmark.ts | 186 ++++++++++++++++++++++++++++++ src/templates/blog/index.ts | 1 + src/templates/registry.ts | 2 + tests/templates/benchmark.test.ts | 141 ++++++++++++++++++++++ 4 files changed, 330 insertions(+) create mode 100644 src/templates/blog/benchmark.ts create mode 100644 tests/templates/benchmark.test.ts diff --git a/src/templates/blog/benchmark.ts b/src/templates/blog/benchmark.ts new file mode 100644 index 0000000..773cbab --- /dev/null +++ b/src/templates/blog/benchmark.ts @@ -0,0 +1,186 @@ +import type { SatoriElement, TemplateDefinition } from '../types'; +import { autoFontSize, commonFields, isLightColor, mixColors, truncate } from '../utils'; + +type BenchmarkRow = { name: string; value: number }; + +const template: TemplateDefinition = { + id: 'blog-benchmark', + name: 'Benchmark', + category: 'blog', + description: 'Compare performance or size measurements with scaled horizontal bars', + tags: ['blog', 'benchmark', 'performance', 'comparison', 'stats'], + fields: [ + commonFields.title, + commonFields.description, + { key: 'name1', label: 'Name 1', type: 'text', defaultValue: 'Bun', placeholder: 'Bun', group: 'Content' }, + { key: 'value1', label: 'Value 1', type: 'number', defaultValue: 112000, placeholder: '112000', min: 0, group: 'Content' }, + { key: 'name2', label: 'Name 2', type: 'text', defaultValue: 'Deno', placeholder: 'Deno', group: 'Content' }, + { key: 'value2', label: 'Value 2', type: 'number', defaultValue: 71000, placeholder: '71000', min: 0, group: 'Content' }, + { key: 'name3', label: 'Name 3', type: 'text', defaultValue: 'Node', placeholder: 'Node', group: 'Content' }, + { key: 'value3', label: 'Value 3', type: 'number', defaultValue: 48000, placeholder: '48000', min: 0, group: 'Content' }, + { key: 'name4', label: 'Name 4', type: 'text', defaultValue: '', placeholder: 'Optional comparison', group: 'Content' }, + { key: 'value4', label: 'Value 4', type: 'number', defaultValue: '', placeholder: 'Optional value', min: 0, group: 'Content' }, + { key: 'unit', label: 'Unit', type: 'text', defaultValue: 'req/s', placeholder: 'req/s', group: 'Content' }, + commonFields.siteName, + commonFields.bgColor, + commonFields.accentColor, + commonFields.textColor, + ], + defaults: { + title: 'Bun vs Deno vs Node: HTTP throughput', + description: 'Requests per second, higher is better', + name1: 'Bun', value1: 112000, + name2: 'Deno', value2: 71000, + name3: 'Node', value3: 48000, + name4: '', value4: '', + unit: 'req/s', + siteName: 'codercops.com', + bgColor: '#0a0a0a', + accentColor: '#E07A5F', + textColor: '#FFFFFF', + }, + render: (params) => { + const rawTitle = String(params.title ?? template.defaults.title).replace(/\s+/g, ' ').trim(); + const title = truncate(rawTitle || String(template.defaults.title), 120); + const titleSize = autoFontSize(title, [ + { maxLen: 36, size: 46 }, + { maxLen: 60, size: 40 }, + { maxLen: 90, size: 34 }, + { maxLen: 120, size: 28 }, + ]); + const rawDescription = String(params.description ?? '').replace(/\s+/g, ' ').trim(); + const description = truncate(rawDescription, 120); + const descriptionSize = autoFontSize(description, [ + { maxLen: 40, size: 20 }, + { maxLen: 75, size: 18 }, + { maxLen: 120, size: 15 }, + ]); + const rows: BenchmarkRow[] = [1, 2, 3, 4].flatMap((index) => { + const name = String(params[`name${index}`] ?? '').replace(/\s+/g, ' ').trim(); + const rawValue = params[`value${index}`]; + if (!name || rawValue === undefined || rawValue === null || String(rawValue).trim() === '') return []; + + const value = Number(rawValue); + return Number.isFinite(value) && value >= 0 ? [{ name: truncate(name, 80), value }] : []; + }); + const maxValue = rows.reduce((max, row) => Math.max(max, row.value), 0); + const largestIndex = rows.findIndex(({ value }) => value === maxValue); + const unit = String(params.unit ?? 'req/s').replace(/\s+/g, ' ').trim(); + const siteName = String(params.siteName ?? '').replace(/\s+/g, ' ').trim(); + const bgColor = String(params.bgColor || '#0a0a0a'); + const accentColor = String(params.accentColor || '#E07A5F'); + const textColor = String(params.textColor || '#FFFFFF'); + const lightBg = isLightColor(bgColor); + const accentInk = lightBg ? mixColors(accentColor, '#000000', 0.45) : accentColor; + const foregroundColor = lightBg && isLightColor(textColor) ? '#111111' : textColor; + const mutedText = mixColors(bgColor, foregroundColor, lightBg ? 0.68 : 0.62); + const trackColor = mixColors(bgColor, foregroundColor, lightBg ? 0.13 : 0.2); + + const rowElements: SatoriElement[] = rows.map(({ name, value }, index) => { + const nameSize = autoFontSize(name, [ + { maxLen: 16, size: 22 }, + { maxLen: 28, size: 19 }, + { maxLen: 48, size: 16 }, + { maxLen: 80, size: 14 }, + ]); + const fillColor = index === largestIndex ? accentInk : mixColors(bgColor, foregroundColor, lightBg ? 0.3 : 0.36); + const width = maxValue > 0 ? (value / maxValue) * 100 : 0; + const formattedValue = new Intl.NumberFormat('en-US', { maximumFractionDigits: 3 }).format(value); + + return { + type: 'div', + props: { + style: { display: 'flex', alignItems: 'center', gap: '22px', width: '100%', minHeight: '68px' }, + children: [ + { + type: 'span', + props: { + style: { + display: 'flex', flexShrink: 0, width: '190px', minWidth: 0, + overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', + fontSize: `${nameSize}px`, fontWeight: 600, color: foregroundColor, + }, + children: name, + }, + }, + { + type: 'div', + props: { + style: { display: 'flex', flex: 1, minWidth: 0, height: '16px', overflow: 'hidden', borderRadius: '999px', backgroundColor: trackColor }, + children: [ + { + type: 'div', + props: { + style: { display: 'flex', width: `${width}%`, height: '100%', borderRadius: '999px', backgroundColor: fillColor }, + children: [], + }, + }, + ], + }, + }, + { + type: 'span', + props: { + style: { display: 'flex', justifyContent: 'flex-end', flexShrink: 0, width: '220px', fontSize: '20px', fontWeight: 700, color: foregroundColor, whiteSpace: 'nowrap' }, + children: `${formattedValue}${unit ? ` ${unit}` : ''}`, + }, + }, + ], + }, + }; + }); + + return { + type: 'div', + props: { + style: { + display: 'flex', flexDirection: 'column', width: '1200px', height: '630px', + boxSizing: 'border-box', padding: '52px 64px 42px', backgroundColor: bgColor, + fontFamily: 'Inter, sans-serif', + }, + children: [ + { + type: 'div', + props: { + style: { display: 'flex', alignSelf: 'flex-start', padding: '8px 16px', marginBottom: '18px', borderRadius: '999px', backgroundColor: mixColors(bgColor, accentInk, lightBg ? 0.08 : 0.17), border: `1px solid ${accentInk}66` }, + children: { + type: 'span', + props: { style: { display: 'flex', color: accentInk, fontSize: '14px', fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase' }, children: 'Benchmark' }, + }, + }, + }, + { + type: 'h1', + props: { + style: { display: 'flex', width: '100%', margin: 0, color: foregroundColor, fontSize: `${titleSize}px`, fontWeight: 700, lineHeight: 1.18, letterSpacing: '-0.02em', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, + children: title, + }, + }, + ...(description ? [{ + type: 'span', + props: { + style: { display: 'flex', marginTop: '10px', color: mutedText, fontSize: `${descriptionSize}px`, lineHeight: 1.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, + children: description, + }, + }] : []), + ...(rowElements.length ? [{ + type: 'div', + props: { + style: { display: 'flex', flexDirection: 'column', width: '100%', gap: '14px', marginTop: 'auto', marginBottom: 'auto' }, + children: rowElements, + }, + }] : []), + ...(siteName ? [{ + type: 'div', + props: { + style: { display: 'flex', justifyContent: 'flex-end', marginTop: 'auto', paddingTop: '14px', borderTop: `1px solid ${trackColor}` }, + children: { type: 'span', props: { style: { display: 'flex', fontSize: '16px', fontWeight: 600, color: mutedText }, children: truncate(siteName, 48) } }, + }, + }] : []), + ], + }, + }; + }, +}; + +export default template; diff --git a/src/templates/blog/index.ts b/src/templates/blog/index.ts index 7b02d75..dca8d07 100644 --- a/src/templates/blog/index.ts +++ b/src/templates/blog/index.ts @@ -13,3 +13,4 @@ export { default as blogRetroTerminal } from './retro-terminal'; export { default as blogSeriesPart } from './series-part'; export { default as blogSummaryCard } from './summary-card'; export { default as blogTableOfContents } from './table-of-contents'; +export { default as blogBenchmark } from './benchmark'; diff --git a/src/templates/registry.ts b/src/templates/registry.ts index 7d71171..a05c820 100644 --- a/src/templates/registry.ts +++ b/src/templates/registry.ts @@ -57,6 +57,7 @@ import blogRetroTerminal from './blog/retro-terminal'; import blogSeriesPart from './blog/series-part'; import blogSummaryCard from './blog/summary-card'; import blogTableOfContents from './blog/table-of-contents'; +import blogBenchmark from './blog/benchmark'; [ blogMinimalDark, @@ -74,6 +75,7 @@ import blogTableOfContents from './blog/table-of-contents'; blogSeriesPart, blogSummaryCard, blogTableOfContents, + blogBenchmark, ].forEach(registerTemplate); // Product diff --git a/tests/templates/benchmark.test.ts b/tests/templates/benchmark.test.ts new file mode 100644 index 0000000..d40b16a --- /dev/null +++ b/tests/templates/benchmark.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, it } from 'vitest'; +import benchmark from '@/templates/blog/benchmark'; +import { blogBenchmark } from '@/templates/blog'; +import { getTemplate, getTemplatesByCategory } from '@/templates/registry'; +import { mixColors } from '@/templates/utils'; +import type { SatoriElement } from '@/templates/types'; + +function elements(node: unknown): SatoriElement[] { + if (Array.isArray(node)) return node.flatMap(elements); + if (!node || typeof node !== 'object' || !('type' in node)) return []; + + const element = node as SatoriElement; + return [element, ...elements(element.props.children)]; +} + +function textContent(node: unknown): string[] { + if (typeof node === 'string') return [node]; + if (Array.isArray(node)) return node.flatMap(textContent); + if (node && typeof node === 'object' && 'props' in node) { + return textContent((node as SatoriElement).props.children); + } + return []; +} + +const render = (overrides: Record = {}) => + benchmark.render({ ...benchmark.defaults, ...overrides }); +const rows = (tree: unknown) => elements(tree).filter((element) => element.type === 'div' && element.props.style?.minHeight === '68px'); + +describe('blog-benchmark', () => { + it('is re-exported and registered once in the Blog category', () => { + expect(blogBenchmark).toBe(benchmark); + expect(getTemplate('blog-benchmark')).toBe(benchmark); + expect(getTemplatesByCategory('blog').filter(({ id }) => id === 'blog-benchmark')).toHaveLength(1); + }); + + it('renders the label, title, optional note, ordered rows, and formatted values', () => { + const tree = render(); + const text = textContent(tree); + + expect(text).toContain('Benchmark'); + expect(text).toContain('Bun vs Deno vs Node: HTTP throughput'); + expect(text).toContain('Requests per second, higher is better'); + expect(text).toContain('112,000 req/s'); + expect(text).toContain('71,000 req/s'); + expect(text).toContain('48,000 req/s'); + expect(text).toContain('codercops.com'); + expect(rows(tree)).toHaveLength(3); + expect(rows(tree).map((row) => textContent((row.props.children as SatoriElement[])[0])[0])) + .toEqual(['Bun', 'Deno', 'Node']); + }); + + it('scales every bar to the largest value and highlights only the first largest row', () => { + const tree = render({ value1: '500', value2: '250', value3: '125' }); + const rowElements = rows(tree); + const fills = rowElements.map((row) => (row.props.children as SatoriElement[])[1].props.children[0] as SatoriElement); + + expect(fills.map(({ props }) => props.style.width)).toEqual(['100%', '50%', '25%']); + expect(fills[0].props.style.backgroundColor).toBe('#E07A5F'); + expect(fills[1].props.style.backgroundColor).not.toBe('#E07A5F'); + expect(fills[2].props.style.backgroundColor).not.toBe('#E07A5F'); + }); + + it('skips blank names and invalid, negative, or blank values without gaps', () => { + const tree = render({ + name1: ' ', value1: '100', + name2: 'Valid', value2: '25', + name3: 'Negative', value3: '-1', + name4: 'Not a number', value4: 'abc', + }); + + expect(rows(tree)).toHaveLength(1); + expect(textContent(rows(tree)[0])).toEqual(['Valid', '25 req/s']); + }); + + it('draws empty bars for all-zero values without dividing by zero', () => { + const tree = render({ value1: 0, value2: '0', value3: 0 }); + const fills = rows(tree).map((row) => (row.props.children as SatoriElement[])[1].props.children[0] as SatoriElement); + + expect(fills.map(({ props }) => props.style.width)).toEqual(['0%', '0%', '0%']); + expect(JSON.stringify(tree)).not.toContain('NaN'); + }); + + it('hides the chart when every row is skipped and hides an empty note', () => { + const tree = render({ + description: ' ', + name1: '', value1: 10, + name2: 'Invalid', value2: 'Infinity', + name3: 'Negative', value3: -1, + name4: 'Blank', value4: '', + }); + const allText = textContent(tree); + + expect(rows(tree)).toHaveLength(0); + expect(allText).not.toContain(' '); + expect(allText).not.toContain('10 req/s'); + }); + + it('shrinks and truncates long titles, notes, and names', () => { + const tree = render({ + title: 'Benchmark title '.repeat(10), + description: 'Benchmark note '.repeat(12), + name1: 'A very long benchmark name '.repeat(5), + }); + const all = elements(tree); + const title = all.find((element) => element.type === 'h1'); + const note = all.find((element) => element.type === 'span' && textContent(element)[0]?.startsWith('Benchmark note')); + const name = all.find((element) => element.type === 'span' && textContent(element)[0]?.startsWith('A very long benchmark')); + + expect(textContent(title)[0]).toHaveLength(120); + expect(textContent(title)[0]).toMatch(/…$/); + expect(title?.props.style.fontSize).toBe('28px'); + expect(textContent(note)[0]).toHaveLength(120); + expect(note?.props.style.fontSize).toBe('15px'); + expect(textContent(name)[0]).toHaveLength(80); + expect(textContent(name)[0]).toMatch(/…$/); + expect(name?.props.style.fontSize).toBe('14px'); + }); + + it('darkens the label and largest bar accent on a light background', () => { + const tree = render({ bgColor: '#ffffff', accentColor: '#fde047' }); + const accentInk = mixColors('#fde047', '#000000', 0.45); + const all = elements(tree); + const label = all.find((element) => element.type === 'span' && textContent(element)[0] === 'Benchmark'); + const title = all.find((element) => element.type === 'h1'); + const name = rows(tree)[0].props.children[0] as SatoriElement; + const value = rows(tree)[0].props.children[2] as SatoriElement; + const largestFill = (rows(tree)[0].props.children as SatoriElement[])[1].props.children[0] as SatoriElement; + + expect(label?.props.style.color).toBe(accentInk); + expect(largestFill.props.style.backgroundColor).toBe(accentInk); + expect(title?.props.style.color).toBe('#111111'); + expect(name.props.style.color).toBe('#111111'); + expect(value.props.style.color).toBe('#111111'); + }); + + it('omits the unit when it is blank and trims number formatting', () => { + const tree = render({ value1: '1000', unit: ' ' }); + expect(textContent(rows(tree)[0])).toContain('1,000'); + expect(textContent(rows(tree)[0])).not.toContain('1,000 '); + }); +}); From a23614f4e59d646583145f19645911a7865aa5a4 Mon Sep 17 00:00:00 2001 From: Piyush-jangid-01 Date: Thu, 29 Oct 2026 10:59:01 +0530 Subject: [PATCH 2/3] fix(templates): contain benchmark value labels --- src/templates/blog/benchmark.ts | 16 ++++++++++++++-- tests/templates/benchmark.test.ts | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/templates/blog/benchmark.ts b/src/templates/blog/benchmark.ts index 773cbab..7b1ddd0 100644 --- a/src/templates/blog/benchmark.ts +++ b/src/templates/blog/benchmark.ts @@ -86,6 +86,14 @@ const template: TemplateDefinition = { const fillColor = index === largestIndex ? accentInk : mixColors(bgColor, foregroundColor, lightBg ? 0.3 : 0.36); const width = maxValue > 0 ? (value / maxValue) * 100 : 0; const formattedValue = new Intl.NumberFormat('en-US', { maximumFractionDigits: 3 }).format(value); + const valueLabel = `${formattedValue}${unit ? ` ${unit}` : ''}`; + const displayValue = truncate(valueLabel, 36); + const valueSize = autoFontSize(displayValue, [ + { maxLen: 18, size: 20 }, + { maxLen: 26, size: 17 }, + { maxLen: 36, size: 14 }, + ]); + const valueColumnWidth = Math.min(360, Math.max(220, displayValue.length * 9)); return { type: 'div', @@ -121,8 +129,12 @@ const template: TemplateDefinition = { { type: 'span', props: { - style: { display: 'flex', justifyContent: 'flex-end', flexShrink: 0, width: '220px', fontSize: '20px', fontWeight: 700, color: foregroundColor, whiteSpace: 'nowrap' }, - children: `${formattedValue}${unit ? ` ${unit}` : ''}`, + style: { + display: 'flex', justifyContent: 'flex-end', flexShrink: 0, width: `${valueColumnWidth}px`, minWidth: 0, + overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', + fontSize: `${valueSize}px`, fontWeight: 700, color: foregroundColor, + }, + children: displayValue, }, }, ], diff --git a/tests/templates/benchmark.test.ts b/tests/templates/benchmark.test.ts index d40b16a..1d0a3cd 100644 --- a/tests/templates/benchmark.test.ts +++ b/tests/templates/benchmark.test.ts @@ -138,4 +138,18 @@ describe('blog-benchmark', () => { expect(textContent(rows(tree)[0])).toContain('1,000'); expect(textContent(rows(tree)[0])).not.toContain('1,000 '); }); + + it('sizes and truncates long value labels so they stay inside their column', () => { + const tree = render({ value1: '123456789.125', unit: 'requests per second '.repeat(5) }); + const value = rows(tree)[0].props.children[2] as SatoriElement; + + expect(textContent(value)[0]).toMatch(/^123,456,789\.125 requests per second/); + expect(textContent(value)[0]).toMatch(/…$/); + expect(textContent(value)[0]).toHaveLength(36); + expect(value.props.style.width).toBe('324px'); + expect(value.props.style.fontSize).toBe('14px'); + expect(value.props.style.overflow).toBe('hidden'); + expect(value.props.style.textOverflow).toBe('ellipsis'); + expect(value.props.style.whiteSpace).toBe('nowrap'); + }); }); From 200bc356b8b5b8dbf83e9a0c8d6f7d996475718d Mon Sep 17 00:00:00 2001 From: Piyush-jangid-01 Date: Thu, 29 Oct 2026 11:08:05 +0530 Subject: [PATCH 3/3] fix(templates): set benchmark value field ranges --- src/templates/blog/benchmark.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/templates/blog/benchmark.ts b/src/templates/blog/benchmark.ts index 7b1ddd0..a07f6c0 100644 --- a/src/templates/blog/benchmark.ts +++ b/src/templates/blog/benchmark.ts @@ -13,13 +13,13 @@ const template: TemplateDefinition = { commonFields.title, commonFields.description, { key: 'name1', label: 'Name 1', type: 'text', defaultValue: 'Bun', placeholder: 'Bun', group: 'Content' }, - { key: 'value1', label: 'Value 1', type: 'number', defaultValue: 112000, placeholder: '112000', min: 0, group: 'Content' }, + { key: 'value1', label: 'Value 1', type: 'number', defaultValue: 112000, placeholder: '112000', min: 0, max: 1_000_000_000, group: 'Content' }, { key: 'name2', label: 'Name 2', type: 'text', defaultValue: 'Deno', placeholder: 'Deno', group: 'Content' }, - { key: 'value2', label: 'Value 2', type: 'number', defaultValue: 71000, placeholder: '71000', min: 0, group: 'Content' }, + { key: 'value2', label: 'Value 2', type: 'number', defaultValue: 71000, placeholder: '71000', min: 0, max: 1_000_000_000, group: 'Content' }, { key: 'name3', label: 'Name 3', type: 'text', defaultValue: 'Node', placeholder: 'Node', group: 'Content' }, - { key: 'value3', label: 'Value 3', type: 'number', defaultValue: 48000, placeholder: '48000', min: 0, group: 'Content' }, + { key: 'value3', label: 'Value 3', type: 'number', defaultValue: 48000, placeholder: '48000', min: 0, max: 1_000_000_000, group: 'Content' }, { key: 'name4', label: 'Name 4', type: 'text', defaultValue: '', placeholder: 'Optional comparison', group: 'Content' }, - { key: 'value4', label: 'Value 4', type: 'number', defaultValue: '', placeholder: 'Optional value', min: 0, group: 'Content' }, + { key: 'value4', label: 'Value 4', type: 'number', defaultValue: '', placeholder: 'Optional value', min: 0, max: 1_000_000_000, group: 'Content' }, { key: 'unit', label: 'Unit', type: 'text', defaultValue: 'req/s', placeholder: 'req/s', group: 'Content' }, commonFields.siteName, commonFields.bgColor,