← BACK TO THE CARD
THE TEAM SHEET
HOW KENTCDODDS'S CARD IS BUILT · @UNLAYER/REACT-ELEMENTS
card.jsx
import { registerTool, Email, Row, Column,
         renderToHtml, renderToJson } from "@unlayer/react-elements";

// custom tools — the SAME config shape as the Builder's unlayer.registerTool
const PlayerHero = registerTool({
  name: "player_hero",
  renderer: { exporters: {
    web:   (v) => heroMarkup(v),        // your (values) => html functions
    email: (v) => heroEmailMarkup(v),   // email-safe variant
  }},
});
// …Masthead, FormStats, SeasonNotes, ProgrammeFooter registered the same way

// kentcdodds — GOLD · 85 OVR · computed from public activity
const card = (
  <Email backgroundColor="#f2ecdd" contentWidth="720px">
    <Row><Column>
      <Masthead />
      <PlayerHero playerName="Kent C. Dodds" ovr={85}
                  position="ST" tier="GOLD" />
      <FormStats stats={{"BUILD":81,"REVIEW":48,"SHIP":99,"VOLUME":99,"IMPACT":99}} />
      <SeasonNotes />
      <ProgrammeFooter handle="kentcdodds" />
    </Column></Row>
  </Email>
);

const html   = renderToHtml(card, { title: "kentcdodds · GitHub FC", fonts });
const design = renderToJson(card);  // → stored as { type: "custom", slug, values }
                                    //   opens in the Builder as REAL tools

// exporters are plain functions — the pitch computes your heat blobs
// from activity zones (reviews / commits / shipping) and draws real SVG:
function pitchSvg({ blobs }) {
  const ellipses = blobs.map(([x, y, w]) =>
    `<ellipse cx="${x * 640}" cy="${y * 300}"
              rx="${60 + w * 150}" ry="${44 + w * 96}" fill="url(#jet)"/>`);
  return `<svg viewBox="0 0 640 300">…turf, markings, ${ellipses.join("")}…</svg>`;
}

// register the same definitions in the editor embed (customJS) and a
// remixer gets property panels — change the tier, colors, coordinates
// with widgets, and each tool re-renders itself from its values.

$ npm install @unlayer/react-elements  ·  docs  ·  design.json  ·  open it in the editor →