@johnmorrisdotca/taisen-brackets 0.5.0 → 0.6.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 +25 -0
- package/NOTICE.md +2 -0
- package/README.md +159 -29
- package/dist/furniture.d.ts +10 -0
- package/dist/furniture.js +29 -0
- package/dist/groups.d.ts +50 -0
- package/dist/groups.js +204 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -2
- package/dist/layout.js +56 -6
- package/dist/layouts.d.ts +48 -0
- package/dist/layouts.js +388 -0
- package/dist/locales.js +10 -0
- package/dist/marks.d.ts +2 -0
- package/dist/marks.js +1 -0
- package/dist/matchup.js +1 -0
- package/dist/playin.d.ts +8 -0
- package/dist/playin.js +232 -0
- package/dist/render.d.ts +8 -1
- package/dist/render.js +223 -34
- package/dist/seeding.js +4 -2
- package/dist/table.js +6 -3
- package/dist/theme.d.ts +4 -0
- package/dist/theme.js +6 -0
- package/dist/types.d.ts +66 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -3
package/dist/render.js
CHANGED
|
@@ -6,6 +6,13 @@ import { styleFor } from "./theme.js";
|
|
|
6
6
|
import { resolveBackground } from "./background.js";
|
|
7
7
|
import { emWidth, esc, fit, hash, num } from "./text.js";
|
|
8
8
|
import { fill, voiceOf } from "./locales.js";
|
|
9
|
+
import { paintVariant, seriesLineWidth } from "./layouts.js";
|
|
10
|
+
import { drawGroups } from "./groups.js";
|
|
11
|
+
import { headTile } from "./furniture.js";
|
|
12
|
+
import { drawPlayIn } from "./playin.js";
|
|
13
|
+
/** The names of the layouts, the tree first. */
|
|
14
|
+
export const LAYOUTS = Object.freeze(["tree", "rows", "shields", "update", "columns", "playin", "series", "groups"]);
|
|
15
|
+
const VARIANTS = ["rows", "shields", "update", "columns", "series"];
|
|
9
16
|
/** 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. */
|
|
10
17
|
export const SIZES = Object.freeze({
|
|
11
18
|
wide: { width: 1200, height: 675 },
|
|
@@ -95,40 +102,87 @@ export function describeBracket(input, options = {}) {
|
|
|
95
102
|
*/
|
|
96
103
|
export function drawBracket(input, options = {}) {
|
|
97
104
|
const v = voiceOf(options);
|
|
98
|
-
const
|
|
105
|
+
const kind = options.layout ?? "tree";
|
|
106
|
+
if (!LAYOUTS.includes(kind))
|
|
107
|
+
throw new RangeError(`taisen-brackets: unknown layout ${JSON.stringify(String(kind))}: use one of ${LAYOUTS.join(", ")}`);
|
|
108
|
+
// The series card lists the east first: with no order asked for, a group named east goes before one named west.
|
|
109
|
+
const groupOrder = kind === "series" && options.groupOrder === undefined && !isLayout(input) ? eastFirst(input) : options.groupOrder;
|
|
110
|
+
const layout = localizedLayout(isLayout(input) ? input : layoutBracket(input, groupOrder ? { ...options, groupOrder } : options), v);
|
|
99
111
|
const theme = options.theme ?? "auto";
|
|
100
|
-
|
|
112
|
+
// The series card is a tall card unless a size is asked for.
|
|
113
|
+
const sizeName = typeof options.size === "string" || options.size === undefined ? options.size ?? (kind === "series" ? "card" : "wide") : null;
|
|
101
114
|
const size = sizeName ? SIZES[sizeName] : { width: options.size.width, height: options.size.height ?? null };
|
|
102
|
-
const orientation = options.orientation && options.orientation !== "auto" ? options.orientation : sizeName === "phone" || sizeName === "card" || (size.height !== null && size.width < size.height * 0.8) ? "vertical" : "horizontal";
|
|
115
|
+
const orientation = options.orientation && options.orientation !== "auto" ? options.orientation : kind === "series" || sizeName === "phone" || sizeName === "card" || (size.height !== null && size.width < size.height * 0.8) ? "vertical" : "horizontal";
|
|
103
116
|
const road = options.look === "road";
|
|
104
117
|
const hasBg = typeof options.background === "string" || (typeof options.background === "object" && options.background !== null);
|
|
105
|
-
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, road ? 1 : 0, options.highlightPath ?? null, options.flags ? 1 : 0, ...(hasBg ? [options.background, options.vs, options.codes, options.seeds] : []), ...(options.flagHref ? ["h"] : [])]))}`;
|
|
106
|
-
const ctx = { radii: new Set(), symbols: new Map(), prefix, shape: options.markShape ?? "round", look: road ? "road" : "classic", flags: options.flags, flagHref: options.flagHref, theme: theme === "light" ? "light" : theme === "dark" || theme === "poster" ? "dark" : "inherit", flagCache: new Map() };
|
|
118
|
+
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, road ? 1 : 0, options.highlightPath ?? null, options.flags ? 1 : 0, ...(hasBg ? [options.background, options.vs, options.codes, options.seeds] : []), ...(options.flagHref ? ["h"] : []), ...(kind !== "tree" ? [kind, options.at, options.standings, options.qualify] : []), ...(options.headers ? ["head"] : [])]))}`;
|
|
119
|
+
const ctx = { radii: new Set(), symbols: new Map(), prefix, shape: options.markShape ?? "round", look: road ? "road" : "classic", flags: options.flags, flagHref: options.flagHref, theme: theme === "light" ? "light" : theme === "dark" || theme === "poster" ? "dark" : "inherit", flagCache: new Map(), defs: new Map() };
|
|
107
120
|
const path = pathOf(layout, options.highlightPath);
|
|
108
|
-
const plan = planBracket(layout, options, v, size, orientation === "vertical", ctx, road, path);
|
|
109
121
|
const linked = { count: 0 };
|
|
110
|
-
const drawn = plan.boxes.map((b) => drawBox(b, plan, options, v, ctx, linked)); // draws the marks, so before the defs are written
|
|
111
122
|
const title = options.title ?? v.words.title;
|
|
112
123
|
const desc = describeBracket(layout, options);
|
|
113
|
-
|
|
124
|
+
// The group tables and the play-in are not the tree: they plan and draw their own parts, and share the frame (the style, the background, the title) with it.
|
|
125
|
+
const own = kind === "groups" ? drawGroups(layout, options, v, size, ctx, road, linked) : kind === "playin" ? drawPlayIn(layout, options, v, size, ctx, road, linked) : null;
|
|
126
|
+
if (own)
|
|
127
|
+
return frame({ prefix, v, options, theme, road, kind, title, desc: own.desc ?? desc, width: own.width, height: own.height, followed: false, links: [], parts: [...own.body, ...own.texts], linkCount: linked.count, ctx });
|
|
128
|
+
const plan = planBracket(layout, options, v, size, orientation === "vertical", ctx, road, path, kind);
|
|
129
|
+
const drawn = plan.boxes.map((b) => drawBox(b, plan, options, v, ctx, linked, layout.place[b.tie.index])); // draws the marks, so before the defs are written
|
|
130
|
+
return frame({ prefix, v, options, theme, road, kind, title, desc, width: plan.width, height: plan.height, followed: path.size > 0, links: plan.links, parts: [...drawn, ...plan.texts, ...plan.extras], linkCount: linked.count, ctx });
|
|
131
|
+
}
|
|
132
|
+
/** The drawing's frame: the root `<svg>` with its title, its style, the defs the marks wrote and the background, round the parts a layout drew. */
|
|
133
|
+
function frame(f) {
|
|
134
|
+
const { prefix, v, options, theme, road, ctx } = f;
|
|
135
|
+
const picture = resolveBackground(options.background, theme, f.width, f.height, prefix);
|
|
114
136
|
const defs = markDefs(ctx) + (picture?.defs ?? "");
|
|
115
|
-
const
|
|
116
|
-
const
|
|
137
|
+
const scope = scopeOf(options, prefix);
|
|
138
|
+
const style = theme === "none" || options.style === false ? "" : `<style>${styleFor(`.${scope}`, theme, { road, links: f.linkCount > 0, follow: f.followed, bgAuto: picture?.auto, layout: f.kind !== "tree" || options.headers === true }, picture?.inks)}</style>`;
|
|
139
|
+
const bg = options.background === false ? "" : `<rect class="tb-bg" width="${num(f.width)}" height="${num(f.height)}"${theme === "none" ? ' fill="none"' : ""}/>${picture?.body ?? ""}`;
|
|
117
140
|
return [
|
|
118
|
-
`<svg xmlns="http://www.w3.org/2000/svg" lang="${esc(v.locale.code)}" class="taisen ${prefix}${road ? " tb-look-road" : ""}${
|
|
119
|
-
`<title id="${prefix}-t">${esc(title)}</title><desc id="${prefix}-d">${esc(desc)}</desc>`,
|
|
141
|
+
`<svg xmlns="http://www.w3.org/2000/svg" lang="${esc(v.locale.code)}" class="taisen ${prefix}${scope !== prefix ? ` ${scope}` : ""}${road ? " tb-look-road" : ""}${f.followed ? " tb-follow" : ""}${options.className ? ` ${esc(options.className)}` : ""}" viewBox="0 0 ${num(f.width)} ${num(f.height)}" width="${num(f.width)}" height="${num(f.height)}" style="max-width:100%;height:auto" role="img" aria-labelledby="${prefix}-t ${prefix}-d">`,
|
|
142
|
+
`<title id="${prefix}-t">${esc(f.title)}</title><desc id="${prefix}-d">${esc(f.desc)}</desc>`,
|
|
120
143
|
style,
|
|
121
144
|
defs ? `<defs>${defs}</defs>` : "",
|
|
122
145
|
bg,
|
|
123
|
-
...
|
|
124
|
-
...
|
|
125
|
-
...plan.texts,
|
|
126
|
-
...plan.extras,
|
|
146
|
+
...f.links,
|
|
147
|
+
...f.parts,
|
|
127
148
|
"</svg>",
|
|
128
149
|
].filter(Boolean).join("\n");
|
|
129
150
|
}
|
|
151
|
+
/** The class that carries a drawing's rules: its `styleScope` when it has one, else its own id prefix. */
|
|
152
|
+
function scopeOf(o, prefix) {
|
|
153
|
+
if (o.styleScope === undefined)
|
|
154
|
+
return prefix;
|
|
155
|
+
if (typeof o.styleScope !== "string" || !/^[A-Za-z_][\w-]*$/.test(o.styleScope))
|
|
156
|
+
throw new RangeError("taisen-brackets: styleScope is one word of letters, digits, - and _");
|
|
157
|
+
return o.styleScope;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The stylesheet of a drawing with these options, for a page that draws the same bracket twice (a desk's size and a phone's): put it in the page once, give each drawing the same `styleScope`
|
|
161
|
+
* and `style: false`, and every drawing takes its colours from it. Needs `styleScope`; `theme`, `look`, `background`, `layout`, `headers`, and whether there is an `href` or a `highlightPath` decide which rules are in it.
|
|
162
|
+
*/
|
|
163
|
+
export function bracketStyle(options) {
|
|
164
|
+
if (options.styleScope === undefined)
|
|
165
|
+
throw new RangeError("taisen-brackets: bracketStyle needs a styleScope");
|
|
166
|
+
const scope = scopeOf(options, "");
|
|
167
|
+
const theme = options.theme ?? "auto";
|
|
168
|
+
if (theme === "none")
|
|
169
|
+
return "";
|
|
170
|
+
const picture = resolveBackground(options.background, theme, 100, 100, "x");
|
|
171
|
+
return styleFor(`.${scope}`, theme, { road: options.look === "road", links: typeof options.href === "function", follow: options.highlightPath !== undefined, bgAuto: picture?.auto, layout: (options.layout ?? "tree") !== "tree" || options.headers === true }, picture?.inks);
|
|
172
|
+
}
|
|
173
|
+
/** The groups' names with the east first, for the series card: the names the teams carry, ordered so that a name with "east" comes before one with "west". */
|
|
174
|
+
function eastFirst(input) {
|
|
175
|
+
const teams = Array.isArray(input) ? [] : input.teams ?? [];
|
|
176
|
+
const names = [...new Set(teams.map((t) => t.group).filter((g) => typeof g === "string"))];
|
|
177
|
+
if (names.length < 2)
|
|
178
|
+
return undefined;
|
|
179
|
+
const rank = (g) => (/east/i.test(g) ? 0 : /west/i.test(g) ? 2 : 1);
|
|
180
|
+
return names.sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : 1));
|
|
181
|
+
}
|
|
130
182
|
// ---------------------------------------------------------------------------------------------- the plan
|
|
131
|
-
function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
183
|
+
function planBracket(layout, o, v, size, vertical, ctx, road, path, kind) {
|
|
184
|
+
// The layouts that draw each tie their own way take the tree's places and lines, and fill the boxes themselves.
|
|
185
|
+
const V = VARIANTS.includes(kind) ? kind : null;
|
|
132
186
|
const pad = vertical ? 14 : 22;
|
|
133
187
|
const showLabels = o.roundLabels ?? true;
|
|
134
188
|
const poster = o.theme === "poster";
|
|
@@ -144,6 +198,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
144
198
|
const W0 = size.width;
|
|
145
199
|
const titleH = (o.title ? (poster ? 46 : 38) : 0) + (o.subtitle ? 24 : 0) + (o.title || o.subtitle ? 10 : 0);
|
|
146
200
|
const hasGroups = layout.sides.some((s) => s.label);
|
|
201
|
+
const groupRoom = o.headers === true ? 34 : 28;
|
|
147
202
|
const champ = (o.champion ?? true) && layout.champion !== null && layout.finalIndex !== null;
|
|
148
203
|
const withThird = layout.third !== null && (o.thirdPlace ?? "below") !== "hide";
|
|
149
204
|
// Beside the final in a horizontal bracket of two halves, under the final; elsewhere the lines into the final would run through it, so it goes at the foot.
|
|
@@ -180,7 +235,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
180
235
|
// The names of the rounds head their columns: a name that cannot be one line in its column is set on two, which needs a taller strip over the boxes.
|
|
181
236
|
if (showLabels && layout.columns.some((c) => !nameFits(v, road, roundLabel(layout, c, o.roundNames, v), D + gapT * 0.8)))
|
|
182
237
|
labelRoom = 34;
|
|
183
|
-
const top = pad + titleH + (hasGroups ?
|
|
238
|
+
const top = pad + titleH + (hasGroups ? groupRoom : 0) + labelRoom;
|
|
184
239
|
others = othersOf(layout);
|
|
185
240
|
ow = clamp(D * 1.1, 120, 200);
|
|
186
241
|
extraH0 = othersHeight(others.length, W0, pad, ow, OTHER_H) + thirdBlock;
|
|
@@ -204,7 +259,8 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
204
259
|
const longest = Math.max(0, ...layout.teams.map((t) => wide(t.name, 13)));
|
|
205
260
|
const longestAbbr = Math.max(0, ...layout.teams.map((t) => wide(abbrOf(t, v), 13)));
|
|
206
261
|
// Seed tags (the road look with `seeds`): a small pill beside each mark, or above it in a box of marks alone.
|
|
207
|
-
const
|
|
262
|
+
const seedsDefault = V === "rows" || V === "update" || V === "series" || V === "columns";
|
|
263
|
+
const tagsOn = (road || (V !== null && V !== "columns")) && (o.seeds ?? seedsDefault) && layout.ties.some((t) => tagOf(t, 0) !== "" || tagOf(t, 1) !== "");
|
|
208
264
|
const tagW = tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 8))) + 8 : 0;
|
|
209
265
|
// The road look's marks and scores are large: they are scaled down together until a row fits its box, before a name is dropped for its abbreviation.
|
|
210
266
|
const anyNotes = layout.ties.some((t) => noteParts(t, v).length > 0);
|
|
@@ -263,8 +319,28 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
263
319
|
fitNow = Math.min(1, fitOf(w, chosen.h, chosen.detail));
|
|
264
320
|
return chosen;
|
|
265
321
|
};
|
|
322
|
+
// The layouts of their own: how much a box says follows from its width and height; a tall page gives each a height of its own.
|
|
323
|
+
const variantDetail = (w) => {
|
|
324
|
+
if (o.detail && o.detail !== "auto")
|
|
325
|
+
return o.detail;
|
|
326
|
+
if (V === "shields")
|
|
327
|
+
return "mark";
|
|
328
|
+
if (V === "update" || V === "series")
|
|
329
|
+
return w >= 56 ? "compact" : "mark";
|
|
330
|
+
// The box's own painter leaves out what does not fit (the tag, then the names' length); the names are asked for where there is room for most of them.
|
|
331
|
+
if (V === "columns")
|
|
332
|
+
return w >= 110 ? "full" : w >= 60 ? "compact" : "mark";
|
|
333
|
+
return w >= 150 ? "full" : w >= 84 ? "compact" : "mark";
|
|
334
|
+
};
|
|
335
|
+
const oneLine = V === "series" && vertical && layout.ties.every((t) => seriesLineWidth(t, v, 10, o.scores ?? true) <= boxW - 8);
|
|
336
|
+
const variantH = V === "rows" ? 54 : V === "shields" ? 76 : V === "update" ? 46 : V === "columns" ? 84 : oneLine ? 32 : 46;
|
|
266
337
|
let detail;
|
|
267
|
-
if (
|
|
338
|
+
if (V !== null) {
|
|
339
|
+
detail = variantDetail(boxW);
|
|
340
|
+
if (vertical)
|
|
341
|
+
boxH = variantH;
|
|
342
|
+
}
|
|
343
|
+
else if (road) {
|
|
268
344
|
const picked = roadPick(boxW, vertical ? clamp(boxW * 0.42, 48, 124) : boxH, vertical);
|
|
269
345
|
detail = picked.detail;
|
|
270
346
|
if (vertical)
|
|
@@ -291,7 +367,53 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
291
367
|
const champH = champ ? trophyH + (hasTrophy ? 6 : 0) + champMark + 8 + nameFont + 6 + 11 + 2 : 0;
|
|
292
368
|
const finalW = halves ? (vertical ? Math.min(size.width - 2 * pad, Math.max(boxW, 160)) : Math.min(centreT - 12, Math.max(D * 1.1, 120))) : D;
|
|
293
369
|
let finalH = vertical ? (detail === "mark" ? Math.max(boxH, 52) : boxH) : boxH;
|
|
370
|
+
// The final of the columns layout is two tall slots side by side.
|
|
371
|
+
if (V === "columns")
|
|
372
|
+
finalH = vertical ? Math.max(boxH, 96) : Math.min(Math.max(boxH * 1.7, 120), 220);
|
|
294
373
|
const thirdH = withThird && !bottomThird ? 18 + boxH + 14 : 0;
|
|
374
|
+
// A final alone (the World Cups of 1974 and 1978 ended with one and a third-place match): there is no tree to join, so it is centred, the champion over it and the third place under it.
|
|
375
|
+
const lone = !halves && layout.sides.length === 1 && layout.sides[0].boxes.length === 1 && layout.finalIndex !== null && layout.sides[0].boxes[0].i === layout.finalIndex;
|
|
376
|
+
if (lone) {
|
|
377
|
+
const availW = W0 - 2 * pad;
|
|
378
|
+
const wl = vertical ? Math.min(availW, road ? 340 : 300) : clamp(availW * 0.3, 240, 380);
|
|
379
|
+
let hl = V === "columns" ? 120 : V === "shields" ? 86 : V !== null ? 72 : road ? 0 : 92;
|
|
380
|
+
let dl;
|
|
381
|
+
if (V !== null)
|
|
382
|
+
dl = variantDetail(wl);
|
|
383
|
+
else if (road) {
|
|
384
|
+
const picked = roadPick(wl, 132);
|
|
385
|
+
dl = picked.detail;
|
|
386
|
+
hl = picked.h;
|
|
387
|
+
}
|
|
388
|
+
else
|
|
389
|
+
dl = pick(wl, hl);
|
|
390
|
+
const strays = othersOf(layout);
|
|
391
|
+
const ows = Math.min(availW, 200);
|
|
392
|
+
const strayH = othersHeight(strays.length, W0, pad, ows, OTHER_H);
|
|
393
|
+
const labelH = showLabels ? 22 : 8;
|
|
394
|
+
const content = (champ ? champH + 14 : 0) + labelH + hl + (withThird ? 8 + 18 + hl + 8 : 0) + strayH;
|
|
395
|
+
const top0 = pad + titleH;
|
|
396
|
+
const height = Math.max(size.height ?? 0, top0 + content + pad);
|
|
397
|
+
const cx = W0 / 2;
|
|
398
|
+
let y = top0 + Math.max(6, (height - top0 - pad - content) / 2);
|
|
399
|
+
if (champ) {
|
|
400
|
+
extras.push(...championBlock(layout.champion, cx, y, champMark, trophyH, nameFont, o, v, ctx, poster));
|
|
401
|
+
y += champH + 14;
|
|
402
|
+
}
|
|
403
|
+
if (showLabels)
|
|
404
|
+
texts.push(label(v, road, roundLabel(layout, layout.rounds, o.roundNames, v), cx, y + 12, "middle"));
|
|
405
|
+
y += labelH;
|
|
406
|
+
boxes.push({ rect: { x: cx - wl / 2, y, w: wl, h: hl }, tie: layout.ties[layout.finalIndex], order: layout.sides[0].boxes[0].order, bye: null, byes: [], final: true, champion: champ });
|
|
407
|
+
y += hl;
|
|
408
|
+
if (withThird) {
|
|
409
|
+
thirdAtFoot(layout, y + 8, cx, wl, hl, v, road, boxes, texts);
|
|
410
|
+
y += 8 + 18 + hl + 8;
|
|
411
|
+
}
|
|
412
|
+
if (strays.length > 0)
|
|
413
|
+
othersBlock(layout, strays, y + 6, W0, pad, ows, OTHER_H, v, road, boxes, texts);
|
|
414
|
+
texts.push(...headings(v, o, W0, pad, poster));
|
|
415
|
+
return finish({ width: W0, height, boxes, links: [], texts, extras, detail: dl, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn, variant: V, em: { full: longest / 13, abbr: longestAbbr / 13, tag: tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 1))) : 0 } });
|
|
416
|
+
}
|
|
295
417
|
// ----- the slot positions along t
|
|
296
418
|
if (!vertical) {
|
|
297
419
|
const sizes = [];
|
|
@@ -303,7 +425,7 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
303
425
|
sizes.forEach((sz) => { starts.push(t); t += sz + gapT; });
|
|
304
426
|
const width = Math.max(W0, total + 2 * pad);
|
|
305
427
|
// ----- span positions
|
|
306
|
-
const top = pad + titleH + (hasGroups ?
|
|
428
|
+
const top = pad + titleH + (hasGroups ? groupRoom : 0) + labelRoom;
|
|
307
429
|
const treeH = rows * rowStep;
|
|
308
430
|
// The final sits at the middle of the rows; the champion above it and the third place below need room.
|
|
309
431
|
const upNeed = (champ ? champH + 8 : 0) + 22 + finalH / 2;
|
|
@@ -354,6 +476,8 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
354
476
|
extras.push(...championBlock(layout.champion, cx, fy - champH / 2, champMark, trophyH, nameFont, o, v, ctx, poster));
|
|
355
477
|
}
|
|
356
478
|
}
|
|
479
|
+
if (V === "columns")
|
|
480
|
+
growColumns(layout, rects, false, S, treeH, top, height - pad - extraH0);
|
|
357
481
|
if (showLabels) {
|
|
358
482
|
for (const c of layout.columns) {
|
|
359
483
|
const name = roundLabel(layout, c, o.roundNames, v);
|
|
@@ -370,13 +494,17 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
370
494
|
return;
|
|
371
495
|
const ks = layout.columns.map((_, ci) => slotOfColumn(sd.key, ci));
|
|
372
496
|
const x0 = Math.min(...ks.map((k) => starts[k])), x1 = Math.max(...ks.map((k) => starts[k] + D));
|
|
373
|
-
|
|
497
|
+
const labelY = top - (showLabels ? labelRoom + 12 : 14);
|
|
498
|
+
if (o.headers === true)
|
|
499
|
+
texts.push(headTile(poster ? v.upper(sd.label) : sd.label, (x0 + x1) / 2, x1 - x0, labelY, sd.key === "l" ? 0 : 1, layout.sides.length === 2));
|
|
500
|
+
else
|
|
501
|
+
texts.push(`<text class="tb-half" x="${num((x0 + x1) / 2)}" y="${num(labelY)}" font-size="${poster ? 16 : 15}" font-weight="700" letter-spacing=".1em" text-anchor="middle">${esc(poster ? v.upper(sd.label) : sd.label)}</text>`);
|
|
374
502
|
});
|
|
375
503
|
}
|
|
376
504
|
// lines
|
|
377
505
|
for (const t of layout.ties) {
|
|
378
506
|
const p = layout.parent[t.index];
|
|
379
|
-
if (p === -1)
|
|
507
|
+
if (p === -1 || V === "shields")
|
|
380
508
|
continue;
|
|
381
509
|
const a = rects.get(t.index), b = rects.get(p);
|
|
382
510
|
if (!a || !b || layout.place[t.index] === "o")
|
|
@@ -392,10 +520,10 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
392
520
|
thirdAtFoot(layout, height - pad - extraH0 + 6, width / 2, ow, OTHER_H, v, road, boxes, texts);
|
|
393
521
|
othersBlock(layout, others, height - pad - extraH0 + thirdBlock + 6, width, pad, ow, OTHER_H, v, road, boxes, texts);
|
|
394
522
|
texts.push(...headings(v, o, width, pad, poster));
|
|
395
|
-
return finish({ width, height, boxes, links: orderLinks(links), texts, extras, detail, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn });
|
|
523
|
+
return finish({ width, height, boxes, links: orderLinks(links), texts, extras, detail, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn, variant: V, em: { full: longest / 13, abbr: longestAbbr / 13, tag: tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 1))) : 0 } });
|
|
396
524
|
}
|
|
397
525
|
// ----- vertical: the rounds are rows. The left half is the top; the final is a band in the middle.
|
|
398
|
-
const groupBand = hasGroups ? 26 : 0;
|
|
526
|
+
const groupBand = hasGroups ? (o.headers === true ? 34 : 26) : 0;
|
|
399
527
|
// On a tall page the champion sits at the top, as the page's first thing; the lines into the final must not run through it.
|
|
400
528
|
const champTop = halves && champ ? champH + 14 : 0;
|
|
401
529
|
const topT = pad + titleH + champTop + groupBand + (showLabels ? 14 : 0);
|
|
@@ -406,14 +534,14 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
406
534
|
const othersV = othersHeight(others.length, W0, pad, ow, OTHER_H);
|
|
407
535
|
// The height the page needs for boxes `h` high with `gap` between the rounds, and what it keeps for the final's band and the third-place match.
|
|
408
536
|
const needOf = (h, gap) => {
|
|
409
|
-
const finalHere = detail === "mark" ? Math.max(h, 52) : h;
|
|
537
|
+
const finalHere = V === "columns" ? Math.max(h, finalH) : detail === "mark" ? Math.max(h, 52) : h;
|
|
410
538
|
const centre = (showLabels ? 32 : 0) + 6 + finalHere + 14;
|
|
411
539
|
const thirdV = bottomThird ? 18 + (road ? h : 52) + 16 : 0;
|
|
412
540
|
const band = h + gap;
|
|
413
541
|
return (halves ? topT + bandsPerSide * band + centre + bandsPerSide * band + groupBand + pad : topT + (nc + (champ ? 1 : 0)) * band + (champ ? champH : 0) + pad) + othersV + thirdV;
|
|
414
542
|
};
|
|
415
543
|
// The road look fits a tall page: the boxes are made lower until the rounds fit, or, with room to spare, the room left over is shared between the rounds.
|
|
416
|
-
if (road && size.height !== null) {
|
|
544
|
+
if (road && size.height !== null && V === null) {
|
|
417
545
|
while (boxH > 40 && needOf(boxH, gapT) > size.height)
|
|
418
546
|
boxH -= 2;
|
|
419
547
|
const slack = size.height - needOf(boxH, gapT);
|
|
@@ -422,6 +550,21 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
422
550
|
D = boxH;
|
|
423
551
|
finalH = detail === "mark" ? Math.max(boxH, 52) : boxH;
|
|
424
552
|
}
|
|
553
|
+
// The layouts of their own fill a tall page too: the boxes are made taller, and the rounds are spaced apart, until the page is as high as it was asked to be.
|
|
554
|
+
if (V !== null && size.height !== null && vertical) {
|
|
555
|
+
const cap = V === "series" ? (oneLine ? 52 : 60) : V === "columns" ? 150 : V === "shields" ? 110 : V === "rows" ? 96 : 80;
|
|
556
|
+
while (boxH < cap && needOf(boxH + 2, gapT) <= size.height) {
|
|
557
|
+
boxH += 2;
|
|
558
|
+
if (V !== "columns")
|
|
559
|
+
finalH = boxH;
|
|
560
|
+
}
|
|
561
|
+
D = boxH;
|
|
562
|
+
const slack = size.height - needOf(boxH, gapT);
|
|
563
|
+
if (slack > 0 && bandCount > 0)
|
|
564
|
+
gapT += Math.min(slack / bandCount, 56);
|
|
565
|
+
if (V !== "columns")
|
|
566
|
+
finalH = boxH;
|
|
567
|
+
}
|
|
425
568
|
const centreBand = (showLabels ? 32 : 0) + 6 + finalH + 14;
|
|
426
569
|
const thirdH2 = road ? boxH : 52;
|
|
427
570
|
const thirdBlockV = bottomThird ? 18 + thirdH2 + 16 : 0;
|
|
@@ -467,6 +610,8 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
467
610
|
const fr = rects.get(layout.finalIndex);
|
|
468
611
|
extras.push(...championBlock(layout.champion, fr.x + fr.w / 2, fr.y + fr.h + 12, champMark, trophyH, nameFont, o, v, ctx, poster));
|
|
469
612
|
}
|
|
613
|
+
if (V === "columns")
|
|
614
|
+
growColumns(layout, rects, true, S, treeW, pad, W0 - pad);
|
|
470
615
|
if (showLabels) {
|
|
471
616
|
layout.columns.forEach((c, ci) => {
|
|
472
617
|
const name = roundLabel(layout, c, o.roundNames, v);
|
|
@@ -496,12 +641,15 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
496
641
|
if (!sd.label)
|
|
497
642
|
return;
|
|
498
643
|
const y = sd.key === "l" ? pad + titleH + champTop + 16 : totalT - pad - 2;
|
|
499
|
-
|
|
644
|
+
if (o.headers === true)
|
|
645
|
+
texts.push(headTile(v.upper(sd.label), midX, W0 - 2 * pad, y, sd.key === "l" ? 0 : 1, layout.sides.length === 2));
|
|
646
|
+
else
|
|
647
|
+
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>`);
|
|
500
648
|
});
|
|
501
649
|
}
|
|
502
650
|
for (const t of layout.ties) {
|
|
503
651
|
const p = layout.parent[t.index];
|
|
504
|
-
if (p === -1)
|
|
652
|
+
if (p === -1 || V === "shields")
|
|
505
653
|
continue;
|
|
506
654
|
const a = rects.get(t.index), b = rects.get(p);
|
|
507
655
|
if (!a || !b || layout.place[t.index] === "o")
|
|
@@ -518,7 +666,39 @@ function planBracket(layout, o, v, size, vertical, ctx, road, path) {
|
|
|
518
666
|
thirdAtFoot(layout, totalT - pad + 6, W0 / 2, Math.min(W0 - 2 * pad, road ? Math.max(boxW, 200) : 200), thirdH2, v, road, boxes, texts);
|
|
519
667
|
othersBlock(layout, others, totalT - pad + thirdBlockV + 6, W0, pad, ow, OTHER_H, v, road, boxes, texts);
|
|
520
668
|
texts.push(...headings(v, o, W0, pad, poster));
|
|
521
|
-
return finish({ width: W0, height, boxes, links: orderLinks(links), texts, extras, detail, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn });
|
|
669
|
+
return finish({ width: W0, height, boxes, links: orderLinks(links), texts, extras, detail, vertical, road, path, fit: fitNow, notes: anyNotes, markScores, markCols, tags: tagsOn, codes: codesOn, variant: V, em: { full: longest / 13, abbr: longestAbbr / 13, tag: tagsOn ? Math.max(...layout.ties.flatMap((t) => [tagOf(t, 0), tagOf(t, 1)]).map((x) => boldWidth(x, 1))) : 0 } });
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* The columns layout's tall slots: a tie of a later round is made taller than the first round's, as tall as the room between its neighbours of the same column leaves (`base` times 1.55 for each round in, at most),
|
|
673
|
+
* on the same centre. `span` is the room across the tree, for a column with a single tie.
|
|
674
|
+
*/
|
|
675
|
+
function growColumns(layout, rects, vertical, base, span, lo, hi) {
|
|
676
|
+
const centre = (r) => (vertical ? r.x + r.w / 2 : r.y + r.h / 2);
|
|
677
|
+
for (const sd of layout.sides) {
|
|
678
|
+
layout.columns.forEach((col, ci) => {
|
|
679
|
+
const here = sd.boxes.filter((b) => b.round === col).map((b) => rects.get(b.i)).filter((r) => r !== undefined);
|
|
680
|
+
if (here.length === 0)
|
|
681
|
+
return;
|
|
682
|
+
const at = here.map(centre).sort((a, b) => a - b);
|
|
683
|
+
let gap = Infinity;
|
|
684
|
+
for (let i = 1; i < at.length; i++)
|
|
685
|
+
gap = Math.min(gap, at[i] - at[i - 1]);
|
|
686
|
+
const want = Math.max(base, Math.min(base * 1.55 ** ci, gap === Infinity ? span * 0.6 : gap - 8));
|
|
687
|
+
for (const r of here) {
|
|
688
|
+
const c = centre(r);
|
|
689
|
+
// Never past the room there is on either side of the centre.
|
|
690
|
+
const size = Math.max(Math.min(base, 2 * (c - lo), 2 * (hi - c)), Math.min(want, 2 * (c - lo), 2 * (hi - c)));
|
|
691
|
+
if (vertical) {
|
|
692
|
+
r.x = c - size / 2;
|
|
693
|
+
r.w = size;
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
r.y = c - size / 2;
|
|
697
|
+
r.h = size;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
522
702
|
}
|
|
523
703
|
/** A line's class: `tb-path` when both ties it joins are on the highlighted team's path. */
|
|
524
704
|
function lineClass(path, from, to) {
|
|
@@ -675,7 +855,7 @@ function championBlock(team, cx, top, mark, trophyH, nameFont, o, v, ctx, poster
|
|
|
675
855
|
}
|
|
676
856
|
/** 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. */
|
|
677
857
|
function othersOf(layout) {
|
|
678
|
-
return [...layout.other, ...layout.robin];
|
|
858
|
+
return [...layout.other, ...layout.robin, ...layout.playIn];
|
|
679
859
|
}
|
|
680
860
|
function othersHeight(count, width, pad, ow, oh) {
|
|
681
861
|
if (!count)
|
|
@@ -706,7 +886,7 @@ function hrefOf(o, tie, side) {
|
|
|
706
886
|
const url = side === undefined ? o.href(tie) : o.href(tie, side);
|
|
707
887
|
return typeof url === "string" && safeHref(url) ? url.trim() : null;
|
|
708
888
|
}
|
|
709
|
-
function drawBox(p, plan, o, v, ctx, linked) {
|
|
889
|
+
function drawBox(p, plan, o, v, ctx, linked, side) {
|
|
710
890
|
const { rect, tie } = p;
|
|
711
891
|
const { x, y, w, h } = rect;
|
|
712
892
|
const detail = plan.detail;
|
|
@@ -738,7 +918,14 @@ function drawBox(p, plan, o, v, ctx, linked) {
|
|
|
738
918
|
return inner;
|
|
739
919
|
return anchor(url, `${team.name}: ${description}`, `<rect class="tb-hit" x="${num(rx)}" y="${num(ry)}" width="${num(rw)}" height="${num(rh)}" fill="transparent"/>${inner}`);
|
|
740
920
|
};
|
|
741
|
-
if (
|
|
921
|
+
if (plan.variant !== null) {
|
|
922
|
+
const env = {
|
|
923
|
+
kind: plan.variant, x, y, w, h, tie, order: p.order, side, final: p.final, road, detail, vertical: plan.vertical, tags: plan.tags, codes: plan.codes, scoresOn, v, ctx, o,
|
|
924
|
+
cls, link: row, tagOf, em: plan.em,
|
|
925
|
+
};
|
|
926
|
+
parts.push(...paintVariant(env));
|
|
927
|
+
}
|
|
928
|
+
else if (detail === "mark" && plan.markCols) {
|
|
742
929
|
// Two columns, each a stack: the seed tag, the mark, its three letters, the score. The marks are as large as the box lets the whole stack be, and every part is inside the box.
|
|
743
930
|
const padX = 6, padY = 5;
|
|
744
931
|
const colW = (w - 2 * padX) / 2;
|
|
@@ -876,7 +1063,9 @@ function drawBox(p, plan, o, v, ctx, linked) {
|
|
|
876
1063
|
}
|
|
877
1064
|
}
|
|
878
1065
|
const extra = tieClassOf(o, tie);
|
|
879
|
-
|
|
1066
|
+
// The layouts of their own colour a half by its side: the first half (the left, or the top) is `tb-c0`, the second `tb-c1`.
|
|
1067
|
+
const conf = plan.variant !== null && (side === "l" || side === "r") ? ` tb-c${side === "l" ? 0 : 1}` : "";
|
|
1068
|
+
const cls2 = `tb-tie${p.final ? " tb-final" : ""}${p.champion ? " tb-has-champ" : ""}${plan.path.has(tie.index) ? " tb-on-path" : ""}${conf}${extra}`;
|
|
880
1069
|
const body = boxHref !== null ? anchor(boxHref, description, parts.join("")) : parts.join("");
|
|
881
1070
|
return `<g class="${cls2}"><title>${esc(description)}</title>${body}</g>`;
|
|
882
1071
|
}
|
package/dist/seeding.js
CHANGED
|
@@ -80,10 +80,12 @@ export function advanceWinners(matchups) {
|
|
|
80
80
|
m.feeds.forEach((feed, k) => {
|
|
81
81
|
if (feed == null)
|
|
82
82
|
return;
|
|
83
|
-
const child = at.get(feed);
|
|
83
|
+
const child = at.get(typeof feed === "string" ? feed : feed.tie);
|
|
84
84
|
if (!child || child.winner === null)
|
|
85
85
|
return;
|
|
86
|
-
|
|
86
|
+
// The loser goes on from a third-place match's feeders, and from a feed that asks for the loser (a play-in's).
|
|
87
|
+
const takesLoser = m.third || (typeof feed !== "string" && feed.result === "loser");
|
|
88
|
+
const team = child.sides[takesLoser ? (1 - child.winner) : child.winner];
|
|
87
89
|
const here = sides[k];
|
|
88
90
|
if (team && (here !== null && typeof here === "object" ? here.id : here) !== team.id) {
|
|
89
91
|
sides[k] = team.id;
|
package/dist/table.js
CHANGED
|
@@ -15,7 +15,7 @@ export function bracketTable(input, options = {}) {
|
|
|
15
15
|
const w = v.words;
|
|
16
16
|
const caption = options.caption ?? fill(layout.rounds === 1 ? w.summaryOne : w.summary, { n: v.number(layout.teams.length), r: v.number(layout.rounds) });
|
|
17
17
|
const order = layout.ties
|
|
18
|
-
.filter((t) => t.round > 0 || layout.robin.includes(t.index))
|
|
18
|
+
.filter((t) => t.round > 0 || layout.robin.includes(t.index) || layout.playIn.includes(t.index))
|
|
19
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
20
|
const dated = order.some((t) => v.date(t.date) !== null);
|
|
21
21
|
const name = (t, k) => {
|
|
@@ -24,10 +24,13 @@ export function bracketTable(input, options = {}) {
|
|
|
24
24
|
return t.winner === k ? `<strong>${text}</strong>` : text;
|
|
25
25
|
};
|
|
26
26
|
const score = (t) => (t.result.score ? fill(w.scoreText, { a: v.number(t.result.score[0]), b: v.number(t.result.score[1]) }) : "");
|
|
27
|
+
// Where a side comes from, in words, when the tie says so: "Loser of A v B".
|
|
28
|
+
const named = (i) => fill(w.tieVs, { a: layout.ties[i].sides[0]?.name ?? w.tbd, b: layout.ties[i].sides[1]?.name ?? w.tbd });
|
|
29
|
+
const origins = (t) => (t.from ?? []).flatMap((s) => (s ? [fill(s.result === "loser" ? w.loserOf : w.winnerOf, { name: named(s.tie) })] : []));
|
|
27
30
|
const rows = order.map((t) => {
|
|
28
|
-
const roundText = t.third ? w.thirdPlace : t.round === 0 ? "" : roundLabel(layout, t.round, options.roundNames, v);
|
|
31
|
+
const roundText = t.third ? w.thirdPlace : t.playIn ? w.playIn : t.round === 0 ? "" : roundLabel(layout, t.round, options.roundNames, v);
|
|
29
32
|
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>`;
|
|
33
|
+
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), ...origins(t)].join(w.noteJoin))}</td></tr>`;
|
|
31
34
|
});
|
|
32
35
|
return [
|
|
33
36
|
`<table class="${esc(options.className ?? "tb-table")}" lang="${esc(v.locale.code)}">`,
|
package/dist/theme.d.ts
CHANGED
|
@@ -34,7 +34,11 @@ export interface StyleParts {
|
|
|
34
34
|
follow?: boolean;
|
|
35
35
|
/** A background with a light and a dark picture in one drawing: the one that does not fit the look is hidden. */
|
|
36
36
|
bgAuto?: boolean;
|
|
37
|
+
/** The rules of the layouts of their own (slots, shields, conference colours, header tiles, arrows, group cards). */
|
|
38
|
+
layout?: boolean;
|
|
37
39
|
}
|
|
40
|
+
/** The colours of the two halves (conferences) of the layouts that colour them, each with white letters on it: `--tb-conf-a` and `--tb-conf-b`. Both read 5 to 1 or more against white. */
|
|
41
|
+
export declare const CONFERENCE_COLORS: readonly [string, string];
|
|
38
42
|
/** The colours a background says to draw in (`backgroundSvg`'s `ink`): what `styleFor` takes in place of a look's own palette. */
|
|
39
43
|
export interface BackgroundInks {
|
|
40
44
|
light?: BackgroundInk;
|
package/dist/theme.js
CHANGED
|
@@ -10,6 +10,8 @@ export const ROAD_THEMES = Object.freeze({
|
|
|
10
10
|
poster: { ring: "#f6f1e3", edge: "rgba(0,0,0,.5)", panel: "#1b2766", spine: "#d9b24c" },
|
|
11
11
|
});
|
|
12
12
|
const FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Hiragino Sans", "Yu Gothic UI", "Noto Sans CJK JP", Meiryo, sans-serif';
|
|
13
|
+
/** The colours of the two halves (conferences) of the layouts that colour them, each with white letters on it: `--tb-conf-a` and `--tb-conf-b`. Both read 5 to 1 or more against white. */
|
|
14
|
+
export const CONFERENCE_COLORS = Object.freeze(["#2457c5", "#b3261e"]);
|
|
13
15
|
/** A palette and a road palette from a background's ink. */
|
|
14
16
|
function fromInk(ink) {
|
|
15
17
|
return [
|
|
@@ -53,6 +55,10 @@ function rules(p, r, poster, withFont, parts, side) {
|
|
|
53
55
|
if (parts.chart === true) {
|
|
54
56
|
list.push([[".tb-r-t"], `fill:${v("ink")};font-weight:800`], [[".tb-r-s", ".tb-r-e", ".tb-r-sub", ".tb-r-code", ".tb-r-vs", ".tb-r-dash"], `fill:${v("muted")};font-weight:700`], [[".tb-r-name", ".tb-r-n"], `fill:${v("ink")};font-weight:800`], [[".tb-r-n.tb-r-low"], `fill:${v("muted")}`], [[".tb-r-dim"], "opacity:.5"], [["text"], "font-variant-numeric:tabular-nums"]);
|
|
55
57
|
}
|
|
58
|
+
if (parts.layout === true) {
|
|
59
|
+
const [ca, cb] = CONFERENCE_COLORS;
|
|
60
|
+
list.push([[".tb-slot"], `fill:${v("plain")};fill-opacity:.55;stroke:${v("edge")};stroke-width:1`], [[".tb-won .tb-slot"], `fill-opacity:.9;stroke:${v("win")};stroke-width:1.6`], [[".tb-lost .tb-slot"], "fill-opacity:.35"], [[".tb-emptybar"], `fill:${v("plain")};opacity:.8`], [[".tb-seed"], "font-variant-numeric:tabular-nums"], [[".tb-seedn"], `fill:${v("muted")};opacity:.85`], [[".tb-shield"], `fill:none;stroke:${v("edge")};stroke-width:1.4`], [[".tb-won .tb-shield"], `stroke:${v("win")};stroke-width:2.2`], [[".tb-shield.tb-empty"], `fill:${v("plain")};fill-opacity:.5`], [[".tb-vs", ".tb-code"], `fill:${v("muted")};font-weight:700`], [[".tb-c0 .tb-box"], `stroke:var(--tb-conf-a, ${ca});stroke-width:1.6`], [[".tb-c1 .tb-box"], `stroke:var(--tb-conf-b, ${cb});stroke-width:1.6`], [[".tb-atag"], `fill:${v("ink")}`], [[".tb-atag-t"], `fill:${v("bg")}`], [[".tb-c0 .tb-atag"], `fill:var(--tb-conf-a, ${ca})`], [[".tb-c1 .tb-atag"], `fill:var(--tb-conf-b, ${cb})`], [[".tb-c0 .tb-atag-t", ".tb-c1 .tb-atag-t"], "fill:#fff"], [[".tb-head"], `fill:${rv("panel", "panel")}`], [[".tb-head.tb-c0"], `fill:var(--tb-conf-a, ${ca})`], [[".tb-head.tb-c1"], `fill:var(--tb-conf-b, ${cb})`], [[".tb-head-t"], `fill:${v("ink")};font-weight:800`], [[".tb-head.tb-c0 + .tb-head-t", ".tb-head.tb-c1 + .tb-head-t"], "fill:#fff"], [[".tb-arrow"], `stroke:${v("line")};stroke-width:1.6;fill:none;stroke-linecap:round;stroke-linejoin:round`], [[".tb-arrow-loser"], "stroke-dasharray:5 4"], [[".tb-arrowhead"], `fill:${v("line")};stroke:none`], [[".tb-gcard"], `fill:${v("box")};stroke:${v("edge")};stroke-width:1`], [[".tb-gtile"], `fill:var(--tb-conf-a, ${ca})`], [[".tb-gtile-t"], "fill:#fff;font-weight:800"], [[".tb-ghead"], `fill:${v("muted")};font-weight:700`], [[".tb-gcell"], `fill:${v("ink")};font-variant-numeric:tabular-nums`], [[".tb-gcell-p"], "font-weight:800"], [[".tb-gname"], `fill:${v("ink")};font-weight:700`], [[".tb-qual"], `fill:${v("win")};fill-opacity:.16`], [[".tb-gsep"], `stroke:${v("edge")};stroke-width:1;stroke-opacity:.6`]);
|
|
61
|
+
}
|
|
56
62
|
if (parts.follow === true) {
|
|
57
63
|
list.push([[".tb-tie"], "opacity:.4"], [[".tb-tie.tb-on-path"], "opacity:1"], [[".tb-on-path .tb-box"], `stroke:${v("win")};stroke-width:2.2`], [[".tb-line"], "stroke-opacity:.25"], [[".tb-line.tb-path"], `stroke:${v("win")};stroke-width:4.5;stroke-opacity:1;stroke-linecap:round;stroke-dasharray:.1 9`]);
|
|
58
64
|
}
|