@johnmorrisdotca/dougu-sports 0.1.0 → 0.2.1

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 (192) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/NOTICE.md +9 -58
  3. package/README.md +174 -37
  4. package/dist/emblems/award/badge.d.ts +2 -2
  5. package/dist/emblems/award/badge.js +14 -5
  6. package/dist/emblems/award/crown.d.ts +1 -1
  7. package/dist/emblems/award/crown.js +14 -4
  8. package/dist/emblems/award/medal.d.ts +2 -2
  9. package/dist/emblems/award/medal.js +14 -5
  10. package/dist/emblems/award/shield.d.ts +1 -1
  11. package/dist/emblems/award/shield.js +10 -4
  12. package/dist/emblems/award/star.d.ts +1 -1
  13. package/dist/emblems/award/star.js +10 -4
  14. package/dist/emblems/ball/ball.afl.js +3 -2
  15. package/dist/emblems/ball/ball.basketball6.js +1 -1
  16. package/dist/emblems/ball/ball.core.d.ts +9 -3
  17. package/dist/emblems/ball/ball.core.js +13 -0
  18. package/dist/emblems/ball/ball.cricket.js +1 -1
  19. package/dist/emblems/ball/ball.curlingstone.d.ts +2 -0
  20. package/dist/emblems/ball/ball.curlingstone.js +11 -7
  21. package/dist/emblems/ball/ball.discus.js +1 -1
  22. package/dist/emblems/ball/ball.engine.js +3 -3
  23. package/dist/emblems/ball/ball.football.js +9 -7
  24. package/dist/emblems/ball/ball.gaelic.js +1 -1
  25. package/dist/emblems/ball/ball.object.d.ts +5 -2
  26. package/dist/emblems/ball/ball.object.js +3 -2
  27. package/dist/emblems/ball/ball.puck.d.ts +2 -0
  28. package/dist/emblems/ball/ball.puck.js +2 -2
  29. package/dist/emblems/ball/ball.rugby.js +3 -2
  30. package/dist/emblems/ball/ball.shuttlecock.d.ts +4 -0
  31. package/dist/emblems/ball/ball.shuttlecock.js +6 -5
  32. package/dist/emblems/ball/ball.squash.js +5 -5
  33. package/dist/emblems/emblem.api.d.ts +39 -1
  34. package/dist/emblems/emblem.api.js +20 -2
  35. package/dist/emblems/emblem.compose.d.ts +31 -0
  36. package/dist/emblems/emblem.compose.js +91 -0
  37. package/dist/emblems/emblem.core.d.ts +48 -2
  38. package/dist/emblems/emblem.core.js +30 -0
  39. package/dist/emblems/emblem.descriptions.d.ts +7 -0
  40. package/dist/emblems/emblem.descriptions.js +60 -0
  41. package/dist/emblems/emblem.display.d.ts +75 -0
  42. package/dist/emblems/emblem.display.js +214 -0
  43. package/dist/emblems/emblem.engrave.d.ts +50 -0
  44. package/dist/emblems/emblem.engrave.js +145 -0
  45. package/dist/emblems/emblem.equipment.d.ts +24 -0
  46. package/dist/emblems/emblem.equipment.js +57 -0
  47. package/dist/emblems/emblem.flip.d.ts +10 -0
  48. package/dist/emblems/emblem.flip.js +46 -0
  49. package/dist/emblems/emblem.ids.d.ts +19 -11
  50. package/dist/emblems/emblem.ids.js +21 -6
  51. package/dist/emblems/emblem.lathe.d.ts +3 -0
  52. package/dist/emblems/emblem.lathe.js +26 -4
  53. package/dist/emblems/emblem.medal.js +4 -4
  54. package/dist/emblems/emblem.pennant.d.ts +3 -0
  55. package/dist/emblems/emblem.pennant.js +79 -0
  56. package/dist/emblems/emblem.registry.js +20 -0
  57. package/dist/emblems/emblem.shapes.d.ts +19 -2
  58. package/dist/emblems/emblem.shapes.js +55 -13
  59. package/dist/emblems/emblem.sprite.d.ts +38 -0
  60. package/dist/emblems/emblem.sprite.js +65 -0
  61. package/dist/emblems/emblem.uri.d.ts +22 -0
  62. package/dist/emblems/emblem.uri.js +29 -0
  63. package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -1
  64. package/dist/emblems/gear/gear.badmintonracket.js +18 -14
  65. package/dist/emblems/gear/gear.baseballglove.d.ts +1 -1
  66. package/dist/emblems/gear/gear.baseballglove.js +20 -14
  67. package/dist/emblems/gear/gear.budo.d.ts +17 -0
  68. package/dist/emblems/gear/gear.budo.js +60 -0
  69. package/dist/emblems/gear/gear.core.d.ts +12 -31
  70. package/dist/emblems/gear/gear.core.js +61 -54
  71. package/dist/emblems/gear/gear.cricketbat.d.ts +1 -1
  72. package/dist/emblems/gear/gear.cricketbat.js +16 -14
  73. package/dist/emblems/gear/gear.fieldhockeystick.d.ts +1 -1
  74. package/dist/emblems/gear/gear.fieldhockeystick.js +15 -14
  75. package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -1
  76. package/dist/emblems/gear/gear.footballhelmet.js +15 -14
  77. package/dist/emblems/gear/gear.goaliemask.d.ts +1 -1
  78. package/dist/emblems/gear/gear.goaliemask.js +22 -16
  79. package/dist/emblems/gear/gear.goalnet.d.ts +1 -1
  80. package/dist/emblems/gear/gear.goalnet.js +24 -14
  81. package/dist/emblems/gear/gear.hockeystick.d.ts +1 -1
  82. package/dist/emblems/gear/gear.hockeystick.js +14 -14
  83. package/dist/emblems/gear/gear.index.js +5 -1
  84. package/dist/emblems/gear/gear.kendo.d.ts +2 -0
  85. package/dist/emblems/gear/gear.kendo.js +41 -0
  86. package/dist/emblems/gear/gear.kyudo.d.ts +2 -0
  87. package/dist/emblems/gear/gear.kyudo.js +31 -0
  88. package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -1
  89. package/dist/emblems/gear/gear.lacrossestick.js +25 -14
  90. package/dist/emblems/gear/gear.skates.js +1 -1
  91. package/dist/emblems/gear/gear.sticks.js +2 -2
  92. package/dist/emblems/gear/gear.sumo.d.ts +2 -0
  93. package/dist/emblems/gear/gear.sumo.js +43 -0
  94. package/dist/emblems/gear/gear.tabletennispaddle.d.ts +1 -1
  95. package/dist/emblems/gear/gear.tabletennispaddle.js +15 -14
  96. package/dist/emblems/gear/gear.tennisracket.d.ts +1 -1
  97. package/dist/emblems/gear/gear.tennisracket.js +16 -14
  98. package/dist/emblems/pennant/championship.d.ts +2 -0
  99. package/dist/emblems/pennant/championship.js +4 -0
  100. package/dist/emblems/sport/afl.js +1 -1
  101. package/dist/emblems/sport/artisticswimming.js +1 -1
  102. package/dist/emblems/sport/cfl.d.ts +1 -1
  103. package/dist/emblems/sport/cfl.js +4 -4
  104. package/dist/emblems/sport/cricket.d.ts +1 -1
  105. package/dist/emblems/sport/cricket.js +13 -4
  106. package/dist/emblems/sport/fieldhockey.js +1 -1
  107. package/dist/emblems/sport/gaelic.js +1 -1
  108. package/dist/emblems/sport/handball.d.ts +1 -1
  109. package/dist/emblems/sport/handball.js +14 -4
  110. package/dist/emblems/sport/ifaf.d.ts +1 -1
  111. package/dist/emblems/sport/ifaf.js +4 -4
  112. package/dist/emblems/sport/judo.d.ts +2 -0
  113. package/dist/emblems/sport/judo.js +13 -0
  114. package/dist/emblems/sport/karate.d.ts +2 -0
  115. package/dist/emblems/sport/karate.js +13 -0
  116. package/dist/emblems/sport/kendo.d.ts +2 -0
  117. package/dist/emblems/sport/kendo.js +14 -0
  118. package/dist/emblems/sport/kyudo.d.ts +2 -0
  119. package/dist/emblems/sport/kyudo.js +15 -0
  120. package/dist/emblems/sport/mlb.d.ts +1 -1
  121. package/dist/emblems/sport/mlb.js +27 -4
  122. package/dist/emblems/sport/nba.d.ts +1 -1
  123. package/dist/emblems/sport/nba.js +11 -4
  124. package/dist/emblems/sport/netball.js +1 -1
  125. package/dist/emblems/sport/nfl.d.ts +2 -1
  126. package/dist/emblems/sport/nfl.js +14 -4
  127. package/dist/emblems/sport/nhl.d.ts +1 -1
  128. package/dist/emblems/sport/nhl.js +12 -4
  129. package/dist/emblems/sport/pickleball.js +1 -1
  130. package/dist/emblems/sport/racquetball.js +1 -1
  131. package/dist/emblems/sport/roundnet.js +1 -1
  132. package/dist/emblems/sport/rugby.d.ts +1 -1
  133. package/dist/emblems/sport/rugby.js +11 -4
  134. package/dist/emblems/sport/sepaktakraw.js +1 -1
  135. package/dist/emblems/sport/soccer.d.ts +1 -1
  136. package/dist/emblems/sport/soccer.js +30 -4
  137. package/dist/emblems/sport/squash.js +1 -1
  138. package/dist/emblems/sport/sumo.d.ts +2 -0
  139. package/dist/emblems/sport/sumo.js +15 -0
  140. package/dist/emblems/sport/tabletennis.js +1 -1
  141. package/dist/emblems/sport/tennis.d.ts +1 -1
  142. package/dist/emblems/sport/tennis.js +14 -4
  143. package/dist/emblems/sport/teqball.js +1 -1
  144. package/dist/emblems/sport/volleyball.d.ts +1 -1
  145. package/dist/emblems/sport/volleyball.js +24 -4
  146. package/dist/emblems/trophy/banner.d.ts +2 -0
  147. package/dist/emblems/trophy/banner.js +20 -0
  148. package/dist/emblems/trophy/baseball.js +1 -0
  149. package/dist/emblems/trophy/basketball.js +1 -0
  150. package/dist/emblems/trophy/belt.js +2 -1
  151. package/dist/emblems/trophy/bowl.js +1 -0
  152. package/dist/emblems/trophy/canadian-bowl.js +1 -0
  153. package/dist/emblems/trophy/championship-banner.d.ts +2 -0
  154. package/dist/emblems/trophy/championship-banner.js +67 -0
  155. package/dist/emblems/trophy/cricket-urn.js +1 -0
  156. package/dist/emblems/trophy/cup.d.ts +1 -1
  157. package/dist/emblems/trophy/cup.js +15 -4
  158. package/dist/emblems/trophy/football.js +2 -1
  159. package/dist/emblems/trophy/globe.js +1 -0
  160. package/dist/emblems/trophy/hockey-cup.js +2 -1
  161. package/dist/emblems/trophy/lidded-cup.js +1 -0
  162. package/dist/emblems/trophy/plaque.js +2 -1
  163. package/dist/emblems/trophy/ring.d.ts +2 -0
  164. package/dist/emblems/trophy/ring.js +30 -0
  165. package/dist/emblems/trophy/rugby-cup.js +1 -0
  166. package/dist/emblems/trophy/salver.js +1 -0
  167. package/dist/emblems/trophy/soccer-ball.js +2 -1
  168. package/dist/emblems/trophy/soccer-globe.js +1 -0
  169. package/dist/emblems/trophy/sumo-cup.d.ts +2 -0
  170. package/dist/emblems/trophy/sumo-cup.js +28 -0
  171. package/dist/emblems/trophy/tennis-cup.js +2 -1
  172. package/dist/emblems/trophy/tennis-plate.js +1 -0
  173. package/dist/emblems-entry.d.ts +15 -6
  174. package/dist/emblems-entry.js +7 -2
  175. package/dist/index.d.ts +17 -0
  176. package/dist/index.js +15 -0
  177. package/dist/turn-control.d.ts +101 -0
  178. package/dist/turn-control.js +363 -0
  179. package/dist/turn-entry.d.ts +2 -0
  180. package/dist/turn-entry.js +1 -0
  181. package/dist/version.d.ts +1 -1
  182. package/dist/version.js +1 -1
  183. package/package.json +7 -1
  184. package/LICENSES/Apache-2.0.txt +0 -202
  185. package/LICENSES/CC-BY-3.0.txt +0 -319
  186. package/LICENSES/upstream/game-icons.license.txt +0 -45
  187. package/LICENSES/upstream/noto-emoji-2D-svg.LICENSE +0 -13
  188. package/LICENSES/upstream/pictogrammers-mdi.LICENSE +0 -20
  189. package/dist/emblems/emblem.material.d.ts +0 -9
  190. package/dist/emblems/emblem.material.data.d.ts +0 -34
  191. package/dist/emblems/emblem.material.data.js +0 -85
  192. package/dist/emblems/emblem.material.js +0 -23
@@ -0,0 +1,145 @@
1
+ // Engraving: text cut into a face. A plate is a box on a flat face; its lines are fitted to it (the biggest size at which the longest line and the stack of lines both fit), and the ink is chosen
2
+ // to read at 4.5 to 1 on the metal under it, the same way a crest's letters are. A plinth is a band round a body of revolution: its letters are set one by one at their angle round it, so the
3
+ // text foreshortens, goes behind the edge and comes round as the body turns. Imports only the core.
4
+ import { clean, escape, lum, mix, n2, readColour } from "./emblem.core.js";
5
+ const FONT = "system-ui,-apple-system,'Segoe UI','Hiragino Sans','Noto Sans JP','Yu Gothic',sans-serif";
6
+ /** The width of one character in em: a guess that holds for the usual sans-serif fonts, whole for Japanese and the wide forms. */
7
+ export function advance(ch) {
8
+ const c = ch.codePointAt(0);
9
+ if (c >= 0x2e80 && c <= 0xffef && !(c >= 0xff61 && c <= 0xff9f))
10
+ return 1.02;
11
+ if (ch === " ")
12
+ return 0.3;
13
+ if (/[iljI.,:;'!|]/.test(ch))
14
+ return 0.34;
15
+ if (/[mwMW]/.test(ch))
16
+ return 0.92;
17
+ if (/[A-Z]/.test(ch))
18
+ return 0.72;
19
+ if (/[0-9]/.test(ch))
20
+ return 0.62;
21
+ return 0.58;
22
+ }
23
+ /** The width of a text in em. */
24
+ export const widthOf = (text) => [...text].reduce((sum, ch) => sum + advance(ch), 0);
25
+ /** The contrast ratio of two colours, 1 to 21. */
26
+ export const contrastOf = (a, b) => (Math.max(lum(a), lum(b)) + 0.05) / (Math.min(lum(a), lum(b)) + 0.05);
27
+ /** Black or white, or a deep or pale tone of the colour itself, whichever reads at 4.5 to 1 on the colour (the best of them when none does). */
28
+ export function inkFor(main) {
29
+ const dark = lum(main) > 0.18;
30
+ const list = dark ? [mix(main, -0.72), "#000000", mix(main, 0.9), "#ffffff"] : [mix(main, 0.9), "#ffffff", mix(main, -0.72), "#000000"];
31
+ const scored = list.map((c) => [c, contrastOf(readColour(c), main)]);
32
+ return (scored.find((s) => s[1] >= 4.5) ?? scored.reduce((a, b) => (b[1] > a[1] ? b : a)))[0];
33
+ }
34
+ /** The lines to cut, as candidates (the layout takes the one that gives the biggest letters), and the year when a plate of its own holds it. */
35
+ export function linesOf(e, place) {
36
+ const yearApart = place?.yearApart === true;
37
+ if (e.free !== undefined) {
38
+ const rows = e.free.split("\n");
39
+ if (rows.length > 1)
40
+ return { main: [rows.map((text, i) => ({ text, weight: i === 0 ? 1 : i === 1 ? 0.62 : 0.5 }))], year: undefined };
41
+ const words = (e.free.match(/\S+/g) ?? [e.free]);
42
+ const cut = words.slice(1).map((_, i) => [words.slice(0, i + 1).join(" "), words.slice(i + 1).join(" ")]);
43
+ const one = [{ text: e.free, weight: 1 }];
44
+ return { main: [one, ...cut.map((pair) => pair.map((text, i) => ({ text, weight: i === 0 ? 1 : 0.62 })))], year: undefined };
45
+ }
46
+ const rank = e.rank ?? place?.rank;
47
+ const lines = [];
48
+ const big = rank !== undefined;
49
+ if (rank !== undefined)
50
+ lines.push({ text: rank, weight: 1 });
51
+ if (e.label !== undefined)
52
+ lines.push({ text: e.label, weight: big ? 0.5 : 1 });
53
+ if (e.year !== undefined && !yearApart)
54
+ lines.push({ text: e.year, weight: big ? 0.42 : 0.6 });
55
+ return { main: lines.length === 0 ? [] : [lines], year: yearApart ? e.year : undefined };
56
+ }
57
+ /** Lines fitted to a plate: each line's size and baseline, and the smallest size (the score of the fit). */
58
+ function fit(lines, p) {
59
+ const lh = 1.14;
60
+ const unit = p.h / lines.reduce((sum, l) => sum + l.weight * lh, 0);
61
+ const sizes = lines.map((l) => Math.min(unit * l.weight, (p.w * 0.96) / Math.max(widthOf(l.text), 0.4)));
62
+ const total = sizes.reduce((sum, s) => sum + s * lh, 0);
63
+ let top = p.y - total / 2;
64
+ const rows = lines.map((l, i) => {
65
+ const size = sizes[i];
66
+ const y = top + (size * lh) / 2 + size * 0.36;
67
+ top += size * lh;
68
+ return { text: l.text, size, y, width: widthOf(l.text) * size };
69
+ });
70
+ return { rows, score: Math.min(...sizes) };
71
+ }
72
+ /** The ink of an engraving: the page's ink in the line style, else the colour that reads on the metal. */
73
+ export const engravingInk = (style, main) => (style === "line" ? "currentColor" : inkFor(main));
74
+ /** Lines cut into a plate, as markup: the ink, and in the metal style a faint light under each line where the cut catches it. `candidates` are alternative line breaks, and the best fit is used. */
75
+ export function plateMarkup(candidates, plate, main, style) {
76
+ const fits = candidates.filter((c) => c.length > 0).map((c) => fit(c, plate));
77
+ if (fits.length === 0)
78
+ return "";
79
+ const best = fits.reduce((a, b) => (b.score > a.score + 0.01 ? b : a));
80
+ const text = (dx) => best.rows
81
+ .map((r) => `<text x="${n2(plate.x + dx)}" y="${n2(r.y + dx)}" font-size="${n2(r.size)}" textLength="${n2(r.width)}" lengthAdjust="spacingAndGlyphs">${escape(r.text)}</text>`)
82
+ .join("");
83
+ const head = `font-family="${FONT}" font-weight="800" text-anchor="middle" stroke="none"`;
84
+ const glint = style === "metal" ? `<g ${head} fill="${mix(main, 0.7)}" fill-opacity=".7">${text(0.3)}</g>` : "";
85
+ const marks = `${glint}<g ${head} fill="${engravingInk(style, main)}">${text(0)}</g>`;
86
+ return plate.rot === undefined ? marks : `<g transform="rotate(${plate.rot} ${n2(plate.x)} ${n2(plate.y)})">${marks}</g>`;
87
+ }
88
+ /** The letters of a text set round a body at a radius: `angle` is the turn of the body now, `place` puts a point of the surface on the screen. A letter on the far side is left out. */
89
+ export function cylinderText(text, radius, height, ink, angle, centre, place) {
90
+ const em = Math.max(widthOf(text), 0.4);
91
+ const size = Math.min(height * 0.8, (radius * 1.9) / em);
92
+ const letters = [...text];
93
+ let run = -(em * size) / 2;
94
+ let out = "";
95
+ for (const ch of letters) {
96
+ const w = advance(ch) * size;
97
+ const deg = centre + angle + ((run + w / 2) / radius) * (180 / Math.PI);
98
+ run += w;
99
+ if (ch === " ")
100
+ continue;
101
+ const [x, y, depth] = place(deg);
102
+ if (depth < 0.14)
103
+ continue;
104
+ out += `<text transform="translate(${n2(x)} ${n2(y + size * 0.36)}) scale(${n2(depth)} 1)">${escape(ch)}</text>`;
105
+ }
106
+ return out === "" ? "" : `<g font-family="${FONT}" font-weight="800" text-anchor="middle" font-size="${n2(size)}" fill="${ink}" fill-opacity=".92" stroke="none">${out}</g>`;
107
+ }
108
+ /** The letters of a text set on one flat side of a body, from one edge of the side to the other, as that side faces the eye. `a` and `b` are the two edges on the screen, `depth` how squarely the side faces. */
109
+ export function sideText(text, chord, height, ink, a, b, depth) {
110
+ if (depth < 0.14)
111
+ return "";
112
+ const em = Math.max(widthOf(text), 0.4);
113
+ const size = Math.min(height * 0.8, (chord * 0.8) / em);
114
+ const slope = (b[1] - a[1]) / (b[0] - a[0] || 1);
115
+ let run = -(em * size) / 2;
116
+ let out = "";
117
+ for (const ch of text) {
118
+ const w = advance(ch) * size;
119
+ const u = 0.5 + (run + w / 2) / chord;
120
+ run += w;
121
+ if (ch === " ")
122
+ continue;
123
+ out += `<text transform="matrix(${n2(depth)} ${n2(slope * depth)} 0 1 ${n2(a[0] + (b[0] - a[0]) * u)} ${n2(a[1] + (b[1] - a[1]) * u + size * 0.36)})">${escape(ch)}</text>`;
124
+ }
125
+ return `<g font-family="${FONT}" font-weight="800" text-anchor="middle" font-size="${n2(size)}" fill="${ink}" fill-opacity=".92" stroke="none">${out}</g>`;
126
+ }
127
+ /** The text of an engraving as one line, for a band that holds one: the free text with its lines joined, or the rank, label and year. */
128
+ export const flatText = (e) => (e.free ?? [e.rank, e.label, e.year].filter((part) => part !== undefined).join(" ")).replace(/\n/g, " ");
129
+ /** What is to be cut into a face, from the options: the free text, or the label, year and rank, and the text of the back. */
130
+ export function engravingOf(o) {
131
+ const e = {};
132
+ const free = clean(o?.engrave, 3);
133
+ const [label, year, rank, back] = [clean(o?.label), clean(o?.year), clean(o?.rank), clean(o?.engraveBack, 3)];
134
+ if (free !== undefined)
135
+ e.free = free;
136
+ if (label !== undefined)
137
+ e.label = label;
138
+ if (year !== undefined)
139
+ e.year = year;
140
+ if (rank !== undefined)
141
+ e.rank = rank;
142
+ if (back !== undefined)
143
+ e.back = back;
144
+ return e;
145
+ }
@@ -0,0 +1,24 @@
1
+ import type { BallId, GearId, MedalId, SportName, TrophyId } from "./emblem.ids.ts";
2
+ /** The equipment of one sport. */
3
+ export interface SportEquipment {
4
+ sport: SportName;
5
+ /** The ball, or the implement that stands for one (the puck, the shuttlecock, the shot). Swimming, diving, artistic swimming, skating and the Japanese sports (sumo, kendo, judo, kyudo, karate) have none. */
6
+ ball?: BallId;
7
+ /** The gear the sport has an emblem for, most telling first. Empty where the package has none yet. */
8
+ gear: GearId[];
9
+ /** The trophy that stands for the sport's cup: its own where the package draws one, otherwise a plain cup, bowl or globe. */
10
+ trophy: TrophyId;
11
+ /** The medal of the sport's champion, for a sport in the Games. */
12
+ medal?: MedalId;
13
+ }
14
+ /**
15
+ * What a sport is played and won with, as ids of the registry: `sportEquipment("nhl")` is the puck, a stick, a skate, a goalie's mask and a net, and the hockey cup. The arrays are new each call. An unknown sport is an error.
16
+ * @example
17
+ * ```ts
18
+ * import { sportEquipment } from "@johnmorrisdotca/dougu-sports/emblems";
19
+ * const nhl = sportEquipment("nhl");
20
+ * console.log(nhl.ball, nhl.trophy);
21
+ * // ball:puck trophy:hockey-cup
22
+ * ```
23
+ */
24
+ export declare function sportEquipment(sport: SportName): SportEquipment;
@@ -0,0 +1,57 @@
1
+ const GOLD = "medal:gold";
2
+ const TABLE = {
3
+ nfl: ["ball:football", ["gear:football-helmet", "gear:goalposts"], "trophy:football"],
4
+ cfl: ["ball:football-cfl", ["gear:football-helmet", "gear:goalposts"], "trophy:canadian-bowl"],
5
+ ifaf: ["ball:football-ifaf", ["gear:football-helmet", "gear:goalposts"], "trophy:cup"],
6
+ nhl: ["ball:puck", ["gear:hockey-stick", "gear:hockey-skate", "gear:goalie-mask", "gear:goal-net"], "trophy:hockey-cup"],
7
+ nba: ["ball:basketball", ["gear:basketball-hoop"], "trophy:basketball"],
8
+ mlb: ["ball:baseball", ["gear:baseball-bat", "gear:baseball-glove"], "trophy:baseball"],
9
+ soccer: ["ball:soccer", ["gear:goal-net"], "trophy:soccer-globe", GOLD],
10
+ curling: ["ball:curling-stone", ["gear:curling-broom"], "trophy:bowl", GOLD],
11
+ tennis: ["ball:tennis", ["gear:tennis-racket"], "trophy:tennis-cup", GOLD],
12
+ volleyball: ["ball:volleyball", ["gear:volleyball-net"], "trophy:cup", GOLD],
13
+ badminton: ["ball:shuttlecock-feather", ["gear:badminton-racket"], "trophy:cup", GOLD],
14
+ pickleball: ["ball:pickleball-outdoor", ["gear:pickleball-paddle"], "trophy:cup"],
15
+ tabletennis: ["ball:table-tennis", ["gear:table-tennis-paddle"], "trophy:cup", GOLD],
16
+ padel: ["ball:padel", ["gear:padel-racket"], "trophy:cup"],
17
+ squash: ["ball:squash", ["gear:squash-racket"], "trophy:cup", GOLD],
18
+ racquetball: ["ball:racquetball", ["gear:racquetball-racket"], "trophy:cup"],
19
+ sepaktakraw: ["ball:sepak-takraw", [], "trophy:cup"],
20
+ teqball: ["ball:teqball", [], "trophy:cup"],
21
+ roundnet: ["ball:roundnet", ["gear:roundnet-set"], "trophy:cup"],
22
+ netball: ["ball:netball", [], "trophy:cup"],
23
+ swimming: [undefined, ["gear:swim-goggles"], "trophy:cup", GOLD],
24
+ waterpolo: ["ball:water-polo", ["gear:swim-goggles"], "trophy:cup", GOLD],
25
+ diving: [undefined, ["gear:diving-board"], "trophy:cup", GOLD],
26
+ artisticswimming: [undefined, ["gear:swim-goggles"], "trophy:cup", GOLD],
27
+ lacrosse: ["ball:lacrosse", ["gear:lacrosse-stick"], "trophy:cup", GOLD],
28
+ rugby: ["ball:rugby", ["gear:goalposts"], "trophy:rugby-cup", GOLD],
29
+ afl: ["ball:afl", ["gear:goalposts"], "trophy:cup"],
30
+ gaelic: ["ball:sliotar", ["gear:hurley", "gear:goalposts"], "trophy:cup"],
31
+ cricket: ["ball:cricket-red", ["gear:cricket-bat", "gear:wicket"], "trophy:cricket-urn", GOLD],
32
+ fieldhockey: ["ball:field-hockey", ["gear:field-hockey-stick", "gear:goal-net"], "trophy:cup", GOLD],
33
+ handball: ["ball:handball", ["gear:goal-net"], "trophy:cup", GOLD],
34
+ athletics: ["ball:shot", ["gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles"], "trophy:laurel", GOLD],
35
+ skating: [undefined, ["gear:figure-skate", "gear:speed-skate"], "trophy:cup", GOLD],
36
+ sumo: [undefined, ["gear:gunbai", "gear:mawashi"], "trophy:sumo-cup"],
37
+ kendo: [undefined, ["gear:shinai", "gear:kendo-men"], "trophy:cup"],
38
+ judo: [undefined, ["gear:judogi", "gear:obi"], "trophy:cup", GOLD],
39
+ kyudo: [undefined, ["gear:yumi", "gear:ya"], "trophy:cup"],
40
+ karate: [undefined, ["gear:karate-gi"], "trophy:cup", GOLD],
41
+ };
42
+ /**
43
+ * What a sport is played and won with, as ids of the registry: `sportEquipment("nhl")` is the puck, a stick, a skate, a goalie's mask and a net, and the hockey cup. The arrays are new each call. An unknown sport is an error.
44
+ * @example
45
+ * ```ts
46
+ * import { sportEquipment } from "@johnmorrisdotca/dougu-sports/emblems";
47
+ * const nhl = sportEquipment("nhl");
48
+ * console.log(nhl.ball, nhl.trophy);
49
+ * // ball:puck trophy:hockey-cup
50
+ * ```
51
+ */
52
+ export function sportEquipment(sport) {
53
+ if (!Object.hasOwn(TABLE, sport))
54
+ throw new Error(`Unknown sport "${String(sport).slice(0, 40)}": the equipment is for the names in SPORT_NAMES.`);
55
+ const [ball, gear, trophy, medal] = TABLE[sport];
56
+ return { sport, ...(ball === undefined ? {} : { ball }), gear: [...gear], trophy, ...(medal === undefined ? {} : { medal }) };
57
+ }
@@ -0,0 +1,10 @@
1
+ /** What a turn needs. `line` is the line style (the rim is drawn in the page's ink), `back` is the document of the back view, and `thick` is the thickness as a fraction of the width. */
2
+ export interface FlipOptions {
3
+ yaw: number;
4
+ pitch: number;
5
+ line?: boolean;
6
+ back?: string;
7
+ thick?: number;
8
+ }
9
+ /** A document turned by `yaw` and `pitch` degrees from its still drawing. With no turn it is the document itself. */
10
+ export declare function flipSvg(svg: string, o: FlipOptions): string;
@@ -0,0 +1,46 @@
1
+ // The coin-flip turn for any flat drawing: it takes a finished document and returns it turned. The face is squeezed by the cosine of the yaw and shows its back past a quarter turn, and the rim
2
+ // (the thickness of the sheet) shows as a dark edge on the side that is turning away, widest when the drawing is edge on. A piece with no drawing of its own for the back (a bat, a racket, a pictogram)
3
+ // shows its front mirrored and a little darker, as the other side of a cut-out; a pennant passes the drawing of its back. A pure function of strings and numbers; it imports nothing.
4
+ const RAD = Math.PI / 180;
5
+ const n2 = (value) => String(Math.round(value * 100) / 100 + 0);
6
+ /** The parts of a document of SVG: its opening tag, its titles, and the rest. */
7
+ function split(svg) {
8
+ const open = /^<svg[^>]*>/.exec(svg)?.[0] ?? "";
9
+ const after = svg.slice(open.length, svg.length - "</svg>".length);
10
+ const head = /^<title>[\s\S]*?<\/title>(?:<desc>[\s\S]*?<\/desc>)?/.exec(after)?.[0] ?? "";
11
+ const box = (/viewBox="([^"]*)"/.exec(open)?.[1] ?? "0 0 64 64").split(/\s+/).map(Number);
12
+ return { open, head, rest: after.slice(head.length), box: [box[0] ?? 0, box[1] ?? 0, box[2] || 64, box[3] || 64] };
13
+ }
14
+ /** A document turned by `yaw` and `pitch` degrees from its still drawing. With no turn it is the document itself. */
15
+ export function flipSvg(svg, o) {
16
+ if (o.yaw === 0 && o.pitch === 0)
17
+ return svg;
18
+ const across = Math.cos(o.yaw * RAD);
19
+ const tilt = Math.cos(o.pitch * RAD);
20
+ const front = split(svg);
21
+ const showBack = across < 0;
22
+ const own = showBack && o.back !== undefined ? split(o.back) : undefined;
23
+ const face = own ?? front;
24
+ const [x0, y0, w, h] = front.box;
25
+ const sx = Math.max(Math.abs(across), 0.02);
26
+ const sy = Math.max(Math.abs(tilt), 0.02);
27
+ const thick = w * (o.thick ?? 0.035);
28
+ // A turn to a positive yaw carries the front toward the right, as it does for a ball and a cup (and for a drag to the right), so the rim is seen on the left, on the edge that comes toward the eye.
29
+ const dx = -thick * Math.sin(o.yaw * RAD);
30
+ const dy = -thick * Math.sin(o.pitch * RAD) * 0.6;
31
+ const mirror = showBack && own === undefined;
32
+ const cx = x0 + w / 2;
33
+ const cy = y0 + h / 2;
34
+ // The turned face, then the filter that adds the rim behind it (and dims a mirrored back).
35
+ // The id names everything the definition depends on (its region comes from the box), so two drawings in one page that share an id share one filter.
36
+ const id = `dgf${o.line === true ? "l" : "c"}${mirror ? "b" : "f"}${Math.round(dx * 100)}_${Math.round(dy * 100)}_${[x0, y0, w, h].map((v) => Math.round(v)).join("_")}`;
37
+ const flood = o.line === true ? '<feFlood flood-color="currentColor" flood-opacity=".5"/>' : '<feFlood flood-color="#000" flood-opacity=".42"/>';
38
+ const steps = [1, 2, 3].map((k) => (Math.abs(dx) + Math.abs(dy) < 0.05 ? "" : `<feOffset in="s" dx="${n2((dx * k) / 3)}" dy="${n2((dy * k) / 3)}" result="o${k}"/>`)).join("");
39
+ const nodes = Math.abs(dx) + Math.abs(dy) < 0.05 ? "" : '<feMergeNode in="o3"/><feMergeNode in="o2"/><feMergeNode in="o1"/>';
40
+ const dim = mirror && o.line !== true ? '<feColorMatrix in="SourceGraphic" values=".8 0 0 0 0 0 .8 0 0 0 0 0 .8 0 0 0 0 0 1 0" result="d"/>' : "";
41
+ const filter = `<filter id="${id}" x="${n2(x0 - w * 0.15)}" y="${n2(y0 - h * 0.15)}" width="${n2(w * 1.3)}" height="${n2(h * 1.3)}" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">` +
42
+ `${flood}<feComposite in2="SourceAlpha" operator="in" result="s"/>${steps}${dim}<feMerge>${nodes}<feMergeNode in="${dim === "" ? "SourceGraphic" : "d"}"/></feMerge></filter>`;
43
+ const flip = mirror ? -1 : 1;
44
+ const group = `<g filter="url(#${id})"><g transform="translate(${n2(cx)} ${n2(cy)}) scale(${n2(sx * flip)} ${n2(sy)}) translate(${n2(-cx)} ${n2(-cy)})">${face.rest}</g></g>`;
45
+ return `${front.open}${front.head}<defs>${filter}</defs>${group}</svg>`;
46
+ }
@@ -1,11 +1,11 @@
1
- /** The thirty-three sports the emblems are made for: the names that `ball:<sport>`, `gear:<sport>` and `sport:<sport>` use. */
2
- export declare const SPORT_NAMES: readonly ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball", "swimming", "waterpolo", "diving", "artisticswimming", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball", "athletics", "skating"];
1
+ /** The thirty-eight sports the emblems are made for (the five Japanese ones, sumo, kendo, judo, kyudo and karate, have a pictogram and their gear, and no ball): the names that `ball:<sport>`, `gear:<sport>` and `sport:<sport>` use. */
2
+ export declare const SPORT_NAMES: readonly ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball", "swimming", "waterpolo", "diving", "artisticswimming", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball", "athletics", "skating", "sumo", "kendo", "judo", "kyudo", "karate"];
3
3
  export type SportName = (typeof SPORT_NAMES)[number];
4
- /** The awards, drawn from Material Symbols: a medal on a ribbon, a badge, a crown, a star and a shield. */
4
+ /** The awards: a medal on a ribbon, a badge, a crown, a star and a shield. */
5
5
  export declare const AWARD_IDS: readonly ["award:medal", "award:badge", "award:crown", "award:star", "award:shield"];
6
6
  export type AwardId = (typeof AWARD_IDS)[number];
7
7
  /** The trophies: a cup for each league's champion in a descriptive, generic form, the plain cups and dishes, and the flat plaque, belt, wreath and rosette. */
8
- export declare const TROPHY_IDS: readonly ["trophy:cup", "trophy:lidded-cup", "trophy:bowl", "trophy:salver", "trophy:globe", "trophy:hockey-cup", "trophy:basketball", "trophy:baseball", "trophy:football", "trophy:canadian-bowl", "trophy:soccer-ball", "trophy:soccer-globe", "trophy:tennis-plate", "trophy:tennis-cup", "trophy:cricket-urn", "trophy:rugby-cup", "trophy:plaque", "trophy:belt", "trophy:laurel", "trophy:rosette"];
8
+ export declare const TROPHY_IDS: readonly ["trophy:cup", "trophy:lidded-cup", "trophy:bowl", "trophy:salver", "trophy:globe", "trophy:hockey-cup", "trophy:basketball", "trophy:baseball", "trophy:football", "trophy:canadian-bowl", "trophy:soccer-ball", "trophy:soccer-globe", "trophy:tennis-plate", "trophy:tennis-cup", "trophy:cricket-urn", "trophy:rugby-cup", "trophy:plaque", "trophy:belt", "trophy:laurel", "trophy:rosette", "trophy:ring", "trophy:banner", "trophy:sumo-cup", "trophy:championship-banner"];
9
9
  export type TrophyId = (typeof TROPHY_IDS)[number];
10
10
  /** The medals on a ribbon, and the badges for the first three places. */
11
11
  export declare const MEDAL_KINDS: readonly ["gold", "silver", "bronze"];
@@ -20,23 +20,30 @@ export type CrestId = (typeof CREST_IDS)[number];
20
20
  /** The balls: one for each ball and implement the sports use, in 3D, with the official size it comes from. The balls of one family (the three crickets, the five squash balls) share a module. */
21
21
  export declare const BALL_IDS: readonly ["ball:soccer", "ball:soccer-colour", "ball:teqball", "ball:basketball", "ball:basketball-6", "ball:basketball-3x3", "ball:baseball", "ball:softball", "ball:tennis", "ball:padel", "ball:sliotar", "ball:volleyball", "ball:volleyball-8", "ball:beach-volleyball", "ball:cricket-red", "ball:cricket-white", "ball:cricket-pink", "ball:football", "ball:football-cfl", "ball:football-ifaf", "ball:rugby", "ball:afl", "ball:gaelic-football", "ball:netball", "ball:pickleball-outdoor", "ball:pickleball-indoor", "ball:squash", "ball:squash-yellow", "ball:squash-green", "ball:squash-red", "ball:squash-blue", "ball:racquetball", "ball:lacrosse", "ball:field-hockey", "ball:table-tennis", "ball:handball", "ball:water-polo", "ball:golf", "ball:puck", "ball:curling-stone", "ball:shot", "ball:discus", "ball:hammer", "ball:sepak-takraw", "ball:roundnet", "ball:shuttlecock-feather", "ball:shuttlecock-nylon"];
22
22
  export type BallId = (typeof BALL_IDS)[number];
23
- /** The pieces of gear: a piece of equipment for each sport, flat, drawn here or copied from open icon sets (NOTICE.md says which). */
24
- export declare const GEAR_IDS: readonly ["gear:hockey-stick", "gear:goalie-stick", "gear:hockey-skate", "gear:goalie-mask", "gear:goal-net", "gear:field-hockey-stick", "gear:lacrosse-stick", "gear:cricket-bat", "gear:wicket", "gear:table-tennis-paddle", "gear:badminton-racket", "gear:tennis-racket", "gear:squash-racket", "gear:racquetball-racket", "gear:padel-racket", "gear:pickleball-paddle", "gear:roundnet-set", "gear:volleyball-net", "gear:basketball-hoop", "gear:baseball-bat", "gear:baseball-glove", "gear:football-helmet", "gear:goalposts", "gear:hurley", "gear:curling-broom", "gear:figure-skate", "gear:speed-skate", "gear:diving-board", "gear:swim-goggles", "gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles"];
23
+ /** The pieces of gear: a piece of equipment for each sport, flat, drawn for this package, turning like a coin. */
24
+ export declare const GEAR_IDS: readonly ["gear:hockey-stick", "gear:goalie-stick", "gear:hockey-skate", "gear:goalie-mask", "gear:goal-net", "gear:field-hockey-stick", "gear:lacrosse-stick", "gear:cricket-bat", "gear:wicket", "gear:table-tennis-paddle", "gear:badminton-racket", "gear:tennis-racket", "gear:squash-racket", "gear:racquetball-racket", "gear:padel-racket", "gear:pickleball-paddle", "gear:roundnet-set", "gear:volleyball-net", "gear:basketball-hoop", "gear:baseball-bat", "gear:baseball-glove", "gear:football-helmet", "gear:goalposts", "gear:hurley", "gear:curling-broom", "gear:figure-skate", "gear:speed-skate", "gear:diving-board", "gear:swim-goggles", "gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles", "gear:gunbai", "gear:mawashi", "gear:shinai", "gear:kendo-men", "gear:judogi", "gear:obi", "gear:yumi", "gear:ya", "gear:karate-gi"];
25
25
  export type GearId = (typeof GEAR_IDS)[number];
26
- /** The sports' own emblems: one pictogram for each of the thirty-three. */
26
+ /** The Japanese sports and budo: sumo, kendo, judo, kyudo and karate, with the trophies and the gear that belong to them. Every one is also in its own group; this list is for showing them together. */
27
+ export declare const JAPANESE_IDS: readonly ["sport:sumo", "trophy:sumo-cup", "gear:gunbai", "gear:mawashi", "trophy:championship-banner", "sport:kendo", "gear:shinai", "gear:kendo-men", "sport:judo", "gear:judogi", "gear:obi", "sport:kyudo", "gear:yumi", "gear:ya", "sport:karate", "gear:karate-gi"];
28
+ export type JapaneseId = (typeof JAPANESE_IDS)[number];
29
+ /** The pennants: a felt championship pennant with a stitched edge. */
30
+ export declare const PENNANT_IDS: readonly ["pennant:championship"];
31
+ export type PennantId = (typeof PENNANT_IDS)[number];
32
+ /** The sports' own emblems: one pictogram for each of the thirty-eight. */
27
33
  export type SportEmblemId = `sport:${SportName}`;
28
34
  export declare const SPORT_IDS: readonly SportEmblemId[];
29
35
  /** Every emblem id. */
30
- export type EmblemId = AwardId | TrophyId | MedalId | CrestId | BallId | GearId | SportEmblemId;
36
+ export type EmblemId = AwardId | TrophyId | MedalId | CrestId | BallId | GearId | SportEmblemId | PennantId;
31
37
  /** The groups of ids, in the order a gallery shows them. */
32
38
  export declare const EMBLEM_GROUPS: {
33
39
  readonly award: readonly ["award:medal", "award:badge", "award:crown", "award:star", "award:shield"];
34
- readonly trophy: readonly ["trophy:cup", "trophy:lidded-cup", "trophy:bowl", "trophy:salver", "trophy:globe", "trophy:hockey-cup", "trophy:basketball", "trophy:baseball", "trophy:football", "trophy:canadian-bowl", "trophy:soccer-ball", "trophy:soccer-globe", "trophy:tennis-plate", "trophy:tennis-cup", "trophy:cricket-urn", "trophy:rugby-cup", "trophy:plaque", "trophy:belt", "trophy:laurel", "trophy:rosette"];
40
+ readonly trophy: readonly ["trophy:cup", "trophy:lidded-cup", "trophy:bowl", "trophy:salver", "trophy:globe", "trophy:hockey-cup", "trophy:basketball", "trophy:baseball", "trophy:football", "trophy:canadian-bowl", "trophy:soccer-ball", "trophy:soccer-globe", "trophy:tennis-plate", "trophy:tennis-cup", "trophy:cricket-urn", "trophy:rugby-cup", "trophy:plaque", "trophy:belt", "trophy:laurel", "trophy:rosette", "trophy:ring", "trophy:banner", "trophy:sumo-cup", "trophy:championship-banner"];
35
41
  readonly medal: readonly ["medal:gold", "medal:silver", "medal:bronze", "medal:place-1", "medal:place-2", "medal:place-3"];
36
42
  readonly crest: readonly ["crest:shield", "crest:roundel", "crest:diamond", "crest:pennant"];
37
43
  readonly ball: readonly ["ball:soccer", "ball:soccer-colour", "ball:teqball", "ball:basketball", "ball:basketball-6", "ball:basketball-3x3", "ball:baseball", "ball:softball", "ball:tennis", "ball:padel", "ball:sliotar", "ball:volleyball", "ball:volleyball-8", "ball:beach-volleyball", "ball:cricket-red", "ball:cricket-white", "ball:cricket-pink", "ball:football", "ball:football-cfl", "ball:football-ifaf", "ball:rugby", "ball:afl", "ball:gaelic-football", "ball:netball", "ball:pickleball-outdoor", "ball:pickleball-indoor", "ball:squash", "ball:squash-yellow", "ball:squash-green", "ball:squash-red", "ball:squash-blue", "ball:racquetball", "ball:lacrosse", "ball:field-hockey", "ball:table-tennis", "ball:handball", "ball:water-polo", "ball:golf", "ball:puck", "ball:curling-stone", "ball:shot", "ball:discus", "ball:hammer", "ball:sepak-takraw", "ball:roundnet", "ball:shuttlecock-feather", "ball:shuttlecock-nylon"];
38
- readonly gear: readonly ["gear:hockey-stick", "gear:goalie-stick", "gear:hockey-skate", "gear:goalie-mask", "gear:goal-net", "gear:field-hockey-stick", "gear:lacrosse-stick", "gear:cricket-bat", "gear:wicket", "gear:table-tennis-paddle", "gear:badminton-racket", "gear:tennis-racket", "gear:squash-racket", "gear:racquetball-racket", "gear:padel-racket", "gear:pickleball-paddle", "gear:roundnet-set", "gear:volleyball-net", "gear:basketball-hoop", "gear:baseball-bat", "gear:baseball-glove", "gear:football-helmet", "gear:goalposts", "gear:hurley", "gear:curling-broom", "gear:figure-skate", "gear:speed-skate", "gear:diving-board", "gear:swim-goggles", "gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles"];
39
- readonly sport: readonly ("sport:nfl" | "sport:cfl" | "sport:ifaf" | "sport:nhl" | "sport:nba" | "sport:mlb" | "sport:soccer" | "sport:curling" | "sport:tennis" | "sport:volleyball" | "sport:badminton" | "sport:pickleball" | "sport:tabletennis" | "sport:padel" | "sport:squash" | "sport:racquetball" | "sport:sepaktakraw" | "sport:teqball" | "sport:roundnet" | "sport:netball" | "sport:swimming" | "sport:waterpolo" | "sport:diving" | "sport:artisticswimming" | "sport:lacrosse" | "sport:rugby" | "sport:afl" | "sport:gaelic" | "sport:cricket" | "sport:fieldhockey" | "sport:handball" | "sport:athletics" | "sport:skating")[];
44
+ readonly gear: readonly ["gear:hockey-stick", "gear:goalie-stick", "gear:hockey-skate", "gear:goalie-mask", "gear:goal-net", "gear:field-hockey-stick", "gear:lacrosse-stick", "gear:cricket-bat", "gear:wicket", "gear:table-tennis-paddle", "gear:badminton-racket", "gear:tennis-racket", "gear:squash-racket", "gear:racquetball-racket", "gear:padel-racket", "gear:pickleball-paddle", "gear:roundnet-set", "gear:volleyball-net", "gear:basketball-hoop", "gear:baseball-bat", "gear:baseball-glove", "gear:football-helmet", "gear:goalposts", "gear:hurley", "gear:curling-broom", "gear:figure-skate", "gear:speed-skate", "gear:diving-board", "gear:swim-goggles", "gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles", "gear:gunbai", "gear:mawashi", "gear:shinai", "gear:kendo-men", "gear:judogi", "gear:obi", "gear:yumi", "gear:ya", "gear:karate-gi"];
45
+ readonly sport: readonly ("sport:sumo" | "sport:kendo" | "sport:judo" | "sport:kyudo" | "sport:karate" | "sport:nfl" | "sport:cfl" | "sport:ifaf" | "sport:nhl" | "sport:nba" | "sport:mlb" | "sport:soccer" | "sport:curling" | "sport:tennis" | "sport:volleyball" | "sport:badminton" | "sport:pickleball" | "sport:tabletennis" | "sport:padel" | "sport:squash" | "sport:racquetball" | "sport:sepaktakraw" | "sport:teqball" | "sport:roundnet" | "sport:netball" | "sport:swimming" | "sport:waterpolo" | "sport:diving" | "sport:artisticswimming" | "sport:lacrosse" | "sport:rugby" | "sport:afl" | "sport:gaelic" | "sport:cricket" | "sport:fieldhockey" | "sport:handball" | "sport:athletics" | "sport:skating")[];
46
+ readonly pennant: readonly ["pennant:championship"];
40
47
  };
41
48
  export type EmblemGroup = keyof typeof EMBLEM_GROUPS;
42
49
  /** Every id, in group order. */
@@ -50,6 +57,7 @@ export declare const EMBLEM_FOLDERS: {
50
57
  readonly ball: "ball";
51
58
  readonly gear: "gear";
52
59
  readonly sport: "sport";
60
+ readonly pennant: "pennant";
53
61
  };
54
62
  /** The short names of the trophies and awards that stand for a full id: `"cup"` is `"trophy:cup"` and `"crown"` is `"award:crown"`. */
55
63
  export declare const EMBLEM_ALIASES: Readonly<Record<string, EmblemId>>;
@@ -1,18 +1,20 @@
1
1
  // The ids of the emblems, in their groups, and the types they make. Nothing is imported: this file is only names, so it costs nothing to read. An id is `<group>:<name>`.
2
2
  // Each list below holds the emblems that exist. The art for the rest arrives group by group (docs/layout.md), and each arrival adds its names here and its entry to emblem.registry.ts.
3
- /** The thirty-three sports the emblems are made for: the names that `ball:<sport>`, `gear:<sport>` and `sport:<sport>` use. */
3
+ /** The thirty-eight sports the emblems are made for (the five Japanese ones, sumo, kendo, judo, kyudo and karate, have a pictogram and their gear, and no ball): the names that `ball:<sport>`, `gear:<sport>` and `sport:<sport>` use. */
4
4
  export const SPORT_NAMES = [
5
5
  "nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball",
6
6
  "swimming", "waterpolo", "diving", "artisticswimming", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball", "athletics", "skating",
7
+ "sumo", "kendo", "judo", "kyudo", "karate",
7
8
  ];
8
- /** The awards, drawn from Material Symbols: a medal on a ribbon, a badge, a crown, a star and a shield. */
9
+ /** The awards: a medal on a ribbon, a badge, a crown, a star and a shield. */
9
10
  export const AWARD_IDS = ["award:medal", "award:badge", "award:crown", "award:star", "award:shield"];
10
11
  /** The trophies: a cup for each league's champion in a descriptive, generic form, the plain cups and dishes, and the flat plaque, belt, wreath and rosette. */
11
12
  export const TROPHY_IDS = [
12
13
  "trophy:cup", "trophy:lidded-cup", "trophy:bowl", "trophy:salver", "trophy:globe",
13
14
  "trophy:hockey-cup", "trophy:basketball", "trophy:baseball", "trophy:football", "trophy:canadian-bowl", "trophy:soccer-ball", "trophy:soccer-globe",
14
15
  "trophy:tennis-plate", "trophy:tennis-cup", "trophy:cricket-urn", "trophy:rugby-cup",
15
- "trophy:plaque", "trophy:belt", "trophy:laurel", "trophy:rosette",
16
+ "trophy:plaque", "trophy:belt", "trophy:laurel", "trophy:rosette", "trophy:ring", "trophy:banner",
17
+ "trophy:sumo-cup", "trophy:championship-banner",
16
18
  ];
17
19
  /** The medals on a ribbon, and the badges for the first three places. */
18
20
  export const MEDAL_KINDS = ["gold", "silver", "bronze"];
@@ -28,13 +30,25 @@ export const BALL_IDS = [
28
30
  "ball:squash-green", "ball:squash-red", "ball:squash-blue", "ball:racquetball", "ball:lacrosse", "ball:field-hockey", "ball:table-tennis", "ball:handball", "ball:water-polo", "ball:golf",
29
31
  "ball:puck", "ball:curling-stone", "ball:shot", "ball:discus", "ball:hammer", "ball:sepak-takraw", "ball:roundnet", "ball:shuttlecock-feather", "ball:shuttlecock-nylon"
30
32
  ];
31
- /** The pieces of gear: a piece of equipment for each sport, flat, drawn here or copied from open icon sets (NOTICE.md says which). */
33
+ /** The pieces of gear: a piece of equipment for each sport, flat, drawn for this package, turning like a coin. */
32
34
  export const GEAR_IDS = [
33
35
  "gear:hockey-stick", "gear:goalie-stick", "gear:hockey-skate", "gear:goalie-mask", "gear:goal-net", "gear:field-hockey-stick", "gear:lacrosse-stick", "gear:cricket-bat", "gear:wicket",
34
36
  "gear:table-tennis-paddle", "gear:badminton-racket", "gear:tennis-racket", "gear:squash-racket", "gear:racquetball-racket", "gear:padel-racket", "gear:pickleball-paddle",
35
37
  "gear:roundnet-set", "gear:volleyball-net", "gear:basketball-hoop", "gear:baseball-bat", "gear:baseball-glove", "gear:football-helmet", "gear:goalposts", "gear:hurley",
36
- "gear:curling-broom", "gear:figure-skate", "gear:speed-skate", "gear:diving-board", "gear:swim-goggles", "gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles"
38
+ "gear:curling-broom", "gear:figure-skate", "gear:speed-skate", "gear:diving-board", "gear:swim-goggles", "gear:starting-blocks", "gear:relay-baton", "gear:javelin", "gear:hurdles",
39
+ "gear:gunbai", "gear:mawashi", "gear:shinai", "gear:kendo-men", "gear:judogi", "gear:obi", "gear:yumi", "gear:ya", "gear:karate-gi"
37
40
  ];
41
+ /** The Japanese sports and budo: sumo, kendo, judo, kyudo and karate, with the trophies and the gear that belong to them. Every one is also in its own group; this list is for showing them together. */
42
+ export const JAPANESE_IDS = [
43
+ "sport:sumo", "trophy:sumo-cup", "gear:gunbai", "gear:mawashi",
44
+ "trophy:championship-banner",
45
+ "sport:kendo", "gear:shinai", "gear:kendo-men",
46
+ "sport:judo", "gear:judogi", "gear:obi",
47
+ "sport:kyudo", "gear:yumi", "gear:ya",
48
+ "sport:karate", "gear:karate-gi",
49
+ ];
50
+ /** The pennants: a felt championship pennant with a stitched edge. */
51
+ export const PENNANT_IDS = ["pennant:championship"];
38
52
  export const SPORT_IDS = SPORT_NAMES.map((name) => `sport:${name}`);
39
53
  /** The groups of ids, in the order a gallery shows them. */
40
54
  export const EMBLEM_GROUPS = {
@@ -45,10 +59,11 @@ export const EMBLEM_GROUPS = {
45
59
  ball: BALL_IDS,
46
60
  gear: GEAR_IDS,
47
61
  sport: SPORT_IDS,
62
+ pennant: PENNANT_IDS,
48
63
  };
49
64
  /** Every id, in group order. */
50
65
  export const EMBLEM_IDS = Object.values(EMBLEM_GROUPS).flat();
51
66
  /** The folder under `src/emblems/` that holds a group's art, one module per item named by the id's last part: `trophy:hockey-cup` is `trophy/hockey-cup.ts`, `ball:soccer` is `ball/soccer.ts`. Each group has a folder of its own, so no two ids share a file name. */
52
- export const EMBLEM_FOLDERS = { award: "award", trophy: "trophy", medal: "medal", crest: "crest", ball: "ball", gear: "gear", sport: "sport" };
67
+ export const EMBLEM_FOLDERS = { award: "award", trophy: "trophy", medal: "medal", crest: "crest", ball: "ball", gear: "gear", sport: "sport", pennant: "pennant" };
53
68
  /** The short names of the trophies and awards that stand for a full id: `"cup"` is `"trophy:cup"` and `"crown"` is `"award:crown"`. */
54
69
  export const EMBLEM_ALIASES = Object.fromEntries([...TROPHY_IDS, ...AWARD_IDS].map((id) => [id.slice(id.indexOf(":") + 1), id]));
@@ -1,4 +1,5 @@
1
1
  import { type Emblem, type EmblemSource, type Metal } from "./emblem.core.ts";
2
+ import { type Band } from "./emblem.engrave.ts";
2
3
  /** A point of the profile: the height `y` (0 at the top of the 64 unit box), the radius `r` from the axis (x = 32), 1 for a sharp corner (a smooth run of points ends at one), and the tone of the piece that starts here. */
3
4
  export type Pt = readonly [y: number, r: number, corner?: 0 | 1, tone?: string];
4
5
  /** Something drawn on the surface, by height and angle (90 degrees is the right side, 0 faces the eye): a `ring` round the body at a height; `mer`idians from one height to another; `helix` strands that wind `turns` times; a diamond `net`; `dots` (beads) round at a height with a size; on an open top, a `disc` of a tone out to a radius and `pills`, short rounded marks, round a radius. */
@@ -52,6 +53,8 @@ export interface LatheModel {
52
53
  handles?: readonly Handle[];
53
54
  balls?: readonly Ball[];
54
55
  poles?: Poles;
56
+ /** The plinth: the band of the body (heights, the tone it is cut into, and for a body with flat sides which side) that holds an engraving, which turns with the body. */
57
+ plate?: Band;
55
58
  }
56
59
  /** The radius of the body at a height. */
57
60
  export declare function radiusAt(profile: readonly Pt[], y: number): number;
@@ -2,7 +2,8 @@
2
2
  // grooves round the body, a pattern on a dish) are placed by the angle they are turned to. One model draws the still emblem (yaw 0) and the emblem turned (`frame`), so the 2D and the turning view
3
3
  // come from the same data. The silhouette is the profile at every yaw; the light, the handles, the grooves and the seams move to where `sin(yaw)` puts them, and are drawn back to front. Plain SVG.
4
4
  // Imports only the core.
5
- import { cylinderGradient, defsOf, hexOf, mainOf, mix, orbGradient, readColour, resolve, wrap, } from "./emblem.core.js";
5
+ import { cylinderGradient, defsOf, hexOf, labelled, mainOf, mix, orbGradient, readColour, resolve, wrap, } from "./emblem.core.js";
6
+ import { cylinderText, engravingInk, engravingOf, flatText, sideText } from "./emblem.engrave.js";
6
7
  const CX = 32;
7
8
  const RAD = Math.PI / 180;
8
9
  const f = (n) => String(Math.round(n * 10) / 10 + 0);
@@ -64,7 +65,7 @@ function piecesOf(p) {
64
65
  const arc = (y, r, s, toLeft) => `Q${pt(CX, y + 2 * r * s)} ${pt(CX + (toLeft ? -r : r), y)}`;
65
66
  const ellipse = (cx, cy, rx, ry, fill, extra = "") => `<ellipse cx="${f(cx)}" cy="${f(cy)}" rx="${f(rx)}" ry="${f(Math.max(ry, 0.25))}" fill="${fill}"${extra}/>`;
66
67
  /** The geometry and paint of one drawing of the model at a yaw and a pitch. */
67
- function draw(model, r, yaw, pitch) {
68
+ function draw(model, r, o, yaw, pitch) {
68
69
  const line = r.style === "line";
69
70
  const flat = r.style === "color";
70
71
  const elevation = Math.max(0, Math.min(75, (model.tilt ?? 12) + pitch));
@@ -384,6 +385,26 @@ function draw(model, r, yaw, pitch) {
384
385
  }
385
386
  mid.push(...parts);
386
387
  }
388
+ // The engraving on the plinth: letters set round the body (or along one flat side), foreshortened and hidden as they turn away.
389
+ const plate = model.plate;
390
+ const words = plate === undefined ? "" : flatText(engravingOf(o));
391
+ if (plate !== undefined && words !== "") {
392
+ const y = (plate.y0 + plate.y1) / 2;
393
+ const solid = model.solids.find((q) => q.p[0][0] <= y && y <= q.p[q.p.length - 1][0]) ?? model.solids[0];
394
+ const radius = radiusAt(solid.p, y);
395
+ const ink = engravingInk(r.style, tone(plate.tone ?? solid.tone).rgb);
396
+ if (solid.sides === undefined)
397
+ mid.push(cylinderText(words, radius, plate.y1 - plate.y0, ink, yaw, plate.at ?? 0, (deg) => at(radius, y, deg)));
398
+ else {
399
+ const n = solid.sides;
400
+ const k = plate.face ?? 0;
401
+ const a0 = (360 * k) / n + yaw;
402
+ const a1 = (360 * (k + 1)) / n + yaw;
403
+ const [ax, ay] = at(radius, y, a0);
404
+ const [bx, by] = at(radius, y, a1);
405
+ mid.push(sideText(words, 2 * radius * Math.sin(Math.PI / n), plate.y1 - plate.y0, ink, [ax, ay], [bx, by], Math.cos(((a0 + a1) / 2) * RAD)));
406
+ }
407
+ }
387
408
  return { body: back.join("") + mid.join("") + front.join(""), defs: line || flat ? "" : defsOf(grads), edge: main.edge };
388
409
  }
389
410
  /** The twelve pentagons of a ball, as the centres of a sphere's faces: the poles and two rings of five. */
@@ -397,8 +418,9 @@ export function defineLathe(id, en, ja, source, model) {
397
418
  const r = resolve(o);
398
419
  const yaw = typeof o?.yaw === "number" && Number.isFinite(o.yaw) ? o.yaw % 360 : 0;
399
420
  const pitch = typeof o?.pitch === "number" && Number.isFinite(o.pitch) ? Math.max(-40, Math.min(60, o.pitch)) : 0;
400
- const d = draw(model, r, yaw, pitch);
421
+ const d = draw(model, r, o, yaw, pitch);
401
422
  return wrap({ title: en, size: r.size, style: r.style, theme: r.theme, defs: d.defs, body: d.body, edge: d.edge, width: r.style === "line" ? 2.3 : 0.6 });
402
423
  };
403
- return { id, en, ja, source, kind: "lathe", model, svg: (o) => frame(o), frame };
424
+ const still = labelled(en, ja, (o) => frame(o));
425
+ return { id, en, ja, source, kind: "lathe", model, svg: still, frame: labelled(en, ja, frame) };
404
426
  }
@@ -23,16 +23,16 @@ export function defineMedal(id, en, ja, metal) {
23
23
  const front = [
24
24
  ...ribbon(),
25
25
  ...disc,
26
- ["m", starPath(32, 43, 9, 3.9)],
26
+ ["M", starPath(32, 43, 9, 3.9)],
27
27
  ["l", "M19.2 40a13.4 13.4 0 0 1 9.4-10.4 14 14 0 0 0-6.6 12.6z"],
28
28
  ];
29
- const back = [...disc, ["e", circlePath(32, 43, 8.4), "1.4"]];
30
- return defineFlat(id, en, ja, "ours", { metal, front, back, rim: 3 });
29
+ const back = [...disc, ["E", circlePath(32, 43, 8.4), "1.4"]];
30
+ return defineFlat(id, en, ja, "ours", { metal, front, back, rim: 3, engrave: { front: { x: 32, y: 43, w: 19, h: 15 }, back: { x: 32, y: 43, w: 20, h: 13 } } });
31
31
  }
32
32
  /** The numerals 1, 2 and 3 as strokes. */
33
33
  const NUMERALS = { 1: "M26 28l7-5v20", 2: "M25 28c1-9 15-9 15 0 0 6-14 9-15 15h16", 3: "M25 26c4-6 14-4 13 2-1 5-6 6-9 6 3 0 9 1 9 7 0 8-14 9-16 2" };
34
34
  /** A plain badge for a place: a bevelled disc with its numeral. */
35
35
  export function definePlace(id, en, ja, place, metal) {
36
36
  const base = [["o", circlePath(32, 32, 27.5)], ["e", circlePath(32, 32, 24.2), "2.2"]];
37
- return defineFlat(id, en, ja, "ours", { metal, front: [...base, ["d", NUMERALS[place]], ["l", "M10.4 28a22 22 0 0 1 16.6-17 24 24 0 0 0-11 22z"]], back: [...base, ["e", circlePath(32, 32, 14), "1.6"]], rim: 3 });
37
+ return defineFlat(id, en, ja, "ours", { metal, front: [...base, ["D", NUMERALS[place]], ["l", "M10.4 28a22 22 0 0 1 16.6-17 24 24 0 0 0-11 22z"]], back: [...base, ["E", circlePath(32, 32, 14), "1.6"]], rim: 3, engrave: { front: { x: 32, y: 32, w: 30, h: 28 }, back: { x: 32, y: 32, w: 30, h: 20 }, rank: String(place) } });
38
38
  }
@@ -0,0 +1,3 @@
1
+ import { type Emblem } from "./emblem.core.ts";
2
+ /** A pennant: its front with the lettering, turned like a coin to its plain back. */
3
+ export declare function definePennant(id: string, en: string, ja: string): Emblem<null>;