@johnmorrisdotca/taisen-brackets 0.3.0 → 0.4.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 +19 -1
- package/NOTICE.md +3 -2
- package/README.md +140 -9
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/locales.js +30 -0
- package/dist/marks.d.ts +16 -1
- package/dist/marks.js +58 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +236 -74
- package/dist/road-entry.d.ts +2 -0
- package/dist/road-entry.js +1 -0
- package/dist/road.d.ts +28 -0
- package/dist/road.draw.d.ts +4 -0
- package/dist/road.draw.js +256 -0
- package/dist/road.js +58 -0
- package/dist/road.model.d.ts +63 -0
- package/dist/road.model.js +312 -0
- package/dist/road.types.d.ts +115 -0
- package/dist/road.types.js +1 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.js +46 -0
- package/dist/theme.d.ts +21 -2
- package/dist/theme.js +35 -11
- package/dist/types.d.ts +70 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +12 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ All notable changes to this project are written here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-10-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`roadToFinal(input, options?)`**, also the one export of the new `/road` entry point: the road to the final. The two finalists of a tournament are drawn as large round marks with their names, each one's matches on its side as both teams' round marks with the score between them in large type and FT, AET or the shoot-out under it, the rounds named down the middle on a dotted road, and the group stage as a compact row at the foot, on a `card`, `square`, `wide` or `phone`. It takes what `drawBracket` takes (a list of matchups, `{ matchups, teams }` or a layout), so one tournament draws the bracket and either finalist's road (`team` puts it on the left; a round-robin game, `round: 0`, is the group stage); or the two finalists given with their `path` and `group`, a `logo` being another name for `mark`. It gives `{ options, title, summary, totals, draw(extra?), table(options?) }`. [docs/road.md](docs/road.md) has the fields and the options.
|
|
14
|
+
- **`look: "road"`** for `drawBracket`: large round marks with a wide ring and a hairline, big bold scores in which the loser's is quiet, one size of mark and score in every box, the rounds named on pills, and boxes as high as their width lets the marks be. A box that cannot hold names is its abbreviations, and one that cannot hold those is two marks with each score under its mark. The classic look stays the default and is drawn as before; every option works in both. New custom properties `--tb-mark-ring`, `--tb-mark-edge`, `--tb-panel` and `--tb-spine`, and `ROAD_THEMES` for their fallbacks.
|
|
15
|
+
- **`size: "card"`**, 1080 by 1350, a tall post: the rounds are rows, and in the road look the boxes are made lower, or the room left over is shared between the rounds, until the card is exactly that high.
|
|
16
|
+
- **`highlightPath`**: a team's id; its path to the final is drawn as a dotted line in `--tb-win`, the ties on it ringed and the rest quieter.
|
|
17
|
+
- **`href: (tie, side?) => address`**: a link on each tie's box, or, where that gives none, on each known team's row. Each is a real `<a class="tb-link" href aria-label>` in the SVG with an accessible name, a hit area, and a hover and focus ring. An address must be http(s), relative or a `#` fragment (`safeHref`, new): `javascript:`, `data:` and the rest give no link.
|
|
18
|
+
- **`flags`** and `Team.flag`: a team's country code is drawn by a synchronous function you pass, `(code, { shape, size, theme }) => svg`, the same shape as `emblems`; `@johnmorrisdotca/hata`'s round flags are the one to use (load them first, since its `flag()` is asynchronous). It is an optional peer and a development dependency, never a runtime one. The flag is written once as a `<symbol>`; with no renderer, or one that returns nothing, throws or returns something that is not an `<svg>`, the team's `mark` or its initials are drawn. New types `FlagRenderer`, `FlagRequest`, `FlagShape`.
|
|
19
|
+
- Words for the road in all five languages (the keys starting `road`): the French and Japanese were read by native-language reviewers; the Spanish and German need one (docs/languages.md).
|
|
20
|
+
- The demo draws every sport in the road look with Hata's flags through `flags`, and has a size switcher with the card, a switch between the bracket and the road to the final, a Classic and Road style, the champion's path, and links on the boxes. The README's pictures show the road look.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- The demo's flags are team `flag` codes drawn through the `flags` option, not marks baked into the data.
|
|
25
|
+
|
|
9
26
|
## [0.3.0] - 2026-10-14
|
|
10
27
|
|
|
11
28
|
### Added
|
|
@@ -69,7 +86,8 @@ First version.
|
|
|
69
86
|
- A demo with real results read from spxn.com (the NHL 2024 playoffs, the World Cups of 2022, 1938 and 2026, the MLB 2022 postseason; sources and terms in NOTICE.md), two made-up examples (two-legged ties, a 32-team field) and a flags-only poster of the 2026 World Cup, in a playground that changes the size, look and shape.
|
|
70
87
|
- Playoff formats checked against English Wikipedia, each era citing its articles (docs/formats.md). The year is the year the playoffs are played in.
|
|
71
88
|
|
|
72
|
-
[Unreleased]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.
|
|
89
|
+
[Unreleased]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.4.0...HEAD
|
|
90
|
+
[0.4.0]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.3.0...v0.4.0
|
|
73
91
|
[0.3.0]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.2.1...v0.3.0
|
|
74
92
|
[0.2.1]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.2.0...v0.2.1
|
|
75
93
|
[0.2.0]: https://github.com/johnmorrisdotca/taisen-brackets/compare/v0.1.1...v0.2.0
|
package/NOTICE.md
CHANGED
|
@@ -15,9 +15,10 @@ The code of this package is under the MIT licence (see [LICENSE](LICENSE)). The
|
|
|
15
15
|
| The demo's MLB results (`demo/data/mlb-2022.js`) | Every game of the 2022 MLB postseason, read through spxn.com. The information used here was obtained free of charge from and is copyrighted by Retrosheet. Interested parties may contact Retrosheet at www.retrosheet.org | Retrosheet's notice, kept as it asks |
|
|
16
16
|
| How the results were read | spxn.com's public API (a PostgREST service whose `api` schema is readable with a publishable key; the key is not kept in this repository), by `scripts/fetch-demo-data.mjs`, one request at a time, a second apart, retrieved 2026-10-14 | spxn.com is the same owner's site |
|
|
17
17
|
| The demo's two-legged ties and its 32-team generic bracket | Made up for the demo: the clubs do not exist and the teams are "Team 1" to "Team 32" | Not real |
|
|
18
|
-
| The demo's
|
|
18
|
+
| The demo's road to the final (`demo/road-data.js`) | Made up for the demo and the tests: sixteen nations that do not exist, each with a flag of a few plain shapes drawn in code (no real national flag, emblem or colour scheme is copied), and two made-up hockey clubs; the results are invented | Not real; MIT |
|
|
19
|
+
| The demo's national flags (in `demo/flags-data.js`), drawn through the `flags` option | [Hata](https://github.com/johnmorrisdotca/hata) 1.5.0 (MIT code), a development dependency used by `scripts/make-flags.mjs` and the tests, and an optional peer that the package never imports. Each flag is a Wikimedia Commons file that Hata ships only when Commons states it public domain, CC0 or CC BY; the file, author and licence of each are in `FLAG_CREDITS` in that file and in Hata's manifest and NOTICE | Each flag's own licence; none of the flags used asks for a credit. Commons marks flags as official insignia, whose use some countries restrict independently of copyright |
|
|
19
20
|
| The demo's trophies (`demo/emblems.js`, and the `dougu-sports/` files the Pages build copies beside the page) | [Dougu Sports](https://github.com/johnmorrisdotca/dougu-sports) 0.1.0, a development dependency and an optional peer: generic emblems drawn for that package, which carry no league's or federation's name or mark | MIT, same owner; its NOTICE is in the package |
|
|
20
|
-
| The words of the languages (`src/locales.ts`) | The football terms (the rounds of the World Cup, extra time, the shoot-out, a replay, a walkover) were read on 2026-10-14 in the French, Spanish, German and Japanese Wikipedia articles on the World Cups of 2022, 2026 and 1938; the articles are cited in [docs/languages.md](docs/languages.md). The other words are written for this package; the French and Japanese were read by native-language reviewers, the Spanish and German were not | Wikipedia's text is CC BY-SA 4.0; only terms (single words and short phrases) are taken, no sentence or table, and the articles are credited in docs/languages.md |
|
|
21
|
+
| The words of the languages (`src/locales.ts`) | The football terms (the rounds of the World Cup, extra time, the shoot-out, a replay, a walkover) were read on 2026-10-14 in the French, Spanish, German and Japanese Wikipedia articles on the World Cups of 2022, 2026 and 1938; the articles are cited in [docs/languages.md](docs/languages.md). The other words, including those of the road to the final, are written for this package; the French and Japanese were read by native-language reviewers, the Spanish and German were not | Wikipedia's text is CC BY-SA 4.0; only terms (single words and short phrases) are taken, no sentence or table, and the articles are credited in docs/languages.md |
|
|
21
22
|
| The names of countries in each language | Not in the package: `countryName` asks `Intl.DisplayNames`, which holds Unicode CLDR's names; the demo passes it each team's ISO code. The home nations and the two states that no longer exist are written in `src/locales.ts` and `demo/data.js` | CLDR is under the Unicode licence (https://www.unicode.org/license.txt) |
|
|
22
23
|
| The demo's page | Written for this package, with the family stylesheet `demo/family.css` | MIT |
|
|
23
24
|
|
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/drawing.md">Drawing</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/drawing.md">Drawing</a> · <a href="./docs/road.md">Road to the final</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>
|
|
@@ -58,6 +58,23 @@ One layout draws them all, each from real results: the demo has a picker for the
|
|
|
58
58
|
</tr>
|
|
59
59
|
</table>
|
|
60
60
|
|
|
61
|
+
## The road look, and the road to the final
|
|
62
|
+
|
|
63
|
+
`look: "road"` draws the bracket with large round flags, big bold scores and a team's path as a dotted line, and `roadToFinal` draws the way each finalist came from the same tournament.
|
|
64
|
+
|
|
65
|
+
<table>
|
|
66
|
+
<tr>
|
|
67
|
+
<td align="center" valign="top"><img alt="The 2022 World Cup in the road look, wide: large round flags, big bold scores, the loser quiet, the rounds on pills, and Argentina's path to the final as a dotted line." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/path-desk-light.png" width="460"><br><em>The road look, wide, with Argentina's path.</em></td>
|
|
68
|
+
<td align="center" valign="top"><img alt="The 2022 World Cup in the road look on a card, 1080 by 1350: the champion at the top, then the rounds as rows." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/card-light.png" width="260"><br><em>A card, 1080 by 1350.</em></td>
|
|
69
|
+
<td align="center" valign="top"><img alt="The road to the final of the 2022 World Cup on a phone in the dark: Argentina and France as large flags, and each one's matches with the score between the two flags." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/road-phone-dark.png" width="190"><br><em>The road to the final, on a phone.</em></td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td align="center" valign="top"><img alt="The same bracket in the classic look, which stays the default." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/classic-desk-light.png" width="460"><br><em>The classic look, still the default.</em></td>
|
|
73
|
+
<td align="center" valign="top"><img alt="The road to the final of the 2024 Stanley Cup playoffs: Florida and Edmonton, each one's three series with the games won between the two logos." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/road-wide-nhl-light.png" width="460"><br><em>The road to the final, a play-off (NHL 2024).</em></td>
|
|
74
|
+
<td align="center" valign="top"><img alt="The road to the final of the 2022 World Cup on a card." src="https://raw.githubusercontent.com/johnmorrisdotca/taisen-brackets/main/docs/images/road-card-light.png" width="260"><br><em>The road to the final, a card.</em></td>
|
|
75
|
+
</tr>
|
|
76
|
+
</table>
|
|
77
|
+
|
|
61
78
|
## In 30 seconds
|
|
62
79
|
|
|
63
80
|
```sh
|
|
@@ -90,7 +107,11 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
|
|
|
90
107
|
- **Ties of every kind**: a single game (with extra time and a shoot-out), a best-of-N series (by games, or by games won), two or more legs added up, and a drawn game replayed. The winner is worked out from the games, or named.
|
|
91
108
|
- **The layout is worked out**: rounds (from who plays whom, or given), which tie feeds which, two halves meeting at the final (named for the conferences when the groups split them cleanly) or one tree left to right, byes, seeds, a third-place match placed apart, and ties that lead nowhere drawn below.
|
|
92
109
|
- **A bracket that is not played yet**: name the tree with `feeds`, or let `singleElimination` build it for any number of teams, seeded in the standard way, with byes; `advanceWinners` carries results forward.
|
|
93
|
-
- **Wide, square, phone**: 1200 by 675, 1080 by 1080, or 390 wide and as tall as it needs; the rounds are columns, or rows on a phone. Boxes show names and scores, abbreviations, or only the marks, whichever fits.
|
|
110
|
+
- **Wide, square, card, phone**: 1200 by 675, 1080 by 1080, 1080 by 1350 (a tall post), or 390 wide and as tall as it needs; the rounds are columns, or rows on a phone and a card. Boxes show names and scores, abbreviations, or only the marks, whichever fits.
|
|
111
|
+
- **The road look**: `look: "road"` draws large round flags or crests, big bold scores with the loser's quiet, the rounds named on pills, and, for one team, its path through the bracket as a dotted line. It is the look of the road-to-the-final chart below, and every other option works with it.
|
|
112
|
+
- **The road to the final**: `roadToFinal` draws the two finalists of the tournament you already have, with the ties each played on the way and the score between their flags, on a card, a square, wide or a phone.
|
|
113
|
+
- **A link on every box**: `href: (tie, side?) => address` makes each tie's box (or each team's row) a real `<a>` in the SVG with an accessible name.
|
|
114
|
+
- **Flags from a plug-in**: a team's `flag` code is drawn by the `flags` function you pass (Hata's round flags are one line), with no dependency added.
|
|
94
115
|
- **Light, dark, poster**: every colour is a CSS custom property with a fallback, so the drawing follows the page's `prefers-color-scheme` or `data-theme` and can be restyled from outside.
|
|
95
116
|
- **Team marks you supply**: a logo address or inline SVG, drawn round or square; a team with none is its initials on a colour. Round flags from [Hata](https://github.com/johnmorrisdotca/hata) go in as they are.
|
|
96
117
|
- **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.
|
|
@@ -98,7 +119,7 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
|
|
|
98
119
|
- **Formats by era**: NHL, NBA, MLB, NFL, CFL, MLS and FIFA World Cup round names, series lengths and byes.
|
|
99
120
|
- **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.
|
|
100
121
|
- **Deterministic**: no clock, no random number, no counter; the same input gives the same string, and ids come from a hash of the input.
|
|
101
|
-
- **No dependencies**, ESM with types, about
|
|
122
|
+
- **No dependencies**, ESM with types, about 90 kB packed.
|
|
102
123
|
|
|
103
124
|
## Use it in your project
|
|
104
125
|
|
|
@@ -110,7 +131,7 @@ npm install @johnmorrisdotca/taisen-brackets
|
|
|
110
131
|
# or: yarn add @johnmorrisdotca/taisen-brackets
|
|
111
132
|
```
|
|
112
133
|
|
|
113
|
-
It is ESM, and `require` works on Node 22.12 and later. It needs no DOM: the functions return strings.
|
|
134
|
+
It is ESM, and `require` works on Node 22.12 and later. It needs no DOM: the functions return strings. Four entry points: `@johnmorrisdotca/taisen-brackets` (everything), `/layout` (the layout and the helpers, no drawing), `/presets` (the formats) and `/road` (the road to the final).
|
|
114
135
|
|
|
115
136
|
## Examples
|
|
116
137
|
|
|
@@ -238,6 +259,7 @@ const bracket = [
|
|
|
238
259
|
|
|
239
260
|
drawBracket(bracket); // wide: 1200 by 675
|
|
240
261
|
drawBracket(bracket, { size: "square" }); // 1080 by 1080
|
|
262
|
+
drawBracket(bracket, { size: "card" }); // 1080 by 1350, a tall post, the rounds as rows
|
|
241
263
|
drawBracket(bracket, { size: "phone" }); // 390 wide, the rounds as rows
|
|
242
264
|
drawBracket(bracket, { size: { width: 800, height: 500 } });
|
|
243
265
|
drawBracket(bracket, { shape: "ltr" }); // one tree, left to right, the champion at the end
|
|
@@ -249,6 +271,46 @@ drawBracket(bracket, { theme: "none" }); // no <style>: your CSS
|
|
|
249
271
|
|
|
250
272
|
`detail` is `full` (names and scores), `compact` (abbreviations), `mark` (only the marks) or `auto`, which takes the fullest one that fits the box and the longest name.
|
|
251
273
|
|
|
274
|
+
### The road look, and the path of a team
|
|
275
|
+
|
|
276
|
+
`look: "road"` draws the bracket the way the road-to-the-final chart is drawn: large round marks with a wide ring, big bold scores in which the loser's is quiet, the rounds named on pills, and boxes as high as their width lets the marks be. A box that cannot hold names is its abbreviations, and one that cannot hold even those is two marks with each score under its mark. The classic look stays the default, and every option works in both looks. `highlightPath` takes a team's id and draws that team's way to the final as a dotted line, with the other ties quieter.
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
import { drawBracket } from "@johnmorrisdotca/taisen-brackets";
|
|
280
|
+
|
|
281
|
+
const bracket = [
|
|
282
|
+
{ sides: ["A", "B"], score: [1, 0], date: "1" }, { sides: ["C", "D"], score: [0, 2], date: "1" }, { sides: ["A", "D"], score: [2, 1], date: "2" },
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const svg = drawBracket(bracket, { look: "road", size: "card", highlightPath: "A" });
|
|
286
|
+
console.log(svg.includes("tb-look-road")); // true
|
|
287
|
+
console.log(svg.includes('viewBox="0 0 1080 1350"')); // true
|
|
288
|
+
console.log((svg.match(/class="tb-line tb-path"/g) ?? []).length); // 1
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
A team that lost on the way has a path that ends where it lost, and the third-place match is never on a path. Colours are custom properties: `--tb-mark-ring`, `--tb-mark-edge`, `--tb-panel` and `--tb-spine` are the road look's own, over the ones in Theming.
|
|
292
|
+
|
|
293
|
+
### A link on every box
|
|
294
|
+
|
|
295
|
+
`href` is called for each tie with the tie alone. An address makes the whole box a link; with none, it is called for each side (`0` and `1`) and a team's row with an address is a link of its own. Each is a real `<a href>` in the SVG with an accessible name (the tooltip's sentence, "Argentina 3, France 3 (…). Winner: Argentina", or the team's name before it), a hover and a focus ring, and a hit area. An address must be `http(s)`, relative or a `#` fragment: `javascript:`, `data:` and the rest are refused (`safeHref`), and the tie is drawn with no link.
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
import { drawBracket } from "@johnmorrisdotca/taisen-brackets";
|
|
299
|
+
|
|
300
|
+
const bracket = [
|
|
301
|
+
{ sides: ["A", "B"], score: [1, 0], date: "1" }, { sides: ["C", "D"], score: [0, 2], date: "1" }, { sides: ["A", "D"], score: [2, 1], date: "2" },
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
// A link on each box, to the page of its series or match.
|
|
305
|
+
const boxes = drawBracket(bracket, { href: (tie) => `/match/${tie.id}` });
|
|
306
|
+
console.log(boxes.match(/<a /g).length); // 3
|
|
307
|
+
console.log(boxes.includes('href="/match/m3"')); // true
|
|
308
|
+
|
|
309
|
+
// Or a link on each team's row.
|
|
310
|
+
const rows = drawBracket(bracket, { href: (tie, side) => (side === undefined ? undefined : `/team/${tie.sides[side]?.id}`) });
|
|
311
|
+
console.log(rows.match(/<a /g).length); // 6
|
|
312
|
+
```
|
|
313
|
+
|
|
252
314
|
### Team marks, and round flags
|
|
253
315
|
|
|
254
316
|
A team's `mark` is the address of an image (`https://`, `data:image/`, or a relative path) or inline SVG. It is drawn in a circle (`markShape: "round"`, the default), a square, or left out (`"none"`). A team with no mark is its initials on its `color`.
|
|
@@ -306,6 +368,72 @@ console.log(svg.includes('class="tb-emblem"')); // true
|
|
|
306
368
|
- **Without a renderer** an emblem id draws nothing and takes no room, and nothing throws; a trophy that is inline SVG or an address is drawn exactly as before, whatever `emblems` is. A renderer that throws, or returns something that is not an `<svg>`, draws no trophy. What it returns is cleaned like any inline SVG, and the emblem's own `<title>` (English only) is left out; the drawing's description names the champion in the bracket's language. `isEmblemId(value)` tells an emblem id from SVG and from an address.
|
|
307
369
|
- **No global switch.** There is no module-level setting: a drawing is a pure function of its options, so the renderer is passed to each call.
|
|
308
370
|
|
|
371
|
+
### Flags from a plug-in
|
|
372
|
+
|
|
373
|
+
A team can carry a country code, `flag: "ca"`, and the drawing asks the `flags` function you pass for its picture: any synchronous function from a code and a request (`{ shape, size, theme }`) to an SVG string, or to nothing for a code it has no flag for. `shape` is `"round"` (or `"square"` for `markShape: "square"`), `size` the diameter in pixels and `theme` `"light"` or `"dark"` in those looks (`"dark"` for `poster`), else `"inherit"`. [`@johnmorrisdotca/hata`](https://github.com/johnmorrisdotca/hata) has a proper round frame for every flag (`flag(code, { shape: "round" })`), and, like the emblems, it is an optional peer that Taisen never imports. Its `flag()` is asynchronous and a drawing is a pure function of its options, so load the flags first and pass a lookup.
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
import { drawBracket } from "@johnmorrisdotca/taisen-brackets";
|
|
377
|
+
import { flag } from "@johnmorrisdotca/hata/load";
|
|
378
|
+
|
|
379
|
+
const loaded = { AR: await flag("AR", { shape: "round" }), FR: await flag("FR", { shape: "round" }) };
|
|
380
|
+
const teams = [{ id: "ARG", name: "Argentina", flag: "AR" }, { id: "FRA", name: "France", flag: "FR" }];
|
|
381
|
+
const matchups = [{ sides: ["ARG", "FRA"], score: [3, 3], extraTime: true, pens: [4, 2] }];
|
|
382
|
+
|
|
383
|
+
const svg = drawBracket({ teams, matchups }, { flags: (code) => loaded[code], look: "road" });
|
|
384
|
+
console.log(svg.includes("<symbol")); // true
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
- **Each flag is written once**, as a `<symbol>`, and used wherever the team appears; the renderer is called once for each code, shape and size.
|
|
388
|
+
- **Without a renderer**, or when it returns nothing for a code, throws, or returns something that is not an `<svg>`, the team's `mark` is drawn, or its initials on its `color`. It never throws. What a renderer returns is cleaned like any inline SVG.
|
|
389
|
+
- **`logo` and `mark`**: in the road chart's finalists `logo` is another name for `mark`.
|
|
390
|
+
|
|
391
|
+
### The road to the final
|
|
392
|
+
|
|
393
|
+
`roadToFinal` draws the two finalists of a tournament and the way each came: the two large round marks at the top with their names; down the middle the rounds, the last at the top; on each finalist's side its matches, each as the round mark of both teams with the score between them in large type and FT, AET or the shoot-out under it; and the group stage as a compact row of small marks, codes and scores at the foot. It is the kind of graphic sports sites post for a World Cup final, drawn from your data: the arrangement, the type and the code are the package's own. It takes the same input as `drawBracket`, so one tournament draws the bracket and the road, and `team` puts either finalist on the left.
|
|
394
|
+
|
|
395
|
+
```js
|
|
396
|
+
import { drawBracket, roadToFinal } from "@johnmorrisdotca/taisen-brackets";
|
|
397
|
+
|
|
398
|
+
const teams = [
|
|
399
|
+
{ id: "NED", name: "Netherlands" }, { id: "ARG", name: "Argentina" },
|
|
400
|
+
{ id: "CRO", name: "Croatia" }, { id: "BRA", name: "Brazil" },
|
|
401
|
+
];
|
|
402
|
+
const matchups = [
|
|
403
|
+
{ sides: ["NED", "ARG"], score: [2, 2], extraTime: true, pens: [3, 4], date: "2022-12-09" },
|
|
404
|
+
{ sides: ["CRO", "BRA"], score: [1, 1], extraTime: true, pens: [4, 2], date: "2022-12-09" },
|
|
405
|
+
{ sides: ["ARG", "CRO"], score: [3, 0], date: "2022-12-13" },
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
const bracket = drawBracket({ teams, matchups }, { look: "road" });
|
|
409
|
+
const road = roadToFinal({ teams, matchups }, { title: "Qatar 2022", size: "wide" });
|
|
410
|
+
console.log(road.totals.champion); // Argentina
|
|
411
|
+
console.log(road.summary); // Matches played before the final: Argentina (1) and Croatia (1). The final: Argentina 3–0 Croatia (FT). Argentina won the final.
|
|
412
|
+
console.log(roadToFinal({ teams, matchups }, { team: "CRO" }).totals.finalists[0].name); // Croatia
|
|
413
|
+
console.log(road.draw({ size: "phone" }).startsWith("<svg"), bracket.startsWith("<svg")); // true true
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
A tournament's final names the finalists, each one's played ties are its road (a round-robin game, `round: 0`, is the group stage), and the final's score is written between the marks (a final not played yet says "v"). The rounds are named as the bracket names them (`roundNames`), in the language of `locale`. Or give the two finalists yourself, each a team with a `path` (the matches before the final, the earliest first) and, if you like, its `group`:
|
|
417
|
+
|
|
418
|
+
```js
|
|
419
|
+
import { roadToFinal } from "@johnmorrisdotca/taisen-brackets";
|
|
420
|
+
|
|
421
|
+
const owls = { name: "Northern Owls", abbr: "NOW", color: "#3a2d6b", path: [
|
|
422
|
+
{ round: "quarter-final", opponent: { name: "Coastal Pines", abbr: "CPN" }, score: [2, 0] },
|
|
423
|
+
{ round: "semi-final", opponent: { name: "Valley Stars", abbr: "VAL" }, score: [1, 1], decidedBy: "pens", pens: [4, 3] },
|
|
424
|
+
] };
|
|
425
|
+
const gulls = { name: "Harbour City Gulls", abbr: "HCG", color: "#1d4f91", path: [
|
|
426
|
+
{ round: "quarter-final", opponent: { name: "Summit Peaks", abbr: "SMT" }, score: [3, 1] },
|
|
427
|
+
{ round: "semi-final", opponent: { name: "Metro Bolts", abbr: "MTB" }, score: [2, 1], decidedBy: "aet" },
|
|
428
|
+
] };
|
|
429
|
+
|
|
430
|
+
const chart = roadToFinal([owls, gulls], { title: "Northern Cup 2030", final: { score: [1, 2] }, size: "square" });
|
|
431
|
+
console.log(chart.totals.rounds.join(" "), chart.totals.champion); // quarter-final semi-final Harbour City Gulls
|
|
432
|
+
console.log(chart.table().includes("<td>Semi-final</td>")); // true
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
[docs/road.md](./docs/road.md) has the fields of a match, the options, the sizes and the table.
|
|
436
|
+
|
|
309
437
|
### A table for readers the picture does not serve
|
|
310
438
|
|
|
311
439
|
```js
|
|
@@ -337,7 +465,8 @@ The string is built from your data, with every name escaped; do not put untruste
|
|
|
337
465
|
|
|
338
466
|
Everything is exported from `@johnmorrisdotca/taisen-brackets`; the layout part from `/layout`, the formats from `/presets`. The types are in the package; each function's doc comment has its details.
|
|
339
467
|
|
|
340
|
-
- `drawBracket(input, options?)`: the SVG string. `input` is a list of matchups, `{ matchups, teams }` or a layout. Options: `size`, `orientation`, `shape`, `detail`, `theme`, `background`, `title`, `subtitle`, `roundNames`, `roundLabels`, `scores`, `seeds`, `markShape`, `champion`, `trophy`, `emblems`, `emblemStyle`, `emblemTint`, `thirdPlace`, `winnerHighlight`, `locale`, `words`, `teamName`, `groupOrder`, `id`, `className`.
|
|
468
|
+
- `drawBracket(input, options?)`: the SVG string. `input` is a list of matchups, `{ matchups, teams }` or a layout. Options: `size`, `orientation`, `shape`, `detail`, `theme`, `background`, `title`, `subtitle`, `roundNames`, `roundLabels`, `scores`, `seeds`, `markShape`, `champion`, `trophy`, `emblems`, `emblemStyle`, `emblemTint`, `thirdPlace`, `winnerHighlight`, `look`, `href`, `highlightPath`, `flags`, `locale`, `words`, `teamName`, `groupOrder`, `id`, `className`.
|
|
469
|
+
- `roadToFinal(input, options?)`: the road to the final of a tournament or of two finalists: `{ options, title, summary, totals, draw(extra?), table(options?) }`. `input` is what `drawBracket` takes, or the two finalists with their paths. Options: `team`, `title`, `subtitle`, `final`, `labels`, `groupLabel`, `size`, `winner`, `codes`, `endnote`, and `locale`, `words`, `teamName`, `roundNames`, `markShape`, `flags`, `theme`, `background`, `id`, `className` as for `drawBracket`. The types `RoadChart`, `RoadFinalist`, `RoadMatch`, `RoadOptions` and the rest are exported too; it is also the one export of `/road`.
|
|
341
470
|
- `layoutBracket(input, options?)`: the rounds, halves, rows, byes and results, without drawing. `shape: "ltr"` and `groupOrder` are its options.
|
|
342
471
|
- `bracketTable(input, options?)`: the HTML table.
|
|
343
472
|
- `describeBracket(input, options?)`: the sentence a screen reader gets.
|
|
@@ -348,8 +477,8 @@ Everything is exported from `@johnmorrisdotca/taisen-brackets`; the layout part
|
|
|
348
477
|
- `isEmblemId(value)`, and the types `EmblemRenderer`, `EmblemRequest`, `EmblemStyle`: the trophy emblems (see "Emblems for the trophy").
|
|
349
478
|
- `roundName(remaining)`, `roundNamesFor(count)`, `roundLabel(layout, round, names, words)`: round names.
|
|
350
479
|
- `LOCALES`, `LOCALE_CODES`, `defineLocale`, `localeOf`, `voiceOf`, `countryName`: the languages (see Languages), a language of your own, the formats and the names of countries from `Intl`. `localizedLayout(layout, voice)` is a layout with the team names in a language.
|
|
351
|
-
- `SIZES`, `THEMES`, `WORDS`, `VERSION`: the sizes, the looks' colours, the English words, the version.
|
|
352
|
-
- `cleanSvg(markup)`, `safeUrl(url)`, `abbreviate(name)`: the rules for marks, and the short name made from a name.
|
|
480
|
+
- `SIZES`, `THEMES`, `ROAD_THEMES`, `WORDS`, `VERSION`: the sizes (`wide`, `square`, `card`, `phone`), the looks' colours, the road look's extra colours, the English words, the version.
|
|
481
|
+
- `cleanSvg(markup)`, `safeUrl(url)`, `safeHref(url)`, `abbreviate(name)`: the rules for marks and for the addresses of links, and the short name made from a name. The types `FlagRenderer`, `FlagRequest` and `FlagShape` are the flags plug-in's (see "Flags from a plug-in").
|
|
353
482
|
|
|
354
483
|
## Languages
|
|
355
484
|
|
|
@@ -453,7 +582,7 @@ The layout began as the playoff bracket of [spxn.com](https://spxn.com), the his
|
|
|
453
582
|
### The family
|
|
454
583
|
|
|
455
584
|
<!-- family:start (made by scripts/family-readme.mjs from scripts/family-template.mjs; change those, not this) -->
|
|
456
|
-
Taisen Brackets is one of twenty-
|
|
585
|
+
Taisen Brackets is one of twenty-eight packages, each made for the same site, each at
|
|
457
586
|
[github.com/johnmorrisdotca](https://github.com/johnmorrisdotca). The code of every one is MIT.
|
|
458
587
|
|
|
459
588
|
- [Korokoro](https://github.com/johnmorrisdotca/korokoro) (コロコロ): dice, with notation, exact odds, real sounds and the dice of many games. [Demo](https://johnmorrisdotca.github.io/korokoro/).
|
|
@@ -482,8 +611,10 @@ Taisen Brackets is one of twenty-six packages, each made for the same site, each
|
|
|
482
611
|
- [Houseki](https://github.com/johnmorrisdotca/houseki) (宝石): gem and stone matching puzzles: falling triplets, stone collapse, colour chains and gem swap. [Demo](https://johnmorrisdotca.github.io/houseki/).
|
|
483
612
|
- [Kyogijo Stadiums](https://github.com/johnmorrisdotca/kyogijo-stadiums) (競技場): sports fields, rinks, courts and ballparks drawn to scale as SVG, with each team and the score on them. [Demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/).
|
|
484
613
|
- [Taisen Brackets](https://github.com/johnmorrisdotca/taisen-brackets) (対戦): a sport-agnostic tournament bracket, drawn as SVG, from a list of ties. [Demo](https://johnmorrisdotca.github.io/taisen-brackets/).
|
|
614
|
+
- [Dougu Sports](https://github.com/johnmorrisdotca/dougu-sports) (道具): sports emblems as SVG: trophies, medals, crests, and the ball and gear of every sport, with optional 3D turning. [Demo](https://johnmorrisdotca.github.io/dougu-sports/).
|
|
615
|
+
- [Senshu Jerseys](https://github.com/johnmorrisdotca/senshu-jerseys) (選手): sports jerseys, sweaters, kits, caps, helmets and retired-number banners as SVG, front and back, with optional 2.5D turning. [Demo](https://johnmorrisdotca.github.io/senshu-jerseys/).
|
|
485
616
|
|
|
486
|
-
**This package is Taisen Brackets.** The demos of all twenty-
|
|
617
|
+
**This package is Taisen Brackets.** The demos of all twenty-eight share one header and footer, so each links the rest.
|
|
487
618
|
<!-- family:end -->
|
|
488
619
|
|
|
489
620
|
## Development
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ export * from "./layout-entry.ts";
|
|
|
2
2
|
export { drawBracket, describeBracket, localizedLayout, SIZES } from "./render.ts";
|
|
3
3
|
export { bracketTable } from "./table.ts";
|
|
4
4
|
export type { TableOptions } from "./table.ts";
|
|
5
|
-
export {
|
|
6
|
-
export type {
|
|
7
|
-
export {
|
|
5
|
+
export { roadToFinal } from "./road.ts";
|
|
6
|
+
export type { RoadChart, RoadDecided, RoadFinal, RoadFinalist, RoadGroupMatch, RoadInput, RoadMatch, RoadOptions, RoadRecord, RoadScore, RoadSize, RoadTeam, RoadTotals } from "./road.ts";
|
|
7
|
+
export { THEMES, ROAD_THEMES } from "./theme.ts";
|
|
8
|
+
export type { Palette, RoadPalette } from "./theme.ts";
|
|
9
|
+
export { cleanSvg, safeUrl, safeHref, isEmblemId } from "./marks.ts";
|
|
8
10
|
export { abbreviate } from "./text.ts";
|
|
9
11
|
export { VERSION } from "./version.ts";
|
|
10
12
|
export { playoffFormat, playoffEras, LEAGUES, LEAGUE_EMBLEMS } from "./presets.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from "./layout-entry.js";
|
|
2
2
|
export { drawBracket, describeBracket, localizedLayout, SIZES } from "./render.js";
|
|
3
3
|
export { bracketTable } from "./table.js";
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
4
|
+
export { roadToFinal } from "./road.js";
|
|
5
|
+
export { THEMES, ROAD_THEMES } from "./theme.js";
|
|
6
|
+
export { cleanSvg, safeUrl, safeHref, isEmblemId } from "./marks.js";
|
|
6
7
|
export { abbreviate } from "./text.js";
|
|
7
8
|
export { VERSION } from "./version.js";
|
|
8
9
|
export { playoffFormat, playoffEras, LEAGUES, LEAGUE_EMBLEMS } from "./presets.js";
|
package/dist/locales.js
CHANGED
|
@@ -13,6 +13,12 @@ const EN = {
|
|
|
13
13
|
tieVs: "{a} v {b}", tieScore: "{a} {sa}, {b} {sb}", scoreText: "{a}–{b}", winnerIs: "Winner: {name}", championIs: "Champion: {name}", finalLine: "{round}: {tie}",
|
|
14
14
|
summary: "{n} teams, {r} rounds", summaryOne: "{n} teams, 1 round", sep: ". ", end: ".", note: " ({notes})", noteJoin: ", ",
|
|
15
15
|
columnRound: "Round", columnTie: "Tie", columnNote: "Note", columnDate: "Date", title: "Knockout bracket",
|
|
16
|
+
roadTitle: "Road to the final", roadVs: "v", roadGroup: "Group stage", roadFt: "FT", roadAet: "AET", roadPens: "{a}–{b} pens",
|
|
17
|
+
roadWon: "Won", roadLost: "Lost", roadDrew: "Drew", roadQuarterFinal: "Quarter-final", roadSemiFinal: "Semi-final",
|
|
18
|
+
roadTableTeam: "Team", roadTableOpponent: "Opponent", roadTableScore: "Score", roadTableDecided: "Decided", roadTableResult: "Result",
|
|
19
|
+
roadCaption: "{a} and {b}: every match on the way to the final. Scores are written with the team in the first column first.",
|
|
20
|
+
roadName: "{a} and {b}: {title}", roadSummary: "Matches played before the final: {a} ({an}) and {b} ({bn}).",
|
|
21
|
+
roadFinal: "The final: {a} {score} {b} ({how}).", roadChampion: "{name} won the final.", roadToPlay: "The final is still to be played.",
|
|
16
22
|
};
|
|
17
23
|
const FR = {
|
|
18
24
|
tbd: "À déterminer", bye: "exempt", walkover: "forfait", champion: "Champion", thirdPlace: "Match pour la troisième place", other: "Autres rencontres",
|
|
@@ -22,6 +28,12 @@ const FR = {
|
|
|
22
28
|
tieVs: "{a} contre {b}", tieScore: "{a} {sa}, {b} {sb}", scoreText: "{a}–{b}", winnerIs: "Vainqueur : {name}", championIs: "Champion : {name}", finalLine: "{round} : {tie}",
|
|
23
29
|
summary: "{n} équipes, {r} tours", summaryOne: "{n} équipes, 1 tour", sep: ". ", end: ".", note: " ({notes})", noteJoin: ", ",
|
|
24
30
|
columnRound: "Tour", columnTie: "Rencontre", columnNote: "Note", columnDate: "Date", title: "Tableau à élimination directe",
|
|
31
|
+
roadTitle: "Parcours vers la finale", roadVs: "vs", roadGroup: "Phase de groupes", roadFt: "t.r.", roadAet: "a.p.", roadPens: "{a}–{b} t.a.b.",
|
|
32
|
+
roadWon: "Victoire", roadLost: "Défaite", roadDrew: "Nul", roadQuarterFinal: "Quart de finale", roadSemiFinal: "Demi-finale",
|
|
33
|
+
roadTableTeam: "Équipe", roadTableOpponent: "Adversaire", roadTableScore: "Score", roadTableDecided: "Décidé en", roadTableResult: "Résultat",
|
|
34
|
+
roadCaption: "{a} et {b} : tous les matchs jusqu’à la finale. Chaque score commence par celui de l’équipe de la première colonne.",
|
|
35
|
+
roadName: "{a} et {b} : {title}", roadSummary: "Matchs joués avant la finale : {a} ({an}) et {b} ({bn}).",
|
|
36
|
+
roadFinal: "La finale : {a} {score} {b} ({how}).", roadChampion: "{name} a remporté la finale.", roadToPlay: "La finale reste à jouer.",
|
|
25
37
|
};
|
|
26
38
|
const JA = {
|
|
27
39
|
tbd: "未定", bye: "シード", walkover: "不戦勝", champion: "優勝", thirdPlace: "3位決定戦", other: "その他の対戦",
|
|
@@ -31,6 +43,12 @@ const JA = {
|
|
|
31
43
|
tieVs: "{a} 対 {b}", tieScore: "{a} {sa}–{sb} {b}", scoreText: "{a}–{b}", winnerIs: "勝者:{name}", championIs: "優勝:{name}", finalLine: "{round}:{tie}",
|
|
32
44
|
summary: "{n}チーム、{r}ラウンド", summaryOne: "{n}チーム、1ラウンド", sep: "。", end: "。", note: "({notes})", noteJoin: "、",
|
|
33
45
|
columnRound: "ラウンド", columnTie: "対戦", columnNote: "備考", columnDate: "日付", title: "トーナメント表",
|
|
46
|
+
roadTitle: "決勝への道", roadVs: "対", roadGroup: "グループステージ", roadFt: "規定時間", roadAet: "延長", roadPens: "PK戦 {a}–{b}",
|
|
47
|
+
roadWon: "勝ち", roadLost: "負け", roadDrew: "引き分け", roadQuarterFinal: "準々決勝", roadSemiFinal: "準決勝",
|
|
48
|
+
roadTableTeam: "チーム", roadTableOpponent: "対戦相手", roadTableScore: "スコア", roadTableDecided: "決着", roadTableResult: "結果",
|
|
49
|
+
roadCaption: "{a}と{b}:決勝までの全試合。スコアは「チーム」列のチームの得点を先に書いています。",
|
|
50
|
+
roadName: "{a}と{b}:{title}", roadSummary: "{a}は決勝までに{an}試合、{b}は{bn}試合を戦いました。",
|
|
51
|
+
roadFinal: "決勝は{a} {score} {b}({how})でした。", roadChampion: "{name}が優勝しました。", roadToPlay: "決勝はまだ行われていません。",
|
|
34
52
|
};
|
|
35
53
|
const ES = {
|
|
36
54
|
tbd: "Por definir", bye: "exento", walkover: "incomparecencia", champion: "Campeón", thirdPlace: "Partido por el tercer puesto", other: "Otros partidos",
|
|
@@ -40,6 +58,12 @@ const ES = {
|
|
|
40
58
|
tieVs: "{a} vs. {b}", tieScore: "{a} {sa}, {b} {sb}", scoreText: "{a}–{b}", winnerIs: "Ganador: {name}", championIs: "Campeón: {name}", finalLine: "{round}: {tie}",
|
|
41
59
|
summary: "{n} equipos, {r} rondas", summaryOne: "{n} equipos, 1 ronda", sep: ". ", end: ".", note: " ({notes})", noteJoin: ", ",
|
|
42
60
|
columnRound: "Ronda", columnTie: "Partido", columnNote: "Nota", columnDate: "Fecha", title: "Cuadro de eliminatorias",
|
|
61
|
+
roadTitle: "El camino a la final", roadVs: "vs.", roadGroup: "Fase de grupos", roadFt: "T. reg.", roadAet: "Prórroga", roadPens: "{a}–{b} en los penales",
|
|
62
|
+
roadWon: "Ganó", roadLost: "Perdió", roadDrew: "Empató", roadQuarterFinal: "Cuartos de final", roadSemiFinal: "Semifinal",
|
|
63
|
+
roadTableTeam: "Equipo", roadTableOpponent: "Rival", roadTableScore: "Marcador", roadTableDecided: "Decidido en", roadTableResult: "Resultado",
|
|
64
|
+
roadCaption: "{a} y {b}: todos los partidos hasta la final. Los marcadores empiezan por el equipo de la primera columna.",
|
|
65
|
+
roadName: "{a} y {b}: {title}", roadSummary: "Partidos jugados hasta la final: {a} ({an}) y {b} ({bn}).",
|
|
66
|
+
roadFinal: "La final: {a} {score} {b} ({how}).", roadChampion: "{name} ganó la final.", roadToPlay: "La final aún no se ha jugado.",
|
|
43
67
|
};
|
|
44
68
|
const DE = {
|
|
45
69
|
tbd: "offen", bye: "Freilos", walkover: "kampflos", champion: "Turniersieger", thirdPlace: "Spiel um Platz 3", other: "Weitere Begegnungen",
|
|
@@ -49,6 +73,12 @@ const DE = {
|
|
|
49
73
|
tieVs: "{a} gegen {b}", tieScore: "{a} {sa}, {b} {sb}", scoreText: "{a}:{b}", winnerIs: "Sieger: {name}", championIs: "Turniersieger: {name}", finalLine: "{round}: {tie}",
|
|
50
74
|
summary: "{n} Teams, {r} Runden", summaryOne: "{n} Teams, 1 Runde", sep: ". ", end: ".", note: " ({notes})", noteJoin: ", ",
|
|
51
75
|
columnRound: "Runde", columnTie: "Begegnung", columnNote: "Hinweis", columnDate: "Datum", title: "Turnierbaum",
|
|
76
|
+
roadTitle: "Der Weg ins Finale", roadVs: "vs.", roadGroup: "Gruppenphase", roadFt: "reg. Zeit", roadAet: "n. V.", roadPens: "{a}:{b} i. E.",
|
|
77
|
+
roadWon: "Sieg", roadLost: "Niederlage", roadDrew: "Remis", roadQuarterFinal: "Viertelfinale", roadSemiFinal: "Halbfinale",
|
|
78
|
+
roadTableTeam: "Team", roadTableOpponent: "Gegner", roadTableScore: "Ergebnis", roadTableDecided: "Entschieden", roadTableResult: "Ausgang",
|
|
79
|
+
roadCaption: "{a} und {b}: alle Spiele auf dem Weg ins Finale. Die Ergebnisse beginnen mit dem Team der ersten Spalte.",
|
|
80
|
+
roadName: "{a} und {b}: {title}", roadSummary: "Spiele bis zum Finale: {a} ({an}) und {b} ({bn}).",
|
|
81
|
+
roadFinal: "Das Finale: {a} {score} {b} ({how}).", roadChampion: "{name} gewann das Finale.", roadToPlay: "Das Finale steht noch aus.",
|
|
52
82
|
};
|
|
53
83
|
/**
|
|
54
84
|
* The round names, English name first. The football rounds are the terms of each language's Wikipedia article on the World Cup;
|
package/dist/marks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DrawOptions, Team } from "./types.ts";
|
|
1
|
+
import type { DrawOptions, FlagRenderer, FlagRequest, Team } from "./types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Takes inline SVG markup from a caller and makes it safe to put inside a drawing: the outer `<svg>`'s viewBox is kept,
|
|
4
4
|
* and scripts, event handlers, `<style>`, `<foreignObject>`, comments and `javascript:` addresses are removed. Returns the
|
|
@@ -34,7 +34,22 @@ export interface MarkContext {
|
|
|
34
34
|
}>;
|
|
35
35
|
prefix: string;
|
|
36
36
|
shape: "round" | "square" | "none";
|
|
37
|
+
/** The look: `road` draws a team's mark with a wide ring and a hairline outside it. */
|
|
38
|
+
look?: "classic" | "road";
|
|
39
|
+
/** Draws a team's `flag` code; with none a team's `mark` or initials are drawn. */
|
|
40
|
+
flags?: FlagRenderer;
|
|
41
|
+
/** The theme a flag renderer is told about. */
|
|
42
|
+
theme?: FlagRequest["theme"];
|
|
43
|
+
/** The flags already asked for, by code, shape and size, so that a renderer is called once for each. */
|
|
44
|
+
flagCache?: Map<string, {
|
|
45
|
+
viewBox: string;
|
|
46
|
+
inner: string;
|
|
47
|
+
} | null>;
|
|
37
48
|
}
|
|
49
|
+
/** Whether an address may be a link in a drawing: http(s), a relative path or a `#` fragment. `javascript:`, `data:` and every other scheme are refused. */
|
|
50
|
+
export declare function safeHref(url: string): boolean;
|
|
51
|
+
/** The `<defs>` content the marks drawn with a context need: a circular clip for each radius, and each inline mark or flag once as a `<symbol>`. Written after the marks are drawn. */
|
|
52
|
+
export declare function markDefs(ctx: MarkContext): string;
|
|
38
53
|
/** The id of the circular clip for a radius. */
|
|
39
54
|
export declare const clipId: (prefix: string, r: number) => string;
|
|
40
55
|
/**
|
package/dist/marks.js
CHANGED
|
@@ -78,6 +78,44 @@ export function safeUrl(url) {
|
|
|
78
78
|
return true;
|
|
79
79
|
return !/^[a-z][a-z0-9+.-]*:/i.test(u) && !u.startsWith("//") ? u.length > 0 : false;
|
|
80
80
|
}
|
|
81
|
+
/** Whether an address may be a link in a drawing: http(s), a relative path or a `#` fragment. `javascript:`, `data:` and every other scheme are refused. */
|
|
82
|
+
export function safeHref(url) {
|
|
83
|
+
const u = url.trim();
|
|
84
|
+
// eslint-disable-next-line no-control-regex
|
|
85
|
+
if (u.length === 0 || /[\u0000-\u001f\u007f]/.test(u))
|
|
86
|
+
return false;
|
|
87
|
+
if (/^https?:\/\//i.test(u))
|
|
88
|
+
return true;
|
|
89
|
+
return !/^[a-z][a-z0-9+.-]*:/i.test(u) && !u.startsWith("//");
|
|
90
|
+
}
|
|
91
|
+
/** The flag a renderer makes for a code, cleaned like any inline SVG, or `null` when there is no renderer, it has no flag for the code, it throws, or what it returns is not an `<svg>`. */
|
|
92
|
+
function flagOf(code, size, ctx) {
|
|
93
|
+
if (typeof ctx.flags !== "function" || typeof code !== "string" || code.trim() === "")
|
|
94
|
+
return null;
|
|
95
|
+
const shape = ctx.shape === "square" ? "square" : "round";
|
|
96
|
+
const key = `${code.trim()}|${shape}|${Math.round(size)}`;
|
|
97
|
+
const cache = ctx.flagCache;
|
|
98
|
+
if (cache?.has(key))
|
|
99
|
+
return cache.get(key) ?? null;
|
|
100
|
+
let made = null;
|
|
101
|
+
try {
|
|
102
|
+
const markup = ctx.flags(code.trim(), { shape, size: Math.round(size), theme: ctx.theme ?? "inherit" });
|
|
103
|
+
if (typeof markup === "string")
|
|
104
|
+
made = cleanSvg(markup);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
made = null;
|
|
108
|
+
}
|
|
109
|
+
cache?.set(key, made);
|
|
110
|
+
return made;
|
|
111
|
+
}
|
|
112
|
+
/** The `<defs>` content the marks drawn with a context need: a circular clip for each radius, and each inline mark or flag once as a `<symbol>`. Written after the marks are drawn. */
|
|
113
|
+
export function markDefs(ctx) {
|
|
114
|
+
return [
|
|
115
|
+
...[...ctx.radii].sort((a, b) => a - b).map((r) => `<clipPath id="${clipId(ctx.prefix, r)}"><circle r="${num(r)}"/></clipPath>`),
|
|
116
|
+
...[...ctx.symbols.values()].map((s) => `<symbol id="${s.id}" viewBox="${esc(s.viewBox)}" preserveAspectRatio="xMidYMid slice">${s.inner}</symbol>`),
|
|
117
|
+
].join("");
|
|
118
|
+
}
|
|
81
119
|
/** The id of the circular clip for a radius. */
|
|
82
120
|
export const clipId = (prefix, r) => `${prefix}-c${String(num(r)).replace(".", "_")}`;
|
|
83
121
|
/**
|
|
@@ -94,7 +132,26 @@ export function drawMark(team, cx, cy, size, ctx, abbrText) {
|
|
|
94
132
|
ctx.radii.add(r);
|
|
95
133
|
const wrapOpen = clip ? `<g class="tb-mark" transform="translate(${num(cx)} ${num(cy)})" clip-path="url(#${clipId(ctx.prefix, r)})">` : `<g class="tb-mark">`;
|
|
96
134
|
const ox = clip ? -r : x, oy = clip ? -r : y;
|
|
97
|
-
const
|
|
135
|
+
const road = ctx.look === "road";
|
|
136
|
+
const rw = Math.max(r * 0.09, 0.5);
|
|
137
|
+
const hair = num(Math.max(rw * 0.3, 0.3));
|
|
138
|
+
const ring = road
|
|
139
|
+
? clip
|
|
140
|
+
? `<circle class="tb-ring tb-ring-r" cx="${num(cx)}" cy="${num(cy)}" r="${num(r - rw / 2)}" stroke-width="${num(rw)}"/><circle class="tb-edge" cx="${num(cx)}" cy="${num(cy)}" r="${num(r)}" stroke-width="${hair}"/>`
|
|
141
|
+
: `<rect class="tb-ring tb-ring-r" x="${num(x + rw / 2)}" y="${num(y + rw / 2)}" width="${num(size - rw)}" height="${num(size - rw)}" rx="2" stroke-width="${num(rw)}"/><rect class="tb-edge" x="${num(x)}" y="${num(y)}" width="${num(size)}" height="${num(size)}" rx="2" stroke-width="${hair}"/>`
|
|
142
|
+
: clip
|
|
143
|
+
? `<circle class="tb-ring" cx="${num(cx)}" cy="${num(cy)}" r="${num(r)}"/>`
|
|
144
|
+
: `<rect class="tb-ring" x="${num(x)}" y="${num(y)}" width="${num(size)}" height="${num(size)}" rx="2"/>`;
|
|
145
|
+
const flag = flagOf(team.flag, size, ctx);
|
|
146
|
+
if (flag) {
|
|
147
|
+
const key = `flag:${flag.viewBox}:${flag.inner}`;
|
|
148
|
+
let symbol = ctx.symbols.get(key);
|
|
149
|
+
if (!symbol) {
|
|
150
|
+
symbol = { id: `${ctx.prefix}-m${ctx.symbols.size + 1}`, ...flag };
|
|
151
|
+
ctx.symbols.set(key, symbol);
|
|
152
|
+
}
|
|
153
|
+
return `${wrapOpen}<use href="#${symbol.id}" x="${num(ox)}" y="${num(oy)}" width="${num(size)}" height="${num(size)}"/></g>${ring}`;
|
|
154
|
+
}
|
|
98
155
|
const mark = team.mark?.trim();
|
|
99
156
|
if (mark) {
|
|
100
157
|
if (mark.startsWith("<")) {
|
package/dist/render.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BracketInput, BracketLayout, DrawOptions, Matchup, Size, SizeName } from "./types.ts";
|
|
2
2
|
import { type Voice } from "./locales.ts";
|
|
3
|
-
/** The sizes by name. A phone's drawing is 390 wide and as tall as the bracket needs. */
|
|
3
|
+
/** The sizes by name. A phone's drawing is 390 wide and as tall as the bracket needs; a card is 1080 by 1350, a tall post. */
|
|
4
4
|
export declare const SIZES: Readonly<Record<SizeName, Size>>;
|
|
5
5
|
/** The layout with each team's name in the language: what the drawing, the table and the description are written from. */
|
|
6
6
|
export declare function localizedLayout(layout: BracketLayout, v: Voice): BracketLayout;
|