@johnmorrisdotca/dougu-sports 0.6.0 → 0.7.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,24 @@ All notable changes to this project are written here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.7.0] - 2026-10-14
10
+
11
+ Ten national volleyballs that can be told apart, a 2010-style grooved ball that reads, and six pictures of how a judo belt is tied. 269 emblems.
12
+
13
+ ### Changed
14
+
15
+ - **The national volleyballs have a layout of their own, not only their colours.** Japan, Poland, Canada, the USA, the Netherlands and Serbia were all a red with white and read alike at 16 pixels. Now each country lays its colours out differently (`VOLLEYBALL_PLANS` in `ball.volleyball.flag.ts`: 18 letters, one for each panel): Japan is white with one broad crimson band round one side, across the front and over the top; Canada is red with a white stripe down the front and the back, like the flag's pale; the USA has a navy field on two opposite sides and red and white stripes on the rest; Italy is green on one side, red on the other and striped across the front; Poland stands on a corner of the layout with white round the top and red round the bottom, as the flag is cut; France has three big fields, blue, white and red; Serbia has the flag's bands from the top down; the Netherlands has thin stripes of the three colours on every face; Germany has six big fields. Brazil and `ball:volleyball-colours` are as they were. Only colours: nothing is drawn on a ball. `colors`, `tint` and `accent` still replace the first, second and third colour wherever they sit.
16
+ - **`ball:soccer-2010-grooved` is stronger.** White and teal panels (`#0f9b8e`) instead of pale grey and mint, deep dark grooves for the seams with a lit lip, and two rings of contour lines in each panel, dark teal on the white panels and white on the teal ones. It reads on a light page, on a dark one, at 32 pixels and turned. `accent` still replaces the teal.
17
+
18
+ ### Added
19
+
20
+ - **`gear:obi-step-1` to `gear:obi-step-6`, and `obiStep(step, options)`**: how a judo belt is tied, in six close-ups of the jacket of `gear:judogi` (lapels crossed left over right, 右前) with the belt at each stage: 1 the middle of the belt against the belly, 2 wrapped twice round the waist, 3 the ends crossed in front, 4 one end tucked up under both wraps, 5 the flat square knot (本結び), 6 both ends hanging even. A red number and a few red marks say what to look at. `tint` is the jacket and `accent` the belt. Six ids rather than a `step` option, because this package makes a drawing that differs by more than a colour an id (the suits, the back views, the belts) and keeps options for colours and fill-ins. Names and captions in English and Japanese, the pages [docs/obi-steps.md](docs/obi-steps.md) and [in Japanese](docs/obi-steps.ja.md), and `JAPANESE_IDS` has 41 entries.
21
+ - Tests: every pair of national volleyballs differs by a mean CIE L\*a\*b\* distance of 35 or more with at least 7 panels clearly different, and no two share a layout; the grooved ball's contrast against a light and a dark page; the belt steps' order, their agreement with the judogi, their names and captions, and that they turn without clipping.
22
+
23
+ ### Internal
24
+
25
+ - `paintEach18`, `panels18` and `ON_A_CORNER` in the panelled family (a colour for each of the 18 panels, and the layout stood on a corner of its cube); `judogiFlat(torso)` and `belt`'s `tail2` for the belt steps; and a piece's parts that share one transform are now drawn in one group, which makes a close-up smaller.
26
+
9
27
  ## [0.6.0] - 2026-10-14
10
28
 
11
29
  A judogi has no short sleeves, and every judoka at an IJF event wears an official back number: the judo and karate drawings get their sleeves, and the judogi is drawn from behind with the back number sewn on. 263 emblems.
package/README.md CHANGED
@@ -52,7 +52,7 @@ The ball and the gear of every sport, trophies, medals, crests and the icon of e
52
52
  </tr>
53
53
  </table>
54
54
 
55
- > **Status: preview.** 263 emblems: 24 trophies, 5 awards, 6 medals, 4 crests, a pictogram for each of the 38 sports, 116 balls and implements drawn in 3D, 8 printed pucks ([docs/pucks.md](./docs/pucks.md)), 61 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.
55
+ > **Status: preview.** 269 emblems: 24 trophies, 5 awards, 6 medals, 4 crests, a pictogram for each of the 38 sports, 116 balls and implements drawn in 3D, 8 printed pucks ([docs/pucks.md](./docs/pucks.md)), 67 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.
56
56
 
57
57
  ## In 30 seconds
58
58
 
@@ -90,7 +90,7 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
90
90
  - **One module per item**, so a bundler takes only what is imported; see [Use it in your project](#use-it-in-your-project).
91
91
  - **Short names and helpers**: `"cup"` is `"trophy:cup"`, `"trophy:cup/line"` is the line style, and `crest`, `medal`, `sportIcon` and `pennant` draw a team's crest, a medal, a sport's icon and a championship pennant.
92
92
  - **Typed ids**: `EmblemId` is a union of `award:*`, `trophy:*`, `medal:*`, `crest:*`, `ball:*`, `gear:*`, `sport:*` and `pennant:*` ids.
93
- - **Japanese sports and budō** (道具 is the name): a sumo cup, a championship banner, and the war fan, belt, bamboo sword, kendo helmet, judo and karate suits, the white and blue judo suit flat, folded and seen from behind with its back number ([docs/judogi-back.md](./docs/judogi-back.md)), the belts of every grade, long bow and arrow, with Japanese names that are the governing bodies' words; see [docs/japanese-sports.md](./docs/japanese-sports.md).
93
+ - **Japanese sports and budō** (道具 is the name): a sumo cup, a championship banner, and the war fan, belt, bamboo sword, kendo helmet, judo and karate suits, the white and blue judo suit flat, folded and seen from behind with its back number ([docs/judogi-back.md](./docs/judogi-back.md)), the belts of every grade and six pictures of how the belt is tied ([docs/obi-steps.md](./docs/obi-steps.md)), long bow and arrow, with Japanese names that are the governing bodies' words; see [docs/japanese-sports.md](./docs/japanese-sports.md).
94
94
  - **Generic by design**: no emblem carries a league's, a team's or a federation's name or mark.
95
95
  - **Accessible**: every drawing is `role="img"` with a `<title>`; see [Accessibility](#accessibility).
96
96
  - **Deterministic**: the same id and options give the same string; a gradient's id comes from its colour.
@@ -416,7 +416,7 @@ An emblem is a small object: `id`, `en` and `ja` names, a `source` (who drew it)
416
416
  | `crest` | Four generic crests, tinted by a team's colours, with its letters | `src/emblems/crest/` |
417
417
  | `ball` | 116 balls and implements, in 3D, each with its official size: soccer (in 32, 26, 18, 14, 12 and 6 panels, in three colourways, with futsal and beach soccer; [docs/soccer-balls.md](./docs/soccer-balls.md); and vintage leather, era-style and patterned balls, [docs/vintage-balls.md](./docs/vintage-balls.md)), basketball, baseball, tennis, rugby, the footballs, golf, the puck, the curling stone and the rest | `src/emblems/ball/` |
418
418
  | `puck` | 8 printed pucks: the same puck with a print on its top face that turns with it (a flag and CANADA, a red leaf, stars and stripes, a player's name and number, a made-up signature, a souvenir), and a `face` option for any picture, emblem or flag code; [docs/pucks.md](./docs/pucks.md) | `src/emblems/ball/ball.puck.print.ts` |
419
- | `gear` | 61 pieces of equipment: sticks, rackets, skates, a glove, a helmet, nets and posts, a javelin, starting blocks, and for the Japanese sports a war fan, a sumo belt, a bamboo sword, a kendo helmet, the judo suit (white and blue, laid flat and folded, and from behind with its back number), the belts of every grade (white to red, the red-and-white belt, the women's black belt and the two-colour belts), a long bow, an arrow and a karate suit | `src/emblems/gear/` |
419
+ | `gear` | 67 pieces of equipment: sticks, rackets, skates, a glove, a helmet, nets and posts, a javelin, starting blocks, and for the Japanese sports a war fan, a sumo belt, a bamboo sword, a kendo helmet, the judo suit (white and blue, laid flat and folded, and from behind with its back number), the belts of every grade (white to red, the red-and-white belt, the women's black belt and the two-colour belts) and the six steps of tying one, a long bow, an arrow and a karate suit | `src/emblems/gear/` |
420
420
  | `sport` | One pictogram for each of the thirty-eight sports | `src/emblems/sport/` |
421
421
  | `pennant` | A felt championship pennant on a dowel, with a stitched edge, lettered with its words and its year | `src/emblems/pennant/` |
422
422
 
@@ -454,6 +454,7 @@ The thirty-eight sports the ball, gear and sport emblems are made for are `SPORT
454
454
  | `SPORT_NAMES` | The thirty-eight sports |
455
455
  | `JAPANESE_IDS` | The ids of the Japanese sports and budo, together: sumo, kendo, judo, kyudo and karate, with their trophies and gear ([docs/japanese-sports.md](./docs/japanese-sports.md)) |
456
456
  | `OBI_RANKS`, `obi(rank, options?)` | The colours of a belt from `white` to `red` (the kyu, black, the red-and-white belt, red), and the belt of a rank, or of a pair of ranks (a two-colour belt), drawn; `women: true` is the white stripe of the women's black belt |
457
+ | `obiStep(step, options?)`, `GEAR_OBI_STEP_1` to `GEAR_OBI_STEP_6`, `ObiStep` | One of the six steps of tying the judo belt, 1 to 6: the jacket in close-up with the belt at that stage and a red step number ([docs/obi-steps.md](./docs/obi-steps.md), [日本語](./docs/obi-steps.ja.md)); `tint` is the jacket and `accent` the belt |
457
458
  | `JUDOGI_WHITE`, `JUDOGI_BLUE` | The two colours the IJF allows a judogi to be ([docs/japanese-sports.md](./docs/japanese-sports.md)) |
458
459
  | `EMBLEM_DESCRIPTIONS` | The longer description of an emblem, in English and Japanese; `emblemInfo(id).description` is the one of an id |
459
460
  | `EMBLEM_STYLES`, `EMBLEM_INK`, `EMBLEM_PAGE`, `METALS` | The styles, the ink and page colours the themes use, and the metals |
@@ -603,7 +604,7 @@ Bug reports and ideas go in the [issues](https://github.com/johnmorrisdotca/doug
603
604
 
604
605
  ## Changes
605
606
 
606
- See [CHANGELOG.md](./CHANGELOG.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 (263 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.
607
+ See [CHANGELOG.md](./CHANGELOG.md). 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.
607
608
 
608
609
  ## Credits
609
610
 
@@ -1,11 +1,17 @@
1
- import { type V3 } from "./ball.math.ts";
1
+ import { type M3, type V3 } from "./ball.math.ts";
2
2
  import type { Surface } from "./ball.engine.ts";
3
+ /** The turn that stands the layout on a corner of its cube: the diagonal (1, 1, 1) becomes the ball's vertical axis, so the three faces round one corner are the top and the three round the opposite corner the bottom, and the ball turns about that diagonal. */
4
+ export declare const ON_A_CORNER: M3;
5
+ /** The 18 panels of a ball, painted and not outlined. `colourOf(face, strip)` is the colour of strip 0, 1 or 2 of face 0 to 5; the faces are the cube's +z, -z, +x, -x, +y and -y. */
6
+ export declare function paintEach18(s: Surface, colourOf: (face: number, strip: number) => string, rot?: M3): string;
3
7
  /** The 18 panels of a ball, painted and not outlined. `colours[i]` is the three strips of face i, or one colour for the middle strip and one for the outer two. */
4
8
  export declare function paint18(s: Surface, colours: ReadonlyArray<readonly [string, string]>): string;
5
9
  /** The seams of the 18-panel layout, each once: the lines between strips and the twelve edges of the cube. */
6
- export declare function seams18(): V3[][];
10
+ export declare function seams18(rot?: M3): V3[][];
7
11
  /** The 18 panels of a ball. `colours[i]` is the three strips of face i, or one colour for the middle strip and one for the outer two. */
8
12
  export declare function strips18(s: Surface, colours: ReadonlyArray<readonly [string, string]>, seam: string, width?: number): string;
13
+ /** The 18 panels of a ball, each with a colour of its own: `colours[face * 3 + strip]`, in the order of `paintEach18`. */
14
+ export declare function panels18(s: Surface, colours: readonly string[], seam: string, rot?: M3, width?: number): string;
9
15
  /** The 8 panels of an octahedral ball: the octants, coloured by `pick(sx, sy, sz)`. */
10
16
  export declare function octants8(s: Surface, pick: (sx: number, sy: number, sz: number) => string, seam: string, width?: number): string;
11
17
  /** A darker seam colour for a main colour. */
@@ -1,6 +1,6 @@
1
1
  // Balls made of curved panels: the 18-panel layout of a volleyball (six groups of three strips; the strips of a face run square to those of its neighbours), and the 8 panels of an octahedral ball.
2
2
  // The strips are the faces of a cube mapped onto the sphere (a straight line on a face becomes an arc of a great circle), so every seam is a true great-circle arc.
3
- import { arc, greatCircle, norm } from "./ball.math.js";
3
+ import { apply, arc, cross, dot, greatCircle, norm } from "./ball.math.js";
4
4
  import { mix } from "./ball.core.js";
5
5
  /** The six faces of the cube as: the axis the face looks along, its sign, the axis its strips run along, and the axis across them. Neighbouring faces run square to each other. */
6
6
  const FACES = [
@@ -19,21 +19,42 @@ const onFace = (f, p, q) => {
19
19
  v[f.q] = q;
20
20
  return norm(v);
21
21
  };
22
- /** The 18 panels of a ball, painted and not outlined. `colours[i]` is the three strips of face i, or one colour for the middle strip and one for the outer two. */
23
- export function paint18(s, colours) {
22
+ /** The turn that stands the layout on a corner of its cube: the diagonal (1, 1, 1) becomes the ball's vertical axis, so the three faces round one corner are the top and the three round the opposite corner the bottom, and the ball turns about that diagonal. */
23
+ export const ON_A_CORNER = (() => {
24
+ const from = norm([1, 1, 1]);
25
+ const axis = norm(cross(from, [0, 1, 0]));
26
+ const c = dot(from, [0, 1, 0]);
27
+ const s = Math.sqrt(1 - c * c);
28
+ const [x, y, z] = axis;
29
+ const k = 1 - c;
30
+ return [
31
+ [c + x * x * k, x * y * k - z * s, x * z * k + y * s],
32
+ [y * x * k + z * s, c + y * y * k, y * z * k - x * s],
33
+ [z * x * k - y * s, z * y * k + x * s, c + z * z * k],
34
+ ];
35
+ })();
36
+ const turned = (rot, v) => (rot === undefined ? v : apply(rot, v));
37
+ /** The 18 panels of a ball, painted and not outlined. `colourOf(face, strip)` is the colour of strip 0, 1 or 2 of face 0 to 5; the faces are the cube's +z, -z, +x, -x, +y and -y. */
38
+ export function paintEach18(s, colourOf, rot) {
24
39
  let out = "";
25
40
  FACES.forEach((f, fi) => {
26
- const [outer, middle] = colours[fi % colours.length];
27
41
  const bounds = [-1, -SPLIT, SPLIT, 1];
28
42
  for (let i = 0; i < 3; i++) {
29
- const ring = [onFace(f, -1, bounds[i]), onFace(f, 1, bounds[i]), onFace(f, 1, bounds[i + 1]), onFace(f, -1, bounds[i + 1])];
30
- out += s.panel(ring, i === 1 ? middle : outer, { n: 6, ink: false });
43
+ const ring = [onFace(f, -1, bounds[i]), onFace(f, 1, bounds[i]), onFace(f, 1, bounds[i + 1]), onFace(f, -1, bounds[i + 1])].map((v) => turned(rot, v));
44
+ out += s.panel(ring, colourOf(fi, i), { n: 6, ink: false });
31
45
  }
32
46
  });
33
47
  return out;
34
48
  }
49
+ /** The 18 panels of a ball, painted and not outlined. `colours[i]` is the three strips of face i, or one colour for the middle strip and one for the outer two. */
50
+ export function paint18(s, colours) {
51
+ return paintEach18(s, (fi, i) => {
52
+ const [outer, middle] = colours[fi % colours.length];
53
+ return i === 1 ? middle : outer;
54
+ });
55
+ }
35
56
  /** The seams of the 18-panel layout, each once: the lines between strips and the twelve edges of the cube. */
36
- export function seams18() {
57
+ export function seams18(rot) {
37
58
  const curves = [];
38
59
  for (const f of FACES)
39
60
  for (const q of [-SPLIT, SPLIT])
@@ -44,13 +65,18 @@ export function seams18() {
44
65
  curves.push(arc(norm([a, -1, b]), norm([a, 1, b]), 12));
45
66
  curves.push(arc(norm([-1, a, b]), norm([1, a, b]), 12));
46
67
  }
47
- return curves;
68
+ return rot === undefined ? curves : curves.map((c) => c.map((v) => turned(rot, v)));
48
69
  }
49
70
  /** The 18 panels of a ball. `colours[i]` is the three strips of face i, or one colour for the middle strip and one for the outer two. */
50
71
  export function strips18(s, colours, seam, width = 1.1) {
51
72
  const curves = seams18();
52
73
  return paint18(s, colours) + s.strokes(curves, seam, width) + s.strokes(curves, "#fff", width * 0.35, { deco: true, opacity: 0.4, dx: 0.25, dy: 0.3 });
53
74
  }
75
+ /** The 18 panels of a ball, each with a colour of its own: `colours[face * 3 + strip]`, in the order of `paintEach18`. */
76
+ export function panels18(s, colours, seam, rot, width = 1.1) {
77
+ const curves = seams18(rot);
78
+ return paintEach18(s, (fi, i) => colours[fi * 3 + i], rot) + s.strokes(curves, seam, width) + s.strokes(curves, "#fff", width * 0.35, { deco: true, opacity: 0.4, dx: 0.25, dy: 0.3 });
79
+ }
54
80
  /** The 8 panels of an octahedral ball: the octants, coloured by `pick(sx, sy, sz)`. */
55
81
  export function octants8(s, pick, seam, width = 1.1) {
56
82
  let out = "";
@@ -11,7 +11,7 @@ export declare const BALL_SOCCER_1998_COLOURED_TRIAD: import("./ball.core.ts").P
11
11
  export declare const BALL_SOCCER_2002_TRIANGLE_SWEEP: import("./ball.core.ts").Piece<BallSpec>;
12
12
  /** The mid 2000s: the 14-panel propeller layout (six propellers and eight triangles), with a fine line following each curved seam inside the panel, and a small ring on each triangle. */
13
13
  export declare const BALL_SOCCER_2006_PROPELLER: import("./ball.core.ts").Piece<BallSpec>;
14
- /** The early 2010s: the 8-panel layout, an octahedron whose edges are bent into S curves, with grooves for seams and fine contour lines across each panel. */
14
+ /** The early 2010s: the 8-panel layout, an octahedron whose edges are bent into S curves, with deep grooves for seams and contour lines across each panel: white panels with teal lines and teal panels with white ones. */
15
15
  export declare const BALL_SOCCER_2010_GROOVED: import("./ball.core.ts").Piece<BallSpec>;
16
16
  /** The mid 2010s: six twisted panels in white, crossed by wide ribbons that swirl round the ball in teal, orange and violet. */
17
17
  export declare const BALL_SOCCER_2014_RIBBON: import("./ball.core.ts").Piece<BallSpec>;
@@ -156,26 +156,31 @@ export const BALL_SOCCER_2006_PROPELLER = /* @__PURE__ */ ball("ball:soccer-2006
156
156
  return out + lit(s, g.seams);
157
157
  },
158
158
  });
159
- /** The early 2010s: the 8-panel layout, an octahedron whose edges are bent into S curves, with grooves for seams and fine contour lines across each panel. */
159
+ /** The early 2010s: the 8-panel layout, an octahedron whose edges are bent into S curves, with deep grooves for seams and contour lines across each panel: white panels with teal lines and teal panels with white ones. */
160
160
  export const BALL_SOCCER_2010_GROOVED = /* @__PURE__ */ ball("ball:soccer-2010-grooved", "Soccer, 8-panel grooved", "8枚貼り・溝つき", {
161
161
  view: { yaw: 32, pitch: 26, roll: 0 },
162
- base: "#e9ecee",
163
- ground: "#eceff0",
164
- mat: { gloss: 0.7, shade: "#33404c", grain: { f: 1.6, a: 0.2 } },
162
+ base: "#f6f8f8",
163
+ ground: "#f6f8f8",
164
+ mat: { gloss: 0.7, shade: "#1f3340", grain: { f: 1.6, a: 0.2 } },
165
165
  draw(s, base, accent) {
166
166
  const g = octahedron8();
167
- const tone = [base, accent ?? "#cfe3d6"];
167
+ const tone = [base, accent ?? "#0f9b8e"];
168
168
  let out = "";
169
169
  g.panels.forEach((p, i) => {
170
170
  if (g.parity[i] === 1)
171
171
  out += s.panel(p, tone[1], { n: 1 });
172
172
  });
173
- const groove = "#3f4b57";
173
+ // Contour lines: two rings in each panel, in the other panel colour's dark or light so that they show on both.
174
+ const groove = "#16262f";
174
175
  g.panels.forEach((p, i) => {
175
- out += s.strokes([shrink(p, g.centres[i], 0.4).filter((_, j) => j % 2 === 0)], groove, 0.35, { closed: true, deco: true, opacity: 0.6 });
176
+ const onAccent = g.parity[i] === 1;
177
+ const ink = onAccent ? "#ffffff" : mix(tone[1], -0.35);
178
+ for (const [k, w] of [[0.62, 0.9], [0.3, 0.8]])
179
+ out += s.strokes([shrink(p, g.centres[i], k).filter((_, j) => j % 2 === 0)], ink, w, { closed: true, deco: true, opacity: 0.95 });
176
180
  });
177
- out += s.strokes(g.seams, groove, 1.8);
178
- out += s.strokes(g.seams, "#fff", 0.55, { deco: true, opacity: 0.55, dx: 0.5, dy: 0.55 });
181
+ // The grooves: a wide dark channel with a lit lip beside it.
182
+ out += s.strokes(g.seams, groove, 3.2);
183
+ out += s.strokes(g.seams, "#fff", 0.8, { deco: true, opacity: 0.7, dx: 0.8, dy: 0.85 });
179
184
  return out;
180
185
  },
181
186
  });
@@ -1,4 +1,28 @@
1
1
  import { type BallSpec } from "./ball.engine.ts";
2
+ type Trio = readonly [string, string, string];
3
+ /**
4
+ * How one country's ball is laid out. `panels` is 18 letters, `a`, `b` or `c` for the first, second or third colour, three to each of the cube's six faces in the order of `paintEach18` (+z, -z, +x, -x, +y, -y), the strips of a face
5
+ * from one edge to the other. `corner` stands the ball on a corner of its cube, so three faces make the top and three the bottom and it turns about that corner.
6
+ * The layouts differ in where the colours sit, not only in which they are, so that two balls with alike colours (white and red, or red, white and blue) still look different at 16 pixels.
7
+ */
8
+ export interface VolleyballPlan {
9
+ readonly colours: Trio;
10
+ readonly panels: string;
11
+ readonly corner?: boolean;
12
+ }
13
+ /** The national balls: colours and layout of each, by the id's last word. */
14
+ export declare const VOLLEYBALL_PLANS: {
15
+ readonly brazil: VolleyballPlan;
16
+ readonly japan: VolleyballPlan;
17
+ readonly canada: VolleyballPlan;
18
+ readonly usa: VolleyballPlan;
19
+ readonly italy: VolleyballPlan;
20
+ readonly poland: VolleyballPlan;
21
+ readonly france: VolleyballPlan;
22
+ readonly serbia: VolleyballPlan;
23
+ readonly netherlands: VolleyballPlan;
24
+ readonly germany: VolleyballPlan;
25
+ };
2
26
  export declare const BALL_VOLLEYBALL_COLOURS: import("./ball.core.ts").Piece<BallSpec>;
3
27
  export declare const BALL_VOLLEYBALL_BRAZIL: import("./ball.core.ts").Piece<BallSpec>;
4
28
  export declare const BALL_VOLLEYBALL_JAPAN: import("./ball.core.ts").Piece<BallSpec>;
@@ -10,3 +34,4 @@ export declare const BALL_VOLLEYBALL_FRANCE: import("./ball.core.ts").Piece<Ball
10
34
  export declare const BALL_VOLLEYBALL_SERBIA: import("./ball.core.ts").Piece<BallSpec>;
11
35
  export declare const BALL_VOLLEYBALL_NETHERLANDS: import("./ball.core.ts").Piece<BallSpec>;
12
36
  export declare const BALL_VOLLEYBALL_GERMANY: import("./ball.core.ts").Piece<BallSpec>;
37
+ export {};
@@ -1,11 +1,36 @@
1
1
  // Volleyballs in national colours: the 18-panel indoor ball (six groups of three strips) with its panels in the colours of a country's flag, three colours to a ball. They are only colours: no flag is drawn,
2
2
  // no crest or federation's mark, and the ball is the indoor ball of the FIVB's rule. `colors` takes any three colours for any of them, and `ball:volleyball-colours` is the same ball with colours of your own.
3
3
  import { defineBall } from "./ball.engine.js";
4
- import { strips18 } from "./ball.family.panelled.js";
4
+ import { ON_A_CORNER, panels18 } from "./ball.family.panelled.js";
5
5
  import { BALL_VOLLEYBALL } from "./ball.volleyball.js";
6
6
  const WHITE = "#f6f4ee";
7
- /** The ball with the colours of one flag: `tint` replaces the first colour, `accent` the second and `colors` all three. The six faces take the three colours in pairs, so each colour is the outer strips of two faces and the middle strip of two others. */
8
- function nation(id, en, ja, colours, view = { yaw: 28, pitch: 22, roll: 0 }) {
7
+ const plan = (colours, faces, corner = false) => ({ colours, panels: faces.replace(/ /g, ""), corner });
8
+ /** The national balls: colours and layout of each, by the id's last word. */
9
+ export const VOLLEYBALL_PLANS = {
10
+ // Three colours in turn, as before: the middle strips of two faces and the outer strips of the others.
11
+ brazil: plan(["#009c3b", "#ffdf00", "#002776"], "aba bcb cac aca bab cbc"),
12
+ // White with one band of crimson: the three strips of the front face run from the top round the side as one broad sash; the dark red is a thin shadow strip on each side of it.
13
+ japan: plan([WHITE, "#bc002d", "#7d0020"], "baa aab aba aba bab aca"),
14
+ // Red with a white pale down the middle, like the flag: the white middle strips of the front and the back, red everywhere else, the dark red on the two ends.
15
+ canada: plan(["#d52b1e", WHITE, "#9b1c14"], "aba aba aaa aaa ccc ccc"),
16
+ // A navy field of two faces (the canton), the other four faces in red and white stripes.
17
+ usa: plan(["#b22234", WHITE, "#3c3b6e"], "aba bab ccc ccc aba bab"),
18
+ // The flag's green, white and red in three bands round the ball: green on one side, white across the front and red on the other.
19
+ italy: plan(["#009246", WHITE, "#ce2b37"], "abc cba ccc aaa bbb bbb"),
20
+ // White above and red below, as the flag is cut: the three faces round the top corner white, the three round the bottom red.
21
+ poland: plan([WHITE, "#dc143c", "#9b1030"], "aaa bcb aaa bcb aaa bcb", true),
22
+ // Blue, white and red as three big fields turning round one corner, each on two opposite faces.
23
+ france: plan(["#0055a4", WHITE, "#ef4135"], "aaa aaa bbb bbb ccc ccc", true),
24
+ // The flag's red, blue and white from the top down: a red cap, a blue belt and a white foot.
25
+ serbia: plan(["#c6363c", "#0c4076", WHITE], "bbb bbb cba cba aaa ccc"),
26
+ // Thin stripes of red, white and blue on every face of a ball standing on its corner.
27
+ netherlands: plan(["#ae1c28", WHITE, "#21468b"], "abc abc abc abc abc abc", true),
28
+ germany: plan(["#1b1b1b", "#dd0000", "#ffce00"], "aaa ccc bbb aaa ccc bbb", true),
29
+ };
30
+ const SEAM = "#3a4560";
31
+ /** The ball with the colours of one flag: `tint` replaces the first colour, `accent` the second and `colors` all three. */
32
+ function nation(id, en, ja, laid, view = laid.corner === true ? { yaw: 20, pitch: 24, roll: 0 } : { yaw: 28, pitch: 22, roll: 0 }) {
33
+ const colours = laid.colours;
9
34
  return defineBall({
10
35
  id,
11
36
  en,
@@ -18,19 +43,20 @@ function nation(id, en, ja, colours, view = { yaw: 28, pitch: 22, roll: 0 }) {
18
43
  mat: { gloss: 0.75, shade: "#1a2a50", grain: { f: 1.2, a: 0.12 } },
19
44
  draw: (s, base, accent, picked) => {
20
45
  const [a, b, c] = picked ?? [base, accent ?? colours[1], colours[2]];
21
- return strips18(s, [[a, b], [b, c], [c, a], [a, c], [b, a], [c, b]], "#3a4560");
46
+ const by = { a, b, c };
47
+ return panels18(s, [...laid.panels].map((ch) => by[ch]), SEAM, laid.corner === true ? ON_A_CORNER : undefined);
22
48
  },
23
49
  },
24
50
  });
25
51
  }
26
- export const BALL_VOLLEYBALL_COLOURS = /* @__PURE__ */ nation("ball:volleyball-colours", "Volleyball, three colours", "三色のバレーボール", ["#f3c81c", "#1f5fbf", WHITE]);
27
- export const BALL_VOLLEYBALL_BRAZIL = /* @__PURE__ */ nation("ball:volleyball-brazil", "Volleyball, Brazil", "バレー(ブラジル)", ["#009c3b", "#ffdf00", "#002776"]);
28
- export const BALL_VOLLEYBALL_JAPAN = /* @__PURE__ */ nation("ball:volleyball-japan", "Volleyball, Japan", "バレー(日本)", [WHITE, "#bc002d", "#7d0020"]);
29
- export const BALL_VOLLEYBALL_CANADA = /* @__PURE__ */ nation("ball:volleyball-canada", "Volleyball, Canada", "バレー(カナダ)", ["#d52b1e", WHITE, "#9b1c14"]);
30
- export const BALL_VOLLEYBALL_USA = /* @__PURE__ */ nation("ball:volleyball-usa", "Volleyball, USA", "バレー(アメリカ)", ["#b22234", WHITE, "#3c3b6e"]);
31
- export const BALL_VOLLEYBALL_ITALY = /* @__PURE__ */ nation("ball:volleyball-italy", "Volleyball, Italy", "バレー(イタリア)", ["#009246", WHITE, "#ce2b37"]);
32
- export const BALL_VOLLEYBALL_POLAND = /* @__PURE__ */ nation("ball:volleyball-poland", "Volleyball, Poland", "バレー(ポーランド)", [WHITE, "#dc143c", "#9b1030"]);
33
- export const BALL_VOLLEYBALL_FRANCE = /* @__PURE__ */ nation("ball:volleyball-france", "Volleyball, France", "バレー(フランス)", ["#0055a4", WHITE, "#ef4135"]);
34
- export const BALL_VOLLEYBALL_SERBIA = /* @__PURE__ */ nation("ball:volleyball-serbia", "Volleyball, Serbia", "バレー(セルビア)", ["#c6363c", "#0c4076", WHITE]);
35
- export const BALL_VOLLEYBALL_NETHERLANDS = /* @__PURE__ */ nation("ball:volleyball-netherlands", "Volleyball, Netherlands", "バレー(オランダ)", ["#ae1c28", WHITE, "#21468b"]);
36
- export const BALL_VOLLEYBALL_GERMANY = /* @__PURE__ */ nation("ball:volleyball-germany", "Volleyball, Germany", "バレー(ドイツ)", ["#1b1b1b", "#dd0000", "#ffce00"]);
52
+ export const BALL_VOLLEYBALL_COLOURS = /* @__PURE__ */ nation("ball:volleyball-colours", "Volleyball, three colours", "三色のバレーボール", plan(["#f3c81c", "#1f5fbf", WHITE], "aba bcb cac aca bab cbc"));
53
+ export const BALL_VOLLEYBALL_BRAZIL = /* @__PURE__ */ nation("ball:volleyball-brazil", "Volleyball, Brazil", "バレー(ブラジル)", VOLLEYBALL_PLANS.brazil);
54
+ export const BALL_VOLLEYBALL_JAPAN = /* @__PURE__ */ nation("ball:volleyball-japan", "Volleyball, Japan", "バレー(日本)", VOLLEYBALL_PLANS.japan);
55
+ export const BALL_VOLLEYBALL_CANADA = /* @__PURE__ */ nation("ball:volleyball-canada", "Volleyball, Canada", "バレー(カナダ)", VOLLEYBALL_PLANS.canada);
56
+ export const BALL_VOLLEYBALL_USA = /* @__PURE__ */ nation("ball:volleyball-usa", "Volleyball, USA", "バレー(アメリカ)", VOLLEYBALL_PLANS.usa);
57
+ export const BALL_VOLLEYBALL_ITALY = /* @__PURE__ */ nation("ball:volleyball-italy", "Volleyball, Italy", "バレー(イタリア)", VOLLEYBALL_PLANS.italy);
58
+ export const BALL_VOLLEYBALL_POLAND = /* @__PURE__ */ nation("ball:volleyball-poland", "Volleyball, Poland", "バレー(ポーランド)", VOLLEYBALL_PLANS.poland);
59
+ export const BALL_VOLLEYBALL_FRANCE = /* @__PURE__ */ nation("ball:volleyball-france", "Volleyball, France", "バレー(フランス)", VOLLEYBALL_PLANS.france);
60
+ export const BALL_VOLLEYBALL_SERBIA = /* @__PURE__ */ nation("ball:volleyball-serbia", "Volleyball, Serbia", "バレー(セルビア)", VOLLEYBALL_PLANS.serbia);
61
+ export const BALL_VOLLEYBALL_NETHERLANDS = /* @__PURE__ */ nation("ball:volleyball-netherlands", "Volleyball, Netherlands", "バレー(オランダ)", VOLLEYBALL_PLANS.netherlands);
62
+ export const BALL_VOLLEYBALL_GERMANY = /* @__PURE__ */ nation("ball:volleyball-germany", "Volleyball, Germany", "バレー(ドイツ)", VOLLEYBALL_PLANS.germany);
@@ -102,6 +102,12 @@ export const EMBLEM_DESCRIPTIONS = {
102
102
  "gear:obi-orange-green": { en: "A two-colour belt, orange above and green below, between the orange and green grades", ja: "上半分が橙、下半分が緑の二色帯。橙帯と緑帯の間の級の帯" },
103
103
  "gear:obi-green-blue": { en: "A two-colour belt, green above and blue below, between the green and blue grades", ja: "上半分が緑、下半分が青の二色帯。緑帯と青帯の間の級の帯" },
104
104
  "gear:obi-blue-brown": { en: "A two-colour belt, blue above and brown below, between the blue and brown grades", ja: "上半分が青、下半分が茶の二色帯。青帯と茶帯の間の級の帯" },
105
+ "gear:obi-step-1": { en: "Step 1 of tying the judo belt: the middle of the belt is set against the middle of the belly, over the jacket of the judogi", ja: "柔道の帯の結び方、手順1。柔道衣の上から、帯の真ん中をおなかの中心に当てる" },
106
+ "gear:obi-step-2": { en: "Step 2 of tying the judo belt: the belt is wrapped twice round the waist, and both ends come back to the front", ja: "柔道の帯の結び方、手順2。帯を腰に二重に巻き、両端を前に戻す" },
107
+ "gear:obi-step-3": { en: "Step 3 of tying the judo belt: the two ends are crossed in front of the belly, the wearer's left end over the right one", ja: "柔道の帯の結び方、手順3。おなかの前で両端を交差させる。着る人の左の端が、右の端の上になるように重ねる" },
108
+ "gear:obi-step-4": { en: "Step 4 of tying the judo belt: one end is brought up from below, under both layers of the belt round the waist", ja: "柔道の帯の結び方、手順4。片方の端を、腰に巻いた二重の帯の下から上へ通す" },
109
+ "gear:obi-step-5": { en: "Step 5 of tying the judo belt: the flat square knot (hon-musubi, 本結び), left over right and then right over left", ja: "柔道の帯の結び方、手順5。本結びで結ぶ。左を右の上に重ね、次に右を左の上に重ねる" },
110
+ "gear:obi-step-6": { en: "Step 6 of tying the judo belt: the knot is pulled tight and both ends hang down even, with a mark under their tips", ja: "柔道の帯の結び方、手順6。結び目を締め、両端の長さをそろえて垂らす。先端の下に、長さがそろっていることを示す印がある" },
105
111
  "gear:yumi": { en: "The long asymmetric bow, held a third of the way up, with its string and wrapped grip", ja: "上が長い非対称の長弓。弦を張り、握り革と籐巻きが付く" },
106
112
  "gear:ya": { en: "A kyudo arrow with feather fletching, a nock and a steel point", ja: "弓道の矢。矢羽根、筈、矢尻が付く" },
107
113
  "gear:karate-gi": { en: "The white karate suit laid flat: a lighter jacket with three-quarter sleeves, a wide V of plain lapels and tie strings, straight trousers, and a red belt in a flat knot", ja: "白い空手衣を広げた姿。七分袖の軽い上衣、広いV字の襟と結び紐、まっすぐなズボン、本結びにした赤い帯" },
@@ -123,7 +129,7 @@ export const EMBLEM_DESCRIPTIONS = {
123
129
  "ball:soccer-1998-coloured-triad": { en: "In the style of the 1990s match balls: the triads in blue, red and gold on white hexagons, with deep navy pentagons", ja: "1990年代の試合球風。白い六角形に青、赤、金の三つ巴を配し、五角形は深い紺色にしたもの" },
124
130
  "ball:soccer-2002-triangle-sweep": { en: "In the style of the early-2000s match balls: long thin triangles that sweep across each hexagon in graphite, with gold sparks, over charcoal pentagons", ja: "2000年代前半の試合球風。六角形ごとに細長い三角形が黒鉛色で流れるように並び、金色の小さな三角形が添えられ、五角形は炭色" },
125
131
  "ball:soccer-2006-propeller": { en: "In the style of the mid-2000s match balls: 14 panels (6 propellers and 8 triangles), a fine gold line inside each propeller and a gold mark on each dark triangle", ja: "2000年代半ばの試合球風。14枚貼り(プロペラ形6枚、三角形8枚)で、各プロペラの内側に細い金の線、暗い色の三角形に金の印が入る" },
126
- "ball:soccer-2010-grooved": { en: "In the style of the early-2010s match balls: 8 panels (an octahedron whose edges are bent into S curves) with grooves for seams and fine contour lines across each panel", ja: "2010年代前半の試合球風。8枚貼り(辺をS字に曲げた正八面体)で、継ぎ目は溝、各パネルには細い等高線のような線が入る" },
132
+ "ball:soccer-2010-grooved": { en: "In the style of the early-2010s match balls: 8 panels (an octahedron whose edges are bent into S curves) in white and teal, with deep dark grooves for seams and contour lines across each panel, dark teal on the white panels and white on the teal ones", ja: "2010年代前半の試合球風。8枚貼り(辺をS字に曲げた正八面体)で、白とティールのパネル。継ぎ目は深く暗い溝で、各パネルに等高線のような線が入り、白いパネルには濃いティール、ティールのパネルには白の線が走る" },
127
133
  "ball:soccer-2014-ribbon": { en: "In the style of the mid-2010s match balls: 6 twisted panels in white, crossed by wide ribbons in teal, orange and violet", ja: "2010年代半ばの試合球風。ねじれた6枚貼りの白いボールに、ティール、オレンジ、紫の幅広いリボンが交差する" },
128
134
  "ball:soccer-2018-pixel": { en: "In the style of the late-2010s match balls: 6 panels covered with square pixel blocks in graphite, grey, red and gold, thick on one side and thinning out", ja: "2010年代後半の試合球風。6枚貼りで、黒鉛色、灰色、赤、金の四角いピクセルが一方に密集し、反対側へまばらになっていく" },
129
135
  "ball:soccer-2022-speed-shell": { en: "In the style of the early-2020s match balls: 20 triangular panels (an icosahedron) in burgundy, cream and gold, each carrying nested outlines like a shell", ja: "2020年代前半の試合球風。20枚貼り(正二十面体)で、えんじ、クリーム、金の三角形のパネルそれぞれに、貝殻のように重なる輪郭線が入る" },
@@ -153,14 +159,14 @@ export const EMBLEM_DESCRIPTIONS = {
153
159
  "ball:softball-11-optic": { en: "The 11 in softball in optic yellow with red stitching: 27.9 cm round, 166.5 to 173.6 g (WBSC Slow Pitch rules)", ja: "11インチの蛍光イエローのソフトボール。赤い縫い目、円周27.9cm、重さ166.5〜173.6g(WBSCスローピッチ規則)" },
154
160
  "ball:volleyball-colours": { en: "The 18-panel indoor volleyball with panels in three colours of your own (the `colors` option); yellow, blue and white by default", ja: "3色を自由に指定できる(colors オプション)18枚貼りの室内用バレーボール。初期設定は黄、青、白" },
155
161
  "ball:volleyball-brazil": { en: "The 18-panel indoor volleyball in colours taken from the flag: green, yellow and blue. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:緑、黄、青。色だけで、旗や紋章は描かない" },
156
- "ball:volleyball-japan": { en: "The 18-panel indoor volleyball in colours taken from the flag: white, red and a darker red. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:白、赤、濃い赤。色だけで、旗や紋章は描かない" },
157
- "ball:volleyball-canada": { en: "The 18-panel indoor volleyball in colours taken from the flag: red, white and a darker red. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:赤、白、濃い赤。色だけで、旗や紋章は描かない" },
158
- "ball:volleyball-usa": { en: "The 18-panel indoor volleyball in colours taken from the flag: red, white and blue. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:赤、白、青。色だけで、旗や紋章は描かない" },
159
- "ball:volleyball-italy": { en: "The 18-panel indoor volleyball in colours taken from the flag: green, white and red. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:緑、白、赤。色だけで、旗や紋章は描かない" },
160
- "ball:volleyball-poland": { en: "The 18-panel indoor volleyball in colours taken from the flag: white, red and a darker red. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:白、赤、濃い赤。色だけで、旗や紋章は描かない" },
161
- "ball:volleyball-france": { en: "The 18-panel indoor volleyball in colours taken from the flag: blue, white and red. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:青、白、赤。色だけで、旗や紋章は描かない" },
162
- "ball:volleyball-serbia": { en: "The 18-panel indoor volleyball in colours taken from the flag: red, blue and white. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:赤、青、白。色だけで、旗や紋章は描かない" },
163
- "ball:volleyball-netherlands": { en: "The 18-panel indoor volleyball in colours taken from the flag: red, white and blue. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:赤、白、青。色だけで、旗や紋章は描かない" },
164
- "ball:volleyball-germany": { en: "The 18-panel indoor volleyball in colours taken from the flag: black, red and gold. Colours only, no flag or mark is drawn", ja: "国旗の色を取り入れた18枚貼りの室内用バレーボール:黒、赤、金。色だけで、旗や紋章は描かない" },
162
+ "ball:volleyball-japan": { en: "The 18-panel indoor volleyball in colours taken from the flag (white, red and a darker red), laid out so that it does not look like another country's: white, with one broad crimson band that runs round one side, across the front and over the top, and a darker red strip underneath. Colours only, no flag or mark is drawn", ja: "国旗の色(白、赤、濃い赤)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、白地に、側面から正面、上面へ回る幅広い深紅の帯が1本入り、底面には濃い赤の帯が入る。色だけで、旗や紋章は描かない" },
163
+ "ball:volleyball-canada": { en: "The 18-panel indoor volleyball in colours taken from the flag (red, white and a darker red), laid out so that it does not look like another country's: red, with a white stripe down the middle of the front and of the back, as the flag has its white pale, and a darker red on the top and the bottom. Colours only, no flag or mark is drawn", ja: "国旗の色(赤、白、濃い赤)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、赤地の正面と背面の中央に白い縦帯が入り(国旗の白い部分に倣う)、上面と底面は濃い赤。色だけで、旗や紋章は描かない" },
164
+ "ball:volleyball-usa": { en: "The 18-panel indoor volleyball in colours taken from the flag (red, white and blue), laid out so that it does not look like another country's: a navy field on two opposite sides, as the flag has its blue canton, and red and white stripes on the other four faces. Colours only, no flag or mark is drawn", ja: "国旗の色(赤、白、青)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、向かい合う2面が紺色(国旗の青い部分に倣う)、残る4面は赤と白の縞。色だけで、旗や紋章は描かない" },
165
+ "ball:volleyball-italy": { en: "The 18-panel indoor volleyball in colours taken from the flag (green, white and red), laid out so that it does not look like another country's: green on one side, red on the opposite side, white on the top and the bottom, and green, white and red stripes across the front and the back. Colours only, no flag or mark is drawn", ja: "国旗の色(緑、白、赤)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、緑の面と赤の面が向かい合い、上面と底面は白、正面と背面は緑、白、赤の縞。色だけで、旗や紋章は描かない" },
166
+ "ball:volleyball-poland": { en: "The 18-panel indoor volleyball in colours taken from the flag (white, red and a darker red), laid out so that it does not look like another country's: white above and red below, as the flag is cut: the ball stands on a corner, the three faces round the top are white and the three round the bottom red, each with a darker red strip in the middle. Colours only, no flag or mark is drawn", ja: "国旗の色(白、赤、濃い赤)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、国旗の上下の分け方に倣い、上半分が白、下半分が赤。頂点を真上に向けて置き、上を囲む3面が白、下を囲む3面が赤、下の各面の中央には濃い赤の帯が入る。色だけで、旗や紋章は描かない" },
167
+ "ball:volleyball-france": { en: "The 18-panel indoor volleyball in colours taken from the flag (blue, white and red), laid out so that it does not look like another country's: three big fields in blue, white and red turning round a corner of the ball, each colour on two opposite faces. Colours only, no flag or mark is drawn", ja: "国旗の色(青、白、赤)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、頂点を真上に向け、青、白、赤の大きな3つの領域が頂点のまわりを巡る。各色は向かい合う2面を占める。色だけで、旗や紋章は描かない" },
168
+ "ball:volleyball-serbia": { en: "The 18-panel indoor volleyball in colours taken from the flag (red, blue and white), laid out so that it does not look like another country's: red above, blue in the middle and white below, as the flag's bands run: a red top, a white bottom, and blue across the front and the back. Colours only, no flag or mark is drawn", ja: "国旗の色(赤、青、白)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、国旗の帯の順に上から赤、青、白と並ぶ。上面は赤、底面は白、正面と背面は青。色だけで、旗や紋章は描かない" },
169
+ "ball:volleyball-netherlands": { en: "The 18-panel indoor volleyball in colours taken from the flag (red, white and blue), laid out so that it does not look like another country's: thin stripes of red, white and blue on every face of a ball standing on a corner. Colours only, no flag or mark is drawn", ja: "国旗の色(赤、白、青)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、頂点を真上に向けたボールの全面に、赤、白、青の細い縞が入る。色だけで、旗や紋章は描かない" },
170
+ "ball:volleyball-germany": { en: "The 18-panel indoor volleyball in colours taken from the flag (black, red and gold), laid out so that it does not look like another country's: black, red and gold as six big fields on a ball standing on a corner, each colour on two faces. Colours only, no flag or mark is drawn", ja: "国旗の色(黒、赤、金)を取り入れた18枚貼りの室内用バレーボール。他の国のボールと見分けがつく配置で、頂点を真上に向けたボールが、黒、赤、金の大きな6つの面に分かれ、各色が2面ずつを占める。色だけで、旗や紋章は描かない" },
165
171
  ...soccerColourways(),
166
172
  };
@@ -24,10 +24,10 @@ export type BallId = (typeof BALL_IDS)[number];
24
24
  export declare const PUCK_IDS: readonly ["puck:canada", "puck:canada-red", "puck:canada-white", "puck:usa", "puck:usa-navy", "puck:player", "puck:signed", "puck:souvenir"];
25
25
  export type PuckId = (typeof PUCK_IDS)[number];
26
26
  /** The pieces of gear: a piece of equipment for each sport, flat, drawn for this package, turning like a coin. */
27
- 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:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back", "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:yumi", "gear:ya", "gear:karate-gi"];
27
+ 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:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back", "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:obi-step-1", "gear:obi-step-2", "gear:obi-step-3", "gear:obi-step-4", "gear:obi-step-5", "gear:obi-step-6", "gear:yumi", "gear:ya", "gear:karate-gi"];
28
28
  export type GearId = (typeof GEAR_IDS)[number];
29
29
  /** 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. */
30
- 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:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back", "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "sport:kyudo", "gear:yumi", "gear:ya", "sport:karate", "gear:karate-gi"];
30
+ 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:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back", "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:obi-step-1", "gear:obi-step-2", "gear:obi-step-3", "gear:obi-step-4", "gear:obi-step-5", "gear:obi-step-6", "sport:kyudo", "gear:yumi", "gear:ya", "sport:karate", "gear:karate-gi"];
31
31
  export type JapaneseId = (typeof JAPANESE_IDS)[number];
32
32
  /** The pennants: a felt championship pennant with a stitched edge. */
33
33
  export declare const PENNANT_IDS: readonly ["pennant:championship"];
@@ -45,7 +45,7 @@ export declare const EMBLEM_GROUPS: {
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-1970-black-white", "ball:soccer-1978-triad", "ball:soccer-1986-decorated-triad", "ball:soccer-1998-coloured-triad", "ball:soccer-2002-triangle-sweep", "ball:soccer-2006-propeller", "ball:soccer-2010-grooved", "ball:soccer-2014-ribbon", "ball:soccer-2018-pixel", "ball:soccer-2022-speed-shell", "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: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"];
47
47
  readonly puck: readonly ["puck:canada", "puck:canada-red", "puck:canada-white", "puck:usa", "puck:usa-navy", "puck:player", "puck:signed", "puck:souvenir"];
48
- 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:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back", "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:yumi", "gear:ya", "gear:karate-gi"];
48
+ 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:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back", "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:obi-step-1", "gear:obi-step-2", "gear:obi-step-3", "gear:obi-step-4", "gear:obi-step-5", "gear:obi-step-6", "gear:yumi", "gear:ya", "gear:karate-gi"];
49
49
  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")[];
50
50
  readonly pennant: readonly ["pennant:championship"];
51
51
  };
@@ -40,7 +40,7 @@ export const GEAR_IDS = [
40
40
  "gear:roundnet-set", "gear:volleyball-net", "gear:basketball-hoop", "gear:baseball-bat", "gear:baseball-glove", "gear:football-helmet", "gear:goalposts", "gear:hurley",
41
41
  "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",
42
42
  "gear:gunbai", "gear:mawashi", "gear:shinai", "gear:kendo-men", "gear:judogi", "gear:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back",
43
- "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown",
43
+ "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:obi-step-1", "gear:obi-step-2", "gear:obi-step-3", "gear:obi-step-4", "gear:obi-step-5", "gear:obi-step-6",
44
44
  "gear:yumi", "gear:ya", "gear:karate-gi"
45
45
  ];
46
46
  /** 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. */
@@ -49,7 +49,7 @@ export const JAPANESE_IDS = [
49
49
  "trophy:championship-banner",
50
50
  "sport:kendo", "gear:shinai", "gear:kendo-men",
51
51
  "sport:judo", "gear:judogi", "gear:judogi-blue", "gear:judogi-folded", "gear:judogi-blue-folded", "gear:judogi-back", "gear:judogi-blue-back",
52
- "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown",
52
+ "gear:obi", "gear:obi-white", "gear:obi-yellow", "gear:obi-orange", "gear:obi-green", "gear:obi-blue", "gear:obi-brown", "gear:obi-kohaku", "gear:obi-red", "gear:obi-women", "gear:obi-white-yellow", "gear:obi-yellow-orange", "gear:obi-orange-green", "gear:obi-green-blue", "gear:obi-blue-brown", "gear:obi-step-1", "gear:obi-step-2", "gear:obi-step-3", "gear:obi-step-4", "gear:obi-step-5", "gear:obi-step-6",
53
53
  "sport:kyudo", "gear:yumi", "gear:ya",
54
54
  "sport:karate", "gear:karate-gi",
55
55
  ];
@@ -16,9 +16,61 @@ export declare const OBI_RANKS: {
16
16
  readonly red: "#c4202e";
17
17
  };
18
18
  export type ObiRank = keyof typeof OBI_RANKS;
19
+ export type Pt = readonly [number, number];
19
20
  /** Judogi colours: the IJF white ("snow white") and blue (between Pantone 285M and 286M). */
20
21
  export declare const JUDOGI_WHITE = "#f6f4ee";
21
22
  export declare const JUDOGI_BLUE = "#1456b6";
23
+ export declare const f: (n: number) => string;
24
+ export declare const path: (pts: readonly Pt[], close?: boolean) => string;
25
+ /** A rectangle of width `w` along the segment from `a` to `b`, as a polygon. */
26
+ export declare const strip: (a: Pt, b: Pt, w: number) => Pt[];
27
+ export declare const lerp: (a: Pt, b: Pt, t: number) => Pt;
28
+ /** A line parallel to the segment from `a` to `b`, `off` to its side, between the fractions `t0` and `t1` of its length. */
29
+ export declare const along: (a: Pt, b: Pt, off: number, t0: number, t1: number) => string;
30
+ export declare const INK = "#0d1522";
31
+ /** The edge of a black belt: light enough to show on a dark page. */
32
+ export declare const BELT_EDGE = "#5d6672";
33
+ export declare const dark: (d: string, w?: number, op?: number) => Part;
34
+ export declare const light: (d: string, w?: number, op?: number) => Part;
35
+ export interface BeltSpec {
36
+ /** The colour of the belt, and of its second colour (the other half of a split belt, or the white of the kohaku panels). */
37
+ main: string;
38
+ alt?: string;
39
+ /** Centre and ends of the two wraps, the top of the first one, the width of the belt and how far its wraps sag at the middle (the belt is round). */
40
+ cx: number;
41
+ x0: number;
42
+ x1: number;
43
+ y: number;
44
+ w: number;
45
+ sag: number;
46
+ /** The knot's width, the length of the ends and how far they hang apart (degrees). */
47
+ knot: number;
48
+ tail: number;
49
+ splay: number;
50
+ /** The length of the end on the right as the wearer is seen (the left end of the wearer), where it is not `tail`: a knot just tied has one end longer than the other. */
51
+ tail2?: number;
52
+ /** Panels of the red-and-white belt: the x where the panel colour changes along the first and the second wrap, and the colour of the ends. */
53
+ panels?: {
54
+ upper: readonly number[];
55
+ lower: readonly number[];
56
+ tail: string;
57
+ };
58
+ /** A split belt: the second colour fills the lower half of the belt's width, lengthwise. */
59
+ half?: boolean;
60
+ /** The white stripe of a women's black belt, down the middle of the belt. */
61
+ stripe?: boolean;
62
+ /** A bar across each end. */
63
+ bars?: boolean;
64
+ /** The edge of the belt, where a darker tone of the belt itself would vanish (a black belt on a dark page). */
65
+ edge?: string;
66
+ }
67
+ /** A belt tied at the front: two wraps, a flat square knot and two equal ends hanging a little apart. Parts, back to front. */
68
+ export declare function belt(s: BeltSpec): Part[];
69
+ /**
70
+ * The judogi laid flat: jacket (sleeves out to the sides, lapels crossed left over right, thick stitched collar, quilted upper body, vented skirt), the belt tied over it and the trousers below with a drawstring
71
+ * waist and reinforced knees. `$main` is the cloth, `$accent` the belt.
72
+ */
73
+ export declare function judogiFlat(torso?: boolean): Part[];
22
74
  export declare const GEAR_JUDOGI: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
23
75
  export declare const GEAR_JUDOGI_BLUE: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
24
76
  export declare const GEAR_JUDOGI_FOLDED: import("./gear.index.ts").Piece<import("./gear.core.ts").GearSpec>;
@@ -18,19 +18,19 @@ export const OBI_RANKS = {
18
18
  /** Judogi colours: the IJF white ("snow white") and blue (between Pantone 285M and 286M). */
19
19
  export const JUDOGI_WHITE = "#f6f4ee";
20
20
  export const JUDOGI_BLUE = "#1456b6";
21
- const f = (n) => String(Math.round(n * 10) / 10);
22
- const path = (pts, close = true) => `M${pts.map((p) => `${f(p[0])} ${f(p[1])}`).join("L")}${close ? "z" : ""}`;
21
+ export const f = (n) => String(Math.round(n * 10) / 10);
22
+ export const path = (pts, close = true) => `M${pts.map((p) => `${f(p[0])} ${f(p[1])}`).join("L")}${close ? "z" : ""}`;
23
23
  const mirror = (pts) => pts.map((p) => [64 - p[0], p[1]]);
24
24
  /** A rectangle of width `w` along the segment from `a` to `b`, as a polygon. */
25
- const strip = (a, b, w) => {
25
+ export const strip = (a, b, w) => {
26
26
  const dx = b[0] - a[0], dy = b[1] - a[1];
27
27
  const l = Math.hypot(dx, dy) || 1;
28
28
  const nx = (-dy / l) * (w / 2), ny = (dx / l) * (w / 2);
29
29
  return [[a[0] + nx, a[1] + ny], [b[0] + nx, b[1] + ny], [b[0] - nx, b[1] - ny], [a[0] - nx, a[1] - ny]];
30
30
  };
31
- const lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
31
+ export const lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
32
32
  /** A line parallel to the segment from `a` to `b`, `off` to its side, between the fractions `t0` and `t1` of its length. */
33
- const along = (a, b, off, t0, t1) => {
33
+ export const along = (a, b, off, t0, t1) => {
34
34
  const dx = b[0] - a[0], dy = b[1] - a[1];
35
35
  const l = Math.hypot(dx, dy) || 1;
36
36
  const nx = (-dy / l) * off, ny = (dx / l) * off;
@@ -86,13 +86,13 @@ function rows(poly, y0, y1, step, inset = 0) {
86
86
  return d;
87
87
  }
88
88
  // Cloth lines are laid over the cloth as ink at low opacity (dark rows) or as light rows, so they follow any tint.
89
- const INK = "#0d1522";
89
+ export const INK = "#0d1522";
90
90
  /** The edge of a black belt: light enough to show on a dark page. */
91
- const BELT_EDGE = "#5d6672";
92
- const dark = (d, w = 0.3, op = 0.2) => ({ d, fill: "none", stroke: INK, w, op, line: "none" });
93
- const light = (d, w = 0.3, op = 0.3) => ({ d, fill: "none", stroke: "#ffffff", w, op, line: "none" });
91
+ export const BELT_EDGE = "#5d6672";
92
+ export const dark = (d, w = 0.3, op = 0.2) => ({ d, fill: "none", stroke: INK, w, op, line: "none" });
93
+ export const light = (d, w = 0.3, op = 0.3) => ({ d, fill: "none", stroke: "#ffffff", w, op, line: "none" });
94
94
  /** A belt tied at the front: two wraps, a flat square knot and two equal ends hanging a little apart. Parts, back to front. */
95
- function belt(s) {
95
+ export function belt(s) {
96
96
  const parts = [];
97
97
  const hw = (s.x1 - s.x0) / 2;
98
98
  const w = s.w;
@@ -140,7 +140,8 @@ function belt(s) {
140
140
  const tailFill = s.panels?.tail ?? s.main;
141
141
  for (const side of [-1, 1]) {
142
142
  const a = [s.cx + side * kw * 0.2, yc + hh * 0.2];
143
- const b = [a[0] + side * Math.sin(rad) * s.tail, a[1] + Math.cos(rad) * s.tail];
143
+ const length = side === 1 ? (s.tail2 ?? s.tail) : s.tail;
144
+ const b = [a[0] + side * Math.sin(rad) * length, a[1] + Math.cos(rad) * length];
144
145
  parts.push({ d: path(strip(a, b, tw)), fill: tailFill, stroke: s.edge, cyl: 0.55, shade: 0.25 });
145
146
  if (s.half === true) {
146
147
  const q = strip(a, b, tw);
@@ -151,7 +152,7 @@ function belt(s) {
151
152
  if (s.stripe === true)
152
153
  parts.push({ d: path(strip(lerp(a, b, 0.25), lerp(a, b, 1), tw * 0.15)), fill: "#f6f4ee", stroke: "none", line: "none" });
153
154
  if (s.bars === true)
154
- parts.push({ d: path(strip(lerp(a, b, 1 - 3.4 / s.tail), lerp(a, b, 1 - 2.2 / s.tail), tw)), fill: "$accent", stroke: "none", op: 0.9, line: "none" });
155
+ parts.push({ d: path(strip(lerp(a, b, 1 - 3.4 / length), lerp(a, b, 1 - 2.2 / length), tw)), fill: "$accent", stroke: "none", op: 0.9, line: "none" });
155
156
  }
156
157
  const kfill = s.panels === undefined ? s.main : alt;
157
158
  const blob = rect(s.cx - kw / 2, yt, kw, hh * 2, Math.min(2.4, kw * 0.22));
@@ -172,13 +173,9 @@ function belt(s) {
172
173
  return parts;
173
174
  }
174
175
  // ───────────────────────────── the judogi ─────────────────────────────
175
- /**
176
- * The judogi laid flat: jacket (sleeves out to the sides, lapels crossed left over right, thick stitched collar, quilted upper body, vented skirt), the belt tied over it and the trousers below with a drawstring
177
- * waist and reinforced knees. `$main` is the cloth, `$accent` the belt.
178
- */
179
- function judogiFlat() {
176
+ /** The judogi's trousers laid flat: straight legs, a waistband with its drawstring, reinforced knees. */
177
+ function judogiTrousers() {
180
178
  const parts = [];
181
- // Trousers, behind everything: straight legs, a waistband with its drawstring, reinforced knees.
182
179
  const trousers = [[18.4, 37], [45.6, 37], [46.6, 62], [34.6, 62], [32, 43.6], [29.4, 62], [17.4, 62]];
183
180
  parts.push({ d: path(trousers), fill: "$main", shade: 0.3, cyl: 0.25 });
184
181
  parts.push({ d: path([[18.4, 37], [45.6, 37], [45.7, 39.8], [18.3, 39.8]]), fill: "$main", shade: 0.2 });
@@ -190,9 +187,20 @@ function judogiFlat() {
190
187
  parts.push(dark(path(k) + rows(k, 51.4, 56.4, 1.6, 0.3), 0.28, 0.35));
191
188
  }
192
189
  parts.push(dark("M17.6 60.2H29.6M34.4 60.2H46.4", 0.3, 0.3));
190
+ return parts;
191
+ }
192
+ /**
193
+ * The judogi laid flat: jacket (sleeves out to the sides, lapels crossed left over right, thick stitched collar, quilted upper body, vented skirt), the belt tied over it and the trousers below with a drawstring
194
+ * waist and reinforced knees. `$main` is the cloth, `$accent` the belt.
195
+ */
196
+ export function judogiFlat(torso = false) {
197
+ const parts = [];
198
+ // Trousers, behind everything: straight legs, a waistband with its drawstring, reinforced knees. A torso (the jacket alone, for the close-ups of the belt) has no trousers, no sleeves and no belt.
199
+ if (!torso)
200
+ parts.push(...judogiTrousers());
193
201
  // Sleeves: to the wrist, the arms hanging out and down (IJF C1.10: the sleeve covers the wrist bone, at most 5 cm short of it, and is loose all along the forearm).
194
- const sleeve = JUDOGI_SLEEVE;
195
- for (const pts of [sleeve, mirror(sleeve)])
202
+ const sleeves = torso ? [] : [JUDOGI_SLEEVE, mirror(JUDOGI_SLEEVE)];
203
+ for (const pts of sleeves)
196
204
  parts.push({ d: path(pts), fill: "$main", cyl: 0.15, shade: 0.35 });
197
205
  const jacket = [[19.4, 6.6], [26.2, 4], [32, 3.2], [37.8, 4], [44.6, 6.6], [45.6, 8], [45.6, 36], [18.4, 36], [18.4, 8]];
198
206
  parts.push({ d: path(jacket), fill: "$main", shade: 0.35, cyl: 0.18 });
@@ -200,10 +208,10 @@ function judogiFlat() {
200
208
  const body = [[19, 7], [45, 7], [45.6, 26], [18.4, 26]];
201
209
  parts.push(dark(rows(body, 8, 25, 1.4, 0.5), 0.28, 0.1));
202
210
  parts.push(light(rows(body, 8.5, 25.5, 1.4, 0.5), 0.24, 0.2));
203
- for (const pts of [sleeve, mirror(sleeve)])
211
+ for (const pts of sleeves)
204
212
  parts.push(dark(sleeveRows(pts), 0.28, 0.1));
205
213
  // The loose cuff: a hem stitched a little in from the open end, on both sleeves.
206
- for (const pts of [sleeve, mirror(sleeve)])
214
+ for (const pts of sleeves)
207
215
  parts.push(dark(cuffHem(pts), 0.3, 0.38));
208
216
  // The neck: the collar goes round the back of the neck, and the opening is a V that ends under 10 cm below the sternum.
209
217
  parts.push({ d: path([[26.4, 4.2], [37.6, 4.2], [33.4, 14]]), fill: INK, stroke: INK, w: 0.3, op: 0.62, line: "none" });
@@ -229,7 +237,8 @@ function judogiFlat() {
229
237
  parts.push(dark("M18.8 34.4H44.8", 0.3, 0.32));
230
238
  parts.push(dark("M20.2 31.4V36M43.8 31.4V36", 0.4, 0.45));
231
239
  // The belt, over the jacket, wrapped twice.
232
- parts.push(...belt({ edge: BELT_EDGE, main: "$accent", cx: 32, x0: 18.2, x1: 45.8, y: 25.2, w: 2.5, sag: 0, knot: 6.4, tail: 11.4, splay: 6 }));
240
+ if (!torso)
241
+ parts.push(...belt({ edge: BELT_EDGE, main: "$accent", cx: 32, x0: 18.2, x1: 45.8, y: 25.2, w: 2.5, sag: 0, knot: 6.4, tail: 11.4, splay: 6 }));
233
242
  return parts;
234
243
  }
235
244
  /** The judogi folded into a neat rectangle: collar and lapel stitching on top, the belt tied round it. */
@@ -0,0 +1,35 @@
1
+ import { type Part } from "./gear.core.ts";
2
+ import type { PieceOptions } from "../ball/ball.core.ts";
3
+ export type ObiStep = 1 | 2 | 3 | 4 | 5 | 6;
4
+ export declare const GEAR_OBI_STEP_1: import("./gear.index.ts").Piece<{
5
+ main: string;
6
+ accent: string;
7
+ parts: Part[];
8
+ }>;
9
+ export declare const GEAR_OBI_STEP_2: import("./gear.index.ts").Piece<{
10
+ main: string;
11
+ accent: string;
12
+ parts: Part[];
13
+ }>;
14
+ export declare const GEAR_OBI_STEP_3: import("./gear.index.ts").Piece<{
15
+ main: string;
16
+ accent: string;
17
+ parts: Part[];
18
+ }>;
19
+ export declare const GEAR_OBI_STEP_4: import("./gear.index.ts").Piece<{
20
+ main: string;
21
+ accent: string;
22
+ parts: Part[];
23
+ }>;
24
+ export declare const GEAR_OBI_STEP_5: import("./gear.index.ts").Piece<{
25
+ main: string;
26
+ accent: string;
27
+ parts: Part[];
28
+ }>;
29
+ export declare const GEAR_OBI_STEP_6: import("./gear.index.ts").Piece<{
30
+ main: string;
31
+ accent: string;
32
+ parts: Part[];
33
+ }>;
34
+ /** One step of tying the belt, 1 to 6, with the usual options (`tint` is the jacket's colour, `accent` the belt's). A number that is not a step from 1 to 6 draws the last one. */
35
+ export declare const obiStep: (step: ObiStep, options?: PieceOptions) => string;
@@ -0,0 +1,146 @@
1
+ // How a judo belt is tied, in six pictures: the torso of the judogi (the same jacket as `gear:judogi`, lapels crossed left over right, so the wearer's left panel lies on top and the funeral wrap's opposite is
2
+ // avoided) in close-up, and the belt (`$accent`, black by default) at each stage. The belt is the one `gear:judogi` and the belts are drawn with, so a step's belt takes `accent` like theirs.
3
+ //
4
+ // 1. the middle of the belt against the belly; 2. wrapped twice round the waist, both ends back in front; 3. the ends crossed in front; 4. one end brought up under both layers;
5
+ // 5. the flat square knot (hon-musubi, 本結び); 6. both ends hanging even.
6
+ //
7
+ // The close-up is the jacket alone, drawn 1.55 times as big, so that the belt's width and the knot read at small sizes; a red number says which step it is, and a few small red marks (the middle, the way an end
8
+ // travels, the equal lengths) say what to look at. The marks and the number are not the suit's, so they ignore `tint` and `accent`.
9
+ import { circle, defineGear, drawGear, rect } from "./gear.core.js";
10
+ import { fitSize, linePath } from "../ball/ball.letters.js";
11
+ import { BELT_EDGE, belt, dark, f, judogiFlat, light, lerp, path, strip } from "./gear.budo.js";
12
+ /** The belt's width in the jacket's own units (the judogi's), the two wraps' tops, and the middle of the belly. */
13
+ const W = 2.5;
14
+ const Y_UPPER = 25.2;
15
+ const Y_LOWER = 27.4;
16
+ const X0 = 18.2;
17
+ const X1 = 45.8;
18
+ const MID = 32;
19
+ /** The length of the two ends in these pictures: a little shorter than the judogi's, so that they stay inside the picture. */
20
+ const TAIL = 9.5;
21
+ /** The red of the marks. */
22
+ const MARK = "#d8342c";
23
+ const MARK_EDGE = "#8f1d17";
24
+ /** The close-up: the jacket, 1.55 times as big, with the belt about at the middle of the picture. */
25
+ const ZOOM = "translate(32 32) scale(1.55) translate(-32 -21.6)";
26
+ /** One wrap of the belt across the front, its top at `y`, drawn as the judogi's belt is: the band, two rows of stitching and their highlights. */
27
+ function wrap(y, x0 = X0, x1 = X1) {
28
+ const row = (off) => `M${f(x0 + 0.8)} ${f(y + W * off)}H${f(x1 - 0.8)}`;
29
+ return [
30
+ { d: rect(x0, y, Math.round((x1 - x0) * 10) / 10, W), fill: "$accent", stroke: BELT_EDGE, cyl: 0.5, shade: 0.25 },
31
+ dark(row(0.3) + row(0.7), 0.28, 0.3),
32
+ light(`M${f(x0 + 0.8)} ${f(y + W * 0.3 + 0.35)}H${f(x1 - 0.8)}M${f(x0 + 0.8)} ${f(y + W * 0.7 + 0.35)}H${f(x1 - 0.8)}`, 0.25, 0.2),
33
+ ];
34
+ }
35
+ /** One end of the belt, from `a` to `b`: a strip with its two rows of stitching. */
36
+ function end(a, b, width = W * 1.02) {
37
+ const dx = b[0] - a[0], dy = b[1] - a[1];
38
+ const l = Math.hypot(dx, dy) || 1;
39
+ const stitch = (off, t0, t1) => {
40
+ const p = lerp(a, b, t0), q = lerp(a, b, t1);
41
+ const nx = (-dy / l) * off, ny = (dx / l) * off;
42
+ return `M${f(p[0] + nx)} ${f(p[1] + ny)}L${f(q[0] + nx)} ${f(q[1] + ny)}`;
43
+ };
44
+ return [
45
+ { d: path(strip(a, b, width)), fill: "$accent", stroke: BELT_EDGE, cyl: 0.55, shade: 0.25 },
46
+ dark(stitch(-width * 0.18, 0.15, 0.97) + stitch(width * 0.18, 0.15, 0.97), 0.28, 0.3),
47
+ light(stitch(-width * 0.18 + 0.35, 0.15, 0.97) + stitch(width * 0.18 + 0.35, 0.15, 0.97), 0.25, 0.2),
48
+ ];
49
+ }
50
+ /** A small red mark: a line or a filled shape in the colour of the marks, drawn as ink in the line style too. */
51
+ const mark = (d, w = 0.6) => ({ d, fill: "none", stroke: MARK, w });
52
+ /** An arrow from `a` to `b`, with its head. */
53
+ function arrow(a, b, head = 1.7) {
54
+ const dx = b[0] - a[0], dy = b[1] - a[1];
55
+ const l = Math.hypot(dx, dy) || 1;
56
+ const ux = dx / l, uy = dy / l;
57
+ const h = (s) => `${f(b[0] - ux * head + -uy * head * s * 0.6)} ${f(b[1] - uy * head + ux * head * s * 0.6)}`;
58
+ return mark(`M${f(a[0])} ${f(a[1])}L${f(b[0])} ${f(b[1])}M${h(1)}L${f(b[0])} ${f(b[1])}L${h(-1)}`, 0.7);
59
+ }
60
+ /** The belt at each step, in the jacket's units, back to front. */
61
+ function beltAt(step) {
62
+ const ends = (cx) => [cx - 3.2, cx + 3.2];
63
+ switch (step) {
64
+ case 1: {
65
+ // One layer across the belly, a notch marking the middle of the belt, and a red arrow down to it.
66
+ const y = 26.3;
67
+ return [...wrap(y), mark(`M${MID} ${f(y + 0.3)}V${f(y + W - 0.3)}`, 0.5), arrow([MID, y - 5.6], [MID, y - 0.8])];
68
+ }
69
+ case 2: {
70
+ // Two wraps, and the ends that came round the back hanging free in front, apart.
71
+ const [l, r] = ends(MID);
72
+ return [...wrap(Y_LOWER), ...wrap(Y_UPPER), ...end([l, Y_LOWER + 0.8], [l - 0.4, Y_LOWER + 0.8 + TAIL]), ...end([r, Y_LOWER + 0.8], [r + 0.4, Y_LOWER + 0.8 + TAIL])];
73
+ }
74
+ case 3: {
75
+ // The ends cross: the one on the wearer's left (the right of the picture) lies over the other.
76
+ const top = Y_LOWER + 0.8;
77
+ return [...wrap(Y_LOWER), ...wrap(Y_UPPER), ...end([MID - 2.4, top], [MID + 3.6, top + TAIL - 0.6]), ...end([MID + 2.4, top], [MID - 3.6, top + TAIL - 0.6])];
78
+ }
79
+ case 4: {
80
+ // One end goes up under both wraps: it shows below the belt and again above it, the wraps hiding the middle of it, and a red arrow follows its way.
81
+ const x = MID - 1.8;
82
+ const under = end([x, Y_UPPER - 4.2], [x, Y_LOWER + 6.8]);
83
+ const hanging = end([MID + 3.4, Y_LOWER + 0.8], [MID + 3.8, Y_LOWER + 0.8 + TAIL]);
84
+ return [...under, ...wrap(Y_LOWER), ...wrap(Y_UPPER), ...hanging, arrow([MID - 5.8, Y_LOWER + 8.4], [MID - 5.8, Y_UPPER - 5.2])];
85
+ }
86
+ case 5: {
87
+ // The knot: two half knots, the second laid the other way (the flat square knot), so the ends are not yet even, and a red turn over it for each half.
88
+ const y = Y_UPPER + (Y_LOWER + W - Y_UPPER) / 2;
89
+ return [
90
+ ...belt({ edge: BELT_EDGE, main: "$accent", cx: MID, x0: X0, x1: X1, y: Y_UPPER, w: W, sag: 0, knot: 6.4, tail: TAIL + 1.2, tail2: TAIL - 2.6, splay: 14 }),
91
+ mark(`M${MID - 4.4} ${f(y - 4.2)}Q${MID} ${f(y - 7.4)} ${MID + 4.4} ${f(y - 4.2)}`, 0.6),
92
+ arrow([MID + 3.6, y - 6.4], [MID + 4.6, y - 4]),
93
+ ];
94
+ }
95
+ case 6: {
96
+ // Tied: both ends hang even, and a red mark under them shows their tips on one line.
97
+ const yc = (Y_UPPER + Y_LOWER - 0.3 + W) / 2 + 0.2 * 3;
98
+ const tip = yc + Math.cos((6 * Math.PI) / 180) * TAIL;
99
+ const y = tip + 2;
100
+ return [
101
+ ...belt({ edge: BELT_EDGE, main: "$accent", cx: MID, x0: X0, x1: X1, y: Y_UPPER, w: W, sag: 0, knot: 6.4, tail: TAIL, splay: 6 }),
102
+ mark(`M${MID - 4.6} ${f(y)}H${MID + 4.6}M${MID - 4.6} ${f(y - 1)}V${f(y + 1)}M${MID + 4.6} ${f(y - 1)}V${f(y + 1)}`, 0.5),
103
+ ];
104
+ }
105
+ }
106
+ }
107
+ /** The step's number in a red disc at the top left, in the picture's own units (not zoomed). */
108
+ function badge(step) {
109
+ const text = String(step);
110
+ const letters = { weight: 0.2, wide: 1 };
111
+ const size = fitSize(text, letters, 5, 5.2);
112
+ return [
113
+ { d: circle(7.4, 7.4, 5), fill: MARK, stroke: MARK_EDGE, w: 0.7, shade: 0.4 },
114
+ { d: linePath(text, { ...letters, size }, 7.4, 7.4), fill: "none", stroke: "#ffffff", w: letters.weight * size, line: "stroke" },
115
+ ];
116
+ }
117
+ /** The parts of a step: the jacket in close-up, the belt at that stage and the number. */
118
+ function stepParts(step) {
119
+ const zoomed = [...judogiFlat(true), ...beltAt(step)].map((p) => ({ ...p, t: ZOOM }));
120
+ return [...zoomed, ...badge(step)];
121
+ }
122
+ const STEPS = [
123
+ { en: "Belt step 1: centre", ja: "帯の結び方(1)中心" },
124
+ { en: "Belt step 2: two wraps", ja: "帯の結び方(2)二重巻き" },
125
+ { en: "Belt step 3: cross", ja: "帯の結び方(3)交差" },
126
+ { en: "Belt step 4: tuck under", ja: "帯の結び方(4)下に通す" },
127
+ { en: "Belt step 5: square knot", ja: "帯の結び方(5)本結び" },
128
+ { en: "Belt step 6: even ends", ja: "帯の結び方(6)仕上げ" },
129
+ ];
130
+ const SUIT = "#f6f4ee";
131
+ const BLACK = "#1f2228";
132
+ const stepPiece = (step) => {
133
+ const id = `gear:obi-step-${step}`;
134
+ const { en, ja } = STEPS[step - 1];
135
+ const spec = { main: SUIT, accent: BLACK, parts: stepParts(step) };
136
+ return defineGear({ id, en, ja, source: "drawn", model: spec, draw: (o) => drawGear(id, en, spec, o) });
137
+ };
138
+ export const GEAR_OBI_STEP_1 = /* @__PURE__ */ stepPiece(1);
139
+ export const GEAR_OBI_STEP_2 = /* @__PURE__ */ stepPiece(2);
140
+ export const GEAR_OBI_STEP_3 = /* @__PURE__ */ stepPiece(3);
141
+ export const GEAR_OBI_STEP_4 = /* @__PURE__ */ stepPiece(4);
142
+ export const GEAR_OBI_STEP_5 = /* @__PURE__ */ stepPiece(5);
143
+ export const GEAR_OBI_STEP_6 = /* @__PURE__ */ stepPiece(6);
144
+ const BY_STEP = [GEAR_OBI_STEP_1, GEAR_OBI_STEP_2, GEAR_OBI_STEP_3, GEAR_OBI_STEP_4, GEAR_OBI_STEP_5, GEAR_OBI_STEP_6];
145
+ /** One step of tying the belt, 1 to 6, with the usual options (`tint` is the jacket's colour, `accent` the belt's). A number that is not a step from 1 to 6 draws the last one. */
146
+ export const obiStep = (step, options) => BY_STEP[Number.isInteger(step) && step >= 1 && step <= 6 ? step - 1 : 5].svg(options);
@@ -23,8 +23,17 @@ export function drawGear(id, title, spec, options) {
23
23
  let body = "";
24
24
  let across = false;
25
25
  let along = false;
26
+ // Parts in a row with one transform share one group, so that a close-up of many parts says its transform once.
27
+ let open = "";
26
28
  for (const p of spec.parts) {
27
- const t = p.t === undefined ? "" : ` transform="${escape(p.t)}"`;
29
+ const move = p.t ?? "";
30
+ if (move !== open) {
31
+ if (open !== "")
32
+ body += "</g>";
33
+ if (move !== "")
34
+ body += `<g transform="${escape(move)}">`;
35
+ open = move;
36
+ }
28
37
  const fill = resolve(p.fill);
29
38
  const hasFill = fill !== undefined && fill !== "none";
30
39
  if (line) {
@@ -32,33 +41,35 @@ export function drawGear(id, title, spec, options) {
32
41
  if (mode === "none" || p.fx === true)
33
42
  continue;
34
43
  if (mode === "ink" && hasFill)
35
- body += `<path d="${p.d}" fill="currentColor"${t}/>`;
44
+ body += `<path d="${p.d}" fill="currentColor"/>`;
36
45
  else if (!hasFill && p.stroke !== undefined)
37
- body += `<path d="${p.d}" fill="none" stroke="currentColor" stroke-width="${num(p.w ?? 2, 2)}"${t}/>`;
46
+ body += `<path d="${p.d}" fill="none" stroke="currentColor" stroke-width="${num(p.w ?? 2, 2)}"/>`;
38
47
  else
39
- body += `<path d="${p.d}" fill="none" stroke="currentColor" stroke-width="${num(Math.max(p.w ?? 2, 1.6), 2)}"${t}/>`;
48
+ body += `<path d="${p.d}" fill="none" stroke="currentColor" stroke-width="${num(Math.max(p.w ?? 2, 1.6), 2)}"/>`;
40
49
  continue;
41
50
  }
42
51
  const op = p.op === undefined ? "" : ` opacity="${p.op}"`;
43
52
  if (p.fx === true) {
44
53
  if (!flat)
45
- body += `<path d="${p.d}" fill="${fill}"${op}${t}/>`;
54
+ body += `<path d="${p.d}" fill="${fill}"${op}/>`;
46
55
  continue;
47
56
  }
48
57
  const stroke = resolve(p.stroke) ?? (hasFill ? mix(readColour(fill) === undefined ? "#888888" : fill, -0.45) : "none");
49
58
  const w = p.w ?? 0.9;
50
- body += `<path d="${p.d}" fill="${hasFill ? fill : "none"}" stroke="${stroke}" stroke-width="${num(w, 2)}"${hasFill || stroke === "none" ? ` stroke-linejoin="round"` : ` stroke-linecap="round"`}${op}${t}/>`;
59
+ body += `<path d="${p.d}" fill="${hasFill ? fill : "none"}" stroke="${stroke}" stroke-width="${num(w, 2)}"${hasFill || stroke === "none" ? ` stroke-linejoin="round"` : ` stroke-linecap="round"`}${op}/>`;
51
60
  if (hasFill && !flat) {
52
61
  if (p.shade !== 0) {
53
62
  along = true;
54
- body += `<path d="${p.d}" fill="url(#${gid})"${p.shade === undefined ? "" : ` opacity="${p.shade}"`}${t}/>`;
63
+ body += `<path d="${p.d}" fill="url(#${gid})"${p.shade === undefined ? "" : ` opacity="${p.shade}"`}/>`;
55
64
  }
56
65
  if (p.cyl !== undefined && p.cyl > 0) {
57
66
  across = true;
58
- body += `<path d="${p.d}" fill="url(#${gid}c)"${p.cyl === 1 ? "" : ` opacity="${p.cyl}"`}${t}/>`;
67
+ body += `<path d="${p.d}" fill="url(#${gid}c)"${p.cyl === 1 ? "" : ` opacity="${p.cyl}"`}/>`;
59
68
  }
60
69
  }
61
70
  }
71
+ if (open !== "")
72
+ body += "</g>";
62
73
  const defs = (along ? `<linearGradient id="${gid}" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff" stop-opacity=".42"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".3"/></linearGradient>` : "") +
63
74
  (across ? `<linearGradient id="${gid}c" x2="1"><stop offset="0" stop-color="#000" stop-opacity=".34"/><stop offset=".26" stop-color="#fff" stop-opacity=".5"/><stop offset=".55" stop-color="#fff" stop-opacity="0"/><stop offset=".88" stop-color="#000" stop-opacity=".22"/><stop offset="1" stop-color="#000" stop-opacity=".4"/></linearGradient>` : "");
64
75
  const box = spec.box ?? "0 0 64 64";
@@ -18,9 +18,10 @@ import { GEAR_DIVING_BOARD, GEAR_SWIM_GOGGLES } from "./gear.water.js";
18
18
  import { GEAR_GUNBAI, GEAR_MAWASHI } from "./gear.sumo.js";
19
19
  import { GEAR_SHINAI, GEAR_KENDO_MEN } from "./gear.kendo.js";
20
20
  import { GEAR_JUDOGI, GEAR_JUDOGI_BLUE, GEAR_JUDOGI_FOLDED, GEAR_JUDOGI_BLUE_FOLDED, GEAR_JUDOGI_BACK, GEAR_JUDOGI_BLUE_BACK, GEAR_KARATE_GI, GEAR_OBI, GEAR_OBI_WHITE, GEAR_OBI_YELLOW, GEAR_OBI_ORANGE, GEAR_OBI_GREEN, GEAR_OBI_BLUE, GEAR_OBI_BROWN, GEAR_OBI_KOHAKU, GEAR_OBI_RED, GEAR_OBI_WOMEN, GEAR_OBI_WHITE_YELLOW, GEAR_OBI_YELLOW_ORANGE, GEAR_OBI_ORANGE_GREEN, GEAR_OBI_GREEN_BLUE, GEAR_OBI_BLUE_BROWN } from "./gear.budo.js";
21
+ import { GEAR_OBI_STEP_1, GEAR_OBI_STEP_2, GEAR_OBI_STEP_3, GEAR_OBI_STEP_4, GEAR_OBI_STEP_5, GEAR_OBI_STEP_6 } from "./gear.budo.steps.js";
21
22
  import { GEAR_YUMI, GEAR_YA } from "./gear.kyudo.js";
22
23
  /** Every piece of gear, in the order a gallery shows them. */
23
- export const GEARS = [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_JUDOGI_BLUE, GEAR_JUDOGI_FOLDED, GEAR_JUDOGI_BLUE_FOLDED, GEAR_JUDOGI_BACK, GEAR_JUDOGI_BLUE_BACK, GEAR_OBI, GEAR_OBI_WHITE, GEAR_OBI_YELLOW, GEAR_OBI_ORANGE, GEAR_OBI_GREEN, GEAR_OBI_BLUE, GEAR_OBI_BROWN, GEAR_OBI_KOHAKU, GEAR_OBI_RED, GEAR_OBI_WOMEN, GEAR_OBI_WHITE_YELLOW, GEAR_OBI_YELLOW_ORANGE, GEAR_OBI_ORANGE_GREEN, GEAR_OBI_GREEN_BLUE, GEAR_OBI_BLUE_BROWN, GEAR_YUMI, GEAR_YA, GEAR_KARATE_GI];
24
+ export const GEARS = [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_JUDOGI_BLUE, GEAR_JUDOGI_FOLDED, GEAR_JUDOGI_BLUE_FOLDED, GEAR_JUDOGI_BACK, GEAR_JUDOGI_BLUE_BACK, GEAR_OBI, GEAR_OBI_WHITE, GEAR_OBI_YELLOW, GEAR_OBI_ORANGE, GEAR_OBI_GREEN, GEAR_OBI_BLUE, GEAR_OBI_BROWN, GEAR_OBI_KOHAKU, GEAR_OBI_RED, GEAR_OBI_WOMEN, GEAR_OBI_WHITE_YELLOW, GEAR_OBI_YELLOW_ORANGE, GEAR_OBI_ORANGE_GREEN, GEAR_OBI_GREEN_BLUE, GEAR_OBI_BLUE_BROWN, GEAR_OBI_STEP_1, GEAR_OBI_STEP_2, GEAR_OBI_STEP_3, GEAR_OBI_STEP_4, GEAR_OBI_STEP_5, GEAR_OBI_STEP_6, GEAR_YUMI, GEAR_YA, GEAR_KARATE_GI];
24
25
  /** The id of every piece of gear: `"gear:hockey-stick"`. */
25
26
  export const GEAR_IDS = GEARS.map((g) => g.id);
26
27
  const TABLE = new Map(GEARS.map((g) => [g.id, g]));
package/dist/index.d.ts CHANGED
@@ -140,5 +140,7 @@ export { GEAR_GUNBAI, GEAR_MAWASHI } from "./emblems/gear/gear.sumo.ts";
140
140
  export { GEAR_SHINAI, GEAR_KENDO_MEN } from "./emblems/gear/gear.kendo.ts";
141
141
  export { GEAR_JUDOGI, GEAR_JUDOGI_BLUE, GEAR_JUDOGI_FOLDED, GEAR_JUDOGI_BLUE_FOLDED, GEAR_JUDOGI_BACK, GEAR_JUDOGI_BLUE_BACK, GEAR_KARATE_GI, GEAR_OBI, GEAR_OBI_WHITE, GEAR_OBI_YELLOW, GEAR_OBI_ORANGE, GEAR_OBI_GREEN, GEAR_OBI_BLUE, GEAR_OBI_BROWN, GEAR_OBI_KOHAKU, GEAR_OBI_RED, GEAR_OBI_WOMEN, GEAR_OBI_WHITE_YELLOW, GEAR_OBI_YELLOW_ORANGE, GEAR_OBI_ORANGE_GREEN, GEAR_OBI_GREEN_BLUE, GEAR_OBI_BLUE_BROWN, JUDOGI_WHITE, JUDOGI_BLUE, OBI_RANKS, obi } from "./emblems/gear/gear.budo.ts";
142
142
  export type { ObiRank, ObiOptions } from "./emblems/gear/gear.budo.ts";
143
+ export { GEAR_OBI_STEP_1, GEAR_OBI_STEP_2, GEAR_OBI_STEP_3, GEAR_OBI_STEP_4, GEAR_OBI_STEP_5, GEAR_OBI_STEP_6, obiStep } from "./emblems/gear/gear.budo.steps.ts";
144
+ export type { ObiStep } from "./emblems/gear/gear.budo.steps.ts";
143
145
  export { GEAR_YUMI, GEAR_YA } from "./emblems/gear/gear.kyudo.ts";
144
146
  export { VERSION } from "./version.ts";
package/dist/index.js CHANGED
@@ -140,5 +140,6 @@ export { GEAR_DIVING_BOARD, GEAR_SWIM_GOGGLES } from "./emblems/gear/gear.water.
140
140
  export { GEAR_GUNBAI, GEAR_MAWASHI } from "./emblems/gear/gear.sumo.js";
141
141
  export { GEAR_SHINAI, GEAR_KENDO_MEN } from "./emblems/gear/gear.kendo.js";
142
142
  export { GEAR_JUDOGI, GEAR_JUDOGI_BLUE, GEAR_JUDOGI_FOLDED, GEAR_JUDOGI_BLUE_FOLDED, GEAR_JUDOGI_BACK, GEAR_JUDOGI_BLUE_BACK, GEAR_KARATE_GI, GEAR_OBI, GEAR_OBI_WHITE, GEAR_OBI_YELLOW, GEAR_OBI_ORANGE, GEAR_OBI_GREEN, GEAR_OBI_BLUE, GEAR_OBI_BROWN, GEAR_OBI_KOHAKU, GEAR_OBI_RED, GEAR_OBI_WOMEN, GEAR_OBI_WHITE_YELLOW, GEAR_OBI_YELLOW_ORANGE, GEAR_OBI_ORANGE_GREEN, GEAR_OBI_GREEN_BLUE, GEAR_OBI_BLUE_BROWN, JUDOGI_WHITE, JUDOGI_BLUE, OBI_RANKS, obi } from "./emblems/gear/gear.budo.js";
143
+ export { GEAR_OBI_STEP_1, GEAR_OBI_STEP_2, GEAR_OBI_STEP_3, GEAR_OBI_STEP_4, GEAR_OBI_STEP_5, GEAR_OBI_STEP_6, obiStep } from "./emblems/gear/gear.budo.steps.js";
143
144
  export { GEAR_YUMI, GEAR_YA } from "./emblems/gear/gear.kyudo.js";
144
145
  export { VERSION } from "./version.js";
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.6.0";
1
+ export declare const VERSION = "0.7.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.6.0";
1
+ export const VERSION = "0.7.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/dougu-sports",
3
- "version": "0.6.0",
3
+ "version": "0.7.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",