@johnmorrisdotca/kyogijo-stadiums 0.4.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.
Files changed (361) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/NOTICE.md +182 -2
  3. package/README.md +106 -19
  4. package/dist/annotate.js +1 -1
  5. package/dist/arenas-entry.d.ts +4 -0
  6. package/dist/arenas-entry.js +3 -0
  7. package/dist/arenas.d.ts +125 -0
  8. package/dist/arenas.data.d.ts +2 -0
  9. package/dist/arenas.data.js +3763 -0
  10. package/dist/arenas.js +72 -0
  11. package/dist/athletics/describe.d.ts +8 -0
  12. package/dist/athletics/describe.js +41 -0
  13. package/dist/athletics/events.d.ts +17 -0
  14. package/dist/athletics/events.js +48 -0
  15. package/dist/athletics/facilities.d.ts +80 -0
  16. package/dist/athletics/facilities.js +176 -0
  17. package/dist/athletics/infield.d.ts +79 -0
  18. package/dist/athletics/infield.js +166 -0
  19. package/dist/athletics/layout.d.ts +13 -0
  20. package/dist/athletics/layout.js +98 -0
  21. package/dist/athletics/oval.d.ts +57 -0
  22. package/dist/athletics/oval.js +116 -0
  23. package/dist/athletics/plan.d.ts +97 -0
  24. package/dist/athletics/plan.js +355 -0
  25. package/dist/athletics/races.d.ts +102 -0
  26. package/dist/athletics/races.js +355 -0
  27. package/dist/athletics/steeplechase.d.ts +72 -0
  28. package/dist/athletics/steeplechase.js +196 -0
  29. package/dist/athletics/stencil.d.ts +13 -0
  30. package/dist/athletics/stencil.js +85 -0
  31. package/dist/athletics/track.d.ts +54 -0
  32. package/dist/athletics/track.js +105 -0
  33. package/dist/athletics/types.d.ts +77 -0
  34. package/dist/athletics/types.js +1 -0
  35. package/dist/athletics-entry.d.ts +17 -0
  36. package/dist/athletics-entry.js +11 -0
  37. package/dist/athletics.style.d.ts +16 -0
  38. package/dist/athletics.style.js +51 -0
  39. package/dist/atlas-entry.d.ts +6 -2
  40. package/dist/atlas-entry.js +4 -2
  41. package/dist/atlas.d.ts +117 -23
  42. package/dist/atlas.js +197 -12
  43. package/dist/background.js +2 -1
  44. package/dist/budo/describe.d.ts +13 -0
  45. package/dist/budo/describe.js +120 -0
  46. package/dist/budo/dojo.api.d.ts +64 -0
  47. package/dist/budo/dojo.api.js +46 -0
  48. package/dist/budo/dojo.d.ts +72 -0
  49. package/dist/budo/dojo.js +300 -0
  50. package/dist/budo/judo.d.ts +61 -0
  51. package/dist/budo/judo.js +109 -0
  52. package/dist/budo/karate.d.ts +29 -0
  53. package/dist/budo/karate.js +80 -0
  54. package/dist/budo/kendo.d.ts +18 -0
  55. package/dist/budo/kendo.js +44 -0
  56. package/dist/budo/kimarite.d.ts +19 -0
  57. package/dist/budo/kimarite.js +33 -0
  58. package/dist/budo/kit.d.ts +16 -0
  59. package/dist/budo/kit.js +24 -0
  60. package/dist/budo/kyudo.d.ts +28 -0
  61. package/dist/budo/kyudo.js +66 -0
  62. package/dist/budo/looks.d.ts +28 -0
  63. package/dist/budo/looks.js +58 -0
  64. package/dist/budo/parts.d.ts +32 -0
  65. package/dist/budo/parts.js +10 -0
  66. package/dist/budo/plan.d.ts +64 -0
  67. package/dist/budo/plan.js +139 -0
  68. package/dist/budo/room.d.ts +133 -0
  69. package/dist/budo/room.js +111 -0
  70. package/dist/budo/sumo.d.ts +31 -0
  71. package/dist/budo/sumo.js +71 -0
  72. package/dist/budo/tatami.d.ts +55 -0
  73. package/dist/budo/tatami.js +156 -0
  74. package/dist/budo/types.d.ts +192 -0
  75. package/dist/budo/types.js +2 -0
  76. package/dist/budo-entry.d.ts +22 -0
  77. package/dist/budo-entry.js +13 -0
  78. package/dist/budo.board.d.ts +28 -0
  79. package/dist/budo.board.js +81 -0
  80. package/dist/budo.score.d.ts +18 -0
  81. package/dist/budo.score.js +52 -0
  82. package/dist/budo.style.d.ts +6 -0
  83. package/dist/budo.style.js +67 -0
  84. package/dist/charts-entry.d.ts +6 -1
  85. package/dist/charts-entry.js +3 -0
  86. package/dist/charts.athletics.d.ts +183 -0
  87. package/dist/charts.athletics.js +455 -0
  88. package/dist/charts.baseball.js +1 -1
  89. package/dist/charts.curling.heat.d.ts +24 -0
  90. package/dist/charts.curling.heat.js +116 -0
  91. package/dist/charts.curling.js +14 -3
  92. package/dist/charts.js +4 -4
  93. package/dist/charts.pool.d.ts +67 -0
  94. package/dist/charts.pool.js +76 -0
  95. package/dist/charts.skating.d.ts +204 -0
  96. package/dist/charts.skating.js +426 -0
  97. package/dist/charts.soccer.js +3 -3
  98. package/dist/charts.types.d.ts +63 -1
  99. package/dist/colour.d.ts +4 -4
  100. package/dist/colour.js +4 -4
  101. package/dist/court.d.ts +85 -2
  102. package/dist/court.js +102 -7
  103. package/dist/court.more.d.ts +46 -0
  104. package/dist/court.more.js +219 -0
  105. package/dist/courts.style.d.ts +14 -2
  106. package/dist/courts.style.js +51 -4
  107. package/dist/crowd.d.ts +164 -0
  108. package/dist/crowd.draw.d.ts +17 -0
  109. package/dist/crowd.draw.js +204 -0
  110. package/dist/crowd.js +480 -0
  111. package/dist/crowd.park.d.ts +19 -0
  112. package/dist/crowd.park.js +153 -0
  113. package/dist/curling.stats.d.ts +271 -0
  114. package/dist/curling.stats.js +301 -0
  115. package/dist/draw.d.ts +16 -0
  116. package/dist/draw.js +462 -54
  117. package/dist/element-entry.d.ts +3 -1
  118. package/dist/element-entry.js +2 -1
  119. package/dist/element.d.ts +13 -2
  120. package/dist/element.js +79 -4
  121. package/dist/emblems-source.d.ts +50 -0
  122. package/dist/emblems-source.js +56 -0
  123. package/dist/eras.d.ts +3 -1
  124. package/dist/eras.js +8 -3
  125. package/dist/features.js +204 -0
  126. package/dist/field.data.d.ts +16 -0
  127. package/dist/field.data.js +50 -0
  128. package/dist/fields.kit.d.ts +2 -2
  129. package/dist/fields.types.d.ts +24 -4
  130. package/dist/formations.js +18 -7
  131. package/dist/formations.sports.d.ts +8 -0
  132. package/dist/formations.sports.js +251 -0
  133. package/dist/frames.providers.js +71 -8
  134. package/dist/geojson.js +3 -0
  135. package/dist/geometry-entry.d.ts +2 -1
  136. package/dist/geometry-entry.js +2 -1
  137. package/dist/geometry.d.ts +20 -0
  138. package/dist/geometry.js +78 -1
  139. package/dist/grounds.d.ts +4 -0
  140. package/dist/grounds.data.js +12 -0
  141. package/dist/halls.d.ts +65 -0
  142. package/dist/halls.js +122 -0
  143. package/dist/hardwood.d.ts +31 -0
  144. package/dist/hardwood.js +157 -0
  145. package/dist/heat.js +21 -13
  146. package/dist/heat.types.d.ts +5 -0
  147. package/dist/heat.zones.d.ts +4 -2
  148. package/dist/heat.zones.js +207 -7
  149. package/dist/homeside.js +24 -0
  150. package/dist/index.d.ts +30 -4
  151. package/dist/index.js +18 -2
  152. package/dist/layers-entry.d.ts +2 -1
  153. package/dist/layers-entry.js +1 -0
  154. package/dist/layers.d.ts +2 -1
  155. package/dist/layers.env.d.ts +12 -1
  156. package/dist/layers.env.js +36 -4
  157. package/dist/layers.js +32 -13
  158. package/dist/layers.types.d.ts +9 -2
  159. package/dist/leagues/athletics.d.ts +63 -0
  160. package/dist/leagues/athletics.js +87 -0
  161. package/dist/leagues/budo.d.ts +196 -0
  162. package/dist/leagues/budo.js +105 -0
  163. package/dist/leagues/courts.d.ts +269 -4
  164. package/dist/leagues/courts.js +155 -1
  165. package/dist/leagues/hockey.js +41 -19
  166. package/dist/leagues/skating.d.ts +145 -0
  167. package/dist/leagues/skating.js +73 -0
  168. package/dist/leagues/sources.js +41 -0
  169. package/dist/leagues/types.d.ts +1 -1
  170. package/dist/leagues-entry.d.ts +4 -2
  171. package/dist/leagues-entry.js +2 -1
  172. package/dist/leagues.d.ts +17 -4
  173. package/dist/leagues.js +246 -5
  174. package/dist/lineup.codes.d.ts +23 -0
  175. package/dist/lineup.codes.js +122 -0
  176. package/dist/logo.d.ts +25 -0
  177. package/dist/logo.js +47 -0
  178. package/dist/look.d.ts +3 -3
  179. package/dist/look.js +39 -6
  180. package/dist/outline.d.ts +1 -1
  181. package/dist/outline.js +46 -4
  182. package/dist/pageant.d.ts +120 -0
  183. package/dist/pageant.draw.d.ts +21 -0
  184. package/dist/pageant.draw.js +171 -0
  185. package/dist/pageant.js +211 -0
  186. package/dist/pageant.style.d.ts +8 -0
  187. package/dist/pageant.style.js +42 -0
  188. package/dist/png.d.ts +8 -1
  189. package/dist/png.js +21 -1
  190. package/dist/pointer-entry.d.ts +2 -0
  191. package/dist/pointer-entry.js +2 -0
  192. package/dist/pointer.d.ts +113 -0
  193. package/dist/pointer.js +151 -0
  194. package/dist/pool.d.ts +176 -0
  195. package/dist/pool.figures.d.ts +306 -0
  196. package/dist/pool.figures.js +148 -0
  197. package/dist/pool.js +304 -0
  198. package/dist/pool.kit.d.ts +48 -0
  199. package/dist/pool.kit.js +141 -0
  200. package/dist/pool.labels.d.ts +20 -0
  201. package/dist/pool.labels.js +258 -0
  202. package/dist/pool.race.d.ts +65 -0
  203. package/dist/pool.race.js +140 -0
  204. package/dist/pool.replay.d.ts +46 -0
  205. package/dist/pool.replay.js +127 -0
  206. package/dist/pool.section.d.ts +13 -0
  207. package/dist/pool.section.js +83 -0
  208. package/dist/pool.style.d.ts +20 -0
  209. package/dist/pool.style.js +169 -0
  210. package/dist/pool.types.d.ts +145 -0
  211. package/dist/pool.types.js +3 -0
  212. package/dist/pool.words.d.ts +13 -0
  213. package/dist/pool.words.js +101 -0
  214. package/dist/ranges.names.js +53 -3
  215. package/dist/replay-entry.d.ts +3 -0
  216. package/dist/replay-entry.js +2 -0
  217. package/dist/restyle.js +1 -1
  218. package/dist/road.draw.js +3 -2
  219. package/dist/road.js +2 -1
  220. package/dist/road.types.d.ts +3 -0
  221. package/dist/rules.d.ts +138 -0
  222. package/dist/rules.js +511 -0
  223. package/dist/rules.types.d.ts +4 -0
  224. package/dist/rules.types.js +64 -0
  225. package/dist/score.d.ts +3 -0
  226. package/dist/score.js +93 -6
  227. package/dist/sheet.d.ts +3 -0
  228. package/dist/sheet.js +7 -3
  229. package/dist/situation.d.ts +135 -0
  230. package/dist/situation.draw.d.ts +34 -0
  231. package/dist/situation.draw.js +202 -0
  232. package/dist/situation.js +337 -0
  233. package/dist/situation.style.d.ts +2 -0
  234. package/dist/situation.style.js +45 -0
  235. package/dist/situation.types.d.ts +77 -0
  236. package/dist/situation.types.js +3 -0
  237. package/dist/situation.widgets.d.ts +39 -0
  238. package/dist/situation.widgets.js +129 -0
  239. package/dist/skating/describe.d.ts +7 -0
  240. package/dist/skating/describe.js +44 -0
  241. package/dist/skating/events.d.ts +24 -0
  242. package/dist/skating/events.js +47 -0
  243. package/dist/skating/figure.d.ts +24 -0
  244. package/dist/skating/figure.js +37 -0
  245. package/dist/skating/long.d.ts +54 -0
  246. package/dist/skating/long.js +111 -0
  247. package/dist/skating/parts.d.ts +43 -0
  248. package/dist/skating/parts.js +40 -0
  249. package/dist/skating/plan.d.ts +55 -0
  250. package/dist/skating/plan.js +197 -0
  251. package/dist/skating/short.d.ts +47 -0
  252. package/dist/skating/short.js +89 -0
  253. package/dist/skating/track.d.ts +66 -0
  254. package/dist/skating/track.js +94 -0
  255. package/dist/skating/types.d.ts +57 -0
  256. package/dist/skating/types.js +3 -0
  257. package/dist/skating-entry.d.ts +17 -0
  258. package/dist/skating-entry.js +11 -0
  259. package/dist/skating.style.d.ts +4 -0
  260. package/dist/skating.style.js +24 -0
  261. package/dist/strings.d.ts +470 -0
  262. package/dist/strings.js +674 -13
  263. package/dist/style.d.ts +2 -0
  264. package/dist/style.js +19 -2
  265. package/dist/surfaces/artistic.d.ts +9 -0
  266. package/dist/surfaces/artistic.js +94 -0
  267. package/dist/surfaces/athletics.d.ts +16 -0
  268. package/dist/surfaces/athletics.js +132 -0
  269. package/dist/surfaces/baseball.js +17 -0
  270. package/dist/surfaces/basketball.floor.d.ts +27 -0
  271. package/dist/surfaces/basketball.floor.js +83 -0
  272. package/dist/surfaces/basketball.gear.d.ts +27 -0
  273. package/dist/surfaces/basketball.gear.js +90 -0
  274. package/dist/surfaces/basketball.js +44 -22
  275. package/dist/surfaces/budo.d.ts +6 -0
  276. package/dist/surfaces/budo.js +104 -0
  277. package/dist/surfaces/curling.d.ts +16 -0
  278. package/dist/surfaces/curling.fold.d.ts +67 -0
  279. package/dist/surfaces/curling.fold.js +107 -0
  280. package/dist/surfaces/curling.js +37 -3
  281. package/dist/surfaces/dive.d.ts +9 -0
  282. package/dist/surfaces/dive.js +128 -0
  283. package/dist/surfaces/field.d.ts +7 -2
  284. package/dist/surfaces/field.js +15 -9
  285. package/dist/surfaces/football.js +7 -0
  286. package/dist/surfaces/hockey.js +4 -2
  287. package/dist/surfaces/net.d.ts +2 -1
  288. package/dist/surfaces/net.js +160 -15
  289. package/dist/surfaces/polo.d.ts +12 -0
  290. package/dist/surfaces/polo.js +159 -0
  291. package/dist/surfaces/pool.d.ts +27 -0
  292. package/dist/surfaces/pool.js +198 -0
  293. package/dist/surfaces/skating.d.ts +6 -0
  294. package/dist/surfaces/skating.js +62 -0
  295. package/dist/surfaces/soccer.js +2 -1
  296. package/dist/surfaces/surface.d.ts +76 -0
  297. package/dist/surfaces/venue.d.ts +15 -0
  298. package/dist/surfaces/venue.js +95 -0
  299. package/dist/target-entry.d.ts +2 -0
  300. package/dist/target-entry.js +2 -0
  301. package/dist/target.d.ts +200 -0
  302. package/dist/target.js +525 -0
  303. package/dist/team.common.d.ts +1 -1
  304. package/dist/team.common.js +3 -3
  305. package/dist/team.formation.d.ts +3 -1
  306. package/dist/team.formation.js +3 -3
  307. package/dist/team.layers.d.ts +2 -2
  308. package/dist/team.layers.js +4 -1
  309. package/dist/team.lineup.d.ts +27 -0
  310. package/dist/team.lineup.js +207 -0
  311. package/dist/team.network.js +6 -5
  312. package/dist/team.shape.js +5 -5
  313. package/dist/team.types.d.ts +40 -1
  314. package/dist/tenants.d.ts +77 -0
  315. package/dist/tenants.data.d.ts +8 -0
  316. package/dist/tenants.data.js +1793 -0
  317. package/dist/tenants.js +46 -0
  318. package/dist/turn-entry.d.ts +12 -0
  319. package/dist/turn-entry.js +7 -0
  320. package/dist/turn.camera.d.ts +116 -0
  321. package/dist/turn.camera.js +184 -0
  322. package/dist/turn.dom.d.ts +40 -0
  323. package/dist/turn.dom.js +353 -0
  324. package/dist/turn.kit.d.ts +86 -0
  325. package/dist/turn.kit.js +110 -0
  326. package/dist/turn.markup.d.ts +48 -0
  327. package/dist/turn.markup.js +108 -0
  328. package/dist/turn.model.d.ts +78 -0
  329. package/dist/turn.model.js +91 -0
  330. package/dist/turn.style.d.ts +5 -0
  331. package/dist/turn.style.js +56 -0
  332. package/dist/types.d.ts +221 -20
  333. package/dist/units.d.ts +1 -1
  334. package/dist/units.js +22 -1
  335. package/dist/uprights.board.d.ts +17 -0
  336. package/dist/uprights.board.js +59 -0
  337. package/dist/uprights.budo.d.ts +17 -0
  338. package/dist/uprights.budo.js +87 -0
  339. package/dist/uprights.d.ts +37 -0
  340. package/dist/uprights.fields.d.ts +32 -0
  341. package/dist/uprights.fields.js +172 -0
  342. package/dist/uprights.goal.d.ts +49 -0
  343. package/dist/uprights.goal.js +69 -0
  344. package/dist/uprights.js +169 -0
  345. package/dist/uprights.more.d.ts +7 -0
  346. package/dist/uprights.more.js +183 -0
  347. package/dist/uprights.net.d.ts +12 -0
  348. package/dist/uprights.net.js +109 -0
  349. package/dist/uprights.netkit.d.ts +28 -0
  350. package/dist/uprights.netkit.js +67 -0
  351. package/dist/uprights.pools.d.ts +8 -0
  352. package/dist/uprights.pools.js +190 -0
  353. package/dist/uprights.rinks.d.ts +27 -0
  354. package/dist/uprights.rinks.js +106 -0
  355. package/dist/uprights.track.d.ts +50 -0
  356. package/dist/uprights.track.js +246 -0
  357. package/dist/version.d.ts +1 -1
  358. package/dist/version.js +1 -1
  359. package/dist/zones-entry.d.ts +2 -0
  360. package/dist/zones-entry.js +2 -0
  361. package/package.json +56 -3
package/dist/draw.js CHANGED
@@ -1,22 +1,52 @@
1
1
  import { ballparkDrawn, ballparkFor, ballparkSentences } from "./ballpark.js";
2
- import { inkOn, safeColour, safeLogo } from "./colour.js";
2
+ import { inkOn, safeColour } from "./colour.js";
3
+ import { withEmblems } from "./emblems-source.js";
4
+ import { safeLogo } from "./logo.js";
3
5
  import { drawingId, feetFence, isPortrait, lengthUnit, safeClass } from "./common.js";
4
6
  import { el, esc, num } from "./format.js";
7
+ import { fieldAttributes } from "./field.data.js";
5
8
  import { ERA_SPORTS, footballIn, rulesFor, seasonLabel } from "./eras.js";
6
9
  import { FOOTBALL } from "./geometry.js";
7
10
  import { renderLayers } from "./layers.js";
11
+ import { arenaById, arenaCourtIn, arenaNameIn, arenaOfficialName, arenaRinkIn } from "./arenas.js";
8
12
  import { groundById, groundNameIn, groundOfficialName } from "./grounds.js";
9
- import { courtFor, isNetSport } from "./court.js";
13
+ import { BOARD, courtFor, hasSetsBoard, isNetLeague, isNetSport } from "./court.js";
10
14
  import { cricketBoardMarkup } from "./cricket.board.js";
15
+ import { athleticsPlan } from "./athletics/plan.js";
16
+ import { skatingPlan } from "./skating/plan.js";
17
+ import { skatingAbout, skatingSentences } from "./skating/describe.js";
18
+ import { skatingStyle } from "./skating.style.js";
19
+ import { budoPlan, isBudoSport } from "./budo/plan.js";
20
+ import { dojoSpecOf } from "./budo/dojo.js";
21
+ import { dojoGallery } from "./budo/room.js";
22
+ import { budoAbout, budoScoreSentences, budoSentences } from "./budo/describe.js";
23
+ import { lookOf as budoLookOf } from "./budo/looks.js";
24
+ import { kimariteName } from "./budo/kimarite.js";
25
+ import { budoScoreText, budoView } from "./budo.score.js";
26
+ import { KYOGIJO_BUDO_GALLERY_STYLE, budoStyle } from "./budo.style.js";
27
+ import { budoBoardMarkup } from "./budo.board.js";
28
+ import { budoSurface } from "./surfaces/budo.js";
29
+ import { skatingSurface } from "./surfaces/skating.js";
30
+ import { athleticsAbout, athleticsSentences } from "./athletics/describe.js";
31
+ import { athleticsStyle, athleticsTrackRules } from "./athletics.style.js";
32
+ import { athleticsSurface, pitchGroup } from "./surfaces/athletics.js";
11
33
  import { fieldPlan, isFieldSport } from "./fields.js";
12
34
  import { fieldScoreText, fieldTotal } from "./field.score.js";
13
35
  import { fieldsStyle } from "./fields.style.js";
14
36
  import { resolveHomeSide } from "./homeside.js";
15
37
  import { LEAGUES, chooseLeague, courtOf, diamondOf, fieldOf, hasHistory, leagueSeason, leagueSize, netOf, rinkOf, sheetOf } from "./leagues.js";
16
- import { diamondFence } from "./leagues/baseball.js";
17
- import { fenceFor, parkById, parkNameIn, parkOfficialName } from "./parks.js";
38
+ import { MLB_DIAMOND, diamondFence } from "./leagues/baseball.js";
39
+ import { fenceFor, parkById, parkNameIn, parkOfficialName, parkWallsIn } from "./parks.js";
18
40
  import { boardMarkup, endText } from "./curling.board.js";
19
41
  import { sheetFor } from "./sheet.js";
42
+ import { commonOf, isPoolSport, swimPlan, poloPlan, divePlan, artisticPlan } from "./pool.js";
43
+ import { natatorium } from "./surfaces/venue.js";
44
+ import { poolDescription } from "./pool.words.js";
45
+ import { KYOGIJO_POOL_STYLE, poolStyle } from "./pool.style.js";
46
+ import { swimming } from "./surfaces/pool.js";
47
+ import { waterPolo } from "./surfaces/polo.js";
48
+ import { diving } from "./surfaces/dive.js";
49
+ import { artistic } from "./surfaces/artistic.js";
20
50
  import { cropToRange, describeRange, rangeLabel, shows } from "./ranges.js";
21
51
  import { badgeMarkup, bannerMarkup, plateMarkup, stateOf } from "./score.js";
22
52
  import { setsBoardMarkup } from "./sets.board.js";
@@ -24,19 +54,32 @@ import { KYOGIJO_STRINGS, fill } from "./strings.js";
24
54
  import { courtStyle } from "./courts.style.js";
25
55
  import { annotate, annotated, wideView } from "./annotate.js";
26
56
  import { KYOGIJO_BACKGROUND_STYLE, laidOn } from "./background.js";
57
+ import { KYOGIJO_HARDWOOD_STYLE, resolveHardwood } from "./hardwood.js";
58
+ import { WALL_SEATS, densityOf, layoutOf as crowdLayout, resolveCrowd } from "./crowd.js";
59
+ import { drawCrowd, KYOGIJO_CROWD_STYLE } from "./crowd.draw.js";
60
+ import { parkLayout } from "./crowd.park.js";
27
61
  import { featureColours, featureStyle } from "./features.js";
28
62
  import { isLook, lookStyle } from "./look.js";
29
63
  import { lineLook, restyleLines } from "./restyle.js";
30
64
  import { KYOGIJO_MARK_STYLE } from "./mark.js";
31
- import { KYOGIJO_BASE_STYLE, KYOGIJO_CURLING_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_WALLS_STYLE } from "./style.js";
65
+ import { readSituation, situationSentences } from "./situation.js";
66
+ import { badgeSituation, drawSituation } from "./situation.draw.js";
67
+ import { KYOGIJO_SITUATION_STYLE } from "./situation.style.js";
68
+ import { outlineOf } from "./outline.js";
69
+ import { autoScore, pageantLayout, pageantOf, ratioOf } from "./pageant.js";
70
+ import { emblemMarkup, picturesMarkup } from "./pageant.draw.js";
71
+ import { KYOGIJO_PAGEANT_MOTION, KYOGIJO_PAGEANT_STYLE } from "./pageant.style.js";
72
+ import { KYOGIJO_BASE_STYLE, KYOGIJO_CURLING_STYLE, KYOGIJO_FOLD_STYLE, KYOGIJO_NOTE_STYLE, KYOGIJO_WALLS_STYLE } from "./style.js";
32
73
  import { convertLength } from "./units.js";
33
- import { baseball } from "./surfaces/baseball.js";
74
+ import { baseball, fenceOutline } from "./surfaces/baseball.js";
34
75
  import { curling } from "./surfaces/curling.js";
35
76
  import { netCourt } from "./surfaces/net.js";
36
77
  import { fieldSurface } from "./surfaces/field.js";
37
78
  import { basketball } from "./surfaces/basketball.js";
38
79
  import { football } from "./surfaces/football.js";
39
80
  import { hockey } from "./surfaces/hockey.js";
81
+ import { NHL_RINK } from "./leagues/hockey.js";
82
+ import { NBA_COURT } from "./leagues/basketball.js";
40
83
  import { soccer } from "./surfaces/soccer.js";
41
84
  import { frameMatrix, layoutFromFrame, resolveFrame } from "./surfaces/soccer-providers.js";
42
85
  import { composeSpecial, hostSport } from "./special.js";
@@ -47,7 +90,7 @@ import { abbreviate, capitals } from "./text.js";
47
90
  import { VERSION } from "./version.js";
48
91
  export { isPortrait };
49
92
  /** The sports that can be drawn, in the order a menu lists them. */
50
- export const SPORTS = ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball"];
93
+ export const SPORTS = ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball", "swimming", "waterpolo", "diving", "artisticswimming", "athletics", "skating", "judo", "karate", "kendo", "sumo", "kyudo", "dojo", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball"];
51
94
  /** The CSS custom property each colour of `Colors` sets. */
52
95
  export const COLOR_PROPERTIES = {
53
96
  apron: "--ky-apron",
@@ -90,7 +133,8 @@ const isScore = (o) => o.home !== undefined && o.away !== undefined;
90
133
  function minSets(sport, league) {
91
134
  if (sport === "volleyball")
92
135
  return league === "fivb-beach" ? 3 : 5;
93
- return sport === "tabletennis" ? 5 : 3;
136
+ // Table tennis, squash (World Squash Singles Rules 2.3) and racquetball (IRF Rule 1.5) are played to the best of five games.
137
+ return sport === "tabletennis" || sport === "squash" || sport === "racquetball" ? 5 : 3;
94
138
  }
95
139
  /** The standard statuses, spelled out. */
96
140
  const FINAL = /^(?:final|f\b|ft\b|試合終了|終了)/i;
@@ -139,8 +183,8 @@ export function winnerOf(o) {
139
183
  const mode = o.winner ?? "auto";
140
184
  if (mode === "home" || mode === "away" || mode === "none")
141
185
  return mode;
142
- const h = fieldTotal(o.sport, o.home) ?? numeric(o.home.score);
143
- const a = fieldTotal(o.sport, o.away) ?? numeric(o.away.score);
186
+ const h = fieldTotal(o.sport, o.home) ?? numeric(o.home.score) ?? (o.sport === undefined ? undefined : numeric(budoScoreText(o.sport, o.home)));
187
+ const a = fieldTotal(o.sport, o.away) ?? numeric(o.away.score) ?? (o.sport === undefined ? undefined : numeric(budoScoreText(o.sport, o.away)));
144
188
  if (h === undefined || a === undefined)
145
189
  return "none";
146
190
  if (mode === "auto" && !(o.status !== undefined && FINAL.test(o.status.trim())))
@@ -177,7 +221,7 @@ const safeLength = (value) => {
177
221
  const PATTERNS = ["bands", "wide", "narrow", "lengthwise", "checks", "diagonal", "circles", "rings", "fan", "none"];
178
222
  const GOALS = ["box", "line", "strip", "none"];
179
223
  /** The pitch the options ask for: a `pitch` of the caller's, else the size a `ground` publishes, else none (the international pitch). */
180
- function pitchOf(o) {
224
+ export function pitchOf(o) {
181
225
  if (o.pitch !== undefined)
182
226
  return o.pitch;
183
227
  const ground = groundById(o.ground);
@@ -199,10 +243,35 @@ function buildSoccer(o, common, scored, unit) {
199
243
  frame: frame === undefined ? undefined : { id: frame.id, matrix: frameMatrix(frame, spec) },
200
244
  }, common);
201
245
  }
246
+ /** The athletics stadium: its plan, with the soccer pitch laid inside the track as the soccer builder draws it. */
247
+ function buildAthletics(o, common) {
248
+ const plan = athleticsPlan({ ...o, detail: common.detail });
249
+ let embedded;
250
+ if (plan.pitch !== undefined) {
251
+ const spec = plan.pitch.spec;
252
+ const goals = common.goals ?? "box";
253
+ const pitch = soccer({ spec, layout: layoutOf(spec), half: undefined, pattern: "bands", goals, flags: common.detail === "full", frame: undefined }, { ...common, portrait: false, margin: 0, score: false, landmarks: false, id: `${common.id}-pitch` });
254
+ embedded = { base: pitchGroup(pitch.base), lines: pitch.lines, defs: pitch.defs };
255
+ }
256
+ // A cropped view has less ground round it than the stadium whole.
257
+ return athleticsSurface(common, plan, embedded, common.margin ?? (o.range === undefined ? 20 : 5));
258
+ }
259
+ /** The skating rink: its plan, drawn as it lies. */
260
+ function buildSkating(o, common) {
261
+ const plan = skatingPlan({ ...o, detail: common.detail });
262
+ return skatingSurface(common, plan, common.margin ?? (o.range === undefined ? 20 : 5));
263
+ }
264
+ /** The budō surface: its plan, drawn as it lies. A hall, a kyūdōjō and a range of mats have little ground round them. */
265
+ function buildBudo(o, common) {
266
+ const plan = budoPlan({ ...o, detail: common.detail });
267
+ return budoSurface(common, plan, common.margin ?? (o.range === undefined ? 12 : 4));
268
+ }
269
+ /** One text for both ends or sides, or a pair of them. */
270
+ const pair = (text) => (text === undefined ? undefined : typeof text === "string" ? [text, text] : text);
202
271
  /** The surface the options ask for, whole: before it is cropped to a `range`. */
203
272
  function buildWhole(o, portrait, id, score, scored, extra) {
204
273
  const court = o.court;
205
- const baselineText = court?.baselineText === undefined ? undefined : typeof court.baselineText === "string" ? [court.baselineText, court.baselineText] : court.baselineText;
274
+ const baselineText = pair(court?.baselineText);
206
275
  const unit = lengthUnit(o);
207
276
  const common = {
208
277
  portrait,
@@ -212,12 +281,13 @@ function buildWhole(o, portrait, id, score, scored, extra) {
212
281
  stripes: o.stripes !== false,
213
282
  id,
214
283
  score,
215
- court: { baselineText, centreLogo: safeLogo(court?.centreLogo), paintThreePoint: court?.paintThreePoint },
284
+ court: { baselineText, centreLogo: safeLogo(court?.centreLogo), paintThreePoint: court?.paintThreePoint, hardwood: resolveHardwood(court, o.detail === "simple", o.width), sidelineText: pair(court?.sidelineText) },
216
285
  features: extra.features,
217
286
  mow: PATTERNS.includes(o.stripePattern) ? o.stripePattern : undefined,
218
287
  goals: GOALS.includes(o.goals) ? o.goals : undefined,
219
288
  goalMarkup: typeof o.goalMarkup === "function" ? o.goalMarkup : undefined,
220
289
  landmarks: o.landmarks === true,
290
+ pageant: extra.pageant === true ? true : undefined,
221
291
  };
222
292
  // A league's rulebook (`league`), else the markings of the season asked for (`year`), today's without one.
223
293
  const { sport, league } = chooseLeague(o);
@@ -232,10 +302,19 @@ function buildWhole(o, portrait, id, score, scored, extra) {
232
302
  const season = rulesFor(sport, o.year).rules;
233
303
  return football(footballIn(sport, o.year), common, { hashMarks: season.hash !== null, postShape: season.postShape });
234
304
  }
235
- case "nhl":
305
+ case "nhl": {
306
+ // An arena's rink is the size its sources publish, with the markings of the season asked for; an arena with none published has the NHL's.
307
+ const sized = league === undefined ? arenaRinkIn(arenaById(o.arena), o.year) : undefined;
308
+ if (sized !== undefined)
309
+ return hockey(common, rulesFor("nhl", o.year).rules, { ...NHL_RINK, length: sized.length, width: sized.width });
236
310
  return league?.family === "hockey" ? hockey(common, undefined, rinkOf(league, o.year)) : hockey(common, rulesFor("nhl", o.year).rules);
237
- case "nba":
311
+ }
312
+ case "nba": {
313
+ const sized = league === undefined ? arenaCourtIn(arenaById(o.arena), o.year) : undefined;
314
+ if (sized !== undefined)
315
+ return basketball(common, rulesFor("nba", o.year).rules, { ...NBA_COURT, length: sized.length, width: sized.width });
238
316
  return league?.family === "basketball" ? basketball(common, undefined, courtOf(league, o.year)) : basketball(common, rulesFor("nba", o.year).rules);
317
+ }
239
318
  case "mlb": {
240
319
  if (league?.family === "baseball") {
241
320
  const diamond = diamondOf(league, o.year);
@@ -248,14 +327,33 @@ function buildWhole(o, portrait, id, score, scored, extra) {
248
327
  case "curling": {
249
328
  const plan = sheetFor(o);
250
329
  // A score always shows the whole sheet, for the board's sake.
251
- return curling(common, { pebble: plan.pebble, guardZone: plan.guardZone, show: scored ? "sheet" : plan.show, end: plan.end, figures: plan.spec });
330
+ const words = KYOGIJO_STRINGS[o.language === "ja" ? "ja" : "en"].ranges.folded;
331
+ const labels = portrait ? [words.top, words.bottom] : [words.left, words.right];
332
+ return curling(common, { pebble: plan.pebble, guardZone: plan.guardZone, show: scored ? "sheet" : plan.show, end: plan.end, figures: plan.spec, fold: { panels: plan.panels, fit: plan.fit, labels } });
252
333
  }
253
334
  case "tennis":
254
335
  case "volleyball":
255
336
  case "badminton":
256
337
  case "pickleball":
257
338
  case "tabletennis":
339
+ case "padel":
340
+ case "squash":
341
+ case "racquetball":
342
+ case "sepaktakraw":
343
+ case "teqball":
344
+ case "roundnet":
345
+ case "netball":
258
346
  return netCourt(common, courtFor({ ...o, sport }));
347
+ case "swimming":
348
+ if (o.pool?.venue !== undefined)
349
+ return natatorium(common, o.pool, commonOf(o.pool), o.language === "ja" ? "ja" : "en");
350
+ return swimming(common, swimPlan({ ...o, sport }));
351
+ case "waterpolo":
352
+ return waterPolo(common, poloPlan(o));
353
+ case "diving":
354
+ return diving(common, divePlan(o));
355
+ case "artisticswimming":
356
+ return artistic(common, artisticPlan(o));
259
357
  case "lacrosse":
260
358
  case "rugby":
261
359
  case "afl":
@@ -264,18 +362,103 @@ function buildWhole(o, portrait, id, score, scored, extra) {
264
362
  case "fieldhockey":
265
363
  case "handball":
266
364
  return fieldSurface(common, fieldPlan({ ...o, sport, detail: common.detail }), { goals: common.goals });
365
+ case "athletics":
366
+ return buildAthletics({ ...o, sport }, common);
367
+ case "skating":
368
+ return buildSkating({ ...o, sport }, common);
369
+ case "judo":
370
+ case "karate":
371
+ case "kendo":
372
+ case "sumo":
373
+ case "kyudo":
374
+ case "dojo":
375
+ return buildBudo({ ...o, sport }, common);
267
376
  default:
268
377
  throw new Error(`Unknown sport "${String(o.sport)}": use one of ${SPORTS.join(", ")}`);
269
378
  }
270
379
  }
380
+ /**
381
+ * The crowd the options ask for, or `undefined` for none: a special game has none, and nor has a folded curling sheet (its houses are enlarged over where the stands would be, so the
382
+ * stands would show only as strips behind the panels). A score on a curling sheet shows the whole sheet, which has its stands.
383
+ */
384
+ function crowdOf(o) {
385
+ if (o.host !== undefined)
386
+ return undefined;
387
+ if (chooseLeague(o).sport === "curling" && !isScore(o) && sheetFor(o).show === "folded")
388
+ return undefined;
389
+ return resolveCrowd(o.crowd);
390
+ }
271
391
  /** The surface the options ask for, cropped to their `range` when they have one (a curling sheet's named ranges are its builder's own: `sheet.show`). */
272
392
  function buildSurface(o, portrait, id, score, scored, extra) {
273
- const surface = buildWhole(o, portrait, id, score, scored, extra);
393
+ const crowd = crowdOf(o);
394
+ const surface = crowd === undefined ? buildWhole(o, portrait, id, score, scored, extra) : buildCrowded(o, portrait, id, score, scored, extra, crowd);
274
395
  // A score on a curling sheet shows all of it, for the board's sake.
275
396
  if (o.range === undefined || (scored && chooseLeague(o).sport === "curling"))
276
397
  return surface;
398
+ // A close-up of a start is the athletics builder's own view, turned to the start's line.
399
+ if (chooseLeague(o).sport === "athletics" && rangeLabel(o) === "start")
400
+ return surface;
277
401
  const given = typeof o.margin === "number" && Number.isFinite(o.margin) && o.margin >= 0 ? convertLength(o.margin, lengthUnit(o), "ft") : undefined;
278
- return cropToRange(surface, o, { id, margin: extra.room ?? extra.margin ?? given, panel: score });
402
+ return cropToRange(surface, o, { id, margin: extra.room ?? extra.margin ?? given ?? surface.crowdMargin, panel: score });
403
+ }
404
+ /** The fence of the ballpark the options ask for, as the drawing has it, and how far behind home plate the backstop stands. */
405
+ function parkFence(o) {
406
+ const unit = lengthUnit(o);
407
+ const { league } = chooseLeague(o);
408
+ if (league?.family === "baseball") {
409
+ const diamond = diamondOf(league, o.year);
410
+ return { points: fenceFor(parkById(o.park), feetFence(o.fence, unit), o.year, diamondFence(diamond)), backstop: diamond.backstop };
411
+ }
412
+ return { points: fenceFor(parkById(o.park), feetFence(o.fence, unit), o.year), backstop: MLB_DIAMOND.backstop };
413
+ }
414
+ /** The stands round a surface, laid out from its box (or, for a ballpark, from its fence). */
415
+ function standsFor(o, surface, crowd) {
416
+ const { sport } = chooseLeague(o);
417
+ const awaySide = resolveHomeSide({ ...o, sport }) === "left" ? "right" : "left";
418
+ if (sport === "mlb") {
419
+ const { points, backstop } = parkFence(o);
420
+ // The walls that have seats on top of them: the ones the package knows (Fenway Park's Green Monster), or, asked for, every wall of 25 feet or more.
421
+ const park = parkById(o.park);
422
+ const walls = park === undefined || crowd.wallSeats === false ? [] : parkWallsIn(park, o.year).filter((w) => (crowd.wallSeats === true ? w.height_ft >= 25 : w.label !== undefined && WALL_SEATS[park.id] === w.label));
423
+ return parkLayout({ fence: fenceOutline(points), backstop, crowd, seatsOnWalls: walls.map((w) => ({ from: w.from, to: w.to, height: w.height_ft })) });
424
+ }
425
+ // A dojo's crowd sits in its gallery, from the room's balcony data (in feet here); the arenas (the Budokan, the Kokugikan) keep their bowls.
426
+ const hall = sport === "dojo" ? dojoGallery(o) : undefined;
427
+ const gallery = hall === undefined ? undefined : { offset: hall.offset / 0.3048, depth: hall.depth / 0.3048, height: [hall.height[0] / 0.3048, hall.height[1] / 0.3048] };
428
+ return crowdLayout({ sport, box: surface.whole ?? surface.box ?? { x: 0, y: 0, w: 100, h: 100 }, crowd, awaySide, ground: o.ground, arena: o.budo?.hall === "budokan", ...(gallery === undefined ? {} : { gallery }) });
429
+ }
430
+ /** True when the drawing is asked to be small: `detail: "simple"` or a `width` under 480 pixels. */
431
+ const smallDrawing = (o) => {
432
+ const w = typeof o.width === "number" ? o.width : typeof o.width === "string" && /^\s*\d+(?:\.\d+)?(?:px)?\s*$/.test(o.width) ? parseFloat(o.width) : undefined;
433
+ return o.detail === "simple" || (w !== undefined && w < 480);
434
+ };
435
+ /** A surface with the stands and the crowd round it: the ground is made as wide as the stands need, then the stands are laid on it under the surface. */
436
+ function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
437
+ let margin = extra.margin;
438
+ if (!(typeof o.margin === "number" && Number.isFinite(o.margin) && o.margin >= 0)) {
439
+ // How much ground the stands need beyond what the surface has with none, then the surface is made again with it.
440
+ const probe = buildWhole({ ...o, margin: 0 }, portrait, id, score, scored, { ...extra, margin: undefined });
441
+ const stands = standsFor(o, probe, crowd);
442
+ const view = wideView(probe);
443
+ const have = { left: stands.box.x - view.x, right: view.x + view.w - stands.box.x - stands.box.w, top: stands.box.y - view.y, bottom: view.y + view.h - stands.box.y - stands.box.h };
444
+ const need = Math.max(stands.reach.left - have.left, stands.reach.right - have.right, stands.reach.top - have.top, stands.reach.bottom - have.bottom);
445
+ margin = Math.max(margin ?? 0, need + 6);
446
+ }
447
+ const surface = buildWhole(o, portrait, id, score, scored, { ...extra, margin });
448
+ const stands = standsFor(o, surface, crowd);
449
+ const seats = stands.seats;
450
+ const ground = groundById(o.ground)?.capacity;
451
+ const fine = crowd.detail === "fine" || (crowd.detail === "auto" && !smallDrawing(o));
452
+ const first = /^<[^>]*\/>/.exec(surface.base)?.[0] ?? "";
453
+ const bg = /class="([^"]*)"/.exec(first)?.[1] ?? "ky-apron";
454
+ // A gallery is sparsely taken unless the options say how full.
455
+ const taken = stands.gallery === true && crowd.density === undefined && crowd.attendance === undefined ? 0.4 : densityOf(crowd, seats, ground);
456
+ const drawn = drawCrowd(stands, { id, crowd, density: taken, fine, ground: bg });
457
+ surface.base = first + drawn.markup + surface.base.slice(first.length);
458
+ surface.defs += drawn.defs;
459
+ surface.lines += drawn.over;
460
+ surface.crowdMargin = margin;
461
+ return surface;
279
462
  }
280
463
  /** The custom properties a drawing sets on its root, from the options' colours. */
281
464
  function colourStyle(o, teams, features) {
@@ -287,6 +470,13 @@ function colourStyle(o, teams, features) {
287
470
  };
288
471
  for (const [key, property] of Object.entries(COLOR_PROPERTIES))
289
472
  set(property, o.colors?.[key]);
473
+ // A dojo's own colours (and its era's): the mats, the second tone, the border, the heri, the wood and the walls.
474
+ if (o.sport === "dojo" && o.dojo !== undefined) {
475
+ const c = dojoSpecOf(o.dojo).colours;
476
+ if (c !== undefined)
477
+ for (const [key, property] of [["mat", "--ky-surface"], ["alt", "--ky-surface-alt"], ["border", "--ky-red"], ["heri", "--ky-bud-heri"], ["floor", "--ky-bud-floor"], ["wall", "--ky-boards"]])
478
+ set(property, c[key]);
479
+ }
290
480
  if (o.court !== undefined)
291
481
  for (const [key, property] of Object.entries(COURT_PROPERTIES))
292
482
  set(property, o.court[key]);
@@ -315,6 +505,23 @@ function colourStyle(o, teams, features) {
315
505
  props.push(`--ky-left-ink:${leftInk}`);
316
506
  if (rightInk !== undefined)
317
507
  props.push(`--ky-right-ink:${rightInk}`);
508
+ // A hardwood floor's border behind each baseline: the court's colour for it, else the team's, else the border's.
509
+ if (o.court?.hardwood !== undefined && o.court.hardwood !== false) {
510
+ const ends = pair(o.court.baselineColor);
511
+ const endLeft = safeColour(ends?.[0]) ?? left;
512
+ const endRight = safeColour(ends?.[1]) ?? right;
513
+ if (endLeft !== undefined)
514
+ props.push(`--ky-hw-end-left:${endLeft}`);
515
+ if (endRight !== undefined)
516
+ props.push(`--ky-hw-end-right:${endRight}`);
517
+ }
518
+ // The crowd's colours: the home fans in the home team's colour and the visitors in the other's, or the colours the crowd names.
519
+ const crowd = crowdOf(o);
520
+ if (crowd !== undefined) {
521
+ const homeLeft = resolveHomeSide({ ...o, sport: chooseLeague(o).sport }) === "left";
522
+ props.push(`--ky-cr-home:${safeColour(crowd.home) ?? (homeLeft ? left : right) ?? `var(--ky-${homeLeft ? "left" : "right"})`}`);
523
+ props.push(`--ky-cr-away:${safeColour(crowd.away) ?? (homeLeft ? right : left) ?? `var(--ky-${homeLeft ? "right" : "left"})`}`);
524
+ }
318
525
  return props.join(";");
319
526
  }
320
527
  /** The round mark of a team, when its own `mark` or the score's `markShape` asks for one: the fit, the colour of its disc and the ink of its initials. */
@@ -324,17 +531,18 @@ function roundOf(team, markShape) {
324
531
  return undefined;
325
532
  return { round: true, fit: team.markFit === "contain" ? "contain" : undefined, color: safeColour(team.color), ink: safeColour(team.textColor) };
326
533
  }
327
- function teamView(team, side, role, winner, uppercase, sport, markShape) {
534
+ function teamView(team, side, role, winner, uppercase, sport, markShape, theme) {
328
535
  const name = String(team.name ?? "");
329
- const written = fieldScoreText(sport, team);
536
+ const abbr = (team.abbr !== undefined && team.abbr.trim() !== "" ? team.abbr.trim() : abbreviate(name)).slice(0, 5);
537
+ const written = fieldScoreText(sport, team) ?? budoScoreText(sport, team);
330
538
  const score = written ?? (team.score === undefined || team.score === null || String(team.score).trim() === "" ? "–" : String(team.score));
331
539
  return {
332
540
  side,
333
541
  role,
334
542
  name: uppercase ? capitals(name) : name,
335
- abbr: (team.abbr !== undefined && team.abbr.trim() !== "" ? team.abbr.trim() : abbreviate(name)).slice(0, 5),
543
+ abbr,
336
544
  score,
337
- logo: safeLogo(team.logo),
545
+ logo: safeLogo(team.logo, { color: team.color, accent: team.textColor, initials: abbr.slice(0, 3), theme }),
338
546
  ...roundOf(team, markShape),
339
547
  state: stateOf(winner, role),
340
548
  ends: Array.isArray(team.ends) ? team.ends.map(endText) : undefined,
@@ -345,13 +553,52 @@ function teamView(team, side, role, winner, uppercase, sport, markShape) {
345
553
  serving: team.serving === true ? true : undefined,
346
554
  wickets: sport === "cricket" && team.wickets !== undefined && team.wickets !== null && String(team.wickets).trim() !== "" ? String(team.wickets).trim().slice(0, 2) : undefined,
347
555
  overs: sport === "cricket" && team.overs !== undefined && team.overs !== null && String(team.overs).trim() !== "" ? String(team.overs).trim().slice(0, 6) : undefined,
556
+ budo: isBudoSport(sport) ? budoView(team.budo) : undefined,
348
557
  innings: sport === "cricket" && Array.isArray(team.innings) ? team.innings.map((value) => (typeof value === "number" ? (Number.isFinite(value) ? String(value) : "") : typeof value === "string" ? value.trim().slice(0, 7) : "")) : undefined,
349
558
  };
350
559
  }
560
+ /** A team as the pageant lays it out. */
561
+ function pageantTeam(view, team) {
562
+ return { side: view.side, role: view.role, name: view.name, abbr: view.abbr, state: view.state, href: view.logo, fit: team.markFit === "contain" ? "contain" : "cover", ratio: ratioOf(team), color: safeColour(team.color), ink: safeColour(team.textColor) };
563
+ }
564
+ /** True when the drawing is asked to be small: its `width` is under 320 pixels. The pageant then leaves out its fabric, hairlines and name pills. */
565
+ function tiny(width) {
566
+ const px = typeof width === "number" ? width : typeof width === "string" && /^\d+(?:\.\d+)?(?:px)?$/.test(width.trim()) ? Number.parseFloat(width) : undefined;
567
+ return px !== undefined && Number.isFinite(px) && px < 320;
568
+ }
569
+ /** The teams that fill the first and the second place of a surface: a diamond's outfield is the visitors' and its infield the home team's, whichever side each is on. */
570
+ function pageantPair(surface, t) {
571
+ const l = pageantTeam(t.left, t.leftTeam);
572
+ const r = pageantTeam(t.right, t.rightTeam);
573
+ return surface.pageant?.byRole === true && l.role === "home" ? [r, l] : [l, r];
574
+ }
575
+ function pageantParts(o, surface, p, t) {
576
+ const [first, second] = pageantPair(surface, t);
577
+ const layout = pageantLayout({ pageant: p, surface, outline: outlineOf(o), left: first, right: second, turned: surface.turned, sport: t.sport, plate: t.plate });
578
+ if (layout === undefined)
579
+ return undefined;
580
+ // Cropped to a range, an emblem that does not lie wholly in what is shown is left out.
581
+ const shown = surface.whole === undefined ? undefined : surface.box;
582
+ if (layout.emblem !== undefined && shown !== undefined) {
583
+ const e = layout.emblem;
584
+ if (e.cx - e.r < shown.x || e.cx + e.r > shown.x + shown.w || e.cy - e.r < shown.y || e.cy + e.r > shown.y + shown.h)
585
+ delete layout.emblem;
586
+ }
587
+ const draw = { id: t.id, turned: surface.turned, simple: o.detail === "simple" || tiny(o.width), parts: t.parts };
588
+ const pictures = picturesMarkup(layout, p, draw);
589
+ const emblem = emblemMarkup(layout, p, draw);
590
+ return { pictures: pictures.markup, names: pictures.names, emblem: emblem.markup, defs: pictures.defs + emblem.defs, plateScale: layout.plateScale };
591
+ }
351
592
  const trimmed = (value) => String(Math.round(value * 100) / 100);
352
593
  /** What a league's surface is, in a sentence fragment: its size or its bases, from the league's own figures in the unit its rulebook uses. */
353
594
  function leagueAbout(league, year, language, sheet) {
354
595
  const words = KYOGIJO_STRINGS[language].league;
596
+ if (league.family === "skating")
597
+ return skatingAbout({ sport: "skating", league: league.id }, language);
598
+ if (league.family === "judo" || league.family === "karate" || league.family === "kendo" || league.family === "sumo" || league.family === "kyudo" || league.family === "dojo")
599
+ return budoAbout({ sport: league.family, league: league.id }, language);
600
+ if (league.family === "athletics")
601
+ return athleticsAbout({ sport: "athletics", league: league.id, athletics: sheet === undefined ? undefined : undefined }, language);
355
602
  // The field sports: their size is the surface drawn (the one the options ask for), in the unit their rulebook prints first.
356
603
  if (league.family === "lacrosse" || league.family === "rugby" || league.family === "afl" || league.family === "gaelic" || league.family === "cricket" || league.family === "fieldhockey" || league.family === "handball") {
357
604
  const sized = sheet ?? leagueSize(league.family, league.id);
@@ -370,7 +617,7 @@ function leagueAbout(league, year, language, sheet) {
370
617
  ? rinkOf(league, year)
371
618
  : league.family === "curling"
372
619
  ? (sheet ?? sheetOf(league))
373
- : league.family === "tennis" || league.family === "volleyball" || league.family === "badminton" || league.family === "pickleball" || league.family === "tabletennis"
620
+ : isNetLeague(league)
374
621
  ? (() => {
375
622
  const f = netOf(league);
376
623
  return { length: f.length, width: f.widthDoubles ?? f.width };
@@ -381,7 +628,29 @@ function leagueAbout(league, year, language, sheet) {
381
628
  })();
382
629
  const to = league.unit;
383
630
  const text = fill(words.size, { length: trimmed(convertLength(size.length, "ft", to)), width: trimmed(convertLength(size.width, "ft", to)), unit: words.units[to === "m" || to === "yd" ? to : "ft"] });
384
- return fill(league.family === "volleyball" && league.id === "fivb-beach" ? words.beachVolleyball : words[league.family], { size: text });
631
+ return fill(league.family === "volleyball" && league.id === "fivb-beach" ? words.beachVolleyball : league.family === "volleyball" && league.id === "wpv-sitting" ? words.sittingVolleyball : words[league.family], { size: text });
632
+ }
633
+ /** The football field a drawing is on, for a situation to know the yards between the goal lines and the downs a team has (the CFL's three). */
634
+ function footballFieldOf(o, sport, league) {
635
+ if (sport !== "nfl" && sport !== "cfl" && sport !== "ifaf")
636
+ return undefined;
637
+ const spec = league?.family === "football" ? fieldOf(league, o.year) : sport === "ifaf" ? FOOTBALL.ifaf : footballIn(sport, o.year);
638
+ return { yards: spec.length / 3, downs: spec.id === "cfl" ? 3 : 4 };
639
+ }
640
+ /** The situation the options ask for, read: which team attacks, which end, what is lit. `undefined` for none, for a sport that has no situation and for a special game. */
641
+ function situationOf(o) {
642
+ if (o.situation === undefined || o.host !== undefined)
643
+ return undefined;
644
+ const { sport, league } = chooseLeague(o);
645
+ const ctx = {
646
+ sport,
647
+ homeSide: resolveHomeSide({ ...o, sport }),
648
+ direction: o.direction,
649
+ football: footballFieldOf(o, sport, league),
650
+ names: isScore(o) ? { home: String(o.home.name ?? ""), away: String(o.away.name ?? "") } : undefined,
651
+ league: league?.id,
652
+ };
653
+ return readSituation(o.situation, ctx);
385
654
  }
386
655
  /** A drawing's title and description, in a language. */
387
656
  export function describe(o) {
@@ -398,15 +667,25 @@ export function describe(o) {
398
667
  const ground = pitch?.format === "11" ? groundById(o.ground) : undefined;
399
668
  const groundName = ground === undefined ? undefined : groundNameIn(ground, o.year, language);
400
669
  // A league other than the sport's own is named by its rulebook's, and its description is made from its figures.
401
- const surfaceName = groundName !== undefined ? (language === "ja" ? `${groundName}のピッチ` : `${groundName}, soccer pitch`) : league !== undefined && parkName === undefined ? league.name[language] : parkName === undefined ? (pitch?.format === "futsal" ? words.soccer.futsal : words.surface[sport]) : language === "ja" ? `${parkName}のグラウンド` : `${parkName}, baseball field`;
670
+ const trackGround = sport === "athletics" ? groundById(o.ground) : undefined;
671
+ const trackGroundName = trackGround === undefined ? undefined : groundNameIn(trackGround, o.year, language);
672
+ // An arena names its rink or its court.
673
+ const arena = (sport === "nhl" || sport === "nba") && league === undefined ? arenaById(o.arena) : undefined;
674
+ const arenaName = arena === undefined ? undefined : arenaNameIn(arena, o.year, language);
675
+ const arenaSize = arena === undefined || arenaName === undefined ? undefined : sport === "nhl" ? arenaRinkIn(arena, o.year) : arenaCourtIn(arena, o.year);
676
+ const surfaceName = arenaName !== undefined ? fill(sport === "nhl" ? words.said.arenaRink : words.said.arenaCourt, { arena: arenaName }) : trackGroundName !== undefined ? fill(words.athletics.ground, { ground: trackGroundName }) : groundName !== undefined ? (language === "ja" ? `${groundName}のピッチ` : `${groundName}, soccer pitch`) : league !== undefined && parkName === undefined ? league.name[language] : parkName === undefined ? (pitch?.format === "futsal" ? words.soccer.futsal : words.surface[sport]) : language === "ja" ? `${parkName}のグラウンド` : `${parkName}, baseball field`;
402
677
  const { stop, colon, gap } = words.said;
403
678
  const sentence = (text) => (/[.。]$/.test(text) ? text : `${text}${stop}`);
404
679
  const metres = (value) => String(Math.round(value * 10) / 10);
405
680
  const plan = sport === "curling" ? sheetFor(o) : undefined;
406
681
  // A field sport: the surface as drawn, at the size the options ask for.
407
682
  const field = isFieldSport(sport) ? fieldPlan({ ...o, sport }) : undefined;
408
- const aboutText = field !== undefined ? leagueAbout(league ?? LEAGUES[sport][0], o.year, language, { length: field.length, width: field.width }) : plan !== undefined ? leagueAbout(plan.league, o.year, language, plan.spec) : league !== undefined ? leagueAbout(league, o.year, language) : pitch === undefined ? words.about[sport] : fill(pitch.format === "futsal" ? words.soccer.aboutFutsal : words.about.soccer, { length: metres(pitch.length), width: metres(pitch.width) });
409
- const about = `${words.said.on}${colon}${aboutText}${stop}`;
683
+ const pools = isPoolSport(sport) ? poolDescription({ sport, pool: o.pool }, language) : undefined;
684
+ const poolLine = pools === undefined ? "" : pools.after.map((line) => `${gap}${line}`).join("");
685
+ // An arena of a size of its own says what it is at that size.
686
+ const arenaAbout = arenaSize === undefined ? undefined : fill(sport === "nhl" ? words.said.arenaRinkAbout : words.said.arenaCourtAbout, { length: metres(arenaSize.length), width: metres(arenaSize.width), lengthM: metres(arenaSize.length * 0.3048), widthM: metres(arenaSize.width * 0.3048) });
687
+ const aboutText = arenaAbout !== undefined ? arenaAbout : pools !== undefined ? pools.about : isBudoSport(sport) ? budoAbout(o, language) : sport === "skating" ? skatingAbout(o, language) : sport === "athletics" ? athleticsAbout(o, language) : field !== undefined ? leagueAbout(league ?? LEAGUES[sport][0], o.year, language, { length: field.length, width: field.width }) : plan !== undefined ? leagueAbout(plan.league, o.year, language, plan.spec) : league !== undefined ? leagueAbout(league, o.year, language) : pitch === undefined ? words.about[sport] : fill(pitch.format === "futsal" ? words.soccer.aboutFutsal : words.about.soccer, { length: metres(pitch.length), width: metres(pitch.width) });
688
+ const about = `${words.said.on}${colon}${aboutText}${stop}${sport === "athletics" ? athleticsSentences(o, language) : sport === "skating" ? skatingSentences(o, language) : isBudoSport(sport) ? budoSentences(o, language) : ""}`;
410
689
  const halfLine = pitch !== undefined && !isScore(o) && (o.half === "left" || o.half === "right") ? `${gap}${words.soccer.half}` : "";
411
690
  // What the ground says about the size: that it is the published one, or that the ground has none and the international pitch is drawn.
412
691
  const year = typeof o.year === "number" && Number.isFinite(o.year) ? o.year : undefined;
@@ -417,7 +696,9 @@ export function describe(o) {
417
696
  // A park's walls and quirks that are drawn are said too.
418
697
  const ballpark = park === undefined ? undefined : ballparkFor(o);
419
698
  const special = specialText(o, language, surfaceName);
420
- const parkLine = (parkName === undefined ? `${groundLine}${halfLine}` : `${gap}${fill(words.said.park, { park: parkName })}${officially(parkOfficial)}${ballpark === undefined ? "" : ballparkSentences(ballpark).map((line) => `${gap}${line}`).join("")}`) + (special === undefined ? "" : `${gap}${special.text}`);
699
+ // What the arena says about the size: that it is the one its sources publish, or that its rink is said to have been shorter and no size is given.
700
+ const arenaLine = arenaName === undefined ? "" : `${arenaSize !== undefined ? `${gap}${fill(words.said.arenaSize, { arena: arenaName })}` : sport === "nhl" && arena?.rinkUnknown === true ? `${gap}${fill(words.said.arenaUnknown, { arena: arenaName })}` : ""}${officially(arena === undefined ? undefined : arenaOfficialName(arena, language, year))}`;
701
+ const parkLine = (parkName === undefined ? `${groundLine}${halfLine}${arenaLine}` : `${gap}${fill(words.said.park, { park: parkName })}${officially(parkOfficial)}${ballpark === undefined ? "" : ballparkSentences(ballpark).map((line) => `${gap}${line}`).join("")}`) + (special === undefined ? "" : `${gap}${special.text}`);
421
702
  // A drawing of a past season says which: the markings are those of that season.
422
703
  const dated = typeof o.year === "number" && Number.isFinite(o.year);
423
704
  const eraLine = dated && league !== undefined
@@ -428,11 +709,25 @@ export function describe(o) {
428
709
  ? `${gap}${fill(words.said.era, { season: seasonLabel(sport, o.year) })}`
429
710
  : "";
430
711
  // A range says what is shown; a curling score shows all of the sheet, whatever the range.
431
- const rangeLine = isScore(o) && sport === "curling" ? "" : describeRange(o, language, o.layers !== undefined && o.layers.length > 0);
712
+ const hasData = o.layers !== undefined && o.layers.length > 0;
713
+ // A folded sheet says what is enlarged and where, in place of the range's own sentence.
714
+ const foldedWords = words.ranges.folded;
715
+ const rangeLine = isScore(o) && sport === "curling"
716
+ ? ""
717
+ : plan?.show === "folded"
718
+ ? `${gap}${[foldedWords[isPortrait(o) ? "tall" : "wide"], foldedWords[plan.panels], hasData ? foldedWords.data : ""].filter((part) => part !== "").join(gap)}`
719
+ : describeRange(o, language, hasData);
720
+ // A situation says what the pill, the lit bases or the line of scrimmage show.
721
+ const read = situationOf(o);
722
+ const situationLine = read === undefined ? "" : situationSentences(read, language, isScore(o) ? { home: o.home.name, away: o.away.name } : undefined).map((line) => `${gap}${line}`).join("");
432
723
  if (!isScore(o))
433
- return { title: o.title ?? special?.title ?? surfaceName, description: o.description ?? `${about}${parkLine}${eraLine}${rangeLine}` };
724
+ return { title: o.title ?? special?.title ?? surfaceName, description: o.description ?? `${about}${parkLine}${eraLine}${rangeLine}${situationLine}${poolLine}` };
434
725
  const winner = winnerOf({ ...o, sport });
435
- const s = (t) => fieldScoreText(sport, t) ?? (t.score === undefined || String(t.score) === "" ? "–" : String(t.score));
726
+ // A pageant says what lies on each half, and the emblem in the middle.
727
+ const pageant = pageantOf(o);
728
+ const pg = KYOGIJO_STRINGS[language].pageant;
729
+ const pageantLine = pageant === undefined ? "" : `${gap}${pageant.style === "stage" ? pg.stage : pg.carpet}${pageant.emblem === undefined ? "" : `${gap}${pageant.emblem.name === undefined ? pg.emblem : fill(pg.emblemNamed, { name: pageant.emblem.name })}`}`;
730
+ const s = (t) => fieldScoreText(sport, t) ?? budoScoreText(sport, t) ?? (t.score === undefined || String(t.score) === "" ? "–" : String(t.score));
436
731
  // The sentence follows the drawing: the team on the left is first. The words' {away} and {home} are the first and the second team named.
437
732
  const order = resolveHomeSide({ ...o, sport }) === "left" ? [o.home, o.away] : [o.away, o.home];
438
733
  const line = fill(words.scoreTitle, { away: order[0].name, awayScore: s(order[0]), home: order[1].name, homeScore: s(order[1]) });
@@ -463,12 +758,12 @@ export function describe(o) {
463
758
  const tb = endText(team.tiebreaks?.[i]);
464
759
  return `${endText(value) || "–"}${tb === "" ? "" : ` (${tb})`}`;
465
760
  }).join(words.layers.listSep);
466
- const netLine = !isNetSport(sport)
761
+ const netLine = !hasSetsBoard(sport)
467
762
  ? ""
468
763
  : order
469
764
  .flatMap((team) => [
470
765
  team.serving === true ? fill(net.serving, { name: team.name }) : "",
471
- Array.isArray(team.sets) && team.sets.length > 0 ? fill(sport === "badminton" || sport === "pickleball" || sport === "tabletennis" ? net.byGame : net.bySet, { name: team.name, list: setList(team) }) : "",
766
+ Array.isArray(team.sets) && team.sets.length > 0 ? fill(BOARD[sport] === "games" ? net.byGame : net.bySet, { name: team.name, list: setList(team) }) : "",
472
767
  team.points !== undefined && team.points !== null && String(team.points).trim() !== "" ? fill(net.gamePoints, { name: team.name, points: String(team.points).trim() }) : "",
473
768
  ])
474
769
  .filter((part) => part !== "")
@@ -493,7 +788,7 @@ export function describe(o) {
493
788
  : "";
494
789
  return {
495
790
  title: o.title ?? (status === undefined ? line : `${line}${words.said.sep}${status}`),
496
- description: o.description ?? `${extra === "" ? "" : `${sentence(extra)}${gap}`}${sentence(line)}${result}${tied}${curlingLine}${netLine}${fieldLine}${gap}${about}${parkLine}${eraLine}${rangeLine}`.trim(),
791
+ description: o.description ?? `${extra === "" ? "" : `${sentence(extra)}${gap}`}${sentence(line)}${result}${tied}${curlingLine}${netLine}${fieldLine}${isBudoSport(sport) ? budoScoreSentences(sport, order, language) : ""}${gap}${about}${parkLine}${eraLine}${rangeLine}${situationLine}${pageantLine}${poolLine}`.trim(),
497
792
  };
498
793
  }
499
794
  /**
@@ -513,8 +808,14 @@ export function describe(o) {
513
808
  * ```
514
809
  */
515
810
  export function drawKyogijo(o) {
811
+ return withEmblems(o.emblems, () => drawnKyogijo(o));
812
+ }
813
+ function drawnKyogijo(o) {
516
814
  if (o.host !== undefined && o.range !== undefined)
517
815
  return drawKyogijo({ ...o, range: undefined });
816
+ // A special game is a surface set inside another, drawn to scale with no stands.
817
+ if (o.host !== undefined && o.crowd !== undefined)
818
+ return drawKyogijo({ ...o, crowd: undefined });
518
819
  const language = o.language === "ja" ? "ja" : "en";
519
820
  // The sport as drawn: a league that is a sport of its own (`league: "cfl"` on an `nfl`) is that sport.
520
821
  const { sport } = chooseLeague(o);
@@ -526,7 +827,23 @@ export function drawKyogijo(o) {
526
827
  // A tall curling sheet is 30 feet wide on the page and a strip under it would be unreadable: its compact size is the board on the ice.
527
828
  const asked = isScore(o) ? (o.size === "compact" || o.size === "badge" ? o.size : "full") : "full";
528
829
  let sized = asked === "compact" && sport === "curling" && portrait ? "full" : asked;
529
- const id = drawingId(o);
830
+ // A pageant that sends the score to the strip makes the drawing compact; a tall curling sheet cannot have a strip, so its board stays in the middle and the emblem is left out.
831
+ let pageant = isScore(o) ? pageantOf(o) : undefined;
832
+ if (pageant !== undefined && pageant.score === "strip") {
833
+ if (sport === "curling" && portrait)
834
+ pageant = { ...pageant, score: "centre", emblem: undefined };
835
+ else if (sized === "full")
836
+ sized = "compact";
837
+ }
838
+ // A lineup's players stand in the middle of the surface, where the score's panel would hide them: the score goes to the strip under the drawing, as a pageant's does, unless `score: "centre"` asks for it.
839
+ const lineupped = (o.layers ?? []).some((layer) => layer?.kind === "lineup");
840
+ const stripped = isScore(o) && lineupped && o.score !== "centre";
841
+ if (isScore(o) && pageant === undefined && sized === "full" && (o.score === "strip" || stripped) && !(sport === "curling" && portrait))
842
+ sized = "compact";
843
+ // The situation of the game (hockey's skaters, baseball's runners, football's down), decided once: the surface, the badge and the description draw from it.
844
+ const situation = situationOf(o);
845
+ // A situation that is not drawn (a sport that has none, nothing the sport can have in it) leaves the drawing, its ids included, as it was.
846
+ const id = drawingId(o.situation !== undefined && situation === undefined ? { ...o, situation: undefined } : o);
530
847
  const parts = o.parts === true;
531
848
  const homeSide = isScore(o) ? resolveHomeSide({ ...o, sport }) : "right";
532
849
  const awaySide = homeSide === "left" ? "right" : "left";
@@ -540,8 +857,8 @@ export function drawKyogijo(o) {
540
857
  if (isScore(o)) {
541
858
  winner = winnerOf({ ...o, sport });
542
859
  status = statusText({ ...o, sport }, language);
543
- const home = teamView(o.home, homeSide, "home", winner, uppercase, sport, o.markShape);
544
- const away = teamView(o.away, awaySide, "away", winner, uppercase, sport, o.markShape);
860
+ const home = teamView(o.home, homeSide, "home", winner, uppercase, sport, o.markShape, o.theme === "light" || o.theme === "dark" ? o.theme : undefined);
861
+ const away = teamView(o.away, awaySide, "away", winner, uppercase, sport, o.markShape, o.theme === "light" || o.theme === "dark" ? o.theme : undefined);
545
862
  left = homeSide === "left" ? home : away;
546
863
  right = homeSide === "left" ? away : home;
547
864
  leftTeam = homeSide === "left" ? o.home : o.away;
@@ -555,16 +872,21 @@ export function drawKyogijo(o) {
555
872
  const featured = featureColours(sport, o.featureColors);
556
873
  const features = featured.length === 0 ? undefined : new Set(featured.map(([name]) => name));
557
874
  const walled = sport === "mlb" && ballparkDrawn(ballparkFor(o));
875
+ const hardwood = sport === "nba" && o.court?.hardwood !== undefined && o.court.hardwood !== false;
876
+ const crowded = crowdOf(o) !== undefined;
558
877
  const style = colourStyle(o, { left: leftTeam, right: rightTeam, home: isScore(o) ? o.home : undefined, away: isScore(o) ? o.away : undefined }, featured);
559
878
  const palette = isLook(o.look) ? o.look : undefined;
879
+ const budoLook = isBudoSport(sport) ? budoLookOf(sport, o.budo?.look ?? (sport === "dojo" ? dojoSpecOf(o.dojo).look : undefined)) : undefined;
560
880
  let notes;
561
881
  const theme = o.theme === "light" || o.theme === "dark" ? o.theme : undefined;
562
882
  let viewBox;
563
883
  let body;
564
884
  let defs = "";
565
885
  let grounded = false;
886
+ let foldedSheet = false;
887
+ let pg;
566
888
  if (isScore(o) && left !== undefined && right !== undefined && sized === "badge") {
567
- const badge = badgeMarkup(left, right, { sport, status, parts, id });
889
+ const badge = badgeMarkup(left, right, { sport, status, parts, id, situation: situation === undefined ? undefined : badgeSituation(situation, language) });
568
890
  viewBox = `0 0 ${badge.w} ${badge.h}`;
569
891
  body = badge.markup;
570
892
  }
@@ -576,13 +898,28 @@ export function drawKyogijo(o) {
576
898
  const guest = buildSurface({ ...o, margin: 0 }, false, id, isScore(o) && sized === "full", isScore(o), more);
577
899
  return composeSpecial(o, guest, (host, hostId) => buildSurface(host, false, hostId, false, false, { features: undefined }), id);
578
900
  };
579
- let surface = make({ features });
901
+ let surface = make({ features, pageant: pageant !== undefined });
902
+ // The score of a pageant with no emblem and no board: the plate stays in the middle if the pictures lose little to it, else it goes to the strip and the surface is built again.
903
+ if (pageant !== undefined && pageant.score === "auto") {
904
+ let score = stripped ? "strip" : "centre";
905
+ if (!stripped && isScore(o) && left !== undefined && right !== undefined && leftTeam !== undefined && rightTeam !== undefined && sized === "full") {
906
+ const [first, second] = pageantPair(surface, { left, right, leftTeam, rightTeam });
907
+ score = autoScore({ pageant, surface, outline: outlineOf(o), left: first, right: second, turned: surface.turned, sport, plate: true });
908
+ }
909
+ pageant = { ...pageant, score };
910
+ if (score === "strip") {
911
+ sized = "compact";
912
+ surface = make({ features, pageant: true });
913
+ }
914
+ }
915
+ foldedSheet = surface.folded === true;
580
916
  // A range too small to hold the score's panel: the score goes to the strip under the drawing, as a compact card does.
581
917
  if (surface.plateFits === false) {
582
918
  sized = "compact";
583
- surface = buildSurface(o, portrait, id, false, true, { features });
919
+ surface = buildSurface(o, portrait, id, false, true, { features, pageant: pageant !== undefined });
584
920
  }
585
- if (annotated(o) && o.host === undefined) {
921
+ // A folded sheet has no margin for the annotations to stand in: they are not drawn on it.
922
+ if (annotated(o) && o.host === undefined && surface.folded !== true) {
586
923
  const teams = { home: isScore(o) ? o.home.name : undefined, away: isScore(o) ? o.away.name : undefined };
587
924
  notes = annotate({ o, surface, turned: surface.turned, words: KYOGIJO_STRINGS[language], teams });
588
925
  // The annotations need room in the margin: unless the options fix it, the surface is drawn again with a margin that holds them.
@@ -590,7 +927,7 @@ export function drawKyogijo(o) {
590
927
  const have = surface.ground ?? (surface.box === undefined ? 0 : surface.box.y - wideView(surface).y);
591
928
  const roomed = surface.room ?? have;
592
929
  if (o.margin === undefined && (notes.need > have + 0.01 || notes.need > roomed + 0.01)) {
593
- surface = make({ features, margin: notes.need > have + 0.01 ? notes.need : undefined, room: notes.need });
930
+ surface = make({ features, pageant: pageant !== undefined, margin: notes.need > have + 0.01 ? notes.need : undefined, room: notes.need });
594
931
  notes = annotate({ o, surface, turned: surface.turned, words: KYOGIJO_STRINGS[language], teams });
595
932
  }
596
933
  }
@@ -602,12 +939,35 @@ export function drawKyogijo(o) {
602
939
  const ground = laidOn(surface.base, o.background, id);
603
940
  grounded = ground.base !== surface.base;
604
941
  defs = surface.defs + ground.defs;
605
- let inner = ground.base;
942
+ // The situation: a tint under the lines, and the rest over them and the data (cut with the surface when it is cropped).
943
+ const sit = situation === undefined
944
+ ? undefined
945
+ : drawSituation(situation, surface, {
946
+ id,
947
+ language,
948
+ homeSide,
949
+ abbrs: left !== undefined && right !== undefined ? { [left.role]: left.abbr, [right.role]: right.abbr } : undefined,
950
+ plate: isScore(o) && sized === "full" && o.plate !== false,
951
+ strip: stripped && sized === "compact",
952
+ });
953
+ const cutSit = (markup) => (markup !== "" && surface.defs.includes(`id="${id}-rg"`) ? el("g", { "clip-path": `url(#${id}-rg)` }, markup) : markup);
954
+ let inner = ground.base + (sit === undefined ? "" : cutSit(sit.under));
955
+ if (sit !== undefined)
956
+ defs += sit.defs;
606
957
  let teams = "";
607
- if (isScore(o) && left !== undefined && right !== undefined && sized === "full") {
958
+ // A pageant lays each team's picture over its half, in place of the names painted at the ends.
959
+ if (pageant !== undefined && isScore(o) && left !== undefined && right !== undefined && leftTeam !== undefined && rightTeam !== undefined && (sized === "full" || sized === "compact")) {
960
+ pg = pageantParts(o, surface, pageant, { left, right, leftTeam, rightTeam, sport, id, plate: sized === "full", parts });
961
+ if (pg !== undefined) {
962
+ defs += pg.defs;
963
+ if (sized === "full" && pg.plateScale !== 1)
964
+ surface.plate = { ...surface.plate, w: surface.plate.w * pg.plateScale, h: surface.plate.h * pg.plateScale };
965
+ }
966
+ }
967
+ if (isScore(o) && left !== undefined && right !== undefined && sized === "full" && pg === undefined) {
608
968
  const onColour = surface.onColour ?? (sport === "nfl" || sport === "cfl" || sport === "ifaf" || sport === "soccer");
609
969
  // A curling sheet, the net sports' courts and a cricket ground have no place for the names: the board names the teams.
610
- if (sport !== "curling" && !isNetSport(sport) && sport !== "cricket") {
970
+ if (sport !== "curling" && !hasSetsBoard(sport) && sport !== "cricket" && sport !== "judo" && sport !== "karate" && sport !== "kendo" && sport !== "sumo") {
611
971
  // Cropped to a range, a team's name is written only where its place is in what is shown.
612
972
  if (shows(surface, surface.left))
613
973
  teams += bannerMarkup(surface.left, left, { turned: surface.turned, onColour, parts, id });
@@ -616,20 +976,43 @@ export function drawKyogijo(o) {
616
976
  }
617
977
  }
618
978
  // The teams are painted under the lines, unless the surface asks for them over (a panel that must hide them).
619
- inner += surface.bannersOver === true ? surface.lines + teams : teams + surface.lines;
979
+ if (pg !== undefined && pageant !== undefined) {
980
+ // A surface cropped with room for its annotations is clipped to the cut; the pictures are cut with it.
981
+ const cut = surface.defs.includes(`id="${id}-rg"`);
982
+ const laid = cut ? el("g", { "clip-path": `url(#${id}-rg)` }, pg.pictures) : pg.pictures;
983
+ inner += pageant.lines !== "over" ? surface.lines + laid + pg.names + pg.emblem : laid + surface.lines + pg.names + pg.emblem;
984
+ }
985
+ else
986
+ inner += surface.bannersOver === true ? surface.lines + teams : teams + surface.lines;
987
+ // A lineup's players stand over what the situation lights (a runner's base, a drive's arrows), so that none of them is hidden; any other data goes over it.
988
+ if (sit !== undefined && lineupped)
989
+ inner += cutSit(sit.over);
620
990
  // Data goes over the markings and under the score's panel.
621
- const layered = renderLayers(o, o.layers, surface.turned, surface.whole === undefined ? undefined : surface.box);
991
+ const layered = renderLayers(o, o.layers, surface.turned, surface.whole === undefined ? undefined : surface.box, surface.upright);
622
992
  inner += surface.frame === undefined || layered.svg === "" ? layered.svg : el("g", { transform: surface.frame }, layered.svg);
993
+ // A surface drawn in parts shows the data again in each enlarged part, with its own ids, hidden from a reader of the drawing (the strip says it). A colour bar sized
994
+ // against the whole sheet falls outside the panels' part of it and is cut away there; one given a `region` inside a panel's part is drawn there, large.
995
+ if (surface.panels !== undefined && layered.svg !== "" && o.layers !== undefined) {
996
+ surface.panels.forEach((panel, i) => {
997
+ const copy = renderLayers({ ...o, id: `${id}-f${i}` }, o.layers, surface.turned);
998
+ if (copy.svg !== "")
999
+ inner += el("g", { class: "ky-fold-data", "aria-hidden": "true", transform: panel.transform, "clip-path": `url(#${panel.clip})` }, copy.svg);
1000
+ });
1001
+ }
623
1002
  if (layered.summaries.length > 0 && o.description === undefined) {
624
1003
  const { colon, gap } = KYOGIJO_STRINGS[language].said;
625
1004
  description = `${said}${gap}${KYOGIJO_STRINGS[language].layers.data}${colon}${layered.summaries.join(gap)}`;
626
1005
  }
1006
+ if (sit !== undefined && !lineupped)
1007
+ inner += cutSit(sit.over);
1008
+ if (surface.over !== undefined)
1009
+ inner += surface.over;
627
1010
  if (notes !== undefined && notes.svg !== "") {
628
1011
  inner += notes.svg;
629
1012
  if (notes.summaries.length > 0 && o.description === undefined)
630
1013
  description = `${description}${KYOGIJO_STRINGS[language].said.gap}${notes.summaries.join(KYOGIJO_STRINGS[language].said.gap)}`;
631
1014
  }
632
- if (isScore(o) && left !== undefined && right !== undefined && sized === "full") {
1015
+ if (isScore(o) && left !== undefined && right !== undefined && sized === "full" && o.plate !== false) {
633
1016
  const foot = o.label !== undefined && o.label !== "" ? o.label : [status, o.date].filter((part) => part !== undefined && part !== "").join(" · ");
634
1017
  if (sport === "curling") {
635
1018
  const board = boardMarkup(surface.plate, left, right, { competition: o.competition, foot: foot === "" ? undefined : foot, total: KYOGIJO_STRINGS[language].said.total }, { turned: surface.turned, parts });
@@ -640,17 +1023,22 @@ export function drawKyogijo(o) {
640
1023
  const board = cricketBoardMarkup(surface.plate, left, right, { competition: o.competition, foot: foot === "" ? undefined : foot, innings: words.innings, total: words.total, overs: words.overs }, { parts });
641
1024
  inner += el("g", { transform: `translate(${num(surface.plate.cx)} ${num(surface.plate.cy)})${surface.turned ? " rotate(-90)" : ""}` }, board);
642
1025
  }
643
- else if (isNetSport(sport)) {
644
- const board = setsBoardMarkup(surface.plate, left, right, { competition: o.competition, foot: foot === "" ? undefined : foot, head: KYOGIJO_STRINGS[language].net[sport === "badminton" || sport === "pickleball" || sport === "tabletennis" ? "games" : "sets"], points: KYOGIJO_STRINGS[language].net.points }, { parts, least: minSets(sport, chooseLeague(o).league?.id) });
1026
+ else if (isBudoSport(sport) && sport !== "kyudo" && sport !== "dojo") {
1027
+ const b = KYOGIJO_STRINGS[language].budo.board;
1028
+ const board = budoBoardMarkup(sport, surface.plate, left, right, { competition: o.competition, foot: foot === "" ? undefined : foot, wazaari: b.wazaari, yuko: b.yuko, shido: b.shido, senshu: b.senshu, chui: b.chui, kimarite: b.kimarite, techniques: b.techniques, kimariteName: (key) => kimariteName(key, language) }, { parts });
1029
+ inner += el("g", { transform: `translate(${num(surface.plate.cx)} ${num(surface.plate.cy)})${surface.turned ? " rotate(-90)" : ""}` }, board);
1030
+ }
1031
+ else if (hasSetsBoard(sport)) {
1032
+ const board = setsBoardMarkup(surface.plate, left, right, { competition: o.competition, foot: foot === "" ? undefined : foot, head: KYOGIJO_STRINGS[language].net[BOARD[sport] === "games" ? "games" : "sets"], points: KYOGIJO_STRINGS[language].net.points }, { parts, least: minSets(sport, chooseLeague(o).league?.id) });
645
1033
  inner += el("g", { transform: `translate(${num(surface.plate.cx)} ${num(surface.plate.cy)})${surface.turned ? " rotate(-90)" : ""}` }, board);
646
1034
  }
647
1035
  else
648
1036
  inner += plateMarkup(surface.plate, left, right, { competition: o.competition, foot: foot === "" ? undefined : foot }, { turned: surface.turned, parts, surfaceSport: sport });
649
1037
  }
650
- const group = el("g", { class: "ky-surface-group", transform: surface.turn, part: parts ? "surface" : undefined }, inner);
1038
+ const group = el("g", { class: "ky-surface-group", transform: surface.turn, part: parts ? "surface" : undefined, ...(o.pointer === true ? fieldAttributes({ ...o, sport }) : {}) }, inner);
651
1039
  const v = surface.view;
652
1040
  if (isScore(o) && left !== undefined && right !== undefined && sized === "compact") {
653
- const badge = badgeMarkup(left, right, { sport, status, parts, id });
1041
+ const badge = badgeMarkup(left, right, { sport, status, parts, id, situation: situation === undefined ? undefined : badgeSituation(situation, language) });
654
1042
  const scale = v.w / badge.w;
655
1043
  const gap = badge.h * scale * 0.12;
656
1044
  body = group + el("g", { class: "ky-strip", transform: `translate(${num(v.x)} ${num(v.y + v.h + gap)}) scale(${scale.toFixed(4)})` }, badge.markup);
@@ -671,19 +1059,21 @@ export function drawKyogijo(o) {
671
1059
  "data-league": o.league === undefined || o.league === "" ? undefined : o.league,
672
1060
  "data-range": isScore(o) && sport === "curling" ? undefined : rangeLabel(o),
673
1061
  "data-event": o.host !== undefined && typeof o.event === "string" ? safeClass(o.event) : undefined,
674
- "data-look": look,
1062
+ "data-look": look ?? budoLook,
675
1063
  "data-size": sized,
676
1064
  "data-orientation": portrait ? "portrait" : "landscape",
677
1065
  "data-theme": theme,
678
1066
  "data-palette": palette,
1067
+ "data-track": sport === "athletics" ? o.athletics?.track : undefined,
679
1068
  "data-winner": isScore(o) ? winner : undefined,
1069
+ "data-pageant": pg === undefined ? undefined : pageant?.style,
680
1070
  "data-version": VERSION,
681
1071
  width: safeLength(o.width),
682
1072
  height: safeLength(o.height),
683
1073
  part: parts ? "svg" : undefined,
684
1074
  style: style === "" ? undefined : style,
685
1075
  }, `<title id="${titleId}">${esc(title)}</title><desc id="${descId}">${esc(description)}</desc>` +
686
- (o.style === "none" ? "" : `<style>${KYOGIJO_BASE_STYLE}${left?.round === true || right?.round === true ? `\n${KYOGIJO_MARK_STYLE}` : ""}${sport === "curling" ? `\n${KYOGIJO_CURLING_STYLE}` : ""}${plan === undefined ? "" : `\n${courtStyle(plan.sport, plan.look)}`}${fieldsStyle(sport) === "" ? "" : `\n${fieldsStyle(sport)}`}${palette === undefined ? "" : `\n${lookStyle(palette)}`}${walled ? `\n${KYOGIJO_WALLS_STYLE}` : ""}${features === undefined ? "" : `\n${featureStyle(sport, features)}`}${notes === undefined || notes.svg === "" ? "" : `\n${KYOGIJO_NOTE_STYLE}`}${grounded ? `\n${KYOGIJO_BACKGROUND_STYLE}` : ""}${o.host === undefined ? "" : `\n${specialStyle(hostSport(o.host))}`}</style>`) +
1076
+ (o.style === "none" ? "" : `<style>${KYOGIJO_BASE_STYLE}${left?.round === true || right?.round === true || (pg !== undefined && pageant?.style === "stage") || (pg !== undefined && pageant?.emblem?.shape !== "plain" && pageant?.emblem !== undefined) ? `\n${KYOGIJO_MARK_STYLE}` : ""}${pg === undefined ? "" : `\n${KYOGIJO_PAGEANT_STYLE}${pageant?.animate === undefined || pageant.animate === "none" ? "" : `\n${KYOGIJO_PAGEANT_MOTION}`}`}${sport === "curling" ? `\n${KYOGIJO_CURLING_STYLE}` : ""}${foldedSheet ? `\n${KYOGIJO_FOLD_STYLE}` : ""}${isPoolSport(sport) ? `\n${o.pool?.venue !== undefined ? KYOGIJO_POOL_STYLE : poolStyle({ sport, ...commonOf(o.pool ?? {}), race: o.pool?.race !== undefined, section: o.pool?.section !== undefined && o.pool.section !== false })}` : ""}${plan === undefined ? "" : `\n${courtStyle(plan.sport, plan.look)}`}${fieldsStyle(sport) === "" ? "" : `\n${fieldsStyle(sport)}`}${athleticsStyle(sport) === "" ? "" : `\n${athleticsStyle(sport)}`}${skatingStyle(sport) === "" ? "" : `\n${skatingStyle(sport)}`}${budoLook === undefined ? "" : `\n${budoStyle(sport, budoLook)}`}${palette === undefined ? "" : `\n${lookStyle(palette)}`}${athleticsTrackRules(sport, o.athletics?.track) === "" ? "" : `\n${athleticsTrackRules(sport, o.athletics?.track)}`}${walled ? `\n${KYOGIJO_WALLS_STYLE}` : ""}${hardwood ? `\n${KYOGIJO_HARDWOOD_STYLE}` : ""}${features === undefined ? "" : `\n${featureStyle(sport, features)}`}${notes === undefined || notes.svg === "" ? "" : `\n${KYOGIJO_NOTE_STYLE}`}${grounded ? `\n${KYOGIJO_BACKGROUND_STYLE}` : ""}${crowded ? `\n${KYOGIJO_CROWD_STYLE}` : ""}${crowded && sport === "dojo" ? KYOGIJO_BUDO_GALLERY_STYLE : ""}${situation === undefined ? "" : `\n${KYOGIJO_SITUATION_STYLE}`}${o.host === undefined ? "" : `\n${specialStyle(hostSport(o.host))}`}</style>`) +
687
1077
  (defs === "" ? "" : `<defs>${defs}</defs>`) +
688
1078
  body);
689
1079
  return root;
@@ -703,3 +1093,21 @@ export function drawScore(options) {
703
1093
  export function drawBadge(options) {
704
1094
  return drawKyogijo({ ...options, size: "badge" });
705
1095
  }
1096
+ /**
1097
+ * The stands round a surface as data: the rings of seats with their outlines, heights and rake, the blocks of visitors and the places left open, in feet in the surface's
1098
+ * wide frame, so that another view (a 3D one) can raise them. `undefined` when the options ask for no crowd (or for a special game, which has none).
1099
+ * @example
1100
+ * ```ts
1101
+ * import { crowdStands } from "@johnmorrisdotca/kyogijo-stadiums";
1102
+ * const stands = crowdStands({ sport: "soccer", crowd: true });
1103
+ * console.log(stands?.rings.map((ring) => ring.id).join(" "), stands?.shape, stands?.stadium);
1104
+ * // lower upper bowl purpose
1105
+ * ```
1106
+ */
1107
+ export function crowdStands(o) {
1108
+ const crowd = crowdOf(o);
1109
+ if (crowd === undefined)
1110
+ return undefined;
1111
+ const probe = buildWhole({ ...o, margin: 0 }, false, drawingId(o), false, false, { features: undefined });
1112
+ return standsFor(o, probe, crowd);
1113
+ }