Share card
renderShareCard draws the whole year on one card and gives it back as a PNG. It is painted on a canvas, so the file is the same on every machine: 1200 by 630, saved at twice that. Use it for the picture people post, pin to a profile or drop into a README.
Drawing the card.
Installation
pnpm add @react-wrapped/graphics @react-wrapped/core gsapUsage
Call it with a spec and a palette. It needs a browser, so call it from an effect or a click. The component below draws the card once per palette and shows it with a download link.
import { useEffect, useState } from "react";
import { resolvePalette, type WrappedConfig } from "@react-wrapped/core";
import { renderShareCard, type ShareCardSpec } from "@react-wrapped/graphics";
export function ShareCard({
spec,
config,
fileName,
}: {
spec: ShareCardSpec;
config: WrappedConfig;
fileName: string;
}) {
const [url, setUrl] = useState<string | null>(null);
const [failed, setFailed] = useState(false);
const { palette, accent, mode } = config;
// Drawn on a canvas, so only in the browser, and again whenever the palette changes.
useEffect(() => {
let made: string | null = null;
let gone = false;
const draw = async () => {
try {
const png = await renderShareCard(spec, resolvePalette({ palette, accent, mode }));
if (gone) return;
made = URL.createObjectURL(png);
setUrl(made);
} catch {
setFailed(true);
}
};
void draw();
return () => {
gone = true;
if (made) URL.revokeObjectURL(made);
};
}, [spec, palette, accent, mode]);
if (failed) return <p>The card could not be drawn in this browser.</p>;
if (!url) return <p>Drawing the card.</p>;
return (
<div>
<img src={url} alt={`@${spec.handle}, ${spec.caption}`} width={1200} height={630} />
<div role="group" aria-label="Share card">
<a href={url} download={fileName}>
Download the PNG
</a>
</div>
</div>
);
}The spec
| Key | Type | What it is |
|---|---|---|
handle | string | Written as @handle next to the avatar. |
avatarUrl | string | null | The picture. With null, or when it does not load, the handle's first two letters on a disc. |
caption | string | What this Wrapped is called. Set under the handle, before the period. |
period | string | The window the numbers cover, already formatted. |
graph? | { levels: readonly number[]; lead: number } | The year as cells, the same shape as a scene's calendar. Leave it out and the numbers move up. |
stats | readonly { label: string; value: string }[] | The headline numbers, already formatted. Up to four share the row. |
tokens? | readonly WrappedToken[] | What this person got, as tinted plates at the bottom. The first two are drawn. |
brand? | string | A quiet line in the top right: a site address or a product name. |
import type { ShareCardSpec } from "@react-wrapped/graphics";
import { PIXEL_ART } from "@react-wrapped/core";
export const card: ShareCardSpec = {
handle: "ada",
avatarUrl: null,
caption: "Orbit Wrapped",
period: "Oct 2025 to Oct 2026",
stats: [
{ label: "Contributions", value: "2,052" },
{ label: "Longest streak", value: "48 days" },
{ label: "Biggest day", value: "58" },
{ label: "Top language", value: "TypeScript" },
],
tokens: [{ name: "The Marathoner", kind: "Coder type", rarity: "epic", art: PIXEL_ART.mountain }],
brand: "orbit.example",
};What it draws
- The palette's background with one soft light in the top left corner, as on the video.
- The avatar or the initials, the handle, and the caption with the period after it.
- The year as cells, when you pass
graph. - The numbers in one row. A value too long for its column is set smaller until it fits.
- Up to two tokens at the bottom, each tinted in its rarity colour with its pixel art.
As a scene
The same content closes the video as the summary scene. In the gallery the card is one tile: hand it image: () => renderShareCard(card, palette).
