@johnmorrisdotca/kyogijo-stadiums 0.6.2 → 0.8.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/README.md +2 -2
- package/dist/budo/looks.js +1 -1
- package/dist/budo/room.d.ts +34 -2
- package/dist/budo/room.js +39 -7
- package/dist/budo/types.d.ts +6 -1
- package/dist/budo.style.d.ts +1 -1
- package/dist/budo.style.js +6 -3
- package/dist/charts-entry.d.ts +0 -3
- package/dist/charts-entry.js +1 -3
- package/dist/crowd.d.ts +4 -0
- package/dist/crowd.draw.d.ts +5 -0
- package/dist/crowd.draw.js +34 -5
- package/dist/crowd.js +6 -1
- package/dist/draw.js +6 -3
- package/dist/element.d.ts +3 -3
- package/dist/element.js +16 -2
- package/dist/halls.js +4 -3
- package/dist/layout.js +1 -2
- package/dist/mark.d.ts +1 -1
- package/dist/mark.js +1 -1
- package/dist/strings.d.ts +7 -29
- package/dist/strings.js +6 -24
- package/dist/style.js +1 -2
- package/dist/surfaces/budo.js +22 -2
- package/dist/turn-entry.d.ts +2 -0
- package/dist/turn-entry.js +1 -0
- package/dist/turn.dom.d.ts +8 -0
- package/dist/turn.dom.js +147 -8
- package/dist/turn.kit.d.ts +5 -3
- package/dist/turn.kit.js +1 -1
- package/dist/turn.markup.d.ts +8 -1
- package/dist/turn.markup.js +18 -4
- package/dist/turn.model.d.ts +26 -0
- package/dist/turn.model.js +34 -3
- package/dist/turn.style.js +58 -4
- package/dist/uprights.d.ts +7 -0
- package/dist/uprights.js +15 -1
- package/dist/venue.d.ts +31 -0
- package/dist/venue.geom.d.ts +53 -0
- package/dist/venue.geom.js +225 -0
- package/dist/venue.js +172 -0
- package/dist/venue.room.d.ts +9 -0
- package/dist/venue.room.js +240 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/road.d.ts +0 -26
- package/dist/road.draw.d.ts +0 -6
- package/dist/road.draw.js +0 -270
- package/dist/road.js +0 -60
- package/dist/road.model.d.ts +0 -63
- package/dist/road.model.js +0 -215
- package/dist/road.style.d.ts +0 -7
- package/dist/road.style.js +0 -26
- package/dist/road.types.d.ts +0 -125
- package/dist/road.types.js +0 -1
package/dist/road.draw.js
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
// The road chart, drawn: the two finalists' big round marks with their names, the round names down the middle on a dotted road, each finalist's matches on its side as
|
|
2
|
-
// the round marks of both teams with the score between them, and the group stage as a compact row at the foot. Each size has its own arrangement: `card`, `square` and
|
|
3
|
-
// `phone` stack the finalists over the rounds; `wide` puts them at the two ends. Nothing is measured in a browser: type is set by estimates (`textWidth`).
|
|
4
|
-
import { safeColour } from "./colour.js";
|
|
5
|
-
import { safeLogo } from "./logo.js";
|
|
6
|
-
import { safeClass } from "./common.js";
|
|
7
|
-
import { el, esc, num } from "./format.js";
|
|
8
|
-
import { KYOGIJO_MARK_STYLE, roundMarkup } from "./mark.js";
|
|
9
|
-
import { codeOf, decidedWords, nameOf, summaryOf } from "./road.model.js";
|
|
10
|
-
import { KYOGIJO_ROAD_STYLE } from "./road.style.js";
|
|
11
|
-
import { capitals, fitLines, textWidth } from "./text.js";
|
|
12
|
-
import { VERSION } from "./version.js";
|
|
13
|
-
/** The sizes in pixels: `card` 1080 by 1350, `square` 1080 by 1080, `wide` 1200 by 675 and `phone` 390 wide and as tall as the rounds need. */
|
|
14
|
-
export const ROAD_SIZES = Object.freeze({
|
|
15
|
-
card: { width: 1080, height: 1350, about: "1080 by 1350, a tall post" },
|
|
16
|
-
square: { width: 1080, height: 1080, about: "1080 by 1080, a square post" },
|
|
17
|
-
wide: { width: 1200, height: 675, about: "1200 by 675, a wide card with the finalists at the two ends" },
|
|
18
|
-
phone: { width: 390, height: undefined, about: "390 wide, as tall as the rounds need" },
|
|
19
|
-
});
|
|
20
|
-
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
21
|
-
/** A text centred on `y`, `size` high. */
|
|
22
|
-
const text = (cls, x, y, size, content, anchor = "middle") => el("text", { class: cls, x: num(x), y: num(y + size * 0.36), "font-size": num(size), "text-anchor": anchor }, esc(content));
|
|
23
|
-
/** The width of a match's score block in ems of its number size: the two numbers and the dash. */
|
|
24
|
-
const scoreEms = (v) => (v.whole !== undefined ? textWidth(v.whole) : textWidth(v.mine) + textWidth(v.theirs) + 1.1);
|
|
25
|
-
/** The group stage's score as one text. */
|
|
26
|
-
const sub0 = (v) => v.note !== undefined ? v.note !== "" : v.says;
|
|
27
|
-
const groupScore = (v) => v.whole ?? `${v.mine}–${v.theirs}`;
|
|
28
|
-
/** Draw the chart as an SVG string. `gid` is the prefix of its ids. */
|
|
29
|
-
export function drawRoad(model, o, gid) {
|
|
30
|
-
const size = o.size !== undefined && Object.prototype.hasOwnProperty.call(ROAD_SIZES, o.size) ? o.size : "card";
|
|
31
|
-
const info = ROAD_SIZES[size];
|
|
32
|
-
const w = model.words.road;
|
|
33
|
-
const W = info.width;
|
|
34
|
-
const phone = size === "phone";
|
|
35
|
-
const wide = size === "wide";
|
|
36
|
-
const [fa, fb] = model.finalists;
|
|
37
|
-
const rounds = [...model.rounds].reverse();
|
|
38
|
-
const R = rounds.length;
|
|
39
|
-
const nGroup = Math.max(model.group[0].length, model.group[1].length);
|
|
40
|
-
const emphasis = o.winner !== "none";
|
|
41
|
-
const codes = o.codes !== false;
|
|
42
|
-
const theme = o.theme === "light" || o.theme === "dark" ? o.theme : undefined;
|
|
43
|
-
let marks = 0;
|
|
44
|
-
const markOf = (team, cx, cy, r, cls) => roundMarkup({
|
|
45
|
-
id: `${gid}-m${marks++}`,
|
|
46
|
-
cx,
|
|
47
|
-
cy,
|
|
48
|
-
r,
|
|
49
|
-
href: safeLogo(team.logo, { color: team.color, accent: team.textColor, initials: codeOf(team).slice(0, 3), theme }),
|
|
50
|
-
fit: team.markFit === "contain" ? "contain" : undefined,
|
|
51
|
-
initials: codeOf(team).slice(0, 3),
|
|
52
|
-
color: safeColour(team.color),
|
|
53
|
-
ink: safeColour(team.textColor),
|
|
54
|
-
title: team.name,
|
|
55
|
-
className: cls,
|
|
56
|
-
});
|
|
57
|
-
// ---- The type and the room round the edge. ----
|
|
58
|
-
const tall = info.height ?? 1e9;
|
|
59
|
-
const pad = phone ? 16 : Math.round(Math.min(W * 0.045, tall * 0.05));
|
|
60
|
-
const ts = clamp(Math.min(W * 0.044, tall * 0.056), 17, 48);
|
|
61
|
-
const es = clamp(Math.min(W * 0.0185, tall * 0.022), 9, 20);
|
|
62
|
-
const room = W - 2 * pad;
|
|
63
|
-
const titleFit = fitLines(model.title, room * 0.94, ts * 2.3, ts);
|
|
64
|
-
const subSize = model.subtitle === undefined ? 0 : Math.min(ts * 0.5, (room * 0.94) / Math.max(1, textWidth(model.subtitle)));
|
|
65
|
-
const headH = titleFit.height + (model.subtitle === undefined ? 0 : subSize * 1.5) + ts * 0.45;
|
|
66
|
-
const note = o.endnote === undefined ? "" : String(o.endnote).trim();
|
|
67
|
-
const endSize = note === "" ? 0 : Math.min(es, room / Math.max(1, textWidth(note, false)));
|
|
68
|
-
const footH = endSize === 0 ? 0 : endSize * 2.4;
|
|
69
|
-
// ---- The columns: the middle one holds the rounds' names; a wide chart has a column for each finalist at its end. ----
|
|
70
|
-
const cl = phone ? 70 : wide ? W * 0.12 : W * 0.175;
|
|
71
|
-
const fc = wide ? W * 0.17 : 0;
|
|
72
|
-
const sideW = (W - 2 * pad - cl - 2 * fc) / 2;
|
|
73
|
-
const lanes = [
|
|
74
|
-
{ x0: pad + fc, x1: pad + fc + sideW },
|
|
75
|
-
{ x0: W - pad - fc - sideW, x1: W - pad - fc },
|
|
76
|
-
];
|
|
77
|
-
const cx = W / 2;
|
|
78
|
-
// ---- The group stage: how many marks in a row, and how large. ----
|
|
79
|
-
const maxCols = Math.max(1, Math.floor(sideW / (phone ? 46 : W * 0.062)));
|
|
80
|
-
const cols = nGroup === 0 ? 1 : Math.min(maxCols, nGroup <= 4 ? nGroup : Math.ceil(nGroup / 2));
|
|
81
|
-
const groupRows = nGroup === 0 ? 0 : Math.ceil(nGroup / cols);
|
|
82
|
-
const cw = sideW / cols;
|
|
83
|
-
const dm = Math.min(cw * 0.56, phone ? 36 : Math.min(W * 0.052, tall * 0.065));
|
|
84
|
-
const cellH = dm * 2.08;
|
|
85
|
-
const groupBlock = nGroup === 0 ? 0 : groupRows * cellH + (groupRows - 1) * dm * 0.2 + (phone ? 26 : dm * 0.1);
|
|
86
|
-
// ---- The big marks, the rows and the vertical room. ----
|
|
87
|
-
const rowLabelH = phone ? 22 : 0;
|
|
88
|
-
let H = info.height ?? 0;
|
|
89
|
-
let D = wide ? Math.min(fc * 0.8, H * 0.36) : Math.min(sideW * 0.68, (info.height ?? 1e9) * 0.19);
|
|
90
|
-
const names = (d) => [fa, fb].map((t) => fitLines(capitals(t.name), (wide ? fc : sideW) * 0.96, d * 0.17 * 2.3, d * 0.17));
|
|
91
|
-
const slots = wide ? R + 1 : R;
|
|
92
|
-
const minRow = W * 0.085;
|
|
93
|
-
let rowH = phone ? 76 : 0;
|
|
94
|
-
let bandH = 0;
|
|
95
|
-
let regionTop = 0;
|
|
96
|
-
let regionBottom = 0;
|
|
97
|
-
const gapS = phone ? 14 : (info.height ?? 1000) * 0.016;
|
|
98
|
-
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
99
|
-
bandH = D + D * 0.08 + Math.max(...names(D).map((n) => n.height));
|
|
100
|
-
if (phone) {
|
|
101
|
-
regionTop = pad + headH + bandH + gapS;
|
|
102
|
-
regionBottom = regionTop + R * (rowH + rowLabelH);
|
|
103
|
-
H = regionBottom + (nGroup > 0 ? gapS + groupBlock : 0) + footH + pad;
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
regionTop = pad + headH + (wide ? 0 : bandH + gapS);
|
|
107
|
-
regionBottom = H - pad - footH - (nGroup > 0 ? groupBlock + gapS : 0);
|
|
108
|
-
rowH = slots > 0 ? (regionBottom - regionTop) / slots : 0;
|
|
109
|
-
if (wide || rowH >= minRow || D < sideW * 0.25)
|
|
110
|
-
break;
|
|
111
|
-
D *= 0.86;
|
|
112
|
-
}
|
|
113
|
-
rowH = Math.min(rowH, W * 0.2);
|
|
114
|
-
// ---- The pieces of a row: the marks, the numbers, the gaps. ----
|
|
115
|
-
const subRatio = 0.42;
|
|
116
|
-
let ns = rowH * 0.34;
|
|
117
|
-
let d = rowH * 0.58;
|
|
118
|
-
let ems = 2.2;
|
|
119
|
-
for (const key of rounds) {
|
|
120
|
-
for (const i of [0, 1]) {
|
|
121
|
-
const v = model.path[i].get(key);
|
|
122
|
-
if (v !== undefined)
|
|
123
|
-
ems = Math.max(ems, scoreEms(v), textWidth(decidedWords(v, w, true), false) * subRatio);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const fit = Math.min(1, (sideW * 0.97) / (2 * d + 2 * d * 0.14 + ems * ns));
|
|
127
|
-
ns *= fit;
|
|
128
|
-
d *= fit;
|
|
129
|
-
const pair = 2 * d + 2 * d * 0.14 + ems * ns;
|
|
130
|
-
// The top of a row, the first being the last round. A wide chart's first slot is the final.
|
|
131
|
-
const rowTop = (i) => {
|
|
132
|
-
if (phone)
|
|
133
|
-
return regionTop + i * (rowH + rowLabelH) + rowLabelH;
|
|
134
|
-
return regionTop + (regionBottom - regionTop - slots * rowH) / 2 + (wide ? i + 1 : i) * rowH;
|
|
135
|
-
};
|
|
136
|
-
const dimOf = (result, forFinalist) => (emphasis && ((forFinalist && result === "loss") || (!forFinalist && result === "win")) ? " ky-r-dim" : "");
|
|
137
|
-
// The round's name: a panel in the middle column, or a line over the row on a phone.
|
|
138
|
-
const panel = (words, yc, height, width) => {
|
|
139
|
-
const fitted = fitLines(words, width * 0.88, height * 0.74, height * 0.54);
|
|
140
|
-
const lines = fitted.lines.map((line, i) => text("ky-r-lab", cx, yc + (i - (fitted.lines.length - 1) / 2) * fitted.size * 1.1, fitted.size, line));
|
|
141
|
-
return el("rect", { class: "ky-r-panel", x: num(cx - width / 2), y: num(yc - height / 2), width: num(width), height: num(height), rx: num(height / 2) }) + lines.join("");
|
|
142
|
-
};
|
|
143
|
-
const label = (key, yc) => {
|
|
144
|
-
const words = model.label(key);
|
|
145
|
-
if (!phone)
|
|
146
|
-
return panel(words, yc, Math.min(rowH * 0.34, cl * 0.2), cl * 0.94);
|
|
147
|
-
const y = yc - rowH / 2 - rowLabelH / 2;
|
|
148
|
-
const width = textWidth(words) * 11.5 + 20;
|
|
149
|
-
return el("rect", { class: "ky-r-panel", x: num(cx - width / 2), y: num(y - 9), width: num(width), height: 18, rx: 9 }) + text("ky-r-lab", cx, y, 11.5, words);
|
|
150
|
-
};
|
|
151
|
-
// One finalist's match in a row: the two marks and the score between them.
|
|
152
|
-
const matchMarkup = (lane, side, yc, v) => {
|
|
153
|
-
const left = v.homeFirst !== undefined ? v.homeFirst : side === 0;
|
|
154
|
-
const xc = (lane.x0 + lane.x1) / 2;
|
|
155
|
-
const y = yc - rowH * 0.04;
|
|
156
|
-
const lx = xc - pair / 2 + d / 2;
|
|
157
|
-
const rx = xc + pair / 2 - d / 2;
|
|
158
|
-
let out = markOf(model.finalists[side], left ? lx : rx, y, d / 2, `${side === 0 ? "ky-r-left" : "ky-r-right"}${dimOf(v.result, true)}`);
|
|
159
|
-
out += markOf(v.opponent, left ? rx : lx, y, d / 2, `ky-r-plain${dimOf(v.result, false)}`);
|
|
160
|
-
if (codes)
|
|
161
|
-
out += text(`ky-r-code${dimOf(v.result, false)}`, left ? rx : lx, y + d / 2 + d * 0.2, d * 0.2, codeOf(v.opponent));
|
|
162
|
-
out += scoreMarkup(v, xc, y, ns, left);
|
|
163
|
-
const sub = decidedWords(v, w, left);
|
|
164
|
-
return sub === "" ? out : out + text("ky-r-sub", xc, y + ns * 0.82, ns * subRatio, sub);
|
|
165
|
-
};
|
|
166
|
-
// The score: the two numbers either side of a dash, the loser's quiet. The first side's number is on the left when `left`.
|
|
167
|
-
const scoreMarkup = (v, x, y, n, left) => {
|
|
168
|
-
if (v.whole !== undefined)
|
|
169
|
-
return text("ky-r-n", x, y, n * 0.9, v.whole);
|
|
170
|
-
const [a, b, lowA, lowB] = left ? [v.mine, v.theirs, v.result === "loss", v.result === "win"] : [v.theirs, v.mine, v.result === "win", v.result === "loss"];
|
|
171
|
-
return (text(`ky-r-n${emphasis && lowA ? " ky-r-low" : ""}`, x - n * 0.4, y, n, a, "end") +
|
|
172
|
-
text("ky-r-dash", x, y, n * 0.8, "–") +
|
|
173
|
-
text(`ky-r-n${emphasis && lowB ? " ky-r-low" : ""}`, x + n * 0.4, y, n, b, "start"));
|
|
174
|
-
};
|
|
175
|
-
let body = "";
|
|
176
|
-
for (const [i, key] of rounds.entries()) {
|
|
177
|
-
const yc = rowTop(i) + rowH / 2;
|
|
178
|
-
body += label(key, yc);
|
|
179
|
-
for (const side of [0, 1]) {
|
|
180
|
-
const v = model.path[side].get(key);
|
|
181
|
-
if (v !== undefined)
|
|
182
|
-
body += matchMarkup(lanes[side], side, yc, v);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// ---- The finalists. ----
|
|
186
|
-
const final = model.final;
|
|
187
|
-
const lost = (side) => emphasis && final !== undefined && (side === 0 ? final.result === "loss" : final.result === "win");
|
|
188
|
-
const named = names(D);
|
|
189
|
-
const flagX = wide ? [pad + fc / 2, W - pad - fc / 2] : [(lanes[0].x0 + lanes[0].x1) / 2, (lanes[1].x0 + lanes[1].x1) / 2];
|
|
190
|
-
const flagY = wide ? (regionTop + regionBottom) / 2 - (D * 0.08 + Math.max(...named.map((n) => n.height))) / 2 : pad + headH + (phone ? 0 : (info.height ?? 1000) * 0.008) + D / 2;
|
|
191
|
-
let band = "";
|
|
192
|
-
for (const side of [0, 1]) {
|
|
193
|
-
const dim = lost(side) ? " ky-r-dim" : "";
|
|
194
|
-
band += markOf(model.finalists[side], flagX[side], flagY, D / 2, `${side === 0 ? "ky-r-left" : "ky-r-right"}${dim}`);
|
|
195
|
-
const nb = named[side];
|
|
196
|
-
const top = flagY + D / 2 + D * 0.08;
|
|
197
|
-
band += nb.lines.map((line, k) => text(`ky-r-name${dim}`, flagX[side], top + nb.size * 0.55 + k * nb.size * 1.1, nb.size, line)).join("");
|
|
198
|
-
}
|
|
199
|
-
// The final between them: its name over its score, or "v" when it is not played.
|
|
200
|
-
const fy = wide ? rowTop(-1) + rowH / 2 : flagY;
|
|
201
|
-
const labelSize = clamp(cl * 0.13, 9, 26);
|
|
202
|
-
const fs = wide ? Math.min(clamp(cl * 0.27, 14, 58), rowH * 0.36) : clamp(cl * 0.27, 14, 58);
|
|
203
|
-
let middle = text("ky-r-sub", cx, fy - (wide ? fs * 0.95 : Math.min(D * 0.3, cl * 0.34)), labelSize, model.label("final"));
|
|
204
|
-
if (final === undefined)
|
|
205
|
-
middle += text("ky-r-vs", cx, fy + labelSize * 0.2, fs * 0.9, w.vs);
|
|
206
|
-
else {
|
|
207
|
-
middle += scoreMarkup(final, cx, fy, fs, true);
|
|
208
|
-
const sub = decidedWords(final, w, true);
|
|
209
|
-
if (sub !== "")
|
|
210
|
-
middle += text("ky-r-sub", cx, fy + fs * 0.85, fs * 0.4, sub);
|
|
211
|
-
}
|
|
212
|
-
// ---- The road: a dotted line down the middle, behind the round names, from the final down to the group stage. ----
|
|
213
|
-
const groupTop = phone ? regionBottom + gapS : H - pad - footH - groupBlock;
|
|
214
|
-
const roadTop = wide ? fy + rowH * 0.42 : fy + (final === undefined ? labelSize * 1.6 : fs * (sub0(final) ? 1.5 : 1.0));
|
|
215
|
-
const roadEnd = nGroup > 0 ? groupTop + (phone ? 0 : cellH / 2) : R > 0 ? rowTop(R - 1) + rowH / 2 : roadTop;
|
|
216
|
-
const spine = R > 0 && roadEnd > roadTop
|
|
217
|
-
? el("path", { class: "ky-r-spine", d: `M${num(cx)} ${num(roadTop)}V${num(roadEnd)}`, "stroke-width": num(clamp(W * 0.004, 1.5, 4)), "stroke-dasharray": `0.1 ${num(clamp(W * 0.014, 5, 16))}`, fill: "none" })
|
|
218
|
-
: "";
|
|
219
|
-
// ---- The group stage: small round marks with their codes and scores. ----
|
|
220
|
-
let groupMarkup = "";
|
|
221
|
-
if (nGroup > 0) {
|
|
222
|
-
const gy = groupTop + (phone ? 26 : 0);
|
|
223
|
-
if (phone) {
|
|
224
|
-
const width = textWidth(model.groupLabel) * 11.5 + 20;
|
|
225
|
-
groupMarkup += el("rect", { class: "ky-r-panel", x: num(cx - width / 2), y: num(groupTop + 1), width: num(width), height: 18, rx: 9 }) + text("ky-r-lab", cx, groupTop + 10, 11.5, model.groupLabel);
|
|
226
|
-
}
|
|
227
|
-
else
|
|
228
|
-
groupMarkup += panel(model.groupLabel, gy + cellH / 2, Math.min(cellH * 0.42, cl * 0.2), cl * 0.94);
|
|
229
|
-
for (const side of [0, 1]) {
|
|
230
|
-
const lane = lanes[side];
|
|
231
|
-
model.group[side].forEach((v, k) => {
|
|
232
|
-
const row = Math.floor(k / cols);
|
|
233
|
-
const inRow = Math.min(cols, model.group[side].length - row * cols);
|
|
234
|
-
const x = (lane.x0 + lane.x1) / 2 - (inRow * cw) / 2 + (k % cols) * cw + cw / 2;
|
|
235
|
-
const y = gy + row * (cellH + dm * 0.2);
|
|
236
|
-
const dim = dimOf(v.result, false);
|
|
237
|
-
groupMarkup += markOf(v.opponent, x, y + dm / 2, dm / 2, `ky-r-plain${dim}`);
|
|
238
|
-
if (codes)
|
|
239
|
-
groupMarkup += text(`ky-r-code${dim}`, x, y + dm * 1.27, dm * 0.3, codeOf(v.opponent));
|
|
240
|
-
groupMarkup += text(`ky-r-n${emphasis && v.result === "loss" ? " ky-r-low" : ""}`, x, y + dm * 1.78, dm * 0.44, groupScore(v));
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// ---- The head and the foot. ----
|
|
245
|
-
let head = titleFit.lines.map((line, k) => text("ky-r-t", cx, pad + titleFit.size * 0.55 + k * titleFit.size * 1.1, titleFit.size, line)).join("");
|
|
246
|
-
if (model.subtitle !== undefined)
|
|
247
|
-
head += text("ky-r-s", cx, pad + titleFit.height + subSize * 0.7, subSize, model.subtitle);
|
|
248
|
-
const foot = endSize === 0 ? "" : text("ky-r-e", cx, H - pad - endSize * 0.2, endSize, note);
|
|
249
|
-
const sheet = o.style === "none" ? "" : `${KYOGIJO_MARK_STYLE}\n${KYOGIJO_ROAD_STYLE}`;
|
|
250
|
-
const extra = safeClass(o.className);
|
|
251
|
-
return el("svg", {
|
|
252
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
253
|
-
class: `kyogijo kyogijo-road${extra === "" ? "" : ` ${extra}`}`,
|
|
254
|
-
viewBox: `0 0 ${num(W)} ${num(H)}`,
|
|
255
|
-
role: "img",
|
|
256
|
-
"aria-labelledby": `${gid}-t ${gid}-d`,
|
|
257
|
-
"data-size": size,
|
|
258
|
-
"data-theme": theme,
|
|
259
|
-
"data-version": VERSION,
|
|
260
|
-
}, `<title id="${gid}-t">${esc(nameOf(model))}</title><desc id="${gid}-d">${esc(summaryOf(model))}</desc>` +
|
|
261
|
-
(sheet === "" ? "" : `<style>${sheet}</style>`) +
|
|
262
|
-
el("rect", { class: "ky-r-bg", x: 0, y: 0, width: num(W), height: num(H) }) +
|
|
263
|
-
head +
|
|
264
|
-
spine +
|
|
265
|
-
band +
|
|
266
|
-
middle +
|
|
267
|
-
body +
|
|
268
|
-
groupMarkup +
|
|
269
|
-
foot);
|
|
270
|
-
}
|
package/dist/road.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// The road-to-the-final chart: the two finalists of a tournament with the matches each played on the way, as round flags (or crests) of both teams with the score between
|
|
2
|
-
// them, the rounds named down the middle, and the group stage as a compact row at the foot. The idea is that of the "road to the final" graphics that sports sites post
|
|
3
|
-
// for a World Cup or a Stanley Cup; the arrangement, the type and the code are this package's own, and no one's artwork is used: the marks are the caller's flags and
|
|
4
|
-
// logos, or the team's initials on its colour.
|
|
5
|
-
import { withEmblems } from "./emblems-source.js";
|
|
6
|
-
import { hash } from "./format.js";
|
|
7
|
-
import { safeClass } from "./common.js";
|
|
8
|
-
import { drawRoad } from "./road.draw.js";
|
|
9
|
-
import { nameOf, readModel, summaryOf, tableOf, totalsOf } from "./road.model.js";
|
|
10
|
-
import { VERSION } from "./version.js";
|
|
11
|
-
export { ROAD_SIZES } from "./road.draw.js";
|
|
12
|
-
const SIZES = ["card", "square", "wide", "phone"];
|
|
13
|
-
/**
|
|
14
|
-
* The road to the final: the two finalists as large round marks with their names at the top, each one's matches on its side (the round mark of both teams with the
|
|
15
|
-
* score between them, and FT, AET or the shoot-out under it), the round names down the middle, and the group stage as a compact row of small marks, codes and scores.
|
|
16
|
-
* A finalist is a team (`name`, `abbr`, `logo` for the flag or the crest, `color`) with its `path`, the matches before the final, the earliest first, and optionally its
|
|
17
|
-
* `group`. A match is `{ round, opponent, score: [for, against], decidedBy, pens }`; a round is a key (`quarter-final`) with words in English and Japanese, or any
|
|
18
|
-
* key you name in `labels`, so a series (`First round`, `4-2`) draws as well as a knockout. A team with no `logo` is drawn as its initials on its colour. The side that won
|
|
19
|
-
* each match is brighter; the sizes are `card` (1080 by 1350), `square`, `wide` (1200 by 675) and `phone` (390 wide). `table()` lists every match as text.
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* import { roadToFinal, type RoadFinalist } from "@johnmorrisdotca/kyogijo-stadiums/charts";
|
|
23
|
-
* const owls: RoadFinalist = { name: "Northern Owls", abbr: "NOW", color: "#3a2d6b", path: [
|
|
24
|
-
* { round: "quarter-final", opponent: { name: "Coastal Pines", abbr: "CPN" }, score: [2, 0] },
|
|
25
|
-
* { round: "semi-final", opponent: { name: "Valley Stars", abbr: "VAL" }, score: [1, 1], decidedBy: "pens", pens: [4, 3] },
|
|
26
|
-
* ] };
|
|
27
|
-
* const gulls: RoadFinalist = { name: "Harbour City Gulls", abbr: "HCG", color: "#1d4f91", path: [
|
|
28
|
-
* { round: "quarter-final", opponent: { name: "Summit Peaks", abbr: "SMT" }, score: [3, 1] },
|
|
29
|
-
* { round: "semi-final", opponent: { name: "Metro Bolts", abbr: "MTB" }, score: [2, 1], decidedBy: "aet" },
|
|
30
|
-
* ] };
|
|
31
|
-
* const chart = roadToFinal([owls, gulls], { title: "Northern Cup 2030", size: "square" });
|
|
32
|
-
* console.log(chart.totals.rounds.join(" "), chart.draw().startsWith("<svg"), chart.table().includes("Semi-final"));
|
|
33
|
-
* // quarter-final semi-final true true
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export function roadToFinal(finalists, options = {}) {
|
|
37
|
-
if (typeof options !== "object" || options === null)
|
|
38
|
-
throw new TypeError("options is an object");
|
|
39
|
-
const check = (o) => {
|
|
40
|
-
if (o.size !== undefined && !SIZES.includes(o.size))
|
|
41
|
-
throw new RangeError(`Unknown size "${String(o.size)}": use one of ${SIZES.join(", ")}`);
|
|
42
|
-
};
|
|
43
|
-
check(options);
|
|
44
|
-
const model = readModel(finalists, options);
|
|
45
|
-
const build = (extra = {}) => {
|
|
46
|
-
const merged = { ...options, ...extra };
|
|
47
|
-
check(merged);
|
|
48
|
-
const m = readModel(finalists, merged);
|
|
49
|
-
const gid = safeClass(merged.id).replace(/ /g, "-") || `kr-${hash(JSON.stringify([finalists, merged, VERSION]))}`;
|
|
50
|
-
return withEmblems(merged.emblems, () => drawRoad(m, merged, gid));
|
|
51
|
-
};
|
|
52
|
-
return {
|
|
53
|
-
options: { ...options },
|
|
54
|
-
title: nameOf(model),
|
|
55
|
-
summary: summaryOf(model),
|
|
56
|
-
totals: totalsOf(model),
|
|
57
|
-
draw: build,
|
|
58
|
-
table: (tableOptions = {}) => tableOf(tableOptions.language === undefined ? model : readModel(finalists, { ...options, language: tableOptions.language }), tableOptions),
|
|
59
|
-
};
|
|
60
|
-
}
|
package/dist/road.model.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { type KyogijoWords } from "./strings.ts";
|
|
2
|
-
import type { RoadDecided, RoadFinalist, RoadOptions, RoadTotals } from "./road.types.ts";
|
|
3
|
-
import type { Language, Team } from "./types.ts";
|
|
4
|
-
/** Who won a match, from the finalist's side. `unknown` when the score is not a pair of numbers. */
|
|
5
|
-
export type Result = "win" | "loss" | "draw" | "unknown";
|
|
6
|
-
/** A match as it is drawn: the two scores written, who won, and how it was decided. */
|
|
7
|
-
export interface MatchView {
|
|
8
|
-
/** The round's key; empty for a match of the group stage. */
|
|
9
|
-
round: string;
|
|
10
|
-
opponent: Team;
|
|
11
|
-
/** The finalist's score and the opponent's, written. For a score that is one text that could not be split, `whole` is that text and the two are empty. */
|
|
12
|
-
mine: string;
|
|
13
|
-
theirs: string;
|
|
14
|
-
whole?: string;
|
|
15
|
-
result: Result;
|
|
16
|
-
decided: RoadDecided;
|
|
17
|
-
pens?: [string, string];
|
|
18
|
-
/** The words that replace the usual ones under the score, or undefined. */
|
|
19
|
-
note?: string;
|
|
20
|
-
/** Whether the usual words (FT, AET, the shoot-out) are written: always for a knockout match, only when asked for a group match. */
|
|
21
|
-
says: boolean;
|
|
22
|
-
homeFirst?: boolean;
|
|
23
|
-
}
|
|
24
|
-
/** The chart's data, ready to draw. */
|
|
25
|
-
export interface Model {
|
|
26
|
-
language: Language;
|
|
27
|
-
words: KyogijoWords;
|
|
28
|
-
finalists: [Team, Team];
|
|
29
|
-
/** The matches of the group stage, for each finalist. */
|
|
30
|
-
group: [MatchView[], MatchView[]];
|
|
31
|
-
/** The knockout matches for each finalist, by round key. */
|
|
32
|
-
path: [Map<string, MatchView>, Map<string, MatchView>];
|
|
33
|
-
/** The rounds, the earliest first. */
|
|
34
|
-
rounds: string[];
|
|
35
|
-
/** The final, if played: the scores of the first finalist and the second. */
|
|
36
|
-
final?: MatchView;
|
|
37
|
-
label(key: string): string;
|
|
38
|
-
groupLabel: string;
|
|
39
|
-
title: string;
|
|
40
|
-
subtitle: string | undefined;
|
|
41
|
-
}
|
|
42
|
-
/** A number from a number or from text that is one, else undefined. */
|
|
43
|
-
export declare function numeric(value: unknown): number | undefined;
|
|
44
|
-
/** The rounds of two paths, the earliest first: a round both have is once, and a round only one has stays after the round before it. */
|
|
45
|
-
export declare function mergeRounds(paths: readonly (readonly string[])[]): string[];
|
|
46
|
-
/** Read and check what `roadToFinal` was given. */
|
|
47
|
-
export declare function readModel(finalists: readonly RoadFinalist[], options: RoadOptions): Model;
|
|
48
|
-
/** The words under a score: FT, AET or the shoot-out (the first side's numbers first), a note in their place, or nothing. */
|
|
49
|
-
export declare function decidedWords(view: MatchView, w: KyogijoWords["road"], mineFirst: boolean): string;
|
|
50
|
-
/** The score of a match as one text, the first side's first. */
|
|
51
|
-
export declare function scoreText(view: MatchView, mineFirst?: boolean): string;
|
|
52
|
-
/** The abbreviation written under a mark: the team's own, else made from its name. */
|
|
53
|
-
export declare function codeOf(team: Team): string;
|
|
54
|
-
/** The numbers behind the chart. */
|
|
55
|
-
export declare function totalsOf(model: Model): RoadTotals;
|
|
56
|
-
/** The chart's accessible name. */
|
|
57
|
-
export declare function nameOf(model: Model): string;
|
|
58
|
-
/** What the chart shows, in a sentence or two. */
|
|
59
|
-
export declare function summaryOf(model: Model): string;
|
|
60
|
-
/** One HTML table with a row for every match: the group stage, the knockout rounds in order, and the final. */
|
|
61
|
-
export declare function tableOf(model: Model, options?: {
|
|
62
|
-
className?: string;
|
|
63
|
-
}): string;
|
package/dist/road.model.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// The road chart's data, read and checked: the scores split into the two sides, who won each match, the words under each score, the rounds in the order played, and
|
|
2
|
-
// the sentences and the table that say the same in words. Nothing here draws.
|
|
3
|
-
import { esc } from "./format.js";
|
|
4
|
-
import { safeClass } from "./common.js";
|
|
5
|
-
import { KYOGIJO_STRINGS, fill } from "./strings.js";
|
|
6
|
-
import { abbreviate } from "./text.js";
|
|
7
|
-
const own = (object, key) => object !== undefined && Object.prototype.hasOwnProperty.call(object, key);
|
|
8
|
-
/** A number from a number or from text that is one, else undefined. */
|
|
9
|
-
export function numeric(value) {
|
|
10
|
-
if (typeof value === "number")
|
|
11
|
-
return Number.isFinite(value) ? value : undefined;
|
|
12
|
-
if (typeof value === "string" && /^\s*-?\d+(?:\.\d+)?\s*$/.test(value))
|
|
13
|
-
return Number(value);
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
const written = (value) => (value === undefined || value === null ? "" : String(value).trim());
|
|
17
|
-
/** The two sides of a score. */
|
|
18
|
-
function split(score) {
|
|
19
|
-
if (Array.isArray(score) && score.length === 2)
|
|
20
|
-
return { mine: written(score[0]), theirs: written(score[1]) };
|
|
21
|
-
const text = written(score);
|
|
22
|
-
const parts = /^(\S+?)\s*[-–—:]\s*(\S+)$/.exec(text);
|
|
23
|
-
if (parts !== null)
|
|
24
|
-
return { mine: parts[1], theirs: parts[2] };
|
|
25
|
-
return { mine: "", theirs: "", whole: text };
|
|
26
|
-
}
|
|
27
|
-
function resultOf(mine, theirs, pens) {
|
|
28
|
-
const a = numeric(mine);
|
|
29
|
-
const b = numeric(theirs);
|
|
30
|
-
if (a === undefined || b === undefined)
|
|
31
|
-
return "unknown";
|
|
32
|
-
if (a !== b)
|
|
33
|
-
return a > b ? "win" : "loss";
|
|
34
|
-
const p = pens === undefined ? undefined : [numeric(pens[0]), numeric(pens[1])];
|
|
35
|
-
if (p !== undefined && p[0] !== undefined && p[1] !== undefined && p[0] !== p[1])
|
|
36
|
-
return p[0] > p[1] ? "win" : "loss";
|
|
37
|
-
return "draw";
|
|
38
|
-
}
|
|
39
|
-
function pensOf(value) {
|
|
40
|
-
return Array.isArray(value) && value.length === 2 && written(value[0]) !== "" && written(value[1]) !== "" ? [written(value[0]), written(value[1])] : undefined;
|
|
41
|
-
}
|
|
42
|
-
function viewOf(m, opponent, round, knockout) {
|
|
43
|
-
const { mine, theirs, whole } = split(m.score);
|
|
44
|
-
const pens = pensOf(m.pens);
|
|
45
|
-
const decided = m.decidedBy === "aet" || m.decidedBy === "pens" || m.decidedBy === "ft" ? m.decidedBy : pens !== undefined ? "pens" : "ft";
|
|
46
|
-
const note = typeof m.note === "string" ? m.note.trim() : undefined;
|
|
47
|
-
return {
|
|
48
|
-
round,
|
|
49
|
-
opponent,
|
|
50
|
-
mine,
|
|
51
|
-
theirs,
|
|
52
|
-
...(whole === undefined ? {} : { whole }),
|
|
53
|
-
result: resultOf(mine, theirs, pens),
|
|
54
|
-
decided,
|
|
55
|
-
...(pens === undefined ? {} : { pens }),
|
|
56
|
-
...(note === undefined ? {} : { note }),
|
|
57
|
-
says: knockout || m.decidedBy !== undefined || pens !== undefined,
|
|
58
|
-
...("homeFirst" in m && typeof m.homeFirst === "boolean" ? { homeFirst: m.homeFirst } : {}),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/** The rounds of two paths, the earliest first: a round both have is once, and a round only one has stays after the round before it. */
|
|
62
|
-
export function mergeRounds(paths) {
|
|
63
|
-
const out = [];
|
|
64
|
-
for (const path of paths) {
|
|
65
|
-
let at = 0;
|
|
66
|
-
for (const key of path) {
|
|
67
|
-
const found = out.indexOf(key);
|
|
68
|
-
if (found >= 0)
|
|
69
|
-
at = found + 1;
|
|
70
|
-
else {
|
|
71
|
-
out.splice(at, 0, key);
|
|
72
|
-
at += 1;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return out;
|
|
77
|
-
}
|
|
78
|
-
const isTeam = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.name === "string";
|
|
79
|
-
/** Read and check what `roadToFinal` was given. */
|
|
80
|
-
export function readModel(finalists, options) {
|
|
81
|
-
if (!Array.isArray(finalists) || finalists.length !== 2)
|
|
82
|
-
throw new TypeError("roadToFinal takes the two finalists as a list of two teams, each with a path");
|
|
83
|
-
const language = options.language === "ja" ? "ja" : "en";
|
|
84
|
-
const words = KYOGIJO_STRINGS[language];
|
|
85
|
-
const w = words.road;
|
|
86
|
-
const teams = finalists;
|
|
87
|
-
for (const [i, f] of teams.entries()) {
|
|
88
|
-
if (!isTeam(f))
|
|
89
|
-
throw new TypeError(`Finalist ${i + 1} is a team with a name`);
|
|
90
|
-
if (!Array.isArray(f.path))
|
|
91
|
-
throw new TypeError(`Finalist ${i + 1} (${f.name}) needs a path: the list of its matches before the final`);
|
|
92
|
-
if (f.group !== undefined && !Array.isArray(f.group))
|
|
93
|
-
throw new TypeError(`The group of ${f.name} is a list of matches`);
|
|
94
|
-
for (const m of [...f.path, ...(f.group ?? [])]) {
|
|
95
|
-
if (typeof m !== "object" || m === null || !isTeam(m.opponent) || m.score === undefined || m.score === null)
|
|
96
|
-
throw new TypeError(`Every match of ${f.name} has an opponent (a team with a name) and a score`);
|
|
97
|
-
}
|
|
98
|
-
for (const m of f.path)
|
|
99
|
-
if (typeof m.round !== "string" || m.round.trim() === "")
|
|
100
|
-
throw new TypeError(`Every match of the path of ${f.name} has a round`);
|
|
101
|
-
}
|
|
102
|
-
const path = teams.map((f) => {
|
|
103
|
-
const map = new Map();
|
|
104
|
-
for (const m of f.path)
|
|
105
|
-
map.set(m.round.trim(), viewOf(m, m.opponent, m.round.trim(), true));
|
|
106
|
-
return map;
|
|
107
|
-
});
|
|
108
|
-
const group = teams.map((f) => (f.group ?? []).map((m) => viewOf(m, m.opponent, "", false)));
|
|
109
|
-
const rounds = mergeRounds(teams.map((f) => f.path.map((m) => m.round.trim())));
|
|
110
|
-
const labels = options.labels;
|
|
111
|
-
const model = {
|
|
112
|
-
language,
|
|
113
|
-
words,
|
|
114
|
-
finalists: teams,
|
|
115
|
-
group,
|
|
116
|
-
path,
|
|
117
|
-
rounds,
|
|
118
|
-
label: (key) => (own(labels, key) && typeof labels?.[key] === "string" ? labels[key] : own(w.rounds, key) ? w.rounds[key] : key),
|
|
119
|
-
groupLabel: typeof options.groupLabel === "string" && options.groupLabel.trim() !== "" ? options.groupLabel.trim() : w.group,
|
|
120
|
-
title: typeof options.title === "string" && options.title.trim() !== "" ? options.title.trim() : w.title,
|
|
121
|
-
subtitle: typeof options.subtitle === "string" && options.subtitle.trim() !== "" ? options.subtitle.trim() : typeof options.title === "string" && options.title.trim() !== "" ? w.title : undefined,
|
|
122
|
-
};
|
|
123
|
-
if (options.final !== undefined) {
|
|
124
|
-
if (typeof options.final !== "object" || options.final === null || options.final.score === undefined || options.final.score === null)
|
|
125
|
-
throw new TypeError("The final is { score }: the first finalist's score first");
|
|
126
|
-
model.final = viewOf(options.final, teams[1], "final", true);
|
|
127
|
-
}
|
|
128
|
-
return model;
|
|
129
|
-
}
|
|
130
|
-
/** The words under a score: FT, AET or the shoot-out (the first side's numbers first), a note in their place, or nothing. */
|
|
131
|
-
export function decidedWords(view, w, mineFirst) {
|
|
132
|
-
if (view.note !== undefined)
|
|
133
|
-
return view.note;
|
|
134
|
-
if (!view.says)
|
|
135
|
-
return "";
|
|
136
|
-
if (view.decided === "aet")
|
|
137
|
-
return w.aet;
|
|
138
|
-
if (view.decided === "pens" && view.pens !== undefined)
|
|
139
|
-
return fill(w.pens, mineFirst ? { a: view.pens[0], b: view.pens[1] } : { a: view.pens[1], b: view.pens[0] });
|
|
140
|
-
return view.decided === "pens" ? w.aet : w.ft;
|
|
141
|
-
}
|
|
142
|
-
/** The score of a match as one text, the first side's first. */
|
|
143
|
-
export function scoreText(view, mineFirst = true) {
|
|
144
|
-
if (view.whole !== undefined)
|
|
145
|
-
return view.whole;
|
|
146
|
-
return mineFirst ? `${view.mine}-${view.theirs}` : `${view.theirs}-${view.mine}`;
|
|
147
|
-
}
|
|
148
|
-
/** The abbreviation written under a mark: the team's own, else made from its name. */
|
|
149
|
-
export function codeOf(team) {
|
|
150
|
-
return (typeof team.abbr === "string" && team.abbr.trim() !== "" ? team.abbr.trim() : abbreviate(String(team.name ?? ""))).slice(0, 5).toLocaleUpperCase("en");
|
|
151
|
-
}
|
|
152
|
-
function record(f, group, path) {
|
|
153
|
-
const all = [...group, ...path.values()];
|
|
154
|
-
let scored = 0;
|
|
155
|
-
let conceded = 0;
|
|
156
|
-
for (const v of all) {
|
|
157
|
-
const a = numeric(v.mine);
|
|
158
|
-
const b = numeric(v.theirs);
|
|
159
|
-
if (a !== undefined && b !== undefined) {
|
|
160
|
-
scored += a;
|
|
161
|
-
conceded += b;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return { name: f.name, matches: all.length, won: all.filter((v) => v.result === "win").length, drawn: all.filter((v) => v.result === "draw").length, lost: all.filter((v) => v.result === "loss").length, scored, conceded };
|
|
165
|
-
}
|
|
166
|
-
/** The numbers behind the chart. */
|
|
167
|
-
export function totalsOf(model) {
|
|
168
|
-
const [a, b] = model.finalists;
|
|
169
|
-
const final = model.final;
|
|
170
|
-
const champion = final === undefined || final.result === "unknown" || final.result === "draw" ? null : final.result === "win" ? a.name : b.name;
|
|
171
|
-
return { finalists: [record(a, model.group[0], model.path[0]), record(b, model.group[1], model.path[1])], rounds: [...model.rounds], played: final !== undefined, champion };
|
|
172
|
-
}
|
|
173
|
-
/** The chart's accessible name. */
|
|
174
|
-
export function nameOf(model) {
|
|
175
|
-
const heading = [model.title, model.subtitle].filter((part) => part !== undefined).join(model.words.said.sep);
|
|
176
|
-
return fill(model.words.road.name, { a: model.finalists[0].name, b: model.finalists[1].name, title: heading });
|
|
177
|
-
}
|
|
178
|
-
/** What the chart shows, in a sentence or two. */
|
|
179
|
-
export function summaryOf(model) {
|
|
180
|
-
const w = model.words.road;
|
|
181
|
-
const [a, b] = model.finalists;
|
|
182
|
-
const t = totalsOf(model);
|
|
183
|
-
const parts = [fill(w.summary, { a: a.name, b: b.name, an: t.finalists[0].matches, bn: t.finalists[1].matches })];
|
|
184
|
-
const final = model.final;
|
|
185
|
-
if (final === undefined)
|
|
186
|
-
parts.push(w.toPlay);
|
|
187
|
-
else {
|
|
188
|
-
parts.push(fill(w.final, { a: a.name, b: b.name, score: scoreText(final), how: decidedWords(final, w, true) || w.ft }));
|
|
189
|
-
if (t.champion !== null)
|
|
190
|
-
parts.push(fill(w.champion, { name: t.champion }));
|
|
191
|
-
}
|
|
192
|
-
return parts.join(model.words.said.gap);
|
|
193
|
-
}
|
|
194
|
-
/** One HTML table with a row for every match: the group stage, the knockout rounds in order, and the final. */
|
|
195
|
-
export function tableOf(model, options = {}) {
|
|
196
|
-
const w = model.words.road;
|
|
197
|
-
const cls = safeClass(options.className) || "ky-table";
|
|
198
|
-
const [a, b] = model.finalists;
|
|
199
|
-
const result = (r) => (r === "win" ? w.won : r === "loss" ? w.lost : r === "draw" ? w.drew : "");
|
|
200
|
-
const row = (team, round, v) => `<tr><th scope="row">${esc(team.name)}</th><td>${esc(round)}</td><td>${esc(v.opponent.name)}</td><td>${esc(scoreText(v))}</td><td>${esc(decidedWords(v, w, true))}</td><td>${esc(result(v.result))}</td></tr>`;
|
|
201
|
-
const rows = [];
|
|
202
|
-
for (const [i, team] of [a, b].entries()) {
|
|
203
|
-
for (const v of model.group[i])
|
|
204
|
-
rows.push(row(team, model.groupLabel, v));
|
|
205
|
-
for (const key of model.rounds) {
|
|
206
|
-
const v = model.path[i].get(key);
|
|
207
|
-
if (v !== undefined)
|
|
208
|
-
rows.push(row(team, model.label(key), v));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (model.final !== undefined)
|
|
212
|
-
rows.push(row(a, model.label("final"), { ...model.final, opponent: b }));
|
|
213
|
-
const head = [w.tableTeam, w.tableRound, w.tableOpponent, w.tableScore, w.tableDecided, w.tableResult].map((name) => `<th scope="col">${esc(name)}</th>`).join("");
|
|
214
|
-
return `<table class="${cls}" data-kind="road"><caption>${esc(fill(w.caption, { a: a.name, b: b.name }))}</caption><thead><tr>${head}</tr></thead><tbody>${rows.join("")}</tbody></table>`;
|
|
215
|
-
}
|
package/dist/road.style.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The rules of the road-to-the-final chart (`roadToFinal`): the ground, the ink and the quiet ink in light and dark, the panel behind a round's name, the road down the
|
|
3
|
-
* middle, and the classes of the title, the names, the scores and the small words under them. The colours are custom properties on the chart (`--ky-road-bg`,
|
|
4
|
-
* `--ky-road-ink`, `--ky-road-muted`, `--ky-road-panel`, `--ky-road-spine`, `--ky-road-left`, `--ky-road-right`), so a page can set them. A chart writes these rules in,
|
|
5
|
-
* with the round mark's (`KYOGIJO_MARK_STYLE`); `KYOGIJO_STYLE` has them for a page that draws with `style: "none"`.
|
|
6
|
-
*/
|
|
7
|
-
export declare const KYOGIJO_ROAD_STYLE: string;
|
package/dist/road.style.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The rules of the road-to-the-final chart (`roadToFinal`): the ground, the ink and the quiet ink in light and dark, the panel behind a round's name, the road down the
|
|
3
|
-
* middle, and the classes of the title, the names, the scores and the small words under them. The colours are custom properties on the chart (`--ky-road-bg`,
|
|
4
|
-
* `--ky-road-ink`, `--ky-road-muted`, `--ky-road-panel`, `--ky-road-spine`, `--ky-road-left`, `--ky-road-right`), so a page can set them. A chart writes these rules in,
|
|
5
|
-
* with the round mark's (`KYOGIJO_MARK_STYLE`); `KYOGIJO_STYLE` has them for a page that draws with `style: "none"`.
|
|
6
|
-
*/
|
|
7
|
-
export const KYOGIJO_ROAD_STYLE = `
|
|
8
|
-
:where(.kyogijo-road){--ky-road-bg:#f6f4ee;--ky-road-ink:#1d2421;--ky-road-muted:#5a655f;--ky-road-panel:#e7e3d6;--ky-road-spine:#c4bfae;--ky-road-left:#2f6db0;--ky-road-right:#b5452c;--ky-road-plain:#8b948f;--ky-mark-ring:#fff;--ky-mark-edge:rgba(0,0,0,.3);--ky-mark-plain:#fff;--ky-font:system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif;display:block;width:100%;height:auto;font-family:var(--ky-font)}
|
|
9
|
-
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .kyogijo-road:not([data-theme=light])){--ky-road-bg:#131716;--ky-road-ink:#ece8dc;--ky-road-muted:#a9b2ac;--ky-road-panel:#252b28;--ky-road-spine:#3d4541;--ky-road-left:#6aa9f0;--ky-road-right:#f59a5b;--ky-mark-ring:#e9ece6;--ky-mark-edge:rgba(0,0,0,.6);--ky-mark-plain:#2a302d}}
|
|
10
|
-
:where(html[data-theme=dark] .kyogijo-road,.kyogijo-road[data-theme=dark]){--ky-road-bg:#131716;--ky-road-ink:#ece8dc;--ky-road-muted:#a9b2ac;--ky-road-panel:#252b28;--ky-road-spine:#3d4541;--ky-road-left:#6aa9f0;--ky-road-right:#f59a5b;--ky-mark-ring:#e9ece6;--ky-mark-edge:rgba(0,0,0,.6);--ky-mark-plain:#2a302d}
|
|
11
|
-
.kyogijo-road .ky-r-bg{fill:var(--ky-road-bg)}
|
|
12
|
-
.kyogijo-road text{font-family:var(--ky-font);font-variant-numeric:tabular-nums}
|
|
13
|
-
.kyogijo-road .ky-r-t{fill:var(--ky-road-ink);font-weight:800}
|
|
14
|
-
.kyogijo-road .ky-r-s,.kyogijo-road .ky-r-e{fill:var(--ky-road-muted);font-weight:600}
|
|
15
|
-
.kyogijo-road .ky-r-name{fill:var(--ky-road-ink);font-weight:800}
|
|
16
|
-
.kyogijo-road .ky-r-panel{fill:var(--ky-road-panel)}
|
|
17
|
-
.kyogijo-road .ky-r-lab{fill:var(--ky-road-ink);font-weight:700}
|
|
18
|
-
.kyogijo-road .ky-r-spine{stroke:var(--ky-road-spine);fill:none;stroke-linecap:round}
|
|
19
|
-
.kyogijo-road .ky-r-n{fill:var(--ky-road-ink);font-weight:800}
|
|
20
|
-
.kyogijo-road .ky-r-n.ky-r-low,.kyogijo-road .ky-r-dash{fill:var(--ky-road-muted);font-weight:700}
|
|
21
|
-
.kyogijo-road .ky-r-sub,.kyogijo-road .ky-r-code{fill:var(--ky-road-muted);font-weight:700}
|
|
22
|
-
.kyogijo-road .ky-r-vs{fill:var(--ky-road-muted);font-weight:700}
|
|
23
|
-
.kyogijo-road .ky-r-dim{opacity:.5}
|
|
24
|
-
.kyogijo-road .ky-r-left{--c:var(--ky-road-left)}
|
|
25
|
-
.kyogijo-road .ky-r-right{--c:var(--ky-road-right)}
|
|
26
|
-
.kyogijo-road .ky-r-plain{--c:var(--ky-road-plain)}`.trim();
|