@shipload/item-renderer 0.2.1 → 0.2.3
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.
- package/.claude/settings.local.json +6 -0
- package/bun.lock +2 -2
- package/package.json +8 -4
- package/scripts/check-bundle-size.ts +21 -21
- package/scripts/copy-fonts.ts +19 -19
- package/scripts/preview.ts +13 -15
- package/src/assets/stardust-base64.ts +1 -1
- package/src/errors.ts +8 -8
- package/src/fonts/index.ts +25 -26
- package/src/fonts/load-bun.ts +9 -9
- package/src/index.ts +21 -21
- package/src/links.ts +11 -11
- package/src/meta.ts +16 -16
- package/src/payload/base64url.ts +16 -16
- package/src/payload/codec.ts +13 -13
- package/src/primitives/category-icon.ts +69 -48
- package/src/primitives/compact-row.ts +13 -13
- package/src/primitives/divider.ts +9 -9
- package/src/primitives/icon-hex.ts +18 -16
- package/src/primitives/module-slot.ts +73 -73
- package/src/primitives/panel.ts +10 -10
- package/src/primitives/quantity-badge.ts +13 -13
- package/src/primitives/span-paragraph.ts +48 -50
- package/src/primitives/stat-bar.ts +24 -24
- package/src/primitives/svg.ts +13 -13
- package/src/primitives/text.ts +25 -25
- package/src/primitives/wrap.ts +12 -12
- package/src/render.ts +15 -19
- package/src/templates/_shared.ts +6 -7
- package/src/templates/component.ts +68 -63
- package/src/templates/index.ts +17 -17
- package/src/templates/item-cell.ts +48 -41
- package/src/templates/module.ts +84 -83
- package/src/templates/packed-entity.ts +12 -14
- package/src/templates/resource.ts +63 -65
- package/src/templates/ship-panel.ts +67 -72
- package/src/templates/social-card.ts +27 -25
- package/src/tokens/colors.ts +29 -29
- package/src/tokens/index.ts +6 -6
- package/src/tokens/spacing.ts +1 -1
- package/src/tokens/typography.ts +1 -1
- package/test/__image_snapshots__/component-hull-plates.diff.png +0 -0
- package/test/__image_snapshots__/module-engine-t1.diff.png +0 -0
- package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
- package/test/__image_snapshots__/packed-entity-ship-t1-only-engine.diff.png +0 -0
- package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
- package/test/__image_snapshots__/resource-ore-t1.diff.png +0 -0
- package/test/base64url.test.ts +22 -22
- package/test/codec.test.ts +26 -35
- package/test/errors.test.ts +21 -21
- package/test/fixtures/cargo-items.ts +43 -43
- package/test/fonts.test.ts +23 -23
- package/test/links-meta.test.ts +37 -37
- package/test/pixel.test.ts +44 -41
- package/test/primitives-category-icon.test.ts +74 -67
- package/test/primitives-compact-row.test.ts +29 -29
- package/test/primitives-domain.test.ts +61 -50
- package/test/primitives-layout.test.ts +47 -47
- package/test/primitives-module-slot.test.ts +58 -58
- package/test/render.test.ts +38 -35
- package/test/sanity.test.ts +5 -5
- package/test/sdk-link.test.ts +13 -13
- package/test/svg.test.ts +24 -22
- package/test/templates-component.test.ts +32 -32
- package/test/templates-dispatch.test.ts +29 -29
- package/test/templates-item-cell.test.ts +79 -79
- package/test/templates-module.test.ts +52 -52
- package/test/templates-packed-entity.test.ts +42 -42
- package/test/templates-resource.test.ts +61 -61
- package/test/templates-ship-panel.test.ts +69 -65
- package/test/tokens.test.ts +28 -26
|
@@ -1,90 +1,88 @@
|
|
|
1
|
-
import type { ResolvedItem } from
|
|
2
|
-
import { getStatDefinitions, categoryColors, displayName } from
|
|
3
|
-
import type { CargoItem } from
|
|
4
|
-
import { panel } from
|
|
5
|
-
import { iconHex } from
|
|
6
|
-
import { text } from
|
|
7
|
-
import { divider } from
|
|
8
|
-
import { statBar } from
|
|
9
|
-
import { quantityBadge } from
|
|
10
|
-
import { tokens } from
|
|
11
|
-
import { shortCode, formatMass, tierBorder } from
|
|
1
|
+
import type { ResolvedItem } from "@shipload/sdk";
|
|
2
|
+
import { getStatDefinitions, categoryColors, displayName } 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
|
const CATEGORY_LABELS: Record<string, string> = {
|
|
14
|
-
ore:
|
|
15
|
-
crystal:
|
|
16
|
-
gas:
|
|
17
|
-
regolith:
|
|
18
|
-
biomass:
|
|
19
|
-
}
|
|
14
|
+
ore: "Ore",
|
|
15
|
+
crystal: "Crystal",
|
|
16
|
+
gas: "Gas",
|
|
17
|
+
regolith: "Regolith",
|
|
18
|
+
biomass: "Biomass",
|
|
19
|
+
};
|
|
20
20
|
|
|
21
21
|
function categoryColor(category?: string): string {
|
|
22
|
-
if (!category) return tokens.colors.text.muted
|
|
23
|
-
const key = category as keyof typeof tokens.colors.category
|
|
24
|
-
return tokens.colors.category[key] ?? tokens.colors.text.muted
|
|
22
|
+
if (!category) return tokens.colors.text.muted;
|
|
23
|
+
const key = category as keyof typeof tokens.colors.category;
|
|
24
|
+
return tokens.colors.category[key] ?? tokens.colors.text.muted;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface RenderResourceOpts {
|
|
28
|
-
mode?:
|
|
28
|
+
mode?: "values" | "ranges";
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
type StatRow = {
|
|
32
|
-
label: string
|
|
33
|
-
abbreviation: string
|
|
34
|
-
value: number | null
|
|
35
|
-
color: string
|
|
36
|
-
inverted?: boolean
|
|
37
|
-
}
|
|
32
|
+
label: string;
|
|
33
|
+
abbreviation: string;
|
|
34
|
+
value: number | null;
|
|
35
|
+
color: string;
|
|
36
|
+
inverted?: boolean;
|
|
37
|
+
};
|
|
38
38
|
|
|
39
39
|
export function renderResource(
|
|
40
40
|
item: CargoItem,
|
|
41
41
|
resolved: ResolvedItem,
|
|
42
42
|
opts?: RenderResourceOpts,
|
|
43
43
|
): string {
|
|
44
|
-
const mode = opts?.mode ??
|
|
45
|
-
const w = tokens.spacing.panelWidth
|
|
46
|
-
const pad = tokens.spacing.panelPadding
|
|
47
|
-
const innerW = w - pad * 2
|
|
48
|
-
|
|
49
|
-
let rows: StatRow[]
|
|
50
|
-
if (mode ===
|
|
51
|
-
rows = (resolved.stats ?? []).map(s => ({
|
|
44
|
+
const mode = opts?.mode ?? "values";
|
|
45
|
+
const w = tokens.spacing.panelWidth;
|
|
46
|
+
const pad = tokens.spacing.panelPadding;
|
|
47
|
+
const innerW = w - pad * 2;
|
|
48
|
+
|
|
49
|
+
let rows: StatRow[];
|
|
50
|
+
if (mode === "values") {
|
|
51
|
+
rows = (resolved.stats ?? []).map((s) => ({
|
|
52
52
|
label: s.label,
|
|
53
53
|
abbreviation: s.abbreviation,
|
|
54
54
|
value: s.value,
|
|
55
55
|
color: s.color,
|
|
56
56
|
inverted: s.inverted,
|
|
57
|
-
}))
|
|
57
|
+
}));
|
|
58
58
|
} else {
|
|
59
|
-
const defs = resolved.category ? getStatDefinitions(resolved.category) : []
|
|
60
|
-
const color = resolved.category
|
|
61
|
-
|
|
62
|
-
: tokens.colors.text.muted
|
|
63
|
-
rows = defs.map(d => ({
|
|
59
|
+
const defs = resolved.category ? getStatDefinitions(resolved.category) : [];
|
|
60
|
+
const color = resolved.category ? categoryColors[resolved.category] : tokens.colors.text.muted;
|
|
61
|
+
rows = defs.map((d) => ({
|
|
64
62
|
label: d.label,
|
|
65
63
|
abbreviation: d.abbreviation,
|
|
66
64
|
value: null,
|
|
67
65
|
color,
|
|
68
66
|
inverted: d.inverted,
|
|
69
|
-
}))
|
|
67
|
+
}));
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
const headerH = 48
|
|
73
|
-
const metaRowH = 28
|
|
74
|
-
const statsH = rows.length * 26 + 8
|
|
75
|
-
const height = headerH + metaRowH + 14 + statsH + pad
|
|
70
|
+
const headerH = 48;
|
|
71
|
+
const metaRowH = 28;
|
|
72
|
+
const statsH = rows.length * 26 + 8;
|
|
73
|
+
const height = headerH + metaRowH + 14 + statsH + pad;
|
|
76
74
|
|
|
77
|
-
const chrome = panel({ width: w, height, borderColor: tierBorder(resolved.tier) })
|
|
75
|
+
const chrome = panel({ width: w, height, borderColor: tierBorder(resolved.tier) });
|
|
78
76
|
|
|
79
|
-
const quantity = Number(BigInt(item.quantity.toString()))
|
|
80
|
-
const badge = quantityBadge({ x: w - pad, y: pad, quantity })
|
|
77
|
+
const quantity = Number(BigInt(item.quantity.toString()));
|
|
78
|
+
const badge = quantityBadge({ x: w - pad, y: pad, quantity });
|
|
81
79
|
|
|
82
80
|
const icon = iconHex({
|
|
83
81
|
x: pad,
|
|
84
82
|
y: pad + 4,
|
|
85
83
|
color: categoryColor(resolved.category),
|
|
86
84
|
code: shortCode(resolved.itemId),
|
|
87
|
-
})
|
|
85
|
+
});
|
|
88
86
|
|
|
89
87
|
const name = text({
|
|
90
88
|
x: pad + 34,
|
|
@@ -93,42 +91,42 @@ export function renderResource(
|
|
|
93
91
|
size: tokens.typography.sizes.title,
|
|
94
92
|
weight: 700,
|
|
95
93
|
family: tokens.typography.display,
|
|
96
|
-
})
|
|
94
|
+
});
|
|
97
95
|
|
|
98
|
-
const catLabel = resolved.category ? (CATEGORY_LABELS[resolved.category] ??
|
|
96
|
+
const catLabel = resolved.category ? (CATEGORY_LABELS[resolved.category] ?? "Item") : "Item";
|
|
99
97
|
const catText = text({
|
|
100
98
|
x: pad,
|
|
101
99
|
y: pad + headerH + 4,
|
|
102
|
-
value:
|
|
100
|
+
value: "Category",
|
|
103
101
|
size: tokens.typography.sizes.body,
|
|
104
102
|
color: tokens.colors.text.secondary,
|
|
105
|
-
})
|
|
103
|
+
});
|
|
106
104
|
const catValue = text({
|
|
107
105
|
x: w - pad,
|
|
108
106
|
y: pad + headerH + 4,
|
|
109
107
|
value: catLabel,
|
|
110
108
|
size: tokens.typography.sizes.body,
|
|
111
109
|
weight: 600,
|
|
112
|
-
anchor:
|
|
113
|
-
})
|
|
110
|
+
anchor: "end",
|
|
111
|
+
});
|
|
114
112
|
const massLabel = text({
|
|
115
113
|
x: pad,
|
|
116
114
|
y: pad + headerH + metaRowH - 8,
|
|
117
|
-
value:
|
|
115
|
+
value: "Mass",
|
|
118
116
|
size: tokens.typography.sizes.body,
|
|
119
117
|
color: tokens.colors.text.secondary,
|
|
120
|
-
})
|
|
118
|
+
});
|
|
121
119
|
const massValue = text({
|
|
122
120
|
x: w - pad,
|
|
123
121
|
y: pad + headerH + metaRowH - 8,
|
|
124
122
|
value: formatMass(resolved.mass),
|
|
125
123
|
size: tokens.typography.sizes.body,
|
|
126
124
|
weight: 600,
|
|
127
|
-
anchor:
|
|
128
|
-
})
|
|
125
|
+
anchor: "end",
|
|
126
|
+
});
|
|
129
127
|
|
|
130
|
-
const sepY = pad + headerH + metaRowH + 6
|
|
131
|
-
const sep = divider({ x: pad, y: sepY, width: innerW })
|
|
128
|
+
const sepY = pad + headerH + metaRowH + 6;
|
|
129
|
+
const sep = divider({ x: pad, y: sepY, width: innerW });
|
|
132
130
|
|
|
133
131
|
const statsSvg = rows
|
|
134
132
|
.map((row, i) =>
|
|
@@ -143,9 +141,9 @@ export function renderResource(
|
|
|
143
141
|
inverted: row.inverted,
|
|
144
142
|
}),
|
|
145
143
|
)
|
|
146
|
-
.join(
|
|
144
|
+
.join("");
|
|
147
145
|
|
|
148
|
-
const inner = `${chrome}${icon}${name}${badge}${catText}${catValue}${massLabel}${massValue}${sep}${statsSvg}
|
|
146
|
+
const inner = `${chrome}${icon}${name}${badge}${catText}${catValue}${massLabel}${massValue}${sep}${statsSvg}`;
|
|
149
147
|
|
|
150
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg
|
|
148
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`;
|
|
151
149
|
}
|
|
@@ -1,84 +1,74 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { panel } from
|
|
3
|
-
import { iconHex } from
|
|
4
|
-
import { text } from
|
|
5
|
-
import { divider } from
|
|
6
|
-
import { moduleSlot } from
|
|
7
|
-
import { quantityBadge } from
|
|
8
|
-
import { wrapText } from
|
|
9
|
-
import { tokens } from
|
|
1
|
+
import type { TextSpan } from "@shipload/sdk";
|
|
2
|
+
import { panel } from "../primitives/panel.ts";
|
|
3
|
+
import { iconHex } from "../primitives/icon-hex.ts";
|
|
4
|
+
import { text } from "../primitives/text.ts";
|
|
5
|
+
import { divider } from "../primitives/divider.ts";
|
|
6
|
+
import { moduleSlot } from "../primitives/module-slot.ts";
|
|
7
|
+
import { quantityBadge } from "../primitives/quantity-badge.ts";
|
|
8
|
+
import { wrapText } from "../primitives/wrap.ts";
|
|
9
|
+
import { tokens } from "../tokens/index.ts";
|
|
10
10
|
|
|
11
11
|
export interface ShipPanelSlot {
|
|
12
|
-
name?: string
|
|
13
|
-
installed: boolean
|
|
14
|
-
description?: string | TextSpan[]
|
|
12
|
+
name?: string;
|
|
13
|
+
installed: boolean;
|
|
14
|
+
description?: string | TextSpan[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface ShipPanelProps {
|
|
18
|
-
name: string
|
|
19
|
-
tier:
|
|
20
|
-
quantity?: number
|
|
21
|
-
attributes: { capability: string; attributes: { label: string; value: number }[] }[]
|
|
22
|
-
slots: ShipPanelSlot[]
|
|
18
|
+
name: string;
|
|
19
|
+
tier: number;
|
|
20
|
+
quantity?: number;
|
|
21
|
+
attributes: { capability: string; attributes: { label: string; value: number }[] }[];
|
|
22
|
+
slots: ShipPanelSlot[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function formatNumber(n: number): string {
|
|
26
|
-
return n.toLocaleString(
|
|
26
|
+
return n.toLocaleString("en-US");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function tierBorder(tier:
|
|
30
|
-
|
|
31
|
-
return tokens.colors.tier[key] ?? tokens.colors.surface.panelBorder
|
|
29
|
+
function tierBorder(tier: number): string {
|
|
30
|
+
return tokens.colors.tier[tier] ?? tokens.colors.surface.panelBorder;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
const MODULE_LABEL_PREFIX = (capability: string) => `${capability}:
|
|
33
|
+
const MODULE_LABEL_PREFIX = (capability: string) => `${capability}: `;
|
|
35
34
|
|
|
36
35
|
function rowHeightFor(slot: ShipPanelSlot): number {
|
|
37
|
-
if (!slot.installed) return 24
|
|
38
|
-
const desc = slot.description
|
|
36
|
+
if (!slot.installed) return 24;
|
|
37
|
+
const desc = slot.description;
|
|
39
38
|
const plain =
|
|
40
|
-
typeof desc ===
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (plain.length === 0) return 24
|
|
46
|
-
const combined = MODULE_LABEL_PREFIX(slot.name ?? 'Module') + plain
|
|
47
|
-
const lineCount = Math.max(1, wrapText({ value: combined, charsPerLine: 36 }).length)
|
|
48
|
-
return 10 + lineCount * 14
|
|
39
|
+
typeof desc === "string" ? desc : Array.isArray(desc) ? desc.map((s) => s.text).join("") : "";
|
|
40
|
+
if (plain.length === 0) return 24;
|
|
41
|
+
const combined = MODULE_LABEL_PREFIX(slot.name ?? "Module") + plain;
|
|
42
|
+
const lineCount = Math.max(1, wrapText({ value: combined, charsPerLine: 36 }).length);
|
|
43
|
+
return 10 + lineCount * 14;
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
export function renderShipPanel(props: ShipPanelProps): string {
|
|
52
|
-
const w = tokens.spacing.panelWidth
|
|
53
|
-
const pad = tokens.spacing.panelPadding
|
|
54
|
-
const innerW = w - pad * 2
|
|
55
|
-
const quantity = props.quantity ?? 0
|
|
56
|
-
|
|
57
|
-
const hullGroup = props.attributes?.find((g) => g.capability.toLowerCase() ===
|
|
58
|
-
const hullRows = hullGroup?.attributes ?? []
|
|
59
|
-
|
|
60
|
-
const headerH = 48
|
|
61
|
-
const hullHeaderH = 20
|
|
62
|
-
const hullRowH = 22
|
|
63
|
-
const sectionGap = 12
|
|
64
|
-
const rowHeights = props.slots.map(rowHeightFor)
|
|
65
|
-
const modulesHeight = rowHeights.reduce((a, b) => a + b, 0)
|
|
47
|
+
const w = tokens.spacing.panelWidth;
|
|
48
|
+
const pad = tokens.spacing.panelPadding;
|
|
49
|
+
const innerW = w - pad * 2;
|
|
50
|
+
const quantity = props.quantity ?? 0;
|
|
51
|
+
|
|
52
|
+
const hullGroup = props.attributes?.find((g) => g.capability.toLowerCase() === "hull");
|
|
53
|
+
const hullRows = hullGroup?.attributes ?? [];
|
|
54
|
+
|
|
55
|
+
const headerH = 48;
|
|
56
|
+
const hullHeaderH = 20;
|
|
57
|
+
const hullRowH = 22;
|
|
58
|
+
const sectionGap = 12;
|
|
59
|
+
const rowHeights = props.slots.map(rowHeightFor);
|
|
60
|
+
const modulesHeight = rowHeights.reduce((a, b) => a + b, 0);
|
|
66
61
|
const height =
|
|
67
|
-
headerH +
|
|
68
|
-
hullHeaderH +
|
|
69
|
-
hullRows.length * hullRowH +
|
|
70
|
-
sectionGap +
|
|
71
|
-
modulesHeight +
|
|
72
|
-
pad
|
|
62
|
+
headerH + hullHeaderH + hullRows.length * hullRowH + sectionGap + modulesHeight + pad;
|
|
73
63
|
|
|
74
|
-
const chrome = panel({ width: w, height, borderColor: tierBorder(props.tier) })
|
|
64
|
+
const chrome = panel({ width: w, height, borderColor: tierBorder(props.tier) });
|
|
75
65
|
|
|
76
66
|
const icon = iconHex({
|
|
77
67
|
x: pad,
|
|
78
68
|
y: pad + 4,
|
|
79
69
|
color: tokens.colors.text.accent,
|
|
80
|
-
code:
|
|
81
|
-
})
|
|
70
|
+
code: "SH",
|
|
71
|
+
});
|
|
82
72
|
|
|
83
73
|
const name = text({
|
|
84
74
|
x: pad + 34,
|
|
@@ -87,22 +77,22 @@ export function renderShipPanel(props: ShipPanelProps): string {
|
|
|
87
77
|
size: tokens.typography.sizes.title,
|
|
88
78
|
weight: 700,
|
|
89
79
|
family: tokens.typography.display,
|
|
90
|
-
})
|
|
80
|
+
});
|
|
91
81
|
|
|
92
|
-
const badge = quantityBadge({ x: w - pad, y: pad, quantity })
|
|
82
|
+
const badge = quantityBadge({ x: w - pad, y: pad, quantity });
|
|
93
83
|
|
|
94
84
|
const hullHeader = text({
|
|
95
85
|
x: pad,
|
|
96
86
|
y: pad + headerH,
|
|
97
|
-
value:
|
|
87
|
+
value: "HULL",
|
|
98
88
|
size: tokens.typography.sizes.subtitle,
|
|
99
89
|
weight: 700,
|
|
100
90
|
color: tokens.colors.text.secondary,
|
|
101
91
|
letterSpacing: 1,
|
|
102
|
-
})
|
|
92
|
+
});
|
|
103
93
|
|
|
104
|
-
let y = pad + headerH + 6
|
|
105
|
-
let hullSvg =
|
|
94
|
+
let y = pad + headerH + 6;
|
|
95
|
+
let hullSvg = "";
|
|
106
96
|
for (const row of hullRows) {
|
|
107
97
|
hullSvg +=
|
|
108
98
|
text({
|
|
@@ -118,16 +108,21 @@ export function renderShipPanel(props: ShipPanelProps): string {
|
|
|
118
108
|
value: formatNumber(row.value),
|
|
119
109
|
size: tokens.typography.sizes.body,
|
|
120
110
|
weight: 700,
|
|
121
|
-
anchor:
|
|
111
|
+
anchor: "end",
|
|
122
112
|
}) +
|
|
123
|
-
divider({
|
|
124
|
-
|
|
113
|
+
divider({
|
|
114
|
+
x: pad,
|
|
115
|
+
y: y + hullRowH - 4,
|
|
116
|
+
width: innerW,
|
|
117
|
+
color: tokens.colors.surface.panelBorderBright,
|
|
118
|
+
});
|
|
119
|
+
y += hullRowH;
|
|
125
120
|
}
|
|
126
121
|
|
|
127
|
-
y += sectionGap
|
|
128
|
-
let modulesSvg =
|
|
122
|
+
y += sectionGap;
|
|
123
|
+
let modulesSvg = "";
|
|
129
124
|
for (let i = 0; i < props.slots.length; i++) {
|
|
130
|
-
const slot = props.slots[i]
|
|
125
|
+
const slot = props.slots[i]!;
|
|
131
126
|
modulesSvg += moduleSlot({
|
|
132
127
|
x: pad,
|
|
133
128
|
y,
|
|
@@ -136,10 +131,10 @@ export function renderShipPanel(props: ShipPanelProps): string {
|
|
|
136
131
|
capability: slot.name,
|
|
137
132
|
description: slot.description,
|
|
138
133
|
accentColor: tokens.colors.brand.teal,
|
|
139
|
-
})
|
|
140
|
-
y += rowHeights[i]
|
|
134
|
+
});
|
|
135
|
+
y += rowHeights[i]!;
|
|
141
136
|
}
|
|
142
137
|
|
|
143
|
-
const inner = `${chrome}${icon}${name}${badge}${hullHeader}${hullSvg}${modulesSvg}
|
|
144
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg
|
|
138
|
+
const inner = `${chrome}${icon}${name}${badge}${hullHeader}${hullSvg}${modulesSvg}`;
|
|
139
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`;
|
|
145
140
|
}
|
|
@@ -1,42 +1,44 @@
|
|
|
1
|
-
import type { ResolvedItem } from
|
|
2
|
-
import type { CargoItem } from
|
|
3
|
-
import { renderByType } from
|
|
4
|
-
import { STARDUST_BASE64 } from
|
|
5
|
-
import { svgDimensions } from
|
|
1
|
+
import type { ResolvedItem } from "@shipload/sdk";
|
|
2
|
+
import type { CargoItem } from "../payload/codec.ts";
|
|
3
|
+
import { renderByType } from "./index.ts";
|
|
4
|
+
import { STARDUST_BASE64 } from "../assets/stardust-base64.ts";
|
|
5
|
+
import { svgDimensions } from "../meta.ts";
|
|
6
6
|
|
|
7
|
-
export const SOCIAL_CARD_WIDTH = 1200
|
|
8
|
-
export const SOCIAL_CARD_HEIGHT = 630
|
|
7
|
+
export const SOCIAL_CARD_WIDTH = 1200;
|
|
8
|
+
export const SOCIAL_CARD_HEIGHT = 630;
|
|
9
9
|
|
|
10
|
-
const SPACE_DEEP =
|
|
11
|
-
const STARDUST_TILE = 512
|
|
12
|
-
const ITEM_MAX_WIDTH_RATIO = 0.35
|
|
13
|
-
const ITEM_MAX_HEIGHT_RATIO = 0.82
|
|
14
|
-
const DOMAIN_LABEL =
|
|
10
|
+
const SPACE_DEEP = "#050c24";
|
|
11
|
+
const STARDUST_TILE = 512;
|
|
12
|
+
const ITEM_MAX_WIDTH_RATIO = 0.35;
|
|
13
|
+
const ITEM_MAX_HEIGHT_RATIO = 0.82;
|
|
14
|
+
const DOMAIN_LABEL = "shiploadgame.com";
|
|
15
15
|
|
|
16
16
|
export function socialCardSvg(item: CargoItem, resolved: ResolvedItem): string {
|
|
17
|
-
const itemSvg = renderByType(item, resolved)
|
|
18
|
-
const { width: itemW, height: itemH } = svgDimensions(itemSvg)
|
|
17
|
+
const itemSvg = renderByType(item, resolved);
|
|
18
|
+
const { width: itemW, height: itemH } = svgDimensions(itemSvg);
|
|
19
19
|
|
|
20
20
|
const scale = Math.min(
|
|
21
21
|
(SOCIAL_CARD_WIDTH * ITEM_MAX_WIDTH_RATIO) / itemW,
|
|
22
22
|
(SOCIAL_CARD_HEIGHT * ITEM_MAX_HEIGHT_RATIO) / itemH,
|
|
23
|
-
)
|
|
24
|
-
const scaledW = itemW * scale
|
|
25
|
-
const scaledH = itemH * scale
|
|
26
|
-
const tx = (SOCIAL_CARD_WIDTH - scaledW) / 2
|
|
27
|
-
const ty = (SOCIAL_CARD_HEIGHT - scaledH) / 2
|
|
23
|
+
);
|
|
24
|
+
const scaledW = itemW * scale;
|
|
25
|
+
const scaledH = itemH * scale;
|
|
26
|
+
const tx = (SOCIAL_CARD_WIDTH - scaledW) / 2;
|
|
27
|
+
const ty = (SOCIAL_CARD_HEIGHT - scaledH) / 2;
|
|
28
28
|
|
|
29
|
-
const itemInner = itemSvg.replace(/^<svg[^>]*>/,
|
|
29
|
+
const itemInner = itemSvg.replace(/^<svg[^>]*>/, "").replace(/<\/svg>\s*$/, "");
|
|
30
30
|
|
|
31
|
-
return
|
|
31
|
+
return (
|
|
32
|
+
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${SOCIAL_CARD_WIDTH}" height="${SOCIAL_CARD_HEIGHT}" viewBox="0 0 ${SOCIAL_CARD_WIDTH} ${SOCIAL_CARD_HEIGHT}">` +
|
|
32
33
|
`<defs>` +
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
`<pattern id="sc-stars" width="${STARDUST_TILE}" height="${STARDUST_TILE}" patternUnits="userSpaceOnUse">` +
|
|
35
|
+
`<image xlink:href="data:image/png;base64,${STARDUST_BASE64}" width="${STARDUST_TILE}" height="${STARDUST_TILE}"/>` +
|
|
36
|
+
`</pattern>` +
|
|
36
37
|
`</defs>` +
|
|
37
38
|
`<rect width="${SOCIAL_CARD_WIDTH}" height="${SOCIAL_CARD_HEIGHT}" fill="${SPACE_DEEP}"/>` +
|
|
38
39
|
`<rect width="${SOCIAL_CARD_WIDTH}" height="${SOCIAL_CARD_HEIGHT}" fill="url(#sc-stars)" opacity="0.75"/>` +
|
|
39
40
|
`<g transform="translate(${tx.toFixed(2)} ${ty.toFixed(2)}) scale(${scale.toFixed(4)})">${itemInner}</g>` +
|
|
40
41
|
`<text x="${SOCIAL_CARD_WIDTH - 40}" y="${SOCIAL_CARD_HEIGHT - 36}" text-anchor="end" fill="#e6e8ec" opacity="0.55" font-size="22" font-family="Inter, sans-serif" letter-spacing="0.04em">${DOMAIN_LABEL}</text>` +
|
|
41
|
-
|
|
42
|
+
`</svg>`
|
|
43
|
+
);
|
|
42
44
|
}
|
package/src/tokens/colors.ts
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { tierColors } from
|
|
1
|
+
import { tierColors } from "@shipload/sdk";
|
|
2
2
|
|
|
3
3
|
export const colors = {
|
|
4
4
|
surface: {
|
|
5
|
-
background:
|
|
6
|
-
panel:
|
|
7
|
-
panelBorder:
|
|
8
|
-
panelBorderBright:
|
|
5
|
+
background: "#0a0a0c",
|
|
6
|
+
panel: "#11141a",
|
|
7
|
+
panelBorder: "#1e242e",
|
|
8
|
+
panelBorderBright: "#2a3340",
|
|
9
9
|
},
|
|
10
10
|
text: {
|
|
11
|
-
primary:
|
|
12
|
-
secondary:
|
|
13
|
-
muted:
|
|
14
|
-
accent:
|
|
11
|
+
primary: "#e6e8ec",
|
|
12
|
+
secondary: "#8f98a8",
|
|
13
|
+
muted: "#5b6373",
|
|
14
|
+
accent: "#f4c96b",
|
|
15
15
|
},
|
|
16
16
|
brand: {
|
|
17
|
-
pink:
|
|
18
|
-
teal:
|
|
19
|
-
cyan:
|
|
17
|
+
pink: "#ff4f9a",
|
|
18
|
+
teal: "#2fd6d1",
|
|
19
|
+
cyan: "#6cb9ff",
|
|
20
20
|
},
|
|
21
21
|
category: {
|
|
22
|
-
ore:
|
|
23
|
-
crystal:
|
|
24
|
-
gas:
|
|
25
|
-
regolith:
|
|
26
|
-
biomass:
|
|
22
|
+
ore: "#C26D3F",
|
|
23
|
+
crystal: "#4ADBFF",
|
|
24
|
+
gas: "#B8E4A0",
|
|
25
|
+
regolith: "#C4A57B",
|
|
26
|
+
biomass: "#5A8B3E",
|
|
27
27
|
},
|
|
28
28
|
tier: tierColors,
|
|
29
29
|
accent: {
|
|
30
|
-
component:
|
|
30
|
+
component: "#8f98a8",
|
|
31
31
|
},
|
|
32
32
|
capability: {
|
|
33
|
-
engine:
|
|
34
|
-
generator:
|
|
35
|
-
gatherer:
|
|
36
|
-
loader:
|
|
37
|
-
manufacturing:
|
|
38
|
-
storage:
|
|
39
|
-
hauler:
|
|
33
|
+
engine: "#4a8abf",
|
|
34
|
+
generator: "#22c55e",
|
|
35
|
+
gatherer: "#f59e0b",
|
|
36
|
+
loader: "#eab308",
|
|
37
|
+
manufacturing: "#a855f7",
|
|
38
|
+
storage: "#14b8a6",
|
|
39
|
+
hauler: "#f97316",
|
|
40
40
|
},
|
|
41
|
-
} as const
|
|
41
|
+
} as const;
|
|
42
42
|
|
|
43
|
-
export type CategoryColorKey = keyof typeof colors.category
|
|
44
|
-
export type TierColorKey = keyof typeof colors.tier
|
|
45
|
-
export type CapabilityColorKey = keyof typeof colors.capability
|
|
43
|
+
export type CategoryColorKey = keyof typeof colors.category;
|
|
44
|
+
export type TierColorKey = keyof typeof colors.tier;
|
|
45
|
+
export type CapabilityColorKey = keyof typeof colors.capability;
|
package/src/tokens/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { colors } from
|
|
2
|
-
import { spacing } from
|
|
3
|
-
import { typography } from
|
|
1
|
+
import { colors } from "./colors.ts";
|
|
2
|
+
import { spacing } from "./spacing.ts";
|
|
3
|
+
import { typography } from "./typography.ts";
|
|
4
4
|
|
|
5
|
-
export const tokens = { colors, spacing, typography } as const
|
|
6
|
-
export type Tokens = typeof tokens
|
|
7
|
-
export { colors, spacing, typography }
|
|
5
|
+
export const tokens = { colors, spacing, typography } as const;
|
|
6
|
+
export type Tokens = typeof tokens;
|
|
7
|
+
export { colors, spacing, typography };
|
package/src/tokens/spacing.ts
CHANGED
package/src/tokens/typography.ts
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|