@johnmorrisdotca/taisen-brackets 0.9.0 → 0.11.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 +26 -0
- package/README.md +16 -4
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/judo-road.d.ts +53 -0
- package/dist/judo-road.js +207 -0
- package/dist/judo-sheets.d.ts +20 -0
- package/dist/judo-sheets.js +12 -11
- package/dist/locales.js +10 -0
- package/dist/rank-kit.d.ts +90 -0
- package/dist/rank-kit.js +124 -0
- package/dist/ranking.d.ts +95 -0
- package/dist/ranking.js +391 -0
- package/dist/types.d.ts +19 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,32 @@ All notable changes to this project are written here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.11.0] - 2026-10-20
|
|
10
|
+
|
|
11
|
+
Rankings, step 1: the ranking list.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`rankingList(source, options)`**: a ranking for any ranked sport as an SVG, from the caller's data (a province's or a club's feed, a federation's list). Text first, with no photo slot: the rank, a flag (a country's or a Canadian province's or territory's, `CA-BC`), the name, the club and the points, with the movement since the last list. `source` is `{ title?, category?, date?, source?, entries }` and an entry `{ name, rank?, points?, club?, country?, code?, flag?, mark?, category?, change?, champion? }`. Styles: `podium` (the top three as big rows tinted gold, silver and bronze above the compact rest; on a wide board the big rows stand in a panel beside the others), `featured` (one big row above two columns) and `list`; `big`, `columns`, `split`, `medals`. Movement as arrows with the number of places, as green and red row colours, both, or none; `NR` for the newly ranked; a dash for no change. Tied ranks (equal points share a rank: 9, 10, 10, 12), an own `rank`, `rankStyle: "hash"` (`#10`). A champion row marked `C` (`IC` for an interim champion) above rank 1. `precision` writes points to the sport's own decimals (`91.60`, `50.00`), `nameStyle` writes `SURNAME Given`, `Given SURNAME` or the name as it is, `sort`, `categories`, `badge` (a club's initials where there is no flag), `rows` and `page` (the header says which entries are shown) and `flagShape`. Where the row is narrow the type steps down and then the text is cut with an ellipsis; nothing is stretched.
|
|
16
|
+
- **`rankingRanks(entries, { ties })`**, **`rankingCategoryLabel({ age, sex, weight }, locale)`** (`U18 · Men -60 kg`) and **`describeRanking(source, options)`**, the drawing's description.
|
|
17
|
+
- **`BOARD_SIZES`** and the options every board takes (`BoardOptions`): `tv`, `tablet`, `tablet-tall`, `phone` (600 by 1300), `square` and `portrait` (4:5) for a social card, or your own `{ width, height }`; light, dark or `auto`; five languages; custom properties `--rk-*`. The medal table and the results boards that follow share them.
|
|
18
|
+
- Words for ranking lists in the five languages (the French and Japanese were read by native-language reviewers; the Spanish and German were not). The Japanese marks the champion with `王者` and the interim champion with `暫定`.
|
|
19
|
+
- The demo has a Ranking lists section with a made-up provincial feed (U18 men -60 kg, and a senior women's list with a champion): style, size, look, movement, columns, name and rank style and paging, with the call that draws it; the state is in the address. [docs/rankings.md](docs/rankings.md).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- The demo's flags come from Hata 1.6.2, which has all thirteen Canadian provinces and territories.
|
|
24
|
+
|
|
25
|
+
## [0.10.0] - 2026-10-20
|
|
26
|
+
|
|
27
|
+
Judo, step 4: the road to the final of one judoka.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`judoWins(bracket, athleteId, options)`**: the contests an athlete has won in a bracket, oldest first, as `{ round, opponent, how }`: the round (a repechage and a bronze medal contest named as such), the opponent as an athlete (`name`, `code`, `flag`, `mark` of the team, or what `athlete(team)` gives) and how it was won (a judo result's method, golden score and time, or the score). A bye is a row for each round a top seed skipped (`byes: false` leaves them out). A name that is not in the bracket is a `RangeError`. `Team.code`, the three letters of the country, is new.
|
|
32
|
+
- **`judoRoadPanel({ athlete, wins }, options)`**: the panel for a broadcast or a hall screen. The athlete's block (flag, name, code, coach) in the colour of a judogi (`side`), and a row for each win, most recent first (`order`): the round on a pill, the opponent's flag, code and name, and how it was won; a bye is a quiet row. A wide screen puts the block on the left and the rows on the right, an upright one the block on top and the result under each name. `size`, `theme`, `flags`, `flagHref`, `locale`, `words`, `title`, `weight`, `rows`, `coach`. `describeJudoRoad`. The title is the road chart's word in each language.
|
|
33
|
+
- The demo has a section for it: any of the 16 athletes of the Judo example (a bronze medallist's repechage and bronze medal contest included), size, look, the colour of the block and the order, with the call that draws it; the state is in the address. Tests hold every text inside the drawing and every opponent inside its row, in every size and several languages.
|
|
34
|
+
|
|
9
35
|
## [0.9.0] - 2026-10-20
|
|
10
36
|
|
|
11
37
|
Judo, step 3: the contest card and the mat queue board.
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Give it the games, series, two-legged ties or replays of a tournament and it wor
|
|
|
11
11
|
<img alt="TypeScript" src="https://img.shields.io/badge/types-TypeScript-3178c6">
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
-
<p align="center"><a href="https://johnmorrisdotca.github.io/taisen-brackets/"><strong>Open the demo →</strong></a> · <a href="./docs/layout.md">Layout</a> · <a href="./docs/layouts.md">Layouts</a> · <a href="./docs/drawing.md">Drawing</a> · <a href="./docs/road.md">Road to the final</a> · <a href="./docs/backgrounds.md">Backgrounds</a> · <a href="./docs/judo.md">Judo</a> · <a href="./docs/formats.md">Formats</a> · <a href="./docs/compare.md">How it compares</a></p>
|
|
14
|
+
<p align="center"><a href="https://johnmorrisdotca.github.io/taisen-brackets/"><strong>Open the demo →</strong></a> · <a href="./docs/layout.md">Layout</a> · <a href="./docs/layouts.md">Layouts</a> · <a href="./docs/drawing.md">Drawing</a> · <a href="./docs/road.md">Road to the final</a> · <a href="./docs/backgrounds.md">Backgrounds</a> · <a href="./docs/judo.md">Judo</a> · <a href="./docs/rankings.md">Rankings</a> · <a href="./docs/formats.md">Formats</a> · <a href="./docs/compare.md">How it compares</a></p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
17
17
|
<picture>
|
|
@@ -240,13 +240,22 @@ In the road look, `seeds: true` writes a small tag (`P1`, `WC2`) beside each tea
|
|
|
240
240
|
|
|
241
241
|
## Judo
|
|
242
242
|
|
|
243
|
-
A judo contest is a tie with a `judo` result: ippon, waza-ari, yuko and shido as the IJF's current Sport and Organisation Rules score them (yuko is back since 2025), golden score, the contest time and how it was won. The box writes the marks (`I`, `W`, `Y`), the shido as yellow and red cards and a note under the two rows, in the five languages. `judoBracket` makes a weight category's bracket with the IJF's repechage and its two bronze medal contests. [docs/judo.md](./docs/judo.md) quotes the rules it follows. `judoScoreboard` draws one contest's scoreboard for a given state (the athletes with flags and codes, ippon, waza-ari and yuko counters, shido cards, clock, golden score, osaekomi timer, weight class and round), on a TV or a tablet, as a screen board or as table-top flip cards: [try it in the demo](https://johnmorrisdotca.github.io/taisen-brackets/#board). `judoContestCard` and `judoMatQueue` draw the card shown when the athletes enter and the board of the next contests per mat, with a big mat number and colour ([demo](https://johnmorrisdotca.github.io/taisen-brackets/#queue)).
|
|
243
|
+
A judo contest is a tie with a `judo` result: ippon, waza-ari, yuko and shido as the IJF's current Sport and Organisation Rules score them (yuko is back since 2025), golden score, the contest time and how it was won. The box writes the marks (`I`, `W`, `Y`), the shido as yellow and red cards and a note under the two rows, in the five languages. `judoBracket` makes a weight category's bracket with the IJF's repechage and its two bronze medal contests. [docs/judo.md](./docs/judo.md) quotes the rules it follows. `judoScoreboard` draws one contest's scoreboard for a given state (the athletes with flags and codes, ippon, waza-ari and yuko counters, shido cards, clock, golden score, osaekomi timer, weight class and round), on a TV or a tablet, as a screen board or as table-top flip cards: [try it in the demo](https://johnmorrisdotca.github.io/taisen-brackets/#board). `judoContestCard` and `judoMatQueue` draw the card shown when the athletes enter and the board of the next contests per mat, with a big mat number and colour ([demo](https://johnmorrisdotca.github.io/taisen-brackets/#queue)). `judoWins` and `judoRoadPanel` draw the road to the final of one judoka, the contests they have won on the way, most recent first ([demo](https://johnmorrisdotca.github.io/taisen-brackets/#judoroad)).
|
|
244
244
|
|
|
245
245
|
<p align="center"><picture>
|
|
246
246
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/judo-desk-dark.webp">
|
|
247
247
|
<img alt="A made-up men's minus 66 kilogram judo tournament of 16 athletes with round flags, score marks, shido cards, how each contest was won and its time, and the repechage and bronze medal contests under the tree." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/judo-desk-light.webp" width="720">
|
|
248
248
|
</picture><br><em>Men's -66 kg, with the quarter-final repechage (invented names).</em></p>
|
|
249
249
|
|
|
250
|
+
## Rankings
|
|
251
|
+
|
|
252
|
+
`rankingList` draws a ranking for any ranked sport from your own data (a province's or a club's feed, a federation's list), text first, with no photo slot to leave empty: the rank, a flag (a country's or a Canadian province's, `CA-BC`), the name, the club and the points, with the movement since the last list as arrows or row colours. The top three can be big gold, silver and bronze rows above a compact list, a champion row is marked `C`, tied ranks share a number (`10`, `10`), a newly ranked athlete is `NR`, and points are written to the sport's own precision (`91.60`). `rankingRanks` and `rankingCategoryLabel` are its helpers, `describeRanking` its description, and `BOARD_SIZES` the sizes of every board (a TV, a tablet, a phone, and a square and a 4:5 card for social media). [docs/rankings.md](./docs/rankings.md) · [try it in the demo](https://johnmorrisdotca.github.io/taisen-brackets/#rankings)
|
|
253
|
+
|
|
254
|
+
<p align="center"><picture>
|
|
255
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/ranking-podium-tv-dark.webp">
|
|
256
|
+
<img alt="A ranking list on a TV: three big rows tinted gold, silver and bronze for the first three, and the ranks 4 to 15 as compact rows beside them, each with its rank, movement arrow, round flag, name, club and points." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/ranking-podium-tv-light.webp" width="720">
|
|
257
|
+
</picture><br><em>A made-up provincial ranking (invented names and clubs).</em></p>
|
|
258
|
+
|
|
250
259
|
## In 30 seconds
|
|
251
260
|
|
|
252
261
|
```sh
|
|
@@ -293,7 +302,9 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
|
|
|
293
302
|
- **The champion in the centre**, with a slot for your trophy: your own SVG or image, or an emblem from [Dougu Sports](https://github.com/johnmorrisdotca/dougu-sports) (a hockey cup, a basketball trophy, a globe) through an optional plug-in, with no dependency added.
|
|
294
303
|
- **Accessible**: `role="img"` with a title and a description, a tooltip sentence on every tie, and `bracketTable`.
|
|
295
304
|
- **A judo scoreboard**: `judoScoreboard` draws one contest for a given state, in two styles (a board, and table-top flip cards) and three sizes, in five languages; live updating is yours.
|
|
305
|
+
- **The road to the final of a judoka**: `judoWins` reads an athlete's wins from a bracket (a top seed's bye included) and `judoRoadPanel` draws them with the opponent's flag, code and name and how each was won.
|
|
296
306
|
- **Judo screens for the hall**: `judoContestCard` (the category, the round, the white athlete over the blue one) and `judoMatQueue` (per mat, a big number and colour and the contests to come in order, with flags and codes), on a TV or a tablet, in five languages.
|
|
307
|
+
- **Ranking lists**: `rankingList` draws a ranking from your data, text first: rank, flag (a country or a Canadian province), name, club, points, movement arrows, a podium, a champion row, ties and paging ([docs/rankings.md](./docs/rankings.md)).
|
|
297
308
|
- **Judo scoring**: `Matchup.judo` takes ippon, waza-ari, yuko, shido, hansoku-make, golden score and the contest time, checked against the IJF rules (`judoOutcome`); a box shows the marks, the shido cards and how the contest was won, and `judoBracket` adds the repechage and the two bronze medal contests ([docs/judo.md](./docs/judo.md)).
|
|
298
309
|
- **Formats by era**: NHL, NBA, MLB, NFL, CFL, MLS and FIFA World Cup round names, series lengths and byes.
|
|
299
310
|
- **Five languages**: English, French, Japanese, Spanish and German, for every word the drawing, the tooltips, the description and the table write, with the football terms of each language; countries named by `Intl` from their ISO code; numbers and dates by `Intl`. Add a language of your own.
|
|
@@ -654,7 +665,8 @@ Everything is exported from `@johnmorrisdotca/taisen-brackets`; the layout part
|
|
|
654
665
|
- `singleElimination(teams, options?)`, `advanceWinners(matchups)`, `standardSeeds(size)`: brackets for any number of teams.
|
|
655
666
|
- `matchupsFromGames(games, options)`: matchups from a flat list of games.
|
|
656
667
|
- `resolveMatchup`, `collectTeams`, `describeTie`, `noteParts`, `noteLong`: one tie's result, the teams of a bracket, a tie in a sentence.
|
|
657
|
-
- `judoOutcome(result, winner?)`, `judoBracket(athletes, options?)`, `judoPodium(input)`, `judoCategoryLabel(category, locale?)`, `JUDO_WEIGHT_CLASSES`, `judoSeconds(time)`, `judoClock(seconds)`: judo results checked against the IJF rules, the bracket with its repechage and two bronze medal contests, the medallists, a weight category as a heading ([docs/judo.md](./docs/judo.md)). `judoScoreboard(contest, options?)`, `describeJudoContest(contest, options?)`, `JUDO_BOARD_SIZES`: the scoreboard of a contest as SVG and as a sentence. `judoContestCard(contest, options?)`, `judoMatQueue(mats, options?)`, `describeJudoCard`, `describeJudoQueue`, `JUDO_MAT_COLORS`: the contest card and the mat queue board as SVG and as sentences. `Matchup.judo` and `Matchup.stage` (`"repechage"`, `"bronze"`) carry them into `drawBracket`.
|
|
668
|
+
- `judoOutcome(result, winner?)`, `judoBracket(athletes, options?)`, `judoPodium(input)`, `judoCategoryLabel(category, locale?)`, `JUDO_WEIGHT_CLASSES`, `judoSeconds(time)`, `judoClock(seconds)`: judo results checked against the IJF rules, the bracket with its repechage and two bronze medal contests, the medallists, a weight category as a heading ([docs/judo.md](./docs/judo.md)). `judoScoreboard(contest, options?)`, `describeJudoContest(contest, options?)`, `JUDO_BOARD_SIZES`: the scoreboard of a contest as SVG and as a sentence. `judoContestCard(contest, options?)`, `judoMatQueue(mats, options?)`, `describeJudoCard`, `describeJudoQueue`, `JUDO_MAT_COLORS`: the contest card and the mat queue board as SVG and as sentences. `judoWins(bracket, athlete, options?)`, `judoRoadPanel(source, options?)`, `describeJudoRoad`: an athlete's wins on the road to the final, and the panel. `Matchup.judo` and `Matchup.stage` (`"repechage"`, `"bronze"`) carry them into `drawBracket`.
|
|
669
|
+
- `rankingList(source, options?)`, `rankingRanks(entries, options?)`, `rankingCategoryLabel(category, locale?)`, `describeRanking(source, options?)`, `BOARD_SIZES`: a ranking list as SVG, its ranks as data, a category as a heading and the drawing as a sentence ([docs/rankings.md](./docs/rankings.md)).
|
|
658
670
|
- `playoffFormat(league, year)`, `playoffEras(league)`, `LEAGUES`: the formats by era (`nhl`, `nba`, `mlb`, `nfl`, `cfl`, `mls`, `worldcup`). `LEAGUE_EMBLEMS`: each league's default emblem id.
|
|
659
671
|
- `isEmblemId(value)`, and the types `EmblemRenderer`, `EmblemRequest`, `EmblemStyle`: the trophy emblems (see "Emblems for the trophy").
|
|
660
672
|
- `roundName(remaining)`, `roundNamesFor(count)`, `roundLabel(layout, round, names, words)`: round names.
|
|
@@ -746,7 +758,7 @@ Any current browser, and Node 22 or later. The package makes no network request
|
|
|
746
758
|
|
|
747
759
|
## Roadmap
|
|
748
760
|
|
|
749
|
-
- Judo:
|
|
761
|
+
- Judo: double and full repechage ([docs/judo.md](./docs/judo.md)).
|
|
750
762
|
- Double elimination, and the group tables on one page with the knockout they lead to.
|
|
751
763
|
- More languages (Portuguese, Italian, Korean, Chinese), and native review of the Spanish and German words.
|
|
752
764
|
- A custom element and a React helper, as in the other packages of the family.
|
package/dist/index.d.ts
CHANGED
|
@@ -23,3 +23,9 @@ export { judoScoreboard, describeJudoContest, JUDO_BOARD_SIZES } from "./judo-bo
|
|
|
23
23
|
export type { JudoAthlete, JudoBoardOptions, JudoBoardSizeName, JudoContest } from "./judo-board.ts";
|
|
24
24
|
export { judoContestCard, describeJudoCard, judoMatQueue, describeJudoQueue, JUDO_MAT_COLORS } from "./judo-sheets.ts";
|
|
25
25
|
export type { JudoCardContest, JudoMat, JudoQueueContest, JudoQueueOptions } from "./judo-sheets.ts";
|
|
26
|
+
export { judoWins, judoRoadPanel, describeJudoRoad } from "./judo-road.ts";
|
|
27
|
+
export type { JudoRoadOptions, JudoRoadSource, JudoWin, JudoWinsOptions } from "./judo-road.ts";
|
|
28
|
+
export { rankingList, rankingRanks, rankingCategoryLabel, describeRanking } from "./ranking.ts";
|
|
29
|
+
export type { RankingEntry, RankingName, RankingOptions, RankingSource } from "./ranking.ts";
|
|
30
|
+
export { BOARD_SIZES } from "./rank-kit.ts";
|
|
31
|
+
export type { BoardOptions, BoardSizeName } from "./rank-kit.ts";
|
package/dist/index.js
CHANGED
|
@@ -13,3 +13,6 @@ export { playoffFormat, playoffEras, LEAGUES, LEAGUE_EMBLEMS } from "./presets.j
|
|
|
13
13
|
export { judoBracket, judoPodium, judoCategoryLabel, judoOutcome, judoSeconds, judoClock, JUDO_WEIGHT_CLASSES } from "./judo.js";
|
|
14
14
|
export { judoScoreboard, describeJudoContest, JUDO_BOARD_SIZES } from "./judo-board.js";
|
|
15
15
|
export { judoContestCard, describeJudoCard, judoMatQueue, describeJudoQueue, JUDO_MAT_COLORS } from "./judo-sheets.js";
|
|
16
|
+
export { judoWins, judoRoadPanel, describeJudoRoad } from "./judo-road.js";
|
|
17
|
+
export { rankingList, rankingRanks, rankingCategoryLabel, describeRanking } from "./ranking.js";
|
|
18
|
+
export { BOARD_SIZES } from "./rank-kit.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The road to the final of one judoka as a panel for a broadcast or a hall screen: the athlete (flag, name, code, coach) and the contests they have won on the way, most
|
|
3
|
+
* recent first, each with its round, the opponent (flag, code, name) and how it was won. `judoWins` reads the wins from a bracket; `judoRoadPanel` draws them. Static SVG for a
|
|
4
|
+
* given state, like the other judo drawings.
|
|
5
|
+
*/
|
|
6
|
+
import type { JudoAthlete, JudoBoardOptions } from "./judo-board.ts";
|
|
7
|
+
import type { BracketInput, BracketLayout, DrawOptions, Matchup, SideId, Team } from "./types.ts";
|
|
8
|
+
/** One win on the road: the round it was in, the opponent (none for a bye), and how it was won in words ("ippon · 1:12", "decision · GS · 5:12", or a score). */
|
|
9
|
+
export interface JudoWin {
|
|
10
|
+
round: string;
|
|
11
|
+
opponent?: JudoAthlete;
|
|
12
|
+
how: string;
|
|
13
|
+
bye?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface JudoWinsOptions {
|
|
16
|
+
locale?: DrawOptions["locale"];
|
|
17
|
+
words?: DrawOptions["words"];
|
|
18
|
+
/** The names of the rounds, as for `drawBracket`. */
|
|
19
|
+
roundNames?: DrawOptions["roundNames"];
|
|
20
|
+
/** Turns a bracket's team into the athlete the panel draws. Default: its name, flag, mark and `code`. */
|
|
21
|
+
athlete?: (team: Team) => JudoAthlete;
|
|
22
|
+
/** Write a bye for each round the athlete skipped before their first contest. Default true. */
|
|
23
|
+
byes?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The contests an athlete has won in a bracket, oldest first, with a bye for each round skipped at the start: the opponent, the round (a repechage and a bronze medal contest are named as
|
|
27
|
+
* such) and how it was won, from the judo result's notes or the score. Throws a `RangeError` for an athlete that is not in the bracket.
|
|
28
|
+
*/
|
|
29
|
+
export declare function judoWins(input: BracketLayout | readonly Matchup[] | BracketInput, athlete: SideId, options?: JudoWinsOptions): JudoWin[];
|
|
30
|
+
export interface JudoRoadSource {
|
|
31
|
+
athlete: JudoAthlete;
|
|
32
|
+
/** Oldest first, as `judoWins` gives them. */
|
|
33
|
+
wins: readonly JudoWin[];
|
|
34
|
+
}
|
|
35
|
+
export interface JudoRoadOptions extends Omit<JudoBoardOptions, "style" | "swap" | "coaches"> {
|
|
36
|
+
/** The weight class as a heading (`judoCategoryLabel`), written at the right of the header. */
|
|
37
|
+
weight?: string;
|
|
38
|
+
/** The colour of the athlete's block: the white judogi's or the blue's. Default `blue`. */
|
|
39
|
+
side?: "white" | "blue";
|
|
40
|
+
/** `latest` (the default) puts the most recent win first, as a broadcast does; `first` the oldest. */
|
|
41
|
+
order?: "latest" | "first";
|
|
42
|
+
/** The most wins drawn. Default: as many as fit at a readable size, up to 8. */
|
|
43
|
+
rows?: number;
|
|
44
|
+
/** Write the coach under the code. Default true. */
|
|
45
|
+
coach?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** The road as a sentence, the drawing's description. */
|
|
48
|
+
export declare function describeJudoRoad(source: JudoRoadSource, options?: Pick<JudoRoadOptions, "locale" | "words" | "weight" | "order">): string;
|
|
49
|
+
/**
|
|
50
|
+
* The road-to-the-final panel for an athlete: their block (flag, name, code, coach) in the colour of a judogi, and a row for each contest won, most recent first: the round, the
|
|
51
|
+
* opponent's flag, code and name, and how it was won. A bye is a quiet row. Throws a `RangeError` for an athlete with no name, a win with no round, or a bad size, theme or `rows`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function judoRoadPanel(source: JudoRoadSource, options?: JudoRoadOptions): string;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { bw, clampN, fitSize } from "./judo-board.js";
|
|
2
|
+
import { judoNotes } from "./judo-text.js";
|
|
3
|
+
import { check, frameOf, text, teamOf, wrap } from "./judo-sheets.js";
|
|
4
|
+
import { layoutBracket } from "./layout.js";
|
|
5
|
+
import { fill, voiceOf } from "./locales.js";
|
|
6
|
+
import { drawMark } from "./marks.js";
|
|
7
|
+
import { roundLabel } from "./rounds.js";
|
|
8
|
+
import { fit, fitLines, num } from "./text.js";
|
|
9
|
+
const asAthlete = (t) => ({ name: t.name, ...(t.code ? { code: t.code } : {}), ...(t.flag ? { flag: t.flag } : {}), ...(t.mark ? { mark: t.mark } : {}) });
|
|
10
|
+
/**
|
|
11
|
+
* The contests an athlete has won in a bracket, oldest first, with a bye for each round skipped at the start: the opponent, the round (a repechage and a bronze medal contest are named as
|
|
12
|
+
* such) and how it was won, from the judo result's notes or the score. Throws a `RangeError` for an athlete that is not in the bracket.
|
|
13
|
+
*/
|
|
14
|
+
export function judoWins(input, athlete, options = {}) {
|
|
15
|
+
const layout = "ties" in input && "sides" in input ? input : layoutBracket(input);
|
|
16
|
+
const me = layout.teams.find((t) => t.id === athlete);
|
|
17
|
+
if (!me)
|
|
18
|
+
throw new RangeError(`taisen-brackets: ${JSON.stringify(athlete)} is not in the bracket`);
|
|
19
|
+
const v = voiceOf(options);
|
|
20
|
+
const w = v.words;
|
|
21
|
+
const map = options.athlete ?? asAthlete;
|
|
22
|
+
const label = (round) => roundLabel(layout, round, options.roundNames, v);
|
|
23
|
+
const order = (t) => (t.stage === "repechage" ? layout.rounds + 1 : t.stage === "bronze" ? layout.rounds + 2 : t.round);
|
|
24
|
+
const won = layout.ties
|
|
25
|
+
.filter((t) => t.winner !== null && t.sides[t.winner]?.id === athlete && (t.stage !== undefined || t.round > 0))
|
|
26
|
+
.sort((a, b) => order(a) - order(b) || a.index - b.index);
|
|
27
|
+
const rows = won.map((t) => {
|
|
28
|
+
const other = t.sides[(1 - t.winner)];
|
|
29
|
+
const how = t.judo
|
|
30
|
+
? judoNotes(t, v, false).join(" · ")
|
|
31
|
+
: t.result.score ? fill(w.scoreText, { a: v.number(t.result.score[t.winner]), b: v.number(t.result.score[(1 - t.winner)]) }) : "";
|
|
32
|
+
const round = t.stage === "repechage" ? w.judoRepechage : t.stage === "bronze" ? w.judoBronze : label(t.round);
|
|
33
|
+
return { round, ...(other ? { opponent: map(other) } : {}), how };
|
|
34
|
+
});
|
|
35
|
+
const firstTree = won.find((t) => t.stage === undefined);
|
|
36
|
+
const byes = [];
|
|
37
|
+
if ((options.byes ?? true) && firstTree && firstTree.round > 1)
|
|
38
|
+
for (let r = 1; r < firstTree.round; r++)
|
|
39
|
+
byes.push({ round: label(r), how: w.bye, bye: true });
|
|
40
|
+
return [...byes, ...rows];
|
|
41
|
+
}
|
|
42
|
+
const ROAD_CSS = [
|
|
43
|
+
"$S .jr-row{fill:#f7f6f1;stroke:#bdbbae;stroke-width:1.5}",
|
|
44
|
+
"$S .jr-pill{fill:#dcdbd2}",
|
|
45
|
+
"$S .jr-ink{fill:#15181a}",
|
|
46
|
+
"$S .jr-sub{fill:#4a4f54}",
|
|
47
|
+
"$S .jr-bye{fill:#e9e8e1;stroke:#bdbbae;stroke-width:1.5}",
|
|
48
|
+
].join("\n");
|
|
49
|
+
/** The road as a sentence, the drawing's description. */
|
|
50
|
+
export function describeJudoRoad(source, options = {}) {
|
|
51
|
+
const v = voiceOf(options);
|
|
52
|
+
const w = v.words;
|
|
53
|
+
const list = options.order === "first" ? [...source.wins] : [...source.wins].reverse();
|
|
54
|
+
const parts = [`${w.roadTitle}: ${source.athlete.name}${source.athlete.code ? ` (${source.athlete.code})` : ""}`];
|
|
55
|
+
if (options.weight)
|
|
56
|
+
parts.push(options.weight);
|
|
57
|
+
for (const x of list)
|
|
58
|
+
parts.push(x.bye ? `${x.round}: ${w.bye}` : `${x.round}: ${x.opponent?.name ?? ""}${x.how ? ` (${x.how})` : ""}`);
|
|
59
|
+
return parts.join(w.sep) + w.end;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The road-to-the-final panel for an athlete: their block (flag, name, code, coach) in the colour of a judogi, and a row for each contest won, most recent first: the round, the
|
|
63
|
+
* opponent's flag, code and name, and how it was won. A bye is a quiet row. Throws a `RangeError` for an athlete with no name, a win with no round, or a bad size, theme or `rows`.
|
|
64
|
+
*/
|
|
65
|
+
export function judoRoadPanel(source, options = {}) {
|
|
66
|
+
check(source.athlete, "road");
|
|
67
|
+
for (const x of source.wins) {
|
|
68
|
+
if (typeof x.round !== "string" || x.round === "")
|
|
69
|
+
throw new RangeError("taisen-brackets: every win on the road needs a round");
|
|
70
|
+
if (!x.bye && x.opponent)
|
|
71
|
+
check(x.opponent, `opponent in ${x.round}`);
|
|
72
|
+
}
|
|
73
|
+
if (options.side !== undefined && options.side !== "white" && options.side !== "blue")
|
|
74
|
+
throw new RangeError(`taisen-brackets: a road panel's side is "white" or "blue", not ${JSON.stringify(options.side)}`);
|
|
75
|
+
if (options.rows !== undefined && !(Number.isInteger(options.rows) && options.rows >= 1 && options.rows <= 20))
|
|
76
|
+
throw new RangeError("taisen-brackets: rows is a whole number from 1 to 20");
|
|
77
|
+
const f = frameOf(options, [source, options.side, options.order, options.rows, options.weight]);
|
|
78
|
+
const { W, H, v } = f;
|
|
79
|
+
const w = v.words;
|
|
80
|
+
const unit = Math.min(W, H);
|
|
81
|
+
const pad = Math.round(unit * 0.025);
|
|
82
|
+
const landscape = W >= H;
|
|
83
|
+
const body = [];
|
|
84
|
+
const headH = Math.round(H * (landscape ? 0.11 : 0.075));
|
|
85
|
+
body.push(`<rect class="jb-band" x="0" y="0" width="${num(W)}" height="${num(headH)}"/>`);
|
|
86
|
+
const hf = clampN(headH * 0.46, 13, 60);
|
|
87
|
+
const title = w.roadTitle;
|
|
88
|
+
{
|
|
89
|
+
const s = fitSize(title, hf, (W - pad * 2) * 0.55, 0.5);
|
|
90
|
+
body.push(text(pad, headH / 2 + s * 0.36, s, fit(title, s, (W - pad * 2) * 0.55, bw), "jb-ink", "start"));
|
|
91
|
+
}
|
|
92
|
+
if (options.weight) {
|
|
93
|
+
const s = fitSize(options.weight, hf, (W - pad * 2) * 0.4, 0.5);
|
|
94
|
+
body.push(text(W - pad, headH / 2 + s * 0.36, s, fit(options.weight, s, (W - pad * 2) * 0.4, bw), "jb-muted", "end"));
|
|
95
|
+
}
|
|
96
|
+
const top = headH + pad, availH = H - top - pad;
|
|
97
|
+
const side = options.side ?? "blue";
|
|
98
|
+
const A = source.athlete;
|
|
99
|
+
// The athlete's block.
|
|
100
|
+
const blockRect = landscape ? { x: pad, y: top, w: Math.round((W - pad * 3) * 0.4), h: availH } : { x: pad, y: top, w: W - pad * 2, h: Math.round(availH * 0.3) };
|
|
101
|
+
{
|
|
102
|
+
const g = [];
|
|
103
|
+
const rx = Math.round(unit * 0.014);
|
|
104
|
+
g.push(`<rect class="jb-panel" x="${num(blockRect.x)}" y="${num(blockRect.y)}" width="${num(blockRect.w)}" height="${num(blockRect.h)}" rx="${rx}"/>`);
|
|
105
|
+
const ip = Math.max(pad, blockRect.w * 0.05);
|
|
106
|
+
const inner = { x: blockRect.x + ip, y: blockRect.y + ip, w: blockRect.w - ip * 2, h: blockRect.h - ip * 2 };
|
|
107
|
+
const showCoach = (options.coach ?? true) && !!A.coach;
|
|
108
|
+
if (landscape) {
|
|
109
|
+
const flagD = Math.min(inner.w * 0.5, inner.h * 0.36);
|
|
110
|
+
g.push(drawMark(teamOf(A, "road"), inner.x + inner.w / 2, inner.y + flagD / 2, flagD, f.ctx));
|
|
111
|
+
const ty = inner.y + flagD + ip * 0.8;
|
|
112
|
+
const codeH = A.code ? inner.h * 0.07 : 0, coachH = showCoach ? inner.h * 0.045 : 0;
|
|
113
|
+
const fl = fitLines(A.name, inner.w, inner.h - flagD - ip * 0.8 - codeH - coachH - ip, Math.min(inner.h * 0.14, 150));
|
|
114
|
+
fl.lines.forEach((line, i) => g.push(text(inner.x + inner.w / 2, ty + fl.size * (i + 0.86), fl.size, fit(line, fl.size, inner.w + 1, bw), "jb-pink", "middle")));
|
|
115
|
+
let yy = ty + fl.height + ip * 0.3;
|
|
116
|
+
if (A.code) {
|
|
117
|
+
g.push(text(inner.x + inner.w / 2, yy + codeH * 0.9, codeH, A.code, "jb-psub", "middle", 800, ' letter-spacing=".1em"'));
|
|
118
|
+
yy += codeH + ip * 0.2;
|
|
119
|
+
}
|
|
120
|
+
if (showCoach)
|
|
121
|
+
g.push(text(inner.x + inner.w / 2, yy + coachH * 0.9, coachH, fit(fill(w.judoCoach, { name: A.coach }), coachH, inner.w, bw), "jb-psub", "middle", 600));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const flagD = Math.min(inner.h * 0.8, inner.w * 0.22);
|
|
125
|
+
g.push(drawMark(teamOf(A, "road"), inner.x + flagD / 2, inner.y + inner.h / 2, flagD, f.ctx));
|
|
126
|
+
const tx = inner.x + flagD + ip, tw = inner.w - flagD - ip;
|
|
127
|
+
const codeH = A.code ? inner.h * 0.2 : 0, coachH = showCoach ? inner.h * 0.14 : 0;
|
|
128
|
+
const fl = fitLines(A.name, tw, inner.h - codeH - coachH - ip * 0.4, Math.min(inner.h * 0.42, 130));
|
|
129
|
+
const blockH = fl.height + codeH + coachH + (A.code ? ip * 0.2 : 0);
|
|
130
|
+
let yy = inner.y + (inner.h - blockH) / 2;
|
|
131
|
+
fl.lines.forEach((line, i) => g.push(text(tx, yy + fl.size * (i + 0.86), fl.size, fit(line, fl.size, tw + 1, bw), "jb-pink", "start")));
|
|
132
|
+
yy += fl.height + ip * 0.2;
|
|
133
|
+
if (A.code) {
|
|
134
|
+
g.push(text(tx, yy + codeH * 0.9, codeH, A.code, "jb-psub", "start", 800, ' letter-spacing=".1em"'));
|
|
135
|
+
yy += codeH;
|
|
136
|
+
}
|
|
137
|
+
if (showCoach)
|
|
138
|
+
g.push(text(tx, yy + coachH * 0.95, coachH, fit(fill(w.judoCoach, { name: A.coach }), coachH, tw, bw), "jb-psub", "start", 600));
|
|
139
|
+
}
|
|
140
|
+
body.push(`<g class="${side === "white" ? "jb-w" : "jb-b"}">${g.join("")}</g>`);
|
|
141
|
+
}
|
|
142
|
+
// The list.
|
|
143
|
+
const list = options.order === "first" ? [...source.wins] : [...source.wins].reverse();
|
|
144
|
+
const listRect = landscape ? { x: blockRect.x + blockRect.w + pad, y: top, w: W - blockRect.x - blockRect.w - pad * 2, h: availH } : { x: pad, y: top + blockRect.h + pad, w: W - pad * 2, h: availH - blockRect.h - pad };
|
|
145
|
+
const minRow = Math.max(40, unit * 0.06);
|
|
146
|
+
const capacity = Math.min(options.rows ?? 8, Math.max(1, Math.floor(listRect.h / minRow)));
|
|
147
|
+
const shown = list.slice(0, capacity);
|
|
148
|
+
if (shown.length) {
|
|
149
|
+
const rowH = Math.min(listRect.h / shown.length, unit * 0.15);
|
|
150
|
+
const gap = Math.max(4, rowH * 0.08);
|
|
151
|
+
const h = rowH - gap;
|
|
152
|
+
// Every round pill is as wide as the widest round name needs; where the list is narrow the result goes under the name, not beside it.
|
|
153
|
+
const stacked = listRect.w < h * 8;
|
|
154
|
+
const pillFont = h * 0.24;
|
|
155
|
+
const upper = (t) => t.toLocaleUpperCase(v.locale.tag);
|
|
156
|
+
const ip0 = h * 0.2;
|
|
157
|
+
const pillW = Math.min(listRect.w * 0.32, Math.max(...shown.map((x) => bw(upper(x.round), pillFont) * 0.8), listRect.w * 0.16) + ip0 * 1.4);
|
|
158
|
+
shown.forEach((x, i) => {
|
|
159
|
+
const y = listRect.y + i * rowH;
|
|
160
|
+
const g = [];
|
|
161
|
+
const rx = Math.round(unit * 0.01);
|
|
162
|
+
g.push(`<rect class="${x.bye ? "jr-bye" : "jr-row"}" x="${num(listRect.x)}" y="${num(y)}" width="${num(listRect.w)}" height="${num(h)}" rx="${rx}"/>`);
|
|
163
|
+
const ip = ip0;
|
|
164
|
+
g.push(`<rect class="jr-pill" x="${num(listRect.x + ip * 0.6)}" y="${num(y + h * 0.18)}" width="${num(pillW)}" height="${num(h * 0.64)}" rx="${num(h * 0.32)}"/>`);
|
|
165
|
+
const label = upper(x.round);
|
|
166
|
+
const rs = fitSize(label, pillFont, pillW - ip * 0.8, 0.4);
|
|
167
|
+
g.push(text(listRect.x + ip * 0.6 + pillW / 2, y + h / 2 + rs * 0.36, rs, fit(label, rs, pillW - ip * 0.6, bw), "jr-sub", "middle", 800, ' letter-spacing=".03em"'));
|
|
168
|
+
let cx = listRect.x + ip * 0.6 + pillW + ip;
|
|
169
|
+
const right = listRect.x + listRect.w - ip;
|
|
170
|
+
if (x.bye) {
|
|
171
|
+
const s = fitSize(upper(x.how), h * 0.34, right - cx, 0.4);
|
|
172
|
+
g.push(text(cx, y + h / 2 + s * 0.36, s, upper(x.how), "jr-sub", "start"));
|
|
173
|
+
}
|
|
174
|
+
else if (x.opponent) {
|
|
175
|
+
const d = h * 0.6;
|
|
176
|
+
g.push(drawMark(teamOf(x.opponent, `win-${i}`), cx + d / 2, y + h / 2, d, f.ctx));
|
|
177
|
+
cx += d + ip * 0.6;
|
|
178
|
+
const cs = h * 0.24;
|
|
179
|
+
if (x.opponent.code) {
|
|
180
|
+
g.push(text(cx, y + h / 2 + cs * 0.36 - (stacked ? h * 0.2 : 0), cs, x.opponent.code, "jr-sub", "start", 800));
|
|
181
|
+
cx += bw(x.opponent.code, cs) + ip * 0.6;
|
|
182
|
+
}
|
|
183
|
+
if (stacked) {
|
|
184
|
+
const avail = right - cx;
|
|
185
|
+
const ns = fitSize(x.opponent.name, Math.min(h * 0.34, 64), avail, 0.35);
|
|
186
|
+
g.push(text(cx, y + h * 0.46, ns, fit(x.opponent.name, ns, avail + 1, bw), "jr-ink", "start"));
|
|
187
|
+
if (x.how) {
|
|
188
|
+
const hs = fitSize(x.how, h * 0.22, avail, 0.4);
|
|
189
|
+
g.push(text(cx, y + h * 0.82, hs, fit(x.how, hs, avail + 1, bw), "jr-sub", "start", 700));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const howW = Math.min(listRect.w * 0.3, bw(x.how, h * 0.28) * 0.85);
|
|
194
|
+
const nameAvail = right - howW - ip * 0.6 - cx;
|
|
195
|
+
const ns = fitSize(x.opponent.name, Math.min(h * 0.38, 64), nameAvail, 0.35);
|
|
196
|
+
g.push(text(cx, y + h / 2 + ns * 0.36, ns, fit(x.opponent.name, ns, nameAvail + 1, bw), "jr-ink", "start"));
|
|
197
|
+
if (x.how) {
|
|
198
|
+
const hs = fitSize(x.how, h * 0.28, howW, 0.4);
|
|
199
|
+
g.push(text(right, y + h / 2 + hs * 0.36, hs, fit(x.how, hs, howW + 1, bw), "jr-sub", "end", 700));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
body.push(`<g class="jr-win">${g.join("")}</g>`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return wrap(f, options.title ?? w.roadTitle, describeJudoRoad(source, options), body, "road", ROAD_CSS);
|
|
207
|
+
}
|
package/dist/judo-sheets.d.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* and the blue athlete with flags and codes). Static SVG for a given state, like the scoreboard; showing the next state is the caller's job.
|
|
5
5
|
*/
|
|
6
6
|
import type { JudoAthlete, JudoBoardOptions, JudoContest } from "./judo-board.ts";
|
|
7
|
+
import { type Voice } from "./locales.ts";
|
|
8
|
+
import { type MarkContext } from "./marks.ts";
|
|
9
|
+
import type { Team } from "./types.ts";
|
|
7
10
|
/** What the contest card shows: the two athletes and the header. The scores and the clock of a `JudoContest` are not drawn. */
|
|
8
11
|
export type JudoCardContest = Pick<JudoContest, "white" | "blue" | "weight" | "round" | "mat" | "contest">;
|
|
9
12
|
/** One contest of a mat's queue: its number (`"G-24"`, `24`) and the two athletes, and optionally the weight class and the round (for a caption). */
|
|
@@ -28,6 +31,23 @@ export interface JudoQueueOptions extends Omit<JudoBoardOptions, "style" | "coac
|
|
|
28
31
|
}
|
|
29
32
|
/** The IJF-style mat colours, in turn: red, green, yellow, purple, orange, teal. Each reads with the ink the board picks for it. */
|
|
30
33
|
export declare const JUDO_MAT_COLORS: readonly string[];
|
|
34
|
+
/** White or near-black, whichever reads better on a hex colour. */
|
|
35
|
+
export declare function inkOn(color: string): string;
|
|
36
|
+
export declare function sheetStyle(scope: string, theme: "auto" | "light" | "dark", extra?: string): string;
|
|
37
|
+
export interface Frame {
|
|
38
|
+
W: number;
|
|
39
|
+
H: number;
|
|
40
|
+
v: Voice;
|
|
41
|
+
scope: string;
|
|
42
|
+
theme: "auto" | "light" | "dark";
|
|
43
|
+
ctx: MarkContext;
|
|
44
|
+
options: JudoBoardOptions;
|
|
45
|
+
}
|
|
46
|
+
export declare function frameOf(options: JudoBoardOptions, seed: unknown): Frame;
|
|
47
|
+
export declare function wrap(f: Frame, title: string, desc: string, body: string[], label: string, extraCss?: string): string;
|
|
48
|
+
export declare const text: (x: number, y: number, size: number, content: string, cls: string, anchor?: "start" | "middle" | "end", weight?: number, extra?: string) => string;
|
|
49
|
+
export declare const teamOf: (a: JudoAthlete, id: string) => Team;
|
|
50
|
+
export declare function check(a: JudoAthlete | undefined, side: string): void;
|
|
31
51
|
/** The contest card as a sentence, the drawing's description. */
|
|
32
52
|
export declare function describeJudoCard(contest: JudoCardContest, options?: Pick<JudoBoardOptions, "locale" | "words">): string;
|
|
33
53
|
/**
|
package/dist/judo-sheets.js
CHANGED
|
@@ -5,7 +5,7 @@ import { emWidth, esc, fit, fitLines, hash, num } from "./text.js";
|
|
|
5
5
|
/** The IJF-style mat colours, in turn: red, green, yellow, purple, orange, teal. Each reads with the ink the board picks for it. */
|
|
6
6
|
export const JUDO_MAT_COLORS = Object.freeze(["#d2232a", "#1e9c4c", "#e7b800", "#7a3fb4", "#e8741a", "#12877f"]);
|
|
7
7
|
/** White or near-black, whichever reads better on a hex colour. */
|
|
8
|
-
function inkOn(color) {
|
|
8
|
+
export function inkOn(color) {
|
|
9
9
|
const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim())?.[1];
|
|
10
10
|
if (!hex)
|
|
11
11
|
return "#ffffff";
|
|
@@ -14,7 +14,7 @@ function inkOn(color) {
|
|
|
14
14
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.4 ? "#ffffff" : "#15181a";
|
|
15
15
|
}
|
|
16
16
|
const HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
17
|
-
function sheetStyle(scope, theme) {
|
|
17
|
+
export function sheetStyle(scope, theme, extra = "") {
|
|
18
18
|
const base = (p, before) => rulesFor(p, `.${scope}`, before);
|
|
19
19
|
const fixed = [
|
|
20
20
|
`.${scope} .jb-w .jb-panel{fill:var(--jb-white-panel, ${WHITE_PANEL.panel});stroke:${WHITE_PANEL.edge};stroke-width:2}`,
|
|
@@ -32,13 +32,14 @@ function sheetStyle(scope, theme) {
|
|
|
32
32
|
`.${scope} .tb-initials{fill:#1f2320}`,
|
|
33
33
|
`.${scope} text{font-family:var(--jb-font, ${FONT});font-variant-numeric:tabular-nums}`,
|
|
34
34
|
].join("\n");
|
|
35
|
+
const tail = extra ? `${fixed}\n${extra.replaceAll("$S", `.${scope}`)}` : fixed;
|
|
35
36
|
if (theme === "light")
|
|
36
|
-
return `${base(LIGHT, "")}\n${
|
|
37
|
+
return `${base(LIGHT, "")}\n${tail}`;
|
|
37
38
|
if (theme === "dark")
|
|
38
|
-
return `${base(DARK, "")}\n${
|
|
39
|
-
return `${base(LIGHT, "")}\n@media (prefers-color-scheme: dark){\n${base(DARK, ':root:not([data-theme="light"]) ')}\n}\n${base(DARK, ':root[data-theme="dark"] ')}\n${
|
|
39
|
+
return `${base(DARK, "")}\n${tail}`;
|
|
40
|
+
return `${base(LIGHT, "")}\n@media (prefers-color-scheme: dark){\n${base(DARK, ':root:not([data-theme="light"]) ')}\n}\n${base(DARK, ':root[data-theme="dark"] ')}\n${tail}`;
|
|
40
41
|
}
|
|
41
|
-
function frameOf(options, seed) {
|
|
42
|
+
export function frameOf(options, seed) {
|
|
42
43
|
const v = voiceOf(options);
|
|
43
44
|
const size = typeof options.size === "string" ? JUDO_BOARD_SIZES[options.size] : options.size ?? JUDO_BOARD_SIZES.tv;
|
|
44
45
|
if (!size || !(size.width >= 200) || !(size.height >= 200))
|
|
@@ -50,20 +51,20 @@ function frameOf(options, seed) {
|
|
|
50
51
|
const ctx = { radii: new Set(), symbols: new Map(), prefix: scope, shape: "round", theme: "inherit", flagCache: new Map(), ...(options.flags ? { flags: options.flags } : {}), ...(options.flagHref ? { flagHref: options.flagHref } : {}) };
|
|
51
52
|
return { W: size.width, H: size.height, v, scope, theme, ctx, options };
|
|
52
53
|
}
|
|
53
|
-
function wrap(f, title, desc, body, label) {
|
|
54
|
+
export function wrap(f, title, desc, body, label, extraCss = "") {
|
|
54
55
|
return [
|
|
55
56
|
`<svg xmlns="http://www.w3.org/2000/svg" lang="${esc(f.v.locale.code)}" class="taisen taisen-judo ${esc(f.scope)}${f.options.className ? ` ${esc(f.options.className)}` : ""}" viewBox="0 0 ${num(f.W)} ${num(f.H)}" width="${num(f.W)}" height="${num(f.H)}" style="max-width:100%;height:auto" role="img" aria-labelledby="${esc(f.scope)}-t ${esc(f.scope)}-d" data-board="${label}">`,
|
|
56
57
|
`<title id="${esc(f.scope)}-t">${esc(title)}</title><desc id="${esc(f.scope)}-d">${esc(desc)}</desc>`,
|
|
57
|
-
`<style>${sheetStyle(f.scope, f.theme)}</style>`,
|
|
58
|
+
`<style>${sheetStyle(f.scope, f.theme, extraCss)}</style>`,
|
|
58
59
|
markDefs(f.ctx) ? `<defs>${markDefs(f.ctx)}</defs>` : "",
|
|
59
60
|
`<rect class="jb-bg" width="${num(f.W)}" height="${num(f.H)}"/>`,
|
|
60
61
|
...body,
|
|
61
62
|
"</svg>",
|
|
62
63
|
].filter(Boolean).join("\n");
|
|
63
64
|
}
|
|
64
|
-
const text = (x, y, size, content, cls, anchor = "start", weight = 800, extra = "") => `<text class="${cls}" x="${num(x)}" y="${num(y)}" font-size="${num(size)}" font-weight="${weight}" text-anchor="${anchor}"${extra}>${esc(content)}</text>`;
|
|
65
|
-
const teamOf = (a, id) => ({ id, name: a.name, abbr: (a.code ?? a.name).slice(0, 3).toUpperCase(), ...(a.flag ? { flag: a.flag } : {}), ...(a.mark ? { mark: a.mark } : {}) });
|
|
66
|
-
function check(a, side) {
|
|
65
|
+
export const text = (x, y, size, content, cls, anchor = "start", weight = 800, extra = "") => `<text class="${cls}" x="${num(x)}" y="${num(y)}" font-size="${num(size)}" font-weight="${weight}" text-anchor="${anchor}"${extra}>${esc(content)}</text>`;
|
|
66
|
+
export const teamOf = (a, id) => ({ id, name: a.name, abbr: (a.code ?? a.name).slice(0, 3).toUpperCase(), ...(a.flag ? { flag: a.flag } : {}), ...(a.mark ? { mark: a.mark } : {}) });
|
|
67
|
+
export function check(a, side) {
|
|
67
68
|
if (!a || typeof a.name !== "string" || a.name.trim() === "")
|
|
68
69
|
throw new RangeError(`taisen-brackets: the ${side} athlete needs a name`);
|
|
69
70
|
}
|
package/dist/locales.js
CHANGED
|
@@ -28,6 +28,8 @@ const EN = {
|
|
|
28
28
|
judoWeightClass: "{sex} -{w} kg", judoWeightOver: "{sex} +{w} kg", judoMen: "Men", judoWomen: "Women",
|
|
29
29
|
judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Mat {n}", judoContest: "Contest {n}", judoCoach: "Coach: {name}", judoBoardTitle: "Judo contest scoreboard",
|
|
30
30
|
judoCardTitle: "Judo contest card", judoQueueTitle: "Judo mat queue",
|
|
31
|
+
rankTitle: "Ranking", rankPoints: "points", rankPointsShort: "pts", rankChampion: "C", rankChampionLong: "Champion", rankInterim: "IC", rankInterimLong: "Interim champion",
|
|
32
|
+
rankNew: "NR", rankNewLong: "newly ranked", rankUp: "up {n}", rankDown: "down {n}", rankSame: "unchanged", rankRange: "{a}–{b} of {n}", rankNone: "No ranked athletes yet", rankJoin: ", ",
|
|
31
33
|
};
|
|
32
34
|
const FR = {
|
|
33
35
|
tbd: "À déterminer", bye: "exempt", walkover: "forfait", champion: "Champion", thirdPlace: "Match pour la troisième place", other: "Autres rencontres",
|
|
@@ -52,6 +54,8 @@ const FR = {
|
|
|
52
54
|
judoWeightClass: "{sex} -{w}\u00a0kg", judoWeightOver: "{sex} +{w}\u00a0kg", judoMen: "Hommes", judoWomen: "Femmes",
|
|
53
55
|
judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Tapis {n}", judoContest: "Combat {n}", judoCoach: "Entraîneur\u00a0: {name}", judoBoardTitle: "Tableau d’affichage d’un combat de judo",
|
|
54
56
|
judoCardTitle: "Présentation d’un combat de judo", judoQueueTitle: "Prochains combats par tapis",
|
|
57
|
+
rankTitle: "Classement", rankPoints: "points", rankPointsShort: "pts", rankChampion: "C", rankChampionLong: "Champion", rankInterim: "CI", rankInterimLong: "Champion intérimaire",
|
|
58
|
+
rankNew: "Nouv.", rankNewLong: "nouvelle entrée au classement", rankUp: "en hausse de {n}", rankDown: "en baisse de {n}", rankSame: "rang inchangé", rankRange: "{a}–{b} sur {n}", rankNone: "Aucun athlète classé pour l’instant", rankJoin: ", ",
|
|
55
59
|
};
|
|
56
60
|
const JA = {
|
|
57
61
|
tbd: "未定", bye: "シード", walkover: "不戦勝", champion: "優勝", thirdPlace: "3位決定戦", other: "その他の対戦",
|
|
@@ -76,6 +80,8 @@ const JA = {
|
|
|
76
80
|
judoWeightClass: "{sex}{w}kg級", judoWeightOver: "{sex}{w}kg超級", judoMen: "男子", judoWomen: "女子",
|
|
77
81
|
judoOsaekomi: "抑え込み", judoShido: "指導", judoMat: "第{n}試合場", judoContest: "試合 {n}", judoCoach: "コーチ:{name}", judoBoardTitle: "柔道試合のスコアボード",
|
|
78
82
|
judoCardTitle: "柔道の対戦カード", judoQueueTitle: "各試合場の次の試合",
|
|
83
|
+
rankTitle: "ランキング", rankPoints: "ポイント", rankPointsShort: "pt", rankChampion: "王者", rankChampionLong: "王者", rankInterim: "暫定", rankInterimLong: "暫定王者",
|
|
84
|
+
rankNew: "NEW", rankNewLong: "新規ランクイン", rankUp: "{n}ランク上昇", rankDown: "{n}ランク下降", rankSame: "変動なし", rankRange: "{n}人中{a}–{b}位", rankNone: "ランキング入りした選手はまだいません", rankJoin: "、",
|
|
79
85
|
};
|
|
80
86
|
const ES = {
|
|
81
87
|
tbd: "Por definir", bye: "exento", walkover: "incomparecencia", champion: "Campeón", thirdPlace: "Partido por el tercer puesto", other: "Otros partidos",
|
|
@@ -100,6 +106,8 @@ const ES = {
|
|
|
100
106
|
judoWeightClass: "{sex} -{w} kg", judoWeightOver: "{sex} +{w} kg", judoMen: "Hombres", judoWomen: "Mujeres",
|
|
101
107
|
judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Tatami {n}", judoContest: "Combate {n}", judoCoach: "Entrenador: {name}", judoBoardTitle: "Marcador de un combate de judo",
|
|
102
108
|
judoCardTitle: "Ficha de un combate de judo", judoQueueTitle: "Próximos combates por tatami",
|
|
109
|
+
rankTitle: "Clasificación", rankPoints: "puntos", rankPointsShort: "pts", rankChampion: "C", rankChampionLong: "Campeón", rankInterim: "CI", rankInterimLong: "Campeón interino",
|
|
110
|
+
rankNew: "NUEVO", rankNewLong: "nuevo en la clasificación", rankUp: "sube {n}", rankDown: "baja {n}", rankSame: "sin cambios", rankRange: "{a}–{b} de {n}", rankNone: "Aún no hay deportistas clasificados", rankJoin: ", ",
|
|
103
111
|
};
|
|
104
112
|
const DE = {
|
|
105
113
|
tbd: "offen", bye: "Freilos", walkover: "kampflos", champion: "Turniersieger", thirdPlace: "Spiel um Platz 3", other: "Weitere Begegnungen",
|
|
@@ -124,6 +132,8 @@ const DE = {
|
|
|
124
132
|
judoWeightClass: "{sex} -{w} kg", judoWeightOver: "{sex} +{w} kg", judoMen: "Herren", judoWomen: "Damen",
|
|
125
133
|
judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Matte {n}", judoContest: "Kampf {n}", judoCoach: "Trainer: {name}", judoBoardTitle: "Anzeigetafel eines Judokampfes",
|
|
126
134
|
judoCardTitle: "Kampfkarte eines Judokampfes", judoQueueTitle: "Nächste Kämpfe je Matte",
|
|
135
|
+
rankTitle: "Rangliste", rankPoints: "Punkte", rankPointsShort: "Pkt.", rankChampion: "C", rankChampionLong: "Champion", rankInterim: "IC", rankInterimLong: "Interimschampion",
|
|
136
|
+
rankNew: "NEU", rankNewLong: "neu in der Rangliste", rankUp: "{n} Plätze höher", rankDown: "{n} Plätze tiefer", rankSame: "unverändert", rankRange: "{a}–{b} von {n}", rankNone: "Noch keine platzierten Athleten", rankJoin: ", ",
|
|
127
137
|
};
|
|
128
138
|
/**
|
|
129
139
|
* The round names, English name first. The football rounds are the terms of each language's Wikipedia article on the World Cup;
|