@shipload/item-renderer 0.2.2 → 1.0.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/package.json +47 -49
  2. package/src/assets/stardust-base64.ts +1 -1
  3. package/src/errors.ts +14 -14
  4. package/src/fonts/index.ts +23 -24
  5. package/src/fonts/load-bun.ts +11 -11
  6. package/src/index.ts +28 -28
  7. package/src/links.ts +11 -11
  8. package/src/meta.ts +25 -25
  9. package/src/payload/base64url.ts +21 -21
  10. package/src/payload/codec.ts +17 -17
  11. package/src/primitives/category-icon.ts +90 -67
  12. package/src/primitives/compact-row.ts +32 -32
  13. package/src/primitives/divider.ts +14 -14
  14. package/src/primitives/icon-hex.ts +36 -34
  15. package/src/primitives/module-slot.ts +131 -131
  16. package/src/primitives/panel.ts +18 -18
  17. package/src/primitives/quantity-badge.ts +32 -32
  18. package/src/primitives/span-paragraph.ts +55 -57
  19. package/src/primitives/stat-bar.ts +71 -71
  20. package/src/primitives/svg.ts +15 -15
  21. package/src/primitives/text.ts +33 -33
  22. package/src/primitives/wrap.ts +19 -19
  23. package/src/render.ts +21 -25
  24. package/src/templates/_shared.ts +5 -6
  25. package/src/templates/component.ts +123 -121
  26. package/src/templates/index.ts +23 -23
  27. package/src/templates/item-cell.ts +84 -81
  28. package/src/templates/module.ts +177 -174
  29. package/src/templates/packed-entity.ts +22 -24
  30. package/src/templates/resource.ts +134 -134
  31. package/src/templates/ship-panel.ts +120 -121
  32. package/src/templates/social-card.ts +28 -26
  33. package/src/tokens/colors.ts +38 -38
  34. package/src/tokens/index.ts +5 -5
  35. package/src/tokens/spacing.ts +8 -8
  36. package/src/tokens/typography.ts +17 -17
  37. package/.github/workflows/ci.yml +0 -14
  38. package/.gitignore +0 -6
  39. package/Makefile +0 -50
  40. package/biome.json +0 -18
  41. package/bun.lock +0 -123
  42. package/scripts/check-bundle-size.ts +0 -37
  43. package/scripts/copy-fonts.ts +0 -41
  44. package/scripts/preview.ts +0 -43
  45. package/test/__image_snapshots__/.gitkeep +0 -0
  46. package/test/__image_snapshots__/component-hull-plates.diff.png +0 -0
  47. package/test/__image_snapshots__/component-hull-plates.png +0 -0
  48. package/test/__image_snapshots__/module-engine-t1.diff.png +0 -0
  49. package/test/__image_snapshots__/module-engine-t1.png +0 -0
  50. package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
  51. package/test/__image_snapshots__/module-storage-t1.png +0 -0
  52. package/test/__image_snapshots__/packed-entity-ship-t1-only-engine.diff.png +0 -0
  53. package/test/__image_snapshots__/packed-entity-ship-t1-only-engine.png +0 -0
  54. package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
  55. package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.png +0 -0
  56. package/test/__image_snapshots__/resource-ore-t1.diff.png +0 -0
  57. package/test/__image_snapshots__/resource-ore-t1.png +0 -0
  58. package/test/__snapshots__/templates-component.test.ts.snap +0 -5
  59. package/test/__snapshots__/templates-item-cell.test.ts.snap +0 -9
  60. package/test/__snapshots__/templates-module.test.ts.snap +0 -17
  61. package/test/__snapshots__/templates-packed-entity.test.ts.snap +0 -5
  62. package/test/__snapshots__/templates-resource.test.ts.snap +0 -7
  63. package/test/base64url.test.ts +0 -33
  64. package/test/codec.test.ts +0 -43
  65. package/test/errors.test.ts +0 -24
  66. package/test/fixtures/cargo-items.ts +0 -122
  67. package/test/fonts.test.ts +0 -28
  68. package/test/links-meta.test.ts +0 -43
  69. package/test/pixel.test.ts +0 -66
  70. package/test/primitives-category-icon.test.ts +0 -79
  71. package/test/primitives-compact-row.test.ts +0 -44
  72. package/test/primitives-domain.test.ts +0 -72
  73. package/test/primitives-layout.test.ts +0 -56
  74. package/test/primitives-module-slot.test.ts +0 -88
  75. package/test/render.test.ts +0 -40
  76. package/test/sanity.test.ts +0 -6
  77. package/test/sdk-link.test.ts +0 -19
  78. package/test/snapshots/.gitkeep +0 -0
  79. package/test/svg.test.ts +0 -28
  80. package/test/templates-component.test.ts +0 -36
  81. package/test/templates-dispatch.test.ts +0 -35
  82. package/test/templates-item-cell.test.ts +0 -94
  83. package/test/templates-module.test.ts +0 -63
  84. package/test/templates-packed-entity.test.ts +0 -47
  85. package/test/templates-resource.test.ts +0 -71
  86. package/test/templates-ship-panel.test.ts +0 -87
  87. package/test/tokens.test.ts +0 -32
  88. package/tsconfig.json +0 -20
@@ -1,142 +1,144 @@
1
- import type { ResolvedItem, ResourceCategory } from '@shipload/sdk'
2
- import { formatTier, getRecipe, getStatDefinitions, categoryColors } from '@shipload/sdk'
3
- import type { CargoItem } from '../payload/codec.ts'
4
- import { panel } from '../primitives/panel.ts'
5
- import { iconHex } from '../primitives/icon-hex.ts'
6
- import { text } from '../primitives/text.ts'
7
- import { divider } from '../primitives/divider.ts'
8
- import { statBar } from '../primitives/stat-bar.ts'
9
- import { quantityBadge } from '../primitives/quantity-badge.ts'
10
- import { tokens } from '../tokens/index.ts'
11
- import { shortCode, formatMass, tierBorder } from './_shared.ts'
1
+ import type {ResolvedItem, ResourceCategory} from '@shipload/sdk'
2
+ import {formatTier, getRecipe, getStatDefinitions, categoryColors} from '@shipload/sdk'
3
+ import type {CargoItem} from '../payload/codec.ts'
4
+ import {panel} from '../primitives/panel.ts'
5
+ import {iconHex} from '../primitives/icon-hex.ts'
6
+ import {text} from '../primitives/text.ts'
7
+ import {divider} from '../primitives/divider.ts'
8
+ import {statBar} from '../primitives/stat-bar.ts'
9
+ import {quantityBadge} from '../primitives/quantity-badge.ts'
10
+ import {tokens} from '../tokens/index.ts'
11
+ import {shortCode, formatMass, tierBorder} from './_shared.ts'
12
12
 
13
13
  export interface RenderComponentOpts {
14
- mode?: 'values' | 'ranges'
14
+ mode?: 'values' | 'ranges'
15
15
  }
16
16
 
17
17
  type StatRow = {
18
- label: string
19
- abbreviation: string
20
- value: number | null
21
- color: string
22
- inverted?: boolean
18
+ label: string
19
+ abbreviation: string
20
+ value: number | null
21
+ color: string
22
+ inverted?: boolean
23
23
  }
24
24
 
25
25
  export function renderComponent(
26
- item: CargoItem,
27
- resolved: ResolvedItem,
28
- opts?: RenderComponentOpts,
26
+ item: CargoItem,
27
+ resolved: ResolvedItem,
28
+ opts?: RenderComponentOpts
29
29
  ): string {
30
- const mode = opts?.mode ?? 'values'
31
- const w = tokens.spacing.panelWidth
32
- const pad = tokens.spacing.panelPadding
33
- const innerW = w - pad * 2
30
+ const mode = opts?.mode ?? 'values'
31
+ const w = tokens.spacing.panelWidth
32
+ const pad = tokens.spacing.panelPadding
33
+ const innerW = w - pad * 2
34
34
 
35
- let rows: StatRow[]
36
- if (mode === 'values') {
37
- rows = (resolved.stats ?? []).map(s => ({
38
- label: s.label,
39
- abbreviation: s.abbreviation,
40
- value: s.value,
41
- color: s.color,
42
- inverted: s.inverted,
43
- }))
44
- } else {
45
- const recipe = getRecipe(resolved.itemId)
46
- rows = (recipe?.statSlots ?? []).flatMap(slot => {
47
- const src = slot.sources[0]
48
- if (!src) return []
49
- const input = recipe!.inputs[src.inputIndex]
50
- if (!input || !('category' in input)) return []
51
- const category = input.category as ResourceCategory
52
- const def = getStatDefinitions(category)[src.statIndex]
53
- if (!def) return []
54
- return [{
55
- label: def.label,
56
- abbreviation: def.abbreviation,
57
- value: null,
58
- color: categoryColors[category],
59
- inverted: def.inverted,
60
- }]
61
- })
62
- }
35
+ let rows: StatRow[]
36
+ if (mode === 'values') {
37
+ rows = (resolved.stats ?? []).map((s) => ({
38
+ label: s.label,
39
+ abbreviation: s.abbreviation,
40
+ value: s.value,
41
+ color: s.color,
42
+ inverted: s.inverted,
43
+ }))
44
+ } else {
45
+ const recipe = getRecipe(resolved.itemId)
46
+ rows = (recipe?.statSlots ?? []).flatMap((slot) => {
47
+ const src = slot.sources[0]
48
+ if (!src) return []
49
+ const input = recipe!.inputs[src.inputIndex]
50
+ if (!input || !('category' in input)) return []
51
+ const category = input.category as ResourceCategory
52
+ const def = getStatDefinitions(category)[src.statIndex]
53
+ if (!def) return []
54
+ return [
55
+ {
56
+ label: def.label,
57
+ abbreviation: def.abbreviation,
58
+ value: null,
59
+ color: categoryColors[category],
60
+ inverted: def.inverted,
61
+ },
62
+ ]
63
+ })
64
+ }
63
65
 
64
- const headerH = 48
65
- const metaRowH = 28
66
- const statsH = rows.length * 26 + 8
67
- const height = headerH + metaRowH + 14 + statsH + pad
66
+ const headerH = 48
67
+ const metaRowH = 28
68
+ const statsH = rows.length * 26 + 8
69
+ const height = headerH + metaRowH + 14 + statsH + pad
68
70
 
69
- const chrome = panel({ width: w, height, borderColor: tierBorder(resolved.tier) })
71
+ const chrome = panel({width: w, height, borderColor: tierBorder(resolved.tier)})
70
72
 
71
- const quantity = Number(BigInt(item.quantity.toString()))
72
- const badge = quantityBadge({ x: w - pad, y: pad, quantity })
73
+ const quantity = Number(BigInt(item.quantity.toString()))
74
+ const badge = quantityBadge({x: w - pad, y: pad, quantity})
73
75
 
74
- const icon = iconHex({
75
- x: pad,
76
- y: pad + 4,
77
- color: tokens.colors.accent.component,
78
- code: shortCode(resolved.itemId),
79
- })
76
+ const icon = iconHex({
77
+ x: pad,
78
+ y: pad + 4,
79
+ color: tokens.colors.accent.component,
80
+ code: shortCode(resolved.itemId),
81
+ })
80
82
 
81
- const name = text({
82
- x: pad + 34,
83
- y: pad + 22,
84
- value: resolved.name,
85
- size: tokens.typography.sizes.title,
86
- weight: 700,
87
- family: tokens.typography.display,
88
- })
83
+ const name = text({
84
+ x: pad + 34,
85
+ y: pad + 22,
86
+ value: resolved.name,
87
+ size: tokens.typography.sizes.title,
88
+ weight: 700,
89
+ family: tokens.typography.display,
90
+ })
89
91
 
90
- const subtitleText = text({
91
- x: pad,
92
- y: pad + headerH + 4,
93
- value: 'Type',
94
- size: tokens.typography.sizes.body,
95
- color: tokens.colors.text.secondary,
96
- })
97
- const subtitleValue = text({
98
- x: w - pad,
99
- y: pad + headerH + 4,
100
- value: `COMPONENT · ${formatTier(resolved.tier)}`,
101
- size: tokens.typography.sizes.body,
102
- weight: 600,
103
- anchor: 'end',
104
- })
105
- const massLabel = text({
106
- x: pad,
107
- y: pad + headerH + metaRowH - 8,
108
- value: 'Mass',
109
- size: tokens.typography.sizes.body,
110
- color: tokens.colors.text.secondary,
111
- })
112
- const massValue = text({
113
- x: w - pad,
114
- y: pad + headerH + metaRowH - 8,
115
- value: formatMass(resolved.mass),
116
- size: tokens.typography.sizes.body,
117
- weight: 600,
118
- anchor: 'end',
119
- })
92
+ const subtitleText = text({
93
+ x: pad,
94
+ y: pad + headerH + 4,
95
+ value: 'Type',
96
+ size: tokens.typography.sizes.body,
97
+ color: tokens.colors.text.secondary,
98
+ })
99
+ const subtitleValue = text({
100
+ x: w - pad,
101
+ y: pad + headerH + 4,
102
+ value: `COMPONENT · ${formatTier(resolved.tier)}`,
103
+ size: tokens.typography.sizes.body,
104
+ weight: 600,
105
+ anchor: 'end',
106
+ })
107
+ const massLabel = text({
108
+ x: pad,
109
+ y: pad + headerH + metaRowH - 8,
110
+ value: 'Mass',
111
+ size: tokens.typography.sizes.body,
112
+ color: tokens.colors.text.secondary,
113
+ })
114
+ const massValue = text({
115
+ x: w - pad,
116
+ y: pad + headerH + metaRowH - 8,
117
+ value: formatMass(resolved.mass),
118
+ size: tokens.typography.sizes.body,
119
+ weight: 600,
120
+ anchor: 'end',
121
+ })
120
122
 
121
- const sepY = pad + headerH + metaRowH + 6
122
- const sep = divider({ x: pad, y: sepY, width: innerW })
123
+ const sepY = pad + headerH + metaRowH + 6
124
+ const sep = divider({x: pad, y: sepY, width: innerW})
123
125
 
124
- const statsSvg = rows
125
- .map((row, i) =>
126
- statBar({
127
- x: pad,
128
- y: sepY + 18 + i * 26,
129
- width: innerW,
130
- label: row.label,
131
- abbreviation: row.abbreviation,
132
- value: row.value,
133
- color: row.color,
134
- inverted: row.inverted,
135
- }),
136
- )
137
- .join('')
126
+ const statsSvg = rows
127
+ .map((row, i) =>
128
+ statBar({
129
+ x: pad,
130
+ y: sepY + 18 + i * 26,
131
+ width: innerW,
132
+ label: row.label,
133
+ abbreviation: row.abbreviation,
134
+ value: row.value,
135
+ color: row.color,
136
+ inverted: row.inverted,
137
+ })
138
+ )
139
+ .join('')
138
140
 
139
- const inner = `${chrome}${icon}${name}${badge}${subtitleText}${subtitleValue}${massLabel}${massValue}${sep}${statsSvg}`
141
+ const inner = `${chrome}${icon}${name}${badge}${subtitleText}${subtitleValue}${massLabel}${massValue}${sep}${statsSvg}`
140
142
 
141
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`
143
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`
142
144
  }
@@ -1,30 +1,30 @@
1
- import type { ResolvedItem } from '@shipload/sdk'
2
- import type { CargoItem } from '../payload/codec.ts'
3
- import { RenderError } from '../errors.ts'
4
- import { renderResource } from './resource.ts'
5
- import { renderPackedEntity } from './packed-entity.ts'
6
- import { renderComponent } from './component.ts'
7
- import { renderModule } from './module.ts'
1
+ import type {ResolvedItem} from '@shipload/sdk'
2
+ import type {CargoItem} from '../payload/codec.ts'
3
+ import {RenderError} from '../errors.ts'
4
+ import {renderResource} from './resource.ts'
5
+ import {renderPackedEntity} from './packed-entity.ts'
6
+ import {renderComponent} from './component.ts'
7
+ import {renderModule} from './module.ts'
8
8
 
9
9
  export interface RenderByTypeOpts {
10
- mode?: 'values' | 'ranges'
10
+ mode?: 'values' | 'ranges'
11
11
  }
12
12
 
13
13
  export function renderByType(
14
- item: CargoItem,
15
- resolved: ResolvedItem,
16
- opts?: RenderByTypeOpts,
14
+ item: CargoItem,
15
+ resolved: ResolvedItem,
16
+ opts?: RenderByTypeOpts
17
17
  ): string {
18
- switch (resolved.itemType) {
19
- case 'resource':
20
- return renderResource(item, resolved, opts)
21
- case 'entity':
22
- return renderPackedEntity(item, resolved)
23
- case 'component':
24
- return renderComponent(item, resolved, opts)
25
- case 'module':
26
- return renderModule(item, resolved, opts)
27
- default:
28
- throw new RenderError(`unknown itemType '${String(resolved.itemType)}'`)
29
- }
18
+ switch (resolved.itemType) {
19
+ case 'resource':
20
+ return renderResource(item, resolved, opts)
21
+ case 'entity':
22
+ return renderPackedEntity(item, resolved)
23
+ case 'component':
24
+ return renderComponent(item, resolved, opts)
25
+ case 'module':
26
+ return renderModule(item, resolved, opts)
27
+ default:
28
+ throw new RenderError(`unknown itemType '${String(resolved.itemType)}'`)
29
+ }
30
30
  }
@@ -1,100 +1,103 @@
1
- import type { ResolvedItem, ResourceTier } from '@shipload/sdk'
2
- import { tierColors, categoryColors, categoryIconShapes } from '@shipload/sdk'
3
- import { el } from '../primitives/svg.ts'
4
- import { text } from '../primitives/text.ts'
5
- import { categoryIconPath } from '../primitives/category-icon.ts'
6
- import { tokens } from '../tokens/index.ts'
7
-
8
- function tierKey(tier: number): ResourceTier {
9
- return `t${tier}` as ResourceTier
10
- }
1
+ import type {ResolvedItem} from '@shipload/sdk'
2
+ import {tierColors, categoryColors, categoryIconShapes} from '@shipload/sdk'
3
+ import {el} from '../primitives/svg.ts'
4
+ import {text} from '../primitives/text.ts'
5
+ import {categoryIconPath} from '../primitives/category-icon.ts'
6
+ import {tokens} from '../tokens/index.ts'
11
7
 
12
8
  export interface ItemCellProps {
13
- resolved: ResolvedItem
14
- quantity?: number
15
- size?: number
9
+ resolved: ResolvedItem
10
+ quantity?: number
11
+ size?: number
16
12
  }
17
13
 
18
14
  export interface ItemCellGroupProps extends ItemCellProps {
19
- x: number
20
- y: number
15
+ x: number
16
+ y: number
21
17
  }
22
18
 
23
19
  function cellInner(props: ItemCellProps): string {
24
- const size = props.size ?? 48
25
- const height = Math.round(size * 1.25)
26
- const r = Math.max(4, Math.round(size * 0.12))
27
- const cx = size / 2
28
-
29
- const border = el('rect', {
30
- x: 0.5,
31
- y: 0.5,
32
- width: size - 1,
33
- height: height - 1,
34
- rx: r,
35
- ry: r,
36
- fill: tokens.colors.surface.panel,
37
- stroke: tierColors[tierKey(props.resolved.tier)] ?? tokens.colors.surface.panelBorder,
38
- 'stroke-width': 1.5,
39
- })
20
+ const size = props.size ?? 48
21
+ const height = Math.round(size * 1.25)
22
+ const r = Math.max(4, Math.round(size * 0.12))
23
+ const cx = size / 2
40
24
 
41
- let content = ''
42
- if (props.resolved.abbreviation) {
43
- const iconCy = size * 0.45
44
- content = text({
45
- x: cx,
46
- y: iconCy,
47
- value: props.resolved.abbreviation,
48
- size: Math.round(size * 0.28),
49
- weight: 700,
50
- anchor: 'middle',
51
- color: tokens.colors.text.primary,
52
- family: tokens.typography.display,
25
+ const border = el('rect', {
26
+ x: 0.5,
27
+ y: 0.5,
28
+ width: size - 1,
29
+ height: height - 1,
30
+ rx: r,
31
+ ry: r,
32
+ fill: tokens.colors.surface.panel,
33
+ stroke: tierColors[props.resolved.tier] ?? tokens.colors.surface.panelBorder,
34
+ 'stroke-width': 1.5,
53
35
  })
54
- } else if (props.resolved.category) {
55
- const shape = categoryIconShapes[props.resolved.category]
56
- const color = categoryColors[props.resolved.category]
57
- const iconCy = size * 0.4
58
- content = categoryIconPath({ shape, cx, cy: iconCy, size: size * 0.32, color, strokeWidth: 1.5 })
59
- } else if (props.resolved.icon) {
60
- content = text({
61
- x: cx,
62
- y: size * 0.4,
63
- value: props.resolved.icon,
64
- size: Math.round(size * 0.44),
65
- weight: 400,
66
- anchor: 'middle',
67
- dominantBaseline: 'central',
68
- color: tokens.colors.text.primary,
69
- })
70
- }
71
36
 
72
- const qty = props.quantity ?? 0
73
- let quantityText = ''
74
- if (qty > 1) {
75
- const qtyFontSize = Math.max(9, Math.round(size * 0.22))
76
- const qtyPad = Math.max(3, Math.round(size * 0.12))
77
- quantityText = text({
78
- x: size - qtyPad,
79
- y: height - qtyPad,
80
- value: String(qty),
81
- size: qtyFontSize,
82
- weight: 700,
83
- anchor: 'end',
84
- color: tokens.colors.text.primary,
85
- family: tokens.typography.display,
86
- })
87
- }
37
+ let content = ''
38
+ if (props.resolved.abbreviation) {
39
+ const iconCy = size * 0.45
40
+ content = text({
41
+ x: cx,
42
+ y: iconCy,
43
+ value: props.resolved.abbreviation,
44
+ size: Math.round(size * 0.28),
45
+ weight: 700,
46
+ anchor: 'middle',
47
+ color: tokens.colors.text.primary,
48
+ family: tokens.typography.display,
49
+ })
50
+ } else if (props.resolved.category) {
51
+ const shape = categoryIconShapes[props.resolved.category]
52
+ const color = categoryColors[props.resolved.category]
53
+ const iconCy = size * 0.4
54
+ content = categoryIconPath({
55
+ shape,
56
+ cx,
57
+ cy: iconCy,
58
+ size: size * 0.32,
59
+ color,
60
+ strokeWidth: 1.5,
61
+ })
62
+ } else if (props.resolved.icon) {
63
+ content = text({
64
+ x: cx,
65
+ y: size * 0.4,
66
+ value: props.resolved.icon,
67
+ size: Math.round(size * 0.44),
68
+ weight: 400,
69
+ anchor: 'middle',
70
+ dominantBaseline: 'central',
71
+ color: tokens.colors.text.primary,
72
+ })
73
+ }
74
+
75
+ const qty = props.quantity ?? 0
76
+ let quantityText = ''
77
+ if (qty > 1) {
78
+ const qtyFontSize = Math.max(9, Math.round(size * 0.22))
79
+ const qtyPad = Math.max(3, Math.round(size * 0.12))
80
+ quantityText = text({
81
+ x: size - qtyPad,
82
+ y: height - qtyPad,
83
+ value: String(qty),
84
+ size: qtyFontSize,
85
+ weight: 700,
86
+ anchor: 'end',
87
+ color: tokens.colors.text.primary,
88
+ family: tokens.typography.display,
89
+ })
90
+ }
88
91
 
89
- return border + content + quantityText
92
+ return border + content + quantityText
90
93
  }
91
94
 
92
95
  export function itemCellGroup(props: ItemCellGroupProps): string {
93
- return `<g transform="translate(${props.x}, ${props.y})">${cellInner(props)}</g>`
96
+ return `<g transform="translate(${props.x}, ${props.y})">${cellInner(props)}</g>`
94
97
  }
95
98
 
96
99
  export function renderItemCell(props: ItemCellProps): string {
97
- const size = props.size ?? 48
98
- const height = Math.round(size * 1.25)
99
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${height}" viewBox="0 0 ${size} ${height}">${cellInner(props)}</svg>`
100
+ const size = props.size ?? 48
101
+ const height = Math.round(size * 1.25)
102
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${height}" viewBox="0 0 ${size} ${height}">${cellInner(props)}</svg>`
100
103
  }