@johnmorrisdotca/senshu-jerseys
v0.10.0
Published
Senshu Jerseys 選手, sports jerseys, sweaters, kits, caps, helmets, retired-number banners and judogi as SVG strings: hockey, baseball, basketball, football, soccer, rugby and handball cuts, front and back, in stripes, hoops, halves and pinstripes, with num
Maintainers
Readme
The best of it
- Seven cuts, front and back: hockey, baseball, basketball, football, soccer, rugby and handball, each on the ground of its own sport, and each also in one line.
- Hockey patterns that turn: shrinking sleeve bands, sawtooth sleeves and a pointed yoke (hockey patterns).
- Officials: referees, linesmen and umpires of seven sports (officials).
- Retired-number banners, and championship pennants and garden flags (banners).
- On a phone, and turned by hand: drag, arrow keys or the automatic turn (turning).
Status: preview. Seven cuts, ten patterns, caps, helmets, banners, a matchup and a lineup, turning, a custom element, and presets of the colours and stripes of real teams. The presets are a first reading from public descriptions: see Presets before relying on one.
In 30 seconds
npm install @johnmorrisdotca/senshu-jerseysimport { renderJersey } from "@johnmorrisdotca/senshu-jerseys";
const svg = renderJersey({ cut: "hockey", colors: ["#af1e2d", "#ffffff", "#192168"], number: 9, name: "Beaumont", view: "both" });
console.log(svg.startsWith("<svg")); // true
console.log(svg.includes("Hockey sweater, number 9, front and back")); // truePut the string in a page (element.innerHTML = svg), write it to a file, or send it from a server: it is plain SVG, with no script, no font and no network. Numbers and names are drawn as shapes, not typed as text, so the jersey looks the same on every device.
Who it is for
- Sports sites and apps that show a roster, a lineup, a result or a retired number and want a shirt in the team's colours without a photograph or a licence.
- Scoreboards, brackets and match pages that want a kit for each side, and the two kits to be told apart.
- Games and cards that need a jersey with a number, front and back, that turns.
- Pages that care about weight: the drawing is about 19 kB gzipped, the turn about 14 kB more, and a league of presets about 5 kB; the package says
sideEffectsonly for the element's registration.
Features
- Seven cuts: hockey (lace-up, V or crew collar; curved, square or full-arm yoke; chest band, hem and sleeve stripes; upper-arm numbers; a C or A patch), baseball (button front, pullover or vest; pinstripes, piping, raglan sleeves; chest letters), basketball (a tank with a round, V or wishbone neck and side panels), football (wide shoulders, sleeve stripes, shoulder numbers), soccer, rugby and handball.
- Ten patterns: solid, stripes, hoops, halves, quarters, sash, chevron, pinstripes, yoke and chest band; stripes and hoops take any repeat of widths and colours.
- Front, back, or both, in three styles:
cloth(soft folds, stitched numbers, a self-toned edge),flat, andline(one colour, legible at 16 pixels). - Numbers and names that read: each gets an ink with 4.5 to 1 contrast against what is behind it, and an outline over stripes. The letters are drawn here, as block strokes: no font file and no system font. A Japanese name is set as system text.
- Caps, helmets and banners: a solid or two-tone cap with a contrast brim; football, hockey and batting helmets; retired-number banners as a rectangle, a pennant or a shield, with an optional sweater filling the cloth (number and last name on the back, years below), or
kind: "hanging"for a plate with last name, number and a caller-supplied logo; championship pennants and garden flags throughrenderTitleBanner. - A matchup and a lineup: two kits side by side that say whether they clash; a formation of shirts,
4-4-2, with names and numbers. - Whole uniforms: the pants or shorts, the socks and the helmet that go with the jersey.
- Presets: more than fifteen hundred eras of the NHL (more than four hundred, with its defunct teams), MLB, the NBA, the NFL, the CFL, soccer clubs and nations, rugby, and national hockey teams, as colours and stripes only.
- Officials: referees, linesmen and umpires of hockey, basketball, football, soccer, baseball, rugby and lacrosse, with armbands, a chest pocket, a plain badge spot, zip and long-point collars, and a few historic looks (docs/officials.md).
- Turning (
./turn): drag, arrow keys, Home and Space, or an automatic turn that waits for you, in either direction (direction: "clockwise"or"counterclockwise"). - A custom element (
./element) and an embed page for sites that cannot run script. - English and Japanese labels. TypeScript types. No dependencies.
What's in it
| Part | Entry | What it is |
| --- | --- | --- |
| Drawing | @johnmorrisdotca/senshu-jerseys | renderJersey, renderCap, renderHelmet, renderBanner, renderTitleBanner, renderJudogi, kitMatchup, jerseyLineup, and what they need. Pure: strings in, strings out. |
| Turning | @johnmorrisdotca/senshu-jerseys/turn | jerseyFrame (one angle) and bindTurn (a container that turns, either way: turnDirection). |
| Element | @johnmorrisdotca/senshu-jerseys/element, /define | <senshu-jersey>, and the import that registers it. |
| Presets | @johnmorrisdotca/senshu-jerseys/presets/<league> | One module per league: nhl, mlb, nba, nfl, cfl, soccer and the others in docs/presets.md. |
Use it in your project
Install
npm install @johnmorrisdotca/senshu-jerseys
# or: pnpm add @johnmorrisdotca/senshu-jerseys
# or: yarn add @johnmorrisdotca/senshu-jerseysNeeds Node 22 or later on a server; in a browser, any current one.
In a page
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/senshu-jerseys@0/dist/element-define.js"></script>
<senshu-jersey cut="soccer" colors="#c8102e,#ffffff" pattern="stripes" number="10" name="Gray" view="both"></senshu-jersey>On a server
import { writeFileSync } from "node:fs";
import { renderJersey } from "@johnmorrisdotca/senshu-jerseys";
writeFileSync(new URL("./shirt.svg", import.meta.url), renderJersey({ cut: "basketball", colors: ["#1f3a8a", "#f4f0e4"], number: 23, view: "back" }));Examples
Draw a jersey
cut picks the shirt, colors is [body, trim, second trim, number fill, number outline] (or body, trim and accent), and view is front, back or both.
import { renderJersey } from "@johnmorrisdotca/senshu-jerseys";
const front = renderJersey({ cut: "football", colors: ["#0b6e4f", "#f4f0e4", "#e8b900"], number: 12, sleeveNumbers: true, frontNumber: true });
const back = renderJersey({ cut: "football", colors: ["#0b6e4f", "#f4f0e4", "#e8b900"], number: 12, name: "Larocque", view: "back" });
console.log(front.includes("Football jersey, number 12, front")); // true
console.log(back.includes("Football jersey, number 12, back")); // truePatterns, collars and stripes
import { renderJersey } from "@johnmorrisdotca/senshu-jerseys";
// Barber-pole hoops: 9 units of trim, 9 of the second trim, 9 of the body.
const hoops = renderJersey({ cut: "hockey", colors: ["#c8102e", "#ffffff", "#14181c"], pattern: "hoops", repeat: [9, 1, 9, 2, 9, 0], collar: "crew", sleeveStripes: 0, hemStripes: 0 });
// Nine equal stripes, the sleeves plain.
const stripes = renderJersey({ cut: "soccer", colors: ["#6cace4", "#ffffff"], pattern: "stripes", stripeCount: 9, patternSleeves: false });
// A sash, and a yoke with the arms in the trim colour.
const sash = renderJersey({ cut: "soccer", colors: ["#ffffff", "#c8102e"], pattern: "sash" });
const yoke = renderJersey({ cut: "hockey", colors: ["#1f3a8a", "#f4f0e4"], yoke: "full-arm", collar: "v" });
console.log([hoops, stripes, sash, yoke].every((svg) => svg.startsWith("<svg"))); // trueSleeve, hem and chest bands are lists of numbers read in pairs: a width in drawing units and a colour (0 the body, 1 the trim, 2 the second trim, 3 the number's fill, the fourth colour), from the edge inward. [4, 0, 3, 2, 6, 1, 3, 2] is a gap of 4, a line of the second trim, a band of the trim, a line of the second trim.
import { renderJersey } from "@johnmorrisdotca/senshu-jerseys";
const svg = renderJersey({ cut: "hockey", colors: ["#af1e2d", "#ffffff", "#192168"], sleeveStripes: [4, 0, 3, 2, 6, 1, 3, 2], hemStripes: [4, 0, 3, 2, 6, 1, 3, 2], collar: "lace-up" });
console.log(svg.includes("<title")); // trueMen's and women's cuts
fit: "womens" (the fit attribute) draws the tailored cut of any sport: a shorter body, narrower shoulders and sleeves, and a waist that tapers in and flares to the hem. fit: "mens" is the default and is unchanged. Patterns, stripes, names, numbers and badges follow the women's outline, flat and in volume; cutSpec(cut, fit) returns the table and FIT_IDS lists the fits. See men's and women's cuts.
Cuffs, diagonal text and wide arm bands
cuffs ends the sleeve in a contrasting colour, frontTextAngle sets how the chest text runs ("straight", "arch", "diagonal-up", "diagonal-down"), a wide elbow band and sets of three colours need only sleeveStripes and hemStripes, and sleeveNumbers, sleeveLeft, sleeveRight and patch put numbers and patches on the arms and chest. The options, their values and every example are on one page: cuffs, diagonal text and wide arm bands, with sleeves and chest badges.
Numbers and names that read
The number's colour is the one asked for if it reads on the shirt, and black or white if it does not. Over stripes it gets an outline. The letters are strokes, so there is no <text> for a Latin name.
import { contrast, readColour, renderJersey } from "@johnmorrisdotca/senshu-jerseys";
// White number asked for on a white shirt: the drawing writes dark ink.
const svg = renderJersey({ colors: ["#ffffff", "#ffffff"], number: 5, view: "back" });
console.log(svg.includes('stroke="#111111"')); // true
console.log(svg.includes("<text")); // false
console.log(contrast(readColour("#000000"), readColour("#ffffff"))); // 21Put your own crest on the chest
crest is any SVG document as text, shown as an image (so it runs no script and loads nothing), or an emblem's id if you plug in a renderer such as Dougu Sports with useEmblems.
import { renderJersey, useEmblems } from "@johnmorrisdotca/senshu-jerseys";
const crest = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#f4f0e4"/></svg>`;
console.log(renderJersey({ cut: "soccer", crest }).includes("<image")); // true
// With dougu-sports: useEmblems(renderEmblem), then crest: "crest:shield".
useEmblems((id) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><title>${id}</title></svg>`);
console.log(renderJersey({ cut: "soccer", crest: "crest:shield" }).includes("<image")); // true
useEmblems(undefined);Presets: a team's colours and stripes
A preset holds a team's colours, stripe layout, yoke, collar and number colours for one era, and the matching pants, socks and helmet where they are known. It never draws a crest, a logo, a monogram, a wordmark or a sponsor's mark. Load a league, then use a preset by its id.
import { renderJersey, usePresets, presetOptions } from "@johnmorrisdotca/senshu-jerseys";
import { MLB } from "@johnmorrisdotca/senshu-jerseys/presets/mlb";
usePresets(MLB);
const options = presetOptions("mlb:yankees-1929-home");
const svg = renderJersey({ ...options, number: 3, name: "Ruth", view: "both" });
console.log(options.pattern); // pinstripes
console.log(svg.startsWith("<svg")); // true
console.log(MLB.some((p) => p.team === "Houston Astros")); // trueOfficials: referees, linesmen and umpires
The officials' shirts are presets (ref:nhl-modern-referee, ref:soccer-yellow, ref:mlb-umpire-navy) built from options of their own: armbands (a colour, a width and a place on the upper arm), pocket, badge (a plain spot, never a federation's mark), protector, the zip and point collars, and collarColor: 3. docs/officials.md lists them with their sources.
import { renderJersey } from "@johnmorrisdotca/senshu-jerseys";
// A referee: striped, a black zip collar, orange armbands. The linesman is the same without them.
const striped = { cut: "hockey", colors: ["#ffffff", "#101010", "#f58220"], pattern: "stripes", repeat: [5, 0, 10, 1, 5, 0], sleeveStripes: 0, hemStripes: 0, collar: "zip" };
const referee = renderJersey({ ...striped, armbands: [2, 12, 30], number: 14, name: "Beaumont", nameArch: "straight", view: "back" });
const linesman = renderJersey({ ...striped, number: 14, name: "Beaumont", nameArch: "straight", view: "back" });
// A soccer referee: a chest pocket with a plain shield for the badge spot.
const soccer = renderJersey({ cut: "soccer", colors: ["#f7e017", "#111111"], pocket: true, badge: "shield", cuffs: [7, 1] });
console.log(referee.length > linesman.length); // true
console.log(soccer.startsWith("<svg")); // trueA whole uniform
kit: true draws the pants or shorts, the socks and the helmet (or the cap) under the jersey, from pants, pantsTrim, pantsBands, socks, socksTrim, socksBands and helmet. A preset holds them where they are known. renderKit is the same call by name.
import { renderJersey, renderKit } from "@johnmorrisdotca/senshu-jerseys";
const set = { cut: "hockey", colors: ["#af1e2d", "#ffffff", "#192168"], pants: "#192168", pantsBands: [4, 2, 3, 1], socks: "#af1e2d", socksBands: [20, 0, 5, 1, 3, 2, 5, 1], helmet: "#192168" };
console.log(renderJersey({ ...set, kit: true }) === renderKit(set)); // trueCaps, helmets and retired banners
import { renderBanner, renderCap, renderHelmet, renderTitleBanner } from "@johnmorrisdotca/senshu-jerseys";
const cap = renderCap({ colors: ["#d52b1e", "#0b2a6a", "#ffffff"], tone: "two-tone", initials: "CAN" });
const helmet = renderHelmet({ kind: "football", colors: ["#1f3a8a", "#f4f0e4"], mark: "number", number: 12, facing: "left" });
const banner = renderBanner({ shape: "pennant", colors: ["#d52b1e", "#ffffff", "#0b2a6a"], number: 9, name: "Beaumont", years: "1948-1963", sweater: true });
const hanging = renderBanner({ kind: "hanging", colors: ["#041e42", "#ffffff", "#ff4c00"], number: 99, name: "Lindqvist" });
const pennant = renderTitleBanner({ kind: "pennant", colors: ["#041e42", "#ff4c00"], title: "Stanley Cup", subtitle: "Champions", years: [1984, 1985, 1987, 1988, 1990] });
console.log(cap.includes("<title")); // true
console.log(helmet.includes("<title")); // true
console.log(banner.includes("Retired number banner 9")); // true
console.log(hanging.includes("Retired number banner 99")); // true
console.log(pennant.includes("Championship banner")); // trueA judogi (jacket, trousers and belt) is its own drawing. The designer demo keeps every choice in the address.
import { JUDOGI_COLOURS, judogiCheck, renderJudogi } from "@johnmorrisdotca/senshu-jerseys";
console.log(JUDOGI_COLOURS.join(",")); // white,blue
console.log(judogiCheck({ size: "170" }).ok); // true
console.log(renderJudogi({ colour: "white", mode: "jacket" }).includes("<svg")); // trueA caller's own letters go on a cap (up to three) and a banner. Title banners set honour, city, club and years as condensed sports sans (SVG <text>), not the jersey's tackle-twill glyphs. No helmet, cap or banner draws a team's mark of its own: a hanging banner or a title banner shows a logo only when the caller passes one. Trophies and rings are in Dougu Sports.
Two kits and a lineup
import { jerseyLineup, kitClash, kitMatchup } from "@johnmorrisdotca/senshu-jerseys";
const away = { cut: "soccer", colors: ["#1f3a8a", "#ffffff"], label: "Japan" };
const home = { cut: "soccer", colors: ["#d52b1e", "#ffffff"], label: "Canada" };
console.log(kitMatchup({ away, home }).includes("Japan vs Canada")); // true
console.log(kitClash(away, home)); // false
console.log(kitClash({ colors: ["#c8102e"] }, { colors: ["#c8102f"] })); // true
const players = Array.from({ length: 11 }, (_, i) => ({ number: i + 1, name: `Player ${i + 1}` }));
console.log(jerseyLineup(players, { formation: "4-4-2", jersey: { cut: "soccer", colors: ["#c8102e", "#ffffff"] } }).includes("Lineup, 4-4-2")); // trueNeighbouring shirts tuck under each other by default, like shirts hung on a rail: each is drawn over the one on its left, with a soft shadow where it overlaps. overlap is a fraction of a shirt's width, from 0 to 0.4.
| Option | Where | What it does |
| --- | --- | --- |
| overlap | jerseyLineup | How far each shirt in a row tucks under the next. 0.15 by default; 0 leaves the shirts apart, not touching. |
| overlap | kitMatchup | The same, for each side's back and front in the both view (a single view has nothing to tuck). 0.15 by default. |
| overlap | renderJersey | The same, for view: "both". 0 by default, so the two shirts stay side by side unless asked. |
import { jerseyLineup, renderJersey } from "@johnmorrisdotca/senshu-jerseys";
const players = Array.from({ length: 11 }, (_, i) => ({ number: i + 1 }));
const apart = jerseyLineup(players, { rows: [5, 6], overlap: 0 });
const tucked = jerseyLineup(players, { rows: [5, 6], overlap: 0.25 });
console.log(apart.includes("<filter")); // false
console.log(tucked.includes("<filter")); // true
console.log(renderJersey({ view: "both", overlap: 0.2 }).includes("<filter")); // trueMore in docs/overlap.md.
Turn a jersey
jerseyFrame is the jersey at one angle: 0 is the front, 90 an edge where the cloth's thickness shows, 180 the back. bindTurn makes a container turn.
import { jerseyFrame } from "@johnmorrisdotca/senshu-jerseys/turn";
const frames = [0, 45, 90, 135, 180].map((yaw) => jerseyFrame({ cut: "hockey", number: 9, name: "Gray", yaw }));
console.log(frames.length); // 5
console.log(frames[2].includes('data-yaw="90"')); // trueimport { bindTurn } from "@johnmorrisdotca/senshu-jerseys/turn";
const turn = bindTurn(document.querySelector("#shirt"), { cut: "hockey", number: 9, name: "Gray" }, { autoPlay: false });
turn.play(); // or turn.pause(), turn.togglePlay(), turn.setYaw(180)
turn.setDirection("clockwise"); // seen from above: the other way (the default, `"counterclockwise"`, swings the front to the viewer's right)See turning for the pointer, the keys, the direction and prefers-reduced-motion, and for volume: true, which turns the shirt as a garment with depth (a body, sleeves, a hem) instead of a flat card.
Read and write options as text
The attributes of the element and the query of the embed page are the same words, read by one function and written by another.
import { attributesFromOptions, embedCode, jerseyFromParams, jerseyToParams, optionsFromAttributes } from "@johnmorrisdotca/senshu-jerseys";
const options = { cut: "rugby", colors: ["#000000", "#ffffff"], number: 8 };
const attributes = attributesFromOptions(options);
console.log(attributes.cut); // rugby
console.log(optionsFromAttributes((name) => attributes[name] ?? null).number); // 8
console.log(jerseyToParams(options).toString().startsWith("cut=rugby")); // true
console.log(jerseyFromParams(new URLSearchParams("cut=rugby&number=8")).cut); // rugby
console.log(embedCode(options, "iframe").includes("embed.html?cut=rugby")); // trueAPI
| Name | What it does |
| --- | --- |
| renderJersey(options), renderKit(options) | A jersey (or the whole uniform) as SVG. |
| titleOf(resolved) | The accessible title a drawing gets, in language. |
| renderCap, renderHelmet, renderBanner, renderTitleBanner | A cap, a helmet, a retired-number banner, a championship pennant or garden flag. |
| renderJudogi, judogiFrame, judogiCheck, judogiHeightCm, JUDOGI_COLOURS, JUDOGI_BELTS, JUDOGI_BELT_LABELS, JUDOGI_MODES, JUDOGI_VIEWS, JUDOGI_SIZES | A judogi: jacket, trousers and belt, contest white or blue, a turning frame, and the cited IJF size check. Judogi. |
| kitMatchup, kitClash, jerseyLineup, formationRows | Two kits face to face, whether they clash, a formation of shirts (tucked under each other with overlap), and the rows a formation means. |
| useEmblems | Plug in an emblem renderer so that crest can be an id. |
| usePresets, presetOf, presetOptions, loadedPresets, decode, defineLeague | Load presets, find one, and read or write a preset's one-line spec. |
| embedCode, EMBED_KINDS, SITE, CDN | The text that puts a jersey in a page: element, iframe, module, SVG or data address. |
| OPTION_ATTRIBUTES, attributesFromOptions, optionsFromAttributes, jerseyToParams, jerseyFromParams | Options as attributes and as a query. |
| CUT_IDS, CUTS, COLLAR_IDS, SLEEVE_IDS, BADGE_IDS, PATTERN_IDS, TEXT_ANGLE_IDS, YOKE_IDS, CLOSURE_IDS, VIEW_IDS, LOOK_IDS, ARCH_IDS, PATCH_IDS, CHEST_KINDS, LANGUAGE_IDS | The lists of what an option may be, and each cut's measures. |
| CUT_NAMES, PATTERN_NAMES | English and Japanese names. |
| NATIONAL_COUNTRIES, NATIONAL_LAYOUTS, NATIONAL_DIRECTIONS, NATIONAL_NAMES | The twelve countries, the eight national layouts, the way the diagonal's name runs (down, up), and the words each country sets on the chest: national layouts. |
| FIT_IDS, cutSpec | The fits (mens, womens) and a cut's table in a fit: men's and women's cuts. |
| HELMET_KINDS, HELMET_MARKS, BANNER_SHAPES, BANNER_KINDS, TITLE_BANNER_KINDS, logoHref, yearLines | The helmets, their marks, the banner shapes, the rafter or hanging kind, championship pennant or garden, a check of a caller-supplied logo, and championship years split onto lines. |
| contrast, readColour | The contrast ratio of two colours, and a colour read from text. |
| JERSEY_WIDTH, JERSEY_HEIGHT, jerseyBox, VERSION | The body's 200 by 232 units, the box a cut is drawn in (a little wider than the body for a cut whose sleeves reach past it), and the package's version. |
Options
cut, colors or body, trim, accent, numberFill, numberOutline, pattern, repeat, stripeCount, patternSleeves, collar, sleeve, yoke, closure, raglan, piping, armStripes, sleeveStripes, hemStripes, cuffs, armbands, pocket, badge, protector, plate, chestBand, chestY, collarColor, sleeveColor, panels, number, name, nameArch, national, frontText, frontTextAngle, frontNumber, patch, sleeveNumbers, sleeveLeft, sleeveRight, chestBadges, crest, view, look, size, language, title, description, kit, pants, pantsTrim, pantsBands, socks, socksTrim, socksBands and helmet. A value that is not one of the allowed ones is replaced by the default; nothing throws. size is the width of one jersey's body in pixels (200 by default); a cut whose sleeves reach past the body (football, rugby, soccer, baseball and handball) is drawn in a box a little wider, so its document is a little wider than size.
Theming
The cloth and flat styles carry their own colours. The line style is drawn in currentColor, so it takes the text colour of the page and follows light and dark mode. The custom element's drawing sits in an open shadow root with a part="box".
Limits
Every limit here is held by a test.
- A drawing is a pure function of its options. The same options give the same string, with ids that are the same for the same drawing and differ between drawings, so several can share a page.
- Every colour is checked. Only
#rgb,#rrggbbandrgb()are taken; anything else is the default. - Every text is escaped or drawn as strokes. A crest is shown as an image, which runs no script and loads nothing.
- No font is used for a Latin number or name. A name with a character that has no glyph here (a Japanese name) is set as system text.
- The drawing is about 19 kB gzipped; the turn about 14 kB more; a league of presets about 5 kB.
- A preset is colours and stripes only. No crest, logo, monogram, wordmark or sponsor mark.
Accessibility
Each drawing is role="img" with a <title> that says the cut, the number and the view, in English or Japanese (language). A turning container is a focusable group that says it turns, with the arrow keys, Home and Space; it starts still under prefers-reduced-motion, stops while a tab is hidden, and does nothing while nobody asks. Every control in the demo is 44 pixels or more.
Browser and runtime support
Node 22 or later, and any current browser. The turn needs requestAnimationFrame and pointer events; the element needs custom elements and shadow DOM.
Languages
The package's own words (cut and pattern names, accessible titles) are in English and Japanese; the demo is too. Corrections are welcome: docs/references.md lists where the facts come from.
Presets
More than fifteen hundred uniforms of one team in one era, each written as a short line of colours and stripes and read into renderJersey's options: docs/presets.md lists every one with its id. They reproduce colours and stripe patterns only, with no logos or crests: no club crest, logo, monogram, wordmark or sponsor mark is drawn, and the chest stays empty or takes your own crest. That holds for defunct teams too, whose marks are still owned and sold today. Where a uniform's identity is mostly its chest mark, the preset still reads by its colours and stripes.
Apart from the real teams, presets/hockey-styles holds twelve original national hockey styles, home and away, each with a layout of its own and made to be personalised (personalStyle): see national styles, and national layouts for each country's own mark or flag in eight arrangements (national: "canada:diagonal").
The four presets/hockey-patterns sweaters show the shrinking sleeve bands, the sawtooth sleeves and the pointed yoke (hockey patterns).
The colours and layouts are facts taken from the uniform sections of Wikipedia articles, Wikidata and the clubs' published colour codes, and recorded as data here; no picture is copied. Treat them as a first reading, not a proof: where a source gave only colour names the layout is the plainest one that fits, and a row that could not be confirmed is marked unsure in its source. Corrections with a source are welcome.
Roadmap
- More eras, and the real count of stripes where a source gives it.
- Checked patterns and argyle.
- A cuff and a collar in a second colour for the tank cut.
- Kit view for turning.
Architecture
src/index.ts is the pure entry. A jersey is drawn from a cut (a table of measures in a box 200 wide: the neck, shoulders, sleeves, armpits and hem), its silhouette is a rounded polygon mirrored about the middle, and what it carries (collar, bands, pattern, panels, folds) is laid over it inside clips. Letters are centre lines with a stroke. The turn is a pair of matrices on the front and the back and a few slices of the outline for the edge. docs/turning.md says how.
The name
Senshu (選手, せんしゅ) is Japanese for a player or an athlete: the person who wears the shirt.
Where it comes from, and where it is used
Made for itsutsu.com and the sports pages of its sister sites, in the same style as Kyogijo Stadiums, which draws the field, and Dougu Sports, which draws the trophies and the gear.
Used by
Nothing yet: it is not published. Kyogijo Stadiums can plug it in to draw the players on a field, the way it plugs in Dougu Sports.
The family
Senshu Jerseys is one of thirty-two packages, each made for the same site, each at github.com/johnmorrisdotca or github.com/spxis. The code of every one is MIT.
Games — board
- Domino (ドミノ): dominoes and Mexican Train. Demo.
- Gunjin (軍人): five hidden-rank strategy games with pass-the-device play. Demo.
- Narabe (並べ): one rules engine for abstract board games, from gomoku and Reversi to Go and checkers. Demo.
- Sugoroku (双六): backgammon and its variants, with the doubling cube and match play. Demo.
- Tenka (天下): world conquest for two to six, on a map of the real world. Demo.
Games — cards & tiles
- Hitotsu (一つ): a colour-card shedding game for two to eight, with the house rules people play. Demo.
- Jarajara (ジャラジャラ): mahjong tiles drawn as SVG, stacked layouts, and the matching solitaire Awase. Demo.
- Kumimoji (組み文字): a crossword tile race, in English and Japanese kana. Demo.
- Toranpu (トランプ): a deck of playing cards, card games with computer players, and solitaires. Demo.
Games — puzzles
- Houseki (宝石): gem and stone matching puzzles: falling triplets, stone collapse, colour chains and gem swap. Demo.
- Jirai (地雷): minesweeper on shaped grids with verified no-guess boards. Demo.
- Karakuri (からくり): eight hyper-casual puzzle games, some of them physics: draw a shield, pull pins, cut ropes, slide blocks, pour tubes. Demo.
- Kazu (数): grid number puzzles: Sudoku and its variants, Futoshiki and Skyscrapers. Demo.
- Kyuubu (キューブ): a turning cube for the browser, 2×2 to 7×7, with record solves to replay. Demo.
- Meikyuu (迷宮): mazes on squares, hexagons, triangles and circles, made from a seed and drawn through with a finger or the mouse. Demo.
- Suido (水道): a pipe puzzle: turn the pieces until the water reaches every drain. Demo.
- Tobiishi (飛び石): peg solitaire with nine boards and seeded solvable challenges. Demo.
- Tsunagi (繋ぎ): a line-joining logic puzzle whose every level has exactly one answer. Demo.
Games — dice & random
- Korokoro (コロコロ): dice, with notation, exact odds, real sounds and the dice of many games. Demo.
- Tane (種): seeded random numbers and daily seeds, the same in every browser and on every server. Demo.
Language
- Bushu (部首): find a kanji by the parts it is made of. Demo.
- Hikidashi (引き出し): a drawer of small Japanese text tools: era dates, kanji numerals, readings and sentence difficulty. Demo.
- Kotoba (言葉): word lists and word-game rules in English, French, German and Japanese. Demo.
Geography & data
- address-plus (アドレスプラス): parse and format addresses in the United States, Canada, Japan, Australia, France, Germany and the United Kingdom. Demo.
- Chizu (地図): maps of the world and of countries' regions, in English and Japanese, with a quiz and callouts. Demo.
- Hata (旗): country and regional flags as SVG, at their true proportions and keyed by ISO code. Demo.
- Kuni (国): countries and their regions, with ISO codes and names in English and Japanese. Demo.
Sports
- Dougu Sports (道具): sports emblems as SVG: trophies, medals, crests, and the ball and gear of every sport, with optional 3D turning. Demo.
- Kyogijo Stadiums (競技場): sports fields, rinks, courts and ballparks drawn to scale as SVG, with each team and the score on them. Demo.
- Senshu Jerseys (選手): sports jerseys, sweaters, kits, caps, helmets and retired-number banners as SVG, front and back, with optional 2.5D turning. Demo.
- Taisen Brackets (対戦): a sport-agnostic tournament bracket, drawn as SVG, from a list of ties. Demo.
Developer tools
- REST in Pieces (レスト・イン・ピーシーズ): seeded test data and REST APIs for frontend development, with latency and error drills. Demo.
This package is Senshu Jerseys. The demos of all thirty-two share one header and footer, so each links the rest.
Development
pnpm install
pnpm check # lint, types, tests, the build and the packed package
pnpm test:demo # the demo, played in a real browser at a phone's width and a desk's
pnpm test:readme # the README's and the docs' examples, run against the built package
pnpm dev # the demo on http://127.0.0.1:7300/
pnpm sheets # contact sheets of every cut, pattern, cap, helmet and bannerContributing
CONTRIBUTING.md has the rules and the commands.
Changes
Credits
Drawn for this package by John Morris. The team presets record facts from public sources: see NOTICE.md and docs/references.md.
Licence
MIT. See LICENSE.
