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,497 @@
1
+ // Copyright 2026 The cuju Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Surprise me, a seeded generator of harmonious teams. A seed and a kit style give the same name,
5
+ // badge, kit, goalkeeper kit, number color, fans and roster names on every machine. Colors come
6
+ // from color schemes in CIE LCh, are checked for readable numbers by the WCAG contrast ratio, for
7
+ // shirts and shorts apart by CIEDE2000, and against common kits by the viewer's own clash measure.
8
+ // The module runs in the page and in Node, where the three package or a stub of it serves the
9
+ // import of kits.js.
10
+
11
+ import {
12
+ BUILTIN_KIT, DEFAULT_GOALKEEPER, DEFAULT_KIT, FORMATS, KIT_CLASH, ciede2000, deltaE, kitDifference,
13
+ labToRgb, rgbToLab,
14
+ } from '../players/kits.js';
15
+
16
+ // The kit styles, each setting the pattern, the color schemes it draws from and how the colors
17
+ // relate. Minimal wears shorts of the shirt's color.
18
+ export const KIT_STYLES = [
19
+ { id: 'stripes', name: 'Classic stripes', pattern: 'stripes', schemes: ['complementary', 'split', 'analogous', 'monochrome'] },
20
+ { id: 'hoops', name: 'Retro hoops', pattern: 'hoops', schemes: ['analogous', 'triadic', 'complementary'] },
21
+ { id: 'sash', name: 'Sash', pattern: 'sash', schemes: ['split', 'complementary', 'triadic'] },
22
+ { id: 'halves', name: 'Halves', pattern: 'halves', schemes: ['complementary', 'triadic', 'split'] },
23
+ { id: 'checkers', name: 'Checkers', pattern: 'checkers', schemes: ['complementary', 'monochrome', 'analogous'] },
24
+ { id: 'minimal', name: 'Minimal', pattern: 'plain', schemes: ['monochrome', 'analogous'], sameShorts: true },
25
+ { id: 'neon', name: 'Bold neon', pattern: 'plain', schemes: ['complementary', 'triadic', 'split'] },
26
+ ];
27
+ export const STYLE_IDS = ['any', ...KIT_STYLES.map((s) => s.id)];
28
+
29
+ // The hue offsets of each scheme in degrees, from the shirt's hue.
30
+ const SCHEMES = {
31
+ complementary: { name: 'complementary', hues: [180] },
32
+ split: { name: 'split complementary', hues: [150, 210] },
33
+ analogous: { name: 'analogous', hues: [30, -30, 45, -45] },
34
+ triadic: { name: 'triadic', hues: [120, 240] },
35
+ monochrome: { name: 'monochrome', hues: [0] },
36
+ };
37
+
38
+ // The smallest CIEDE2000 between the shirt and its pattern, and between the shirt and the shorts.
39
+ export const MIN_PATTERN = 25;
40
+ export const MIN_SHORTS = 20;
41
+ // The smallest WCAG contrast ratio between the numbers and the shirt.
42
+ export const MIN_NUMBER_CONTRAST = 3;
43
+ // The numbers as kits.js numberColor draws them.
44
+ const NUMBER_RGB = { white: [250, 250, 250], black: [20, 20, 20] };
45
+
46
+ const plainKit = (c, shorts = c) => ({ pattern: 'plain', primary: c, secondary: c, shorts, socks: c });
47
+
48
+ // The kits a generated team must stay clear of, by the measure the viewer uses to pick an away kit.
49
+ export const COMMON_KITS = [
50
+ { name: 'Built-in AI', kit: BUILTIN_KIT },
51
+ { name: 'Default', kit: DEFAULT_KIT },
52
+ { name: 'Red', kit: plainKit([200, 30, 45]) },
53
+ { name: 'White', kit: plainKit([245, 245, 245]) },
54
+ { name: 'Blue', kit: plainKit([25, 70, 190]) },
55
+ { name: 'Black', kit: plainKit([22, 22, 26]) },
56
+ { name: 'Yellow', kit: plainKit([250, 210, 20]) },
57
+ { name: 'Green', kit: plainKit([20, 140, 60]) },
58
+ ];
59
+
60
+ const NEUTRALS = {
61
+ white: [246, 246, 244],
62
+ cream: [238, 228, 204],
63
+ black: [22, 22, 26],
64
+ charcoal: [52, 54, 62],
65
+ };
66
+
67
+ const PLACES = [
68
+ 'Harbor', 'Northgate', 'Eastbrook', 'Westmere', 'Kestrel Bay', 'Mossvale', 'Ironcrest',
69
+ 'Silverholm', 'Ashmere', 'Brightwater', 'Elmridge', 'Foxhollow', 'Glenwick', 'Highmoor',
70
+ 'Larkspur', 'Oakhaven', 'Pinecrest', 'Ravenmoor', 'Saltmarsh', 'Valewood', 'Willowmere',
71
+ 'Amberfield', 'Duskwater', 'Embervale', 'Frostholm', 'Hollowmere', 'Juniper Point',
72
+ 'Kingsreach', 'Lanternside', 'Pebblebrook', 'Seabright', 'Tidewater', 'Marigold Hill',
73
+ 'Copperdale', 'Starling Cove', 'Briarwood', 'Cloudmere', 'Thistlebank', 'Quillford Heath',
74
+ 'Nettlecombe Vale',
75
+ ];
76
+ const ADJECTIVES = [
77
+ 'Crimson', 'Amber', 'Cobalt', 'Copper', 'Velvet', 'Electric', 'Midnight', 'Roaring', 'Silver',
78
+ 'Swift', 'Daring', 'Flying', 'Wandering', 'Rising', 'Lucky', 'Jolly', 'Bouncing', 'Dashing',
79
+ 'Sparkling', 'Cosmic', 'Thundering', 'Frosty', 'Sunny', 'Howling', 'Gallant', 'Nimble',
80
+ 'Fearless', 'Merry', 'Stormy', 'Twilight',
81
+ ];
82
+ const CREATURES = [
83
+ 'Herons', 'Comets', 'Otters', 'Badgers', 'Lynxes', 'Hares', 'Kestrels', 'Owls', 'Puffins',
84
+ 'Moths', 'Newts', 'Geese', 'Bison', 'Yaks', 'Narwhals', 'Pelicans', 'Meteors', 'Stags',
85
+ 'Salamanders', 'Hedgehogs', 'Mantises', 'Albatrosses', 'Squirrels', 'Walruses', 'Fireflies',
86
+ 'Lemurs', 'Ibexes', 'Marmots', 'Cranes', 'Dragonflies', 'Starlings', 'Wombats',
87
+ ];
88
+ const CLUB_WORDS = ['United', 'Athletic', 'Rovers', 'Wanderers', 'Town', 'City', 'Albion', 'Harriers', 'Rangers'];
89
+ const PREFIX_WORDS = ['Sporting', 'Athletic'];
90
+ const SUFFIX_WORDS = ['FC', 'AFC'];
91
+ const SURNAMES = [
92
+ 'Ashby', 'Barlow', 'Crane', 'Dalton', 'Ellery', 'Finch', 'Garnett', 'Hale', 'Ingram', 'Jessop',
93
+ 'Kerr', 'Lowe', 'Marsh', 'Nash', 'Oakley', 'Pryce', 'Quill', 'Rook', 'Sayer', 'Thorne',
94
+ 'Underhill', 'Vance', 'Wilde', 'Yates', 'Okafor', 'Tanaka', 'Silva', 'Novak', 'Haddad',
95
+ 'Moreau', 'Kowal', 'Larsen', 'Duarte', 'Ferreira', 'Mensah', 'Petrov', 'Ruiz', 'Sato', 'Varga',
96
+ 'Weber', 'Zhou', 'Ibrahim', 'Kaur', 'Nakamura', 'Rossi', 'Santos', 'Lindqvist', 'Brennan',
97
+ 'Moyo', 'Castillo', 'Demir', 'Faure', 'Achebe', 'Bergström', 'Costa', 'Delgado', 'Eriksen',
98
+ 'Fontaine', 'Gallo', 'Hughes', 'Iwu', 'Jovanovic', 'Kim', 'Lopes', 'Mbeki', 'Nilsen',
99
+ ];
100
+
101
+ // The 32 bit number of a seed, FNV-1a over the code units of its text.
102
+ export function seedNumber(seed) {
103
+ const text = String(seed).trim();
104
+ let h = 0x811c9dc5;
105
+ for (let i = 0; i < text.length; i++) {
106
+ h ^= text.charCodeAt(i);
107
+ h = Math.imul(h, 0x01000193);
108
+ }
109
+ return h >>> 0;
110
+ }
111
+
112
+ // Mulberry32, a small generator of numbers in [0, 1) from a 32 bit state.
113
+ export function mulberry32(a) {
114
+ let state = a >>> 0;
115
+ return () => {
116
+ state = (state + 0x6d2b79f5) >>> 0;
117
+ let t = state;
118
+ t = Math.imul(t ^ (t >>> 15), t | 1);
119
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
120
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
121
+ };
122
+ }
123
+
124
+ // A fresh seed of six digits, for a surprise asked without one.
125
+ export function drawSeed() {
126
+ let n;
127
+ const c = globalThis.crypto;
128
+ if (c && typeof c.getRandomValues === 'function') {
129
+ n = c.getRandomValues(new Uint32Array(1))[0];
130
+ } else {
131
+ n = Math.floor(Math.random() * 4294967296);
132
+ }
133
+ return String(100000 + (n % 900000));
134
+ }
135
+
136
+ function linear(v) {
137
+ const c = v / 255;
138
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
139
+ }
140
+
141
+ // The WCAG relative luminance of an sRGB color.
142
+ export function luminance(c) {
143
+ return 0.2126 * linear(c[0]) + 0.7152 * linear(c[1]) + 0.0722 * linear(c[2]);
144
+ }
145
+
146
+ // The WCAG contrast ratio of two sRGB colors, from 1 to 21.
147
+ export function contrastRatio(a, b) {
148
+ const la = luminance(a);
149
+ const lb = luminance(b);
150
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
151
+ }
152
+
153
+ // The rendered color of the numbers on a kit, as kits.js numberColor draws it.
154
+ export function numberRgb(number, kit) {
155
+ return number === 'secondary' ? kit.secondary : NUMBER_RGB[number] || NUMBER_RGB.white;
156
+ }
157
+
158
+ // An sRGB color of whole numbers from a lightness, chroma and hue in degrees, the chroma lowered
159
+ // until the color sits inside the gamut.
160
+ function lch(L, C, hue) {
161
+ const l = Math.max(2, Math.min(98, L));
162
+ const r = (((hue % 360) + 360) % 360) * Math.PI / 180;
163
+ let c = C;
164
+ for (let k = 0; k < 60 && c >= 1; k++) {
165
+ const lab = [l, c * Math.cos(r), c * Math.sin(r)];
166
+ const rgb = labToRgb(lab).map((v) => Math.round(v));
167
+ if (ciede2000(rgbToLab(rgb), lab) < 2) {
168
+ return rgb;
169
+ }
170
+ c *= 0.9;
171
+ }
172
+ return labToRgb([l, 0, 0]).map((v) => Math.round(v));
173
+ }
174
+
175
+ function shade(rgb, dL) {
176
+ const lab = rgbToLab(rgb);
177
+ const C = Math.hypot(lab[1], lab[2]);
178
+ const hue = Math.atan2(lab[2], lab[1]) * 180 / Math.PI;
179
+ return lch(Math.max(4, Math.min(97, lab[0] + dL)), C, hue);
180
+ }
181
+
182
+ const pick = (rng, list) => list[Math.floor(rng() * list.length)];
183
+ const range = (rng, lo, hi) => lo + (hi - lo) * rng();
184
+
185
+ // A neutral far in lightness from the color, light on dark and dark on light.
186
+ function neutralFor(rng, rgb) {
187
+ const L = rgbToLab(rgb)[0];
188
+ if (L < 55) {
189
+ return NEUTRALS[rng() < 0.7 ? 'white' : 'cream'].slice();
190
+ }
191
+ return NEUTRALS[rng() < 0.7 ? 'black' : 'charcoal'].slice();
192
+ }
193
+
194
+ // Neon hues in degrees of CIE LCh with the lightness where sRGB holds their brightest chroma, green,
195
+ // pink, hot pink, orange, cyan, lime, aqua, purple and coral.
196
+ const NEON = [[135, 85], [330, 60], [356, 57], [47, 62], [196, 88], [114, 92], [168, 88], [318, 52], [19, 60]];
197
+
198
+ function hueGap(a, b) {
199
+ const d = Math.abs((((a - b) % 360) + 360) % 360);
200
+ return Math.min(d, 360 - d);
201
+ }
202
+
203
+ // A neon color near a hue, its lightness and hue jittered.
204
+ function neon(rng, hue) {
205
+ const [h, L] = NEON.reduce((best, n) => (hueGap(n[0], hue) < hueGap(best[0], hue) ? n : best));
206
+ return lch(L + range(rng, -5, 5), 130, h + range(rng, -6, 6));
207
+ }
208
+
209
+ // The outfield kit of a style and scheme, before any check.
210
+ function drawKit(rng, look, scheme) {
211
+ const hue = look.id === 'neon' ? pick(rng, NEON)[0] : range(rng, 0, 360);
212
+ const offsets = SCHEMES[scheme].hues;
213
+ const other = hue + pick(rng, offsets);
214
+ let primary;
215
+ let secondary;
216
+ let shorts;
217
+ let socks;
218
+ if (look.id === 'neon') {
219
+ primary = neon(rng, hue);
220
+ secondary = rng() < 0.5 ? NEUTRALS.black.slice() : neon(rng, other);
221
+ shorts = rng() < 0.75 ? NEUTRALS.black.slice() : NEUTRALS.charcoal.slice();
222
+ socks = rng() < 0.6 ? primary.slice() : secondary.slice();
223
+ } else if (look.id === 'minimal') {
224
+ const L = range(rng, 22, 78);
225
+ primary = lch(L, range(rng, 15, 60), hue);
226
+ const trimHue = scheme === 'monochrome' ? hue : other;
227
+ secondary = lch(L + (L < 50 ? 1 : -1) * range(rng, 14, 24), range(rng, 10, 50), trimHue);
228
+ shorts = primary.slice();
229
+ socks = primary.slice();
230
+ } else if (look.id === 'hoops') {
231
+ // Retro, softer chroma and a cream or a muted second color.
232
+ primary = lch(range(rng, 30, 68), range(rng, 25, 55), hue);
233
+ secondary = rng() < 0.45 ? NEUTRALS.cream.slice()
234
+ : lch(range(rng, 25, 80), range(rng, 20, 50), scheme === 'monochrome' ? hue : other);
235
+ shorts = rng() < 0.5 ? NEUTRALS.cream.slice() : shade(primary, -range(rng, 18, 30));
236
+ socks = rng() < 0.5 ? primary.slice() : secondary.slice();
237
+ } else {
238
+ primary = lch(range(rng, 28, 82), range(rng, 35, 95), hue);
239
+ if (scheme === 'monochrome') {
240
+ const L = rgbToLab(primary)[0];
241
+ secondary = lch(L < 55 ? range(rng, L + 35, 96) : range(rng, 8, L - 35), range(rng, 10, 60), hue);
242
+ } else if (rng() < 0.35 && look.id !== 'halves') {
243
+ secondary = neutralFor(rng, primary);
244
+ } else {
245
+ secondary = lch(range(rng, 25, 88), range(rng, 35, 95), other);
246
+ }
247
+ const r = rng();
248
+ if (r < 0.4) {
249
+ shorts = neutralFor(rng, primary);
250
+ } else if (r < 0.7) {
251
+ shorts = secondary.slice();
252
+ } else {
253
+ shorts = shade(primary, rgbToLab(primary)[0] > 50 ? -range(rng, 30, 45) : range(rng, 30, 45));
254
+ }
255
+ const s = rng();
256
+ socks = (s < 0.5 ? primary : s < 0.8 ? secondary : shorts).slice();
257
+ }
258
+ return { pattern: look.pattern, primary, secondary, shorts, socks };
259
+ }
260
+
261
+ // A goalkeeper kit whose shirt keeps the numbers readable.
262
+ function drawKeeper(rng, number) {
263
+ const hue = range(rng, 0, 360);
264
+ let L;
265
+ if (number === 'white') {
266
+ L = range(rng, 18, 50);
267
+ } else if (number === 'black') {
268
+ L = range(rng, 62, 92);
269
+ } else {
270
+ L = range(rng, 20, 85);
271
+ }
272
+ const primary = lch(L, range(rng, 35, 100), hue);
273
+ let secondary;
274
+ if (number === 'secondary') {
275
+ secondary = neutralFor(rng, primary);
276
+ } else {
277
+ secondary = shade(primary, L > 50 ? -range(rng, 25, 40) : range(rng, 25, 40));
278
+ }
279
+ const shorts = rng() < 0.5 ? shade(primary, -range(rng, 25, 40)) : (L > 50 ? NEUTRALS.charcoal.slice() : NEUTRALS.black.slice());
280
+ return { primary, secondary, shorts, socks: primary.slice() };
281
+ }
282
+
283
+ const keeperKit = (gk) => ({ pattern: 'plain', ...gk });
284
+ const swappedKit = (kit) => ({ ...kit, primary: kit.secondary, secondary: kit.primary, shorts: kit.socks, socks: kit.shorts });
285
+
286
+ // The problems of the outfield kit alone, an empty list when it passes.
287
+ function kitProblems(kit, look) {
288
+ const out = [];
289
+ if (kit.pattern !== 'plain' && deltaE(kit.primary, kit.secondary) < MIN_PATTERN) {
290
+ out.push('pattern too close to the shirt');
291
+ }
292
+ if (look && look.sameShorts) {
293
+ if (kit.shorts.join() !== kit.primary.join()) {
294
+ out.push('shorts differ from the shirt in a style that matches them');
295
+ }
296
+ } else if (deltaE(kit.primary, kit.shorts) < MIN_SHORTS) {
297
+ out.push('shorts too close to the shirt');
298
+ }
299
+ for (const c of COMMON_KITS) {
300
+ if (kitDifference(kit, c.kit) < KIT_CLASH) {
301
+ out.push(`clashes with ${c.name}`);
302
+ }
303
+ }
304
+ return out;
305
+ }
306
+
307
+ // The problems of the goalkeeper kit and the numbers, an empty list when they pass.
308
+ function keeperProblems(kit, gk, number) {
309
+ const out = [];
310
+ for (const [label, k] of [['shirt', kit], ['goalkeeper shirt', gk]]) {
311
+ if (contrastRatio(numberRgb(number, k), k.primary) < MIN_NUMBER_CONTRAST) {
312
+ out.push(`numbers unreadable on the ${label}`);
313
+ }
314
+ }
315
+ const keeper = keeperKit(gk);
316
+ if (kitDifference(keeper, kit) < KIT_CLASH || kitDifference(keeper, swappedKit(kit)) < KIT_CLASH) {
317
+ out.push('goalkeeper looks like the outfield kit');
318
+ }
319
+ if (kitDifference(keeper, keeperKit(DEFAULT_GOALKEEPER)) < KIT_CLASH) {
320
+ out.push('goalkeeper looks like the default goalkeeper');
321
+ }
322
+ for (const c of COMMON_KITS) {
323
+ if (kitDifference(keeper, c.kit) < KIT_CLASH) {
324
+ out.push(`goalkeeper clashes with ${c.name}`);
325
+ }
326
+ }
327
+ return out;
328
+ }
329
+
330
+ // Every problem of a generated style, by the rules above, an empty list for a sound team. The
331
+ // kit style id tells whether the shorts should match the shirt.
332
+ export function styleProblems(style, kitStyle) {
333
+ const look = KIT_STYLES.find((s) => s.id === kitStyle);
334
+ const out = [...kitProblems(style.kit, look), ...keeperProblems(style.kit, style.goalkeeper, style.number)];
335
+ const colors = [
336
+ ...['primary', 'secondary', 'shorts', 'socks'].flatMap((k) => [style.kit[k], style.goalkeeper[k]]),
337
+ ...style.crowd.colors,
338
+ ];
339
+ if (!colors.every((c) => Array.isArray(c) && c.length === 3 && c.every((v) => Number.isInteger(v) && v >= 0 && v <= 255))) {
340
+ out.push('a color is not three whole numbers from 0 to 255');
341
+ }
342
+ return out;
343
+ }
344
+
345
+ // The number colors to try for a kit, the best of white and black first.
346
+ function numberChoices(rng, kit) {
347
+ const white = contrastRatio(NUMBER_RGB.white, kit.primary);
348
+ const black = contrastRatio(NUMBER_RGB.black, kit.primary);
349
+ const order = white >= black ? ['white', 'black'] : ['black', 'white'];
350
+ if (kit.pattern === 'plain' && rng() < 0.35) {
351
+ order.unshift('secondary');
352
+ }
353
+ return order.filter((n) => contrastRatio(numberRgb(n, kit), kit.primary) >= MIN_NUMBER_CONTRAST);
354
+ }
355
+
356
+ const clone = (v) => JSON.parse(JSON.stringify(v));
357
+
358
+ // A safe style for the rare seed that draws nothing that passes, purple and white, purple and
359
+ // orange over black shorts for a pattern, plum for Minimal, with a teal goalkeeper.
360
+ export function fallbackStyle(look) {
361
+ const purple = [120, 40, 150];
362
+ const plum = [100, 30, 90];
363
+ let kit;
364
+ if (look.sameShorts) {
365
+ kit = { primary: plum, secondary: [246, 246, 244], shorts: plum, socks: plum };
366
+ } else if (look.pattern === 'plain') {
367
+ kit = { primary: purple, secondary: [246, 246, 244], shorts: [246, 246, 244], socks: purple };
368
+ } else {
369
+ kit = { primary: purple, secondary: [240, 150, 30], shorts: [22, 22, 26], socks: purple };
370
+ }
371
+ const goalkeeper = { primary: [0, 128, 128], secondary: [52, 54, 62], shorts: [52, 54, 62], socks: [0, 128, 128] };
372
+ return clone({ kit: { pattern: look.pattern, ...kit }, goalkeeper, number: 'white' });
373
+ }
374
+
375
+ // The colors of a style and scheme that pass every check, drawn from the generator.
376
+ function drawStyle(rng, look, scheme) {
377
+ for (let attempt = 0; attempt < 400; attempt++) {
378
+ const kit = drawKit(rng, look, scheme);
379
+ if (kitProblems(kit, look).length) {
380
+ continue;
381
+ }
382
+ for (const number of numberChoices(rng, kit)) {
383
+ for (let k = 0; k < 40; k++) {
384
+ const gk = drawKeeper(rng, number);
385
+ if (!keeperProblems(kit, gk, number).length) {
386
+ return { kit, goalkeeper: gk, number };
387
+ }
388
+ }
389
+ }
390
+ }
391
+ return fallbackStyle(look);
392
+ }
393
+
394
+ const words = (name) => name.split(/\s+/).filter(Boolean);
395
+ const initial = (w) => w[0].toUpperCase();
396
+
397
+ // A badge of up to three letters, the initials of the name without FC and AFC, or the first
398
+ // letters of a single word.
399
+ export function badgeFor(name) {
400
+ const parts = words(String(name || '').replace(/[^\p{L}\p{N}\s]/gu, ' ')).filter((w) => !SUFFIX_WORDS.includes(w.toUpperCase()));
401
+ if (!parts.length) {
402
+ return '';
403
+ }
404
+ if (parts.length === 1) {
405
+ return Array.from(parts[0]).slice(0, 3).join('').toUpperCase();
406
+ }
407
+ return parts.slice(0, 3).map(initial).join('');
408
+ }
409
+
410
+ // A club name from the word lists, often alliterating.
411
+ function drawName(rng) {
412
+ const alliterate = (first, list) => {
413
+ const same = list.filter((w) => w[0] === first[0]);
414
+ return same.length && rng() < 0.5 ? pick(rng, same) : pick(rng, list);
415
+ };
416
+ const form = rng();
417
+ if (form < 0.3) {
418
+ const adjective = pick(rng, ADJECTIVES);
419
+ return `${adjective} ${alliterate(adjective, CREATURES)}`;
420
+ }
421
+ const place = pick(rng, PLACES);
422
+ if (form < 0.55) {
423
+ return `${place} ${alliterate(place, CREATURES)}`;
424
+ }
425
+ if (form < 0.7) {
426
+ return `${place} ${alliterate(place, CREATURES)} ${pick(rng, SUFFIX_WORDS)}`;
427
+ }
428
+ if (form < 0.9) {
429
+ return `${place} ${pick(rng, CLUB_WORDS)}`;
430
+ }
431
+ return `${pick(rng, PREFIX_WORDS)} ${place}`;
432
+ }
433
+
434
+ // Distinct surnames for a roster, in a shuffled order.
435
+ function drawRoster(rng, n) {
436
+ const list = SURNAMES.slice();
437
+ for (let i = list.length - 1; i > 0; i--) {
438
+ const j = Math.floor(rng() * (i + 1));
439
+ [list[i], list[j]] = [list[j], list[i]];
440
+ }
441
+ return list.slice(0, n);
442
+ }
443
+
444
+ // A whole team from a seed and a kit style.
445
+ //
446
+ // options.seed Any text or number. Empty or absent draws a fresh seed of six digits, which the
447
+ // result names, so the team can be made again.
448
+ // options.style A kit style id from KIT_STYLES, or any, the default, for one chosen by the seed.
449
+ // options.format 5v5 or 11v11, the default, the length of the roster names.
450
+ //
451
+ // Returns { seed, kitStyle, kitStyleName, scheme, name, badge, style }, where style is a partial
452
+ // style that PATCH /api/teams/<id> takes as it is, with the kit, goalkeeper kit, badge, number
453
+ // color, fan colors and roster names.
454
+ export function surprise(options = {}) {
455
+ const given = options.seed;
456
+ if (given !== undefined && given !== null && !['string', 'number'].includes(typeof given)) {
457
+ throw new Error(`expected the seed as text or a number, found ${Array.isArray(given) ? 'a list' : typeof given === 'object' ? 'an object' : `a ${typeof given}`}`);
458
+ }
459
+ const seed = given === undefined || given === null || String(given).trim() === '' ? drawSeed() : String(given).trim();
460
+ const wanted = options.style === undefined || options.style === null || options.style === '' ? 'any' : String(options.style);
461
+ if (!STYLE_IDS.includes(wanted)) {
462
+ throw new Error(`expected a kit style among ${STYLE_IDS.join(', ')}, found ${wanted}`);
463
+ }
464
+ const format = options.format === undefined ? '11v11' : String(options.format);
465
+ if (!FORMATS[format]) {
466
+ throw new Error(`expected the format 5v5 or 11v11, found ${format}`);
467
+ }
468
+ const rng = mulberry32(seedNumber(seed));
469
+ // The name and roster draw from a generator of their own, so the same seed keeps its name
470
+ // across kit styles. Any picks the style from a third, so a seed with any makes the same team
471
+ // as that seed with the style it picked.
472
+ const naming = mulberry32(seedNumber(`${seed}:name`));
473
+ const look = wanted === 'any' ? pick(mulberry32(seedNumber(`${seed}:style`)), KIT_STYLES)
474
+ : KIT_STYLES.find((s) => s.id === wanted);
475
+ const scheme = pick(rng, look.schemes);
476
+ const colors = drawStyle(rng, look, scheme);
477
+ const name = drawName(naming);
478
+ const badge = badgeFor(name);
479
+ const roster = drawRoster(naming, FORMATS[format]);
480
+ const style = {
481
+ kit: colors.kit,
482
+ goalkeeper: colors.goalkeeper,
483
+ badge,
484
+ number: colors.number,
485
+ crowd: { colors: [colors.kit.primary.slice(), colors.kit.secondary.slice()] },
486
+ players: roster.map((n) => ({ name: n })),
487
+ };
488
+ return {
489
+ seed,
490
+ kitStyle: look.id,
491
+ kitStyleName: look.name,
492
+ scheme: SCHEMES[scheme].name,
493
+ name,
494
+ badge,
495
+ style: clone(style),
496
+ };
497
+ }
@@ -0,0 +1,127 @@
1
+ // Copyright 2026 The cuju Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // The check of a registered team under strict rules, on its card of the Teams page. The team
5
+ // loads in a process of its own and plays a few steps against the built-in AI within the default
6
+ // limits of strict rules, and the card says whether it passed, the rule it broke otherwise and its
7
+ // timings. The checks outlive the redraws of the list, so a check that runs keeps its card busy
8
+ // and its answer reaches the card that shows the team then.
9
+
10
+ import { serverText, t } from '../core/i18n.js';
11
+ import { el as h } from '../core/menu.js';
12
+ import { icon } from '../core/icons.js';
13
+ import { teamName } from '../core/names.js';
14
+ import { checkSides, folderText, ruleReason, sideText, timingsText, warningTexts } from '../play/tournament-results.js';
15
+
16
+ // A request to the server that answers JSON, which throws an error with the server's reason.
17
+ export async function jsonCall(method, path, json) {
18
+ let res;
19
+ try {
20
+ res = await fetch(path, {
21
+ method,
22
+ cache: 'no-store',
23
+ headers: json === undefined ? {} : { 'Content-Type': 'application/json' },
24
+ body: json === undefined ? undefined : JSON.stringify(json),
25
+ });
26
+ } catch {
27
+ const error = new Error(t('api.unreachable', { origin: location.origin }));
28
+ error.status = 0;
29
+ throw error;
30
+ }
31
+ const text = await res.text();
32
+ let body = null;
33
+ try {
34
+ body = text ? JSON.parse(text) : null;
35
+ } catch {
36
+ body = null;
37
+ }
38
+ if (!res.ok) {
39
+ const error = new Error((body && body.error && serverText(body, 'error')) || t('tournament.api.status', { status: res.status }));
40
+ error.status = res.status;
41
+ error.id = (body && body.error_id) || null;
42
+ error.values = (body && body.error_values) || {};
43
+ throw error;
44
+ }
45
+ return body;
46
+ }
47
+
48
+ // The checks by team, each busy, its report or its error, and the card that shows each team now.
49
+ const checks = new Map();
50
+ const cards = new Map();
51
+
52
+ // Fills the part of a card that holds the check of its team. A check that runs keeps its button,
53
+ // which says so and takes no click, so the focus stays on it until the answer.
54
+ function draw(node, team) {
55
+ const check = checks.get(team.id) || null;
56
+ const busy = !!check && check.busy;
57
+ const button = h('button', {
58
+ type: 'button', class: 'btn small ghost team-check-run', 'aria-disabled': busy ? 'true' : 'false',
59
+ 'aria-label': t('tournament.teamCheck.label', { name: teamName(team) }),
60
+ onclick: (e) => {
61
+ e.stopPropagation();
62
+ run(team);
63
+ },
64
+ }, icon(busy ? 'running' : 'speed'), t(busy ? 'tournament.teamCheck.busy' : 'tournament.teamCheck.button'));
65
+ const status = h('div', { class: 'team-check-status small', role: 'status', 'aria-busy': busy ? 'true' : 'false' });
66
+ if (busy) {
67
+ status.append(t('tournament.teamCheck.running'));
68
+ } else if (check && check.error) {
69
+ status.classList.add('failed');
70
+ status.append(icon('failed', 'icon text-symbol'), check.error);
71
+ } else if (check && check.report) {
72
+ // The check plays the team on the left and then on the right, each side with its timings,
73
+ // and names the size of its folder and any warning, which never fails the check.
74
+ const report = check.report;
75
+ const sides = checkSides(report);
76
+ const timings = sides.length ? '' : timingsText(report.timings);
77
+ const folder = folderText(report);
78
+ const warnings = warningTexts(report);
79
+ status.classList.add(report.ok ? 'passed' : 'failed');
80
+ status.append(...[icon(report.ok ? 'done' : 'failed', 'icon text-symbol'),
81
+ report.ok ? t('tournament.teamCheck.passed') : t('tournament.teamCheck.failed', { reason: ruleReason(report.rule) }),
82
+ timings ? h('span', { class: 'muted team-check-timings' }, `, ${timings}`) : null,
83
+ folder ? h('span', { class: 'muted' }, `, ${folder}`) : null,
84
+ sides.length || warnings.length ? h('ul', { class: 'team-check-sides symbol-list' },
85
+ sides.map((side) => h('li', { class: side.skipped ? 'skipped muted' : side.ok ? 'passed' : 'failed' }, sideText(side))),
86
+ warnings.map((text) => h('li', { class: 'warning' }, icon('warning', 'icon text-symbol'), text))) : null].filter(Boolean));
87
+ }
88
+ node.replaceChildren(button, status);
89
+ }
90
+
91
+ // Runs the check of a team, once at a time, and draws its answer on the team's card.
92
+ async function run(team) {
93
+ if (checks.get(team.id)?.busy) {
94
+ return;
95
+ }
96
+ checks.set(team.id, { busy: true });
97
+ redraw(team);
98
+ let found;
99
+ try {
100
+ found = { report: await jsonCall('POST', `/api/teams/${encodeURIComponent(team.id)}/preflight`, {}) };
101
+ } catch (err) {
102
+ found = { error: err.message };
103
+ }
104
+ checks.set(team.id, found);
105
+ redraw(team);
106
+ }
107
+
108
+ // Draws the card that shows a team now, and keeps the focus on its button when it held it.
109
+ function redraw(team) {
110
+ const node = cards.get(team.id);
111
+ if (!node || !node.isConnected) {
112
+ return;
113
+ }
114
+ const had = node.contains(document.activeElement);
115
+ draw(node, team);
116
+ if (had) {
117
+ node.querySelector('.team-check-run')?.focus();
118
+ }
119
+ }
120
+
121
+ // The check of a team for its card, a button and the line of its answer.
122
+ export function teamCheck(team) {
123
+ const node = h('div', { class: 'team-check', 'data-team': team.id });
124
+ cards.set(team.id, node);
125
+ draw(node, team);
126
+ return node;
127
+ }