@johnmorrisdotca/kyogijo-stadiums 0.19.0 → 0.20.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,14 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.20.0] - 2026-10-20
11
+
12
+ ### Added
13
+
14
+ - **The MMA cage** (`sport: "mma"`, [the combat sports page](./docs/combat.md)): an eight-sided fenced area on its platform, to scale, from the Nevada Rules of Unarmed Combat (5.010 3) and the IMMAF's rules for amateur competition: 20 to 32 feet across the flats (`budo.size`, 30 feet drawn and 25 feet the smaller cage), a platform of at most 4 feet (`budo.height`) with steps up at each gate, a fence of 5 to 7 feet (`budo.fence`, 6 feet drawn), eight padded posts, two gates opposite each other and a canvas over foam, with a grey, a black and a blue look in light and dark. The leagues are `nruc` and `immaf`; `RANGES.mma` has `cage`, `gate` and `half`; the parts are named for `landmarks` and the features for `featureColors`, in English and Japanese. The cage is named for what it is, an eight-sided cage: no promotion's name or mark is used. It is a sport of the package (40 now), with `SPORTS`, a unit of feet, no home side, a badge glyph, a crowd profile and its sources in `LEAGUE_SOURCES` (the figure of the rule, the figure that is typical and the figure that is ours are told apart).
15
+ - **The cage in 3D** (`buildTurn({ sport: "mma" })`): the platform is a solid block (four sides to the floor of the hall and a top that is the drawing's own canvas, lifted), the fence is wire mesh on each of its eight flats with a pad along the top and the foot so that the canvas stays in sight, and gives way to an eye that has it in front of the cage, the posts are solid, the gates are frames of padded rails, 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 cage in its sport picker, with the looks, the cage's width (30 or 25 feet, `cagesize` in the address), the score of a bout and the 3D view; the docs have a page for it and a gallery of its own.
17
+
10
18
  ## [0.19.0] - 2026-10-20
11
19
 
12
20
  ### Added
package/NOTICE.md CHANGED
@@ -35,6 +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
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 |
39
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/) |
40
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 NFL, CFL and IFAF football fields, the NHL rink, the NBA court, the soccer pitch and futsal court, the baseball field of every MLB ballpark, current and past, and every NPB home park, the rink and court of 167 NHL, NBA, WNBA and PWHL arenas, and the surfaces of curling, athletics, speed, short track and figure skating, swimming, water polo, diving, artistic swimming, tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey, handball, judo, karate, kendo, sumo, kyūdō and a dojo of tatami, thirty-nine sports in all, 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 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>
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>
@@ -72,7 +72,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
72
72
 
73
73
  ## Features
74
74
 
75
- - **Thirty-nine 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 and the budō. Every figure is cited in [sources](./docs/sources.md). [The surfaces](./docs/surfaces.md).
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).
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 thirty-nine sports, the 94 ballparks, the court looks. |
122
+ | `SPORTS`, `PARKS`, `PARK_IDS`, `COURT_THEMES` | The forty 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 thirty-nine, 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, 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
 
@@ -575,12 +575,12 @@ The broadcast view puts the mat large, with the officials on a board at its side
575
575
 
576
576
  ### A table tennis area
577
577
 
578
- A table is drawn in the ITTF's playing area, 14 m by 7 m (`net.area` for Masters and wheelchair events), with dark surrounds and the umpires' chairs ([courts](./docs/courts.md#the-table-tennis-playing-area)).
578
+ A table in the ITTF's playing area, 14 m by 7 m, with surrounds and chairs ([courts](./docs/courts.md#the-table-tennis-playing-area)).
579
579
 
580
580
  <p align="center">
581
581
  <picture>
582
582
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-desk-dark.webp">
583
- <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-desk-light.webp" alt="A table tennis table in its 14 m by 7 m playing area, with dark surrounds, chairs and table for the umpires" width="660">
583
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-desk-light.webp" alt="A table tennis table in its playing area, with surrounds and the umpires' chairs" width="660">
584
584
  </picture>
585
585
  <br><em>The table in its playing area.</em>
586
586
  </p>
@@ -588,7 +588,7 @@ A table is drawn in the ITTF's playing area, 14 m by 7 m (`net.area` for Masters
588
588
  <p align="center">
589
589
  <picture>
590
590
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-3d-desk-dark.webp">
591
- <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-3d-desk-light.webp" alt="The same area in 3D: the surrounds round the floor, the table on its legs" width="660">
591
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-3d-desk-light.webp" alt="The same table area in 3D, from a low corner" width="660">
592
592
  </picture>
593
593
  <br><em>The same area in 3D.</em>
594
594
  </p>
@@ -600,7 +600,7 @@ A table is drawn in the ITTF's playing area, 14 m by 7 m (`net.area` for Masters
600
600
  <p align="center">
601
601
  <picture>
602
602
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-desk-dark.webp">
603
- <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-desk-light.webp" alt="The IJF's kata area with the five judges' seats above it and the coach's chair below" width="660">
603
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-desk-light.webp" alt="The IJF's kata area with the judges' seats above and the coach's chair below" width="660">
604
604
  </picture>
605
605
  <br><em>The kata area, its judges and the coach's chair.</em>
606
606
  </p>
@@ -608,11 +608,31 @@ A table is drawn in the ITTF's playing area, 14 m by 7 m (`net.area` for Masters
608
608
  <p align="center">
609
609
  <picture>
610
610
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-tapes-desk-dark.webp">
611
- <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-tapes-desk-light.webp" alt="Three short white tapes 3 m apart across the middle of a kata area" width="660">
611
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-tapes-desk-light.webp" alt="Three short white tapes across the middle of a kata area" width="660">
612
612
  </picture>
613
613
  <br><em>Close up: the centre tape and the two starting tapes.</em>
614
614
  </p>
615
615
 
616
+ ### An MMA cage
617
+
618
+ [The combat sports page](./docs/combat.md).
619
+
620
+ <p align="center">
621
+ <picture>
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">
624
+ </picture>
625
+ <br><em>With a score.</em>
626
+ </p>
627
+
628
+ <p align="center">
629
+ <picture>
630
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/mma-cage-3d-desk-dark.webp">
631
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/mma-cage-3d-desk-light.webp" alt="The cage in 3D: a mesh fence on a solid platform" width="660">
632
+ </picture>
633
+ <br><em>The cage in 3D.</em>
634
+ </p>
635
+
616
636
  ### The element in a page
617
637
 
618
638
  ```html
@@ -686,7 +706,7 @@ Nothing is branded. The drawing is coloured by custom properties on its `svg`, w
686
706
 
687
707
  ## Limits
688
708
 
689
- Each is held by a test: thirty-nine 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).
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).
690
710
 
691
711
  ## Accessibility
692
712
 
@@ -0,0 +1,46 @@
1
+ import type { MmaFigures } from "../leagues/combat.ts";
2
+ import { type MBox, type MMark, type Parts } from "./parts.ts";
3
+ /** One gate of the cage: the flat it is in, the two ends of its opening, and the way it opens (outward). */
4
+ export interface CageGate {
5
+ side: "north" | "south";
6
+ /** The number of the flat it is in, counted from the first corner. */
7
+ flat: number;
8
+ from: readonly [number, number];
9
+ to: readonly [number, number];
10
+ }
11
+ /** What a cage plan is. */
12
+ export interface CageLayout {
13
+ parts: Parts;
14
+ /** The platform, which is the surface's own box: the cage's canvas and the apron round it. */
15
+ platform: MBox;
16
+ extent: MBox;
17
+ centre: [number, number];
18
+ /** The cage's width across the flats, and the distance from its middle to a post. */
19
+ width: number;
20
+ apothem: number;
21
+ corner: number;
22
+ /** The posts, one at each corner of the cage, as points on the platform. */
23
+ posts: [number, number][];
24
+ /** The gates, and the steps (the boxes outside the platform's edge, where a gate faces). */
25
+ gates: CageGate[];
26
+ steps: MBox[];
27
+ /** The height of the platform over the floor of the building and of the fence over the canvas, in metres. */
28
+ height: number;
29
+ fence: number;
30
+ marks: MMark[];
31
+ ranges: Record<string, MBox>;
32
+ }
33
+ /** The width the options ask for, held to 20 feet to 32 feet. */
34
+ export declare const cageWidth: (asked: number | undefined, fig: MmaFigures) => number;
35
+ /** The platform's height the options ask for, held to 0 to 4 feet. */
36
+ export declare const cageHeight: (asked: number | undefined, fig: MmaFigures) => number;
37
+ /** The fence's height the options ask for, held to 5 feet to 7 feet. */
38
+ export declare const fenceHeight: (asked: number | undefined, fig: MmaFigures) => number;
39
+ /** The plan of the cage the options ask for. */
40
+ export declare function cageParts(fig: MmaFigures, o: {
41
+ width: number;
42
+ height: number;
43
+ fence: number;
44
+ gates: boolean;
45
+ steps: boolean;
46
+ }): CageLayout;
@@ -0,0 +1,91 @@
1
+ // An MMA cage (a fenced area): the platform it stands on with its steps, the canvas inside the fence, the eight-sided fence with its two gates and its padded posts, from the rules a commission or the IMMAF
2
+ // writes for a fenced area (the Nevada Rules of Unarmed Combat 5.010 3, the IMMAF's Unified Rules for amateur competition). The cage is named for what it is, an eight-sided cage: no promotion's own name is used.
3
+ // In metres, the origin at the top left of the platform, north at the top. Nothing here draws SVG.
4
+ import { arc, line } from "../fields.kit.js";
5
+ import { discArea, rectArea, within } from "./kit.js";
6
+ import { emptyParts, grow, union } from "./parts.js";
7
+ import { num } from "../format.js";
8
+ /** The width the options ask for, held to 20 feet to 32 feet. */
9
+ export const cageWidth = (asked, fig) => (asked === undefined ? fig.width.drawn : within(asked, fig.width.min, fig.width.max, "The cage's width", "NRUC 5.010 3(a) and the IMMAF's rules"));
10
+ /** The platform's height the options ask for, held to 0 to 4 feet. */
11
+ export const cageHeight = (asked, fig) => (asked === undefined ? fig.platform.height.drawn : within(asked, fig.platform.height.min, fig.platform.height.max, "The cage's platform", "NRUC 5.010 3(c)"));
12
+ /** The fence's height the options ask for, held to 5 feet to 7 feet. */
13
+ export const fenceHeight = (asked, fig) => (asked === undefined ? fig.fence.drawn : within(asked, fig.fence.min, fig.fence.max, "The cage's fence", "NRUC 5.010 3(d)"));
14
+ const rd = (v) => Math.round(v * 1000) / 1000;
15
+ /** The plan of the cage the options ask for. */
16
+ export function cageParts(fig, o) {
17
+ const a = o.width / 2;
18
+ const corner = a / Math.cos(Math.PI / fig.sides);
19
+ const side = o.width + 2 * fig.apron;
20
+ const c = side / 2;
21
+ const platform = { x0: 0, y0: 0, x1: side, y1: side };
22
+ const parts = emptyParts();
23
+ // The corners of the cage: the flats face the four sides of the platform and the four diagonals, and the first corner lies a sixteenth of a turn clockwise of north.
24
+ const step = (2 * Math.PI) / fig.sides;
25
+ const start = -Math.PI / 2 + step / 2;
26
+ const posts = Array.from({ length: fig.sides }, (_, i) => [rd(c + corner * Math.cos(start + i * step)), rd(c + corner * Math.sin(start + i * step))]);
27
+ parts.areas.push(rectArea("platform", "platform", "ky-bud-platform", platform));
28
+ parts.areas.push({ id: "canvas", feature: "canvas", cls: "ky-bud-t1", d: `M${posts.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}Z` });
29
+ // The steps, one at each gate: three treads, outside the platform's edge.
30
+ const steps = [];
31
+ const s = fig.platform.steps;
32
+ const gateSides = ["north", "south"];
33
+ if (o.steps && o.gates) {
34
+ for (const side_ of gateSides) {
35
+ const depth = s.treads * s.tread;
36
+ const b = side_ === "north" ? { x0: c - s.width / 2, y0: -depth, x1: c + s.width / 2, y1: 0 } : { x0: c - s.width / 2, y0: side, x1: c + s.width / 2, y1: side + depth };
37
+ steps.push(b);
38
+ for (let t = 0; t < s.treads; t += 1) {
39
+ const y0 = side_ === "north" ? -depth + t * s.tread : side + t * s.tread;
40
+ parts.areas.push(rectArea(`step-${side_}-${t + 1}`, "steps", "ky-bud-step", { x0: b.x0, y0, x1: b.x1, y1: y0 + s.tread }));
41
+ }
42
+ }
43
+ }
44
+ // The fence is one line along each flat; where a gate is, the line stops at its opening and the gate's swing is drawn dashed, outward.
45
+ const gates = [];
46
+ const g = fig.gates.width;
47
+ const gateAt = (i) => (o.gates ? (i === 7 ? "north" : i === 3 ? "south" : undefined) : undefined);
48
+ // Flat i runs from corner i to corner i+1; the flat that faces north is the last (corner 7 to corner 0), the one that faces south is 3.
49
+ for (let i = 0; i < fig.sides; i += 1) {
50
+ const p = posts[i];
51
+ const q = posts[(i + 1) % fig.sides];
52
+ const gate = gateAt(i);
53
+ if (gate === undefined) {
54
+ parts.shapes.push(line(`fence-${i + 1}`, "fence", p[0], p[1], q[0], q[1], { w: 0.1, cls: "ky-bud-ink", least: 0.1 }));
55
+ continue;
56
+ }
57
+ // Along the flat, from p to q, the opening lies in the middle.
58
+ const len = Math.hypot(q[0] - p[0], q[1] - p[1]);
59
+ const ux = (q[0] - p[0]) / len;
60
+ const uy = (q[1] - p[1]) / len;
61
+ const m = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
62
+ const from = [rd(m[0] - (ux * g) / 2), rd(m[1] - (uy * g) / 2)];
63
+ const to = [rd(m[0] + (ux * g) / 2), rd(m[1] + (uy * g) / 2)];
64
+ gates.push({ side: gate, flat: i, from, to });
65
+ parts.shapes.push(line(`fence-${i + 1}a`, "fence", p[0], p[1], from[0], from[1], { w: 0.1, cls: "ky-bud-ink", least: 0.1 }), line(`fence-${i + 1}b`, "fence", to[0], to[1], q[0], q[1], { w: 0.1, cls: "ky-bud-ink", least: 0.1 }));
66
+ // The gate opens outward, about the end of the opening that lies on the right of the north gate and on the left of the south one (the cage is the same turned half a turn).
67
+ const hinge = to;
68
+ parts.shapes.push(arc(`gate-${gate}`, "gates", hinge[0], hinge[1], g, gate === "north" ? 180 : 0, gate === "north" ? 270 : 90, { w: 0.04, dash: [0.14, 0.1], cls: "ky-bud-ink", least: 0.04 }));
69
+ }
70
+ gates.sort((x, y) => (x.side === "north" ? -1 : 1) - (y.side === "north" ? -1 : 1));
71
+ // The posts, padded, drawn over the fence.
72
+ posts.forEach(([x, y], i) => parts.areas.push(discArea(`post-${i + 1}`, "posts", "ky-bud-pad", x, y, fig.post.pad / 2)));
73
+ const extent = grow(union([platform, ...steps]), 0.6);
74
+ const north = gates.find((x) => x.side === "north");
75
+ const ranges = {
76
+ cage: { x0: c - a - 0.3, y0: c - a - 0.3, x1: c + a + 0.3, y1: c + a + 0.3 },
77
+ };
78
+ if (north !== undefined)
79
+ ranges.gate = { x0: c - 2.4, y0: -0.5 - s.treads * s.tread, x1: c + 2.4, y1: c - a + 2 };
80
+ const marks = [
81
+ { id: "cage-canvas", x: c - a * 0.35, y: c + a * 0.25, side: "bottom" },
82
+ { id: "cage-fence", x: c + a, y: c + a * 0.35, side: "bottom" },
83
+ { id: "cage-post", x: posts[1][0], y: posts[1][1], side: "top" },
84
+ { id: "cage-platform", x: side * 0.04, y: side * 0.1, side: "top" },
85
+ ];
86
+ if (north !== undefined)
87
+ marks.push({ id: "cage-gate", x: (north.from[0] + north.to[0]) / 2, y: north.from[1] + 0.2, side: "top" });
88
+ if (steps.length > 0)
89
+ marks.push({ id: "cage-steps", x: c, y: side + s.treads * s.tread * 0.5, side: "bottom" });
90
+ return { parts, platform, extent, centre: [c, c], width: o.width, apothem: a, corner, posts, gates, steps, height: o.height, fence: o.fence, marks, ranges };
91
+ }
@@ -35,6 +35,11 @@ export function budoAbout(o, language, plan = budoPlan(o)) {
35
35
  const bare = !L.parts.areas.some((a) => a.id === "tawara");
36
36
  return fill(bare ? words.sumoBare : words.sumo, { platform: trim(fig.platform), height: cm(L.height), circle: trim(fig.circle), count: fig.tawara.count });
37
37
  }
38
+ if (plan.mma !== undefined) {
39
+ const L = plan.mma;
40
+ const platform = L.height > 0.0005 ? fill(words.mmaPlatform, { height: cm(L.height) }) : words.mmaFloor;
41
+ return fill(words.mma, { width: trim(L.width), feet: trim(L.width / 0.3048), fence: cm(L.fence), platform });
42
+ }
38
43
  if (plan.kyudo !== undefined) {
39
44
  const L = plan.kyudo;
40
45
  const fig = plan.figures;
@@ -67,6 +72,12 @@ export function budoSentences(o, language, plan = budoPlan(o)) {
67
72
  out.push(words.karateOfficials);
68
73
  if (plan.sumo !== undefined && plan.areas.some((a) => a.feature === "tassels"))
69
74
  out.push(words.tassels);
75
+ if (plan.mma !== undefined) {
76
+ if (plan.mma.gates.length > 0)
77
+ out.push(words.mmaGates);
78
+ if (plan.mma.steps.length > 0)
79
+ out.push(words.mmaSteps);
80
+ }
70
81
  if (plan.dojo !== undefined) {
71
82
  const L = plan.dojo;
72
83
  out.push(fill(words.dojoKamiza, { side: words.sides[L.kamiza] }));
@@ -14,6 +14,8 @@ function pair(light, k = 0.34) {
14
14
  }
15
15
  return { light, dark };
16
16
  }
17
+ /** A look whose dark platform is a lighter one than the plain dimming makes, so that it stands out from the dark floor of the hall under it. */
18
+ const lifted = (p, floor) => ({ light: p.light, dark: { ...p.dark, floor } });
17
19
  const FLOOR = "#c9a26a";
18
20
  const WALLS = "#d8d0bd";
19
21
  const COMMON = { apron: "#d6d3cb", free: "#e8e4d8", floor: FLOOR, boards: WALLS };
@@ -44,6 +46,12 @@ export const BUDO_LOOKS = Object.freeze({
44
46
  blond: pair({ ...COMMON, surface: "#e0bf8a", alt: "#d3b27c", red: "#b32a2f", floor: "#e0bf8a", free: "#ece4d3" }),
45
47
  },
46
48
  sumo: { clay: pair({ ...COMMON, surface: "#b0794a", alt: "#a06c40", red: "#b32a2f", dirt: "#b0794a", sand: "#e8dcc2", straw: "#c8a45c" }) },
49
+ // The cage's canvas, over the platform's dark deck: a grey canvas (the default), black, and blue. Ours: no rule paints a cage.
50
+ mma: {
51
+ grey: lifted(pair({ ...COMMON, surface: "#8d949b", alt: "#6f767d", red: "#b32a2f", floor: "#43484e" }), "#444a52"),
52
+ black: lifted(pair({ ...COMMON, surface: "#2b2e33", alt: "#1f2226", red: "#b32a2f", floor: "#24272b" }), "#3b3f45"),
53
+ blue: lifted(pair({ ...COMMON, surface: "#3a68ad", alt: "#2b528d", red: "#b32a2f", floor: "#2c3440" }), "#3a4452"),
54
+ },
47
55
  kyudo: { wood: pair({ ...COMMON, surface: "#9db57d", alt: "#d8cfb0", red: "#b32a2f", floor: "#b98c58", dirt: "#c9ad7a" }) },
48
56
  });
49
57
  /** The names of a sport's looks, the default first. */
@@ -7,9 +7,10 @@ import { type KarateLayout } from "./karate.ts";
7
7
  import { type KendoLayout } from "./kendo.ts";
8
8
  import { type KyudoLayout } from "./kyudo.ts";
9
9
  import { boxD } from "./kit.ts";
10
+ import { type CageLayout } from "./cage.ts";
10
11
  import { type MBox, type MMark, type MRange } from "./parts.ts";
11
12
  import { type SumoLayout } from "./sumo.ts";
12
- import type { BudoKind } from "./types.ts";
13
+ import type { BudoKind, CombatKind } from "./types.ts";
13
14
  export type { MBox, MMark, MRange };
14
15
  /** The apothem of the Nippon Budokan's arena, an octagon 55 m across the flats (the Budokan's leaflet). */
15
16
  export declare const BUDOKAN_ARENA = 27.5;
@@ -48,11 +49,18 @@ export interface BudoPlan {
48
49
  sumo?: SumoLayout;
49
50
  kyudo?: KyudoLayout;
50
51
  dojo?: DojoLayout;
52
+ mma?: CageLayout;
51
53
  }
52
54
  /** The sports of the budō family. */
53
55
  export declare const BUDO_SPORTS: readonly BudoKind[];
54
56
  /** True for a sport of the budō family. */
55
- export declare const isBudoSport: (sport: string) => sport is BudoKind;
57
+ 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. */
59
+ export declare const COMBAT_SPORTS: readonly CombatKind[];
60
+ /** True for a combat sport (the MMA cage). */
61
+ export declare const isCombatSport: (sport: string) => sport is CombatKind;
62
+ /** True for a sport this engine draws: a sport of the budō or a combat sport. */
63
+ export declare const isMatSport: (sport: string) => sport is BudoKind;
56
64
  /** The league a set of options asks for: a rulebook's, else the sport's own. */
57
65
  export declare function budoLeague(o: Pick<SurfaceOptions, "sport" | "league" | "budo" | "dojo">): BudoLeague;
58
66
  /** The octagon of the Budokan's arena as a path, centred on (cx, cy). */
package/dist/budo/plan.js CHANGED
@@ -1,11 +1,13 @@
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
4
  import { dojoLayout, dojoLeague, dojoSpecOf } from "./dojo.js";
4
5
  import { contestOf, gridOf, judoParts, matOf, safetyOf, snapTo } from "./judo.js";
5
6
  import { karateParts } from "./karate.js";
6
7
  import { kendoParts, kendoSide } from "./kendo.js";
7
8
  import { kyudoParts, shootersOf } from "./kyudo.js";
8
9
  import { boxD } from "./kit.js";
10
+ import { cageHeight, cageParts, cageWidth, fenceHeight } from "./cage.js";
9
11
  import { emptyParts, grow, union } from "./parts.js";
10
12
  import { dohyoHeight, sumoParts } from "./sumo.js";
11
13
  import { lookOf } from "./looks.js";
@@ -16,7 +18,13 @@ const isBudoLeague = (l) => typeof l === "object" && l !== null && BUDO_LEAGUES.
16
18
  export const BUDO_SPORTS = Object.freeze(["judo", "karate", "kendo", "sumo", "kyudo", "dojo"]);
17
19
  /** True for a sport of the budō family. */
18
20
  export const isBudoSport = (sport) => BUDO_SPORTS.includes(sport);
19
- const OWN = { judo: JUDO_LEAGUES, karate: KARATE_LEAGUES, kendo: KENDO_LEAGUES, sumo: SUMO_LEAGUES, kyudo: KYUDO_LEAGUES, dojo: DOJO_LEAGUES };
21
+ /** The combat sports the engine of the budō also draws: the MMA cage. */
22
+ export const COMBAT_SPORTS = Object.freeze(["mma"]);
23
+ /** True for a combat sport (the MMA cage). */
24
+ export const isCombatSport = (sport) => COMBAT_SPORTS.includes(sport);
25
+ /** True for a sport this engine draws: a sport of the budō or a combat sport. */
26
+ 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 };
20
28
  /** 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`. */
21
29
  function kataOf(style) {
22
30
  if (style === undefined)
@@ -118,6 +126,12 @@ export function budoPlan(o) {
118
126
  const ranges = { shajo: L.shajo, yamichi: L.yamichi, azuchi: grow(L.azuchi, 0.8) };
119
127
  return finish({ box: L.range, extent: L.extent, areas: L.parts.areas, shapes: L.parts.shapes, lineWidth: 0.05, ranges, marks: L.marks, centre: { x: (L.shajo.x0 + L.shajo.x1) / 2, y: (L.shajo.y0 + L.shajo.y1) / 2, r: 1.5 }, kyudo: L });
120
128
  }
129
+ case "mma": {
130
+ const f = fig;
131
+ const L = cageParts(f, { width: cageWidth(style.size, f), height: cageHeight(style.height, f), fence: fenceHeight(style.fence, f), gates: flag(style.gates, true, "budo.gates"), steps: flag(style.steps, true, "budo.steps") });
132
+ const ranges = { ...L.ranges, half: halves(L.platform) };
133
+ 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
+ }
121
135
  default: {
122
136
  const L = dojoLayout(o.dojo ?? {}, dojoLeague(league.id));
123
137
  const f = L.floor;
@@ -1,5 +1,7 @@
1
1
  /** The sports of the budō family. */
2
- export type BudoKind = "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo";
2
+ export type BudoKind = "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo" | CombatKind;
3
+ /** The combat sports the engine of the budō also draws: the MMA cage. */
4
+ export type CombatKind = "mma";
3
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. */
4
6
  export type Compass = "north" | "south" | "east" | "west";
5
7
  export type Side4 = Compass | "top" | "bottom" | "left" | "right";
@@ -19,7 +21,7 @@ export interface BudoStyle {
19
21
  hall?: "budokan";
20
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). */
21
23
  style?: "shiai" | "kata";
22
- /** Judo: the contest area's side, 8 m to 10 m. Kendo: the shiai-jo's side, 9 m to 11 m. 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. In metres. */
23
25
  size?: number;
24
26
  /** Judo: the width of the safety area, 3 m or more (4 m by default). In metres. */
25
27
  safety?: number;
@@ -37,12 +39,18 @@ export interface BudoStyle {
37
39
  texture?: "weave" | "none";
38
40
  /** Karate: the referee's and the judges' places. On by default. */
39
41
  judges?: boolean;
40
- /** Sumo: the height of the clay square in metres, 0.34 to 0.60. 0.55 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. Seen in 3D. */
41
43
  height?: number;
42
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. */
43
45
  tassels?: boolean;
44
46
  /** Sumo: the tawara, the shikiri-sen and the sand of the jano-me. On by default. */
45
47
  tawara?: boolean;
48
+ /** MMA cage: the fence's height over the canvas, 1.524 m to 2.134 m (5 to 7 feet), 1.829 m (6 feet) by default. Seen in 3D. */
49
+ fence?: number;
50
+ /** MMA cage: the two gates, opposite each other (the fence is whole without them). On by default. */
51
+ gates?: boolean;
52
+ /** MMA cage: the steps up to the platform at each gate, three treads. On by default. */
53
+ steps?: boolean;
46
54
  /** Kyūdō: the number of shooters in a line, 1 to 12 (5 by default). */
47
55
  shooters?: number;
48
56
  /** Kyūdō: the kind of range, `kinteki` (28 m, the default) or `enteki` (60 m); `league` says it too. */
@@ -3,7 +3,7 @@ export type { Dojo, Tatami } from "./budo/dojo.api.ts";
3
3
  export { DOJO_SIZES, floorOf, shapeOfJo } from "./budo/dojo.ts";
4
4
  export { dojoGallery, dojoRoom, ROOM_HEIGHTS } from "./budo/room.ts";
5
5
  export type { DojoGallery, DojoRoom, RoomBalcony, RoomBasis, RoomOpening, RoomWall } from "./budo/room.ts";
6
- export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, isBudoSport } from "./budo/plan.ts";
6
+ export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, COMBAT_SPORTS, isBudoSport, isCombatSport, isMatSport } from "./budo/plan.ts";
7
7
  export type { BudoPlan, MBox, MMark, MRange } from "./budo/plan.ts";
8
8
  export { checker, fourCorners, kindOf, lay, matCount, tint } from "./budo/tatami.ts";
9
9
  export type { MatSize, Piece, PieceKind } from "./budo/tatami.ts";
@@ -2,7 +2,7 @@
2
2
  export { DOJO_STANDARDS, dojo, drawDojo } from "./budo/dojo.api.js";
3
3
  export { DOJO_SIZES, floorOf, shapeOfJo } from "./budo/dojo.js";
4
4
  export { dojoGallery, dojoRoom, ROOM_HEIGHTS } from "./budo/room.js";
5
- export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, isBudoSport } from "./budo/plan.js";
5
+ export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, COMBAT_SPORTS, isBudoSport, isCombatSport, isMatSport } from "./budo/plan.js";
6
6
  export { checker, fourCorners, kindOf, lay, matCount, tint } from "./budo/tatami.js";
7
7
  export { TATAMI } from "./budo/judo.js";
8
8
  export { KIMARITE, KIMARITE_COUNT, KIMARITE_OTHERS, kimariteName, kimariteOf } from "./budo/kimarite.js";
@@ -37,6 +37,11 @@ const CLASSES = `
37
37
  .kyogijo .ky-bud-tassel-s{fill:#d23b32;stroke:var(--ky-ink);stroke-opacity:.5;stroke-width:.03}
38
38
  .kyogijo .ky-bud-tassel-w{fill:#f4f1e8;stroke:var(--ky-ink);stroke-opacity:.5;stroke-width:.03}
39
39
  .kyogijo .ky-bud-tassel-n{fill:#232323;stroke:var(--ky-ink);stroke-opacity:.5;stroke-width:.03}`.trim();
40
+ /** The rules of the combat sports' classes, added to a combat drawing only, so that the budō drawings are the text they were: the steps up to a platform and the padding of a post. */
41
+ const COMBAT_CLASSES = `
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
+ .kyogijo .ky-bud-pad{fill:var(--ky-ink);fill-opacity:.82}`.trim();
44
+ const COMBAT = ["mma"];
40
45
  /** The custom properties of a look. */
41
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}`}`;
42
47
  /** The three rules of one look: light, dark by the device's setting, and dark by the page's `data-theme`. */
@@ -64,7 +69,7 @@ export function budoStyle(sport, look) {
64
69
  const kind = sport;
65
70
  if (BUDO_LOOKS[kind] === undefined)
66
71
  return "";
67
- return `${CLASSES}\n${look === undefined ? lookRules(kind, Object.keys(BUDO_LOOKS[kind])[0]) : lookRules(kind, look)}`;
72
+ return `${CLASSES}${COMBAT.includes(sport) ? `\n${COMBAT_CLASSES}` : ""}\n${look === undefined ? lookRules(kind, Object.keys(BUDO_LOOKS[kind])[0]) : lookRules(kind, look)}`;
68
73
  }
69
74
  /** Every budō sport's rules and every look, for a page that draws with `style: "none"` and supplies the stylesheet itself. */
70
- export const KYOGIJO_BUDO_STYLE = `${CLASSES}${KYOGIJO_BUDO_GALLERY_STYLE}\n${Object.keys(BUDO_LOOKS).flatMap((sport) => Object.keys(BUDO_LOOKS[sport]).map((name) => lookRules(sport, name))).join("\n")}`;
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")}`;
package/dist/crowd.js CHANGED
@@ -100,6 +100,8 @@ const PROFILES = {
100
100
  sumo: { shape: "bowl", side: 58, end: 58, bench: 0, corner: 56, tiers: [{ id: "lower", from: 0, to: 54, height: [1, 8], fill: 1 }, upper(62, 128, 26, 62)], away: "corner" },
101
101
  kyudo: { shape: "sides", side: 10, end: 8, bench: 0, corner: 6, tiers: [lower(24, 2, 8), upper(30, 52, 12, 22)], away: "corner" },
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
+ // An MMA cage stands in the middle of an arena, with the seats rising from the ring of the floor.
104
+ mma: { shape: "bowl", side: 40, end: 40, bench: 0, corner: 56, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner" },
103
105
  };
104
106
  /** 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. */
105
107
  export const GRIDIRON_GROUNDS = Object.freeze({
package/dist/draw.js CHANGED
@@ -17,7 +17,7 @@ import { athleticsPlan } from "./athletics/plan.js";
17
17
  import { skatingPlan } from "./skating/plan.js";
18
18
  import { skatingAbout, skatingSentences } from "./skating/describe.js";
19
19
  import { skatingStyle } from "./skating.style.js";
20
- import { budoPlan, isBudoSport } from "./budo/plan.js";
20
+ import { budoPlan, isBudoSport, isMatSport } from "./budo/plan.js";
21
21
  import { dojoSpecOf } from "./budo/dojo.js";
22
22
  import { dojoGallery } from "./budo/room.js";
23
23
  import { budoAbout, budoScoreSentences, budoSentences } from "./budo/describe.js";
@@ -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", "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", "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 ? 12 : 4));
268
+ return budoSurface(common, plan, common.margin ?? (o.range === undefined ? (plan.mma === 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);
@@ -373,6 +373,7 @@ function buildWhole(o, portrait, id, score, scored, extra) {
373
373
  case "sumo":
374
374
  case "kyudo":
375
375
  case "dojo":
376
+ case "mma":
376
377
  return buildBudo({ ...o, sport }, common);
377
378
  default:
378
379
  throw new Error(`Unknown sport "${String(o.sport)}": use one of ${SPORTS.join(", ")}`);
@@ -608,7 +609,7 @@ function leagueAbout(league, year, language, sheet) {
608
609
  const words = KYOGIJO_STRINGS[language].league;
609
610
  if (league.family === "skating")
610
611
  return skatingAbout({ sport: "skating", league: league.id }, language);
611
- if (league.family === "judo" || league.family === "karate" || league.family === "kendo" || league.family === "sumo" || league.family === "kyudo" || league.family === "dojo")
612
+ if (league.family === "judo" || league.family === "karate" || league.family === "kendo" || league.family === "sumo" || league.family === "kyudo" || league.family === "dojo" || league.family === "mma")
612
613
  return budoAbout({ sport: league.family, league: league.id }, language);
613
614
  if (league.family === "athletics")
614
615
  return athleticsAbout({ sport: "athletics", league: league.id, athletics: sheet === undefined ? undefined : undefined }, language);
@@ -697,8 +698,8 @@ export function describe(o) {
697
698
  const poolLine = pools === undefined ? "" : pools.after.map((line) => `${gap}${line}`).join("");
698
699
  // An arena of a size of its own says what it is at that size.
699
700
  const arenaAbout = arenaSize === undefined ? undefined : fill(sport === "nhl" ? words.said.arenaRinkAbout : words.said.arenaCourtAbout, { length: metres(arenaSize.length), width: metres(arenaSize.width), lengthM: metres(arenaSize.length * 0.3048), widthM: metres(arenaSize.width * 0.3048) });
700
- const aboutText = arenaAbout !== undefined ? arenaAbout : pools !== undefined ? pools.about : isBudoSport(sport) ? budoAbout(o, language) : sport === "skating" ? skatingAbout(o, language) : sport === "athletics" ? athleticsAbout(o, language) : field !== undefined ? leagueAbout(league ?? LEAGUES[sport][0], o.year, language, { length: field.length, width: field.width }) : plan !== undefined ? leagueAbout(plan.league, o.year, language, plan.spec) : league !== undefined ? leagueAbout(league, o.year, language) : pitch === undefined ? words.about[sport] : fill(pitch.format === "futsal" ? words.soccer.aboutFutsal : words.about.soccer, { length: metres(pitch.length), width: metres(pitch.width) });
701
- const about = `${words.said.on}${colon}${aboutText}${stop}${sport === "athletics" ? athleticsSentences(o, language) : sport === "skating" ? skatingSentences(o, language) : isBudoSport(sport) ? budoSentences(o, language) : ""}`;
701
+ const aboutText = arenaAbout !== undefined ? arenaAbout : pools !== undefined ? pools.about : isMatSport(sport) ? budoAbout(o, language) : sport === "skating" ? skatingAbout(o, language) : sport === "athletics" ? athleticsAbout(o, language) : field !== undefined ? leagueAbout(league ?? LEAGUES[sport][0], o.year, language, { length: field.length, width: field.width }) : plan !== undefined ? leagueAbout(plan.league, o.year, language, plan.spec) : league !== undefined ? leagueAbout(league, o.year, language) : pitch === undefined ? words.about[sport] : fill(pitch.format === "futsal" ? words.soccer.aboutFutsal : words.about.soccer, { length: metres(pitch.length), width: metres(pitch.width) });
702
+ const about = `${words.said.on}${colon}${aboutText}${stop}${sport === "athletics" ? athleticsSentences(o, language) : sport === "skating" ? skatingSentences(o, language) : isMatSport(sport) ? budoSentences(o, language) : ""}`;
702
703
  const halfLine = pitch !== undefined && !isScore(o) && (o.half === "left" || o.half === "right") ? `${gap}${words.soccer.half}` : "";
703
704
  // What the ground says about the size: that it is the published one, or that the ground has none and the international pitch is drawn.
704
705
  const year = typeof o.year === "number" && Number.isFinite(o.year) ? o.year : undefined;
@@ -889,7 +890,7 @@ function drawnKyogijo(o) {
889
890
  const crowded = crowdOf(o) !== undefined;
890
891
  const style = colourStyle(o, { left: leftTeam, right: rightTeam, home: isScore(o) ? o.home : undefined, away: isScore(o) ? o.away : undefined }, featured);
891
892
  const palette = isLook(o.look) ? o.look : undefined;
892
- const budoLook = isBudoSport(sport) ? budoLookOf(sport, o.budo?.look ?? (sport === "dojo" ? dojoSpecOf(o.dojo).look : undefined)) : undefined;
893
+ const budoLook = isMatSport(sport) ? budoLookOf(sport, o.budo?.look ?? (sport === "dojo" ? dojoSpecOf(o.dojo).look : undefined)) : undefined;
893
894
  let notes;
894
895
  const theme = o.theme === "light" || o.theme === "dark" ? o.theme : undefined;
895
896
  let viewBox;
package/dist/features.js CHANGED
@@ -128,6 +128,15 @@ const SUMO = [
128
128
  f("tawara", "--ky-bud-straw", "The tawara of the ring."),
129
129
  f("shikiri-sen", "--ky-line", "The two shikiri-sen."),
130
130
  ];
131
+ const MMA = [
132
+ f("apron", "--ky-apron", "The floor of the hall round the platform."),
133
+ f("platform", "--ky-bud-floor", "The platform the cage stands on."),
134
+ f("steps", "--ky-bud-floor", "The steps up to the platform."),
135
+ f("canvas", "--ky-surface", "The canvas inside the fence."),
136
+ f("fence", "--ky-ink", "The fence, one line on each flat."),
137
+ f("gates", "--ky-ink", "The swing of the two gates."),
138
+ f("posts", "--ky-ink", "The eight padded posts."),
139
+ ];
131
140
  const KYUDO = [
132
141
  f("apron", "--ky-apron", "The ground round the range."),
133
142
  f("shajo", "--ky-bud-floor", "The floor of the shajō."),
@@ -435,6 +444,7 @@ export const FEATURE_INFO = Object.freeze({
435
444
  sumo: SUMO,
436
445
  kyudo: KYUDO,
437
446
  dojo: DOJO,
447
+ mma: MMA,
438
448
  });
439
449
  /**
440
450
  * 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.
@@ -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";
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";
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 {