cuju-render 0.1.0__py3-none-any.whl

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 (382) hide show
  1. cuju/render/__init__.py +759 -0
  2. cuju/render/__main__.py +1065 -0
  3. cuju/render/_export/__init__.py +3 -0
  4. cuju/render/_export/feed.py +542 -0
  5. cuju/render/_export/sizes.py +157 -0
  6. cuju/render/_export/video.py +1618 -0
  7. cuju/render/_export/video_jobs.py +408 -0
  8. cuju/render/_format/__init__.py +3 -0
  9. cuju/render/_format/annotations.py +235 -0
  10. cuju/render/_format/codec.py +307 -0
  11. cuju/render/_format/keep.py +121 -0
  12. cuju/render/_format/recording.py +860 -0
  13. cuju/render/_format/writer.py +54 -0
  14. cuju/render/_guide/__init__.py +3 -0
  15. cuju/render/_guide/doctor.py +523 -0
  16. cuju/render/_guide/help.py +62 -0
  17. cuju/render/_guide/tutorial.py +254 -0
  18. cuju/render/_http/__init__.py +3 -0
  19. cuju/render/_http/commands.py +57 -0
  20. cuju/render/_http/headers.py +103 -0
  21. cuju/render/_http/launch.py +500 -0
  22. cuju/render/_http/launchers.py +408 -0
  23. cuju/render/_http/pwa.py +128 -0
  24. cuju/render/_http/server.py +2105 -0
  25. cuju/render/_http/window.py +360 -0
  26. cuju/render/_moments/__init__.py +3 -0
  27. cuju/render/_moments/captions.py +284 -0
  28. cuju/render/_moments/events.py +1123 -0
  29. cuju/render/_moments/search.py +694 -0
  30. cuju/render/_packages/__init__.py +3 -0
  31. cuju/render/_packages/glsl.py +746 -0
  32. cuju/render/_packages/loudness.py +334 -0
  33. cuju/render/_packages/pack.py +1959 -0
  34. cuju/render/_packages/presentation.py +1376 -0
  35. cuju/render/_packages/vocabulary.py +609 -0
  36. cuju/render/_playing/__init__.py +3 -0
  37. cuju/render/_playing/achievements.py +463 -0
  38. cuju/render/_playing/bench.py +609 -0
  39. cuju/render/_playing/challenges.py +1813 -0
  40. cuju/render/_playing/demo.py +323 -0
  41. cuju/render/_playing/draw.py +211 -0
  42. cuju/render/_playing/errors.py +20 -0
  43. cuju/render/_playing/game.py +645 -0
  44. cuju/render/_playing/limits.py +339 -0
  45. cuju/render/_playing/play.py +926 -0
  46. cuju/render/_playing/policy_worker.py +250 -0
  47. cuju/render/_playing/strict.py +810 -0
  48. cuju/render/_registry/__init__.py +3 -0
  49. cuju/render/_registry/clips.py +394 -0
  50. cuju/render/_registry/joints.py +108 -0
  51. cuju/render/_registry/teams.py +2058 -0
  52. cuju/render/_store/__init__.py +3 -0
  53. cuju/render/_store/home.py +70 -0
  54. cuju/render/_store/library.py +604 -0
  55. cuju/render/_store/samples.py +163 -0
  56. cuju/render/_text/__init__.py +3 -0
  57. cuju/render/_text/languages.py +637 -0
  58. cuju/render/_text/messages.py +1396 -0
  59. cuju/render/_tournaments/__init__.py +3 -0
  60. cuju/render/_tournaments/results.py +547 -0
  61. cuju/render/_tournaments/tournament.py +2742 -0
  62. cuju/render/_tournaments/tournament_cli.py +587 -0
  63. cuju/render/_tournaments/tournament_runner.py +1975 -0
  64. cuju/render/_version.py +6 -0
  65. cuju/render/_watch/__init__.py +3 -0
  66. cuju/render/_watch/grid.py +265 -0
  67. cuju/render/_watch/live.py +664 -0
  68. cuju/render/_watch/notebook.py +79 -0
  69. cuju/render/_watch/show.py +171 -0
  70. cuju/render/samples/sample-11v11.cjr +0 -0
  71. cuju/render/samples/sample-5v5.cjr +0 -0
  72. cuju/render/samples/teams/starter/main.py +33 -0
  73. cuju/render/scenarios/benchmark_full_game_11_vs_11_easy.py +46 -0
  74. cuju/render/scenarios/benchmark_full_game_11_vs_11_hard.py +46 -0
  75. cuju/render/scenarios/benchmark_full_game_11_vs_11_medium.py +46 -0
  76. cuju/render/scenarios/benchmark_full_game_5_vs_5_easy.py +34 -0
  77. cuju/render/scenarios/benchmark_full_game_5_vs_5_hard.py +34 -0
  78. cuju/render/scenarios/benchmark_full_game_5_vs_5_medium.py +34 -0
  79. cuju/render/web/assets/adboard_polygon.webp +0 -0
  80. cuju/render/web/assets/adboard_your_ad_here.webp +0 -0
  81. cuju/render/web/assets/animations/kaykit-rig-medium-combatmelee.glb +0 -0
  82. cuju/render/web/assets/animations/kaykit-rig-medium-general.glb +0 -0
  83. cuju/render/web/assets/animations/kaykit-rig-medium-movementadvanced.glb +0 -0
  84. cuju/render/web/assets/animations/kaykit-rig-medium-movementbasic.glb +0 -0
  85. cuju/render/web/assets/animations/kaykit-rig-medium-simulation.glb +0 -0
  86. cuju/render/web/assets/animations/kaykit-rig-medium-special.glb +0 -0
  87. cuju/render/web/assets/ball.glb +0 -0
  88. cuju/render/web/assets/challenges/3_vs_1_with_keeper.mp4 +0 -0
  89. cuju/render/web/assets/challenges/3_vs_1_with_keeper.webp +0 -0
  90. cuju/render/web/assets/challenges/corner.mp4 +0 -0
  91. cuju/render/web/assets/challenges/corner.webp +0 -0
  92. cuju/render/web/assets/challenges/counterattack_easy.mp4 +0 -0
  93. cuju/render/web/assets/challenges/counterattack_easy.webp +0 -0
  94. cuju/render/web/assets/challenges/counterattack_hard.mp4 +0 -0
  95. cuju/render/web/assets/challenges/counterattack_hard.webp +0 -0
  96. cuju/render/web/assets/challenges/empty_goal.mp4 +0 -0
  97. cuju/render/web/assets/challenges/empty_goal.webp +0 -0
  98. cuju/render/web/assets/challenges/empty_goal_close.mp4 +0 -0
  99. cuju/render/web/assets/challenges/empty_goal_close.webp +0 -0
  100. cuju/render/web/assets/challenges/manifest.json +375 -0
  101. cuju/render/web/assets/challenges/one_on_one.mp4 +0 -0
  102. cuju/render/web/assets/challenges/one_on_one.webp +0 -0
  103. cuju/render/web/assets/challenges/one_on_one_hard.mp4 +0 -0
  104. cuju/render/web/assets/challenges/one_on_one_hard.webp +0 -0
  105. cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.mp4 +0 -0
  106. cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.webp +0 -0
  107. cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.mp4 +0 -0
  108. cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.webp +0 -0
  109. cuju/render/web/assets/challenges/run_to_score.mp4 +0 -0
  110. cuju/render/web/assets/challenges/run_to_score.webp +0 -0
  111. cuju/render/web/assets/challenges/run_to_score_with_keeper.mp4 +0 -0
  112. cuju/render/web/assets/challenges/run_to_score_with_keeper.webp +0 -0
  113. cuju/render/web/assets/challenges/single_goal_versus_lazy.mp4 +0 -0
  114. cuju/render/web/assets/challenges/single_goal_versus_lazy.webp +0 -0
  115. cuju/render/web/assets/crowd.webp +0 -0
  116. cuju/render/web/assets/crowd_normal.webp +0 -0
  117. cuju/render/web/assets/cuju-logo-384.png +0 -0
  118. cuju/render/web/assets/cuju-logo.png +0 -0
  119. cuju/render/web/assets/cuju-logo.svg +46 -0
  120. cuju/render/web/assets/cuju-mark-light.svg +33 -0
  121. cuju/render/web/assets/cuju-mark.svg +33 -0
  122. cuju/render/web/assets/goal.glb +0 -0
  123. cuju/render/web/assets/grass.webp +0 -0
  124. cuju/render/web/assets/hair_black.png +0 -0
  125. cuju/render/web/assets/hair_blonde.png +0 -0
  126. cuju/render/web/assets/hair_brown.png +0 -0
  127. cuju/render/web/assets/hair_darkblonde.png +0 -0
  128. cuju/render/web/assets/hair_long01.glb +0 -0
  129. cuju/render/web/assets/hair_long02.glb +0 -0
  130. cuju/render/web/assets/hair_medium01.glb +0 -0
  131. cuju/render/web/assets/hair_red.png +0 -0
  132. cuju/render/web/assets/hair_short01.glb +0 -0
  133. cuju/render/web/assets/hair_short02.glb +0 -0
  134. cuju/render/web/assets/icons/cuju-192.png +0 -0
  135. cuju/render/web/assets/icons/cuju-512.png +0 -0
  136. cuju/render/web/assets/icons/cuju-maskable-512.png +0 -0
  137. cuju/render/web/assets/kit_goalkeeper.webp +0 -0
  138. cuju/render/web/assets/kit_mask.png +0 -0
  139. cuju/render/web/assets/kit_normal.webp +0 -0
  140. cuju/render/web/assets/manifest.json +601 -0
  141. cuju/render/web/assets/models/kaykit-barbarian.glb +0 -0
  142. cuju/render/web/assets/models/kaykit-barbarian.json +23 -0
  143. cuju/render/web/assets/models/kaykit-knight.glb +0 -0
  144. cuju/render/web/assets/models/kaykit-knight.json +23 -0
  145. cuju/render/web/assets/models/kaykit-mage.glb +0 -0
  146. cuju/render/web/assets/models/kaykit-mage.json +23 -0
  147. cuju/render/web/assets/models/kaykit-mannequin-medium.glb +0 -0
  148. cuju/render/web/assets/models/kaykit-mannequin-medium.json +23 -0
  149. cuju/render/web/assets/models/kaykit-ranger.glb +0 -0
  150. cuju/render/web/assets/models/kaykit-ranger.json +23 -0
  151. cuju/render/web/assets/models/kaykit-rogue-hooded.glb +0 -0
  152. cuju/render/web/assets/models/kaykit-rogue-hooded.json +23 -0
  153. cuju/render/web/assets/models/kaykit-rogue.glb +0 -0
  154. cuju/render/web/assets/models/kaykit-rogue.json +23 -0
  155. cuju/render/web/assets/models/kaykit-skeleton-mage.glb +0 -0
  156. cuju/render/web/assets/models/kaykit-skeleton-mage.json +23 -0
  157. cuju/render/web/assets/models/kaykit-skeleton-minion.glb +0 -0
  158. cuju/render/web/assets/models/kaykit-skeleton-minion.json +23 -0
  159. cuju/render/web/assets/models/kaykit-skeleton-rogue.glb +0 -0
  160. cuju/render/web/assets/models/kaykit-skeleton-rogue.json +23 -0
  161. cuju/render/web/assets/models/kaykit-skeleton-warrior.glb +0 -0
  162. cuju/render/web/assets/models/kaykit-skeleton-warrior.json +23 -0
  163. cuju/render/web/assets/pitch.webp +0 -0
  164. cuju/render/web/assets/pitch_normal.webp +0 -0
  165. cuju/render/web/assets/player.glb +0 -0
  166. cuju/render/web/assets/shoe.webp +0 -0
  167. cuju/render/web/assets/shoe_sole.png +0 -0
  168. cuju/render/web/assets/skin_01.png +0 -0
  169. cuju/render/web/assets/skin_02.png +0 -0
  170. cuju/render/web/assets/skin_03.png +0 -0
  171. cuju/render/web/assets/skin_04.png +0 -0
  172. cuju/render/web/assets/sounds/applause.ogg +0 -0
  173. cuju/render/web/assets/sounds/beach-wave-1.ogg +0 -0
  174. cuju/render/web/assets/sounds/beach-wave-2.ogg +0 -0
  175. cuju/render/web/assets/sounds/beach-wave-3.ogg +0 -0
  176. cuju/render/web/assets/sounds/beach-wave-4.ogg +0 -0
  177. cuju/render/web/assets/sounds/city-horn.ogg +0 -0
  178. cuju/render/web/assets/sounds/city-loop.ogg +0 -0
  179. cuju/render/web/assets/sounds/city-siren.ogg +0 -0
  180. cuju/render/web/assets/sounds/crowd-cheer.ogg +0 -0
  181. cuju/render/web/assets/sounds/crowd-loop.ogg +0 -0
  182. cuju/render/web/assets/sounds/crowd-ooh.ogg +0 -0
  183. cuju/render/web/assets/sounds/gulls.ogg +0 -0
  184. cuju/render/web/assets/sounds/neon-sting.ogg +0 -0
  185. cuju/render/web/assets/sounds/rain-loop.ogg +0 -0
  186. cuju/render/web/assets/sounds/sounds.json +46 -0
  187. cuju/render/web/assets/sounds/space-beep.ogg +0 -0
  188. cuju/render/web/assets/sounds/space-hum.ogg +0 -0
  189. cuju/render/web/assets/sounds/space-radio.ogg +0 -0
  190. cuju/render/web/assets/sounds/whistle-long.ogg +0 -0
  191. cuju/render/web/assets/sounds/whistle-short.ogg +0 -0
  192. cuju/render/web/assets/sounds/wind-loop.ogg +0 -0
  193. cuju/render/web/easy.css +357 -0
  194. cuju/render/web/export.html +23 -0
  195. cuju/render/web/feed.css +262 -0
  196. cuju/render/web/feed.html +28 -0
  197. cuju/render/web/flat.css +120 -0
  198. cuju/render/web/home.css +837 -0
  199. cuju/render/web/index.html +373 -0
  200. cuju/render/web/js/boot.js +50 -0
  201. cuju/render/web/js/broadcast/audio.js +2141 -0
  202. cuju/render/web/js/broadcast/celebrate.js +1238 -0
  203. cuju/render/web/js/broadcast/decisions.js +609 -0
  204. cuju/render/web/js/broadcast/hud.js +352 -0
  205. cuju/render/web/js/broadcast/show.js +3614 -0
  206. cuju/render/web/js/broadcast/transition.js +2231 -0
  207. cuju/render/web/js/broadcast/voices.js +153 -0
  208. cuju/render/web/js/capture.js +101 -0
  209. cuju/render/web/js/core/accessibility.js +90 -0
  210. cuju/render/web/js/core/api.js +429 -0
  211. cuju/render/web/js/core/comfort.js +135 -0
  212. cuju/render/web/js/core/i18n.js +705 -0
  213. cuju/render/web/js/core/icons.js +167 -0
  214. cuju/render/web/js/core/input-settings.js +156 -0
  215. cuju/render/web/js/core/keys.js +277 -0
  216. cuju/render/web/js/core/language-ui.js +125 -0
  217. cuju/render/web/js/core/menu.js +853 -0
  218. cuju/render/web/js/core/names.js +113 -0
  219. cuju/render/web/js/core/page-structure.js +110 -0
  220. cuju/render/web/js/core/scenarios.js +109 -0
  221. cuju/render/web/js/core/spoilers.js +236 -0
  222. cuju/render/web/js/core/typeahead.js +38 -0
  223. cuju/render/web/js/export/dom-picture.js +330 -0
  224. cuju/render/web/js/export/export-host.js +97 -0
  225. cuju/render/web/js/export/feed-dialog.js +191 -0
  226. cuju/render/web/js/export/feed-params.js +266 -0
  227. cuju/render/web/js/export/feed.js +363 -0
  228. cuju/render/web/js/export/mp4-mux.js +295 -0
  229. cuju/render/web/js/export/photo-core.js +313 -0
  230. cuju/render/web/js/export/photo.js +1314 -0
  231. cuju/render/web/js/export/safe-frame.js +59 -0
  232. cuju/render/web/js/export/size-fields.js +103 -0
  233. cuju/render/web/js/export/sizes.js +178 -0
  234. cuju/render/web/js/export/video-boot.js +55 -0
  235. cuju/render/web/js/export/video-browser.js +325 -0
  236. cuju/render/web/js/export/video-export.js +417 -0
  237. cuju/render/web/js/export/video-plan.js +207 -0
  238. cuju/render/web/js/export/video-range.js +185 -0
  239. cuju/render/web/js/export/webm-mux.js +198 -0
  240. cuju/render/web/js/feed-boot.js +151 -0
  241. cuju/render/web/js/feed-frame.js +101 -0
  242. cuju/render/web/js/help/doctor.js +488 -0
  243. cuju/render/web/js/help/firstrun.js +379 -0
  244. cuju/render/web/js/help/help-center.js +438 -0
  245. cuju/render/web/js/help/install-offer.js +164 -0
  246. cuju/render/web/js/help/install-state.js +104 -0
  247. cuju/render/web/js/help/lessons-core.js +207 -0
  248. cuju/render/web/js/help/lessons.js +463 -0
  249. cuju/render/web/js/help/palette.js +584 -0
  250. cuju/render/web/js/help/pwa.js +257 -0
  251. cuju/render/web/js/help/tour-core.js +241 -0
  252. cuju/render/web/js/help/tour.js +461 -0
  253. cuju/render/web/js/main.js +2513 -0
  254. cuju/render/web/js/pages/draw.js +188 -0
  255. cuju/render/web/js/pages/fieldpage.js +530 -0
  256. cuju/render/web/js/pages/home.js +521 -0
  257. cuju/render/web/js/pages/matches.js +779 -0
  258. cuju/render/web/js/pages/moments-ui.js +359 -0
  259. cuju/render/web/js/pages/queue-filters.js +63 -0
  260. cuju/render/web/js/pages/recording-groups.js +80 -0
  261. cuju/render/web/js/pages/recordings.js +1069 -0
  262. cuju/render/web/js/play/bench.js +347 -0
  263. cuju/render/web/js/play/challenges.js +1302 -0
  264. cuju/render/web/js/play/medals.js +127 -0
  265. cuju/render/web/js/play/play.js +1953 -0
  266. cuju/render/web/js/play/teasers.js +196 -0
  267. cuju/render/web/js/play/touch.js +350 -0
  268. cuju/render/web/js/play/tournament-layout.js +487 -0
  269. cuju/render/web/js/play/tournament-results.js +441 -0
  270. cuju/render/web/js/play/tournament-strict.js +560 -0
  271. cuju/render/web/js/play/tournament.js +2585 -0
  272. cuju/render/web/js/players/animate.js +972 -0
  273. cuju/render/web/js/players/clips.js +224 -0
  274. cuju/render/web/js/players/kaykit.js +162 -0
  275. cuju/render/web/js/players/kits.js +676 -0
  276. cuju/render/web/js/players/number-patch.js +82 -0
  277. cuju/render/web/js/players/players.js +2694 -0
  278. cuju/render/web/js/players/recolor.js +893 -0
  279. cuju/render/web/js/players/retarget.js +325 -0
  280. cuju/render/web/js/players/rig-numbers.js +174 -0
  281. cuju/render/web/js/presentation/animator.js +231 -0
  282. cuju/render/web/js/presentation/assign.js +85 -0
  283. cuju/render/web/js/presentation/behaviors.js +440 -0
  284. cuju/render/web/js/presentation/budget.js +209 -0
  285. cuju/render/web/js/presentation/catalog.js +1674 -0
  286. cuju/render/web/js/presentation/clocks.js +72 -0
  287. cuju/render/web/js/presentation/commands.js +301 -0
  288. cuju/render/web/js/presentation/contract.js +240 -0
  289. cuju/render/web/js/presentation/effects.js +904 -0
  290. cuju/render/web/js/presentation/flash.js +605 -0
  291. cuju/render/web/js/presentation/freeze-worklet.js +22 -0
  292. cuju/render/web/js/presentation/glsl.js +464 -0
  293. cuju/render/web/js/presentation/host.js +323 -0
  294. cuju/render/web/js/presentation/index.js +2066 -0
  295. cuju/render/web/js/presentation/loudness-worklet.js +235 -0
  296. cuju/render/web/js/presentation/loudness.js +740 -0
  297. cuju/render/web/js/presentation/moments.js +536 -0
  298. cuju/render/web/js/presentation/post.js +327 -0
  299. cuju/render/web/js/presentation/preview.js +180 -0
  300. cuju/render/web/js/presentation/protocol.js +174 -0
  301. cuju/render/web/js/presentation/reach.js +158 -0
  302. cuju/render/web/js/presentation/rigging.js +130 -0
  303. cuju/render/web/js/presentation/sandbox.js +157 -0
  304. cuju/render/web/js/presentation/shaders.js +235 -0
  305. cuju/render/web/js/presentation/stage.js +402 -0
  306. cuju/render/web/js/presentation/teamaudio.js +722 -0
  307. cuju/render/web/js/presentation/tracks.js +197 -0
  308. cuju/render/web/js/presentation/values.js +81 -0
  309. cuju/render/web/js/recording/annotation-data.js +203 -0
  310. cuju/render/web/js/recording/captions.js +491 -0
  311. cuju/render/web/js/recording/codec.js +185 -0
  312. cuju/render/web/js/recording/decode-worker.js +70 -0
  313. cuju/render/web/js/recording/decode.js +94 -0
  314. cuju/render/web/js/recording/episodes.js +46 -0
  315. cuju/render/web/js/recording/format.js +461 -0
  316. cuju/render/web/js/recording/live.js +348 -0
  317. cuju/render/web/js/recording/moments-query.js +59 -0
  318. cuju/render/web/js/recording/moments.js +149 -0
  319. cuju/render/web/js/recording/pose.js +86 -0
  320. cuju/render/web/js/recording/timeline.js +275 -0
  321. cuju/render/web/js/teams/art.js +50 -0
  322. cuju/render/web/js/teams/clip-editor.js +293 -0
  323. cuju/render/web/js/teams/editor.js +1979 -0
  324. cuju/render/web/js/teams/library.js +344 -0
  325. cuju/render/web/js/teams/painter.js +633 -0
  326. cuju/render/web/js/teams/surprise-ui.js +195 -0
  327. cuju/render/web/js/teams/surprise.js +497 -0
  328. cuju/render/web/js/teams/team-check.js +127 -0
  329. cuju/render/web/js/watch/annotations.js +270 -0
  330. cuju/render/web/js/watch/flat.js +366 -0
  331. cuju/render/web/js/watch/grid.js +436 -0
  332. cuju/render/web/js/watch/pitch2d.js +289 -0
  333. cuju/render/web/js/watch/player-tools.js +192 -0
  334. cuju/render/web/js/watch/presets.js +201 -0
  335. cuju/render/web/js/world/arenas.js +997 -0
  336. cuju/render/web/js/world/ball.js +204 -0
  337. cuju/render/web/js/world/cameras.js +982 -0
  338. cuju/render/web/js/world/crowd.js +376 -0
  339. cuju/render/web/js/world/field.js +227 -0
  340. cuju/render/web/js/world/framing.js +145 -0
  341. cuju/render/web/js/world/occlusion.js +48 -0
  342. cuju/render/web/js/world/pitch.js +703 -0
  343. cuju/render/web/js/world/scene.js +395 -0
  344. cuju/render/web/js/world/sky.js +325 -0
  345. cuju/render/web/js/world/stadium.js +1262 -0
  346. cuju/render/web/js/world/vendor-sky.js +367 -0
  347. cuju/render/web/js/world/weather.js +264 -0
  348. cuju/render/web/languages/en.json +4750 -0
  349. cuju/render/web/lessons.json +137 -0
  350. cuju/render/web/manifest.webmanifest +23 -0
  351. cuju/render/web/style.css +8399 -0
  352. cuju/render/web/sw.js +235 -0
  353. cuju/render/web/vendor/fabric/LICENSE +16 -0
  354. cuju/render/web/vendor/fabric/fabric.min.mjs +1 -0
  355. cuju/render/web/vendor/lucide/LICENSE +43 -0
  356. cuju/render/web/vendor/lucide/icons.js +141 -0
  357. cuju/render/web/vendor/three/LICENSE +21 -0
  358. cuju/render/web/vendor/three/build/three.core.js +60586 -0
  359. cuju/render/web/vendor/three/build/three.module.js +19719 -0
  360. cuju/render/web/vendor/three/examples/jsm/loaders/GLTFLoader.js +4925 -0
  361. cuju/render/web/vendor/three/examples/jsm/postprocessing/EffectComposer.js +365 -0
  362. cuju/render/web/vendor/three/examples/jsm/postprocessing/MaskPass.js +195 -0
  363. cuju/render/web/vendor/three/examples/jsm/postprocessing/OutputPass.js +148 -0
  364. cuju/render/web/vendor/three/examples/jsm/postprocessing/Pass.js +191 -0
  365. cuju/render/web/vendor/three/examples/jsm/postprocessing/RenderPass.js +193 -0
  366. cuju/render/web/vendor/three/examples/jsm/postprocessing/ShaderPass.js +135 -0
  367. cuju/render/web/vendor/three/examples/jsm/postprocessing/UnrealBloomPass.js +541 -0
  368. cuju/render/web/vendor/three/examples/jsm/shaders/CopyShader.js +52 -0
  369. cuju/render/web/vendor/three/examples/jsm/shaders/LuminosityHighPassShader.js +68 -0
  370. cuju/render/web/vendor/three/examples/jsm/shaders/OutputShader.js +103 -0
  371. cuju/render/web/vendor/three/examples/jsm/utils/BufferGeometryUtils.js +1501 -0
  372. cuju/render/web/vendor/three/examples/jsm/utils/SkeletonUtils.js +496 -0
  373. cuju_render-0.1.0.dist-info/METADATA +116 -0
  374. cuju_render-0.1.0.dist-info/RECORD +382 -0
  375. cuju_render-0.1.0.dist-info/WHEEL +4 -0
  376. cuju_render-0.1.0.dist-info/licenses/LICENSE +202 -0
  377. cuju_render-0.1.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +396 -0
  378. cuju_render-0.1.0.dist-info/licenses/LICENSES/CC0-1.0.txt +127 -0
  379. cuju_render-0.1.0.dist-info/licenses/LICENSES/ISC.txt +16 -0
  380. cuju_render-0.1.0.dist-info/licenses/LICENSES/MIT.txt +24 -0
  381. cuju_render-0.1.0.dist-info/licenses/LICENSES/Unlicense.txt +13 -0
  382. cuju_render-0.1.0.dist-info/licenses/NOTICE +35 -0
@@ -0,0 +1,560 @@
1
+ // Copyright 2026 The cuju Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // The parts of the Tournaments page for strict rules, the random entrant, the pre-flight check,
5
+ // more legs and the results. The wizard gains the switch of strict rules with their limits, the
6
+ // switch of the pre-flight, an adder of the random entrant and a pre-flight that runs from the
7
+ // review. A tournament shows its rules and its pre-flight in its head with the entrants that were
8
+ // left out, an Add legs control for a league or groups, and a Results section with the ratings
9
+ // and their intervals, the matrix of mean goal difference as a heat table and the files to save.
10
+ // tournament.js calls each part, so the page itself changes in a few places alone.
11
+
12
+ import { formatNumber, serverText, t } from '../core/i18n.js';
13
+ import { el as h } from '../core/menu.js';
14
+ import { icon } from '../core/icons.js';
15
+ import { spoilerFree } from '../core/spoilers.js';
16
+ import { jsonCall } from '../teams/team-check.js';
17
+ import { entrantName } from './tournament-layout.js';
18
+ import {
19
+ RESULT_FILES, STRICT_DEFAULTS, STRICT_FLAGS, STRICT_FLAG_KEYS, STRICT_KEYS, STRICT_RANGES, STRICT_STEPS, STRICT_UNITS, analysisStamp, cellName,
20
+ checkSides, entrantKey, fileAddress, folderText, intervalBar, intervalWords, legsRoom, limitNumber, limitsText,
21
+ matrixCells, matrixOrder, methodText, playedText, preflightText, ratingScale, ratingText, ruleReason, sideText,
22
+ signedMean, strictLimits, strictProblems, timingsText, warningTexts, withIncluded, withoutIncluded,
23
+ } from './tournament-results.js';
24
+
25
+ const enc = encodeURIComponent;
26
+
27
+ export const strictApi = {
28
+ analysis: (id) => jsonCall('GET', `/api/tournaments/${enc(id)}/analysis`),
29
+ preflight: (config) => jsonCall('POST', '/api/tournaments/preflight', { config }),
30
+ addLegs: (id, legs) => jsonCall('POST', `/api/tournaments/${enc(id)}/legs`, { legs }),
31
+ };
32
+
33
+ // The symbol of an entrant's kind in the wizard, a letter for a person and the built-in AI and
34
+ // the dice for the random entrant.
35
+ export function kindGlyph(kind) {
36
+ if (kind === 'random') {
37
+ return icon('dice', 'icon text-symbol');
38
+ }
39
+ return kind === 'person' ? t('tournament.entrant.glyph.person') : t('tournament.entrant.glyph.builtin');
40
+ }
41
+
42
+ // The name of the random entrant as the server makes it.
43
+ export function randomName() {
44
+ return t('server.tournament.label.random');
45
+ }
46
+
47
+ // The setup.
48
+
49
+ // Takes the strict rules, the pre-flight and the added entrants of a preset into a draft, and
50
+ // shows the added entrants in its list. Entrants that an earlier preset added leave the list
51
+ // unless this one adds them too.
52
+ export function applyPreset(draft, preset) {
53
+ draft.strict = preset.strict ?? null;
54
+ draft.preflight = !!preset.preflight;
55
+ draft.include = JSON.parse(JSON.stringify(preset.include || []));
56
+ const keys = new Set(draft.include.map(entrantKey));
57
+ draft.entrants = draft.entrants.filter((entry) => !entry.included || keys.has(entrantKey(entry)));
58
+ const before = draft.entrants.length;
59
+ draft.entrants = withIncluded(draft.entrants, draft.include).slice(0, 256);
60
+ for (const entry of draft.entrants.slice(before)) {
61
+ entry.included = true;
62
+ }
63
+ }
64
+
65
+ // Takes the strict rules, the pre-flight and the added entrants of a tournament run again into
66
+ // its draft, the added entrants marked as such in its list.
67
+ export function fromConfig(draft, config) {
68
+ draft.strict = config.strict ?? null;
69
+ draft.preflight = !!config.preflight;
70
+ draft.include = JSON.parse(JSON.stringify(config.include || []));
71
+ const keys = new Set(draft.include.map(entrantKey));
72
+ for (const entry of draft.entrants) {
73
+ if (keys.has(entrantKey(entry))) {
74
+ entry.included = true;
75
+ }
76
+ }
77
+ }
78
+
79
+ // Drops an entrant of the list from the entrants a draft adds, so the server does not add it back.
80
+ export function removedEntrant(draft, entry) {
81
+ draft.include = withoutIncluded(draft.include, entry);
82
+ }
83
+
84
+ // The strict rules, the pre-flight and the added entrants of a draft, for its configuration and
85
+ // for a preset saved from it.
86
+ export function strictConfig(draft) {
87
+ const out = {};
88
+ if (draft.strict !== null && draft.strict !== undefined && draft.strict !== false) {
89
+ out.strict = draft.strict;
90
+ }
91
+ if (draft.preflight) {
92
+ out.preflight = true;
93
+ }
94
+ if (draft.include && draft.include.length) {
95
+ out.include = draft.include;
96
+ }
97
+ return out;
98
+ }
99
+
100
+ // The adder of the random entrant, one player taking random actions, beside the built-in AI.
101
+ export function randomAdder(draft, add, toast) {
102
+ return h('div', { class: 'cup-adder cup-adder-random' },
103
+ h('span', { class: 'field' }, h('span', {}, t('tournament.entrants.random')),
104
+ h('span', { class: 'muted small', id: 'cup-random-note' }, t('tournament.entrants.random.note'))),
105
+ h('button', {
106
+ type: 'button', class: 'btn', 'aria-label': t('tournament.entrants.random.add'), title: t('tournament.entrants.random.add'),
107
+ 'aria-describedby': 'cup-random-note', 'data-symbol': '', 'data-focus': 'add-random',
108
+ onclick: () => {
109
+ if (draft.entrants.some((entry) => entry.kind === 'random')) {
110
+ toast(t('tournament.entrants.random.in'));
111
+ return;
112
+ }
113
+ add({ kind: 'random' });
114
+ },
115
+ }, icon('plus')));
116
+ }
117
+
118
+ // The switch of strict rules with the box of each limit and its unit, and the switch of the
119
+ // pre-flight, for the settings of the wizard.
120
+ export function strictFields(draft) {
121
+ const on = draft.strict !== null && draft.strict !== undefined && draft.strict !== false;
122
+ const limits = strictLimits(on ? draft.strict : true);
123
+ const box = h('input', { type: 'checkbox', role: 'switch', class: 'cup-strict-switch', checked: on, 'aria-describedby': 'cup-strict-note' });
124
+ const boxes = {};
125
+ const fields = STRICT_KEYS.map((key) => {
126
+ const [lowest, highest] = STRICT_RANGES[key];
127
+ const unit = STRICT_UNITS[key];
128
+ const label = t(`tournament.strict.field.${key}`);
129
+ const range = t('tournament.strict.range', { lowest: limitNumber(lowest), highest: limitNumber(highest) });
130
+ const note = h('span', { class: 'muted small cup-strict-range', id: `cup-strict-${key}-range` }, range);
131
+ const input = h('input', {
132
+ type: 'number', min: String(lowest), max: String(highest), step: String(STRICT_STEPS[key]), value: String(limits[key]),
133
+ disabled: !on, 'data-limit': key, 'aria-describedby': `cup-strict-${key}-range`,
134
+ 'aria-label': unit === 'threads' ? label : t('tournament.strict.field.label', { label, unit: t(`tournament.strict.unit.${unit}`) }),
135
+ });
136
+ boxes[key] = input;
137
+ input.addEventListener('input', () => {
138
+ const value = input.value.trim() === '' ? NaN : Number(input.value);
139
+ const given = draft.strict && typeof draft.strict === 'object' ? { ...draft.strict } : strictLimits(true);
140
+ given[key] = value;
141
+ draft.strict = given;
142
+ const bad = strictProblems(strictLimits(given)).includes(key);
143
+ if (bad) {
144
+ input.setAttribute('aria-invalid', 'true');
145
+ } else {
146
+ input.removeAttribute('aria-invalid');
147
+ }
148
+ });
149
+ return h('label', { class: 'field small-field cup-strict-limit' }, h('span', {}, label),
150
+ h('span', { class: 'cup-unit' }, input, unit === 'threads' ? null : h('span', { class: 'muted', 'aria-hidden': 'true' }, t(`tournament.strict.unitShort.${unit}`))),
151
+ note);
152
+ });
153
+ // Each switch of the rules, such as frozen team folders, with what it does beside it.
154
+ const flags = STRICT_FLAG_KEYS.map((key) => {
155
+ const input = h('input', { type: 'checkbox', checked: !!limits[key], disabled: !on, 'data-flag': key, 'aria-describedby': `cup-strict-${key}-note` });
156
+ boxes[key] = input;
157
+ input.addEventListener('change', () => {
158
+ const given = draft.strict && typeof draft.strict === 'object' ? { ...draft.strict } : strictLimits(true);
159
+ given[key] = input.checked;
160
+ draft.strict = given;
161
+ });
162
+ return h('div', { class: 'cup-strict-flag' }, h('label', { class: 'check' }, input, t(`tournament.strict.field.${key}`)),
163
+ h('span', { class: 'muted small', id: `cup-strict-${key}-note` }, t(`tournament.strict.flagNote.${key}`)));
164
+ });
165
+ box.addEventListener('change', () => {
166
+ draft.strict = box.checked ? strictLimits(true) : null;
167
+ for (const key of STRICT_KEYS) {
168
+ boxes[key].disabled = !box.checked;
169
+ boxes[key].value = String(STRICT_DEFAULTS[key]);
170
+ boxes[key].removeAttribute('aria-invalid');
171
+ }
172
+ for (const key of STRICT_FLAG_KEYS) {
173
+ boxes[key].disabled = !box.checked;
174
+ boxes[key].checked = STRICT_FLAGS[key];
175
+ }
176
+ });
177
+ const pre = h('input', { type: 'checkbox', role: 'switch', class: 'cup-preflight-switch', checked: !!draft.preflight, 'aria-describedby': 'cup-preflight-note' });
178
+ pre.addEventListener('change', () => {
179
+ draft.preflight = pre.checked;
180
+ });
181
+ return h('fieldset', { class: 'cup-strict' },
182
+ h('legend', {}, t('tournament.strict.title')),
183
+ h('label', { class: 'check' }, box, t('tournament.strict.switch')),
184
+ h('p', { class: 'muted small', id: 'cup-strict-note' }, t('tournament.strict.note')),
185
+ h('div', { class: 'cup-strict-limits' }, fields),
186
+ h('div', { class: 'cup-strict-flags' }, flags),
187
+ h('label', { class: 'check' }, pre, t('tournament.preflight.switch')),
188
+ h('p', { class: 'muted small', id: 'cup-preflight-note' }, t('tournament.preflight.note')));
189
+ }
190
+
191
+ // The pre-flights that a review ran, by draft, each with the configuration it checked, whether it
192
+ // still runs, and its answer or error.
193
+ const preflights = new WeakMap();
194
+
195
+ // The strict rules and the pre-flight of a draft for its review, and a button that runs the
196
+ // pre-flight now, which shows each team as passed or failed and those it would leave out.
197
+ export function strictReview(page) {
198
+ const draft = page.draft;
199
+ const limits = strictLimits(draft.strict);
200
+ const teams = draft.entrants.filter((entry) => entry.kind === 'team').length;
201
+ const host = h('div', { class: 'cup-preflight-result', role: 'status' });
202
+ const button = h('button', {
203
+ type: 'button', class: 'btn cup-preflight-run', 'data-focus': 'preflight-run', disabled: !teams,
204
+ onclick: () => runPreflight(page, draft),
205
+ }, icon('speed'), t('tournament.preflight.run'));
206
+ const section = h('section', { class: 'cup-review-strict', 'aria-labelledby': 'cup-review-strict-title' },
207
+ h('h3', { id: 'cup-review-strict-title' }, t('tournament.strict.title')),
208
+ h('p', { class: 'small cup-review-limits' }, limits ? t('tournament.strict.summary', { limits: limitsText(limits) }) : t('tournament.strict.none')),
209
+ draft.preflight ? h('p', { class: 'small' }, t('tournament.preflight.review')) : null,
210
+ draft.entrants.some((entry) => entry.kind === 'random') ? h('p', { class: 'small muted' }, t('tournament.entrants.random.note')) : null,
211
+ h('div', { class: 'row' }, button),
212
+ teams ? null : h('p', { class: 'muted small' }, t('tournament.preflight.none')),
213
+ host);
214
+ drawPreflight(host, button, draft, page.config());
215
+ return section;
216
+ }
217
+
218
+ async function runPreflight(page, draft) {
219
+ const config = page.config();
220
+ const stamp = JSON.stringify(config);
221
+ const known = preflights.get(draft);
222
+ if (known && known.busy) {
223
+ return;
224
+ }
225
+ preflights.set(draft, { stamp, busy: true, teams: new Set(draft.entrants.filter((e) => e.kind === 'team').map((e) => e.team)).size });
226
+ redrawPreflight(page, draft);
227
+ let found;
228
+ try {
229
+ found = { stamp, report: await strictApi.preflight(config) };
230
+ } catch (err) {
231
+ found = { stamp, error: err.message };
232
+ }
233
+ preflights.set(draft, found);
234
+ redrawPreflight(page, draft);
235
+ }
236
+
237
+ // Draws the answer of a pre-flight into the review that shows now, if the person is still there.
238
+ function redrawPreflight(page, draft) {
239
+ if (page.mode !== 'wizard' || page.draft !== draft) {
240
+ return;
241
+ }
242
+ const host = page.mainHost.querySelector('.cup-preflight-result');
243
+ const button = page.mainHost.querySelector('.cup-preflight-run');
244
+ if (host && button) {
245
+ drawPreflight(host, button, draft, page.config());
246
+ }
247
+ }
248
+
249
+ function drawPreflight(host, button, draft, config) {
250
+ const run = preflights.get(draft);
251
+ host.removeAttribute('aria-busy');
252
+ button.removeAttribute('aria-disabled');
253
+ if (!run) {
254
+ host.replaceChildren();
255
+ return;
256
+ }
257
+ if (run.busy) {
258
+ host.setAttribute('aria-busy', 'true');
259
+ button.setAttribute('aria-disabled', 'true');
260
+ host.replaceChildren(h('p', { class: 'small' }, icon('running', 'icon text-symbol'), t('tournament.preflight.running', { count: run.teams })));
261
+ return;
262
+ }
263
+ const stale = run.stamp !== JSON.stringify(config) ? h('p', { class: 'muted small' }, icon('warning', 'icon text-symbol'), t('tournament.preflight.stale')) : null;
264
+ if (run.error) {
265
+ host.replaceChildren(h('div', { class: 'notice error' }, h('p', {}, run.error)), stale);
266
+ return;
267
+ }
268
+ const report = run.report;
269
+ const excluded = report.excluded || [];
270
+ host.replaceChildren(
271
+ h('ul', { class: 'cup-checks symbol-list small' }, (report.checks || []).map(checkItem)),
272
+ h('p', { class: 'small' }, excluded.length
273
+ ? t('tournament.preflight.leftOut', { count: excluded.length, names: excluded.map((e) => serverText(e, 'name')).join(', ') })
274
+ : t('tournament.preflight.allPass')),
275
+ stale);
276
+ }
277
+
278
+ // A team's check as an item of a list, passed or failed with its reason, then each side it
279
+ // played with its timings, the size of its folder and its warnings. A check that a tournament
280
+ // keeps has no sides, so its timings follow its state.
281
+ export function checkItem(check) {
282
+ const sides = checkSides(check);
283
+ const timings = sides.length ? '' : timingsText(check.timings);
284
+ const folder = folderText(check);
285
+ const warnings = warningTexts(check);
286
+ return h('li', { class: check.ok ? 'passed' : 'failed' },
287
+ icon(check.ok ? 'done' : 'failed', 'icon text-symbol'),
288
+ h('span', {}, h('strong', {}, serverText(check, 'name') || check.team), ', ',
289
+ check.ok ? t('tournament.preflight.passed') : t('tournament.preflight.failed', { reason: ruleReason(check.rule) }),
290
+ timings ? h('span', { class: 'muted' }, `, ${timings}`) : null,
291
+ folder ? h('span', { class: 'muted' }, `, ${folder}`) : null),
292
+ sides.length || warnings.length ? h('ul', { class: 'cup-check-sides symbol-list' },
293
+ sides.map((side) => h('li', { class: side.skipped ? 'skipped' : side.ok ? 'passed' : 'failed' }, sideText(side))),
294
+ warnings.map((text) => h('li', { class: 'warning' }, icon('warning', 'icon text-symbol'), text))) : null);
295
+ }
296
+
297
+ // The tournament view.
298
+
299
+ // The strict rules and the pre-flight of a tournament for its head, with the entrants that the
300
+ // pre-flight left out, their reasons and timings, and the checks of the other teams.
301
+ export function strictHead(cup) {
302
+ const limits = cup.config && cup.config.strict ? strictLimits(cup.config.strict) : null;
303
+ const state = cup.preflight ?? (cup.config && cup.config.preflight ? (cup.checks && cup.checks.length ? 'done' : 'waiting') : null);
304
+ const excluded = cup.excluded || [];
305
+ const checks = cup.checks || [];
306
+ if (!limits && !state && !excluded.length) {
307
+ return null;
308
+ }
309
+ const passed = checks.filter((check) => check.ok);
310
+ return h('div', { class: 'cup-strict-head' },
311
+ limits ? h('p', { class: 'small cup-strict-line' }, icon('clock', 'icon text-symbol'), t('tournament.strict.summary', { limits: limitsText(limits) })) : null,
312
+ state ? h('p', { class: 'small cup-preflight-state' }, icon(state === 'done' ? 'done' : state === 'running' ? 'running' : 'waiting', 'icon text-symbol'),
313
+ preflightText(state, cup.excluded_count ?? excluded.length)) : null,
314
+ excluded.length ? h('section', { class: 'cup-excluded notice', 'aria-labelledby': 'cup-excluded-title' },
315
+ h('h3', { id: 'cup-excluded-title' }, t('tournament.excluded.title')),
316
+ h('ul', { class: 'symbol-list small' }, excluded.map((entry) => {
317
+ const timings = timingsText(entry.timings);
318
+ return h('li', {}, icon('failed', 'icon text-symbol'),
319
+ h('span', {}, h('strong', {}, serverText(entry, 'name')), ', ', serverText(entry, 'reason'),
320
+ timings ? h('span', { class: 'muted' }, `, ${timings}`) : null));
321
+ }))) : null,
322
+ passed.length ? checksBox(cup.id, passed) : null);
323
+ }
324
+
325
+ // The tournaments whose box of passed checks is open, so it stays open through the redraws.
326
+ const openChecks = new Set();
327
+
328
+ function checksBox(id, passed) {
329
+ const box = h('details', { class: 'cup-checks-box small', open: openChecks.has(id) },
330
+ h('summary', { 'data-focus': 'checks-box' }, t('tournament.checks.title', { count: passed.length })),
331
+ h('ul', { class: 'cup-checks symbol-list' }, passed.map(checkItem)));
332
+ box.addEventListener('toggle', () => {
333
+ if (box.open) {
334
+ openChecks.add(id);
335
+ } else {
336
+ openChecks.delete(id);
337
+ }
338
+ });
339
+ return box;
340
+ }
341
+
342
+ // The control that adds legs to the last stage of a tournament, a league or groups, or null.
343
+ export function legsControl(page, cup) {
344
+ const room = legsRoom(cup);
345
+ if (!room) {
346
+ return null;
347
+ }
348
+ const stages = cup.config.stages;
349
+ const stage = serverText(stages[stages.length - 1], 'name');
350
+ const input = h('input', { type: 'number', class: 'cup-legs-count', min: '1', max: String(room), step: '1', value: '1', 'data-focus': 'legs-count', 'aria-label': t('tournament.legs.label', { stage }) });
351
+ const button = h('button', {
352
+ type: 'button', class: 'btn ghost cup-legs-add', 'data-focus': 'legs-add',
353
+ onclick: async () => {
354
+ const legs = Number(input.value);
355
+ if (!Number.isInteger(legs) || legs < 1 || legs > room) {
356
+ input.setAttribute('aria-invalid', 'true');
357
+ page.app.hud.toast(t('tournament.legs.range', { most: room }), 'error');
358
+ input.focus();
359
+ return;
360
+ }
361
+ button.disabled = true;
362
+ page.focusAfter = 'legs-add';
363
+ try {
364
+ page.current = await strictApi.addLegs(cup.id, legs);
365
+ page.app.hud.toast(t('tournament.legs.added', { count: legs }));
366
+ await page.refresh();
367
+ } catch (err) {
368
+ page.app.hud.toast(err.message, 'error', 7000);
369
+ button.disabled = false;
370
+ }
371
+ },
372
+ }, icon('plus'), t('tournament.legs.add'));
373
+ return h('span', { class: 'cup-legs row', role: 'group', 'aria-label': t('tournament.legs.group', { stage }) }, input, button);
374
+ }
375
+
376
+ // The results of a tournament, kept by page, one node that moves into each redraw of the view.
377
+ const views = new WeakMap();
378
+
379
+ // The Results section of a tournament's view, the same node at each redraw.
380
+ export function resultsSection(page, cup) {
381
+ let view = views.get(page);
382
+ if (!view) {
383
+ view = new ResultsView(page);
384
+ views.set(page, view);
385
+ }
386
+ return view.update(cup);
387
+ }
388
+
389
+ class ResultsView {
390
+ constructor(page) {
391
+ this.page = page;
392
+ this.id = null;
393
+ this.open = false;
394
+ this.data = null;
395
+ this.stamp = '';
396
+ this.asked = '';
397
+ this.error = null;
398
+ this.drawn = '';
399
+ this.toggle = h('button', {
400
+ type: 'button', class: 'btn ghost cup-results-toggle', 'data-focus': 'results-toggle', 'aria-expanded': 'false', 'aria-controls': 'cup-results-body',
401
+ onclick: () => {
402
+ this.open = !this.open;
403
+ this.update(this.cup);
404
+ },
405
+ }, icon('stats'), t('tournament.results.title'));
406
+ this.body = h('div', { class: 'cup-results-body', id: 'cup-results-body' });
407
+ this.node = h('section', { class: 'cup-results', 'aria-labelledby': 'cup-results-title' },
408
+ h('h3', { id: 'cup-results-title' }, this.toggle), this.body);
409
+ }
410
+
411
+ // Brings the section up to date with the tournament, asking for the analysis again while the
412
+ // results show and the tournament's played matches changed.
413
+ update(cup) {
414
+ this.cup = cup;
415
+ if (cup.id !== this.id) {
416
+ // Another tournament opens with its Results closed, so no ratings of the one before
417
+ // stay in view, and its own load only once asked for.
418
+ this.id = cup.id;
419
+ this.open = false;
420
+ this.data = null;
421
+ this.stamp = '';
422
+ this.asked = '';
423
+ this.error = null;
424
+ this.drawn = '';
425
+ this.body.setAttribute('aria-busy', 'false');
426
+ }
427
+ this.toggle.setAttribute('aria-expanded', this.open ? 'true' : 'false');
428
+ this.body.hidden = !this.open;
429
+ const hidden = spoilerFree(cup.id);
430
+ const stamp = analysisStamp(cup);
431
+ if (this.open && !hidden && cup.played && stamp !== this.asked) {
432
+ this.fetch(cup, stamp);
433
+ }
434
+ const drawn = JSON.stringify([this.open, hidden, cup.played > 0, this.stamp, !!this.error, this.asked === stamp]);
435
+ if (drawn !== this.drawn) {
436
+ this.drawn = drawn;
437
+ this.draw(cup, hidden);
438
+ }
439
+ // A box that scrolls keeps its place as the section moves into the next redraw.
440
+ const kept = [...this.body.querySelectorAll('[data-scroll]')].map((node) => [node, node.scrollLeft, node.scrollTop]);
441
+ queueMicrotask(() => kept.forEach(([node, left, top]) => {
442
+ node.scrollLeft = left;
443
+ node.scrollTop = top;
444
+ }));
445
+ return this.node;
446
+ }
447
+
448
+ async fetch(cup, stamp) {
449
+ this.asked = stamp;
450
+ this.body.setAttribute('aria-busy', 'true');
451
+ try {
452
+ const found = await strictApi.analysis(cup.id);
453
+ if (this.id !== cup.id) {
454
+ return;
455
+ }
456
+ this.data = found;
457
+ this.stamp = stamp;
458
+ this.error = null;
459
+ } catch (err) {
460
+ if (this.id !== cup.id) {
461
+ return;
462
+ }
463
+ this.error = err.message;
464
+ }
465
+ this.body.setAttribute('aria-busy', 'false');
466
+ this.update(this.cup);
467
+ }
468
+
469
+ draw(cup, hidden) {
470
+ if (!this.open) {
471
+ this.body.replaceChildren();
472
+ return;
473
+ }
474
+ if (hidden) {
475
+ this.body.replaceChildren(h('p', { class: 'muted small' }, icon('eye', 'icon text-symbol'), t('tournament.results.spoilers')));
476
+ return;
477
+ }
478
+ if (!cup.played) {
479
+ this.body.replaceChildren(h('p', { class: 'muted small' }, t('tournament.results.none')));
480
+ return;
481
+ }
482
+ if (this.error) {
483
+ this.body.replaceChildren(h('div', { class: 'notice error' }, h('p', {}, this.error)));
484
+ return;
485
+ }
486
+ if (!this.data) {
487
+ this.body.replaceChildren(h('p', { class: 'muted small' }, t('tournament.results.loading')));
488
+ return;
489
+ }
490
+ const entrants = cup.config.entrants;
491
+ this.body.replaceChildren(
492
+ h('div', { class: 'cup-results-summary small' }, h('p', {}, playedText(this.data)), h('p', { class: 'muted' }, methodText(this.data))),
493
+ ratingsTable(this.data, entrants), matrixTable(this.data, entrants), filesList(cup));
494
+ }
495
+ }
496
+
497
+ // The ratings, best first, each with its interval as a bar on one scale.
498
+ export function ratingsTable(data, entrants) {
499
+ const rows = data.ratings || [];
500
+ const scale = ratingScale(rows);
501
+ const head = ['rank', 'entrant', 'rating', 'interval', 'low', 'high', 'matches'].map((name) => h('th', { scope: 'col' }, t(`tournament.results.column.${name}`)));
502
+ const body = rows.map((row, index) => {
503
+ const bar = intervalBar(row, scale);
504
+ return h('tr', {},
505
+ h('td', {}, formatNumber(index + 1)),
506
+ h('th', { scope: 'row' }, entrantName(entrants[row.entrant])),
507
+ h('td', {}, ratingText(row.rating)),
508
+ h('td', { class: 'cup-interval-cell' }, h('span', { class: 'cup-interval', role: 'img', 'aria-label': intervalWords(row) },
509
+ bar.centre === null ? null : h('span', { class: 'cup-interval-centre', style: `left:${bar.centre}%` }),
510
+ h('span', { class: 'cup-interval-range', style: `left:${bar.left}%; width:${bar.width}%` }),
511
+ h('span', { class: 'cup-interval-mark', style: `left:${bar.mark}%` }))),
512
+ h('td', {}, ratingText(row.low)),
513
+ h('td', {}, ratingText(row.high)),
514
+ h('td', {}, formatNumber(row.matches)));
515
+ });
516
+ return h('section', { class: 'cup-ratings', 'aria-labelledby': 'cup-ratings-title' },
517
+ h('h4', { id: 'cup-ratings-title' }, t('tournament.results.ratings')),
518
+ h('div', { class: 'cup-ratings-scroll', tabindex: '0', 'data-scroll': '', 'data-focus': 'results-ratings', role: 'region', 'aria-label': t('tournament.results.ratings.scroll') },
519
+ h('table', {}, h('caption', { class: 'muted small' }, t('tournament.results.ratings.caption', { count: data.resamples ?? 0 })),
520
+ h('thead', {}, h('tr', {}, head)), h('tbody', {}, body))));
521
+ }
522
+
523
+ // The matrix of mean goal difference, row against column, as a heat table whose first column
524
+ // stays in place while the rest scrolls inside its own box.
525
+ export function matrixTable(data, entrants) {
526
+ const order = matrixOrder(data.ratings, entrants.length);
527
+ const rows = matrixCells(data.matrix, order);
528
+ const names = entrants.map((entrant) => entrantName(entrant));
529
+ const head = h('tr', {}, h('td', { class: 'cup-matrix-corner small muted' }, t('tournament.results.matrix.corner')),
530
+ order.map((index) => h('th', { scope: 'col' }, names[index])));
531
+ const body = rows.map((row) => h('tr', {},
532
+ h('th', { scope: 'row' }, names[row.entrant]),
533
+ row.cells.map((cell) => {
534
+ const name = cellName(names[cell.row], names[cell.column], cell);
535
+ return h('td', { class: `cup-heat heat-${cell.tone}${cell.level ? ` heat-${cell.tone}-${cell.level}` : ''}`, 'aria-label': name, title: name },
536
+ cell.self || cell.tone === 'none' ? h('span', { class: 'muted', 'aria-hidden': 'true' }, t('tournament.results.cell.empty'))
537
+ : [h('span', { class: 'cup-heat-mean', 'aria-hidden': 'true' }, signedMean(cell.mean)),
538
+ h('span', { class: 'cup-heat-count small', 'aria-hidden': 'true' }, formatNumber(cell.matches))]);
539
+ })));
540
+ return h('section', { class: 'cup-matrix', 'aria-labelledby': 'cup-matrix-title' },
541
+ h('h4', { id: 'cup-matrix-title' }, t('tournament.results.matrix')),
542
+ h('div', { class: 'cup-matrix-scroll', tabindex: '0', 'data-scroll': '', 'data-focus': 'results-matrix', role: 'region', 'aria-label': t('tournament.results.matrix.scroll') },
543
+ h('table', {}, h('caption', { class: 'muted small' }, t('tournament.results.matrix.caption')),
544
+ h('thead', {}, head), h('tbody', {}, body))),
545
+ h('p', { class: 'cup-heat-legend small muted' },
546
+ h('span', { class: 'cup-heat-swatch heat-up-4', 'aria-hidden': 'true' }), t('tournament.results.legend.up'),
547
+ h('span', { class: 'cup-heat-swatch heat-down-4', 'aria-hidden': 'true' }), t('tournament.results.legend.down')));
548
+ }
549
+
550
+ // The files of a tournament's results, each a link that saves it.
551
+ function filesList(cup) {
552
+ return h('section', { class: 'cup-files', 'aria-labelledby': 'cup-files-title' },
553
+ h('h4', { id: 'cup-files-title' }, t('tournament.results.files')),
554
+ h('p', { class: 'muted small' }, t('tournament.results.files.note')),
555
+ h('ul', { class: 'cup-file-list row' }, RESULT_FILES.map((name) => h('li', {},
556
+ h('a', {
557
+ class: 'btn small ghost', href: fileAddress(cup.id, name), download: `${cup.id}-${name}`, 'data-focus': `results-file-${name}`,
558
+ 'aria-label': t('tournament.results.file', { name }),
559
+ }, icon('download'), name)))));
560
+ }