@johnmorrisdotca/kyogijo-stadiums 0.20.0 → 0.21.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
@@ -7,6 +7,15 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.21.0] - 2026-10-20
11
+
12
+ ### Added
13
+
14
+ - **The boxing ring** (`sport: "boxing"`, [the combat sports page](./docs/combat.md)): a square raised platform with its canvas, four ropes that turn to a post at each corner by an extension, the posts and their pads in red, blue and white, an apron and three sets of steps, to scale from the Nevada Rules of Unarmed Combat (2.050), Wisconsin's SPS 192.38 and the WBC Amateur rules, with Ontario's amateur rules beside them. The leagues are `nruc-ring` (20 feet within the ropes), `wisconsin` (16 to 20 feet) and `wbc-amateur` (4.5 to 6.1 m); `budo.size` and `budo.height` stay within each league's range and refuse a number outside it, `budo.steps` and `budo.corners` take the steps away and make the corners plain; the looks are `white`, `blue` and `black`, light and dark. `RANGES.boxing` has `ropes`, `corner` and `half`; the parts are named for `landmarks` and the features for `featureColors`, in English and Japanese. A figure that is the rule's, one that is typical and one that is ours are told apart in [Sources](./docs/sources.md#the-combat-sports). It is the forty-first sport.
15
+ - **The ring in 3D:** the platform is a solid block as the cage's is, the four posts are solid in their corner's colour at 58 inches over the ring floor, each of the four ropes is a thin round bar along each side and out to its post at each corner so that the canvas stays in sight, two ties a side hold the ropes in line, and the steps are three blocks each. Tests hold the order the parts are drawn in from every side, the size of the view as it turns and the scene inside its box at every angle.
16
+ - **The demo** has the ring in its sport picker, with its league, its looks, the score of a bout and the 3D view.
17
+
18
+
10
19
  ## [0.20.0] - 2026-10-20
11
20
 
12
21
  ### Added
package/NOTICE.md CHANGED
@@ -35,7 +35,7 @@ picture or code of the sources is copied; each source is named here so that the
35
35
  | `leagues`: the skating rinks | The International Skating Union's Sports Rules - Speed Skating (Special Regulations and Technical Rules, Rules 203 to 206, 226 to 228 and 253 to 257, and "H. Track", effective 12 June 2026), Special Regulations and Technical Rules Short Track Speed Skating (Rule 280 and the diagram of the tracks, 2024) and Sports Rules - Figure Skating (Rule 342, effective 12 June 2026) | Facts; no text or drawing copied |
36
36
  | `leagues` and `halls`: the budō surfaces | The International Judo Federation's Sport and Organisation Rules (version of 21 January 2026, 8.7, Article 9, B1.8; and the June 2020 version, 18.1) and its Kata Commission's Tournament Handbook (2024) and Criteria for the Evaluation (January 2025); the All Japan Judo Federation's 大会運営ガイドブック2023; the World Karate Federation's Kumite and Kata Competition Rules (2026); the International Kendo Federation's Regulations of Kendo Shiai and Shinpan (Article 2 and Figure 1); the Japan Sumo Association's 相撲規則 土俵規定 第一条 and 大相撲クイズ, as the Sasakawa Sports Foundation and ozumo.jp quote them; the All Nippon Kyudo Federation's 弓道競技運営要領 (2016) and the National Sports Festival 2015's rules for kinteki and enteki; the tatami sizes of English and Japanese Wikipedia; the Kodokan's 施設のご案内, the Nippon Budokan's 日本武道館建物等概要 (October 2021), and English and Japanese Wikipedia and Wikidata on the five halls and the kimarite | Facts |
37
37
  | `frames`: the soccer providers' coordinates | StatsBomb's Open Data Events specification (Appendix 2), the Wyscout and Impect glossaries, the Metrica Sports and SkillCorner open-data READMEs; for Opta, Tracab and Second Spectrum, whose specifications are not public, the figures of mplsoccer, ggsoccer, kloppy and floodlight (docs/sources.md) | Facts; no text, table or code copied |
38
- | `leagues`: the MMA cage | The Nevada Rules of Unarmed Combat (NRUC 5.010, the file of 20 November 2024), Oregon Administrative Rule 230-070-0025, the IMMAF's Unified Rules for amateur, junior and senior competition (January 2021), and the English Wikipedia articles "Mixed martial arts cage" (revision 1377260922) and "Mixed martial arts" (revision 1381048799), for the sizes that are typical | Facts, CC BY-SA 4.0 for the Wikipedia text read |
38
+ | `leagues`: the MMA cage and the boxing ring | The Nevada Rules of Unarmed Combat (NRUC 5.010, the file of 20 November 2024), Oregon Administrative Rule 230-070-0025, the IMMAF's Unified Rules for amateur, junior and senior competition (January 2021), Wisconsin's SPS 192.38, the WBC Amateur competition rules (2026), Ontario's Amateur combative sport rules: Boxing (Part II), and the English Wikipedia articles "Mixed martial arts cage" (revision 1377260922), "Mixed martial arts" (revision 1381048799) and "Boxing ring" (revision 1370389178), for the sizes that are typical | Facts, CC BY-SA 4.0 for the Wikipedia text read |
39
39
  | `parks`: the fence of each ballpark, current and past, and the seasons of a past park | The distances (and, for a park whose fence moved, the years an article dates them) published in the English and Japanese Wikipedia article of each park, at the revision cited below; the seasons a club played there from the same article's list of tenants | Facts. The articles are CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/); none of their text, tables or images is used, and the angles between the distances are our own reading |
40
40
  | `parks`: the Wikidata item of each ballpark (`wikidata`) | The item (`Q49136`) that each cited article is linked to, read with the MediaWiki API (`pageprops`, `wikibase_item`) and checked against the item's label | Wikidata's data is CC0 (https://creativecommons.org/publicdomain/zero/1.0/) |
41
41
  | `parks` and `grounds`: the names (`name`, `nameJa`, `readingJa`, `localName`, `aliases`) | The names are facts. Each alias says where it was read (`source`): the title of the English or Japanese Wikipedia article a place cites (with its revision), a name the infobox or the opening text of the same revision gives (a former or sponsor's name with the years it dates, a nickname, the name in Japanese and its reading), or Wikidata's labels, aliases and properties "official name" (P1448), "nickname" (P1449), "name" (P2561), "short name" (P1813), read with the Wikidata API by `scripts/fetch-names.mjs` into `scripts/names.wikidata.json` | Facts; Wikidata's data is CC0 (https://creativecommons.org/publicdomain/zero/1.0/), and the articles are CC BY-SA 4.0 as above, none of their text copied. Which name is the everyday one is our reading, in docs/references.md ("Names") |
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <h1 align="center">Kyogijo Stadiums <sub>競技場</sub></h1>
2
2
 
3
3
  <p align="center"><strong>Sports surfaces as SVG, drawn to scale, with the score on them.</strong><br>
4
- The surfaces of forty sports, from football and curling to the budō and the MMA cage, each drawn from its rulebook or its published distances, with each team's name and logo on its side and the score in the middle, a lineup and the situation of the game on them, with stands and a crowd, a pageant of flags and a 3D view. Wide or tall, light or dark, a badge for a list, a helper for React, a custom element and a frame for a page that runs no script. One function, no dependencies.</p>
4
+ The surfaces of forty-one sports, from football and curling to the budō and the combat sports, each drawn from its rulebook or its published distances, with each team's name and logo on its side and the score in the middle, a lineup, stands and a crowd, a pageant of flags and a 3D view. Wide or tall, light or dark, a badge for a list, a helper for React, a custom element and a frame for a page that runs no script. One function, no dependencies.</p>
5
5
 
6
6
  <p align="center">
7
7
  <a href="https://github.com/johnmorrisdotca/kyogijo-stadiums/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/johnmorrisdotca/kyogijo-stadiums/actions/workflows/ci.yml/badge.svg"></a>
@@ -34,7 +34,7 @@ The surfaces of forty sports, from football and curling to the budō and the MMA
34
34
  </tr>
35
35
  </table>
36
36
 
37
- Kyogijo Stadiums draws the places a game is played on, and puts a game on them. It was made to show the score of a game or a championship on the surface it was played on: the teams at their ends, the score in the middle, a label for what it was. The surfaces are drawn to scale from the rulebooks, so that a football field has its hash marks where the rules put them and a ballpark has its fence where its published distances put it.
37
+ Kyogijo Stadiums draws the places a game is played on, with the score of the game on them: the teams at their ends, the score in the middle, a label for what it was. The surfaces are drawn to scale from the rulebooks: a football field has its hash marks where the rules put them, a ballpark its fence where its published distances put it.
38
38
 
39
39
  ## In 30 seconds
40
40
 
@@ -72,7 +72,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
72
72
 
73
73
  ## Features
74
74
 
75
- - **Forty surfaces, to scale.** The NFL, CFL and IFAF fields, the NHL rink, the NBA court, the soccer pitch and futsal court, the baseball field, the curling sheet, twelve net and racket sports, the pools, the athletics stadium, the skating rinks, seven field sports, the budō and the MMA cage. Every figure is cited in [sources](./docs/sources.md). [The surfaces](./docs/surfaces.md).
75
+ - **Forty-one surfaces, to scale.** The NFL, CFL and IFAF fields, the NHL rink, the NBA court, the soccer pitch and futsal court, the baseball field, the curling sheet, twelve net and racket sports, the pools, the athletics stadium, the skating rinks, seven field sports, the budō, the MMA cage and the boxing ring. Every figure is cited in [sources](./docs/sources.md). [The surfaces](./docs/surfaces.md).
76
76
  - **Each league's rulebook.** `league: "fiba"` draws a court as FIBA marks it: 48 variants, from the WNBA, the NCAA and the IIHF to Little League, PONY, softball, Curling Canada, FIVB's beach volleyball, rugby league and the NLL's box lacrosse, each cited. [League variants](./docs/variants.md).
77
77
  - **A realistic basketball floor.** `court: { hardwood: true }` draws maple boards that run the length of the court with staggered ends, grain, stained keys, the baskets and stanchions with soft shadows, a coaching box and benches, and a satin sheen, in every league and renderer. The clean floor stays the default. [Hardwood](./docs/hardwood.md).
78
78
  - **The stands and the crowd.** `crowd: true` draws the seating round any surface that has spectators: a bowl, two sides, a horseshoe, courtside rows or a ballpark's grandstands, with the people as seeded heads and shoulders in the home colours and the empty seats showing (`density`, or `attendance` over `capacity`). [The crowd](./docs/crowd.md).
@@ -119,7 +119,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
119
119
  | Part | What it is |
120
120
  | --- | --- |
121
121
  | `drawKyogijo`, `drawSurface`, `drawScore`, `drawBadge` | The drawing, as a string of SVG. |
122
- | `SPORTS`, `PARKS`, `PARK_IDS`, `COURT_THEMES` | The forty sports, the 94 ballparks, the court looks. |
122
+ | `SPORTS`, `PARKS`, `PARK_IDS`, `COURT_THEMES` | The forty-one sports, the 94 ballparks, the court looks. |
123
123
  | `SOCCER_PITCHES`, `SOCCER_PROVIDERS`, `rescale`, `soccerSpec` | The soccer pitches, the data providers' coordinate systems and the map between them. |
124
124
  | `GROUNDS`, `groundById` | The 64 soccer grounds, each pitch at its own size. |
125
125
  | `EVENTS`, `eventOptions` | The 18 special games: a rink in a ballpark, a court on a carrier's deck, with `host` and `placement` in the options. |
@@ -233,7 +233,7 @@ console.log(box(wide), box(tall));
233
233
  <br><em>The same game tall: the teams top and bottom, every word upright.</em>
234
234
  </p>
235
235
 
236
- [The surfaces](./docs/surfaces.md) lists the forty, with their sizes and what is drawn; [score and layout](./docs/score.md) has the options of a score, a strip and a compact card.
236
+ [The surfaces](./docs/surfaces.md) lists the forty-one, with their sizes and what is drawn; [score and layout](./docs/score.md) has the options of a score, a strip and a compact card.
237
237
 
238
238
  ### A league's rulebook
239
239
 
@@ -613,14 +613,14 @@ A table in the ITTF's playing area, 14 m by 7 m, with surrounds and chairs ([cou
613
613
  <br><em>Close up: the centre tape and the two starting tapes.</em>
614
614
  </p>
615
615
 
616
- ### An MMA cage
616
+ ### The combat sports
617
617
 
618
- [The combat sports page](./docs/combat.md).
618
+ An MMA cage and a boxing ring ([the combat sports page](./docs/combat.md)).
619
619
 
620
620
  <p align="center">
621
621
  <picture>
622
622
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/mma-cage-desk-dark.webp">
623
- <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/mma-cage-desk-light.webp" alt="An MMA cage from above, with a score" width="660">
623
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/mma-cage-desk-light.webp" alt="An MMA cage from above with a score" width="660">
624
624
  </picture>
625
625
  <br><em>With a score.</em>
626
626
  </p>
@@ -633,6 +633,14 @@ A table in the ITTF's playing area, 14 m by 7 m, with surrounds and chairs ([cou
633
633
  <br><em>The cage in 3D.</em>
634
634
  </p>
635
635
 
636
+ <p align="center">
637
+ <picture>
638
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/boxing-ring-3d-desk-dark.webp">
639
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/boxing-ring-3d-desk-light.webp" alt="The boxing ring in 3D: coloured corner posts, ropes and steps" width="660">
640
+ </picture>
641
+ <br><em>The ring in 3D.</em>
642
+ </p>
643
+
636
644
  ### The element in a page
637
645
 
638
646
  ```html
@@ -696,17 +704,17 @@ A page that moves (`mountKyogijo`), a file written on a server and a frame for a
696
704
 
697
705
  ## API
698
706
 
699
- The [API reference](https://johnmorrisdotca.github.io/kyogijo-stadiums/api.html) lists every export of every entry point with its signature and its doc comment. It is made from the source by `pnpm site`, so it cannot fall behind the code. [Entry points](./docs/entry-points.md) says what each of the 15 holds: the main one draws; `/geometry`, `/frames`, `/layers`, `/charts`, `/replay`, `/parks`, `/eras`, `/leagues`, `/grounds` and `/atlas` stand apart and need nothing else; `/mount`, `/turn`, `/react`, `/element` and `/element/define` put a drawing in a page.
707
+ The [API reference](https://johnmorrisdotca.github.io/kyogijo-stadiums/api.html) lists every export of every entry point with its signature and its doc comment. It is made from the source by `pnpm site`. [Entry points](./docs/entry-points.md) says what each of the 15 holds: the main one draws; `/geometry`, `/frames`, `/layers`, `/charts`, `/replay`, `/parks`, `/eras`, `/leagues`, `/grounds` and `/atlas` stand apart; `/mount`, `/turn`, `/react`, `/element` and `/element/define` put a drawing in a page.
700
708
 
701
709
  Every function is pure: it returns new values and never changes what it was given.
702
710
 
703
711
  ## Theming
704
712
 
705
- Nothing is branded. The drawing is coloured by custom properties on its `svg`, with a light set and a dark set that follow the device (or `data-theme` on the page's root, or `theme` in the options); a page sets only the ones it wants different, and its own rule always wins. Each property is listed with its light and dark value, with the court's five looks, in [theming](./docs/theming.md).
713
+ Nothing is branded. The drawing is coloured by custom properties on its `svg`, with a light set and a dark set that follow the device (or `data-theme` on the page's root, or `theme` in the options); a page's own rule always wins. Each property is listed with its light and dark value, with the court's five looks, in [theming](./docs/theming.md).
706
714
 
707
715
  ## Limits
708
716
 
709
- Each is held by a test: forty sports, ten soccer pitches and ten providers, 94 ballparks, 64 grounds, six units, forty-eight frames, a drawing of under 22 kB. The table, made from the code, is [limits](./docs/limits.md).
717
+ Each is held by a test: forty-one sports, ten soccer pitches and ten providers, 94 ballparks, 64 grounds, six units, forty-eight frames, a drawing of under 22 kB. The table, made from the code, is [limits](./docs/limits.md).
710
718
 
711
719
  ## Accessibility
712
720
 
@@ -718,7 +726,7 @@ The rest, with the contrast and what is not yet done, is in [accessibility](./do
718
726
 
719
727
  ## Browser and runtime support
720
728
 
721
- Any browser with ES modules, `:where()` and SVG 2's `href` on images: Chrome and Edge 88, Safari 14, Firefox 78, all from 2021 on. The custom element needs shadow DOM and `part`, in every browser of 2021 on. The drawing function runs on Node 22 or later and in any other JavaScript runtime: it touches no DOM. The demo is played in a real browser, at a phone's width and a desk's, in Chromium and WebKit, before every release.
729
+ Any browser with ES modules, `:where()` and SVG 2's `href` on images: Chrome and Edge 88, Safari 14, Firefox 78, all from 2021 on. The custom element needs shadow DOM and `part`. The drawing function runs on Node 22 or later and in any other JavaScript runtime: it touches no DOM. The demo is played in Chromium and WebKit, at a phone's width and a desk's, before every release.
722
730
 
723
731
  ## Languages
724
732
 
@@ -741,7 +749,7 @@ The drawing is plain functions over plain data with no DOM and no dependency. Th
741
749
 
742
750
  ## The name
743
751
 
744
- *Kyogijo* (競技場, kyōgijō, きょうぎじょう) is Japanese for a sports ground: the place a contest is held. "Stadiums" is added in English, so that the name says what it draws; in Japanese the name stays 競技場. It is written with 競技, "contest" or "competition", and 場, "place", and said in five beats, *kyo-u-gi-jo-u*. In Japanese it is the general word for a place where sport is played, a stadium or a ground; the package uses it as a name for every surface it draws, the field, the rink, the court and the diamond. The long vowels are written without a macron in the package's name, as in the other packages of the family.
752
+ *Kyogijo* (競技場, kyōgijō, きょうぎじょう) is Japanese for a sports ground, the place a contest is held, written with 競技, "contest", and 場, "place", and said *kyo-u-gi-jo-u*. "Stadiums" is added in English so that the name says what it draws; the package uses the word for every surface it draws. The long vowels are written without a macron, as in the other packages of the family.
745
753
 
746
754
  ## Where it comes from, and where it is used
747
755
 
@@ -40,6 +40,11 @@ export function budoAbout(o, language, plan = budoPlan(o)) {
40
40
  const platform = L.height > 0.0005 ? fill(words.mmaPlatform, { height: cm(L.height) }) : words.mmaFloor;
41
41
  return fill(words.mma, { width: trim(L.width), feet: trim(L.width / 0.3048), fence: cm(L.fence), platform });
42
42
  }
43
+ if (plan.boxing !== undefined) {
44
+ const L = plan.boxing;
45
+ const platform = L.height > 0.0005 ? fill(words.boxingPlatform, { height: cm(L.height) }) : words.boxingFloor;
46
+ return fill(words.boxing, { size: trim(L.size), feet: trim(L.size / 0.3048), apron: cm(L.apron), platform });
47
+ }
43
48
  if (plan.kyudo !== undefined) {
44
49
  const L = plan.kyudo;
45
50
  const fig = plan.figures;
@@ -78,6 +83,12 @@ export function budoSentences(o, language, plan = budoPlan(o)) {
78
83
  if (plan.mma.steps.length > 0)
79
84
  out.push(words.mmaSteps);
80
85
  }
86
+ if (plan.boxing !== undefined) {
87
+ if (o.budo?.corners !== false)
88
+ out.push(words.boxingCorners);
89
+ if (plan.boxing.steps.length > 0)
90
+ out.push(words.boxingSteps);
91
+ }
81
92
  if (plan.dojo !== undefined) {
82
93
  const L = plan.dojo;
83
94
  out.push(fill(words.dojoKamiza, { side: words.sides[L.kamiza] }));
@@ -14,6 +14,8 @@ export interface BudoLook {
14
14
  dirt?: string;
15
15
  sand?: string;
16
16
  straw?: string;
17
+ /** The ropes of a ring. */
18
+ rope?: string;
17
19
  }
18
20
  interface Pair {
19
21
  light: BudoLook;
@@ -52,6 +52,12 @@ export const BUDO_LOOKS = Object.freeze({
52
52
  black: lifted(pair({ ...COMMON, surface: "#2b2e33", alt: "#1f2226", red: "#b32a2f", floor: "#24272b" }), "#3b3f45"),
53
53
  blue: lifted(pair({ ...COMMON, surface: "#3a68ad", alt: "#2b528d", red: "#b32a2f", floor: "#2c3440" }), "#3a4452"),
54
54
  },
55
+ // The ring's canvas over the dark skirt of its platform: a pale canvas with dark red ropes (the default), a blue one and a black one with white ropes. Ours: the rules do not paint a ring.
56
+ boxing: {
57
+ white: lifted(pair({ ...COMMON, surface: "#e6e3da", alt: "#d4d0c4", red: "#b32a2f", floor: "#2d3a56", rope: "#b32a2f" }), "#3b4a6e"),
58
+ blue: lifted(pair({ ...COMMON, surface: "#3a68ad", alt: "#2b528d", red: "#b32a2f", floor: "#1f3358", rope: "#f4f1e8" }), "#324a78"),
59
+ black: lifted(pair({ ...COMMON, surface: "#2b2e33", alt: "#1f2226", red: "#b32a2f", floor: "#17191c", rope: "#f4f1e8" }), "#3b3f45"),
60
+ },
55
61
  kyudo: { wood: pair({ ...COMMON, surface: "#9db57d", alt: "#d8cfb0", red: "#b32a2f", floor: "#b98c58", dirt: "#c9ad7a" }) },
56
62
  });
57
63
  /** The names of a sport's looks, the default first. */
@@ -8,6 +8,7 @@ import { type KendoLayout } from "./kendo.ts";
8
8
  import { type KyudoLayout } from "./kyudo.ts";
9
9
  import { boxD } from "./kit.ts";
10
10
  import { type CageLayout } from "./cage.ts";
11
+ import { type RingLayout } from "./ring.ts";
11
12
  import { type MBox, type MMark, type MRange } from "./parts.ts";
12
13
  import { type SumoLayout } from "./sumo.ts";
13
14
  import type { BudoKind, CombatKind } from "./types.ts";
@@ -50,12 +51,13 @@ export interface BudoPlan {
50
51
  kyudo?: KyudoLayout;
51
52
  dojo?: DojoLayout;
52
53
  mma?: CageLayout;
54
+ boxing?: RingLayout;
53
55
  }
54
56
  /** The sports of the budō family. */
55
57
  export declare const BUDO_SPORTS: readonly BudoKind[];
56
58
  /** True for a sport of the budō family. */
57
59
  export declare const isBudoSport: (sport: string) => sport is Exclude<BudoKind, CombatKind>;
58
- /** The combat sports the engine of the budō also draws: the MMA cage. */
60
+ /** The combat sports the engine of the budō also draws: the MMA cage and the boxing ring. */
59
61
  export declare const COMBAT_SPORTS: readonly CombatKind[];
60
62
  /** True for a combat sport (the MMA cage). */
61
63
  export declare const isCombatSport: (sport: string) => sport is CombatKind;
package/dist/budo/plan.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { chooseLeague } from "../leagues.js";
2
2
  import { BUDO_LEAGUES, DOJO_LEAGUES, JUDO_LEAGUES, KARATE_LEAGUES, KENDO_LEAGUES, KYUDO_LEAGUES, SUMO_LEAGUES } from "../leagues/budo.js";
3
- import { MMA_LEAGUES } from "../leagues/combat.js";
3
+ import { BOXING_LEAGUES, MMA_LEAGUES } from "../leagues/combat.js";
4
4
  import { dojoLayout, dojoLeague, dojoSpecOf } from "./dojo.js";
5
5
  import { contestOf, gridOf, judoParts, matOf, safetyOf, snapTo } from "./judo.js";
6
6
  import { karateParts } from "./karate.js";
@@ -8,6 +8,7 @@ import { kendoParts, kendoSide } from "./kendo.js";
8
8
  import { kyudoParts, shootersOf } from "./kyudo.js";
9
9
  import { boxD } from "./kit.js";
10
10
  import { cageHeight, cageParts, cageWidth, fenceHeight } from "./cage.js";
11
+ import { ringHeight, ringParts, ringSize } from "./ring.js";
11
12
  import { emptyParts, grow, union } from "./parts.js";
12
13
  import { dohyoHeight, sumoParts } from "./sumo.js";
13
14
  import { lookOf } from "./looks.js";
@@ -18,13 +19,13 @@ const isBudoLeague = (l) => typeof l === "object" && l !== null && BUDO_LEAGUES.
18
19
  export const BUDO_SPORTS = Object.freeze(["judo", "karate", "kendo", "sumo", "kyudo", "dojo"]);
19
20
  /** True for a sport of the budō family. */
20
21
  export const isBudoSport = (sport) => BUDO_SPORTS.includes(sport);
21
- /** The combat sports the engine of the budō also draws: the MMA cage. */
22
- export const COMBAT_SPORTS = Object.freeze(["mma"]);
22
+ /** The combat sports the engine of the budō also draws: the MMA cage and the boxing ring. */
23
+ export const COMBAT_SPORTS = Object.freeze(["mma", "boxing"]);
23
24
  /** True for a combat sport (the MMA cage). */
24
25
  export const isCombatSport = (sport) => COMBAT_SPORTS.includes(sport);
25
26
  /** True for a sport this engine draws: a sport of the budō or a combat sport. */
26
27
  export const isMatSport = (sport) => isBudoSport(sport) || isCombatSport(sport);
27
- const OWN = { judo: JUDO_LEAGUES, karate: KARATE_LEAGUES, kendo: KENDO_LEAGUES, sumo: SUMO_LEAGUES, kyudo: KYUDO_LEAGUES, dojo: DOJO_LEAGUES, mma: MMA_LEAGUES };
28
+ const OWN = { judo: JUDO_LEAGUES, karate: KARATE_LEAGUES, kendo: KENDO_LEAGUES, sumo: SUMO_LEAGUES, kyudo: KYUDO_LEAGUES, dojo: DOJO_LEAGUES, mma: MMA_LEAGUES, boxing: BOXING_LEAGUES };
28
29
  /** What `budo.style` asks of a judo area: true for a kata area, false for a contest area, undefined for neither said. @throws TypeError for a value that is not `shiai` or `kata`. */
29
30
  function kataOf(style) {
30
31
  if (style === undefined)
@@ -132,6 +133,13 @@ export function budoPlan(o) {
132
133
  const ranges = { ...L.ranges, half: halves(L.platform) };
133
134
  return finish({ box: L.platform, extent: L.extent, areas: L.parts.areas, shapes: L.parts.shapes, lineWidth: f.post.diameter, ranges, marks: L.marks, centre: { x: L.centre[0], y: L.centre[1], r: L.apothem * 0.35 }, mma: L });
134
135
  }
136
+ case "boxing": {
137
+ const f = fig;
138
+ const rule = `the rules of ${league.id === "wbc-amateur" ? "the WBC Amateur" : league.id === "wisconsin" ? "Wisconsin" : "Nevada"}`;
139
+ const L = ringParts(f, { size: ringSize(style.size, f, rule), height: ringHeight(style.height, f, rule), steps: flag(style.steps, true, "budo.steps"), corners: flag(style.corners, true, "budo.corners") });
140
+ const ranges = { ...L.ranges, half: halves(L.platform) };
141
+ return finish({ box: L.platform, extent: L.extent, areas: L.parts.areas, shapes: L.parts.shapes, lineWidth: f.ropes.diameter.drawn, ranges, marks: L.marks, centre: { x: L.centre[0], y: L.centre[1], r: L.size * 0.2 }, boxing: L });
142
+ }
135
143
  default: {
136
144
  const L = dojoLayout(o.dojo ?? {}, dojoLeague(league.id));
137
145
  const f = L.floor;
@@ -0,0 +1,47 @@
1
+ import type { RingFigures } from "../leagues/combat.ts";
2
+ import { type MBox, type MMark, type Parts } from "./parts.ts";
3
+ /** The colour of a corner's post and pad: the red and the blue corner of the two boxers, and two neutral ones in white. */
4
+ export type CornerColour = "red" | "blue" | "white";
5
+ /** One corner of the ring: its post's place on the platform, the corner of the rope square its ropes turn at, its colour and which of the four it is (0 top left, clockwise). */
6
+ export interface RingCorner {
7
+ index: number;
8
+ colour: CornerColour;
9
+ post: [number, number];
10
+ rope: [number, number];
11
+ }
12
+ /** A set of steps: the box it covers outside the platform's edge, and the edge it is on. */
13
+ export interface RingSteps {
14
+ edge: "north" | "south";
15
+ corner: number;
16
+ box: MBox;
17
+ }
18
+ /** What a ring plan is. */
19
+ export interface RingLayout {
20
+ parts: Parts;
21
+ /** The platform, which is the surface's own box: the canvas inside the ropes and the apron round it. */
22
+ platform: MBox;
23
+ extent: MBox;
24
+ centre: [number, number];
25
+ /** The ring inside the ropes, and its side. */
26
+ ring: MBox;
27
+ size: number;
28
+ /** The apron's width as drawn, from the ropes to the platform's edge. */
29
+ apron: number;
30
+ corners: RingCorner[];
31
+ steps: RingSteps[];
32
+ /** The platform's height over the floor of the building, in metres. */
33
+ height: number;
34
+ marks: MMark[];
35
+ ranges: Record<string, MBox>;
36
+ }
37
+ /** The size the options ask for, held to what the rules allow. */
38
+ export declare const ringSize: (asked: number | undefined, fig: RingFigures, rule: string) => number;
39
+ /** The platform's height the options ask for, held to what the rules allow. */
40
+ export declare const ringHeight: (asked: number | undefined, fig: RingFigures, rule: string) => number;
41
+ /** The plan of the ring the options ask for. */
42
+ export declare function ringParts(fig: RingFigures, o: {
43
+ size: number;
44
+ height: number;
45
+ steps: boolean;
46
+ corners: boolean;
47
+ }): RingLayout;
@@ -0,0 +1,80 @@
1
+ // A ring: the raised square platform with its canvas, the ropes held by a post at each corner (with the corner pads in the boxers' colours and two neutral ones), the apron beyond the ropes and the steps up to it, from the
2
+ // rules a commission or a federation writes (the Nevada Rules of Unarmed Combat 2.050, Wisconsin's SPS 192.38, the WBC Amateur rules, Ontario's amateur rules). Boxing draws it with its own figures, and the professional
3
+ // wrestling ring will with its own. In metres, the origin at the top left of the platform, north at the top. Nothing here draws SVG.
4
+ import { box, line } from "../fields.kit.js";
5
+ import { rectArea, within } from "./kit.js";
6
+ import { emptyParts, grow, union } from "./parts.js";
7
+ /** The size the options ask for, held to what the rules allow. */
8
+ export const ringSize = (asked, fig, rule) => (asked === undefined ? fig.size.drawn : within(asked, fig.size.min, fig.size.max, "The ring", rule));
9
+ /** The platform's height the options ask for, held to what the rules allow. */
10
+ export const ringHeight = (asked, fig, rule) => (asked === undefined ? fig.platform.height.drawn : within(asked, 0, fig.platform.height.max, "The ring's platform", rule));
11
+ /** The colours of the four corners, clockwise from the top left: neutral, blue, neutral, red (the red corner at the left and near the table). */
12
+ const COLOURS = ["white", "blue", "white", "red"];
13
+ const rd = (v) => Math.round(v * 1000) / 1000;
14
+ /** The plan of the ring the options ask for. */
15
+ export function ringParts(fig, o) {
16
+ const out = fig.post.fromRopes;
17
+ // The apron reaches at least to the far side of the posts and their pads.
18
+ const apron = Math.max(fig.apron.drawn, out + fig.post.pad / 2 + 0.02);
19
+ const side = o.size + 2 * apron;
20
+ const platform = { x0: 0, y0: 0, x1: side, y1: side };
21
+ const ring = { x0: apron, y0: apron, x1: apron + o.size, y1: apron + o.size };
22
+ const parts = emptyParts();
23
+ parts.areas.push(rectArea("platform", "canvas", "ky-bud-t1", platform));
24
+ const rope = (i) => [i === 0 || i === 3 ? ring.x0 : ring.x1, i === 0 || i === 1 ? ring.y0 : ring.y1];
25
+ const corners = [0, 1, 2, 3].map((i) => {
26
+ const [rx, ry] = rope(i);
27
+ const sx = i === 0 || i === 3 ? -1 : 1;
28
+ const sy = i === 0 || i === 1 ? -1 : 1;
29
+ return { index: i, colour: COLOURS[i], post: [rd(rx + sx * out), rd(ry + sy * out)], rope: [rx, ry] };
30
+ });
31
+ // The steps: one in each boxer's corner (the red and the blue) and one in a neutral corner, on the apron's edge beside the corner.
32
+ const steps = [];
33
+ const s = fig.platform.steps;
34
+ if (o.steps && o.height > 0) {
35
+ const depth = s.treads * s.tread;
36
+ const at = (corner) => {
37
+ const c = corners[corner];
38
+ const north = corner === 0 || corner === 1;
39
+ // Beside the post, a metre and a half along the edge from the corner, towards the middle.
40
+ const cx = c.post[0] < side / 2 ? c.post[0] + 1.3 : c.post[0] - 1.3;
41
+ const b = north ? { x0: cx - s.width / 2, y0: -depth, x1: cx + s.width / 2, y1: 0 } : { x0: cx - s.width / 2, y0: side, x1: cx + s.width / 2, y1: side + depth };
42
+ steps.push({ edge: north ? "north" : "south", corner, box: b });
43
+ for (let t = 0; t < s.treads; t += 1) {
44
+ const y0 = north ? -depth + t * s.tread : side + t * s.tread;
45
+ parts.areas.push(rectArea(`step-${corner}-${t + 1}`, "steps", "ky-bud-step", { x0: b.x0, y0, x1: b.x1, y1: y0 + s.tread }));
46
+ }
47
+ };
48
+ // The red corner (bottom left), the blue corner (top right) and the neutral corner nearest the table (bottom right).
49
+ for (const corner of [3, 1, 2])
50
+ at(corner);
51
+ }
52
+ // The ropes seen from above are one line each side; each turns to its post by the extension.
53
+ const w = Math.max(fig.ropes.diameter.drawn, 0.03);
54
+ parts.shapes.push(box("ropes", "ropes", ring.x0, ring.y0, ring.x1, ring.y1, { w, cls: "ky-bud-rope", least: w }));
55
+ for (const c of corners)
56
+ parts.shapes.push(line(`rope-extension-${c.index + 1}`, "ropes", c.rope[0], c.rope[1], c.post[0], c.post[1], { w, cls: "ky-bud-rope", least: w }));
57
+ // The posts with their pads, in the corner colours (or plain when the corners are not coloured).
58
+ for (const c of corners) {
59
+ const pad = fig.post.pad;
60
+ parts.areas.push(rectArea(`corner-${c.index + 1}`, "corners", o.corners ? `ky-bud-corner-${c.colour}` : "ky-bud-pad", { x0: c.post[0] - pad / 2, y0: c.post[1] - pad / 2, x1: c.post[0] + pad / 2, y1: c.post[1] + pad / 2 }));
61
+ }
62
+ const extent = grow(union([platform, ...steps.map((x) => x.box)]), 0.6);
63
+ const mid = side / 2;
64
+ const ranges = { ropes: grow(ring, 0.3), corner: { x0: -0.4, y0: side - apron - 2.4, x1: apron + 3, y1: side + 0.6 } };
65
+ const red = corners[3];
66
+ const blue = corners[1];
67
+ const marks = [
68
+ { id: "ring-canvas", x: mid - o.size * 0.2, y: mid + o.size * 0.15, side: "bottom" },
69
+ { id: "ring-ropes", x: ring.x1, y: mid - o.size * 0.2, side: "bottom" },
70
+ { id: "ring-post", x: corners[0].post[0], y: corners[0].post[1], side: "top" },
71
+ { id: "ring-red-corner", x: red.post[0], y: red.post[1], side: "bottom" },
72
+ { id: "ring-blue-corner", x: blue.post[0], y: blue.post[1], side: "top" },
73
+ { id: "ring-apron", x: mid, y: apron / 2, side: "top" },
74
+ ];
75
+ if (steps.length > 0) {
76
+ const last = steps[steps.length - 1];
77
+ marks.push({ id: "ring-steps", x: (last.box.x0 + last.box.x1) / 2, y: side + (last.box.y1 - last.box.y0) / 2, side: "bottom" });
78
+ }
79
+ return { parts, platform, extent, centre: [mid, mid], ring, size: o.size, apron, corners, steps, height: o.height, marks, ranges };
80
+ }
@@ -1,7 +1,7 @@
1
1
  /** The sports of the budō family. */
2
2
  export type BudoKind = "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo" | CombatKind;
3
3
  /** The combat sports the engine of the budō also draws: the MMA cage. */
4
- export type CombatKind = "mma";
4
+ export type CombatKind = "mma" | "boxing";
5
5
  /** The sides of a hall, as a plan has them: north is the top of the drawing. `top`, `bottom`, `left` and `right` say the same. */
6
6
  export type Compass = "north" | "south" | "east" | "west";
7
7
  export type Side4 = Compass | "top" | "bottom" | "left" | "right";
@@ -21,7 +21,7 @@ export interface BudoStyle {
21
21
  hall?: "budokan";
22
22
  /** Judo: `shiai` (the contest area, the default) or `kata` (the IJF's kata area, `league: "ijf-kata"`: a centre tape, two white tapes 3 m from it either side, a safety area of 2 m; with `officials`, the five judges' seats on the shomen side and the coach's chair opposite). */
23
23
  style?: "shiai" | "kata";
24
- /** Judo: the contest area's side, 8 m to 10 m. Kendo: the shiai-jo's side, 9 m to 11 m. MMA cage: the width across the flats, 6.096 m to 9.754 m (20 to 32 feet), 9.144 m (30 feet) by default; 7.62 m is the smaller cage of 25 feet. In metres. */
24
+ /** Judo: the contest area's side, 8 m to 10 m. Kendo: the shiai-jo's side, 9 m to 11 m. MMA cage: the width across the flats, 6.096 m to 9.754 m (20 to 32 feet), 9.144 m (30 feet) by default; 7.62 m is the smaller cage of 25 feet. Boxing ring: the side inside the ropes, within what the league allows (20 feet for the Nevada rule, 16 to 20 feet for Wisconsin, 4.5 to 6.1 m for the WBC Amateur rules). In metres. */
25
25
  size?: number;
26
26
  /** Judo: the width of the safety area, 3 m or more (4 m by default). In metres. */
27
27
  safety?: number;
@@ -39,7 +39,7 @@ export interface BudoStyle {
39
39
  texture?: "weave" | "none";
40
40
  /** Karate: the referee's and the judges' places. On by default. */
41
41
  judges?: boolean;
42
- /** Sumo: the height of the clay square in metres, 0.34 to 0.60. 0.55 by default. MMA cage: the platform's height over the floor of the building, 0 to 1.219 m (4 feet), 1.219 m by default. Seen in 3D. */
42
+ /** Sumo: the height of the clay square in metres, 0.34 to 0.60. 0.55 by default. MMA cage: the platform's height over the floor of the building, 0 to 1.219 m (4 feet), 1.219 m by default. Boxing ring: the same, 0 to what the league allows (5 feet in Nevada, 4 feet in Wisconsin, 1.2 m for the amateurs). Seen in 3D. */
43
43
  height?: number;
44
44
  /** Sumo: the four tassels of the roof, in the colours of the four quarters (green east, red south, white west, black north). Off by default. */
45
45
  tassels?: boolean;
@@ -49,8 +49,10 @@ export interface BudoStyle {
49
49
  fence?: number;
50
50
  /** MMA cage: the two gates, opposite each other (the fence is whole without them). On by default. */
51
51
  gates?: boolean;
52
- /** MMA cage: the steps up to the platform at each gate, three treads. On by default. */
52
+ /** MMA cage: the steps up to the platform at each gate, three treads. Boxing ring: the three sets of steps, one in each boxer's corner and one in a neutral corner. On by default. */
53
53
  steps?: boolean;
54
+ /** Boxing ring: the posts and their pads in the corner colours (the red and the blue corner of the boxers, two neutral ones in white). On by default. */
55
+ corners?: boolean;
54
56
  /** Kyūdō: the number of shooters in a line, 1 to 12 (5 by default). */
55
57
  shooters?: number;
56
58
  /** Kyūdō: the kind of range, `kinteki` (28 m, the default) or `enteki` (60 m); `league` says it too. */
@@ -41,9 +41,16 @@ const CLASSES = `
41
41
  const COMBAT_CLASSES = `
42
42
  .kyogijo .ky-bud-step{fill:color-mix(in srgb,var(--ky-bud-floor,#43484e) 76%,#fff);stroke:var(--ky-ink);stroke-opacity:.35;stroke-width:.03}
43
43
  .kyogijo .ky-bud-pad{fill:var(--ky-ink);fill-opacity:.82}`.trim();
44
- const COMBAT = ["mma"];
44
+ /** The rules of a ring's own classes: the ropes and the corner pads in the boxers' colours. */
45
+ const RING_CLASSES = `
46
+ .kyogijo .ky-bud-rope{stroke:var(--ky-bud-rope,#f4f1e8);fill:none;stroke-linecap:round}
47
+ .kyogijo .ky-bud-corner-red{fill:var(--ky-red)}
48
+ .kyogijo .ky-bud-corner-blue{fill:var(--ky-blue,#2a5db0)}
49
+ .kyogijo .ky-bud-corner-white{fill:#f4f1e8;stroke:var(--ky-ink);stroke-opacity:.45;stroke-width:.03}`.trim();
50
+ const COMBAT = ["mma", "boxing"];
51
+ const RING = ["boxing"];
45
52
  /** The custom properties of a look. */
46
- const vars = (l) => `--ky-surface:${l.surface};--ky-surface-alt:${l.alt};--ky-red:${l.red};--ky-apron:${l.apron};--ky-free:${l.free};--ky-bud-floor:${l.floor};--ky-boards:${l.boards}${l.dirt === undefined ? "" : `;--ky-dirt:${l.dirt}`}${l.sand === undefined ? "" : `;--ky-sand:${l.sand}`}${l.straw === undefined ? "" : `;--ky-bud-straw:${l.straw}`}`;
53
+ const vars = (l) => `--ky-surface:${l.surface};--ky-surface-alt:${l.alt};--ky-red:${l.red};--ky-apron:${l.apron};--ky-free:${l.free};--ky-bud-floor:${l.floor};--ky-boards:${l.boards}${l.dirt === undefined ? "" : `;--ky-dirt:${l.dirt}`}${l.sand === undefined ? "" : `;--ky-sand:${l.sand}`}${l.straw === undefined ? "" : `;--ky-bud-straw:${l.straw}`}${l.rope === undefined ? "" : `;--ky-bud-rope:${l.rope}`}`;
47
54
  /** The three rules of one look: light, dark by the device's setting, and dark by the page's `data-theme`. */
48
55
  function lookRules(sport, name) {
49
56
  const pair = BUDO_LOOKS[sport][name];
@@ -69,7 +76,7 @@ export function budoStyle(sport, look) {
69
76
  const kind = sport;
70
77
  if (BUDO_LOOKS[kind] === undefined)
71
78
  return "";
72
- return `${CLASSES}${COMBAT.includes(sport) ? `\n${COMBAT_CLASSES}` : ""}\n${look === undefined ? lookRules(kind, Object.keys(BUDO_LOOKS[kind])[0]) : lookRules(kind, look)}`;
79
+ return `${CLASSES}${COMBAT.includes(sport) ? `\n${COMBAT_CLASSES}` : ""}${RING.includes(sport) ? `\n${RING_CLASSES}` : ""}\n${look === undefined ? lookRules(kind, Object.keys(BUDO_LOOKS[kind])[0]) : lookRules(kind, look)}`;
73
80
  }
74
81
  /** Every budō sport's rules and every look, for a page that draws with `style: "none"` and supplies the stylesheet itself. */
75
- export const KYOGIJO_BUDO_STYLE = `${CLASSES}\n${COMBAT_CLASSES}${KYOGIJO_BUDO_GALLERY_STYLE}\n${Object.keys(BUDO_LOOKS).flatMap((sport) => Object.keys(BUDO_LOOKS[sport]).map((name) => lookRules(sport, name))).join("\n")}`;
82
+ export const KYOGIJO_BUDO_STYLE = `${CLASSES}\n${COMBAT_CLASSES}\n${RING_CLASSES}${KYOGIJO_BUDO_GALLERY_STYLE}\n${Object.keys(BUDO_LOOKS).flatMap((sport) => Object.keys(BUDO_LOOKS[sport]).map((name) => lookRules(sport, name))).join("\n")}`;
package/dist/crowd.js CHANGED
@@ -102,6 +102,8 @@ const PROFILES = {
102
102
  dojo: { shape: "bowl", side: 5, end: 5, bench: 0, corner: 8, tiers: [lower(26, 2, 14), upper(32, 58, 20, 36)], away: "corner" },
103
103
  // An MMA cage stands in the middle of an arena, with the seats rising from the ring of the floor.
104
104
  mma: { shape: "bowl", side: 40, end: 40, bench: 0, corner: 56, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner" },
105
+ // A boxing ring stands in the middle of an arena in the same way.
106
+ boxing: { shape: "bowl", side: 40, end: 40, bench: 0, corner: 56, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner" },
105
107
  };
106
108
  /** The grounds of the package that are the home of an NFL or a CFL club, where a soccer pitch stands inside a gridiron's stands. From each ground's article. */
107
109
  export const GRIDIRON_GROUNDS = Object.freeze({
package/dist/draw.js CHANGED
@@ -91,7 +91,7 @@ import { abbreviate, capitals } from "./text.js";
91
91
  import { VERSION } from "./version.js";
92
92
  export { isPortrait };
93
93
  /** The sports that can be drawn, in the order a menu lists them. */
94
- export const SPORTS = ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball", "swimming", "waterpolo", "diving", "artisticswimming", "athletics", "skating", "judo", "karate", "kendo", "sumo", "kyudo", "dojo", "mma", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball"];
94
+ export const SPORTS = ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball", "swimming", "waterpolo", "diving", "artisticswimming", "athletics", "skating", "judo", "karate", "kendo", "sumo", "kyudo", "dojo", "mma", "boxing", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball"];
95
95
  /** The CSS custom property each colour of `Colors` sets. */
96
96
  export const COLOR_PROPERTIES = {
97
97
  apron: "--ky-apron",
@@ -265,7 +265,7 @@ function buildSkating(o, common) {
265
265
  /** The budō surface: its plan, drawn as it lies. A hall, a kyūdōjō and a range of mats have little ground round them. */
266
266
  function buildBudo(o, common) {
267
267
  const plan = budoPlan({ ...o, detail: common.detail });
268
- return budoSurface(common, plan, common.margin ?? (o.range === undefined ? (plan.mma === undefined ? 12 : 5) : 4));
268
+ return budoSurface(common, plan, common.margin ?? (o.range === undefined ? (plan.mma === undefined && plan.boxing === undefined ? 12 : 5) : 4));
269
269
  }
270
270
  /** One text for both ends or sides, or a pair of them. */
271
271
  const pair = (text) => (text === undefined ? undefined : typeof text === "string" ? [text, text] : text);
@@ -374,6 +374,7 @@ function buildWhole(o, portrait, id, score, scored, extra) {
374
374
  case "kyudo":
375
375
  case "dojo":
376
376
  case "mma":
377
+ case "boxing":
377
378
  return buildBudo({ ...o, sport }, common);
378
379
  default:
379
380
  throw new Error(`Unknown sport "${String(o.sport)}": use one of ${SPORTS.join(", ")}`);
@@ -609,7 +610,7 @@ function leagueAbout(league, year, language, sheet) {
609
610
  const words = KYOGIJO_STRINGS[language].league;
610
611
  if (league.family === "skating")
611
612
  return skatingAbout({ sport: "skating", league: league.id }, language);
612
- if (league.family === "judo" || league.family === "karate" || league.family === "kendo" || league.family === "sumo" || league.family === "kyudo" || league.family === "dojo" || league.family === "mma")
613
+ if (league.family === "judo" || league.family === "karate" || league.family === "kendo" || league.family === "sumo" || league.family === "kyudo" || league.family === "dojo" || league.family === "mma" || league.family === "boxing")
613
614
  return budoAbout({ sport: league.family, league: league.id }, language);
614
615
  if (league.family === "athletics")
615
616
  return athleticsAbout({ sport: "athletics", league: league.id, athletics: sheet === undefined ? undefined : undefined }, language);
package/dist/features.js CHANGED
@@ -137,6 +137,13 @@ const MMA = [
137
137
  f("gates", "--ky-ink", "The swing of the two gates."),
138
138
  f("posts", "--ky-ink", "The eight padded posts."),
139
139
  ];
140
+ const BOXING = [
141
+ f("apron", "--ky-apron", "The floor of the hall round the platform."),
142
+ f("canvas", "--ky-surface", "The canvas of the platform, ring and apron."),
143
+ f("ropes", "--ky-bud-rope", "The ropes, and their extensions to the posts."),
144
+ f("steps", "--ky-bud-floor", "The steps up to the platform."),
145
+ f("corners", "--ky-ink", "The four corner posts with their pads."),
146
+ ];
140
147
  const KYUDO = [
141
148
  f("apron", "--ky-apron", "The ground round the range."),
142
149
  f("shajo", "--ky-bud-floor", "The floor of the shajō."),
@@ -445,6 +452,7 @@ export const FEATURE_INFO = Object.freeze({
445
452
  kyudo: KYUDO,
446
453
  dojo: DOJO,
447
454
  mma: MMA,
455
+ boxing: BOXING,
448
456
  });
449
457
  /**
450
458
  * The names of the features of each sport: the keys `featureColors` takes. Football's three fields have the same, and so have the five net sports.
@@ -25,7 +25,7 @@ export type SkatingMark = "ky-ska-b" | "ky-ska-r" | "ky-ska-k" | "ky-ska-blocks"
25
25
  /** The coloured marks of a budō surface: the white line, the blue, white and red tape, the seam between mats. */
26
26
  /** The fills of a budō surface's tape: blue, white, red and ink. */
27
27
  export type BudoFill = "ky-bud-blue-fill" | "ky-bud-white-fill" | "ky-bud-red-fill" | "ky-bud-ink-fill";
28
- export type BudoMark = "ky-bud-line" | "ky-bud-blue" | "ky-bud-white" | "ky-bud-red" | "ky-bud-seam" | "ky-bud-ink";
28
+ export type BudoMark = "ky-bud-line" | "ky-bud-blue" | "ky-bud-white" | "ky-bud-red" | "ky-bud-seam" | "ky-bud-ink" | "ky-bud-rope";
29
29
  /** A line from one point to another. */
30
30
  export interface LineShape extends Stroked {
31
31
  kind: "line";
@@ -95,7 +95,7 @@ export type Shape = LineShape | ArcShape | RingShape | BoxShape | PolyShape | Do
95
95
  /** The areas of a skating rink: the ice, the floor of the infield, the warm-up lane, the padding and the furniture. */
96
96
  export type SkatingArea = "ky-ska-ice" | "ky-ska-floor" | "ky-ska-warm" | "ky-ska-pad" | "ky-ska-desk" | "ky-ska-kiss";
97
97
  /** The areas of a budō surface: the mats in three tones, the heri of a mat, the floor, the walls, the platform and the furniture, the clay and the sand and the straw of a dohyō, the azuchi and the yamichi. */
98
- export type BudoArea = "ky-bud-t1" | "ky-bud-t2" | "ky-bud-t3" | "ky-bud-heri" | "ky-bud-floor" | "ky-bud-wall" | "ky-bud-window" | "ky-bud-door" | "ky-bud-platform" | "ky-bud-free" | "ky-bud-desk" | "ky-bud-clay" | "ky-bud-sand" | "ky-bud-tawara" | "ky-bud-azuchi" | "ky-bud-roof" | "ky-bud-yamichi" | "ky-bud-shrine" | "ky-bud-dais" | "ky-bud-banner" | "ky-bud-lane" | "ky-bud-mato" | "ky-bud-tassel-e" | "ky-bud-tassel-s" | "ky-bud-tassel-w" | "ky-bud-tassel-n" | "ky-bud-seat" | "ky-bud-step" | "ky-bud-pad";
98
+ export type BudoArea = "ky-bud-t1" | "ky-bud-t2" | "ky-bud-t3" | "ky-bud-heri" | "ky-bud-floor" | "ky-bud-wall" | "ky-bud-window" | "ky-bud-door" | "ky-bud-platform" | "ky-bud-free" | "ky-bud-desk" | "ky-bud-clay" | "ky-bud-sand" | "ky-bud-tawara" | "ky-bud-azuchi" | "ky-bud-roof" | "ky-bud-yamichi" | "ky-bud-shrine" | "ky-bud-dais" | "ky-bud-banner" | "ky-bud-lane" | "ky-bud-mato" | "ky-bud-tassel-e" | "ky-bud-tassel-s" | "ky-bud-tassel-w" | "ky-bud-tassel-n" | "ky-bud-seat" | "ky-bud-step" | "ky-bud-pad" | "ky-bud-corner-red" | "ky-bud-corner-blue" | "ky-bud-corner-white";
99
99
  export type AthleticsArea = "ky-ath-track" | "ky-ath-grass" | "ky-ath-sand" | "ky-ath-mat" | "ky-ath-water" | "ky-ath-concrete" | "ky-ath-pad" | "ky-ath-board" | "ky-ath-bank";
100
100
  /** A painted area, under the lines: a rectangle or an ellipse filled with a class of the stylesheet (`ky-area`, the tint of a goal area, or `ky-zone`). */
101
101
  export interface Area extends Named {
@@ -37,7 +37,7 @@ export const ZONE_SETS = Object.freeze({
37
37
  "pool-lanes": { sports: ["swimming"], title: "Lanes: each lane of the pool, the whole length of it", source: "docs/sources.md#zone-sets" },
38
38
  "water-polo-areas": { sports: ["waterpolo"], title: "The goal area, the ground from 2 to 6 m, and the rest of the field, at each end", source: "docs/sources.md#zone-sets" },
39
39
  "pool-thirds": { sports: ["diving", "artisticswimming"], title: "Thirds along the pool, from the wall the boards or the judges stand at", source: "docs/sources.md#zone-sets" },
40
- "budo-areas": { sports: ["judo", "karate", "kendo", "sumo", "kyudo", "dojo", "mma"], title: "The surface in its areas: the contest and the safety areas, the boundary band, the clay and the ring of a dohyō, the shajō, the yamichi and the azuchi, a dojo's floor", source: "docs/sources.md#zone-sets" },
40
+ "budo-areas": { sports: ["judo", "karate", "kendo", "sumo", "kyudo", "dojo", "mma", "boxing"], title: "The surface in its areas: the contest and the safety areas, the boundary band, the clay and the ring of a dohyō, the shajō, the yamichi and the azuchi, a dojo's floor", source: "docs/sources.md#zone-sets" },
41
41
  "field-thirds": { sports: ["lacrosse", "rugby", "afl", "gaelic", "fieldhockey", "handball", "netball"], title: "Thirds: defensive, middle and attacking", source: "docs/sources.md#zone-sets" },
42
42
  "skating-track": { sports: ["skating"], title: "The rink in parts: the finish straight, the first curve, the crossing straight, the second curve and the infield (a figure skating rink in thirds)", source: "docs/sources.md#zone-sets" },
43
43
  "athletics-track": { sports: ["athletics"], title: "The track in parts: the finish straight, the first bend, the back straight, the second bend, and the infield", source: "docs/sources.md#zone-sets" },
package/dist/homeside.js CHANGED
@@ -51,6 +51,8 @@ export const HOME_FIRST = Object.freeze({
51
51
  dojo: false,
52
52
  // A cage fight is between a red corner and a blue corner: no home side.
53
53
  mma: false,
54
+ // A bout is between a red corner and a blue corner: no home side.
55
+ boxing: false,
54
56
  });
55
57
  /** The leagues whose convention is not their sport's: the international federation's lacrosse lists the home team first. */
56
58
  export const HOME_FIRST_LEAGUES = Object.freeze({