@thebattlearena/corner 0.9.1-beta.70 → 0.10.1-beta.78
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/README.md +46 -26
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/ring/agents/cornerman.md +32 -6
- package/ring/agents/rudo.md +2 -1
- package/ring/agents/wrestler.md +19 -4
- package/ring/hooks/hooks.json +42 -0
- package/ring/scripts/bout-stats-render.mjs +366 -0
- package/ring/scripts/bout-stats.mjs +360 -0
- package/ring/scripts/brief-build.mjs +130 -0
- package/ring/scripts/brief-guard.mjs +154 -0
- package/ring/scripts/brief.mjs +272 -0
- package/ring/scripts/compact-hook.mjs +110 -0
- package/ring/scripts/config.mjs +25 -1
- package/ring/scripts/contract-endpoints.mjs +163 -0
- package/ring/scripts/contract-preview.mjs +34 -14
- package/ring/scripts/fight.mjs +27 -0
- package/ring/scripts/findings.mjs +82 -3
- package/ring/scripts/format-gate.mjs +723 -0
- package/ring/scripts/format-worker.mjs +120 -0
- package/ring/scripts/front-routes.mjs +345 -0
- package/ring/scripts/judges-card.mjs +324 -3
- package/ring/scripts/lookout.mjs +22 -4
- package/ring/scripts/mask-guard.mjs +34 -1
- package/ring/scripts/page-embed.mjs +112 -0
- package/ring/scripts/play-by-play.mjs +155 -7
- package/ring/scripts/preparation-collect.mjs +177 -0
- package/ring/scripts/preparation.mjs +547 -0
- package/ring/scripts/round-bell.mjs +9 -2
- package/ring/scripts/rule-ids.mjs +124 -0
- package/ring/scripts/tech-plan-log.mjs +81 -0
- package/ring/scripts/tech-plan.mjs +191 -0
- package/ring/scripts/verdicts-log.mjs +75 -0
- package/ring/scripts/verdicts.mjs +205 -0
- package/ring/skills/play-by-play/SKILL.md +235 -33
- package/ring/skills/play-by-play/templates/blocks.css +196 -0
- package/ring/skills/play-by-play/templates/chronicle.html +358 -55
- package/ring/skills/play-by-play/templates/endpoints-view.js +1153 -0
- package/ring/skills/play-by-play/templates/interview-form.js +516 -0
- package/ring/skills/play-by-play/templates/now-panel.js +719 -0
- package/ring/skills/referee/SKILL.md +181 -56
- package/ring/skills/referee/references/api-diagram.md +56 -0
- package/ring/skills/referee/references/precedents.md +11 -0
- package/ring/skills/referee/references/rulebook.md +97 -6
- package/ring/skills/referee/templates/api-diagram.html +115 -0
- package/ring/skills/referee/templates/cornerman-batch.md +17 -11
- package/ring/skills/referee/templates/cornerman-concept.md +28 -30
- package/ring/skills/referee/templates/cornerman-range.md +16 -11
- package/ring/skills/referee/templates/escalation.md +7 -1
- package/ring/skills/referee/templates/round-bell-body.md +4 -0
- package/ring/skills/referee/templates/wrestler-brief.md +28 -41
- package/ring/skills/referee/templates/wrestler-cutoff.md +4 -0
- package/ring/skills/referee/templates/wrestler-resume.md +4 -0
- package/ring/skills/sparring/SKILL.md +13 -4
- package/ring/skills/sparring/templates/contrato.md +29 -0
- package/ring/skills/sparring/templates/rudo-brief.md +7 -0
- package/ring/skills/title-defense/SKILL.md +2 -2
- package/ring/skills/weigh-in/SKILL.md +6 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// Renders the numbers of bout-stats.mjs as the "El cierre en números" block of the chronicle, in
|
|
2
|
+
// the Battle Arena voice: a weigh-in (code against tests), round by round between the ropes, the
|
|
3
|
+
// fight clock (24 h), a CompuBox of punch types, the referee's scorecards and the highlight reel.
|
|
4
|
+
// Inline SVG and HTML only, colored through the template's tokens (classes cn-*), so light and dark
|
|
5
|
+
// both work and nothing loads from outside. PURE and zero-dependency.
|
|
6
|
+
|
|
7
|
+
const LABELS = {
|
|
8
|
+
es: {
|
|
9
|
+
title: 'El cierre en números',
|
|
10
|
+
tiles: {
|
|
11
|
+
duration: 'de pelea, del primer registro al último',
|
|
12
|
+
commits: 'commits del luchador',
|
|
13
|
+
rounds: 'rounds con su campana',
|
|
14
|
+
lines: 'líneas agregadas',
|
|
15
|
+
},
|
|
16
|
+
weighIn: 'La báscula',
|
|
17
|
+
weighInNote: (ratio) =>
|
|
18
|
+
`${ratio} líneas de prueba por cada línea de código.`,
|
|
19
|
+
red: 'Esquina roja',
|
|
20
|
+
blue: 'Esquina azul',
|
|
21
|
+
code: 'Código',
|
|
22
|
+
tests: 'Pruebas',
|
|
23
|
+
lockfile: 'Lockfile',
|
|
24
|
+
other: 'Config, scripts y assets',
|
|
25
|
+
deadWeight: 'Peso muerto',
|
|
26
|
+
linesUnit: 'líneas',
|
|
27
|
+
rounds: 'Round por round',
|
|
28
|
+
roundsNote: 'Commits por día, por fecha del commit.',
|
|
29
|
+
star: 'Round estelar',
|
|
30
|
+
clock: 'El reloj de la pelea',
|
|
31
|
+
clockNote: 'Commits por hora del día. La franja sombreada es la madrugada.',
|
|
32
|
+
peak: 'hora pico',
|
|
33
|
+
night: 'de madrugada (00–06)',
|
|
34
|
+
bands: ['Madrugada 00–06', 'Mañana 06–12', 'Tarde 12–18', 'Noche 18–24'],
|
|
35
|
+
compubox: 'CompuBox',
|
|
36
|
+
compuboxNote: 'Cada commit, por su tipo de golpe.',
|
|
37
|
+
types: {
|
|
38
|
+
feat: 'Golpes de poder',
|
|
39
|
+
fix: 'Jabs',
|
|
40
|
+
refactor: 'Juego de piernas',
|
|
41
|
+
test: 'Guardia',
|
|
42
|
+
docs: 'Esquina',
|
|
43
|
+
perf: 'Velocidad',
|
|
44
|
+
style: 'Estilo',
|
|
45
|
+
build: 'Calentamiento',
|
|
46
|
+
ci: 'Campana',
|
|
47
|
+
chore: 'Vendajes',
|
|
48
|
+
other: 'Otros',
|
|
49
|
+
},
|
|
50
|
+
card: 'La tarjeta del árbitro',
|
|
51
|
+
decisions: 'decisiones registradas',
|
|
52
|
+
judge: (n) => `${n} del juez`,
|
|
53
|
+
contracts: 'contratos firmados por el juez',
|
|
54
|
+
reboundsLabel: 'puntos de rebote',
|
|
55
|
+
reboundsSub: (closed, open) => `${closed} cerrados · ${open} abiertos`,
|
|
56
|
+
findings: 'hallazgos de esquinero',
|
|
57
|
+
findingsSub: (f) =>
|
|
58
|
+
`${f.applied} aplicados · ${f.discarded} descartados · ${f.ticket} a ticket`,
|
|
59
|
+
fouls: 'faltas que el árbitro se marcó',
|
|
60
|
+
reel: 'Mejores momentos',
|
|
61
|
+
commitsWord: 'commits',
|
|
62
|
+
tags: {
|
|
63
|
+
reclamo: 'Reclamo',
|
|
64
|
+
dado: 'Golpe dado',
|
|
65
|
+
recibido: 'Golpe recibido',
|
|
66
|
+
falta: 'Falta',
|
|
67
|
+
juez: 'Juez',
|
|
68
|
+
arbitro: 'Árbitro',
|
|
69
|
+
bateria: 'Batería',
|
|
70
|
+
campana: 'Campana',
|
|
71
|
+
},
|
|
72
|
+
weekday: 'es-MX',
|
|
73
|
+
},
|
|
74
|
+
en: {
|
|
75
|
+
title: 'The bout in numbers',
|
|
76
|
+
tiles: {
|
|
77
|
+
duration: 'of fighting, first record to last',
|
|
78
|
+
commits: "the wrestler's commits",
|
|
79
|
+
rounds: 'rounds with their bell',
|
|
80
|
+
lines: 'lines added',
|
|
81
|
+
},
|
|
82
|
+
weighIn: 'The weigh-in',
|
|
83
|
+
weighInNote: (ratio) => `${ratio} lines of tests for every line of code.`,
|
|
84
|
+
red: 'Red corner',
|
|
85
|
+
blue: 'Blue corner',
|
|
86
|
+
code: 'Code',
|
|
87
|
+
tests: 'Tests',
|
|
88
|
+
lockfile: 'Lockfile',
|
|
89
|
+
other: 'Config, scripts and assets',
|
|
90
|
+
deadWeight: 'Dead weight',
|
|
91
|
+
linesUnit: 'lines',
|
|
92
|
+
rounds: 'Round by round',
|
|
93
|
+
roundsNote: 'Commits per day, by commit date.',
|
|
94
|
+
star: 'Main event',
|
|
95
|
+
clock: 'The fight clock',
|
|
96
|
+
clockNote:
|
|
97
|
+
'Commits per hour of the day. The shaded band is the small hours.',
|
|
98
|
+
peak: 'peak hour',
|
|
99
|
+
night: 'in the small hours (00–06)',
|
|
100
|
+
bands: [
|
|
101
|
+
'Small hours 00–06',
|
|
102
|
+
'Morning 06–12',
|
|
103
|
+
'Afternoon 12–18',
|
|
104
|
+
'Night 18–24',
|
|
105
|
+
],
|
|
106
|
+
compubox: 'CompuBox',
|
|
107
|
+
compuboxNote: 'Every commit, by its punch type.',
|
|
108
|
+
types: {
|
|
109
|
+
feat: 'Power punches',
|
|
110
|
+
fix: 'Jabs',
|
|
111
|
+
refactor: 'Footwork',
|
|
112
|
+
test: 'Guard',
|
|
113
|
+
docs: 'Corner',
|
|
114
|
+
perf: 'Speed',
|
|
115
|
+
style: 'Style',
|
|
116
|
+
build: 'Warm-up',
|
|
117
|
+
ci: 'Bell',
|
|
118
|
+
chore: 'Hand wraps',
|
|
119
|
+
other: 'Other',
|
|
120
|
+
},
|
|
121
|
+
card: "The referee's card",
|
|
122
|
+
decisions: 'decisions logged',
|
|
123
|
+
judge: (n) => `${n} by the judge`,
|
|
124
|
+
contracts: 'contracts the judge signed',
|
|
125
|
+
reboundsLabel: 'rebound points',
|
|
126
|
+
reboundsSub: (closed, open) => `${closed} closed · ${open} open`,
|
|
127
|
+
findings: 'cornerman findings',
|
|
128
|
+
findingsSub: (f) =>
|
|
129
|
+
`${f.applied} applied · ${f.discarded} discarded · ${f.ticket} ticketed`,
|
|
130
|
+
fouls: 'fouls the referee called on itself',
|
|
131
|
+
reel: 'Highlight reel',
|
|
132
|
+
commitsWord: 'commits',
|
|
133
|
+
tags: {
|
|
134
|
+
reclamo: 'Claim',
|
|
135
|
+
dado: 'Blow landed',
|
|
136
|
+
recibido: 'Blow taken',
|
|
137
|
+
falta: 'Foul',
|
|
138
|
+
juez: 'Judge',
|
|
139
|
+
arbitro: 'Referee',
|
|
140
|
+
bateria: 'Battery',
|
|
141
|
+
campana: 'Bell',
|
|
142
|
+
},
|
|
143
|
+
weekday: 'en-US',
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const esc = (text) =>
|
|
148
|
+
String(text)
|
|
149
|
+
.replaceAll('&', '&')
|
|
150
|
+
.replaceAll('<', '<')
|
|
151
|
+
.replaceAll('>', '>')
|
|
152
|
+
.replaceAll('"', '"');
|
|
153
|
+
const fmt = (n, locale) => new Intl.NumberFormat(locale).format(n);
|
|
154
|
+
const r1 = (n) => Math.round(n * 10) / 10;
|
|
155
|
+
|
|
156
|
+
function tiles(stats, t, locale) {
|
|
157
|
+
const tile = (value, label) =>
|
|
158
|
+
value === null || value === undefined
|
|
159
|
+
? ''
|
|
160
|
+
: `<div><b>${esc(value)}</b><span>${esc(label)}</span></div>`;
|
|
161
|
+
return `<div class="cn-tiles cn-cards">${[
|
|
162
|
+
tile(stats.duration.label, t.tiles.duration),
|
|
163
|
+
tile(stats.rhythm && fmt(stats.rhythm.total, locale), t.tiles.commits),
|
|
164
|
+
tile(stats.book.roundBells, t.tiles.rounds),
|
|
165
|
+
tile(stats.lines && fmt(stats.lines.added, locale), t.tiles.lines),
|
|
166
|
+
].join('')}</div>`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function weighIn({ buckets }, t, locale) {
|
|
170
|
+
const code = buckets.code.added;
|
|
171
|
+
const tests = buckets.tests.added;
|
|
172
|
+
const both = code + tests || 1;
|
|
173
|
+
const ratio = code
|
|
174
|
+
? new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(
|
|
175
|
+
tests / code,
|
|
176
|
+
)
|
|
177
|
+
: '0';
|
|
178
|
+
const dead = [
|
|
179
|
+
[t.lockfile, buckets.lockfile.added],
|
|
180
|
+
[t.other, buckets.other.added],
|
|
181
|
+
].filter(([, n]) => n);
|
|
182
|
+
const corner = (side, label, name, n) =>
|
|
183
|
+
`<div class="cn-corner cn-${side}"><span class="cn-corner-label">${esc(label)}</span><b>${fmt(n, locale)}</b><span>${esc(name)} · ${esc(t.linesUnit)}</span></div>`;
|
|
184
|
+
return `<h4 class="cn-h">${esc(t.weighIn)}</h4><p class="cn-note">${esc(t.weighInNote(ratio))}</p>
|
|
185
|
+
<div class="cn-weigh">
|
|
186
|
+
<div class="cn-weigh-top">${corner('red', t.red, t.code, code)}<div class="cn-vs">VS</div>${corner('blue', t.blue, t.tests, tests)}</div>
|
|
187
|
+
<div class="cn-tape" role="img" aria-label="${esc(`${t.code} ${code}, ${t.tests} ${tests}`)}"><i class="cn-tape-red" style="width:${r1((code * 100) / both)}%" title="${esc(`${t.code}: ${fmt(code, locale)}`)}"></i><i class="cn-tape-blue" style="width:${r1((tests * 100) / both)}%" title="${esc(`${t.tests}: ${fmt(tests, locale)}`)}"></i></div>
|
|
188
|
+
${dead.length ? `<div class="cn-dead"><span>${esc(t.deadWeight)}</span>${dead.map(([name, n]) => `<span>${esc(name)} <b>${fmt(n, locale)}</b></span>`).join('')}</div>` : ''}
|
|
189
|
+
</div>`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function roundByRound(byDay, t, locale) {
|
|
193
|
+
const W = 640;
|
|
194
|
+
const H = 262;
|
|
195
|
+
const top = 72;
|
|
196
|
+
const base = 218;
|
|
197
|
+
const max = Math.max(1, ...byDay.map((d) => d.n));
|
|
198
|
+
const slot = (W - 60) / byDay.length;
|
|
199
|
+
const bar = Math.min(64, slot * 0.6);
|
|
200
|
+
const star = byDay.findIndex((d) => d.n === max);
|
|
201
|
+
const ropes = [0.25, 0.5, 0.75]
|
|
202
|
+
.map(
|
|
203
|
+
(f, i) =>
|
|
204
|
+
`<line class="cn-rope cn-rope-${i}" x1="22" x2="${W - 22}" y1="${r1(base - f * (base - top))}" y2="${r1(base - f * (base - top))}"/>`,
|
|
205
|
+
)
|
|
206
|
+
.join('');
|
|
207
|
+
const label = (day) =>
|
|
208
|
+
new Intl.DateTimeFormat(t.weekday, {
|
|
209
|
+
weekday: 'short',
|
|
210
|
+
day: 'numeric',
|
|
211
|
+
timeZone: 'UTC',
|
|
212
|
+
}).format(new Date(`${day}T12:00:00Z`));
|
|
213
|
+
const bars = byDay
|
|
214
|
+
.map((d, i) => {
|
|
215
|
+
const h = (d.n / max) * (base - top);
|
|
216
|
+
const x = 30 + slot * i + (slot - bar) / 2;
|
|
217
|
+
const y = base - h;
|
|
218
|
+
const cls = `cn-bar cn-fill-${i % 5}${i === star ? ' cn-bar-star' : ''}`;
|
|
219
|
+
return `<g><title>${esc(`${label(d.day)}: ${fmt(d.n, locale)} ${t.commitsWord}`)}</title><path class="${cls}" d="M${r1(x)},${base} V${r1(y + Math.min(4, h))} q0,-4 4,-4 h${r1(bar - 8)} q4,0 4,4 V${base} Z"/>${
|
|
220
|
+
d.n
|
|
221
|
+
? `<text class="cn-val" x="${r1(x + bar / 2)}" y="${r1(y - 8)}">${fmt(d.n, locale)}</text>`
|
|
222
|
+
: ''
|
|
223
|
+
}<text class="cn-axis" x="${r1(x + bar / 2)}" y="${base + 22}">${esc(label(d.day))}</text>${
|
|
224
|
+
i === star
|
|
225
|
+
? `<text class="cn-star" x="${r1(x + bar / 2)}" y="${r1(y - 40)}">${esc(t.star)}</text>`
|
|
226
|
+
: ''
|
|
227
|
+
}</g>`;
|
|
228
|
+
})
|
|
229
|
+
.join('');
|
|
230
|
+
return `<h4 class="cn-h">${esc(t.rounds)}</h4><p class="cn-note">${esc(t.roundsNote)}</p>
|
|
231
|
+
<div class="cn-chart"><svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(t.rounds)}">${ropes}<rect class="cn-post" x="14" y="${top - 6}" width="8" height="${base - top + 6}" rx="2"/><rect class="cn-post" x="${W - 22}" y="${top - 6}" width="8" height="${base - top + 6}" rx="2"/><line class="cn-canvas-line" x1="14" x2="${W - 14}" y1="${base}" y2="${base}"/>${bars}</svg></div>`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function fightClock(byHour, t, locale) {
|
|
235
|
+
const C = 170;
|
|
236
|
+
const r0 = 62;
|
|
237
|
+
const len = 92;
|
|
238
|
+
const max = Math.max(1, ...byHour);
|
|
239
|
+
const peak = byHour.indexOf(max);
|
|
240
|
+
const point = (r, deg) => {
|
|
241
|
+
const a = ((deg - 90) * Math.PI) / 180;
|
|
242
|
+
return [r1(C + r * Math.cos(a)), r1(C + r * Math.sin(a))];
|
|
243
|
+
};
|
|
244
|
+
const wedge = (ri, ro, from, to) => {
|
|
245
|
+
const [x1, y1] = point(ri, from);
|
|
246
|
+
const [x2, y2] = point(ro, from);
|
|
247
|
+
const [x3, y3] = point(ro, to);
|
|
248
|
+
const [x4, y4] = point(ri, to);
|
|
249
|
+
const large = to - from > 180 ? 1 : 0;
|
|
250
|
+
return `M${x1},${y1} L${x2},${y2} A${ro},${ro} 0 ${large} 1 ${x3},${y3} L${x4},${y4} A${ri},${ri} 0 ${large} 0 ${x1},${y1} Z`;
|
|
251
|
+
};
|
|
252
|
+
const night = `<path class="cn-night" d="${wedge(r0, r0 + len + 4, 0, 90)}"/>`;
|
|
253
|
+
const hours = byHour
|
|
254
|
+
.map((n, h) => {
|
|
255
|
+
const ro = r0 + 3 + (n / max) * len;
|
|
256
|
+
const cls = `cn-hour cn-band-${Math.floor(h / 6)}${h === peak ? ' cn-hour-peak' : ''}`;
|
|
257
|
+
return n
|
|
258
|
+
? `<path class="${cls}" d="${wedge(r0 + 3, ro, h * 15 + 2, h * 15 + 13)}"><title>${String(h).padStart(2, '0')}:00 · ${fmt(n, locale)} ${t.commitsWord}</title></path>`
|
|
259
|
+
: '';
|
|
260
|
+
})
|
|
261
|
+
.join('');
|
|
262
|
+
const ticks = [0, 6, 12, 18]
|
|
263
|
+
.map((h) => {
|
|
264
|
+
const [x, y] = point(r0 + len + 18, h * 15 + 7.5);
|
|
265
|
+
return `<text class="cn-axis" x="${x}" y="${y + 4}">${String(h).padStart(2, '0')}</text>`;
|
|
266
|
+
})
|
|
267
|
+
.join('');
|
|
268
|
+
const bandCounts = [0, 1, 2, 3].map((band) =>
|
|
269
|
+
byHour.slice(band * 6, band * 6 + 6).reduce((a, b) => a + b, 0),
|
|
270
|
+
);
|
|
271
|
+
const nightCount = bandCounts[0];
|
|
272
|
+
const legend = bandCounts
|
|
273
|
+
.map(
|
|
274
|
+
(n, band) =>
|
|
275
|
+
`<li><i class="cn-band-${band}"></i>${esc(t.bands[band])}<b>${fmt(n, locale)}</b></li>`,
|
|
276
|
+
)
|
|
277
|
+
.join('');
|
|
278
|
+
return `<h4 class="cn-h">${esc(t.clock)}</h4><p class="cn-note">${esc(t.clockNote)}</p>
|
|
279
|
+
<div class="cn-chart cn-clock-wrap"><svg class="cn-clock" viewBox="-24 -24 388 388" role="img" aria-label="${esc(t.clock)}"><circle class="cn-face" cx="${C}" cy="${C}" r="${r0 + len + 4}"/>${night}<circle class="cn-hub" cx="${C}" cy="${C}" r="${r0}"/>${hours}${ticks}<text class="cn-clock-big" x="${C}" y="${C + 6}">${String(peak).padStart(2, '0')}:00</text><text class="cn-clock-sub" x="${C}" y="${C + 28}">${esc(t.peak)}</text></svg>
|
|
280
|
+
<div class="cn-clock-facts"><div><b>${fmt(max, locale)}</b><span>${esc(t.commitsWord)} · ${String(peak).padStart(2, '0')}:00, ${esc(t.peak)}</span></div><div><b>${fmt(nightCount, locale)}</b><span>${esc(t.commitsWord)} ${esc(t.night)}</span></div><ul class="cn-bands">${legend}</ul></div></div>`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function compuBox(byType, total, t, locale) {
|
|
284
|
+
const max = Math.max(1, ...byType.map((x) => x.n));
|
|
285
|
+
const rows = byType
|
|
286
|
+
.map(({ type, n }) => {
|
|
287
|
+
const cls =
|
|
288
|
+
{
|
|
289
|
+
feat: 'cn-fill-0',
|
|
290
|
+
fix: 'cn-fill-1',
|
|
291
|
+
refactor: 'cn-fill-2',
|
|
292
|
+
test: 'cn-fill-3',
|
|
293
|
+
docs: 'cn-fill-4',
|
|
294
|
+
}[type] ?? 'cn-ink';
|
|
295
|
+
const pct = total ? Math.round((n * 100) / total) : 0;
|
|
296
|
+
return `<div class="cn-box-row" title="${esc(`${t.types[type]} (${type}): ${fmt(n, locale)}`)}"><span class="cn-box-name">${esc(t.types[type])}<code>${esc(type)}</code></span><span class="cn-box-track"><i class="${cls}" style="width:${r1((n * 100) / max)}%"></i></span><b>${fmt(n, locale)}</b><span class="cn-box-pct">${pct}%</span></div>`;
|
|
297
|
+
})
|
|
298
|
+
.join('');
|
|
299
|
+
return `<h4 class="cn-h">${esc(t.compubox)}</h4><p class="cn-note">${esc(t.compuboxNote)}</p><div class="cn-chart cn-box">${rows}</div>`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function refereeCard(book, t, locale) {
|
|
303
|
+
const f = book.findings;
|
|
304
|
+
let next = 0;
|
|
305
|
+
const card = (n, label, sub, cls = `cn-head-${next++ % 5}`) =>
|
|
306
|
+
`<div class="cn-score ${cls}"><span class="cn-score-head">${esc(label)}</span><b>${fmt(n, locale)}</b>${sub ? `<span>${esc(sub)}</span>` : ''}</div>`;
|
|
307
|
+
return `<h4 class="cn-h">${esc(t.card)}</h4><div class="cn-scores">${[
|
|
308
|
+
card(book.decisions, t.decisions, t.judge(book.judgeDecisions)),
|
|
309
|
+
card(
|
|
310
|
+
book.rebounds.total,
|
|
311
|
+
t.reboundsLabel,
|
|
312
|
+
t.reboundsSub(
|
|
313
|
+
book.rebounds.closed,
|
|
314
|
+
book.rebounds.total - book.rebounds.closed,
|
|
315
|
+
),
|
|
316
|
+
),
|
|
317
|
+
f.total ? card(f.total, t.findings, t.findingsSub(f)) : '',
|
|
318
|
+
book.contracts ? card(book.contracts, t.contracts, '') : '',
|
|
319
|
+
card(book.fouls, t.fouls, '', 'cn-score-foul'),
|
|
320
|
+
].join('')}</div>`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function reel(moments, timeZone, t) {
|
|
324
|
+
if (!moments.length) return '';
|
|
325
|
+
const first = Date.parse(moments[0].at);
|
|
326
|
+
const span = Math.max(1, Date.parse(moments.at(-1).at) - first);
|
|
327
|
+
const when = (at) =>
|
|
328
|
+
new Intl.DateTimeFormat(t.weekday, {
|
|
329
|
+
timeZone,
|
|
330
|
+
day: 'numeric',
|
|
331
|
+
hour: '2-digit',
|
|
332
|
+
minute: '2-digit',
|
|
333
|
+
hour12: false,
|
|
334
|
+
}).format(new Date(at));
|
|
335
|
+
const marks = moments
|
|
336
|
+
.map(
|
|
337
|
+
(m, i) =>
|
|
338
|
+
`<span class="cn-bell cn-fill-${i % 5}" style="left:${r1(3 + ((Date.parse(m.at) - first) * 94) / span)}%" title="${esc(`${when(m.at)} · ${m.title}`)}">${i + 1}</span>`,
|
|
339
|
+
)
|
|
340
|
+
.join('');
|
|
341
|
+
const items = moments
|
|
342
|
+
.map(
|
|
343
|
+
(m, i) =>
|
|
344
|
+
`<li><span class="cn-reel-n">${i + 1}</span><time>${esc(when(m.at))}</time><p><span class="tag t-${esc(m.tag)}">${esc(t.tags[m.tag] ?? m.tag)}</span>${esc(m.title)}</p></li>`,
|
|
345
|
+
)
|
|
346
|
+
.join('');
|
|
347
|
+
return `<h4 class="cn-h">${esc(t.reel)}</h4><div class="cn-reel"><div class="cn-reel-line">${marks}</div><ol>${items}</ol></div>`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** The whole block. `stats` is collectStats()'s output; moments come from readMoments(). PURE. */
|
|
351
|
+
export function renderStats(stats, { lang = 'es', moments = [] } = {}) {
|
|
352
|
+
const t = LABELS[lang] ?? LABELS.es;
|
|
353
|
+
const locale = lang === 'en' ? 'en-US' : 'es-MX';
|
|
354
|
+
return `<section class="round cn">
|
|
355
|
+
<div class="cn-picado" aria-hidden="true"></div>
|
|
356
|
+
<h2>${esc(t.title)}</h2>
|
|
357
|
+
${tiles(stats, t, locale)}
|
|
358
|
+
${stats.lines ? weighIn(stats.lines, t, locale) : ''}
|
|
359
|
+
${stats.rhythm?.byDay.length ? roundByRound(stats.rhythm.byDay, t, locale) : ''}
|
|
360
|
+
${stats.rhythm?.total ? fightClock(stats.rhythm.byHour, t, locale) : ''}
|
|
361
|
+
${stats.rhythm?.total ? compuBox(stats.rhythm.byType, stats.rhythm.total, t, locale) : ''}
|
|
362
|
+
${refereeCard(stats.book, t, locale)}
|
|
363
|
+
${reel(moments, stats.timezone, t)}
|
|
364
|
+
<!-- CHANGELOG: the skill writes <h4 class="cn-h"> and <ul class="cn-changelog"> here -->
|
|
365
|
+
</section>`;
|
|
366
|
+
}
|