@johnmorrisdotca/taisen-brackets 0.1.0 → 0.2.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 +24 -2
- package/NOTICE.md +6 -1
- package/README.md +106 -9
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/layout-entry.d.ts +3 -1
- package/dist/layout-entry.js +2 -1
- package/dist/locales.d.ts +42 -0
- package/dist/locales.js +176 -0
- package/dist/matchup.d.ts +9 -6
- package/dist/matchup.js +51 -22
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/render.d.ts +6 -6
- package/dist/render.js +61 -56
- package/dist/rounds.d.ts +2 -2
- package/dist/rounds.js +4 -4
- package/dist/table.d.ts +4 -5
- package/dist/table.js +17 -13
- package/dist/theme.js +1 -1
- package/dist/types.d.ts +60 -14
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -3
- package/dist/words.d.ts +0 -7
- package/dist/words.js +0 -33
package/dist/matchup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { abbreviate } from "./text.js";
|
|
2
|
-
import { fill } from "./
|
|
2
|
+
import { fill } from "./locales.js";
|
|
3
3
|
/** A bracket's teams: the list given, plus a team made from every id that a matchup names and the list does not. */
|
|
4
4
|
export function collectTeams(matchups, given = []) {
|
|
5
5
|
const teams = new Map();
|
|
@@ -98,50 +98,79 @@ export function resolveMatchup(m, index, teams) {
|
|
|
98
98
|
date: m.date ?? m.games?.[0]?.date ?? m.legs?.[0]?.date ?? "",
|
|
99
99
|
no: m.no ?? null,
|
|
100
100
|
third: !!m.third,
|
|
101
|
+
walkover: !!m.walkover,
|
|
101
102
|
note: m.note ?? null,
|
|
102
103
|
};
|
|
103
104
|
}
|
|
104
105
|
/** The short name of a team for a small box. */
|
|
105
|
-
export const abbrOf = (team,
|
|
106
|
-
/** The pieces of a note under a tie's score: "a.e.t.", "pens", "in 7", "agg.". */
|
|
107
|
-
export function noteParts(tie,
|
|
106
|
+
export const abbrOf = (team, v) => (team ? team.abbr ?? abbreviate(v.team(team)) : v.words.tbd);
|
|
107
|
+
/** The pieces of a note under a tie's score, short: "a.e.t.", "pens", "in 7", "agg.". */
|
|
108
|
+
export function noteParts(tie, v) {
|
|
108
109
|
const r = tie.result;
|
|
110
|
+
const w = v.words;
|
|
109
111
|
const out = [];
|
|
110
112
|
if (r.kind === "series") {
|
|
111
113
|
if (r.decided && r.played > 0)
|
|
112
|
-
out.push(fill(
|
|
114
|
+
out.push(fill(w.inGames, { n: v.number(r.played) }));
|
|
113
115
|
else if (r.bestOf)
|
|
114
|
-
out.push(fill(
|
|
116
|
+
out.push(fill(w.bestOf, { n: v.number(r.bestOf), w: v.number(Math.ceil(r.bestOf / 2)) }));
|
|
115
117
|
}
|
|
116
118
|
else {
|
|
117
119
|
if (r.kind === "aggregate")
|
|
118
|
-
out.push(
|
|
120
|
+
out.push(w.aggregate);
|
|
119
121
|
if (r.kind === "replay")
|
|
120
|
-
out.push(
|
|
122
|
+
out.push(w.replay);
|
|
121
123
|
if (r.extraTime)
|
|
122
|
-
out.push(
|
|
124
|
+
out.push(w.aet);
|
|
123
125
|
if (r.pens)
|
|
124
|
-
out.push(
|
|
126
|
+
out.push(w.pens);
|
|
125
127
|
}
|
|
128
|
+
if (tie.walkover)
|
|
129
|
+
out.push(w.walkover);
|
|
126
130
|
if (tie.note)
|
|
127
131
|
out.push(tie.note);
|
|
128
132
|
return out;
|
|
129
133
|
}
|
|
130
|
-
/**
|
|
131
|
-
export function
|
|
132
|
-
const name = (t) => (t ? t.name : words.tbd);
|
|
133
|
-
const [a, b] = tie.sides;
|
|
134
|
+
/** The notes of a tie written out, for a sentence or a table: "after extra time", "4–2 on penalties". */
|
|
135
|
+
export function noteLong(tie, v) {
|
|
134
136
|
const r = tie.result;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
const w = v.words;
|
|
138
|
+
const out = [];
|
|
139
|
+
if (r.kind === "series") {
|
|
140
|
+
if (r.decided && r.played > 0)
|
|
141
|
+
out.push(fill(w.inGames, { n: v.number(r.played) }));
|
|
142
|
+
else if (r.bestOf)
|
|
143
|
+
out.push(fill(w.bestOf, { n: v.number(r.bestOf), w: v.number(Math.ceil(r.bestOf / 2)) }));
|
|
144
|
+
}
|
|
138
145
|
else {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
|
|
146
|
+
if (r.kind === "aggregate")
|
|
147
|
+
out.push(w.aggregateLong);
|
|
148
|
+
if (r.kind === "replay")
|
|
149
|
+
out.push(w.replay);
|
|
150
|
+
if (r.extraTime)
|
|
151
|
+
out.push(w.aetLong);
|
|
152
|
+
if (r.pens)
|
|
153
|
+
out.push(fill(w.pensScore, { a: v.number(r.pens[0]), b: v.number(r.pens[1]) }));
|
|
143
154
|
}
|
|
155
|
+
if (tie.walkover)
|
|
156
|
+
out.push(w.walkover);
|
|
157
|
+
if (tie.note)
|
|
158
|
+
out.push(tie.note);
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
/** A tie in a sentence, for a tooltip, a table and a description: "Argentina 3, France 3 (after extra time, 4–2 on penalties). Winner: Argentina". */
|
|
162
|
+
export function describeTie(tie, v) {
|
|
163
|
+
const w = v.words;
|
|
164
|
+
const name = (t) => (t ? v.team(t) : w.tbd);
|
|
165
|
+
const [a, b] = tie.sides;
|
|
166
|
+
const r = tie.result;
|
|
167
|
+
let text = r.score
|
|
168
|
+
? fill(w.tieScore, { a: name(a), b: name(b), sa: v.number(r.score[0]), sb: v.number(r.score[1]) })
|
|
169
|
+
: fill(w.tieVs, { a: name(a), b: name(b) });
|
|
170
|
+
const notes = noteLong(tie, v);
|
|
171
|
+
if (notes.length)
|
|
172
|
+
text += fill(w.note, { notes: notes.join(w.noteJoin) });
|
|
144
173
|
if (tie.winner !== null)
|
|
145
|
-
text +=
|
|
174
|
+
text += `${w.sep}${fill(w.winnerIs, { name: name(tie.sides[tie.winner]) })}`;
|
|
146
175
|
return text;
|
|
147
176
|
}
|
package/dist/presets.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Playoff formats by era: the round names, the length of each round's series and who has a bye, for the five North
|
|
3
3
|
* American leagues and the FIFA World Cup. Data only; nothing here draws. Each era was checked against the English
|
|
4
4
|
* Wikipedia articles in its `sources` (2026-10-13); where a season did something of its own (a strike, a pandemic) it has
|
|
5
|
-
* its own entry. The year is the year the playoffs are played in
|
|
5
|
+
* its own entry. The year is the year the playoffs are played in (the NHL's and the NBA's 2024 are the 2024 playoffs, of the 2023-24 season), except for the NFL, whose year is its season's: 2024 is the season played in 2024, with playoffs in January and February 2025.
|
|
6
6
|
*/
|
|
7
7
|
export type League = "nhl" | "nba" | "mlb" | "nfl" | "cfl" | "worldcup";
|
|
8
8
|
export interface PlayoffRound {
|
package/dist/presets.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Playoff formats by era: the round names, the length of each round's series and who has a bye, for the five North
|
|
3
3
|
* American leagues and the FIFA World Cup. Data only; nothing here draws. Each era was checked against the English
|
|
4
4
|
* Wikipedia articles in its `sources` (2026-10-13); where a season did something of its own (a strike, a pandemic) it has
|
|
5
|
-
* its own entry. The year is the year the playoffs are played in
|
|
5
|
+
* its own entry. The year is the year the playoffs are played in (the NHL's and the NBA's 2024 are the 2024 playoffs, of the 2023-24 season), except for the NFL, whose year is its season's: 2024 is the season played in 2024, with playoffs in January and February 2025.
|
|
6
6
|
*/
|
|
7
7
|
/** The address of English Wikipedia articles, by title. */
|
|
8
8
|
const wiki = (...titles) => titles.map((t) => `https://en.wikipedia.org/wiki/${t.replace(/ /g, "_")}`);
|
package/dist/render.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { BracketInput, BracketLayout, DrawOptions, Matchup, Size, SizeName
|
|
1
|
+
import type { BracketInput, BracketLayout, DrawOptions, Matchup, Size, SizeName } from "./types.ts";
|
|
2
|
+
import { type Voice } from "./locales.ts";
|
|
2
3
|
/** The sizes by name. A phone's drawing is 390 wide and as tall as the bracket needs. */
|
|
3
4
|
export declare const SIZES: Readonly<Record<SizeName, Size>>;
|
|
4
|
-
/** The
|
|
5
|
-
export declare function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}): string;
|
|
5
|
+
/** The layout with each team's name in the language: what the drawing, the table and the description are written from. */
|
|
6
|
+
export declare function localizedLayout(layout: BracketLayout, v: Voice): BracketLayout;
|
|
7
|
+
/** The text a screen reader gets for the drawing as a whole, in the language of `locale`. */
|
|
8
|
+
export declare function describeBracket(input: BracketLayout | readonly Matchup[] | BracketInput, options?: Pick<DrawOptions, "locale" | "words" | "teamName" | "roundNames">): string;
|
|
9
9
|
/**
|
|
10
10
|
* Draws a bracket as an SVG string. `input` is a list of matchups, `{ matchups, teams }` or a layout from `layoutBracket`.
|
|
11
11
|
* The same input and options always give the same string: no clock, no random number, no counter.
|
package/dist/render.js
CHANGED
|
@@ -4,7 +4,7 @@ import { cleanSvg, clipId, drawMark, safeUrl } from "./marks.js";
|
|
|
4
4
|
import { roundLabel } from "./rounds.js";
|
|
5
5
|
import { styleFor } from "./theme.js";
|
|
6
6
|
import { esc, fit, hash, num, textWidth } from "./text.js";
|
|
7
|
-
import { fill,
|
|
7
|
+
import { fill, voiceOf } from "./locales.js";
|
|
8
8
|
/** The sizes by name. A phone's drawing is 390 wide and as tall as the bracket needs. */
|
|
9
9
|
export const SIZES = Object.freeze({
|
|
10
10
|
wide: { width: 1200, height: 675 },
|
|
@@ -13,34 +13,41 @@ export const SIZES = Object.freeze({
|
|
|
13
13
|
});
|
|
14
14
|
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
15
15
|
const isLayout = (x) => typeof x === "object" && x !== null && "ties" in x && "sides" in x && "rounds" in x;
|
|
16
|
-
/** The
|
|
16
|
+
/** The layout with each team's name in the language: what the drawing, the table and the description are written from. */
|
|
17
|
+
export function localizedLayout(layout, v) {
|
|
18
|
+
const named = new Map(layout.teams.map((t) => [t.id, { ...t, name: v.team(t) }]));
|
|
19
|
+
const swap = (t) => (t ? (named.get(t.id) ?? t) : t);
|
|
20
|
+
return { ...layout, ties: layout.ties.map((t) => ({ ...t, sides: [swap(t.sides[0]), swap(t.sides[1])] })), champion: swap(layout.champion), teams: [...named.values()] };
|
|
21
|
+
}
|
|
22
|
+
/** The text a screen reader gets for the drawing as a whole, in the language of `locale`. */
|
|
17
23
|
export function describeBracket(input, options = {}) {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
24
|
+
const v = voiceOf(options);
|
|
25
|
+
const layout = localizedLayout(isLayout(input) ? input : layoutBracket(input), v);
|
|
26
|
+
const w = v.words;
|
|
27
|
+
const parts = [fill(layout.rounds === 1 ? w.summaryOne : w.summary, { n: v.number(layout.teams.length), r: v.number(layout.rounds) })];
|
|
21
28
|
const finalTie = layout.finalIndex !== null ? layout.ties[layout.finalIndex] : null;
|
|
22
29
|
if (finalTie)
|
|
23
|
-
parts.push(
|
|
30
|
+
parts.push(fill(w.finalLine, { round: roundLabel(layout, layout.rounds, options.roundNames, v), tie: describeTie(finalTie, v) }));
|
|
24
31
|
if (layout.champion)
|
|
25
|
-
parts.push(
|
|
26
|
-
return `${parts.join(
|
|
32
|
+
parts.push(fill(w.championIs, { name: layout.champion.name }));
|
|
33
|
+
return `${parts.join(w.sep)}${w.end}`;
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
36
|
* Draws a bracket as an SVG string. `input` is a list of matchups, `{ matchups, teams }` or a layout from `layoutBracket`.
|
|
30
37
|
* The same input and options always give the same string: no clock, no random number, no counter.
|
|
31
38
|
*/
|
|
32
39
|
export function drawBracket(input, options = {}) {
|
|
33
|
-
const
|
|
34
|
-
const
|
|
40
|
+
const v = voiceOf(options);
|
|
41
|
+
const layout = localizedLayout(isLayout(input) ? input : layoutBracket(input, options), v);
|
|
35
42
|
const theme = options.theme ?? "auto";
|
|
36
43
|
const sizeName = typeof options.size === "string" || options.size === undefined ? options.size ?? "wide" : null;
|
|
37
44
|
const size = sizeName ? SIZES[sizeName] : { width: options.size.width, height: options.size.height ?? null };
|
|
38
45
|
const orientation = options.orientation && options.orientation !== "auto" ? options.orientation : sizeName === "phone" || (size.height !== null && size.width < size.height * 0.8) ? "vertical" : "horizontal";
|
|
39
|
-
const prefix = options.id ?? `tb-${hash(JSON.stringify([layout.ties.map((t) => [t.id, t.sides.map((s) => s?.id ?? null), t.winner, t.result.score, t.round]), options.size, orientation, options.detail, theme, options.title, options.subtitle, options.seeds, options.scores, options.champion, options.markShape, options.roundLabels, options.thirdPlace, options.trophy ? 1 : 0]))}`;
|
|
46
|
+
const prefix = options.id ?? `tb-${hash(JSON.stringify([v.locale.code, layout.teams.map((t) => t.name), layout.ties.map((t) => [t.id, t.sides.map((s) => s?.id ?? null), t.winner, t.result.score, t.round]), options.size, orientation, options.detail, theme, options.title, options.subtitle, options.seeds, options.scores, options.champion, options.markShape, options.roundLabels, options.thirdPlace, options.trophy ? 1 : 0]))}`;
|
|
40
47
|
const ctx = { radii: new Set(), symbols: new Map(), prefix, shape: options.markShape ?? "round" };
|
|
41
|
-
const plan = planBracket(layout, options,
|
|
42
|
-
const drawn = plan.boxes.map((b) => drawBox(b, plan, options,
|
|
43
|
-
const title = options.title ?? words.title;
|
|
48
|
+
const plan = planBracket(layout, options, v, size, orientation === "vertical", ctx);
|
|
49
|
+
const drawn = plan.boxes.map((b) => drawBox(b, plan, options, v, ctx)); // draws the marks, so before the defs are written
|
|
50
|
+
const title = options.title ?? v.words.title;
|
|
44
51
|
const desc = describeBracket(layout, options);
|
|
45
52
|
const defs = [
|
|
46
53
|
...[...ctx.radii].sort((a, b) => a - b).map((r) => `<clipPath id="${clipId(prefix, r)}"><circle r="${num(r)}"/></clipPath>`),
|
|
@@ -49,7 +56,7 @@ export function drawBracket(input, options = {}) {
|
|
|
49
56
|
const style = theme === "none" ? "" : `<style>${styleFor(`.${prefix}`, theme)}</style>`;
|
|
50
57
|
const bg = options.background === false ? "" : `<rect class="tb-bg" width="${num(plan.width)}" height="${num(plan.height)}"${theme === "none" ? ' fill="none"' : ""}/>`;
|
|
51
58
|
return [
|
|
52
|
-
`<svg xmlns="http://www.w3.org/2000/svg" class="taisen ${prefix}${options.className ? ` ${esc(options.className)}` : ""}" viewBox="0 0 ${num(plan.width)} ${num(plan.height)}" width="${num(plan.width)}" height="${num(plan.height)}" style="max-width:100%;height:auto" role="img" aria-labelledby="${prefix}-t ${prefix}-d">`,
|
|
59
|
+
`<svg xmlns="http://www.w3.org/2000/svg" lang="${esc(v.locale.code)}" class="taisen ${prefix}${options.className ? ` ${esc(options.className)}` : ""}" viewBox="0 0 ${num(plan.width)} ${num(plan.height)}" width="${num(plan.width)}" height="${num(plan.height)}" style="max-width:100%;height:auto" role="img" aria-labelledby="${prefix}-t ${prefix}-d">`,
|
|
53
60
|
`<title id="${prefix}-t">${esc(title)}</title><desc id="${prefix}-d">${esc(desc)}</desc>`,
|
|
54
61
|
style,
|
|
55
62
|
defs ? `<defs>${defs}</defs>` : "",
|
|
@@ -62,7 +69,7 @@ export function drawBracket(input, options = {}) {
|
|
|
62
69
|
].filter(Boolean).join("\n");
|
|
63
70
|
}
|
|
64
71
|
// ---------------------------------------------------------------------------------------------- the plan
|
|
65
|
-
function planBracket(layout, o,
|
|
72
|
+
function planBracket(layout, o, v, size, vertical, ctx) {
|
|
66
73
|
const pad = vertical ? 14 : 22;
|
|
67
74
|
const showLabels = o.roundLabels ?? true;
|
|
68
75
|
const poster = o.theme === "poster";
|
|
@@ -190,14 +197,14 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
190
197
|
rects.set(layout.final.i, rect);
|
|
191
198
|
boxes.push({ rect, tie: layout.ties[layout.final.i], order: layout.final.order, bye: null, final: true, champion: champ });
|
|
192
199
|
if (showLabels)
|
|
193
|
-
texts.push(label(roundLabel(layout, layout.rounds, o.roundNames,
|
|
200
|
+
texts.push(label(v, roundLabel(layout, layout.rounds, o.roundNames, v), midX, rect.y - 7, "middle"));
|
|
194
201
|
if (champ)
|
|
195
|
-
extras.push(...championBlock(layout.champion, midX, rect.y - 22 - champH, champMark, trophyH, nameFont, o,
|
|
202
|
+
extras.push(...championBlock(layout.champion, midX, rect.y - 22 - champH, champMark, trophyH, nameFont, o, v, ctx, poster));
|
|
196
203
|
if (withThird) {
|
|
197
204
|
const t3 = layout.ties[layout.third];
|
|
198
205
|
const r3 = { x: midX - finalW / 2, y: rect.y + rect.h + 14 + 18, w: finalW, h: boxH };
|
|
199
206
|
rects.set(layout.third, r3);
|
|
200
|
-
texts.push(label(words.thirdPlace, midX, r3.y - 6,
|
|
207
|
+
texts.push(label(v, v.words.thirdPlace, midX, r3.y - 6, "middle"));
|
|
201
208
|
boxes.push({ rect: r3, tie: t3, order: [0, 1], bye: null, final: false, champion: false });
|
|
202
209
|
}
|
|
203
210
|
}
|
|
@@ -208,17 +215,17 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
208
215
|
if (fb && champ) {
|
|
209
216
|
const cx = starts[k] + sizes[k] / 2;
|
|
210
217
|
const fy = rects.get(fi).y + S / 2;
|
|
211
|
-
extras.push(...championBlock(layout.champion, cx, fy - champH / 2, champMark, trophyH, nameFont, o,
|
|
218
|
+
extras.push(...championBlock(layout.champion, cx, fy - champH / 2, champMark, trophyH, nameFont, o, v, ctx, poster));
|
|
212
219
|
}
|
|
213
220
|
}
|
|
214
221
|
if (showLabels) {
|
|
215
222
|
for (const c of layout.columns) {
|
|
216
|
-
const name = roundLabel(layout, c, o.roundNames,
|
|
223
|
+
const name = roundLabel(layout, c, o.roundNames, v);
|
|
217
224
|
const ci = layout.columns.indexOf(c);
|
|
218
225
|
const ks = halves ? [slotOfColumn("l", ci), slotOfColumn("r", ci)] : [ci];
|
|
219
226
|
const used = layout.sides.length > 1 || !halves ? (halves ? [0, 1] : [0]) : [0];
|
|
220
227
|
ks.forEach((k, n) => { if (used.includes(n) && (!halves || n < layout.sides.length))
|
|
221
|
-
texts.push(label(name, starts[k] + D / 2, top - 9,
|
|
228
|
+
texts.push(label(v, name, starts[k] + D / 2, top - 9, "middle", D + gapT * 0.8)); });
|
|
222
229
|
}
|
|
223
230
|
}
|
|
224
231
|
if (hasGroups) {
|
|
@@ -227,7 +234,7 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
227
234
|
return;
|
|
228
235
|
const ks = layout.columns.map((_, ci) => slotOfColumn(sd.key, ci));
|
|
229
236
|
const x0 = Math.min(...ks.map((k) => starts[k])), x1 = Math.max(...ks.map((k) => starts[k] + D));
|
|
230
|
-
texts.push(`<text class="tb-half" x="${num((x0 + x1) / 2)}" y="${num(top - (showLabels ? 36 : 14))}" font-size="${poster ? 16 : 15}" font-weight="700" letter-spacing=".1em" text-anchor="middle">${esc(poster ? sd.label
|
|
237
|
+
texts.push(`<text class="tb-half" x="${num((x0 + x1) / 2)}" y="${num(top - (showLabels ? 36 : 14))}" font-size="${poster ? 16 : 15}" font-weight="700" letter-spacing=".1em" text-anchor="middle">${esc(poster ? v.upper(sd.label) : sd.label)}</text>`);
|
|
231
238
|
});
|
|
232
239
|
}
|
|
233
240
|
// lines
|
|
@@ -245,9 +252,9 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
245
252
|
links.push(`<path class="tb-line" d="M${num(x0)} ${num(y0)}H${num(xm)}V${num(y1)}H${num(x1)}"${lineAttrs(o)}/>`);
|
|
246
253
|
}
|
|
247
254
|
if (bottomThird)
|
|
248
|
-
thirdAtFoot(layout, height - pad - extraH0 + 6, width / 2, ow, OTHER_H,
|
|
249
|
-
othersBlock(layout, others, height - pad - extraH0 + thirdBlock + 6, width, pad, ow, OTHER_H,
|
|
250
|
-
texts.push(...headings(o, width, pad, poster));
|
|
255
|
+
thirdAtFoot(layout, height - pad - extraH0 + 6, width / 2, ow, OTHER_H, v, boxes, texts);
|
|
256
|
+
othersBlock(layout, others, height - pad - extraH0 + thirdBlock + 6, width, pad, ow, OTHER_H, v, boxes, texts);
|
|
257
|
+
texts.push(...headings(v, o, width, pad, poster));
|
|
251
258
|
return { width, height, boxes, links, texts, extras, detail, vertical };
|
|
252
259
|
}
|
|
253
260
|
// ----- vertical: the rounds are rows. The left half is the top; the final is a band in the middle.
|
|
@@ -287,9 +294,9 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
287
294
|
const cy0 = topT + bandsPerSide * bandT;
|
|
288
295
|
let y = cy0;
|
|
289
296
|
if (champ)
|
|
290
|
-
extras.push(...championBlock(layout.champion, midX, pad + titleH + 4, champMark, trophyH, nameFont, o,
|
|
297
|
+
extras.push(...championBlock(layout.champion, midX, pad + titleH + 4, champMark, trophyH, nameFont, o, v, ctx, poster));
|
|
291
298
|
if (showLabels) {
|
|
292
|
-
texts.push(label(roundLabel(layout, layout.rounds, o.roundNames,
|
|
299
|
+
texts.push(label(v, roundLabel(layout, layout.rounds, o.roundNames, v), midX, y + 12, "middle"));
|
|
293
300
|
y += 16;
|
|
294
301
|
}
|
|
295
302
|
const rect = { x: midX - finalW / 2, y: y + 6, w: finalW, h: finalH };
|
|
@@ -298,14 +305,14 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
298
305
|
}
|
|
299
306
|
else if (!halves && champ && layout.finalIndex !== null) {
|
|
300
307
|
const fr = rects.get(layout.finalIndex);
|
|
301
|
-
extras.push(...championBlock(layout.champion, fr.x + fr.w / 2, fr.y + fr.h + 12, champMark, trophyH, nameFont, o,
|
|
308
|
+
extras.push(...championBlock(layout.champion, fr.x + fr.w / 2, fr.y + fr.h + 12, champMark, trophyH, nameFont, o, v, ctx, poster));
|
|
302
309
|
}
|
|
303
310
|
if (showLabels) {
|
|
304
311
|
layout.columns.forEach((c, ci) => {
|
|
305
|
-
const name = roundLabel(layout, c, o.roundNames,
|
|
312
|
+
const name = roundLabel(layout, c, o.roundNames, v);
|
|
306
313
|
const sides = halves ? (layout.sides.length > 1 ? ["l", "r"] : ["l"]) : ["l"];
|
|
307
314
|
for (const sd of sides)
|
|
308
|
-
texts.push(`<text class="tb-round tb-halo" x="${num(pad)}" y="${num(bandStart(sd, ci) - 8)}" font-size="11" font-weight="600" letter-spacing=".08em">${esc(
|
|
315
|
+
texts.push(`<text class="tb-round tb-halo" x="${num(pad)}" y="${num(bandStart(sd, ci) - 8)}" font-size="11" font-weight="600" letter-spacing=".08em">${esc(v.upper(name))}</text>`);
|
|
309
316
|
});
|
|
310
317
|
}
|
|
311
318
|
if (hasGroups) {
|
|
@@ -313,7 +320,7 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
313
320
|
if (!sd.label)
|
|
314
321
|
return;
|
|
315
322
|
const y = sd.key === "l" ? pad + titleH + champTop + 16 : totalT - pad - 2;
|
|
316
|
-
texts.push(`<text class="tb-half" x="${num(midX)}" y="${num(y)}" font-size="14" font-weight="700" letter-spacing=".1em" text-anchor="middle">${esc(sd.label
|
|
323
|
+
texts.push(`<text class="tb-half" x="${num(midX)}" y="${num(y)}" font-size="14" font-weight="700" letter-spacing=".1em" text-anchor="middle">${esc(v.upper(sd.label))}</text>`);
|
|
317
324
|
});
|
|
318
325
|
}
|
|
319
326
|
for (const t of layout.ties) {
|
|
@@ -330,9 +337,9 @@ function planBracket(layout, o, words, size, vertical, ctx) {
|
|
|
330
337
|
links.push(`<path class="tb-line" d="M${num(x0c)} ${num(y0)}V${num(ym)}H${num(x1c)}V${num(y1)}"${lineAttrs(o)}/>`);
|
|
331
338
|
}
|
|
332
339
|
if (bottomThird)
|
|
333
|
-
thirdAtFoot(layout, totalT - pad + 6, W0 / 2, Math.min(W0 - 2 * pad, 200), 52,
|
|
334
|
-
othersBlock(layout, others, totalT - pad + thirdBlock + 6, W0, pad, ow, OTHER_H,
|
|
335
|
-
texts.push(...headings(o, W0, pad, poster));
|
|
340
|
+
thirdAtFoot(layout, totalT - pad + 6, W0 / 2, Math.min(W0 - 2 * pad, 200), 52, v, boxes, texts);
|
|
341
|
+
othersBlock(layout, others, totalT - pad + thirdBlock + 6, W0, pad, ow, OTHER_H, v, boxes, texts);
|
|
342
|
+
texts.push(...headings(v, o, W0, pad, poster));
|
|
336
343
|
return { width: W0, height, boxes, links, texts, extras, detail, vertical };
|
|
337
344
|
}
|
|
338
345
|
const OTHER_H = 50;
|
|
@@ -340,16 +347,14 @@ const centreMinLtr = (d) => d * 1.1;
|
|
|
340
347
|
function lineAttrs(o) {
|
|
341
348
|
return o.theme === "none" ? ' fill="none" stroke="currentColor" stroke-opacity=".5"' : "";
|
|
342
349
|
}
|
|
343
|
-
function label(text, x, y,
|
|
344
|
-
|
|
345
|
-
const up = text.toUpperCase();
|
|
346
|
-
return `<text class="tb-round" x="${num(x)}" y="${num(y)}" font-size="11" font-weight="600" letter-spacing=".08em" text-anchor="${anchor}">${esc(fit(up, 11 * 1.18, maxWidth))}</text>`;
|
|
350
|
+
function label(v, text, x, y, anchor, maxWidth = 1e9) {
|
|
351
|
+
return `<text class="tb-round" x="${num(x)}" y="${num(y)}" font-size="11" font-weight="600" letter-spacing=".08em" text-anchor="${anchor}">${esc(fit(v.upper(text), 11 * 1.18, maxWidth))}</text>`;
|
|
347
352
|
}
|
|
348
|
-
function headings(o, width, pad, poster) {
|
|
353
|
+
function headings(v, o, width, pad, poster) {
|
|
349
354
|
const out = [];
|
|
350
355
|
let y = pad + 24;
|
|
351
356
|
if (o.title) {
|
|
352
|
-
const text = poster ? o.title
|
|
357
|
+
const text = poster ? v.upper(o.title) : o.title;
|
|
353
358
|
out.push(`<text class="tb-title" x="${num(width / 2)}" y="${num(y)}" font-size="${poster ? 26 : 24}" font-weight="800" ${poster ? 'letter-spacing=".14em" ' : ""}text-anchor="middle">${esc(text)}</text>`);
|
|
354
359
|
y += poster ? 24 : 22;
|
|
355
360
|
}
|
|
@@ -358,7 +363,7 @@ function headings(o, width, pad, poster) {
|
|
|
358
363
|
return out;
|
|
359
364
|
}
|
|
360
365
|
/** The champion, centred on `cx`, its top at `top`: a trophy if there is one, a large mark, the name and the word. */
|
|
361
|
-
function championBlock(team, cx, top, mark, trophyH, nameFont, o,
|
|
366
|
+
function championBlock(team, cx, top, mark, trophyH, nameFont, o, v, ctx, poster) {
|
|
362
367
|
const out = [];
|
|
363
368
|
let y = top;
|
|
364
369
|
if (o.trophy && trophyH) {
|
|
@@ -375,8 +380,8 @@ function championBlock(team, cx, top, mark, trophyH, nameFont, o, words, ctx, po
|
|
|
375
380
|
if (ctx.shape !== "none")
|
|
376
381
|
out.push(`<g class="tb-champ">${drawMark(team, cx, cy, mark, ctx)}</g>`);
|
|
377
382
|
const nameY = cy + mark / 2 + 8 + nameFont * 0.8;
|
|
378
|
-
out.push(`<text class="tb-name" x="${num(cx)}" y="${num(nameY)}" font-size="${num(nameFont)}" font-weight="800" text-anchor="middle">${esc(poster ?
|
|
379
|
-
out.push(`<text class="tb-champ-label" x="${num(cx)}" y="${num(nameY + 6 + 11)}" font-size="11" font-weight="700" letter-spacing=".16em" text-anchor="middle">${esc(words.champion
|
|
383
|
+
out.push(`<text class="tb-name" x="${num(cx)}" y="${num(nameY)}" font-size="${num(nameFont)}" font-weight="800" text-anchor="middle">${esc(poster ? v.upper(name) : name)}</text>`);
|
|
384
|
+
out.push(`<text class="tb-champ-label" x="${num(cx)}" y="${num(nameY + 6 + 11)}" font-size="11" font-weight="700" letter-spacing=".16em" text-anchor="middle">${esc(v.upper(v.words.champion))}</text>`);
|
|
380
385
|
return out;
|
|
381
386
|
}
|
|
382
387
|
/** The ties that are not part of the tree (they lead nowhere near the final, or are round-robin games), drawn in a grid below it. */
|
|
@@ -389,24 +394,24 @@ function othersHeight(count, width, pad, ow, oh) {
|
|
|
389
394
|
const cols = Math.max(1, Math.floor((width - 2 * pad + 10) / (ow + 10)));
|
|
390
395
|
return 26 + Math.ceil(count / cols) * (oh + 10) + 6;
|
|
391
396
|
}
|
|
392
|
-
function othersBlock(layout, list, y0, width, pad, ow, oh,
|
|
397
|
+
function othersBlock(layout, list, y0, width, pad, ow, oh, v, boxes, texts) {
|
|
393
398
|
if (!list.length)
|
|
394
399
|
return;
|
|
395
400
|
const cols = Math.max(1, Math.floor((width - 2 * pad + 10) / (ow + 10)));
|
|
396
401
|
const x0 = pad + (width - 2 * pad - (Math.min(cols, list.length) * (ow + 10) - 10)) / 2;
|
|
397
|
-
texts.push(label(words.other, width / 2, y0 + 12,
|
|
402
|
+
texts.push(label(v, v.words.other, width / 2, y0 + 12, "middle"));
|
|
398
403
|
list.forEach((i, n) => {
|
|
399
404
|
const rect = { x: x0 + (n % cols) * (ow + 10), y: y0 + 24 + Math.floor(n / cols) * (oh + 10), w: ow, h: oh };
|
|
400
405
|
boxes.push({ rect, tie: layout.ties[i], order: [0, 1], bye: null, final: false, champion: false });
|
|
401
406
|
});
|
|
402
407
|
}
|
|
403
408
|
/** The third-place match with its label, centred on `cx`, its label at `y0`. */
|
|
404
|
-
function thirdAtFoot(layout, y0, cx, w, h,
|
|
405
|
-
texts.push(label(words.thirdPlace, cx, y0 + 12,
|
|
409
|
+
function thirdAtFoot(layout, y0, cx, w, h, v, boxes, texts) {
|
|
410
|
+
texts.push(label(v, v.words.thirdPlace, cx, y0 + 12, "middle"));
|
|
406
411
|
boxes.push({ rect: { x: cx - w / 2, y: y0 + 18, w, h }, tie: layout.ties[layout.third], order: [0, 1], bye: null, final: false, champion: false });
|
|
407
412
|
}
|
|
408
413
|
// ---------------------------------------------------------------------------------------------- one box
|
|
409
|
-
function drawBox(p, plan, o,
|
|
414
|
+
function drawBox(p, plan, o, v, ctx) {
|
|
410
415
|
const { rect, tie } = p;
|
|
411
416
|
const { x, y, w, h } = rect;
|
|
412
417
|
const detail = plan.detail;
|
|
@@ -416,7 +421,7 @@ function drawBox(p, plan, o, words, ctx) {
|
|
|
416
421
|
const parts = [];
|
|
417
422
|
parts.push(`<rect class="tb-box" x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" rx="${num(Math.min(7, h / 4))}"${o.theme === "none" ? ' fill="none" stroke="currentColor" stroke-opacity=".4"' : ""}/>`);
|
|
418
423
|
const r = tie.result;
|
|
419
|
-
const notes = noteParts(tie,
|
|
424
|
+
const notes = noteParts(tie, v);
|
|
420
425
|
const noteH = detail !== "mark" && h >= 50 && notes.length ? 13 : 0;
|
|
421
426
|
const rowH = (h - noteH) / 2;
|
|
422
427
|
const rowsOrder = p.order;
|
|
@@ -446,17 +451,17 @@ function drawBox(p, plan, o, words, ctx) {
|
|
|
446
451
|
rowParts.push(drawMark(team, x + 6 + md / 2, cy, md, ctx));
|
|
447
452
|
tx = x + 6 + md + 5;
|
|
448
453
|
}
|
|
449
|
-
const score = scoresOn && r.score ?
|
|
450
|
-
const pensText = scoresOn && r.pens ? `(${r.pens[k]})` : "";
|
|
454
|
+
const score = scoresOn && r.score ? v.number(r.score[k]) : "";
|
|
455
|
+
const pensText = scoresOn && r.pens ? `(${v.number(r.pens[k])})` : "";
|
|
451
456
|
const bye = p.bye === k;
|
|
452
457
|
const right = x + w - 6;
|
|
453
458
|
const scoreW = score ? textWidth(score, font + 1) : 0;
|
|
454
459
|
const pensW = pensText ? textWidth(pensText, font - 2) + 3 : 0;
|
|
455
|
-
const byeText = bye ? words.bye : "";
|
|
460
|
+
const byeText = bye ? v.words.bye : "";
|
|
456
461
|
const byeW = byeText ? textWidth(byeText, Math.max(font - 2, 8)) : 0;
|
|
457
462
|
const avail = right - tx - (scoreW + pensW) - (byeW ? byeW + 6 : 0) - 6;
|
|
458
|
-
const seed = seedsOn && team?.seed !== undefined ? `${team.seed} ` : "";
|
|
459
|
-
const nm = team ? (detail === "full" ? team.name : abbrOf(team,
|
|
463
|
+
const seed = seedsOn && team?.seed !== undefined ? `${v.number(team.seed)} ` : "";
|
|
464
|
+
const nm = team ? (detail === "full" ? team.name : abbrOf(team, v)) : v.words.tbd;
|
|
460
465
|
const text = fit(`${seed}${nm}`, font, Math.max(avail, 8));
|
|
461
466
|
rowParts.push(`<text class="tb-name${team ? "" : " tb-muted"}" x="${num(tx)}" y="${num(cy + font * 0.35)}" font-size="${num(font)}">${esc(text)}</text>`);
|
|
462
467
|
if (score)
|
|
@@ -474,5 +479,5 @@ function drawBox(p, plan, o, words, ctx) {
|
|
|
474
479
|
}
|
|
475
480
|
}
|
|
476
481
|
const cls2 = `tb-tie${p.final ? " tb-final" : ""}${p.champion ? " tb-has-champ" : ""}`;
|
|
477
|
-
return `<g class="${cls2}"><title>${esc(describeTie(tie,
|
|
482
|
+
return `<g class="${cls2}"><title>${esc(describeTie(tie, v))}</title>${parts.join("")}</g>`;
|
|
478
483
|
}
|
package/dist/rounds.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { BracketLayout, DrawOptions } from "./types.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type Voice } from "./locales.ts";
|
|
3
3
|
/** The name of a round that has `remaining` teams left in it, counting from the final: Final, Semi-finals, Quarter-finals, Round of 16… */
|
|
4
4
|
export declare function roundName(remaining: number): string;
|
|
5
5
|
/** The generic names of `count` rounds, first round first: `roundNamesFor(3)` is Quarter-finals, Semi-finals, Final. */
|
|
6
6
|
export declare function roundNamesFor(count: number): string[];
|
|
7
7
|
/** The name of round `round` (1 is the first) of a layout, from the names given (which may be only the last rounds) or the generic ones. */
|
|
8
|
-
export declare function roundLabel(layout: BracketLayout, round: number, names: DrawOptions["roundNames"],
|
|
8
|
+
export declare function roundLabel(layout: BracketLayout, round: number, names: DrawOptions["roundNames"], v: Voice): string;
|
package/dist/rounds.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fill } from "./
|
|
1
|
+
import { fill } from "./locales.js";
|
|
2
2
|
/** The name of a round that has `remaining` teams left in it, counting from the final: Final, Semi-finals, Quarter-finals, Round of 16… */
|
|
3
3
|
export function roundName(remaining) {
|
|
4
4
|
if (remaining <= 2)
|
|
@@ -14,13 +14,13 @@ export function roundNamesFor(count) {
|
|
|
14
14
|
return Array.from({ length: count }, (_, i) => roundName(2 ** (count - i)));
|
|
15
15
|
}
|
|
16
16
|
/** The name of round `round` (1 is the first) of a layout, from the names given (which may be only the last rounds) or the generic ones. */
|
|
17
|
-
export function roundLabel(layout, round, names,
|
|
17
|
+
export function roundLabel(layout, round, names, v) {
|
|
18
18
|
const list = names ? ("rounds" in names ? names.rounds.map((r) => r.name) : names) : null;
|
|
19
19
|
const from = layout.rounds - round; // 0 for the final
|
|
20
20
|
const named = list?.[list.length - 1 - from];
|
|
21
21
|
if (named)
|
|
22
|
-
return named;
|
|
22
|
+
return v.round(named);
|
|
23
23
|
const generic = 2 ** (from + 1);
|
|
24
24
|
const remaining = layout.rounds > 7 ? 0 : generic;
|
|
25
|
-
return remaining ? roundName(remaining) : fill(words.round, { n: round });
|
|
25
|
+
return remaining ? v.round(roundName(remaining)) : fill(v.words.round, { n: v.number(round) });
|
|
26
26
|
}
|
package/dist/table.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type { BracketInput, BracketLayout, DrawOptions, Matchup
|
|
2
|
-
export interface TableOptions {
|
|
3
|
-
words?: Partial<Words>;
|
|
4
|
-
roundNames?: DrawOptions["roundNames"];
|
|
1
|
+
import type { BracketInput, BracketLayout, DrawOptions, Matchup } from "./types.ts";
|
|
2
|
+
export interface TableOptions extends Pick<DrawOptions, "locale" | "words" | "teamName" | "roundNames"> {
|
|
5
3
|
/** The table's caption. Default: the summary ("16 teams, 4 rounds"). */
|
|
6
4
|
caption?: string;
|
|
7
5
|
/** A class for the `<table>`. Default `tb-table`. */
|
|
@@ -9,6 +7,7 @@ export interface TableOptions {
|
|
|
9
7
|
}
|
|
10
8
|
/**
|
|
11
9
|
* The bracket as an HTML table, for a reader the drawing does not serve: one row for every tie, round by round, with the
|
|
12
|
-
* two sides, the score, the winner in bold
|
|
10
|
+
* two sides, the score, the winner in bold, the date where the tie has one (written as the language writes dates) and a note.
|
|
11
|
+
* It is a plain, valid `<table>` with a caption and header cells, in the language of `locale`.
|
|
13
12
|
*/
|
|
14
13
|
export declare function bracketTable(input: BracketLayout | readonly Matchup[] | BracketInput, options?: TableOptions): string;
|
package/dist/table.js
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
import { layoutBracket } from "./layout.js";
|
|
2
|
-
import {
|
|
2
|
+
import { noteLong } from "./matchup.js";
|
|
3
|
+
import { fill, voiceOf } from "./locales.js";
|
|
4
|
+
import { localizedLayout } from "./render.js";
|
|
3
5
|
import { roundLabel } from "./rounds.js";
|
|
4
6
|
import { esc } from "./text.js";
|
|
5
|
-
import { fill, wordsWith } from "./words.js";
|
|
6
7
|
/**
|
|
7
8
|
* The bracket as an HTML table, for a reader the drawing does not serve: one row for every tie, round by round, with the
|
|
8
|
-
* two sides, the score, the winner in bold
|
|
9
|
+
* two sides, the score, the winner in bold, the date where the tie has one (written as the language writes dates) and a note.
|
|
10
|
+
* It is a plain, valid `<table>` with a caption and header cells, in the language of `locale`.
|
|
9
11
|
*/
|
|
10
12
|
export function bracketTable(input, options = {}) {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const v = voiceOf(options);
|
|
14
|
+
const layout = localizedLayout("ties" in input && "rounds" in input ? input : layoutBracket(input), v);
|
|
15
|
+
const w = v.words;
|
|
16
|
+
const caption = options.caption ?? fill(layout.rounds === 1 ? w.summaryOne : w.summary, { n: v.number(layout.teams.length), r: v.number(layout.rounds) });
|
|
14
17
|
const order = layout.ties
|
|
15
18
|
.filter((t) => t.round > 0 || layout.robin.includes(t.index))
|
|
16
19
|
.sort((a, b) => Number(a.third) - Number(b.third) || a.round - b.round || (a.date < b.date ? -1 : a.date > b.date ? 1 : a.index - b.index));
|
|
20
|
+
const dated = order.some((t) => v.date(t.date) !== null);
|
|
17
21
|
const name = (t, k) => {
|
|
18
22
|
const team = t.sides[k];
|
|
19
|
-
const text = team ? esc(team.name) : esc(
|
|
23
|
+
const text = team ? esc(team.name) : esc(w.tbd);
|
|
20
24
|
return t.winner === k ? `<strong>${text}</strong>` : text;
|
|
21
25
|
};
|
|
22
|
-
const score = (t) => (t.result.score ?
|
|
26
|
+
const score = (t) => (t.result.score ? fill(w.scoreText, { a: v.number(t.result.score[0]), b: v.number(t.result.score[1]) }) : "");
|
|
23
27
|
const rows = order.map((t) => {
|
|
24
|
-
const roundText = t.third ?
|
|
25
|
-
const
|
|
26
|
-
return `<tr><th scope="row">${esc(roundText)}</th
|
|
28
|
+
const roundText = t.third ? w.thirdPlace : t.round === 0 ? "" : roundLabel(layout, t.round, options.roundNames, v);
|
|
29
|
+
const date = dated ? `<td>${esc(v.date(t.date) ?? "")}</td>` : "";
|
|
30
|
+
return `<tr><th scope="row">${esc(roundText)}</th>${date}<td>${name(t, 0)}</td><td>${esc(score(t))}</td><td>${name(t, 1)}</td><td>${esc(noteLong(t, v).join(w.noteJoin))}</td></tr>`;
|
|
27
31
|
});
|
|
28
32
|
return [
|
|
29
|
-
`<table class="${esc(options.className ?? "tb-table")}">`,
|
|
33
|
+
`<table class="${esc(options.className ?? "tb-table")}" lang="${esc(v.locale.code)}">`,
|
|
30
34
|
`<caption>${esc(caption)}</caption>`,
|
|
31
|
-
`<thead><tr><th scope="col">${esc(
|
|
35
|
+
`<thead><tr><th scope="col">${esc(w.columnRound)}</th>${dated ? `<th scope="col">${esc(w.columnDate)}</th>` : ""}<th scope="col" colspan="3">${esc(w.columnTie)}</th><th scope="col">${esc(w.columnNote)}</th></tr></thead>`,
|
|
32
36
|
`<tbody>${rows.join("")}</tbody>`,
|
|
33
37
|
"</table>",
|
|
34
38
|
].join("\n");
|
package/dist/theme.js
CHANGED
|
@@ -4,7 +4,7 @@ export const THEMES = Object.freeze({
|
|
|
4
4
|
dark: { bg: "#141614", ink: "#ece8dc", muted: "#9aa095", line: "#6c7168", box: "#1e211d", edge: "#3b403a", win: "#e0705a", gold: "#e0b43b", plain: "#3b403a" },
|
|
5
5
|
poster: { bg: "#0a1130", ink: "#f6f1e3", muted: "#a9b2d3", line: "#d9b24c", box: "#111b47", edge: "#8e7632", win: "#f0c64a", gold: "#f0c64a", plain: "#26336b" },
|
|
6
6
|
});
|
|
7
|
-
const FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
|
|
7
|
+
const FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Hiragino Sans", "Yu Gothic UI", "Noto Sans CJK JP", Meiryo, sans-serif';
|
|
8
8
|
/** The rules for one palette, each colour read from its custom property with the palette's value as the fallback. */
|
|
9
9
|
function rules(p, poster, withFont) {
|
|
10
10
|
const v = (name) => `var(--tb-${name}, ${p[name]})`;
|