@johnmorrisdotca/kyogijo-stadiums 0.13.0 → 0.15.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,30 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.15.0] - 2026-10-20
11
+
12
+ ### Added
13
+
14
+ - **A table tennis table in its playing area.** The default drawing of `sport: "tabletennis"` is no longer a table on a generic floor: it is the table in the ITTF's playing area, from the Regulations for International Competitions (3.2.3, chapter 3 of the ITTF Statutes 2026, read on 2026-10-20): a space of 14 m by 7 m (5 m high), **enclosed by four dark surrounds** (50 cm to 100 cm high, all one dark colour, 3.2.3.3), **with the umpire's chair in line with the net, the assistant umpire's chair facing it across the table and the umpires' table beside the first** (3.2.3.2, 3.3.2.2), on a floor that is not light (3.2.3.8; both looks are dark blues and greens). New `net.area` picks the kind of event, `standard` (14 m by 7 m), `masters` (10 m by 5 m) or `wheelchair` (8 m by 6 m) (3.2.3.1), and `net.surrounds` and `net.umpires` leave the surrounds or the umpires out; `net.free: "none"` draws the table alone, as before, and `free: "least"` is the Masters area. In 3D (all viewpoints) the area has its own floor, the surrounds stand 75 cm round it (they give way to an eye low on their side, as every wall does) and the chairs and the table are solid blocks. New in `TableTennisFigures`: `space`, `surround`, `umpires`; the features `walls`, `umpires-table` and `chairs` can be restyled. Sizes the rules do not give (a chair 0.5 m, the umpires' table 1.2 m by 0.5 m, 0.9 m from the table, a surround 5 cm thick, 75 cm high) are ours and said so. Docs: [the table tennis playing area](./docs/courts.md#the-table-tennis-playing-area), with pictures.
15
+
16
+ ### Changed
17
+
18
+ - **The tournament layouts' table tennis areas are that same space.** The bay of each table in `tabletennis-4` and `tabletennis-8` is `ITTF_TABLE.space.standard` (14 m by 7 m, the table in its middle), and the layouts draw the table alone in it (`net: { free: "none" }`: the layout draws the surrounds and the chairs of its bays), so the layouts are byte for byte what they were. A test holds the bay to the space.
19
+
20
+ ### Fixed
21
+
22
+ - **The surrounds of a table tennis area are 50 cm to 100 cm high, not "about 75 cm", and the corners need no 1.5 m limit.** The layouts' rule text and the sources page quoted the Handbook of 2022; the current text (2025 and 2026) says "a minimum of 50cm and a maximum of 100 cm in height, all of the same dark background colour, but corners may be left open". The 75 cm drawn stays, as a choice of ours inside the rule. The 2026 Statutes are now the preset's source (`ittf-statutes-2026`); the 2022 Handbook's entry says it was superseded.
23
+
24
+ ## [0.14.0] - 2026-10-20
25
+
26
+ ### Added
27
+
28
+ - **The judo kata area, with its centre tape and its judges** (`budo: { style: "kata" }`, which says `league: "ijf-kata"`; `style: "shiai"` is the contest area, the default). From the IJF's Sport and Organisation Rules of 21 January 2026 (B1.8, the judges' article, B1.10), the Kata Commission's Tournament Handbook of 2024 and its Criteria for the Evaluation of January 2025: an area of 10 m (World Championships) or 8 m, a safety area of at least 2 m, two white tapes of 5 cm by 50 cm 3 m from the centre either side, **and now the tape of the same colour at the centre** (the area had the two only); with `officials`, **the five judges' seats 1.25 m apart on the shomen side, the central judge at the centre, with their table, and the coach's chair at the centre on the opposite side**, where the pair enters (a label says so), and no referee's place. A new `JudoFigures.centre` and `JudoFigures.panel`, four new labels (`centre-tape`, `kata-judges`, `coach-chair`, `kata-entry`, English and Japanese), the two sources in `LEAGUE_SOURCES`, 2D and 3D, light and dark. Docs: a kata section with two pictures in [the budō page](./docs/budo.md#the-kata-area); the gallery has the area with and without its judges. The rules do not say which tape is tori's and which is uke's, nor which way a tape lies, so none is assigned.
29
+
30
+ ### Fixed
31
+
32
+ - **Two kata areas side by side are 2 m apart, not 4 m.** The kata area took the contest area's 4 m common safe zone, but the Handbook asks for "a common safety area of minimum 2 meters" between adjoining kata areas and B1.8 for a safety area "between and around the mat" of at least 2 m. `IJF_KATA.common` is 2 (`docs/variants.md` said 4).
33
+
10
34
  ## [0.13.0] - 2026-10-20
11
35
 
12
36
  ### Added
package/NOTICE.md CHANGED
@@ -33,7 +33,7 @@ picture or code of the sources is copied; each source is named here so that the
33
33
  | `geometry`: the swimming pool, the water polo field, the diving well and the artistic swimming area | World Aquatics, Competition Regulations, in force as from 25 June 2025 (Part Two Article 16, Part Four Article 13 and Appendix 2, Part Six Article 18 and Appendices 3 and 4, Part Seven Article 27), and the Facilities Rules 2017-2021 and 2021-2025 (FR 2 to FR 5) | Facts; no text or drawing copied |
34
34
  | `leagues`: the athletics track | World Athletics' Track and Field Facilities Manual (2019 edition, Chapters 1 to 3) and its 400 Metre Standard Track Marking Plan (Figure 2.2.1.6a), the Competition and Technical Rules (Books C1.1 and C2.1, in force 1 July 2026) and the Marking Plan for the Standard Short Track (1 January 2026) | Facts; no text or drawing copied |
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
- | `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); 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 |
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
38
  | `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
39
  | `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/) |
package/README.md CHANGED
@@ -72,40 +72,40 @@ 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, the courts of tennis, volleyball (beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet and netball, the pools, the athletics stadium, the skating rinks, the fields of lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budō. Every figure is cited in [sources](./docs/sources.md). [The surfaces](./docs/surfaces.md).
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, the courts of twelve net and racket sports, the pools, the athletics stadium, the skating rinks, the fields of seven field sports, and the budō. 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
- - **A realistic basketball floor.** `court: { hardwood: true }` draws maple boards that run the length of the court with staggered ends, grain and tone, stained keys, circle and border, a strip behind each baseline in the team's colour, the baskets and stanchions with soft shadows from above, a coaching box and benches, and a satin sheen, in every league and every renderer, with the detail dropping at small sizes. The clean floor stays the default. [Hardwood](./docs/hardwood.md).
78
- - **The stands and the crowd.** `crowd: true` draws the seating round any surface that has spectators: a bowl, two sides, a horseshoe, an arena's courtside rows, or a ballpark's grandstands and bleachers, with the people as seeded heads and shoulders in the home colours, the visitors at an end, the empty seats showing (`density`, or `attendance` over `capacity`), and a soccer pitch inside a gridiron stadium's footprint, to scale. Blocks at small sizes; the stands as data. [The crowd](./docs/crowd.md).
79
- - **Curling, from the rule books.** A sheet from World Curling's and Curling Canada's rules, tall by default, with the board and the hammer in a score, `curlingStones` for the stones of an end, scored by the rules, `show: "folded"` for the whole sheet with both houses enlarged beside it in a square, and a heat map and statistics (free guards, hammer, steals, shots) for many ends. [Curling](./docs/curling.md).
80
- - **Net, racket and court sports, from their rulebooks.** The ITF's court, the FIVB's indoor and beach courts, World ParaVolley's sitting volleyball court, the BWF's, USA Pickleball's, the ITTF's table, the FIP's padel court, World Squash's courts (with the front wall laid flat beside the floor), the IRF's racquetball court, the ISTAF's sepak takraw court, the FITEQ's teqball court and table, the Roundnet Federation's court and World Netball's court, each in its look (hard court, clay, grass, wood, sand), with a board of sets and games and a ball beside the server. [Net, racket and court sports](./docs/courts.md).
81
- - **Swimming, water polo, diving and artistic swimming, from World Aquatics' regulations.** The competition pool of 50 or 25 m with its lanes, ropes, blocks, touch panels and flags; the water polo field; the diving well with its springboards and platforms; the artistic swimming area; several pools in a natatorium. The lanes of a race carry names, flags, places, times and splits, with a race replay by lane (`raceTimeline`) and a `splitsChart`. [Pools](./docs/pools.md).
82
- - **Lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, from their rulebooks.** The fields and floors of World Lacrosse, the NCAA, the PLL and the NLL; World Rugby's union and the NRL's and IRL's league; the AFL's oval at the size of a named ground; the GAA's field for football and hurling; a cricket ground with a boundary of your own and the 30-yard circle; the FIH's pitch and the IHF's court. A score is goals and behinds (10.12 (72)), goals and points (2-14 (20)) or a board of runs, wickets and overs. [Field sports](./docs/field-sports.md).
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
+ - **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, the visitors at an end and the empty seats showing (`density`, or `attendance` over `capacity`). Blocks at small sizes. [The crowd](./docs/crowd.md).
79
+ - **Curling, from the rule books.** A sheet from World Curling's and Curling Canada's rules, tall by default, with the board and the hammer in a score, `curlingStones` for the stones of an end, scored by the rules, `show: "folded"` for the whole sheet with both houses enlarged beside it, and a heat map and statistics for many ends. [Curling](./docs/curling.md).
80
+ - **Net, racket and court sports, from their rulebooks.** The ITF's, FIVB's (indoor, beach, sitting), BWF's, USA Pickleball's and ITTF's courts and table, the padel, squash, racquetball, sepak takraw, teqball, roundnet and netball courts, each in its look (hard court, clay, grass, wood, sand), with a board of sets and games. [Net, racket and court sports](./docs/courts.md).
81
+ - **Swimming, water polo, diving and artistic swimming, from World Aquatics' regulations.** The competition pool of 50 or 25 m with its lanes, ropes, blocks, touch panels and flags; the water polo field; the diving well with its springboards and platforms; the artistic swimming area; several pools in a natatorium. A race's lanes carry names, flags, places, times and splits, with a replay (`raceTimeline`) and a `splitsChart`. [Pools](./docs/pools.md).
82
+ - **Lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, from their rulebooks.** The fields and floors of World Lacrosse, World Rugby, the NRL, the AFL, the GAA, the FIH and the IHF, and a cricket ground with a boundary of your own. A score is goals and behinds, goals and points or a board of runs, wickets and overs. [Field sports](./docs/field-sports.md).
83
83
  - **The athletics stadium, from World Athletics' rules.** The 400 m track with its lanes and staggers worked out from the running lines, curved and relay starts, hurdles, the steeplechase, every field event, a close-up of each start, the covered 200 m track, and lane draws, results, a race replay and the marks of throws and jumps. [The athletics stadium](./docs/athletics.md).
84
- - **The skating rinks, from the ISU's rules.** The standard 400 m speed skating track with its lanes, blocks and coaches' area and the starting and finishing lines of every race worked out lane by lane; the short track's oval of 111.12 m with its blocks and starting positions; the figure skating rink of 60 by 30 m with its judges' seats; and pairs, results, a race replay and the elements of a programme on the ice. [The skating rinks](./docs/skating.md).
85
- - **The budō, from the federations' rules.** The IJF's and the All Japan Judo Federation's judo areas, the WKF's karate areas, the kendo shiai-jo, the sumo dohyō, the kyūdōjō, and **a dojo builder**: a floor of tatami laid as real units (no four corners meet), in jō or metres, with the walls, doors, windows, shrine and kamiza. Five famous halls are on the atlas, each with only what a source gives, and the scores are judo's ippon, waza-ari and shido, karate's points, kendo's points and sumo's kimarite. [The budō](./docs/budo.md).
84
+ - **The skating rinks, from the ISU's rules.** The 400 m speed skating track with its lanes, blocks and the starting and finishing lines of every race worked out lane by lane; the short track's oval of 111.12 m; the figure skating rink of 60 by 30 m with its judges' seats; and pairs, results, a race replay and the elements of a programme on the ice. [The skating rinks](./docs/skating.md).
85
+ - **The budō, from the federations' rules.** The IJF's and the All Japan Judo Federation's judo areas, the WKF's karate areas, the kendo shiai-jo, the sumo dohyō, the kyūdōjō, and **a dojo builder**: a floor of tatami laid as real units (no four corners meet), in jō or metres, with the walls, doors, windows, shrine and kamiza. Five famous halls are on the atlas, and the scores are judo's ippon, waza-ari and shido, karate's and kendo's points and sumo's kimarite. [The budō](./docs/budo.md).
86
86
  - **Soccer, to the Laws.** A pitch from Law 1 of the IFAB Laws of the Game of any size, with presets (international, UEFA, MLS, NCAA, the FA's 9-, 7- and 5-a-side, FIFA's futsal), half pitches, five mowing patterns, four goal styles, penalties and extra time in a score. [Soccer](./docs/soccer.md).
87
87
  - **Every ballpark.** The 30 current MLB parks and 44 that MLB clubs have left (the Polo Grounds, Ebbets Field, the Astrodome and more), and the 12 home parks of Nippon Professional Baseball with eight other NPB grounds, each with its fence drawn from the distances published for it; a past park whose fence moved is drawn as it stood in a season. [Ballparks](./docs/ballparks.md).
88
- - **Soccer grounds, arenas and an atlas.** 64 soccer grounds, each pitch at its published size, and 167 NHL, NBA, WNBA and PWHL arenas (`drawKyogijo({ sport: "nhl", arena: "boston-garden" })`), a rink or court at the arena's own size where known, and every park, ground, arena and budō hall (330 places) on a map, by sport, league, country and year, each found by any of its names. [The grounds](./docs/grounds.md), [the arenas](./docs/arenas.md), [the atlas](./docs/atlas.md), [names](./docs/ballparks.md#names).
89
- - **Walls and quirks.** How high the fence stands, stretch by stretch and season by season (the Green Monster at 37 feet 2 inches, the Crawford Boxes, the Baggie), drawn as a band outside the fence whose width and shade say the height, with a key (`walls`); and Tal's Hill, Wrigley Field's ivy and basket, overhangs, poles, bullpens, the backstop and the dugouts (`quirks`), each from the park's article, none guessed: [ballparks](./docs/ballparks.md).
90
- - **Special games.** A surface set inside another, to scale: an NHL rink on a ballpark's infield or a football stadium's field (the Winter Classic, the Stadium Series, the Heritage Classic), a college basketball court on the deck of a carrier, a baseball field laid over a London stadium's pitch. 18 games, each with its day, its venue and its sources, and the place of the rink cited where a source gives it and marked as a convention where none does. [Special games](./docs/special-games.md).
88
+ - **Soccer grounds, arenas and an atlas.** 64 soccer grounds, each pitch at its published size, and 167 NHL, NBA, WNBA and PWHL arenas (`drawKyogijo({ sport: "nhl", arena: "boston-garden" })`), and every park, ground, arena and budō hall (330 places) on a map, by sport, league, country and year, each found by any of its names. [The grounds](./docs/grounds.md), [the arenas](./docs/arenas.md), [the atlas](./docs/atlas.md), [names](./docs/ballparks.md#names).
89
+ - **Walls and quirks.** How high the fence stands, stretch by stretch and season by season (the Green Monster at 37 feet 2 inches), drawn as a band outside the fence whose width and shade say the height, with a key (`walls`); and Tal's Hill, Wrigley Field's ivy and basket, poles, bullpens and dugouts (`quirks`), each from the park's article, none guessed: [ballparks](./docs/ballparks.md).
90
+ - **Special games.** A surface set inside another, to scale: an NHL rink on a ballpark's infield or a football stadium's field, a college basketball court on the deck of a carrier, a baseball field laid over a London stadium's pitch. 18 games, each with its day, its venue and its sources. [Special games](./docs/special-games.md).
91
91
  - **Surfaces through the years.** `year: 1975` draws a surface as its league's rules marked it that season, each change cited. [Surfaces through the years](./docs/eras.md).
92
- - **Score mode.** Each team's name and logo on its side (the end zones of a football field, the ends of the ice, the halves of the court, the dugouts of a ballpark) and the score large in the middle, with the competition, the status and the date, and the winner emphasised; a flag or a crest can be round, in a circle with a ring (`markShape: "round"`). [Score and layout](./docs/score.md#round-marks).
93
- - **The situation of a game.** `situation` draws what is happening: hockey's power play or empty net as a pill with the attacking zone tinted, and a shootout as a strip of dots; baseball's runners lit on the diamond with a scorebug (outs, inning, count); football's line of scrimmage, first-down line, red zone and down-and-distance tag, with a drive chart; in the badge and the compact card too, and in the description. [The situation](./docs/situation.md).
94
- - **Lineups.** A `lineup` layer places a team's players by position code (`GK`, `CB`, `HK`, `C`) with no coordinates: it reads where each stands from a formation (36 of them, in 13 sports), draws a disc or a jersey with the shirt number and the name under it, a captain's C and a flag, and puts the visitors at the other end. A drawing with a lineup puts the score in the strip under it, so the panel hides no player. [Lineups](./docs/team-shape.md#lineups).
95
- - **A pageant.** Each team's flag or crest laid large over its half of the surface, as a stadium lays giant flags and crests for a final: `pageant: "carpet"` (a flag at its own ratio with a border and a gentle fabric) or `"stage"` (a crest on a round disc with a gold ring), an emblem or a trophy of your own in the centre circle, the score in the middle or in the strip, upright wide and tall, in light and dark, with an entrance in CSS for readers who have not asked for less motion. [The pageant](./docs/pageant.md).
96
- - **Trophies, medals, balls and crests, by id.** `useEmblems(renderEmblem)` plugs in the sister package [Dougu Sports](https://johnmorrisdotca.github.io/dougu-sports/) in one line, and an id goes wherever a logo, a flag or a mark is taken: `logo: "crest:shield"` (a crest in the team's colour with its letters), `pageant: { emblem: "trophy:hockey-cup" }`, `"sport:nhl/line"`, `"ball:puck"`, `"cup"`. Kyogijo bundles no emblem and keeps zero dependencies; without the plug-in an id is refused like any unsafe address. [Emblems](./docs/emblems.md).
97
- - **Country flags, by code.** `useFlags(render)` plugs in the sister package [Hata](https://github.com/johnmorrisdotca/hata) through a small adapter, and a team's `flag: "CA"` is its flag: round in a round mark, at its own proportions on a banner, a badge or a pageant's carpet. The same adapter serves [Taisen Brackets](https://github.com/johnmorrisdotca/taisen-brackets). Kyogijo bundles no flag and keeps zero dependencies; without the plug-in a code is ignored. [Flags](./docs/flags.md).
92
+ - **Score mode.** Each team's name and logo on its side (the end zones, the ends of the ice, the halves of the court, the dugouts) and the score large in the middle, with the competition, the status and the date, and the winner emphasised; a flag or a crest can be round, in a circle with a ring (`markShape: "round"`). [Score and layout](./docs/score.md#round-marks).
93
+ - **The situation of a game.** `situation` draws what is happening: hockey's power play or empty net and a shootout; baseball's runners lit on the diamond with a scorebug; football's line of scrimmage, first-down line, red zone and down-and-distance tag, with a drive chart; in the badge and the compact card too. [The situation](./docs/situation.md).
94
+ - **Lineups.** A `lineup` layer places a team's players by position code (`GK`, `CB`, `HK`, `C`) with no coordinates: it reads where each stands from a formation (36 of them, in 13 sports), draws a disc or a jersey with the shirt number and the name under it, a captain's C and a flag, and puts the visitors at the other end. [Lineups](./docs/team-shape.md#lineups).
95
+ - **A pageant.** Each team's flag or crest laid large over its half of the surface: `pageant: "carpet"` (a flag at its own ratio with a border) or `"stage"` (a crest on a round disc with a gold ring), an emblem or a trophy in the centre circle, the score in the middle or in the strip, with an entrance in CSS for readers who have not asked for less motion. [The pageant](./docs/pageant.md).
96
+ - **Trophies, medals, balls and crests, by id.** `useEmblems(renderEmblem)` plugs in the sister package [Dougu Sports](https://johnmorrisdotca.github.io/dougu-sports/) in one line, and an id goes wherever a logo, a flag or a mark is taken: `logo: "crest:shield"`, `pageant: { emblem: "trophy:hockey-cup" }`, `"ball:puck"`. Kyogijo bundles no emblem and keeps zero dependencies. [Emblems](./docs/emblems.md).
97
+ - **Country flags, by code.** `useFlags(render)` plugs in the sister package [Hata](https://github.com/johnmorrisdotca/hata) through a small adapter, and a team's `flag: "CA"` is its flag: round in a round mark, at its own proportions on a banner, a badge or a carpet. The same adapter serves [Taisen Brackets](https://github.com/johnmorrisdotca/taisen-brackets). Kyogijo bundles no flag and keeps zero dependencies. [Flags](./docs/flags.md).
98
98
  - **Zoom to a part of a surface.** `range: "offensive-zone"` crops a drawing to a rink's zone, a half court, the paint, the red zone, a soccer half, third or penalty area, the infield, a curling house, or a box of your own in any frame, with its data, heat, labels and score following. [Display ranges](./docs/ranges.md).
99
99
  - **Wide, tall or fit the box.** Every surface lies wide or tall: tall turns it a quarter, keeps every word upright and puts the teams top and bottom. With `orientation: "auto"` the browser helpers follow the box.
100
- - **Where a point lies, by the rules, and a click as a place.** `zoneAt("nhl", [69, 22], { frame: "nhl-api" })` names the zone and the area in it from the rink's, court's, pitch's, park's or field's own figures, with `isThreePointer`, `inSlot`, `inPenaltyArea`, `isFair`, `overWall`, `isHomeRun` and the like; `clientPointToField(svg, x, y, { provider })` is a pointer's place in any of the frames, with the zone, and `fieldPointToClient` the way back. `marks: true` writes `data-mark-id`, `data-index` and `data-zone` on every mark of a layer, for one listener to link it to a table. [Zones](./docs/zones.md), [pointing](./docs/pointer.md).
101
- - **Goals, the strike zone and the stumps, end-on.** A goal mouth for hockey (6 by 4 feet), soccer, handball, water polo, lacrosse (6 by 6) and field hockey with the shots laid on it and its zones (the top shelf, the five-hole, the corners), the catcher's view of the strike zone with its nine and thirteen zones and the pitches numbered, and the wicket from the bowler's end with its line and length zones. [Targets](./docs/targets.md).
102
- - **Data on the surface.** Points, lines, arrows, text, heat maps, hexagons, zone charts and density contours over any surface, in its feet or a data provider's frame, cut to its outline, with a table for each layer; ready-made shot and pass charts (the road to a tournament's final moved to [`@johnmorrisdotca/taisen-brackets`](https://www.npmjs.com/package/@johnmorrisdotca/taisen-brackets), its `/road` entry). [Data layers](./docs/layers.md), [charts](./docs/charts.md).
100
+ - **Where a point lies, by the rules, and a click as a place.** `zoneAt("nhl", [69, 22], { frame: "nhl-api" })` names the zone and the area in it from the surface's own figures, with `isThreePointer`, `inSlot`, `isFair`, `isHomeRun` and the like; `clientPointToField(svg, x, y, { provider })` is a pointer's place in any of the frames, with the zone. `marks: true` writes `data-mark-id` and `data-zone` on every mark of a layer. [Zones](./docs/zones.md), [pointing](./docs/pointer.md).
101
+ - **Goals, the strike zone and the stumps, end-on.** A goal mouth for hockey, soccer, handball, water polo, lacrosse and field hockey with the shots laid on it and its zones (the top shelf, the five-hole, the corners), the catcher's view of the strike zone with its pitches numbered, and the wicket from the bowler's end with its line and length zones. [Targets](./docs/targets.md).
102
+ - **Data on the surface.** Points, lines, arrows, text, heat maps, hexagons, zone charts and density contours over any surface, in its feet or a data provider's frame, cut to its outline, with a table for each layer; ready-made shot and pass charts. [Data layers](./docs/layers.md), [charts](./docs/charts.md).
103
103
  - **Small multiples and backgrounds.** `drawGrid` puts many drawings in one SVG, under a title and a subtitle, over a credit line, with a key they share, in a size for a social card, a square post or a phone; `background` lays a quiet pattern (felt, grass, wood, ice, paper, a grid, dots) or a photograph behind a drawing or a grid. [Layouts and backgrounds](./docs/layouts.md).
104
104
  - **Tournament layouts.** Several surfaces laid out together for one large event, to scale, on one floor, as one SVG: thirteen presets from table tennis, pickleball, curling, judo, karate, badminton and tennis, each with its rule and sources, whether it fits a hall, and a real stadium round it. [Tournament layouts](./docs/multi.md).
105
105
  - **Replay.** Tracking data played on any surface: the players with their numbers, the ball and its trail, and the passes, shots and goals as they happen, with controls, the keyboard and a live region, steps for a reader who prefers reduced motion, and a pause when the page is hidden. [Replay](./docs/replay.md).
106
- - **A 3D view.** Any surface as a floor that turns by mouse, touch, wheel or keys, with four viewpoints (overhead, broadcast, end-on, low corner), and what the rulebook stands on it at its height: nets, antennae, baskets, rink boards and glass, goals, posts, stumps, a ballpark's fence, the walls of padel and squash, a pool's blocks, hurdles; and the venue round it (raked stands, roofs on request, a dojo's walls and gallery). Plain DOM and CSS over the same SVG. [The 3D view](./docs/turn.md).
106
+ - **A 3D view.** Any surface as a floor that turns by mouse, touch, wheel or keys, with four viewpoints (overhead, broadcast, end-on, low corner), and what the rulebook stands on it at its height (nets, baskets, rink boards and glass, goals, a ballpark's fence, the walls of padel and squash, a pool's blocks, a table tennis area's surrounds); and the venue round it (raked stands, roofs on request, a dojo's walls and gallery). Plain DOM and CSS over the same SVG. [The 3D view](./docs/turn.md).
107
107
  - **Renderers.** The same drawing as plain data (a scene), on a canvas (a browser's or a Node canvas), in a d3 selection, as a mark of an Observable Plot, as GeoJSON and as a PNG in Node, each an entry of its own that imports nothing from outside the package. [Renderers](./docs/renderers.md).
108
- - **Units and frames.** Each surface in its rulebook's unit and any other, and 48 coordinate frames (ten soccer providers, the NHL API, HockeyTech, NBA stats, NFL tracking, MLBAM, Retrosheet, a curling stone's place from the button, a place from the net in each net sport (from the centre of the court in squash, racquetball, roundnet and netball), the centre of each field sport's surface, the middle of the water of each pool, the middle of the athletics stadium and of each skating rink) to move a provider's points onto a surface. [Units](./docs/units.md), [coordinate frames](./docs/frames.md).
108
+ - **Units and frames.** Each surface in its rulebook's unit and any other, and 48 coordinate frames (ten soccer providers, the NHL API, HockeyTech, NBA stats, NFL tracking, MLBAM, Retrosheet, and a place from the net, the centre or the middle of the water of each other surface) to move a provider's points onto a surface. [Units](./docs/units.md), [coordinate frames](./docs/frames.md).
109
109
  - **A compact badge and a compact card.** One strip for a list, or the surface with that strip under it.
110
110
  - **Light and dark**, by the device or the page, and every colour a custom property; eight named looks, colours by feature, line widths and dashes, goal styles and mowing patterns, and arrows, axes and labels for teaching. [Theming](./docs/theming.md), [annotations](./docs/annotations.md).
111
111
  - **Accessible.** Each drawing is an `img` with a title and a description that say the surface and the result, in English or Japanese. [Accessibility](./docs/accessibility.md).
@@ -533,6 +533,46 @@ console.log(layout.needs.length, layout.needs.width);
533
533
 
534
534
  The [demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/halls.html) picks a preset, sets it in a hall, switches the stadium on and off, and turns it in 3D.
535
535
 
536
+ ### A table tennis area
537
+
538
+ 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)).
539
+
540
+ <p align="center">
541
+ <picture>
542
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-desk-dark.webp">
543
+ <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">
544
+ </picture>
545
+ <br><em>The table in its playing area.</em>
546
+ </p>
547
+
548
+ <p align="center">
549
+ <picture>
550
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/tabletennis-area-3d-desk-dark.webp">
551
+ <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">
552
+ </picture>
553
+ <br><em>The same area in 3D.</em>
554
+ </p>
555
+
556
+ ### A judo kata area
557
+
558
+ `budo: { style: "kata" }` is the IJF's kata area: a tape at the centre and one 3 m either side, and with `officials` the five judges and the coach's chair ([the budō page](./docs/budo.md#the-kata-area)).
559
+
560
+ <p align="center">
561
+ <picture>
562
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-desk-dark.webp">
563
+ <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">
564
+ </picture>
565
+ <br><em>The kata area, its judges and the coach's chair.</em>
566
+ </p>
567
+
568
+ <p align="center">
569
+ <picture>
570
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judo-kata-tapes-desk-dark.webp">
571
+ <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">
572
+ </picture>
573
+ <br><em>Close up: the centre tape and the two starting tapes.</em>
574
+ </p>
575
+
536
576
  ### The element in a page
537
577
 
538
578
  ```html
@@ -37,6 +37,12 @@ export interface JudoLayout {
37
37
  marks: MMark[];
38
38
  /** The width of the red band of a danger zone, or 0. */
39
39
  danger: number;
40
+ /** A kata area's officials: the judges' tables (ours: the length of the seats), the five judges' seats of each and the coach's chairs, each as the rule places them; empty for a contest area. */
41
+ judges: {
42
+ tables: MBox[];
43
+ seats: MBox[];
44
+ coaches: MBox[];
45
+ };
40
46
  }
41
47
  /** The mats of a `tatami` option, or the league's own when none. @throws Error for a name that is not a size of mat. */
42
48
  export declare function matOf(asked: BudoStyle["tatami"], fig: JudoFigures): MatSize;
package/dist/budo/judo.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { boxD, rectArea, tapeBar, within } from "./kit.js";
2
- import { emptyParts, grow, union } from "./parts.js";
2
+ import { boxOf, emptyParts, grow, union } from "./parts.js";
3
3
  import { lay, matCount, seams, tatamiAreas, tint } from "./tatami.js";
4
4
  /** The mats a `tatami` option names. */
5
5
  export const TATAMI = Object.freeze({ "2x1": { length: 2, width: 1 }, "1x1": { length: 1, width: 1 }, "182x91": { length: 1.82, width: 0.91 } });
@@ -36,6 +36,8 @@ export const contestOf = (asked, fig) => (asked === undefined ? fig.contest.draw
36
36
  export const safetyOf = (asked, fig) => (asked === undefined ? fig.safety.drawn : within(asked, fig.safety.min, fig.safety.max, "The safety area", "the IJF's Sport and Organisation Rules (8.7)"));
37
37
  /** The technical table: 3 m by 0.8 m (ours: the rules give no size), centred on its contest area and set well outside the free zone. */
38
38
  const TABLE = { length: 3, depth: 0.8, clear: 1.5 };
39
+ /** A kata area's chairs: 0.5 m square (ours: no rule gives a chair's size), the judges' table 0.8 m deep (ours) and the gap between a table and the free zone round the area, as for the technical table. */
40
+ const CHAIR = 0.5;
39
41
  /** The plan of the competition area the options ask for. */
40
42
  export function judoParts(o) {
41
43
  const { fig, contest: c, safety: s, across, down, mat } = o;
@@ -73,9 +75,34 @@ export function judoParts(o) {
73
75
  parts.shapes.push(tapeBar(`start-${k + 1}-left`, "start-left", `ky-bud-${o.tape[1]}-fill`, at(cx - m.fromCentre)));
74
76
  });
75
77
  }
78
+ // A kata area's centre: a strip of tape of the same colour as the two starting tapes (SOR B1.8).
79
+ if (o.start && fig.centre !== null) {
80
+ const m = fig.centre;
81
+ contests.forEach((b, k) => parts.shapes.push(tapeBar(`centre-${k + 1}`, "centre-tape", "ky-bud-white-fill", boxOf((b.x0 + b.x1) / 2, (b.y0 + b.y1) / 2, m.width, m.length))));
82
+ }
76
83
  // The technical table of each area, outside the free zone, at the top.
77
84
  const desks = [];
78
- if (o.officials) {
85
+ const judges = { tables: [], seats: [], coaches: [] };
86
+ if (o.officials && fig.panel !== null) {
87
+ // A kata area has no technical table: the five judges sit on the shomen side (the top), `apart` metres from each other, the central one at the centre of the area, and the coach's chair is at the centre on the
88
+ // opposite side (SOR, Kata judges and B1.10). The table and the chairs are drawn outside the free zone.
89
+ const { count, apart } = fig.panel;
90
+ const lastRow = contests.filter((_, k) => k >= (down - 1) * across);
91
+ for (const b of contests.filter((_, k) => k < across)) {
92
+ const cx = (b.x0 + b.x1) / 2;
93
+ const y1 = free.y0 - TABLE.clear;
94
+ const table = { x0: cx - (count * apart) / 2, y0: y1 - TABLE.depth, x1: cx + (count * apart) / 2, y1 };
95
+ judges.tables.push(table);
96
+ for (let i = 0; i < count; i += 1)
97
+ judges.seats.push(boxOf(cx + (i - (count - 1) / 2) * apart, table.y0 - 0.1 - CHAIR / 2, CHAIR, CHAIR));
98
+ }
99
+ for (const b of lastRow)
100
+ judges.coaches.push(boxOf((b.x0 + b.x1) / 2, free.y1 + TABLE.clear + CHAIR / 2, CHAIR, CHAIR));
101
+ judges.tables.forEach((d, k) => parts.areas.push(rectArea(`judges-table-${k + 1}`, "officials", "ky-bud-desk", d)));
102
+ judges.seats.forEach((d, k) => parts.areas.push(rectArea(`judge-${k + 1}`, "judges-seat", "ky-bud-seat", d)));
103
+ judges.coaches.forEach((d, k) => parts.areas.push(rectArea(`coach-${k + 1}`, "coach-chair", "ky-bud-seat", d)));
104
+ }
105
+ else if (o.officials) {
79
106
  for (const b of contests.filter((_, k) => k < across)) {
80
107
  const cx = (b.x0 + b.x1) / 2;
81
108
  const y1 = free.y0 - TABLE.clear;
@@ -94,15 +121,25 @@ export function judoParts(o) {
94
121
  if (across > 1)
95
122
  marks.push({ id: "common-safe-zone", x: first.x1 + gap / 2, y: (first.y0 + first.y1) / 2, side: "bottom" });
96
123
  marks.push({ id: "free-zone", x: width * 0.12, y: free.y0 + fig.freeZone / 2, side: "top" });
97
- // The referee stands at the middle of the tatami, 2 m back from the line the contestants start on, facing the table (Article 9).
98
- marks.push({ id: "referee-position", x: fx, y: (first.y0 + first.y1) / 2 + 2, side: "bottom" });
124
+ // The referee stands at the middle of the tatami, 2 m back from the line the contestants start on, facing the table (Article 9). A kata area has none.
125
+ if (!fig.kata)
126
+ marks.push({ id: "referee-position", x: fx, y: (first.y0 + first.y1) / 2 + 2, side: "bottom" });
127
+ if (fig.centre !== null && o.start)
128
+ marks.push({ id: "centre-tape", x: fx, y: (first.y0 + first.y1) / 2, side: "top" });
129
+ // The pair enters and leaves the mat on the side opposite the shomen (SOR, Kata judges): the bottom of the plan.
130
+ if (fig.kata)
131
+ marks.push({ id: "kata-entry", x: first.x0 + c * 0.2, y: first.y1 - 0.5, side: "bottom" });
99
132
  marks.push({ id: "tatami", x: first.x0 + 0.5 * c, y: first.y0 + c * 0.7, side: "bottom" });
100
133
  if (platform !== undefined)
101
134
  marks.push({ id: "platform", x: width * 0.88, y: platform.y1 - fig.platform / 2, side: "bottom" });
102
135
  if (desks.length > 0)
103
136
  marks.push({ id: "technical-table", x: (desks[0].x0 + desks[0].x1) / 2, y: desks[0].y0 + TABLE.depth / 2, side: "top" });
104
- const extent = union([free, ...desks.map((d) => grow(d, 0.3))]);
105
- return { parts, tatami, extent, contests, gap, pieces, mat, marks, danger: band };
137
+ if (judges.tables.length > 0)
138
+ marks.push({ id: "kata-judges", x: (judges.tables[0].x0 + judges.tables[0].x1) / 2, y: judges.tables[0].y0 + TABLE.depth / 2, side: "top" });
139
+ if (judges.coaches.length > 0)
140
+ marks.push({ id: "coach-chair", x: (judges.coaches[0].x0 + judges.coaches[0].x1) / 2, y: (judges.coaches[0].y0 + judges.coaches[0].y1) / 2, side: "bottom" });
141
+ const extent = union([free, ...desks.map((d) => grow(d, 0.3)), ...[...judges.tables, ...judges.seats, ...judges.coaches].map((d) => grow(d, 0.3))]);
142
+ return { parts, tatami, extent, contests, gap, pieces, mat, marks, danger: band, judges };
106
143
  }
107
144
  /** The number of mats of the competition area: a half mat counts a half. */
108
145
  export const mats = (layout, mat) => Math.round(matCount(layout.pieces, mat) * 100) / 100;
package/dist/budo/plan.js CHANGED
@@ -17,11 +17,22 @@ export const BUDO_SPORTS = Object.freeze(["judo", "karate", "kendo", "sumo", "ky
17
17
  /** True for a sport of the budō family. */
18
18
  export const isBudoSport = (sport) => BUDO_SPORTS.includes(sport);
19
19
  const OWN = { judo: JUDO_LEAGUES, karate: KARATE_LEAGUES, kendo: KENDO_LEAGUES, sumo: SUMO_LEAGUES, kyudo: KYUDO_LEAGUES, dojo: DOJO_LEAGUES };
20
+ /** 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
+ function kataOf(style) {
22
+ if (style === undefined)
23
+ return undefined;
24
+ if (style !== "shiai" && style !== "kata")
25
+ throw new TypeError(`budo.style is "shiai" or "kata"`);
26
+ return style === "kata";
27
+ }
20
28
  /** The league a set of options asks for: a rulebook's, else the sport's own. */
21
29
  export function budoLeague(o) {
22
30
  const sport = o.sport;
23
31
  // `budo.range` names a kyūdōjō's kind, and `dojo.standard` a dojo's mats, where `league` does not.
24
- const asked = o.league === undefined || o.league === "" ? (sport === "kyudo" ? o.budo?.range : sport === "dojo" ? dojoSpecOf(o.dojo).standard : undefined) : o.league;
32
+ const kata = sport === "judo" ? kataOf(o.budo?.style) : undefined;
33
+ if (kata !== undefined && o.league !== undefined && o.league !== "" && (o.league === "ijf-kata") !== kata)
34
+ throw new Error(`budo.style "${kata ? "kata" : "shiai"}" does not go with league "${o.league}": a kata area is the league "ijf-kata"`);
35
+ const asked = o.league === undefined || o.league === "" ? (sport === "kyudo" ? o.budo?.range : sport === "dojo" ? dojoSpecOf(o.dojo).standard : kata ? "ijf-kata" : undefined) : o.league;
25
36
  if (asked !== undefined && asked !== "") {
26
37
  const found = OWN[sport].find((l) => l.id === asked);
27
38
  if (found === undefined)
@@ -17,6 +17,8 @@ export interface BudoStyle {
17
17
  };
18
18
  /** Judo, karate and kendo: the arena floor under the surface. `budokan` is the Nippon Budokan's, an octagon 55 m across the flats (the Budokan's leaflet), with the surface in the middle; it must fit. */
19
19
  hall?: "budokan";
20
+ /** 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
+ style?: "shiai" | "kata";
20
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. */
21
23
  size?: number;
22
24
  /** Judo: the width of the safety area, 3 m or more (4 m by default). In metres. */
@@ -27,7 +29,7 @@ export interface BudoStyle {
27
29
  start?: boolean;
28
30
  /** Judo: a solid platform under the tatami, one metre wider and longer than it. Off by default. */
29
31
  platform?: boolean;
30
- /** Judo and karate: the technical table, the officials' seats and the judges, outside the safety area. Off by default. */
32
+ /** Judo and karate: the technical table, the officials' seats and the judges, outside the safety area (a judo kata area: the judges' table with the five judges' seats, and the coach's chair). Off by default. */
31
33
  officials?: boolean;
32
34
  /** Judo: draw the lines of the mats (the seams between them). On by default. */
33
35
  seams?: boolean;
package/dist/court.d.ts CHANGED
@@ -46,6 +46,12 @@ export type Solid = {
46
46
  cx: number;
47
47
  cy: number;
48
48
  r: number;
49
+ } | {
50
+ kind: "chair";
51
+ rect: Rect;
52
+ } | {
53
+ kind: "desk";
54
+ rect: Rect;
49
55
  };
50
56
  /** The looks each sport has, the sport's default first. */
51
57
  export declare const LOOKS: Readonly<Record<NetSport, readonly string[]>>;
@@ -64,6 +70,10 @@ interface Common {
64
70
  zone: FreeZone;
65
71
  /** How the free zone was asked for. */
66
72
  free: "official" | "least" | "none";
73
+ /** Table tennis: the kind of event the playing area is for, and whether its surrounds and its umpires' places are drawn. */
74
+ area: "standard" | "masters" | "wheelchair";
75
+ surrounds: boolean;
76
+ umpires: boolean;
67
77
  /** Tennis: the singles sticks are drawn. */
68
78
  sticks: boolean;
69
79
  }
package/dist/court.js CHANGED
@@ -44,6 +44,7 @@ export const isNetLeague = (league) => isNetSport(league.family);
44
44
  /** True for a sport whose score is a board of sets or games (all of these courts but netball's). */
45
45
  export const hasSetsBoard = (sport) => isNetSport(sport) && BOARD[sport] !== "total";
46
46
  const FREE = ["official", "least", "none"];
47
+ const AREAS = ["standard", "masters", "wheelchair"];
47
48
  /**
48
49
  * The court the options ask for. A `league` picks the rulebook (the sport's own by default; `fivb-beach` for beach volleyball); `net.look` a named look, `net.free`
49
50
  * the free zone, `net.sticks` the tennis singles sticks.
@@ -64,7 +65,12 @@ export function courtFor(o) {
64
65
  const look = asked.look === undefined ? (beach ? "sand" : names[0]) : asked.look;
65
66
  if (!names.includes(look))
66
67
  throw new Error(`Unknown look "${String(look)}" for ${sport}: use one of ${names.join(", ")}`);
67
- const common = { look, free, sticks: sport === "tennis" && asked.sticks === true };
68
+ const area = asked.area === undefined ? (free === "least" ? "masters" : "standard") : AREAS.includes(asked.area) ? asked.area : undefined;
69
+ if (area === undefined)
70
+ throw new Error(`Unknown area "${String(asked.area)}": use one of ${AREAS.join(", ")}`);
71
+ if (asked.area !== undefined && sport !== "tabletennis")
72
+ throw new Error(`net.area is for table tennis, not "${sport}"`);
73
+ const common = { look, free, sticks: sport === "tennis" && asked.sticks === true, area, surrounds: asked.surrounds !== false, umpires: asked.umpires !== false };
68
74
  const zoneOf = (zones) => (free === "none" ? null : zones[free]);
69
75
  switch (sport) {
70
76
  case "tennis": {
@@ -90,7 +96,10 @@ export function courtFor(o) {
90
96
  case "tabletennis": {
91
97
  const league = picked?.family === "tabletennis" ? picked : TABLE_TENNIS_LEAGUES[0];
92
98
  const figures = netOf(league);
93
- return { ...common, sport, league, figures, zone: zoneOf(figures.free) };
99
+ // The playing area is the space of the event's kind, the table centred in it; `free` says whether one is drawn at all.
100
+ const space = figures.space[area];
101
+ const zone = free === "none" ? null : { side: (space.width - figures.width) / 2, end: (space.length - figures.length) / 2 };
102
+ return { ...common, sport, league, figures, zone };
94
103
  }
95
104
  case "padel": {
96
105
  const league = picked?.family === "padel" ? picked : PADEL_LEAGUES[0];
@@ -151,7 +160,7 @@ export function courtGeometry(plan) {
151
160
  case "pickleball":
152
161
  return pickleballGeometry(plan.figures);
153
162
  case "tabletennis":
154
- return tableGeometry(plan.figures);
163
+ return tableGeometry(plan.figures, plan);
155
164
  case "padel":
156
165
  return padelGeometry(plan.figures);
157
166
  case "squash":
@@ -304,11 +313,30 @@ function pickleballGeometry(f) {
304
313
  ...none(),
305
314
  };
306
315
  }
307
- function tableGeometry(f) {
316
+ function tableGeometry(f, plan) {
308
317
  const { length: L, width: W, edgeLine: e, centreLine: c } = f;
309
318
  const cx = L / 2;
310
319
  const out = f.net.postOutside;
311
320
  const post = Math.max(feet(0.03), L * 0.01);
321
+ const bands = [];
322
+ const solids = [];
323
+ const zone = plan.zone;
324
+ if (zone !== null) {
325
+ // The playing area is enclosed by surrounds, which are part of it (3.2.3.2 and 3.2.3.3): four boards along the edge of the space, inside it.
326
+ if (plan.surrounds) {
327
+ const t = f.surround.thickness;
328
+ const [x0, y0, x1, y1] = [-zone.end, -zone.side, L + zone.end, W + zone.side];
329
+ bands.push({ kind: "wall", rect: [x0, y0, x1, y0 + t] }, { kind: "wall", rect: [x0, y1 - t, x1, y1] }, { kind: "wall", rect: [x0, y0 + t, x0 + t, y1 - t] }, { kind: "wall", rect: [x1 - t, y0 + t, x1, y1 - t] });
330
+ }
331
+ // The umpire sits or stands in line with the net and the assistant umpire directly facing him or her at the other side of the table (3.3.2.2); the umpires' table is beside the umpire's chair.
332
+ if (plan.umpires) {
333
+ const half = f.umpires.chair / 2;
334
+ const { length: dl, width: dw } = f.umpires.desk;
335
+ solids.push({ kind: "chair", rect: [cx - half, -f.umpires.away - half, cx + half, -f.umpires.away + half] });
336
+ solids.push({ kind: "chair", rect: [cx - half, W + f.umpires.away - half, cx + half, W + f.umpires.away + half] });
337
+ solids.push({ kind: "desk", rect: [cx + half + dl * 0.1, -f.umpires.away - dw / 2, cx + half + dl * 1.1, -f.umpires.away + dw / 2] });
338
+ }
339
+ }
312
340
  return {
313
341
  length: L,
314
342
  width: W,
@@ -321,6 +349,8 @@ function tableGeometry(f) {
321
349
  antennae: [],
322
350
  sticks: [],
323
351
  ...none(),
352
+ bands,
353
+ solids,
324
354
  };
325
355
  }
326
356
  /** How far the court's fixtures reach beyond its side lines: the net's posts and the antennae's marks. */
@@ -18,6 +18,8 @@ interface Look {
18
18
  tin?: string;
19
19
  table?: string;
20
20
  setnet?: string;
21
+ /** The umpires' chairs of a table tennis area. */
22
+ chair?: string;
21
23
  }
22
24
  interface Pair {
23
25
  light: Look;
@@ -2,6 +2,7 @@ const WALL = { light: { wall: "#4f575f", mesh: "#98a1a9" }, dark: { wall: "#8b94
2
2
  // World Squash's specification asks that court markings contrast with the surfaces beside them (5.01.01) and lets them be any colour that does (10.13), the walls' lines
3
3
  // differing from the floor's if need be. Red is the colour squash lines are painted in: ours, a red dark enough on the light floor and pale enough on the dark one to read at 3:1.
4
4
  const FACE = { light: { face: "#efeadf", tin: "#c3c8cd", wallLine: "#a8201a" }, dark: { face: "#454b52", tin: "#34393f", wallLine: "#ffb3a8" } };
5
+ const TT = { light: { wall: "#161a1f", table: "#d8d4c8", chair: "#ece7da" }, dark: { wall: "#05070a", table: "#8f8b80", chair: "#b9b4a6" } };
5
6
  const NET = { light: "#20262b", dark: "#e6e9ec" };
6
7
  /** The looks of every sport, each in light and dark. */
7
8
  export const COURT_LOOKS = {
@@ -26,9 +27,10 @@ export const COURT_LOOKS = {
26
27
  green: { light: { surface: "#4f9a5c", free: "#3a7a47", apron: "#cdd6c8", area: "#82c28d" }, dark: { surface: "#386f43", free: "#2b5835", apron: "#1a1c1b", area: "#4e8c59" } },
27
28
  red: { light: { surface: "#b5503a", free: "#8f3b2b", apron: "#d6cdc4", area: "#d27a62" }, dark: { surface: "#8a3d2c", free: "#6c2e21", apron: "#1a1c1b", area: "#a8523d" } },
28
29
  },
30
+ // The surrounds are of one dark colour (the ITTF's 3.2.3.3), and the floor is not light (3.2.3.8): both looks are dark blues and greens. The surrounds, the umpires' table and the chairs are ours.
29
31
  tabletennis: {
30
- blue: { light: { surface: "#1f4f8f", free: "#1f4f8f", apron: "#cfd6df" }, dark: { surface: "#1a4079", free: "#1a4079", apron: "#1a1d21" } },
31
- green: { light: { surface: "#2f6f4f", free: "#2f6f4f", apron: "#cdd6c8" }, dark: { surface: "#25583f", free: "#25583f", apron: "#1a1d1b" } },
32
+ blue: { light: { surface: "#1f4f8f", free: "#1f4f8f", apron: "#cfd6df", ...TT.light }, dark: { surface: "#1a4079", free: "#1a4079", apron: "#1a1d21", ...TT.dark } },
33
+ green: { light: { surface: "#2f6f4f", free: "#2f6f4f", apron: "#cdd6c8", ...TT.light }, dark: { surface: "#25583f", free: "#25583f", apron: "#1a1d1b", ...TT.dark } },
32
34
  },
33
35
  padel: {
34
36
  blue: { light: { surface: "#2f6fb5", free: "#2f6fb5", apron: "#d3d9d0", ...WALL.light }, dark: { surface: "#2a5a93", free: "#2a5a93", apron: "#1a1c1b", ...WALL.dark } },
@@ -64,7 +66,7 @@ export const COURT_LOOKS = {
64
66
  green: { light: { surface: "#4d9468", free: "#3c7852", apron: "#dcd8c9" }, dark: { surface: "#35704b", free: "#285539", apron: "#1b1f1c" } },
65
67
  },
66
68
  };
67
- const OPTIONAL = [["area", "--ky-area"], ["line", "--ky-line"], ["wallLine", "--ky-wall-line"], ["wall", "--ky-wall"], ["mesh", "--ky-mesh"], ["face", "--ky-face"], ["tin", "--ky-tin"], ["table", "--ky-table"], ["setnet", "--ky-setnet"]];
69
+ const OPTIONAL = [["area", "--ky-area"], ["line", "--ky-line"], ["wallLine", "--ky-wall-line"], ["wall", "--ky-wall"], ["mesh", "--ky-mesh"], ["face", "--ky-face"], ["tin", "--ky-tin"], ["table", "--ky-table"], ["setnet", "--ky-setnet"], ["chair", "--ky-chair"]];
68
70
  const vars = (look, net) => `--ky-surface:${look.surface};--ky-free:${look.free};--ky-apron:${look.apron};--ky-net:${net}${OPTIONAL.map(([key, name]) => (look[key] === undefined ? "" : `;${name}:${look[key]}`)).join("")}`;
69
71
  /** The three rules of one look: light, dark by the device's setting, and dark by the page's `data-theme`. */
70
72
  function lookRules(sport, name) {
@@ -92,6 +94,7 @@ const FIXTURES = `
92
94
  .kyogijo .ky-tin{fill:var(--ky-tin)}
93
95
  .kyogijo .ky-wall-line{fill:var(--ky-wall-line,var(--ky-line))}
94
96
  .kyogijo .ky-table{fill:var(--ky-table)}
97
+ .kyogijo .ky-chair{fill:var(--ky-chair)}
95
98
  .kyogijo .ky-set-net{fill:var(--ky-setnet)}`.trim();
96
99
  /** The rules of a drawing of this sport in this look: the shared rules, the rules of the fixtures its sport has, and the look's colours. */
97
100
  export function courtStyle(sport, look) {
package/dist/draw.js CHANGED
@@ -1069,7 +1069,7 @@ function drawnKyogijo(o) {
1069
1069
  role: "img",
1070
1070
  "aria-labelledby": `${titleId} ${descId}`,
1071
1071
  "data-sport": sport,
1072
- "data-league": o.league === undefined || o.league === "" ? undefined : o.league,
1072
+ "data-league": o.league === undefined || o.league === "" ? (chooseLeague(o).league?.id ?? undefined) : o.league,
1073
1073
  "data-range": isScore(o) && sport === "curling" ? undefined : rangeLabel(o),
1074
1074
  "data-event": o.host !== undefined && typeof o.event === "string" ? safeClass(o.event) : undefined,
1075
1075
  "data-look": look ?? budoLook,
package/dist/features.js CHANGED
@@ -292,6 +292,8 @@ const NET = [
292
292
  const APRON_COURT = [f("apron", "--ky-apron", "The ground round the court."), f("court", "--ky-surface", "The court."), f("lines", LINE, "The painted lines.")];
293
293
  /** A padel court: the net's features, and the walls and the metal fence of its enclosure. */
294
294
  const PADEL = [...NET, f("walls", "--ky-wall", "The walls of the enclosure."), f("fence", "--ky-mesh", "The metal fence between the walls.")];
295
+ /** A table tennis area: the net's features, the surrounds round the playing area, the umpires' table and their chairs. */
296
+ const TABLE_TENNIS = [...NET, f("walls", "--ky-wall", "The surrounds that enclose the playing area."), f("umpires-table", "--ky-table", "The umpires' table."), f("chairs", "--ky-chair", "The umpire's chair and the assistant umpire's.")];
295
297
  /** A squash court: no net, walls round it, and its front wall laid flat beside it, with its tin. */
296
298
  const SQUASH = [...APRON_COURT, f("walls", "--ky-wall", "The walls round the court."), f("wall-face", "--ky-face", "The front wall, laid flat beside the court."), f("tin", "--ky-tin", "The tin, on the front wall."), f("wall-lines", "--ky-wall-line", "The lines on the front wall.")];
297
299
  /** A racquetball court: no net, and walls round it. */
@@ -410,7 +412,7 @@ export const FEATURE_INFO = Object.freeze({
410
412
  volleyball: NET,
411
413
  badminton: NET,
412
414
  pickleball: NET,
413
- tabletennis: NET,
415
+ tabletennis: TABLE_TENNIS,
414
416
  padel: PADEL,
415
417
  squash: SQUASH,
416
418
  racquetball: RACQUETBALL,
@@ -32,6 +32,16 @@ export interface JudoFigures {
32
32
  danger: boolean;
33
33
  /** A kata area: two white tapes 5 cm by 50 cm, 3 m from the centre either side, and a safety area of 2 m (SOR B1.8). */
34
34
  kata: boolean;
35
+ /** A kata area: the strip of tape of the same colour at the centre of the area, 5 cm by 50 cm (SOR B1.8; the Kata Commission's Tournament Handbook 2024). None in a contest area. */
36
+ centre: {
37
+ width: number;
38
+ length: number;
39
+ } | null;
40
+ /** A kata area: the panel of judges, `count` of them on the shomen side, sitting about `apart` metres from each other, the central one at the centre of the area (SOR, Kata judges and the system of judging). None in a contest area. */
41
+ panel: {
42
+ count: number;
43
+ apart: number;
44
+ } | null;
35
45
  }
36
46
  /** The figures of a karate competition area, in metres. */
37
47
  export interface KarateFigures {
@@ -171,7 +181,10 @@ export type BudoLeague = JudoLeague | KarateLeague | KendoLeague | SumoLeague |
171
181
  export declare const IJF_CONTEST: JudoFigures;
172
182
  /** The All Japan Judo Federation's: the tatami of 182 cm by 91 cm, the red tatami of the danger zone, and the red and white tape 4 m (2 ken) apart (Tournament Management Guidebook 2023, 2-1, 2-3, 2-4). */
173
183
  export declare const AJJF_CONTEST: JudoFigures;
174
- /** The kata area (SOR B1.8): 10 m for the World Championships and 8 m for other events, a safety area of 2 m, two white tapes of 5 cm by 50 cm, 3 m from the centre. */
184
+ /**
185
+ * The kata area (SOR 21 January 2026, B1.8; the Kata Commission's Tournament Handbook 2024): 10 m for the World Championships and 8 m for other events, a safety area of at least 2 m between and round the
186
+ * areas, two white tapes of 5 cm by 50 cm 3 m from the centre either side (the 6 m line), a tape of the same colour at the centre, five judges on the shomen side 1.25 m apart with the central judge at the centre.
187
+ */
175
188
  export declare const IJF_KATA: JudoFigures;
176
189
  /** The WKF's kumite area (Kumite Competition Rules 2026.01, Article 1). */
177
190
  export declare const WKF_KUMITE: KarateFigures;
@@ -13,6 +13,8 @@ export const IJF_CONTEST = {
13
13
  marks: { width: 0.1, length: 0.5, fromCentre: 2 },
14
14
  danger: false,
15
15
  kata: false,
16
+ centre: null,
17
+ panel: null,
16
18
  };
17
19
  /** The All Japan Judo Federation's: the tatami of 182 cm by 91 cm, the red tatami of the danger zone, and the red and white tape 4 m (2 ken) apart (Tournament Management Guidebook 2023, 2-1, 2-3, 2-4). */
18
20
  export const AJJF_CONTEST = {
@@ -22,13 +24,19 @@ export const AJJF_CONTEST = {
22
24
  tatami: { length: 1.82, width: 0.91, thickness: 0.06 },
23
25
  danger: true,
24
26
  };
25
- /** The kata area (SOR B1.8): 10 m for the World Championships and 8 m for other events, a safety area of 2 m, two white tapes of 5 cm by 50 cm, 3 m from the centre. */
27
+ /**
28
+ * The kata area (SOR 21 January 2026, B1.8; the Kata Commission's Tournament Handbook 2024): 10 m for the World Championships and 8 m for other events, a safety area of at least 2 m between and round the
29
+ * areas, two white tapes of 5 cm by 50 cm 3 m from the centre either side (the 6 m line), a tape of the same colour at the centre, five judges on the shomen side 1.25 m apart with the central judge at the centre.
30
+ */
26
31
  export const IJF_KATA = {
27
32
  ...IJF_CONTEST,
28
33
  contest: span(8, 10, 10),
29
34
  safety: span(2, 6, 2),
35
+ common: 2,
30
36
  marks: { width: 0.05, length: 0.5, fromCentre: 3 },
31
37
  kata: true,
38
+ centre: { width: 0.05, length: 0.5 },
39
+ panel: { count: 5, apart: 1.25 },
32
40
  };
33
41
  /** The WKF's kumite area (Kumite Competition Rules 2026.01, Article 1). */
34
42
  export const WKF_KUMITE = { kind: "karate", area: 8, band: 1, safety: 2, between: 2, startMats: { size: 1, fromCentre: 1 }, referee: 2, judges: 1.5 };
@@ -82,7 +90,7 @@ const mat = (length, width, en, ja) => ({ kind: "dojo", mat: { length, width },
82
90
  export const JUDO_LEAGUES = [
83
91
  { id: "ijf", family: "judo", name: { en: "IJF contest area", ja: "国際柔道連盟の試合場" }, unit: "m", dimensions: IJF_CONTEST, sources: ["ijf-sor", "ijf-sor-2020"], builtIn: true },
84
92
  { id: "ajjf", family: "judo", name: { en: "AJJF contest area", ja: "全日本柔道連盟の試合場" }, unit: "m", dimensions: AJJF_CONTEST, sources: ["ajjf-guide", "ijf-sor"] },
85
- { id: "ijf-kata", family: "judo", name: { en: "IJF kata area", ja: "国際柔道連盟の形の試合場" }, unit: "m", dimensions: IJF_KATA, sources: ["ijf-sor"] },
93
+ { id: "ijf-kata", family: "judo", name: { en: "IJF kata area", ja: "国際柔道連盟の形の試合場" }, unit: "m", dimensions: IJF_KATA, sources: ["ijf-sor", "ijf-kata-handbook", "ijf-kata-criteria"] },
86
94
  ];
87
95
  export const KARATE_LEAGUES = [
88
96
  { id: "wkf-kumite", family: "karate", name: { en: "WKF kumite area", ja: "世界空手連盟の組手の試合場" }, unit: "m", dimensions: WKF_KUMITE, sources: ["wkf-kumite"], builtIn: true },
@@ -143,8 +143,40 @@ export interface TableTennisFigures {
143
143
  /** The outside limits of a post are this far outside the side line. */
144
144
  postOutside: number;
145
145
  };
146
+ /**
147
+ * The playing space of each kind of event (the ITTF's Regulations for International Competitions, 3.2.3.1): a cuboid of at least 14 m by 7 m by 5 m; for wheelchair events it may be reduced to
148
+ * 8 m by 6 m, and for Masters events to 10 m by 5 m. The height is the cuboid's, which no reduction touches.
149
+ */
150
+ space: {
151
+ standard: PlayingSpace;
152
+ masters: PlayingSpace;
153
+ wheelchair: PlayingSpace;
154
+ };
155
+ /** The surrounds that enclose the playing area: 50 cm to 100 cm high, all of one dark colour, the corners may be left open (3.2.3.3); `drawn` is the height drawn and `thickness` the board's, both ours within the rule. */
156
+ surround: {
157
+ least: number;
158
+ greatest: number;
159
+ drawn: number;
160
+ thickness: number;
161
+ };
162
+ /** The umpires' chairs and table, part of the playing area (3.2.3.2). The umpire sits or stands in line with the net, the assistant umpire directly facing him or her at the other side of the table (3.3.2.2); the sizes and the distance from the table are ours. */
163
+ umpires: {
164
+ chair: number;
165
+ desk: {
166
+ length: number;
167
+ width: number;
168
+ };
169
+ away: number;
170
+ };
171
+ /** The playing area is `standard` for the international events, `masters` the least a Masters event allows (the least for any event but a wheelchair one). The `official` zone is the standard space and the `least` the Masters one. */
146
172
  free: FreeZones;
147
173
  }
174
+ /** A playing space: the cuboid's length, width and height. */
175
+ export interface PlayingSpace {
176
+ length: number;
177
+ width: number;
178
+ height: number;
179
+ }
148
180
  /**
149
181
  * A padel court, from "The court" in the FIP's Rules of Padel (review of application 1 January 2026). Metres. The court is measured inside its walls, so it has
150
182
  * no lines round it: the enclosure is its boundary.
@@ -387,7 +419,8 @@ export declare const USAP_PICKLEBALL: PickleballFigures;
387
419
  /**
388
420
  * The ITTF's table, from Laws 2.1 and 2.2 of the Laws of Table Tennis (the 2026 statutes), in metres: 2.74 by 1.525 m and 76 cm above the floor; a white side line
389
421
  * and end line 2 cm wide, a white centre line 3 mm wide; the net 15.25 cm above the playing surface, hung from posts 15.25 cm high whose outside limits are 15.25 cm
390
- * outside the side lines.
422
+ * outside the side lines. The playing area round it is the ITTF's Regulations for International Competitions, 3.2.3 (ITTF Statutes 2026, chapter 3): a space of 14 m by 7 m by 5 m (8 m by 6 m
423
+ * for wheelchair events, 10 m by 5 m for Masters events), enclosed by surrounds 50 cm to 100 cm high, all of one dark colour, with the umpires' chairs and tables inside it.
391
424
  */
392
425
  export declare const ITTF_TABLE: TableTennisFigures;
393
426
  /**
@@ -88,7 +88,8 @@ export const USAP_PICKLEBALL = {
88
88
  /**
89
89
  * The ITTF's table, from Laws 2.1 and 2.2 of the Laws of Table Tennis (the 2026 statutes), in metres: 2.74 by 1.525 m and 76 cm above the floor; a white side line
90
90
  * and end line 2 cm wide, a white centre line 3 mm wide; the net 15.25 cm above the playing surface, hung from posts 15.25 cm high whose outside limits are 15.25 cm
91
- * outside the side lines.
91
+ * outside the side lines. The playing area round it is the ITTF's Regulations for International Competitions, 3.2.3 (ITTF Statutes 2026, chapter 3): a space of 14 m by 7 m by 5 m (8 m by 6 m
92
+ * for wheelchair events, 10 m by 5 m for Masters events), enclosed by surrounds 50 cm to 100 cm high, all of one dark colour, with the umpires' chairs and tables inside it.
92
93
  */
93
94
  export const ITTF_TABLE = {
94
95
  length: 2.74,
@@ -97,7 +98,11 @@ export const ITTF_TABLE = {
97
98
  edgeLine: 0.02,
98
99
  centreLine: 0.003,
99
100
  net: { height: 0.1525, postHeight: 0.1525, postOutside: 0.1525 },
100
- free: { official: null, least: null },
101
+ space: { standard: { length: 14, width: 7, height: 5 }, masters: { length: 10, width: 5, height: 5 }, wheelchair: { length: 8, width: 6, height: 5 } },
102
+ surround: { least: 0.5, greatest: 1, drawn: 0.75, thickness: 0.05 },
103
+ umpires: { chair: 0.5, desk: { length: 1.2, width: 0.5 }, away: 0.9 },
104
+ // The table is centred in the space: what lies beyond its end lines and its side lines is half of what the space has over the table.
105
+ free: { official: { side: (7 - 1.525) / 2, end: (14 - 2.74) / 2 }, least: { side: (5 - 1.525) / 2, end: (10 - 2.74) / 2 } },
101
106
  };
102
107
  /**
103
108
  * The World ParaVolley's sitting volleyball court, from Rules 1 and 2 of the Official Sitting Volleyball Rules 2025-2028, in metres: 10 by 6 m inside a free zone of
@@ -105,6 +105,8 @@ export const LEAGUE_SOURCES = Object.freeze(Object.fromEntries([
105
105
  source({ id: "isu-figure-skating-rules", title: "Sports Rules - Figure Skating (Single and Pair Skating and Ice Dance): Rule 342 Required rinks", by: "International Skating Union", edition: "effective 12 June 2026 (dated 10 June 2026)", url: "https://www.isu.org/en/figure-skating-rules/figure-skating-rules", read: READ, note: "the skating area of 60 m by 30 m if possible, not larger and not less than 56 m by 26 m; the judges and the referee at the rinkboard, the technical panel in an elevated position if possible" }),
106
106
  // Budō: judo, karate, kendo, sumo, kyudo and the tatami of a dojo.
107
107
  source({ id: "ijf-sor", title: "Sport and Organisation Rules of the International Judo Federation, 8.7 Field of Play and Competition Area, Article 9 and B1.8 (the kata area)", by: "International Judo Federation", edition: "version of 21 January 2026", url: "https://78884ca60822a34fb0e6-082b8fd5551e97bc65e327988b444396.ssl.cf3.rackcdn.com/up/2026/01/IJF_Sport_and_Organisation_Rul-1769443746.pdf", read: BUDO_READ, note: "the contest and safety areas, the common safe zone, the free zone, the tatami of 1 m by 2 m and its two official colours, the platform; the kata area and its marks" }),
108
+ source({ id: "ijf-kata-handbook", title: "IJF Kata Commission Tournament Handbook, Competition Area", by: "International Judo Federation, Kata Commission", edition: "2024", url: "https://78884ca60822a34fb0e6-082b8fd5551e97bc65e327988b444396.ssl.cf3.rackcdn.com/up/2024/01/2024_IJF_kata_commission_Tourn-1706106608.pdf", read: "2026-10-20", note: "the kata area of 10 m by 10 m (World Championships) or 8 m by 8 m, a common safety area of at least 2 m between areas, the centre tape and the two tapes of the same colour for the starting places of tori and uke, the mats on a resilient floor, a platform of at most 1 m, 5 m clear height" }),
109
+ source({ id: "ijf-kata-criteria", title: "Kata Competition, Criteria for the Evaluation, General elements", by: "International Judo Federation, Kata Commission", edition: "January 2025", url: "https://78884ca60822a34fb0e6-082b8fd5551e97bc65e327988b444396.ssl.cf3.rackcdn.com/up/2025/01/IJF_KATA_EVALUATION_Jan_2025-E-1737012784.pdf", read: "2026-10-20", note: "the opening ceremony at the 8 m or 10 m line, the closing ceremony behind the 6 m line (the line of the two tapes), the pair advancing along the safety zone and leaving where they entered" }),
108
110
  source({ id: "ijf-sor-2020", title: "Sports and Organization Rules of the International Judo Federation, 18.1 Competition Area", by: "International Judo Federation", edition: "2020 (the version of June 2020)", url: "https://78884ca60822a34fb0e6-082b8fd5551e97bc65e327988b444396.ssl.cf3.rackcdn.com/up/2020/06/IJF_Sport_and_Organisation_Rul-1592329624.pdf", read: BUDO_READ, note: "the blue and the white tape, 10 cm by 50 cm, 4 m apart at the centre of the contest area, the blue to the referee's right (the 2026 version no longer marks them)" }),
109
111
  source({ id: "ajjf-guide", title: "大会運営ガイドブック2023 (Tournament Management Guidebook 2023), 2-1 畳, 2-3 試合場 and 2-4 開始線と選手の位置", by: "公益財団法人全日本柔道連盟 (All Japan Judo Federation)", edition: "2023, as of 2023-12-15", url: "https://www.judo.or.jp/cms/wp-content/uploads/2021/03/%E5%A4%A7%E4%BC%9A%E9%81%8B%E5%96%B6%E3%82%AC%E3%82%A4%E3%83%89%E3%83%96%E3%83%83%E3%82%AF2023_20240229.pdf", read: BUDO_READ, note: "the domestic tatami of 182 cm by 91 cm (and the international 200 by 100 cm, and their halves), 5 or 6 cm thick; the red tatami of the danger zone; the red and white tape 4 m (2 ken) apart" }),
110
112
  source({ id: "wkf-kumite", title: "WKF Kumite Competition Rules, Article 1: Kumite Competition Area, and 8.6 (the scale of points)", by: "World Karate Federation", edition: "Rules version 2026.01", url: "https://www.wkf.net/files/pdf/documents/WKF_2026_Kumite_Competition_Rules_MASTER_COPY_V13FINAL.pdf", read: BUDO_READ, note: "the matted square of 8 m measured from the outside, its outer 1 m in another colour, the 2 m safety area, 2 m between areas, the two inverted mats 1 m from the centre, the referee and the judges; yuko 1, waza-ari 2, ippon 3" }),
package/dist/leagues.d.ts CHANGED
@@ -69,7 +69,7 @@ export declare function leagueById(sport: Sport, id: string): League | undefined
69
69
  * // FIBA basketball court cfl
70
70
  * ```
71
71
  */
72
- export declare function chooseLeague(o: Pick<SurfaceOptions, "sport" | "league">): {
72
+ export declare function chooseLeague(o: Pick<SurfaceOptions, "sport" | "league"> & Partial<Pick<SurfaceOptions, "budo">>): {
73
73
  sport: Sport;
74
74
  league: League | undefined;
75
75
  };
package/dist/leagues.js CHANGED
@@ -98,9 +98,11 @@ const figuresUnit = (league) => league.figuresUnit ?? league.unit;
98
98
  * ```
99
99
  */
100
100
  export function chooseLeague(o) {
101
- if (o.league === undefined || o.league === "")
101
+ // A judo area asked for in the kata style is the IJF's kata area when no league is named (`budo.style`).
102
+ const asked = (o.league === undefined || o.league === "") && o.sport === "judo" && o.budo?.style === "kata" ? "ijf-kata" : o.league;
103
+ if (asked === undefined || asked === "")
102
104
  return { sport: o.sport, league: undefined };
103
- const id = String(o.league);
105
+ const id = String(asked);
104
106
  const family = FAMILY_OF[o.sport];
105
107
  if (family === undefined)
106
108
  throw new Error(`"${String(o.sport)}" has no leagues: a soccer pitch is chosen with pitch`);
@@ -1,10 +1,20 @@
1
1
  // The tournament layouts and the halls they are set in, as data. Every figure is a rulebook's or a federation's specification, or a venue's published floor, and says where in `sources`; docs/sources.md
2
2
  // (Tournament layouts) has the rule for each and says plainly what is ours. Nothing here draws.
3
+ import { ITTF_TABLE } from "./leagues/courts.js";
3
4
  import { LEAGUE_SOURCES } from "./leagues/sources.js";
4
5
  const READ = "2026-10-19";
5
6
  const source = (s) => [s.id, Object.freeze(s)];
6
7
  /** The documents a layout's figures were read from, by id, beside the rulebooks of `LEAGUE_SOURCES` (a key of either names a source). */
7
8
  export const MULTI_SOURCES = Object.freeze(Object.fromEntries([
9
+ source({
10
+ id: "ittf-statutes-2026",
11
+ title: "ITTF Statutes 2026 (clean version), chapter 3 Regulations for International Competitions (the Handbook's chapter 3): 3.2.3 Playing Conditions, 3.2.5.5 and 3.3.2.2",
12
+ by: "International Table Tennis Federation",
13
+ edition: "2026 (effective 1 January 2026)",
14
+ url: "https://db.ittf.com/sites/default/files/public/2026-02/2026_Statutes_v1_consolidated_clean.pdf",
15
+ read: "2026-10-20",
16
+ note: "the playing space a cuboid of at least 14 m by 7 m by 5 m, for wheelchair events at least 8 m by 6 m and for Masters events at least 10 m by 5 m (3.2.3.1); the equipment and fittings that are part of each playing area: the table, the flooring, the umpires' tables and chairs, the score indicators, the surrounds (3.2.3.2); surrounds enclosing the playing area, 50 cm to 100 cm high, all of the same dark colour, the corners may be left open (3.2.3.3); the flooring not light-coloured, not brightly reflecting or slippery, resilient, and in World, Olympic and Paralympic title competitions wood or a rollable synthetic material the ITTF authorises (3.2.3.8); up to 6 advertisements on the floor of the playing area (3.2.5.5); the umpire sits or stands in line with the net and the assistant umpire directly facing him or her at the other side of the table (3.3.2.2)",
17
+ }),
8
18
  source({
9
19
  id: "ittf-handbook",
10
20
  title: "The ITTF Handbook 2022, Section 3 Regulations for International Competitions, 3.2.3 Playing Conditions and 3.2.5.5 (advertisements on the floor)",
@@ -12,7 +22,7 @@ export const MULTI_SOURCES = Object.freeze(Object.fromEntries([
12
22
  edition: "2022 (v1.0, clean)",
13
23
  url: "https://documents.ittf.sport/sites/default/files/public/2022-02/ITTF_HB_2022_clean_v1_0.pdf",
14
24
  read: READ,
15
- note: "the playing space at least 14 m by 7 m by 5 m, the four corners may be covered by surrounds of at most 1.5 m (3.2.3.1); each playing area enclosed by surrounds about 75 cm high, of one dark colour, separating it from its neighbours and from the spectators (3.2.3.3); the umpire's table and chair and the score indicators are part of the area (3.2.3.2); up to 6 advertisements on the floor of the area (3.2.5.5). A later edition (the 2024 Statutes) was seen only in a search summary, which says the space may be of any shape within a rectangle of the same size; the 2022 text is the one drawn.",
25
+ note: "(superseded by the 2026 Statutes' chapter 3, which has no 1.5 m for the corners and 50 cm to 100 cm for the surrounds) the playing space at least 14 m by 7 m by 5 m, the four corners may be covered by surrounds of at most 1.5 m (3.2.3.1); each playing area enclosed by surrounds about 75 cm high, of one dark colour, separating it from its neighbours and from the spectators (3.2.3.3); the umpire's table and chair and the score indicators are part of the area (3.2.3.2); up to 6 advertisements on the floor of the area (3.2.5.5). A later edition (the 2024 Statutes) was seen only in a search summary, which says the space may be of any shape within a rectangle of the same size; the 2022 text is the one drawn.",
16
26
  }),
17
27
  source({
18
28
  id: "bwf-facilities",
@@ -193,11 +203,12 @@ export const MULTI_PRESETS = Object.freeze([
193
203
  id: "tabletennis-4",
194
204
  name: { en: "Table tennis, four areas (2 x 2)", ja: "卓球 4面(2×2)" },
195
205
  sport: "tabletennis",
196
- surface: { sport: "tabletennis" },
206
+ // The area is the unit: each bay below is the playing space the table stands centred in (the single surface's own `net.free`), so the layout draws that area itself, with its surrounds and chairs, and the surface only the table.
207
+ surface: { sport: "tabletennis", net: { free: "none" } },
197
208
  kind: "cells",
198
209
  rows: 2,
199
210
  columns: 2,
200
- bay: { length: 14, width: 7, unit: "m" },
211
+ bay: { length: ITTF_TABLE.space.standard.length, width: ITTF_TABLE.space.standard.width, unit: "m" },
201
212
  word: "table",
202
213
  parts: TT_PARTS,
203
214
  floor: "blue",
@@ -211,35 +222,36 @@ export const MULTI_PRESETS = Object.freeze([
211
222
  ja: { name: "世界卓球選手権団体戦ファイナルズ", venue: "OVOアリーナ・ウェンブリー", city: "ロンドン" },
212
223
  floor: "ovo-arena-wembley",
213
224
  note: {
214
- en: "Four playing areas on one blue floor with seats round the perimeter, one scoreboard for all four hung over their middle, barriers and an umpire's chair at each (read from the owner's photographs; no plan was seen). Each area is drawn at the Handbook's least, 14 m by 7 m.",
225
+ en: "Four playing areas on one blue floor with seats round the perimeter, one scoreboard for all four hung over their middle, barriers and an umpire's chair at each (read from the owner's photographs; no plan was seen). Each area is drawn at the Statutes' least, 14 m by 7 m.",
215
226
  ja: "青い床1枚の上に競技エリアが4面あり、周囲に客席があります。4面の中央の真上に共用のスコアボードが1台吊るされ、各エリアにフェンスと審判席があります(提供された写真から読み取りました。図面は見ていません)。各エリアは規則の最小値14m×7mで描いています。",
216
227
  },
217
- sources: ["ittf-london-2026", "ittf-handbook", "ovo-arena"],
228
+ sources: ["ittf-london-2026", "ittf-statutes-2026", "ovo-arena"],
218
229
  },
219
230
  rule: {
220
- en: "ITTF Handbook 2022, 3.2.3.1: a playing space of at least 14 m by 7 m for each area; 3.2.3.3: surrounds about 75 cm high between neighbouring areas and the spectators.",
221
- ja: "ITTFハンドブック2022 3.2.3.1:各エリアは14m×7m以上の競技空間。3.2.3.3:隣のエリアや観客との間に高さ約75cmのフェンス。",
231
+ en: "ITTF Statutes 2026, chapter 3 (the Handbook), 3.2.3.1: a playing space of at least 14 m by 7 m by 5 m for each area; 3.2.3.3: surrounds 50 cm to 100 cm high, all of one dark colour, enclosing each area (75 cm drawn: ours, within the rule).",
232
+ ja: "ITTF規程2026 第3章(ハンドブック)3.2.3.1:各エリアは14m×7m×5m以上の競技空間。3.2.3.3:各エリアを囲むフェンスは高さ50cm〜100cmで、すべて同じ濃い色(描画は75cm:このパッケージの判断で、規則の範囲内)。",
222
233
  },
223
- sources: ["ittf-handbook", "ittf-london-2026", "ovo-arena"],
234
+ sources: ["ittf-statutes-2026", "ittf-london-2026", "ovo-arena"],
224
235
  },
225
236
  {
226
237
  id: "tabletennis-8",
227
238
  name: { en: "Table tennis, eight areas (2 x 4)", ja: "卓球 8面(2×4)" },
228
239
  sport: "tabletennis",
229
- surface: { sport: "tabletennis" },
240
+ // The area is the unit: each bay below is the playing space the table stands centred in (the single surface's own `net.free`), so the layout draws that area itself, with its surrounds and chairs, and the surface only the table.
241
+ surface: { sport: "tabletennis", net: { free: "none" } },
230
242
  kind: "cells",
231
243
  rows: 2,
232
244
  columns: 4,
233
- bay: { length: 14, width: 7, unit: "m" },
245
+ bay: { length: ITTF_TABLE.space.standard.length, width: ITTF_TABLE.space.standard.width, unit: "m" },
234
246
  word: "table",
235
247
  parts: TT_PARTS,
236
248
  floor: "blue",
237
249
  basis: "rulebook",
238
250
  rule: {
239
- en: "ITTF Handbook 2022, 3.2.3.1 and 3.2.3.3: eight areas of 14 m by 7 m, in two rows. No event's count of areas was sourced for this one; it is the grid the rule allows.",
240
- ja: "ITTFハンドブック2022 3.2.3.1と3.2.3.3:14m×7mのエリア8面を2行4列に並べます。特定の大会の面数は確認できていません。規則が認める並べ方です。",
251
+ en: "ITTF Statutes 2026, chapter 3 (the Handbook), 3.2.3.1 and 3.2.3.3: eight areas of 14 m by 7 m, in two rows. No event's count of areas was sourced for this one; it is the grid the rule allows.",
252
+ ja: "ITTF規程2026 第3章(ハンドブック)3.2.3.1と3.2.3.3:14m×7mのエリア8面を2行4列に並べます。特定の大会の面数は確認できていません。規則が認める並べ方です。",
241
253
  },
242
- sources: ["ittf-handbook"],
254
+ sources: ["ittf-statutes-2026"],
243
255
  },
244
256
  {
245
257
  id: "pickleball-2x2",
package/dist/strings.js CHANGED
@@ -382,7 +382,7 @@ export const KYOGIJO_STRINGS = {
382
382
  boundary: "Boundary", pitch: "Pitch", "bowling-crease": "Bowling crease", "popping-crease": "Popping crease", "return-crease": "Return crease", wicket: "Wicket", "thirty-yard-circle": "30-yard circle",
383
383
  "twenty-three-line": "23-metre line", "shooting-circle": "Shooting circle", "penalty-spot": "Penalty spot", "broken-circle-line": "Broken line round the circle", "goal-area-line": "Goal-area line",
384
384
  "finish-line": "Finish line", kerb: "Kerb", "lane-lines": "Lane lines", bend: "Bend", straight: "Straight", "infield-pitch": "Soccer pitch", "start-line": "Start line", breakline: "Breakline", "water-jump": "Water jump", "exchange-zone": "Takeover zone",
385
- "referee-position": "Referee's place", "shiai-jo": "Shiai-jo", floorboard: "Floorboard", heri: "Heri, the mat's edge", "contest-area": "Contest area", "safety-area": "Safety area", "danger-zone": "Danger zone", "start-mark": "Start mark", "common-safe-zone": "Common safe zone", "free-zone": "Free zone", platform: "Platform", "technical-table": "Technical table", arena: "Arena floor", "competition-area": "Competition area", "boundary-band": "Boundary band", "start-mat": "Start mat", judge: "Judge", "between-areas": "Between the areas", "centre-cross": "Centre cross", "kaishi-sen": "Kaishi-sen", "outer-boundary": "Outer boundary", dohyo: "Dohyō", ring: "Ring", tawara: "Tawara", tokudawara: "Tokudawara", "shikiri-sen": "Shikiri-sen", "jano-me": "Jano-me", shajo: "Shajō", "shooting-position": "Shooting position", honza: "Honza", yamichi: "Yamichi", "yatori-michi": "Path for the arrows", azuchi: "Azuchi", mato: "Target", kamiza: "Kamiza", shimoza: "Shimoza", shinzen: "Shinzen", dais: "Dais, the seat of honour", banner: "Banner", entrance: "Entrance", window: "Window", tatami: "Tatami", wall: "Wall", "inner-lane": "Inner lane", "outer-lane": "Outer lane", "crossing-straight": "Crossing straight", "warm-up-lane": "Warm-up lane", padding: "Padding", infield: "Infield", "coaches-area": "Coaches' area", blocks: "Blocks", "track-line": "Line of the track", "start-lines": "Starting lines", ice: "Ice", "technical-panel": "Technical panel", referee: "Referee", "kiss-and-cry": "Kiss and cry", judges: "Judges", runway: "Runway", "take-off-board": "Take-off board", "landing-area": "Landing area", "throwing-circle": "Throwing circle", sector: "Landing sector", "stop-board": "Stop board", cage: "Cage", "throwing-arc": "Throwing arc", crossbar: "Crossbar", "landing-mat": "Landing mat", "run-up": "Run-up", "zero-line": "Zero line", box: "Box",
385
+ "referee-position": "Referee's place", "shiai-jo": "Shiai-jo", floorboard: "Floorboard", heri: "Heri, the mat's edge", "contest-area": "Contest area", "safety-area": "Safety area", "danger-zone": "Danger zone", "start-mark": "Start mark", "common-safe-zone": "Common safe zone", "free-zone": "Free zone", platform: "Platform", "technical-table": "Technical table", "centre-tape": "Centre tape", "kata-judges": "Judges, shomen side", "coach-chair": "Coach's chair", "kata-entry": "Entry and exit, opposite shomen", arena: "Arena floor", "competition-area": "Competition area", "boundary-band": "Boundary band", "start-mat": "Start mat", judge: "Judge", "between-areas": "Between the areas", "centre-cross": "Centre cross", "kaishi-sen": "Kaishi-sen", "outer-boundary": "Outer boundary", dohyo: "Dohyō", ring: "Ring", tawara: "Tawara", tokudawara: "Tokudawara", "shikiri-sen": "Shikiri-sen", "jano-me": "Jano-me", shajo: "Shajō", "shooting-position": "Shooting position", honza: "Honza", yamichi: "Yamichi", "yatori-michi": "Path for the arrows", azuchi: "Azuchi", mato: "Target", kamiza: "Kamiza", shimoza: "Shimoza", shinzen: "Shinzen", dais: "Dais, the seat of honour", banner: "Banner", entrance: "Entrance", window: "Window", tatami: "Tatami", wall: "Wall", "inner-lane": "Inner lane", "outer-lane": "Outer lane", "crossing-straight": "Crossing straight", "warm-up-lane": "Warm-up lane", padding: "Padding", infield: "Infield", "coaches-area": "Coaches' area", blocks: "Blocks", "track-line": "Line of the track", "start-lines": "Starting lines", ice: "Ice", "technical-panel": "Technical panel", referee: "Referee", "kiss-and-cry": "Kiss and cry", judges: "Judges", runway: "Runway", "take-off-board": "Take-off board", "landing-area": "Landing area", "throwing-circle": "Throwing circle", sector: "Landing sector", "stop-board": "Stop board", cage: "Cage", "throwing-arc": "Throwing arc", crossbar: "Crossbar", "landing-mat": "Landing mat", "run-up": "Run-up", "zero-line": "Zero line", box: "Box",
386
386
  "seven-metre-line": "7-metre line", "four-metre-line": "4-metre line", "throw-off-area": "Throw-off area", "restraining-line": "Restraining line", "wing-line": "Wing line", "marking-area": "Marking area", "two-point-arc": "Two-point arc",
387
387
  "lane-rope": "Lane rope", "lane-line": "Lane marking on the floor", "lane-end-cross": "Cross line at the end of the lane marking", "fifteen-metre-mark": "15 m mark", "touch-pad": "Touch panel", "starting-block": "Starting block", "backstroke-flags": "Backstroke flags", "false-start-rope": "False start rope", gutter: "Gutter",
388
388
  "two-metre-line": "2 m line", "five-metre-mark": "5 m mark", "six-metre-line": "6 m line", "half-distance-line": "Half distance line", "re-entry-area": "Re-entry area", "referee-platform": "Referee platform", "official-table": "Official table", "substitution-area": "Substitution area",
@@ -917,7 +917,7 @@ export const KYOGIJO_STRINGS = {
917
917
  "distance-line": "距離を示すライン", "large-rectangle": "大きい長方形", "small-rectangle": "小さい長方形", "thirteen-metre-arc": "13メートルアーク", "forty-metre-arc": "40メートルアーク", "penalty-point": "ペナルティーマーク",
918
918
  boundary: "バウンダリー", pitch: "ピッチ", "bowling-crease": "ボウリングクリース", "popping-crease": "ポッピングクリース", "return-crease": "リターンクリース", wicket: "ウィケット", "thirty-yard-circle": "30ヤードサークル",
919
919
  "twenty-three-line": "23メートルライン", "shooting-circle": "シューティングサークル", "penalty-spot": "ペナルティースポット", "broken-circle-line": "サークルを囲む破線", "goal-area-line": "ゴールエリアライン",
920
- "referee-position": "主審の位置", "shiai-jo": "試合場", floorboard: "床板", heri: "畳縁", "contest-area": "試合場内", "safety-area": "安全地帯", "danger-zone": "危険地帯", "start-mark": "開始線", "common-safe-zone": "共通の安全地帯", "free-zone": "フリーゾーン", platform: "畳の台", "technical-table": "係員席", arena: "アリーナの床", "competition-area": "試合場", "boundary-band": "境界の帯", "start-mat": "開始位置のマット", judge: "副審", "between-areas": "試合場の間", "centre-cross": "中央の×印", "kaishi-sen": "開始線", "outer-boundary": "場外の余地", dohyo: "土俵", ring: "土俵の円", tawara: "俵", tokudawara: "徳俵", "shikiri-sen": "仕切り線", "jano-me": "蛇の目", shajo: "射場", "shooting-position": "射位", honza: "本座", yamichi: "矢道", "yatori-michi": "矢取道", azuchi: "安土", mato: "的", kamiza: "上座", shimoza: "下座", shinzen: "神棚", dais: "上座の台", banner: "垂れ幕", entrance: "入口", window: "窓", tatami: "畳", wall: "壁", "inner-lane": "インレーン", "outer-lane": "アウトレーン", "crossing-straight": "交差ストレート", "warm-up-lane": "ウォームアップレーン", padding: "防護マット", infield: "インフィールド", "coaches-area": "コーチエリア", blocks: "ブロック", "track-line": "トラックのライン", "start-lines": "スタートライン", ice: "氷面", "technical-panel": "テクニカルパネル", referee: "レフェリー", "kiss-and-cry": "キス・アンド・クライ", judges: "ジャッジ席", runway: "助走路", "take-off-board": "踏切板", "landing-area": "砂場", "throwing-circle": "投てきサークル", sector: "着地場所", "stop-board": "足留材", cage: "囲い", "throwing-arc": "スターティング・ライン", crossbar: "バー", "landing-mat": "着地マット", "run-up": "助走路", "zero-line": "ゼロライン", box: "ボックス",
920
+ "referee-position": "主審の位置", "shiai-jo": "試合場", floorboard: "床板", heri: "畳縁", "contest-area": "試合場内", "safety-area": "安全地帯", "danger-zone": "危険地帯", "start-mark": "開始線", "common-safe-zone": "共通の安全地帯", "free-zone": "フリーゾーン", platform: "畳の台", "technical-table": "係員席", "centre-tape": "中央のテープ", "kata-judges": "審判員席(正面側)", "coach-chair": "コーチ席", "kata-entry": "入退場口(正面の反対側)", arena: "アリーナの床", "competition-area": "試合場", "boundary-band": "境界の帯", "start-mat": "開始位置のマット", judge: "副審", "between-areas": "試合場の間", "centre-cross": "中央の×印", "kaishi-sen": "開始線", "outer-boundary": "場外の余地", dohyo: "土俵", ring: "土俵の円", tawara: "俵", tokudawara: "徳俵", "shikiri-sen": "仕切り線", "jano-me": "蛇の目", shajo: "射場", "shooting-position": "射位", honza: "本座", yamichi: "矢道", "yatori-michi": "矢取道", azuchi: "安土", mato: "的", kamiza: "上座", shimoza: "下座", shinzen: "神棚", dais: "上座の台", banner: "垂れ幕", entrance: "入口", window: "窓", tatami: "畳", wall: "壁", "inner-lane": "インレーン", "outer-lane": "アウトレーン", "crossing-straight": "交差ストレート", "warm-up-lane": "ウォームアップレーン", padding: "防護マット", infield: "インフィールド", "coaches-area": "コーチエリア", blocks: "ブロック", "track-line": "トラックのライン", "start-lines": "スタートライン", ice: "氷面", "technical-panel": "テクニカルパネル", referee: "レフェリー", "kiss-and-cry": "キス・アンド・クライ", judges: "ジャッジ席", runway: "助走路", "take-off-board": "踏切板", "landing-area": "砂場", "throwing-circle": "投てきサークル", sector: "着地場所", "stop-board": "足留材", cage: "囲い", "throwing-arc": "スターティング・ライン", crossbar: "バー", "landing-mat": "着地マット", "run-up": "助走路", "zero-line": "ゼロライン", box: "ボックス",
921
921
  "finish-line": "フィニッシュライン", kerb: "縁石", "lane-lines": "レーンライン", bend: "曲走路", straight: "直走路", "infield-pitch": "サッカーのピッチ", "start-line": "スタートライン", breakline: "ブレイクライン", "water-jump": "水濠", "exchange-zone": "テークオーバーゾーン",
922
922
  "seven-metre-line": "7mスローライン", "four-metre-line": "4mライン", "throw-off-area": "スローオフエリア", "restraining-line": "リストレイニングライン", "wing-line": "ウイングライン", "marking-area": "マーキングエリア", "two-point-arc": "2ポイントアーク",
923
923
  "lane-rope": "レーンロープ", "lane-line": "床面のレーンライン", "lane-end-cross": "クロスライン", "fifteen-metre-mark": "15mマーク", "touch-pad": "タッチ板", "starting-block": "スタート台", "backstroke-flags": "背泳ぎ用フラッグ", "false-start-rope": "フォルススタートロープ", gutter: "ガター",
@@ -31,6 +31,10 @@ const BAND = { wall: ["walls", "ky-wall"], mesh: ["fence", "ky-mesh"], face: ["w
31
31
  function solid(o, s) {
32
32
  if (s.kind === "table")
33
33
  return el("path", { class: ft(o, "table", "ky-table"), d: box(s.rect) });
34
+ if (s.kind === "chair")
35
+ return el("path", { class: ft(o, "chairs", "ky-chair"), d: box(s.rect) });
36
+ if (s.kind === "desk")
37
+ return el("path", { class: ft(o, "umpires-table", "ky-table"), d: box(s.rect) });
34
38
  return el("circle", { class: ft(o, "net", "ky-nt-post"), cx: s.cx, cy: s.cy, r: s.r }) + el("circle", { class: ft(o, "net", "ky-set-net"), cx: s.cx, cy: s.cy, r: s.r * 0.84 });
35
39
  }
36
40
  /**
package/dist/types.d.ts CHANGED
@@ -406,9 +406,18 @@ export interface NetStyle {
406
406
  look?: string;
407
407
  /**
408
408
  * The free zone drawn round the court: `official` (the default: the figure a rulebook gives for its own competitions, where it gives one), `least` (the least it allows
409
- * or recommends) or `none`. Tennis (Appendix X), volleyball, beach volleyball and sitting volleyball (Rule 1.1), pickleball (Rule 3.A.3), sepak takraw (Law 1.5) and netball (Rule 1, item 8) give figures; badminton, table tennis, padel, squash, racquetball, teqball and roundnet do not.
409
+ * or recommends) or `none`. Tennis (Appendix X), volleyball, beach volleyball and sitting volleyball (Rule 1.1), pickleball (Rule 3.A.3), sepak takraw (Law 1.5), netball (Rule 1, item 8) and table tennis (the ITTF's 3.2.3.1, the playing space) give figures; badminton, padel, squash, racquetball, teqball and roundnet do not.
410
410
  */
411
411
  free?: "official" | "least" | "none";
412
+ /**
413
+ * Table tennis: the kind of event the playing area is for (the ITTF's Regulations for International Competitions, 3.2.3.1): `standard` (the default: a space of at least 14 m by 7 m by 5 m, for the Worlds and every international event),
414
+ * `masters` (10 m by 5 m, the least a Masters event allows) or `wheelchair` (8 m by 6 m). It sets the free zone round the table (`free: "none"` still draws none); the standard one is `free: "official"` and the Masters one `free: "least"`.
415
+ */
416
+ area?: "standard" | "masters" | "wheelchair";
417
+ /** Table tennis: draw the surrounds that enclose the playing area, 75 cm high (ours, within the 50 cm to 100 cm of 3.2.3.3), of one dark colour. On by default where a playing area is drawn. */
418
+ surrounds?: boolean;
419
+ /** Table tennis: draw the umpire's chair in line with the net, the assistant umpire's chair facing it across the table, and the umpires' table (3.3.2.2; their sizes are ours). On by default where a playing area is drawn. */
420
+ umpires?: boolean;
412
421
  /** Tennis: draw the two singles sticks that hold a doubles net up for a singles match, 3 feet outside the singles court (Rule 1). Off by default. */
413
422
  sticks?: boolean;
414
423
  }
@@ -1,6 +1,6 @@
1
1
  import { bar, flat, n, pole } from "./turn.kit.js";
2
2
  import { moreUprights } from "./uprights.more.js";
3
- import { netPlane, sag, tableBody } from "./uprights.netkit.js";
3
+ import { netPlane, sag, standingBox, surroundWalls, tableBody } from "./uprights.netkit.js";
4
4
  import { convertLength } from "./units.js";
5
5
  /** The uprights of a court: what hangs on its posts. `thin` is the least thickness drawn, in feet. */
6
6
  export function netUprights(plan, geo, settings, thin) {
@@ -102,16 +102,23 @@ export function netUprights(plan, geo, settings, thin) {
102
102
  const room = Math.max(L, W) * 5;
103
103
  // Under the table lies its shadow on that floor, so that the legs stand on something.
104
104
  const grow = Math.max(convertLength(0.25, "m", "ft"), thin * 4);
105
- items.push({
106
- id: "ground",
107
- kind: "table-legs",
108
- at: [L / 2, W / 2],
109
- height: 0,
110
- planes: [
111
- flat("ground", "ground", -room, -room, L + room, W + room, 0, `<rect width="${n(L + 2 * room)}" height="${n(W + 2 * room)}" class="u-ground"/>`),
112
- flat("table-shadow", "ground", -grow / 2, -grow / 2, L + grow / 2, W + grow / 2, 0.005, `<rect width="${n(L + grow)}" height="${n(W + grow)}" rx="${n(grow)}" class="u-shadow"/>`),
113
- ],
114
- });
105
+ const ground = [flat("ground", "ground", -room, -room, L + room, W + room, 0, `<rect width="${n(L + 2 * room)}" height="${n(W + 2 * room)}" class="u-ground"/>`)];
106
+ // The playing area has its own floor, in the look's colour, and is enclosed by its surrounds, with the umpires' chairs and table inside (the same figures as the flat drawing's).
107
+ const zone = plan.zone;
108
+ if (zone !== null) {
109
+ const [ax0, ay0, ax1, ay1] = [-zone.end, -zone.side, L + zone.end, W + zone.side];
110
+ ground.push(flat("area-floor", "ground", ax0, ay0, ax1, ay1, 0.003, `<rect width="${n(ax1 - ax0)}" height="${n(ay1 - ay0)}" class="ky-free"/>`));
111
+ if (plan.surrounds)
112
+ items.push({ id: "surrounds", kind: "surrounds", at: [L / 2, W / 2], height: f.surround.drawn, planes: surroundWalls("surround", [ax0, ay0, ax1, ay1], f.surround.drawn) });
113
+ const chairs = geo.solids.filter((one) => one.kind === "chair" || one.kind === "desk");
114
+ if (chairs.length > 0) {
115
+ // An umpire's chair is 40 cm to the seat and the table 75 cm (ours: the rules give no height).
116
+ const planes = chairs.flatMap((one, i) => (one.kind === "chair" || one.kind === "desk" ? standingBox(`${one.kind}-${i + 1}`, one.kind, one.rect, convertLength(one.kind === "chair" ? 0.45 : 0.75, "m", "ft"), "u-chair") : []));
117
+ items.push({ id: "officials", kind: "officials", at: [L / 2, W / 2], height: convertLength(0.75, "m", "ft"), planes });
118
+ }
119
+ }
120
+ ground.push(flat("table-shadow", "ground", -grow / 2, -grow / 2, L + grow / 2, W + grow / 2, 0.005, `<rect width="${n(L + grow)}" height="${n(W + grow)}" rx="${n(grow)}" class="u-shadow"/>`));
121
+ items.push({ id: "ground", kind: "table-legs", at: [L / 2, W / 2], height: 0, planes: ground });
115
122
  return { items, lift };
116
123
  }
117
124
  default:
@@ -26,3 +26,12 @@ export declare const sag: (y0: number, y1: number, centre: number, posts: number
26
26
  * itself (a teqball table stands on a court; a table tennis table is the surface, which the drawing already shows).
27
27
  */
28
28
  export declare function tableBody(x0: number, y0: number, x1: number, y1: number, lift: number, thin: number, top: boolean): Plane[];
29
+ /** A rectangle on the floor, in feet. */
30
+ export type FloorRect = readonly [x0: number, y0: number, x1: number, y1: number];
31
+ /**
32
+ * The surrounds that enclose a playing area: four boards of one height standing over the edge of `r`, each in pieces of at most ten feet (so each is sorted among the table and the chairs and not as one slab by its
33
+ * middle), facing out from the area's middle and giving way to a nearly level eye, as the glass of a rink does. Painted in the look's own surround colour.
34
+ */
35
+ export declare function surroundWalls(id: string, r: FloorRect, height: number): Plane[];
36
+ /** A solid box on the floor, in feet: four sides up to `top` and a flat top; `cls` paints the top and `cls-side` (a tone darker) the sides. */
37
+ export declare function standingBox(id: string, kind: Plane["kind"], r: FloorRect, top: number, cls: string, side?: string): Plane[];
@@ -1,5 +1,5 @@
1
1
  // The pieces the net sports' uprights share: a net hung between a top line and a bottom line, the sag of a net between its posts, and a table lifted on its legs.
2
- import { flat, n, plane, pole, threads, wall } from "./turn.kit.js";
2
+ import { flat, n, plane, pole, shy, threads, wall } from "./turn.kit.js";
3
3
  import { convertLength } from "./units.js";
4
4
  /** The net: a mesh between a top line and a bottom line, with its tape, on one plane standing over the line x. */
5
5
  export function netPlane(b, lift) {
@@ -65,3 +65,34 @@ export function tableBody(x0, y0, x1, y1, lift, thin, top) {
65
65
  planes.push(...pole(`leg-${i}`, "leg", lx, ly, 0, lift - skirt, leg, "u-leg"));
66
66
  return planes;
67
67
  }
68
+ /**
69
+ * The surrounds that enclose a playing area: four boards of one height standing over the edge of `r`, each in pieces of at most ten feet (so each is sorted among the table and the chairs and not as one slab by its
70
+ * middle), facing out from the area's middle and giving way to a nearly level eye, as the glass of a rink does. Painted in the look's own surround colour.
71
+ */
72
+ export function surroundWalls(id, r, height) {
73
+ const [x0, y0, x1, y1] = r;
74
+ const centre = [(x0 + x1) / 2, (y0 + y1) / 2];
75
+ const sides = [[x0, y0, x1, y0], [x1, y0, x1, y1], [x1, y1, x0, y1], [x0, y1, x0, y0]];
76
+ const rim = Math.min(0.3, height * 0.12);
77
+ const planes = [];
78
+ sides.forEach(([xa, ya, xb, yb], side) => {
79
+ const long = Math.hypot(xb - xa, yb - ya);
80
+ const pieces = Math.max(1, Math.ceil(long / 10));
81
+ const run = long / pieces;
82
+ for (let i = 0; i < pieces; i += 1) {
83
+ const [t0, t1] = [i / pieces, (i + 1) / pieces];
84
+ const svg = `<rect width="${n(run)}" height="${n(height)}" class="ky-wall"/><rect width="${n(run)}" height="${n(rim)}" class="u-surround-rim"/>`;
85
+ const p = wall(`${id}-${side + 1}-${i + 1}`, "surround", xa + (xb - xa) * t0, ya + (yb - ya) * t0, xa + (xb - xa) * t1, ya + (yb - ya) * t1, 0, height, svg);
86
+ planes.push({ ...shy(p, centre), fadeTo: 0.35, fadeAt: 0.85 });
87
+ }
88
+ });
89
+ return planes;
90
+ }
91
+ /** A solid box on the floor, in feet: four sides up to `top` and a flat top; `cls` paints the top and `cls-side` (a tone darker) the sides. */
92
+ export function standingBox(id, kind, r, top, cls, side = `${cls}-side`) {
93
+ const [x0, y0, x1, y1] = r;
94
+ const sides = [[x0, y0, x1, y0], [x1, y0, x1, y1], [x1, y1, x0, y1], [x0, y1, x0, y0]];
95
+ const planes = sides.map(([a, b, c, d], i) => wall(`${id}-${i + 1}`, kind, a, b, c, d, 0, top, `<rect width="${n(Math.hypot(c - a, d - b))}" height="${n(top)}" class="${side}"/>`));
96
+ planes.push(flat(`${id}-top`, kind, x0, y0, x1, y1, top, `<rect width="${n(x1 - x0)}" height="${n(y1 - y0)}" class="${cls}"/>`));
97
+ return planes;
98
+ }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export declare const VERSION = "0.13.0";
2
+ export declare const VERSION = "0.15.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export const VERSION = "0.13.0";
2
+ export const VERSION = "0.15.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Kyogijo Stadiums 競技場, 39 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of 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 and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",