@johnmorrisdotca/dougu-sports 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@ All notable changes to this project are written here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.27.0] - 2026-10-30
10
+
11
+ A new NHL award, and two of the new cups redrawn closer to the photographs.
12
+
13
+ ### Added
14
+
15
+ - **`trophy:foundation-player-award`**: the NHL Foundation Player Award, a tall block of clear crystal a little more than twice as tall as it is wide, with two long V-cut facets from its top corners down to a point near the bottom centre, drawn as two bright reflecting triangles, with the thin bright strips of the glass edges. Measured on photographs John supplied (looked at, never copied, not committed). The text is left off: plain glass. 2.6 kB of markup in the metal style, 0.5 kB in line, 1.2 kB in colour; the block turns with a solid glass edge and its depth, and shows its back mirrored.
16
+ - **Cut-outs and ribbons for a body of revolution** (`src/emblems/lathe.sculpt.ts`, `parts` and `sculptor` in a lathe model): a flat silhouette drawn from an original path and extruded, standing in a plane that turns with the body (it squeezes with the turn, shows its back and a solid edge, and clear glass is a convex slab), and a ribbon that twists as it rises. A model with no parts carries none of the code; a cup's bundle grows by about 20 bytes gzipped for the hook (the `lathe` size limit is 7.3 kB, was 7.1). No new runtime dependency.
17
+ - 335 emblems, 79 trophies, 55 named.
18
+
19
+ ### Fixed
20
+
21
+ - **`trophy:kelly-cup`**: the bowl is a straight-sided beaker as tall as it is wide, with two raised ring bands a third of the way down, a flat shelf under it, a short thick neck and a domed foot with a ring, on the first black drum; the handles are thinner straps. Compared with the photograph at 1:1.
22
+ - **`trophy:calder-cup`**: the bowl is a rounder, wider hemisphere under its laurel band, over a short chunky stem (a collar, a round knob and a narrow waist) and a stepped foot; the knob and foot were measured on John's photographs and the Commons photograph. The line style's stem follows the knob.
23
+
9
24
  ## [0.26.0] - 2026-10-30
10
25
 
11
26
  Four league championship trophies, each measured on a photograph of the real one, for the big-game cards of a results site.
package/README.md CHANGED
@@ -110,7 +110,7 @@ The ball and the gear of every sport, trophies, medals, crests and the icon of e
110
110
 
111
111
  - **On a phone**, and **turned by hand**: drag, arrow keys or the automatic turn ([turning](./docs/turning.md)).
112
112
 
113
- > **Status: preview.** 334 emblems: 78 trophies, 6 awards, 6 medals, 4 crests, a pictogram for each of the 42 sports, 118 balls and implements drawn in 3D, 8 printed pucks ([docs/pucks.md](./docs/pucks.md)), 71 pieces of gear (among them the Japanese sports and budo, [docs/japanese-sports.md](./docs/japanese-sports.md)) and a championship pennant; [docs/layout.md](./docs/layout.md) says where each goes. All of it is drawn for this package: no art is copied from another project.
113
+ > **Status: preview.** 335 emblems: 79 trophies, 6 awards, 6 medals, 4 crests, a pictogram for each of the 42 sports, 118 balls and implements drawn in 3D, 8 printed pucks ([docs/pucks.md](./docs/pucks.md)), 71 pieces of gear (among them the Japanese sports and budo, [docs/japanese-sports.md](./docs/japanese-sports.md)) and a championship pennant; [docs/layout.md](./docs/layout.md) says where each goes. All of it is drawn for this package: no art is copied from another project.
114
114
 
115
115
  ## In 30 seconds
116
116
 
@@ -676,7 +676,7 @@ Bug reports and ideas go in the [issues](https://github.com/johnmorrisdotca/doug
676
676
 
677
677
  ## Changes
678
678
 
679
- See [CHANGELOG.md](./CHANGELOG.md). 0.26.0 adds four league championship trophies, drawn from photographs of the real ones: the Calder Cup of the AHL, the Kelly Cup of the ECHL, the MLS Cup and the College Football Playoff trophy (`trophy:calder-cup`, `trophy:kelly-cup`, `trophy:mls-cup`, `trophy:cfp-trophy`, 334 emblems); 0.25.0 adds two of baseball's awards, drawn from photographs of the real ones: the Hank Aaron Award and the Relief Man Award (`trophy:hank-aaron-award`, `trophy:relief-man-award`, 330 emblems); 0.24.0 adds a baseball cap (`gear:baseball-cap`, 328 emblems); 0.23.0 adds the Bill Russell Finals MVP Trophy, drawn from a photograph of the real one (`trophy:bill-russell-trophy`, 327 emblems); 0.22.0 takes out the award cups and the cap of the unpublished 0.17.0 to 0.21.0, which did not match the real objects; 0.16.0 adds a slotted plastic practice baseball (`ball:plastic-baseball-slotted`, 326 emblems: [docs/slotted-ball.md](./docs/slotted-ball.md)); 0.15.0 gives the pickleballs real holes, cut through the shell and drawn with the inside of the ball behind them, so the page shows through where two holes line up ([docs/pickleball.md](./docs/pickleball.md)); 0.14.0 lets the auto-turn go either way (`direction: "clockwise"` or `"counterclockwise"`, `setDirection()`); 0.13.0 adds a pictogram for each of four combat sports, MMA (a cage), boxing (a glove), wrestling (two wrestlers on the mat) and professional wrestling (a ring with ropes), in the three styles (326 emblems); 0.12.0 adds forty-seven named trophies, each a close likeness measured on a photograph (321 emblems); 0.11.0 gives the era soccer balls neutral ids (the old ones stay as aliases), new looks and a second 1980s ball (274 emblems); 0.10.0 adds a standalone ribbon and the nets and posts of sepak takraw, netball and teqball from their rulebooks' sizes (273 emblems); 0.9.0 lets the baseball, the softball and the classic soccer balls be painted and printed: solid and two-tone colours, a logo of your own wrapped onto the ball, stamped text, an original cursive word and a small mark, all turning with the ball ([docs/custom-balls.md](./docs/custom-balls.md)); 0.8.0 makes `bindTurn` light: it takes an emblem object and then needs no registry, so a turnable cup is about 9.4 kB gzipped instead of about 81 kB, and an id loads the registry on demand; 0.7.3 turns the build, docs and picture scripts, the browser tests and the demo's source from JavaScript into TypeScript, with no change to the package; 0.7.2 makes every docs page single-language (a page in Japanese beside each page that had Japanese words in it, and a test that holds them apart), with no change to the pictures; 0.7.1 puts real countries (Canada, Japan) in the crest and puck examples in place of made-up teams, with no change to the code; 0.7.0 gives the national volleyballs layouts that tell them apart (Japan a crimson band, Poland white above and red below, the USA a navy field), strengthens the 2010-style grooved soccer ball, and adds six pictures of how the judo belt is tied, `gear:obi-step-1` to `gear:obi-step-6` (269 emblems: [docs/obi-steps.md](./docs/obi-steps.md)); 0.6.0 gives the judo suit and the karate suit their sleeves (a judogi has no short ones: they reach the wrist) and adds the judogi seen from behind with the IJF-style back number, a family name and a country code on a patch, with a `backnumber` option (269 emblems: [docs/judogi-back.md](./docs/judogi-back.md)); 0.5.1 redraws the judo suit, the belt and the football pictograms and adds the blue and folded suits and the belts of every grade (261 emblems: [docs/japanese-sports.md](./docs/japanese-sports.md)); 0.5.0 adds fifty balls: vintage leather soccer balls, era-style and shop-wall soccer balls, baseballs of other threads and volleyballs in national colours, with a `colors` option (244 emblems: [docs/vintage-balls.md](./docs/vintage-balls.md)); 0.4.0 adds eight printed pucks and a `face` option (194 emblems: [docs/pucks.md](./docs/pucks.md)); 0.3.0 adds nineteen soccer balls (26, 18, 14, 12 and 6 panels, winter orange and high-visibility yellow, futsal and beach soccer: 186 emblems); 0.2.1 draws every ball and implement to the rulebook's proportions; 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
679
+ See [CHANGELOG.md](./CHANGELOG.md). 0.27.0 adds the NHL Foundation Player Award (`trophy:foundation-player-award`, 335 emblems) and redraws the Kelly Cup and the Calder Cup closer to their photographs; 0.26.0 adds four league championship trophies, drawn from photographs of the real ones: the Calder Cup of the AHL, the Kelly Cup of the ECHL, the MLS Cup and the College Football Playoff trophy (`trophy:calder-cup`, `trophy:kelly-cup`, `trophy:mls-cup`, `trophy:cfp-trophy`, 334 emblems); 0.25.0 adds two of baseball's awards, drawn from photographs of the real ones: the Hank Aaron Award and the Relief Man Award (`trophy:hank-aaron-award`, `trophy:relief-man-award`, 330 emblems); 0.24.0 adds a baseball cap (`gear:baseball-cap`, 328 emblems); 0.23.0 adds the Bill Russell Finals MVP Trophy, drawn from a photograph of the real one (`trophy:bill-russell-trophy`, 327 emblems); 0.22.0 takes out the award cups and the cap of the unpublished 0.17.0 to 0.21.0, which did not match the real objects; 0.16.0 adds a slotted plastic practice baseball (`ball:plastic-baseball-slotted`, 326 emblems: [docs/slotted-ball.md](./docs/slotted-ball.md)); 0.15.0 gives the pickleballs real holes, cut through the shell and drawn with the inside of the ball behind them, so the page shows through where two holes line up ([docs/pickleball.md](./docs/pickleball.md)); 0.14.0 lets the auto-turn go either way (`direction: "clockwise"` or `"counterclockwise"`, `setDirection()`); 0.13.0 adds a pictogram for each of four combat sports, MMA (a cage), boxing (a glove), wrestling (two wrestlers on the mat) and professional wrestling (a ring with ropes), in the three styles (326 emblems); 0.12.0 adds forty-seven named trophies, each a close likeness measured on a photograph (321 emblems); 0.11.0 gives the era soccer balls neutral ids (the old ones stay as aliases), new looks and a second 1980s ball (274 emblems); 0.10.0 adds a standalone ribbon and the nets and posts of sepak takraw, netball and teqball from their rulebooks' sizes (273 emblems); 0.9.0 lets the baseball, the softball and the classic soccer balls be painted and printed: solid and two-tone colours, a logo of your own wrapped onto the ball, stamped text, an original cursive word and a small mark, all turning with the ball ([docs/custom-balls.md](./docs/custom-balls.md)); 0.8.0 makes `bindTurn` light: it takes an emblem object and then needs no registry, so a turnable cup is about 9.4 kB gzipped instead of about 81 kB, and an id loads the registry on demand; 0.7.3 turns the build, docs and picture scripts, the browser tests and the demo's source from JavaScript into TypeScript, with no change to the package; 0.7.2 makes every docs page single-language (a page in Japanese beside each page that had Japanese words in it, and a test that holds them apart), with no change to the pictures; 0.7.1 puts real countries (Canada, Japan) in the crest and puck examples in place of made-up teams, with no change to the code; 0.7.0 gives the national volleyballs layouts that tell them apart (Japan a crimson band, Poland white above and red below, the USA a navy field), strengthens the 2010-style grooved soccer ball, and adds six pictures of how the judo belt is tied, `gear:obi-step-1` to `gear:obi-step-6` (269 emblems: [docs/obi-steps.md](./docs/obi-steps.md)); 0.6.0 gives the judo suit and the karate suit their sleeves (a judogi has no short ones: they reach the wrist) and adds the judogi seen from behind with the IJF-style back number, a family name and a country code on a patch, with a `backnumber` option (269 emblems: [docs/judogi-back.md](./docs/judogi-back.md)); 0.5.1 redraws the judo suit, the belt and the football pictograms and adds the blue and folded suits and the belts of every grade (261 emblems: [docs/japanese-sports.md](./docs/japanese-sports.md)); 0.5.0 adds fifty balls: vintage leather soccer balls, era-style and shop-wall soccer balls, baseballs of other threads and volleyballs in national colours, with a `colors` option (244 emblems: [docs/vintage-balls.md](./docs/vintage-balls.md)); 0.4.0 adds eight printed pucks and a `face` option (194 emblems: [docs/pucks.md](./docs/pucks.md)); 0.3.0 adds nineteen soccer balls (26, 18, 14, 12 and 6 panels, winter orange and high-visibility yellow, futsal and beach soccer: 186 emblems); 0.2.1 draws every ball and implement to the rulebook's proportions; 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
680
680
 
681
681
  ## Credits
682
682
 
@@ -126,6 +126,7 @@ export const EMBLEM_DESCRIPTIONS = {
126
126
  "trophy:kelly-cup": { en: "The Kelly Cup: a small two-handled silver cup with two thin rings under its lip, on a stack of three black drums with silver-edged plate bands, the lowest the widest.", ja: "ケリー・カップは、口縁の下に細い輪を2本めぐらせ、持ち手が2つある小さな銀色のカップが、銀で縁取った板の帯を巻いた3段の黒い円筒の台座の上に立つ姿を描いています。下の段ほど幅が広くなっています。ECHLの優勝チームに贈られます。" },
127
127
  "trophy:cfp-trophy": { en: "The College Football Playoff trophy: an upright dark football with a gilt rim on a tall slim gold column that narrows to a small collar, over a black drum with a plate band.", ja: "CFPトロフィーは、縁が金色で縦に立てた黒いフットボールが、背の高い細い金色の柱の上に載る姿を描いています。柱は上へ細くすぼまって小さな受け皿になり、板の帯を巻いた黒い円筒の台座の上に立っています。カレッジフットボール・プレーオフの全米王者に贈られます。" },
128
128
  "trophy:mls-cup": { en: "The MLS Cup: a tall silver goblet with a gilt rim whose bowl narrows steadily to a slim stem, on a flared round pedestal with one dark groove, with four fluted blades that sweep up and out from the stem, past the rim, and curl in over it.", ja: "MLSカップは、金色の縁をもつ背の高い銀色のゴブレットが、裾の広がった丸い台座の上に載る姿を描いています。ゴブレットの杯部は脚に向かってなだらかにすぼまり、台座には濃い色の溝が1本刻まれています。脚から立ち上がる溝入りの4枚の羽根状の飾りが、外側へ広がりながら上へ伸び、縁を越えてから内側へ巻いています。メジャーリーグサッカー(MLS)のプレーオフの優勝クラブに贈られます。" },
129
+ "trophy:foundation-player-award": { en: "The NHL Foundation Player Award: a tall block of clear crystal, a little more than twice as tall as it is wide, with two long V-cut facets from the top corners down to a point near the bottom centre.", ja: "背の高い透明なクリスタルのブロックを描いています。高さは幅の2倍強あり、正面には上の両角から下の中央近くまで伸びる、細長いV字のカット面が2つあります。ホッケーの価値観を生かして、暮らす地域の人々の生活を最も豊かにした選手に贈られます。" },
129
130
  "trophy:championship-banner": { en: "A fringed banner on a pole with a gilt finial and an embroidered border, with no lettering unless some is given", ja: "金の先端飾りの付いた竿に掛けた、刺繍の縁と房の付いた旗。文字は指定したときだけ入る" },
130
131
  "sport:sumo": { en: "Two wrestlers in the crouch they face each other in at the start of a bout, with their belts", ja: "仕切りの姿勢で向かい合う、廻しを締めた二人の力士" },
131
132
  "sport:handball": { en: "A hand throwing the ball, with lines of speed", ja: "ボールを投げる手と、速さを表す線" },
@@ -5,7 +5,7 @@ export type SportName = (typeof SPORT_NAMES)[number];
5
5
  export declare const AWARD_IDS: readonly ["award:medal", "award:badge", "award:crown", "award:star", "award:shield", "award:ribbon"];
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", "trophy:ring", "trophy:banner", "trophy:sumo-cup", "trophy:championship-banner", "trophy:paterson-cup", "trophy:stanley-cup", "trophy:hart-trophy", "trophy:vezina-trophy", "trophy:norris-trophy", "trophy:calder-trophy", "trophy:presidents-trophy", "trophy:art-ross-trophy", "trophy:lady-byng-trophy", "trophy:jennings-trophy", "trophy:selke-trophy", "trophy:king-clancy-trophy", "trophy:masterton-trophy", "trophy:jack-adams-trophy", "trophy:prince-of-wales-trophy", "trophy:campbell-bowl", "trophy:rocket-richard-trophy", "trophy:conn-smythe-trophy", "trophy:ted-lindsay-award", "trophy:lester-patrick-trophy", "trophy:lombardi-trophy", "trophy:larry-obrien-trophy", "trophy:commissioners-trophy", "trophy:grey-cup", "trophy:cy-young-award", "trophy:mlb-mvp-award", "trophy:relief-man-award", "trophy:hank-aaron-award", "trophy:bill-russell-trophy", "trophy:walter-cup", "trophy:wnba-championship-trophy", "trophy:lamar-hunt-trophy", "trophy:silver-slugger-award", "trophy:world-series-mvp-trophy", "trophy:avco-world-trophy", "trophy:obrien-cup", "trophy:gold-glove-award", "trophy:fifa-world-cup", "trophy:woodlawn-vase", "trophy:dfb-pokal", "trophy:champions-league-trophy", "trophy:webb-ellis-cup", "trophy:fa-cup", "trophy:cricket-world-cup", "trophy:wimbledon-gentlemens-trophy", "trophy:venus-rosewater-dish", "trophy:ballon-dor", "trophy:ashes-urn", "trophy:borg-warner-trophy", "trophy:americas-cup", "trophy:calder-cup", "trophy:kelly-cup", "trophy:cfp-trophy", "trophy:mls-cup"];
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", "trophy:paterson-cup", "trophy:stanley-cup", "trophy:hart-trophy", "trophy:vezina-trophy", "trophy:norris-trophy", "trophy:calder-trophy", "trophy:presidents-trophy", "trophy:art-ross-trophy", "trophy:lady-byng-trophy", "trophy:jennings-trophy", "trophy:selke-trophy", "trophy:king-clancy-trophy", "trophy:masterton-trophy", "trophy:jack-adams-trophy", "trophy:prince-of-wales-trophy", "trophy:campbell-bowl", "trophy:rocket-richard-trophy", "trophy:conn-smythe-trophy", "trophy:ted-lindsay-award", "trophy:lester-patrick-trophy", "trophy:lombardi-trophy", "trophy:larry-obrien-trophy", "trophy:commissioners-trophy", "trophy:grey-cup", "trophy:cy-young-award", "trophy:mlb-mvp-award", "trophy:relief-man-award", "trophy:hank-aaron-award", "trophy:bill-russell-trophy", "trophy:walter-cup", "trophy:wnba-championship-trophy", "trophy:lamar-hunt-trophy", "trophy:silver-slugger-award", "trophy:world-series-mvp-trophy", "trophy:avco-world-trophy", "trophy:obrien-cup", "trophy:gold-glove-award", "trophy:fifa-world-cup", "trophy:woodlawn-vase", "trophy:dfb-pokal", "trophy:champions-league-trophy", "trophy:webb-ellis-cup", "trophy:fa-cup", "trophy:cricket-world-cup", "trophy:wimbledon-gentlemens-trophy", "trophy:venus-rosewater-dish", "trophy:ballon-dor", "trophy:ashes-urn", "trophy:borg-warner-trophy", "trophy:americas-cup", "trophy:calder-cup", "trophy:kelly-cup", "trophy:cfp-trophy", "trophy:mls-cup", "trophy:foundation-player-award"];
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"];
@@ -40,7 +40,7 @@ export type EmblemId = AwardId | TrophyId | MedalId | CrestId | BallId | PuckId
40
40
  /** The groups of ids, in the order a gallery shows them. */
41
41
  export declare const EMBLEM_GROUPS: {
42
42
  readonly award: readonly ["award:medal", "award:badge", "award:crown", "award:star", "award:shield", "award:ribbon"];
43
- 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", "trophy:paterson-cup", "trophy:stanley-cup", "trophy:hart-trophy", "trophy:vezina-trophy", "trophy:norris-trophy", "trophy:calder-trophy", "trophy:presidents-trophy", "trophy:art-ross-trophy", "trophy:lady-byng-trophy", "trophy:jennings-trophy", "trophy:selke-trophy", "trophy:king-clancy-trophy", "trophy:masterton-trophy", "trophy:jack-adams-trophy", "trophy:prince-of-wales-trophy", "trophy:campbell-bowl", "trophy:rocket-richard-trophy", "trophy:conn-smythe-trophy", "trophy:ted-lindsay-award", "trophy:lester-patrick-trophy", "trophy:lombardi-trophy", "trophy:larry-obrien-trophy", "trophy:commissioners-trophy", "trophy:grey-cup", "trophy:cy-young-award", "trophy:mlb-mvp-award", "trophy:relief-man-award", "trophy:hank-aaron-award", "trophy:bill-russell-trophy", "trophy:walter-cup", "trophy:wnba-championship-trophy", "trophy:lamar-hunt-trophy", "trophy:silver-slugger-award", "trophy:world-series-mvp-trophy", "trophy:avco-world-trophy", "trophy:obrien-cup", "trophy:gold-glove-award", "trophy:fifa-world-cup", "trophy:woodlawn-vase", "trophy:dfb-pokal", "trophy:champions-league-trophy", "trophy:webb-ellis-cup", "trophy:fa-cup", "trophy:cricket-world-cup", "trophy:wimbledon-gentlemens-trophy", "trophy:venus-rosewater-dish", "trophy:ballon-dor", "trophy:ashes-urn", "trophy:borg-warner-trophy", "trophy:americas-cup", "trophy:calder-cup", "trophy:kelly-cup", "trophy:cfp-trophy", "trophy:mls-cup"];
43
+ 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", "trophy:paterson-cup", "trophy:stanley-cup", "trophy:hart-trophy", "trophy:vezina-trophy", "trophy:norris-trophy", "trophy:calder-trophy", "trophy:presidents-trophy", "trophy:art-ross-trophy", "trophy:lady-byng-trophy", "trophy:jennings-trophy", "trophy:selke-trophy", "trophy:king-clancy-trophy", "trophy:masterton-trophy", "trophy:jack-adams-trophy", "trophy:prince-of-wales-trophy", "trophy:campbell-bowl", "trophy:rocket-richard-trophy", "trophy:conn-smythe-trophy", "trophy:ted-lindsay-award", "trophy:lester-patrick-trophy", "trophy:lombardi-trophy", "trophy:larry-obrien-trophy", "trophy:commissioners-trophy", "trophy:grey-cup", "trophy:cy-young-award", "trophy:mlb-mvp-award", "trophy:relief-man-award", "trophy:hank-aaron-award", "trophy:bill-russell-trophy", "trophy:walter-cup", "trophy:wnba-championship-trophy", "trophy:lamar-hunt-trophy", "trophy:silver-slugger-award", "trophy:world-series-mvp-trophy", "trophy:avco-world-trophy", "trophy:obrien-cup", "trophy:gold-glove-award", "trophy:fifa-world-cup", "trophy:woodlawn-vase", "trophy:dfb-pokal", "trophy:champions-league-trophy", "trophy:webb-ellis-cup", "trophy:fa-cup", "trophy:cricket-world-cup", "trophy:wimbledon-gentlemens-trophy", "trophy:venus-rosewater-dish", "trophy:ballon-dor", "trophy:ashes-urn", "trophy:borg-warner-trophy", "trophy:americas-cup", "trophy:calder-cup", "trophy:kelly-cup", "trophy:cfp-trophy", "trophy:mls-cup", "trophy:foundation-player-award"];
44
44
  readonly medal: readonly ["medal:gold", "medal:silver", "medal:bronze", "medal:place-1", "medal:place-2", "medal:place-3"];
45
45
  readonly crest: readonly ["crest:shield", "crest:roundel", "crest:diamond", "crest:pennant"];
46
46
  readonly ball: readonly ["ball:soccer", "ball:soccer-colour", "ball:soccer-26", "ball:soccer-18", "ball:soccer-14", "ball:soccer-12", "ball:soccer-6", "ball:soccer-orange", "ball:soccer-26-orange", "ball:soccer-18-orange", "ball:soccer-14-orange", "ball:soccer-12-orange", "ball:soccer-6-orange", "ball:soccer-yellow", "ball:soccer-26-yellow", "ball:soccer-18-yellow", "ball:soccer-14-yellow", "ball:soccer-12-yellow", "ball:soccer-6-yellow", "ball:futsal", "ball:beach-soccer", "ball:soccer-1870-hand-stitched", "ball:soccer-1900-laced", "ball:soccer-1930-18-panel", "ball:soccer-1950-12-panel", "ball:soccer-1960-32-panel-leather", "ball:soccer-retro-pentagons", "ball:soccer-retro-leaves", "ball:soccer-retro-rosette", "ball:soccer-retro-star", "ball:soccer-retro-pinwheel", "ball:soccer-retro-kites", "ball:soccer-14-panel-double-border", "ball:soccer-8-panel-plain", "ball:soccer-6-panel-bands", "ball:soccer-6-panel-diamonds", "ball:soccer-20-panel-dots", "ball:soccer-training-high-vis", "ball:soccer-stars-blue", "ball:soccer-stars-red", "ball:soccer-stars-night", "ball:soccer-hexagons-cyan", "ball:soccer-hexagons-magenta", "ball:soccer-hexagons-lime", "ball:soccer-flames-fire", "ball:soccer-flames-ice", "ball:soccer-flames-toxic", "ball:soccer-chevrons-red", "ball:soccer-chevrons-green", "ball:soccer-chevrons-blue", "ball:soccer-swirl-blue", "ball:soccer-swirl-red", "ball:soccer-swirl-teal", "ball:soccer-metallic-gold", "ball:soccer-metallic-silver", "ball:soccer-metallic-cobalt", "ball:teqball", "ball:basketball", "ball:basketball-6", "ball:basketball-3x3", "ball:baseball", "ball:softball", "ball:baseball-lemon-peel", "ball:baseball-red-blue", "ball:baseball-black-red", "ball:baseball-vintage-leather", "ball:softball-11-optic", "ball:tennis", "ball:padel", "ball:sliotar", "ball:volleyball", "ball:volleyball-8", "ball:beach-volleyball", "ball:volleyball-colours", "ball:volleyball-brazil", "ball:volleyball-japan", "ball:volleyball-canada", "ball:volleyball-usa", "ball:volleyball-italy", "ball:volleyball-poland", "ball:volleyball-france", "ball:volleyball-serbia", "ball:volleyball-netherlands", "ball:volleyball-germany", "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:plastic-baseball-slotted", "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"];
@@ -70,6 +70,7 @@ export const TROPHY_IDS = [
70
70
  "trophy:kelly-cup",
71
71
  "trophy:cfp-trophy",
72
72
  "trophy:mls-cup",
73
+ "trophy:foundation-player-award",
73
74
  ];
74
75
  /** The medals on a ribbon, and the badges for the first three places. */
75
76
  export const MEDAL_KINDS = ["gold", "silver", "bronze"];
@@ -62,6 +62,48 @@ export interface Poles {
62
62
  /** The tops lie on a tilted ring, not a spiral: `top1` at the pole that faces the eye at rest, `top2` at the one opposite, and the sides between. */
63
63
  tilted?: boolean;
64
64
  }
65
+ /** What a drawer of `parts` is given: the turn, the tilt (the sine of the elevation), the tones and the style. */
66
+ export interface SculptCtx {
67
+ yaw: number;
68
+ s: number;
69
+ tone: (name?: string) => Tone;
70
+ line: boolean;
71
+ flat: boolean;
72
+ }
73
+ /**
74
+ * One layer of a cut-out: a tone, a path (absolute `M L H V C Q Z` in the 64 unit box) and how it is painted: by default the tone's own metal shading with an edge; `dk` and `lt` a darker and a lighter
75
+ * shade laid over it (a fold, a highlight); `ln` a thin line; `gl` clear glass; `rf` a bright reflection; `sh` a white shine. The first layer is the silhouette, and the thickness of the cut-out is the extrusion of it.
76
+ */
77
+ export type Layer = readonly [tone: string, d: string, mode?: "dk" | "lt" | "ln" | "gl" | "rf" | "sh"];
78
+ /**
79
+ * A flat cut-out with thickness that stands in a plane and turns with the body: a silhouette (a player, a jersey, a plate of glass) drawn as original paths, extruded `t` thick. `at` is where its plane stands (`x`
80
+ * to the right and `z` toward the eye, from the axis), `y` moves it down, `rot` turns the way it faces (90 faces the right side), `copies` stand more of it at other places (`[x, z, y]`). A `decal` is on a
81
+ * face of a body and is not drawn when it faces away; a cut-out that is `under` is drawn before the bodies (a clear arc behind a cup). `n` is how many steps of edge a thick one is given at most.
82
+ */
83
+ export interface Cut {
84
+ kind: "cut";
85
+ layers: readonly Layer[];
86
+ at?: readonly [x: number, z: number];
87
+ y?: number;
88
+ rot?: number;
89
+ t?: number;
90
+ decal?: boolean;
91
+ under?: boolean;
92
+ copies?: readonly (readonly [x: number, z: number, y?: number])[];
93
+ n?: number;
94
+ }
95
+ /**
96
+ * A ribbon of metal that twists as it rises: a centre line through points `[x, y, z, phi, w]` (a place in the 3D of the body, the way the width lies in the horizontal plane in degrees, and the half width),
97
+ * drawn as a strip of quadrilaterals, each shaded by which way it faces.
98
+ */
99
+ export interface Ribbon {
100
+ kind: "ribbon";
101
+ pts: readonly (readonly [x: number, y: number, z: number, phi: number, w: number])[];
102
+ tone?: string;
103
+ steps?: number;
104
+ under?: boolean;
105
+ }
106
+ export type Part = Cut | Ribbon;
65
107
  /** What a body of revolution is made of. All of it is in the 64 by 64 box with the axis at x = 32. */
66
108
  export interface LatheModel {
67
109
  metal: Metal;
@@ -87,6 +129,13 @@ export interface LatheModel {
87
129
  plate?: Band;
88
130
  /** Draws the `mesh`, `patch` and `panel` decorations of the solids (`LATHE_EXTRAS` in `lathe.extras.ts`); a body that uses none leaves it out. */
89
131
  extras?: (c: DecorCtx, d: ExtraDecor) => string;
132
+ /** Cut-outs and ribbons that stand beside the solids and turn with them (`lathe.sculpt.ts`): a player, a plate of glass, a twisting ribbon. */
133
+ parts?: readonly Part[];
134
+ /** Draws the `parts` (`LATHE_SCULPT`); a body with no parts leaves it out, and carries none of the code. */
135
+ sculptor?: (c: SculptCtx, parts: readonly Part[]) => {
136
+ under: string;
137
+ over: string;
138
+ };
90
139
  }
91
140
  /** The radius of the body at a height. */
92
141
  export declare function radiusAt(profile: readonly Pt[], y: number): number;
@@ -397,6 +397,14 @@ function draw(model, r, o, yaw, pitch) {
397
397
  }
398
398
  mid.push(...parts);
399
399
  }
400
+ // Cut-outs and ribbons: the ones behind the bodies first, then the rest in front of them.
401
+ if (model.parts !== undefined && model.sculptor !== undefined) {
402
+ const made = model.sculptor({ yaw, s, tone, line, flat }, model.parts);
403
+ if (made.under !== "")
404
+ back.unshift(made.under);
405
+ if (made.over !== "")
406
+ mid.push(made.over);
407
+ }
400
408
  // The engraving on the plinth: letters set round the body (or along one flat side), foreshortened and hidden as they turn away.
401
409
  const plate = model.plate;
402
410
  const words = plate === undefined ? "" : flatText(engravingOf(o));
@@ -540,6 +540,16 @@ export const NAMED_TROPHIES = [
540
540
  first: "1996",
541
541
  reference: "File:KC Ballet & Sporting KC (11232058043).jpg (Wikimedia Commons, CC BY 2.0), looked at and measured only; not copied.",
542
542
  },
543
+ {
544
+ id: "trophy:foundation-player-award",
545
+ name: "NHL Foundation Player Award",
546
+ sport: "hockey",
547
+ competition: "National Hockey League",
548
+ kind: "player",
549
+ honours: "The player who best applies hockey's core values of commitment, perseverance and teamwork to enrich the lives of people in his community.",
550
+ first: "2003-04",
551
+ reference: "Photographs of the 2022 and 2020 award on a dark ground, supplied by John Morris (Dropbox /Projects/Resources/dougu-sports/nhl-foundation-player/), looked at and measured only; never copied or committed; not ours to licence. No photograph on Wikimedia Commons.",
552
+ },
543
553
  ];
544
554
  /** The facts of a named trophy by its emblem id, or `undefined` for a generic trophy. */
545
555
  export function namedTrophy(id) {
@@ -73,6 +73,7 @@ import { TROPHY_CALDER_CUP } from "./trophy/calder-cup.js";
73
73
  import { TROPHY_KELLY_CUP } from "./trophy/kelly-cup.js";
74
74
  import { TROPHY_CFP_TROPHY } from "./trophy/cfp-trophy.js";
75
75
  import { TROPHY_MLS_CUP } from "./trophy/mls-cup.js";
76
+ import { TROPHY_FOUNDATION_PLAYER_AWARD } from "./trophy/foundation-player-award.js";
76
77
  import { TROPHY_WEBB_ELLIS_CUP } from "./trophy/webb-ellis-cup.js";
77
78
  import { TROPHY_FA_CUP } from "./trophy/fa-cup.js";
78
79
  import { TROPHY_CRICKET_WORLD_CUP } from "./trophy/cricket-world-cup.js";
@@ -185,6 +186,7 @@ const OWN = {
185
186
  "trophy:kelly-cup": TROPHY_KELLY_CUP,
186
187
  "trophy:cfp-trophy": TROPHY_CFP_TROPHY,
187
188
  "trophy:mls-cup": TROPHY_MLS_CUP,
189
+ "trophy:foundation-player-award": TROPHY_FOUNDATION_PLAYER_AWARD,
188
190
  "trophy:dfb-pokal": TROPHY_DFB_POKAL,
189
191
  "trophy:woodlawn-vase": TROPHY_WOODLAWN_VASE,
190
192
  "trophy:fifa-world-cup": TROPHY_FIFA_WORLD_CUP,
@@ -0,0 +1,6 @@
1
+ import type { Part, SculptCtx } from "./emblem.lathe.ts";
2
+ /** Draws the parts of a model: the ones that are `under` the bodies, and the rest, each group from far to near. */
3
+ export declare function LATHE_SCULPT(c: SculptCtx, parts: readonly Part[]): {
4
+ under: string;
5
+ over: string;
6
+ };
@@ -0,0 +1,267 @@
1
+ // The parts a body of revolution cannot be: a flat cut-out with thickness (a player, a jersey, a plate of glass) and a ribbon that twists as it rises. A cut-out is a silhouette drawn as original paths
2
+ // and extruded; it stands in a plane that turns with the body, so it squeezes by the cosine of the turn, shows its back past a quarter turn (the front mirrored and a little darker) and shows its thickness
3
+ // as a solid edge, widest when it is edge on. A ribbon is a centre line with a width that turns about it, drawn as a strip of shaded quadrilaterals. A model that wants them sets `sculptor: LATHE_SCULPT`;
4
+ // one that does not never imports this file, so its bundle carries none of the code. Plain SVG.
5
+ import { mix } from "./emblem.core.js";
6
+ const CX = 32;
7
+ const RAD = Math.PI / 180;
8
+ const f = (n) => String(Math.round(n * 10) / 10 + 0);
9
+ const CACHE = new Map();
10
+ /** The polygons of a path of absolute `M L H V C Q Z` commands, curves cut into short straight pieces. */
11
+ function polys(d) {
12
+ const known = CACHE.get(d);
13
+ if (known !== undefined)
14
+ return known;
15
+ const tok = d.match(/[MLHVCQZ]|-?\d*\.?\d+/g) ?? [];
16
+ const out = [];
17
+ let cur = [];
18
+ let cmd = "";
19
+ let x = 0;
20
+ let y = 0;
21
+ let i = 0;
22
+ const num = () => Number(tok[i++]);
23
+ while (i < tok.length) {
24
+ const t = tok[i];
25
+ if (/[A-Z]/.test(t)) {
26
+ cmd = t;
27
+ i++;
28
+ if (cmd === "Z") {
29
+ if (cur.length > 0)
30
+ out.push(cur);
31
+ cur = [];
32
+ }
33
+ continue;
34
+ }
35
+ if (cmd === "M") {
36
+ x = num();
37
+ y = num();
38
+ if (cur.length > 0)
39
+ out.push(cur);
40
+ cur = [[x, y]];
41
+ cmd = "L";
42
+ }
43
+ else if (cmd === "L") {
44
+ x = num();
45
+ y = num();
46
+ cur.push([x, y]);
47
+ }
48
+ else if (cmd === "H") {
49
+ x = num();
50
+ cur.push([x, y]);
51
+ }
52
+ else if (cmd === "V") {
53
+ y = num();
54
+ cur.push([x, y]);
55
+ }
56
+ else if (cmd === "Q") {
57
+ const [x1, y1, x2, y2] = [num(), num(), num(), num()];
58
+ for (let k = 1; k <= 4; k++) {
59
+ const u = k / 4;
60
+ cur.push([(1 - u) ** 2 * x + 2 * (1 - u) * u * x1 + u * u * x2, (1 - u) ** 2 * y + 2 * (1 - u) * u * y1 + u * u * y2]);
61
+ }
62
+ x = x2;
63
+ y = y2;
64
+ }
65
+ else if (cmd === "C") {
66
+ const [x1, y1, x2, y2, x3, y3] = [num(), num(), num(), num(), num(), num()];
67
+ for (let k = 1; k <= 5; k++) {
68
+ const u = k / 5;
69
+ const a = (1 - u) ** 3;
70
+ const b = 3 * (1 - u) ** 2 * u;
71
+ const c = 3 * (1 - u) * u * u;
72
+ const e = u ** 3;
73
+ cur.push([a * x + b * x1 + c * x2 + e * x3, a * y + b * y1 + c * y2 + e * y3]);
74
+ }
75
+ x = x3;
76
+ y = y3;
77
+ }
78
+ else {
79
+ i++;
80
+ }
81
+ }
82
+ if (cur.length > 0)
83
+ out.push(cur);
84
+ CACHE.set(d, out);
85
+ return out;
86
+ }
87
+ function drawCut(c, stand) {
88
+ const { cut, ax, az, dy } = stand;
89
+ const { yaw, s, tone, line, flat } = c;
90
+ const th = yaw * RAD;
91
+ const phi = ((cut.rot ?? 0) + yaw) * RAD;
92
+ const cosP = Math.cos(phi);
93
+ const sinP = Math.sin(phi);
94
+ const A = ax * Math.cos(th) + az * Math.sin(th);
95
+ const B = az * Math.cos(th) - ax * Math.sin(th);
96
+ if (cut.decal === true && cosP <= 0.03)
97
+ return undefined;
98
+ const t = cut.t ?? 1;
99
+ const sign = cosP >= 0 ? 1 : -1;
100
+ const at = (px, py, d) => `${f(CX + A + (px - CX) * cosP + d * sinP)} ${f(py + dy + s * (B - (px - CX) * sinP + d * cosP))}`;
101
+ const path = (d, depth) => {
102
+ let out = "";
103
+ for (const poly of polys(d))
104
+ out += `M${poly.map(([px, py]) => at(px, py, depth)).join("L")}Z`;
105
+ return out;
106
+ };
107
+ const near = (sign * t) / 2;
108
+ const first = cut.layers[0];
109
+ const base = tone(first[0]);
110
+ let svg = "";
111
+ const reach = t * Math.hypot(sinP, s * cosP);
112
+ const glass = first[2] === "gl";
113
+ if (line) {
114
+ if (reach > 1.3 && Math.abs(sinP) > 0.3)
115
+ svg += `<path d="${path(first[1], -near)}" stroke-opacity=".6"/>`;
116
+ }
117
+ else if (glass) {
118
+ // Clear glass: its sides are the hull of the front and the back (a block of glass is convex), with the far edges faint inside it.
119
+ const hull = convexHull(polys(first[1]).flat().flatMap(([px, py]) => [[CX + A + (px - CX) * cosP + near * sinP, py + dy + s * (B - (px - CX) * sinP + near * cosP)], [CX + A + (px - CX) * cosP - near * sinP, py + dy + s * (B - (px - CX) * sinP - near * cosP)]]));
120
+ svg += `<path d="M${hull.map(([x, y]) => `${f(x)} ${f(y)}`).join("L")}Z" fill="${mix(base.rgb, 0.2)}" fill-opacity=".2" stroke="${base.edge}" stroke-opacity=".5" stroke-width=".4"/>`;
121
+ if (reach > 0.6)
122
+ svg += `<path d="${path(first[1], -near)}" stroke="${base.edge}" stroke-opacity=".4" stroke-width=".4"/>`;
123
+ }
124
+ else {
125
+ // The edge: the silhouette moved a little further along the thickness, back to front, so that together they are a solid band.
126
+ const steps = reach < 0.3 ? 0 : Math.min(cut.n ?? 8, Math.ceil(reach / 0.8));
127
+ const side = flat ? mix(base.rgb, -0.3) : mix(base.rgb, -0.42);
128
+ for (let k = 0; k < steps; k++)
129
+ svg += `<path d="${path(first[1], -near + (2 * near * k) / steps)}" fill="${side}" stroke="${side}" stroke-width="${f(Math.max(0.6, (reach / steps) * 1.3))}"/>`;
130
+ }
131
+ for (const [name, d, mode] of cut.layers) {
132
+ const q = tone(name);
133
+ if (line) {
134
+ if (mode === undefined || mode === "gl" || mode === "rf")
135
+ svg += `<path d="${path(d, near)}"/>`;
136
+ else if (mode === "ln")
137
+ svg += `<path d="${path(d, near)}" stroke-width="1.1"/>`;
138
+ continue;
139
+ }
140
+ const p = path(d, near);
141
+ if (mode === undefined)
142
+ svg += `<path d="${p}" fill="${q.fill}" stroke="${q.edge}"/>`;
143
+ else if (mode === "dk")
144
+ svg += `<path d="${p}" fill="${mix(q.rgb, -0.55)}" fill-opacity=".5" stroke="none"/>`;
145
+ else if (mode === "lt")
146
+ svg += `<path d="${p}" fill="${mix(q.rgb, 0.55)}" fill-opacity=".55" stroke="none"/>`;
147
+ else if (mode === "ln")
148
+ svg += `<path d="${p}" stroke="${q.edge}" stroke-width=".5"/>`;
149
+ else if (mode === "gl")
150
+ svg += `<path d="${p}" fill="${mix(q.rgb, 0.35)}" fill-opacity=".3" stroke="${q.edge}" stroke-width=".45"/>`;
151
+ else if (mode === "rf")
152
+ svg += `<path d="${p}" fill="${q.fill}" fill-opacity=".8" stroke="${q.edge}" stroke-width=".4"/>`;
153
+ else
154
+ svg += `<path d="${p}" fill="#fff" fill-opacity=".6" stroke="none"/>`;
155
+ }
156
+ if (sign < 0 && !line)
157
+ svg += `<path d="${path(first[1], near)}" fill="#000" fill-opacity=".22" stroke="none"/>`;
158
+ return { z: B, svg };
159
+ }
160
+ /** The convex hull of some points (Andrew's monotone chain), counter-clockwise. */
161
+ function convexHull(points) {
162
+ const sorted = [...points].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
163
+ const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
164
+ const build = (list) => {
165
+ const out = [];
166
+ for (const p of list) {
167
+ while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], p) <= 0)
168
+ out.pop();
169
+ out.push(p);
170
+ }
171
+ out.pop();
172
+ return out;
173
+ };
174
+ return [...build(sorted), ...build([...sorted].reverse())];
175
+ }
176
+ const cr = (a, b, c, d, t) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t * t + (-a + 3 * b - 3 * c + d) * t * t * t);
177
+ function drawRibbon(c, r) {
178
+ const { yaw, s, tone, line } = c;
179
+ const th = yaw * RAD;
180
+ const base = tone(r.tone);
181
+ const n = r.steps ?? 14;
182
+ const k = r.pts.length;
183
+ const samples = [];
184
+ for (let i = 0; i <= n; i++) {
185
+ const u = (i / n) * (k - 1);
186
+ const j = Math.min(k - 2, Math.floor(u));
187
+ const t = u - j;
188
+ const g = (q, m) => r.pts[Math.max(0, Math.min(k - 1, q))][m];
189
+ const mixed = (m) => cr(g(j - 1, m), g(j, m), g(j + 1, m), g(j + 2, m), t);
190
+ samples.push({ x: mixed(0), y: mixed(1), z: mixed(2), ph: g(j, 3) + (g(j + 1, 3) - g(j, 3)) * t, w: g(j, 4) + (g(j + 1, 4) - g(j, 4)) * t });
191
+ }
192
+ const turned = samples.map((p) => {
193
+ const phi = (p.ph + yaw) * RAD;
194
+ const px = p.x * Math.cos(th) + p.z * Math.sin(th);
195
+ const pz = p.z * Math.cos(th) - p.x * Math.sin(th);
196
+ return { px, py: p.y, pz, wx: p.w * Math.cos(phi), wz: -p.w * Math.sin(phi) };
197
+ });
198
+ const pt = (px, py, pz) => `${f(CX + px)} ${f(py + s * pz)}`;
199
+ if (line) {
200
+ let left = "";
201
+ let right = "";
202
+ turned.forEach((p, i) => {
203
+ left += `${i === 0 ? "M" : "L"}${pt(p.px - p.wx, p.py, p.pz - p.wz)}`;
204
+ right += `${i === 0 ? "M" : "L"}${pt(p.px + p.wx, p.py, p.pz + p.wz)}`;
205
+ });
206
+ const a = turned[0];
207
+ const b = turned[n];
208
+ const ends = `M${pt(a.px - a.wx, a.py, a.pz - a.wz)}L${pt(a.px + a.wx, a.py, a.pz + a.wz)}M${pt(b.px - b.wx, b.py, b.pz - b.wz)}L${pt(b.px + b.wx, b.py, b.pz + b.wz)}`;
209
+ return { z: turned.reduce((m, p) => m + p.pz, 0) / turned.length, svg: `<path d="${left}${right}${ends}"/>` };
210
+ }
211
+ const L = [-0.5, -0.6, 0.65];
212
+ const len = Math.hypot(L[0], L[1], L[2]);
213
+ const quads = [];
214
+ for (let i = 0; i < n; i++) {
215
+ const p = turned[i];
216
+ const q = turned[i + 1];
217
+ const tx = q.px - p.px;
218
+ const ty = q.py - p.py;
219
+ const tz = q.pz - p.pz;
220
+ const wx = p.wx + q.wx;
221
+ const wz = p.wz + q.wz;
222
+ // The normal of the strip: the tangent crossed with the width (which lies flat).
223
+ const nx = ty * wz;
224
+ const ny = tz * wx - tx * wz;
225
+ const nz = -ty * wx;
226
+ const nl = Math.hypot(nx, ny, nz) || 1;
227
+ const lambert = Math.abs((nx * L[0] + ny * L[1] + nz * L[2]) / (nl * len));
228
+ const bucket = Math.round(Math.max(-0.5, Math.min(0.6, (lambert - 0.42) * 1.5)) * 8) / 8;
229
+ const colour = mix(base.rgb, bucket);
230
+ quads.push({ z: (p.pz + q.pz) / 2, d: `M${pt(p.px - p.wx, p.py, p.pz - p.wz)}L${pt(p.px + p.wx, p.py, p.pz + p.wz)}L${pt(q.px + q.wx, q.py, q.pz + q.wz)}L${pt(q.px - q.wx, q.py, q.pz - q.wz)}Z`, colour });
231
+ }
232
+ quads.sort((a, b) => a.z - b.z);
233
+ let svg = "";
234
+ let run = "";
235
+ let colour = "";
236
+ for (const q of quads) {
237
+ if (q.colour !== colour && run !== "") {
238
+ svg += `<path d="${run}" fill="${colour}" stroke="${colour}" stroke-width=".3"/>`;
239
+ run = "";
240
+ }
241
+ colour = q.colour;
242
+ run += q.d;
243
+ }
244
+ if (run !== "")
245
+ svg += `<path d="${run}" fill="${colour}" stroke="${colour}" stroke-width=".3"/>`;
246
+ return { z: turned.reduce((m, p) => m + p.pz, 0) / turned.length, svg };
247
+ }
248
+ /** Draws the parts of a model: the ones that are `under` the bodies, and the rest, each group from far to near. */
249
+ export function LATHE_SCULPT(c, parts) {
250
+ const under = [];
251
+ const over = [];
252
+ for (const part of parts) {
253
+ if (part.kind === "ribbon") {
254
+ (part.under === true ? under : over).push(drawRibbon(c, part));
255
+ continue;
256
+ }
257
+ const [x, z] = part.at ?? [0, 0];
258
+ const stands = [{ cut: part, ax: x, az: z, dy: part.y ?? 0 }, ...(part.copies ?? []).map(([cx, cz, cy]) => ({ cut: part, ax: x + cx, az: z + cz, dy: (part.y ?? 0) + (cy ?? 0) }))];
259
+ for (const stand of stands) {
260
+ const made = drawCut(c, stand);
261
+ if (made !== undefined)
262
+ (part.under === true ? under : over).push(made);
263
+ }
264
+ }
265
+ const join = (list) => list.sort((a, b) => a.z - b.z).map((p) => p.svg).join("");
266
+ return { under: join(under), over: join(over) };
267
+ }
@@ -22,9 +22,9 @@ export const TROPHY_CALDER_CUP = /* @__PURE__ */ defineLathe("trophy:calder-cup"
22
22
  {
23
23
  open: true,
24
24
  p: [
25
- [3, 9.5, 1], [3.5, 9.4], [6.2, 9.4, 1], [6.6, 8.9, 1], [7.6, 9.4], [8.5, 9.7], [10.5, 9.5], [12.4, 8.5], [14.1, 6.8], [15.4, 4.7], [16, 3.3, 1],
26
- [16.9, 3], [17.8, 3.5], [18.8, 4.1], [19.8, 4.1], [20.8, 3.6], [21.6, 2.8], [22.3, 2.7], [22.8, 4.3], [23.3, 4.4], [24.2, 5.6], [25, 6.7], [25.7, 7.3, 1], [25.8, 7.6, 1], [26.4, 7.6, 1],
27
- [26.5, 7.5, 1, "dark"], [28.5, 7.4, 0, "dark"], [29.8, 7.3, 1, "dark"],
25
+ [3, 9.5, 1], [3.5, 9.4], [6.2, 9.4, 1], [6.6, 8.9, 1], [7.6, 9.5], [8.8, 9.9], [10.6, 9.7], [12.4, 9], [14.2, 7.7], [15.7, 5.9], [16.7, 3.9], [17.3, 3, 1],
26
+ [18.1, 3], [18.7, 3.7], [19.5, 4.2], [20.5, 4.7], [21.4, 4.6], [22.1, 3.9], [22.7, 3.1], [23.3, 3], [23.8, 3.9, 1], [24.2, 4], [24.9, 5], [25.6, 5.9], [26.3, 6.4, 1],
27
+ [26.4, 7.5, 1, "dark"], [28.4, 7.4, 0, "dark"], [29.7, 7.3, 1, "dark"],
28
28
  ],
29
29
  decor: [["ring", 3.5], ["ring", 6.2], ["dots", 4.85, 30, 0.45]],
30
30
  },
@@ -32,7 +32,7 @@ export const TROPHY_CALDER_CUP = /* @__PURE__ */ defineLathe("trophy:calder-cup"
32
32
  handles: [90, -90].map((a) => ({
33
33
  a,
34
34
  w: 0.8,
35
- pts: [[4.6, 19.9], [5.4, 19.2], [6, 18], [7.1, 16.9], [9, 16], [11.2, 14.6], [13.1, 12.3], [14, 10.3], [14.5, 8], [14.1, 6], [13.1, 4.8], [11.7, 4.4], [11.1, 5.05], [11.2, 6.6], [11.7, 8.5], [11.1, 9.6], [9.4, 10.05], [8.8, 9.9]],
35
+ pts: [[4.4, 21.4], [4.7, 20.9], [5.5, 19.8], [6.2, 18.6], [7.4, 17.4], [9.4, 16.4], [11.2, 14.9], [13.1, 12.3], [14, 10.3], [14.5, 8], [14.1, 6], [13.1, 4.8], [11.7, 4.4], [11.1, 5.05], [11.2, 6.6], [11.7, 8.5], [11.1, 9.6], [9.4, 10.05], [8.8, 9.9]],
36
36
  })),
37
37
  plate: { y0: 46.5, y1: 56.5, face: 0 },
38
38
  });
@@ -0,0 +1,2 @@
1
+ /** The NHL Foundation Player Award: a tall block of clear crystal with two V-cut facets. */
2
+ export declare const TROPHY_FOUNDATION_PLAYER_AWARD: import("../emblem.core.ts").Emblem<import("../emblem.lathe.ts").LatheModel>;
@@ -0,0 +1,29 @@
1
+ // A tall clear crystal block, a rectangular prism a little more than twice as tall as it is wide, with two long V facets cut into its front from the top corners down to a point near the bottom centre, which
2
+ // catch the light as two bright triangles. Drawn for this package from photographs (docs/likeness.md); the engraved text is left out, so the glass is plain.
3
+ import { defineLathe } from "../emblem.lathe.js";
4
+ import { LATHE_SCULPT } from "../lathe.sculpt.js";
5
+ /** The NHL Foundation Player Award: a tall block of clear crystal with two V-cut facets. */
6
+ export const TROPHY_FOUNDATION_PLAYER_AWARD = /* @__PURE__ */ defineLathe("trophy:foundation-player-award", "Foundation Player Award", "NHL財団選手賞", "ours", {
7
+ metal: "silver",
8
+ sculptor: LATHE_SCULPT,
9
+ tones: { glass: "#8fb3c9", copper: "#b98660", cream: "#d9d2bc" },
10
+ turnBox: "-11.7 -11.6 87.4 87.4",
11
+ tilt: 9,
12
+ solids: [],
13
+ parts: [
14
+ {
15
+ kind: "cut",
16
+ t: 8.4,
17
+ layers: [
18
+ ["glass", "M19.2 2.4H44.8V61.8H19.2Z", "gl"],
19
+ ["copper", "M20.2 4.6L31.6 60.6H20.2Z", "rf"],
20
+ ["cream", "M43.8 4.6L32.4 60.6H43.8Z", "rf"],
21
+ ["glass", "M20.2 4.6L31.6 60.6", "ln"],
22
+ ["glass", "M43.8 4.6L32.4 60.6", "ln"],
23
+ ["glass", "M21.6 6.4V59.2H22.8V12Z", "sh"],
24
+ ["glass", "M42.4 6.4V59.2H41.2V12Z", "sh"],
25
+ ["glass", "M19.2 61.8L20.6 60.4H43.4L44.8 61.8Z", "sh"],
26
+ ],
27
+ },
28
+ ],
29
+ });
@@ -5,27 +5,28 @@ import { defineLathe } from "../emblem.lathe.js";
5
5
  export const TROPHY_KELLY_CUP = /* @__PURE__ */ defineLathe("trophy:kelly-cup", "Kelly Cup", "ケリー・カップ", "ours", {
6
6
  metal: "silver",
7
7
  box: "-1.4 -1 66.9 66.9",
8
- turnBox: "-12.8 -10.3 89.7 89.7",
8
+ turnBox: "-12.7 -10.1 89.5 89.5",
9
9
  tones: { dark: "#17181c" },
10
10
  tilt: 9,
11
11
  solids: [
12
12
  {
13
13
  open: true,
14
14
  p: [
15
- [1.7, 8.4, 1], [2.2, 8.6], [2.6, 8.4, 1], [2.9, 7.4, 1], [3.1, 7.2], [6.5, 7.2], [6.9, 7.6], [7.3, 7.2], [7.7, 7.25], [8.05, 7.6], [8.4, 7.1], [12.7, 6.8], [13.7, 6.5], [14.8, 5.75], [15.6, 4.9], [16.3, 3.7], [16.7, 3.2, 1], [17.1, 3.1],
16
- [17.8, 2.8], [18, 3.2], [19.05, 4.9], [19.9, 6], [20.3, 6.3], [20.9, 6.4, 1],
15
+ [1.7, 8.4, 1], [2.2, 8.6], [2.6, 8.4, 1], [2.9, 7.4, 1], [3.1, 7.2], [6.2, 7.2], [6.55, 7.75], [6.9, 7.2], [7.6, 7.2], [7.95, 7.8], [8.3, 7.15], [12.9, 6.95], [13.7, 6.5], [14.8, 5.65], [15.3, 5], [15.8, 4.55], [15.9, 4.2, 1], [16.4, 4.15, 1],
16
+ [16.5, 3.1, 1], [18.1, 3], [18.5, 4.4], [18.9, 5.3], [19.4, 5.9], [19.9, 6.25], [20.4, 6.4], [20.9, 6.4, 1],
17
17
  [21.1, 7, 1, "dark"], [21.6, 7.5, 1, "dark"], [21.7, 7.5, 1, "main"], [22, 7.5, 1, "main"], [22.1, 7.5, 1, "dark"], [24.2, 7.5, 1, "dark"], [24.3, 7.5, 1, "main"], [24.6, 7.5, 1, "main"],
18
- [24.7, 7.8, 1, "dark"], [25.4, 8.2, 1, "dark"], [26.8, 8.6, 1, "dark"], [27.8, 9.5, 1, "dark"], [28.6, 10.6, 1, "dark"],
18
+ [24.7, 7.8, 1, "dark"], [26.2, 8.5, 1, "dark"], [28.6, 10.6, 1, "dark"],
19
19
  [28.7, 11.35, 1, "main"], [29.1, 11.35, 1, "main"], [29.2, 11.35, 1, "dark"], [37.3, 11.4, 1, "dark"], [37.4, 11.4, 1, "main"], [37.8, 11.4, 1, "main"],
20
- [37.9, 12.2, 1, "dark"], [39.5, 13.6, 1, "dark"], [40.8, 13.7, 1, "dark"], [41.8, 14.6, 1, "dark"], [43.6, 15.4, 1, "dark"],
20
+ [37.9, 12.2, 1, "dark"], [40, 13.6, 1, "dark"], [43.6, 15.4, 1, "dark"],
21
21
  [43.7, 15.9, 1, "main"], [44.1, 15.9, 1, "main"], [44.2, 15.9, 1, "dark"], [58.2, 15.9, 1, "dark"], [58.3, 15.9, 1, "main"], [58.7, 15.9, 1, "main"],
22
22
  [58.8, 16.2, 1, "dark"], [59.6, 17.2, 1, "dark"], [61.7, 17.5, 1, "dark"],
23
23
  ],
24
+ decor: [["ring", 6.7], ["ring", 8.1]],
24
25
  },
25
26
  ],
26
27
  handles: [90, -90].map((a) => ({
27
28
  a,
28
- w: 0.8,
29
+ w: 0.42,
29
30
  pts: [[7.7, 3.9], [8.5, 3.1], [9.5, 2], [10.2, 1.1], [11.3, 1.4], [12, 2.4], [12.5, 3.9], [12.2, 6], [10.9, 8.9], [9.6, 11.1], [8.1, 13.2], [7, 14.5], [7.3, 15]],
30
31
  })),
31
32
  plate: { y0: 44.2, y1: 58.2 },
package/dist/index.d.ts CHANGED
@@ -79,6 +79,7 @@ export { TROPHY_CALDER_CUP } from "./emblems/trophy/calder-cup.ts";
79
79
  export { TROPHY_KELLY_CUP } from "./emblems/trophy/kelly-cup.ts";
80
80
  export { TROPHY_CFP_TROPHY } from "./emblems/trophy/cfp-trophy.ts";
81
81
  export { TROPHY_MLS_CUP } from "./emblems/trophy/mls-cup.ts";
82
+ export { TROPHY_FOUNDATION_PLAYER_AWARD } from "./emblems/trophy/foundation-player-award.ts";
82
83
  export { TROPHY_WEBB_ELLIS_CUP } from "./emblems/trophy/webb-ellis-cup.ts";
83
84
  export { TROPHY_FA_CUP } from "./emblems/trophy/fa-cup.ts";
84
85
  export { TROPHY_CRICKET_WORLD_CUP } from "./emblems/trophy/cricket-world-cup.ts";
package/dist/index.js CHANGED
@@ -80,6 +80,7 @@ export { TROPHY_CALDER_CUP } from "./emblems/trophy/calder-cup.js";
80
80
  export { TROPHY_KELLY_CUP } from "./emblems/trophy/kelly-cup.js";
81
81
  export { TROPHY_CFP_TROPHY } from "./emblems/trophy/cfp-trophy.js";
82
82
  export { TROPHY_MLS_CUP } from "./emblems/trophy/mls-cup.js";
83
+ export { TROPHY_FOUNDATION_PLAYER_AWARD } from "./emblems/trophy/foundation-player-award.js";
83
84
  export { TROPHY_WEBB_ELLIS_CUP } from "./emblems/trophy/webb-ellis-cup.js";
84
85
  export { TROPHY_FA_CUP } from "./emblems/trophy/fa-cup.js";
85
86
  export { TROPHY_CRICKET_WORLD_CUP } from "./emblems/trophy/cricket-world-cup.js";
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.26.0";
1
+ export declare const VERSION = "0.27.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.26.0";
1
+ export const VERSION = "0.27.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/dougu-sports",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Dougu Sports 道具, sports emblems as SVG strings: trophies, medals, crests, sport icons, and the ball and gear of every sport, in metal, line or flat colour, with optional 3D turning. Light and dark, accessible, tree-shakeable. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",