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.
- cuju/render/__init__.py +759 -0
- cuju/render/__main__.py +1065 -0
- cuju/render/_export/__init__.py +3 -0
- cuju/render/_export/feed.py +542 -0
- cuju/render/_export/sizes.py +157 -0
- cuju/render/_export/video.py +1618 -0
- cuju/render/_export/video_jobs.py +408 -0
- cuju/render/_format/__init__.py +3 -0
- cuju/render/_format/annotations.py +235 -0
- cuju/render/_format/codec.py +307 -0
- cuju/render/_format/keep.py +121 -0
- cuju/render/_format/recording.py +860 -0
- cuju/render/_format/writer.py +54 -0
- cuju/render/_guide/__init__.py +3 -0
- cuju/render/_guide/doctor.py +523 -0
- cuju/render/_guide/help.py +62 -0
- cuju/render/_guide/tutorial.py +254 -0
- cuju/render/_http/__init__.py +3 -0
- cuju/render/_http/commands.py +57 -0
- cuju/render/_http/headers.py +103 -0
- cuju/render/_http/launch.py +500 -0
- cuju/render/_http/launchers.py +408 -0
- cuju/render/_http/pwa.py +128 -0
- cuju/render/_http/server.py +2105 -0
- cuju/render/_http/window.py +360 -0
- cuju/render/_moments/__init__.py +3 -0
- cuju/render/_moments/captions.py +284 -0
- cuju/render/_moments/events.py +1123 -0
- cuju/render/_moments/search.py +694 -0
- cuju/render/_packages/__init__.py +3 -0
- cuju/render/_packages/glsl.py +746 -0
- cuju/render/_packages/loudness.py +334 -0
- cuju/render/_packages/pack.py +1959 -0
- cuju/render/_packages/presentation.py +1376 -0
- cuju/render/_packages/vocabulary.py +609 -0
- cuju/render/_playing/__init__.py +3 -0
- cuju/render/_playing/achievements.py +463 -0
- cuju/render/_playing/bench.py +609 -0
- cuju/render/_playing/challenges.py +1813 -0
- cuju/render/_playing/demo.py +323 -0
- cuju/render/_playing/draw.py +211 -0
- cuju/render/_playing/errors.py +20 -0
- cuju/render/_playing/game.py +645 -0
- cuju/render/_playing/limits.py +339 -0
- cuju/render/_playing/play.py +926 -0
- cuju/render/_playing/policy_worker.py +250 -0
- cuju/render/_playing/strict.py +810 -0
- cuju/render/_registry/__init__.py +3 -0
- cuju/render/_registry/clips.py +394 -0
- cuju/render/_registry/joints.py +108 -0
- cuju/render/_registry/teams.py +2058 -0
- cuju/render/_store/__init__.py +3 -0
- cuju/render/_store/home.py +70 -0
- cuju/render/_store/library.py +604 -0
- cuju/render/_store/samples.py +163 -0
- cuju/render/_text/__init__.py +3 -0
- cuju/render/_text/languages.py +637 -0
- cuju/render/_text/messages.py +1396 -0
- cuju/render/_tournaments/__init__.py +3 -0
- cuju/render/_tournaments/results.py +547 -0
- cuju/render/_tournaments/tournament.py +2742 -0
- cuju/render/_tournaments/tournament_cli.py +587 -0
- cuju/render/_tournaments/tournament_runner.py +1975 -0
- cuju/render/_version.py +6 -0
- cuju/render/_watch/__init__.py +3 -0
- cuju/render/_watch/grid.py +265 -0
- cuju/render/_watch/live.py +664 -0
- cuju/render/_watch/notebook.py +79 -0
- cuju/render/_watch/show.py +171 -0
- cuju/render/samples/sample-11v11.cjr +0 -0
- cuju/render/samples/sample-5v5.cjr +0 -0
- cuju/render/samples/teams/starter/main.py +33 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_easy.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_hard.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_medium.py +46 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_easy.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_hard.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_medium.py +34 -0
- cuju/render/web/assets/adboard_polygon.webp +0 -0
- cuju/render/web/assets/adboard_your_ad_here.webp +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-combatmelee.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-general.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementadvanced.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementbasic.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-simulation.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-special.glb +0 -0
- cuju/render/web/assets/ball.glb +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/corner.mp4 +0 -0
- cuju/render/web/assets/challenges/corner.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.webp +0 -0
- cuju/render/web/assets/challenges/manifest.json +375 -0
- cuju/render/web/assets/challenges/one_on_one.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one.webp +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.webp +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.mp4 +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.webp +0 -0
- cuju/render/web/assets/crowd.webp +0 -0
- cuju/render/web/assets/crowd_normal.webp +0 -0
- cuju/render/web/assets/cuju-logo-384.png +0 -0
- cuju/render/web/assets/cuju-logo.png +0 -0
- cuju/render/web/assets/cuju-logo.svg +46 -0
- cuju/render/web/assets/cuju-mark-light.svg +33 -0
- cuju/render/web/assets/cuju-mark.svg +33 -0
- cuju/render/web/assets/goal.glb +0 -0
- cuju/render/web/assets/grass.webp +0 -0
- cuju/render/web/assets/hair_black.png +0 -0
- cuju/render/web/assets/hair_blonde.png +0 -0
- cuju/render/web/assets/hair_brown.png +0 -0
- cuju/render/web/assets/hair_darkblonde.png +0 -0
- cuju/render/web/assets/hair_long01.glb +0 -0
- cuju/render/web/assets/hair_long02.glb +0 -0
- cuju/render/web/assets/hair_medium01.glb +0 -0
- cuju/render/web/assets/hair_red.png +0 -0
- cuju/render/web/assets/hair_short01.glb +0 -0
- cuju/render/web/assets/hair_short02.glb +0 -0
- cuju/render/web/assets/icons/cuju-192.png +0 -0
- cuju/render/web/assets/icons/cuju-512.png +0 -0
- cuju/render/web/assets/icons/cuju-maskable-512.png +0 -0
- cuju/render/web/assets/kit_goalkeeper.webp +0 -0
- cuju/render/web/assets/kit_mask.png +0 -0
- cuju/render/web/assets/kit_normal.webp +0 -0
- cuju/render/web/assets/manifest.json +601 -0
- cuju/render/web/assets/models/kaykit-barbarian.glb +0 -0
- cuju/render/web/assets/models/kaykit-barbarian.json +23 -0
- cuju/render/web/assets/models/kaykit-knight.glb +0 -0
- cuju/render/web/assets/models/kaykit-knight.json +23 -0
- cuju/render/web/assets/models/kaykit-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.glb +0 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.json +23 -0
- cuju/render/web/assets/models/kaykit-ranger.glb +0 -0
- cuju/render/web/assets/models/kaykit-ranger.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.json +23 -0
- cuju/render/web/assets/pitch.webp +0 -0
- cuju/render/web/assets/pitch_normal.webp +0 -0
- cuju/render/web/assets/player.glb +0 -0
- cuju/render/web/assets/shoe.webp +0 -0
- cuju/render/web/assets/shoe_sole.png +0 -0
- cuju/render/web/assets/skin_01.png +0 -0
- cuju/render/web/assets/skin_02.png +0 -0
- cuju/render/web/assets/skin_03.png +0 -0
- cuju/render/web/assets/skin_04.png +0 -0
- cuju/render/web/assets/sounds/applause.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-1.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-2.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-3.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-4.ogg +0 -0
- cuju/render/web/assets/sounds/city-horn.ogg +0 -0
- cuju/render/web/assets/sounds/city-loop.ogg +0 -0
- cuju/render/web/assets/sounds/city-siren.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-cheer.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-loop.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-ooh.ogg +0 -0
- cuju/render/web/assets/sounds/gulls.ogg +0 -0
- cuju/render/web/assets/sounds/neon-sting.ogg +0 -0
- cuju/render/web/assets/sounds/rain-loop.ogg +0 -0
- cuju/render/web/assets/sounds/sounds.json +46 -0
- cuju/render/web/assets/sounds/space-beep.ogg +0 -0
- cuju/render/web/assets/sounds/space-hum.ogg +0 -0
- cuju/render/web/assets/sounds/space-radio.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-long.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-short.ogg +0 -0
- cuju/render/web/assets/sounds/wind-loop.ogg +0 -0
- cuju/render/web/easy.css +357 -0
- cuju/render/web/export.html +23 -0
- cuju/render/web/feed.css +262 -0
- cuju/render/web/feed.html +28 -0
- cuju/render/web/flat.css +120 -0
- cuju/render/web/home.css +837 -0
- cuju/render/web/index.html +373 -0
- cuju/render/web/js/boot.js +50 -0
- cuju/render/web/js/broadcast/audio.js +2141 -0
- cuju/render/web/js/broadcast/celebrate.js +1238 -0
- cuju/render/web/js/broadcast/decisions.js +609 -0
- cuju/render/web/js/broadcast/hud.js +352 -0
- cuju/render/web/js/broadcast/show.js +3614 -0
- cuju/render/web/js/broadcast/transition.js +2231 -0
- cuju/render/web/js/broadcast/voices.js +153 -0
- cuju/render/web/js/capture.js +101 -0
- cuju/render/web/js/core/accessibility.js +90 -0
- cuju/render/web/js/core/api.js +429 -0
- cuju/render/web/js/core/comfort.js +135 -0
- cuju/render/web/js/core/i18n.js +705 -0
- cuju/render/web/js/core/icons.js +167 -0
- cuju/render/web/js/core/input-settings.js +156 -0
- cuju/render/web/js/core/keys.js +277 -0
- cuju/render/web/js/core/language-ui.js +125 -0
- cuju/render/web/js/core/menu.js +853 -0
- cuju/render/web/js/core/names.js +113 -0
- cuju/render/web/js/core/page-structure.js +110 -0
- cuju/render/web/js/core/scenarios.js +109 -0
- cuju/render/web/js/core/spoilers.js +236 -0
- cuju/render/web/js/core/typeahead.js +38 -0
- cuju/render/web/js/export/dom-picture.js +330 -0
- cuju/render/web/js/export/export-host.js +97 -0
- cuju/render/web/js/export/feed-dialog.js +191 -0
- cuju/render/web/js/export/feed-params.js +266 -0
- cuju/render/web/js/export/feed.js +363 -0
- cuju/render/web/js/export/mp4-mux.js +295 -0
- cuju/render/web/js/export/photo-core.js +313 -0
- cuju/render/web/js/export/photo.js +1314 -0
- cuju/render/web/js/export/safe-frame.js +59 -0
- cuju/render/web/js/export/size-fields.js +103 -0
- cuju/render/web/js/export/sizes.js +178 -0
- cuju/render/web/js/export/video-boot.js +55 -0
- cuju/render/web/js/export/video-browser.js +325 -0
- cuju/render/web/js/export/video-export.js +417 -0
- cuju/render/web/js/export/video-plan.js +207 -0
- cuju/render/web/js/export/video-range.js +185 -0
- cuju/render/web/js/export/webm-mux.js +198 -0
- cuju/render/web/js/feed-boot.js +151 -0
- cuju/render/web/js/feed-frame.js +101 -0
- cuju/render/web/js/help/doctor.js +488 -0
- cuju/render/web/js/help/firstrun.js +379 -0
- cuju/render/web/js/help/help-center.js +438 -0
- cuju/render/web/js/help/install-offer.js +164 -0
- cuju/render/web/js/help/install-state.js +104 -0
- cuju/render/web/js/help/lessons-core.js +207 -0
- cuju/render/web/js/help/lessons.js +463 -0
- cuju/render/web/js/help/palette.js +584 -0
- cuju/render/web/js/help/pwa.js +257 -0
- cuju/render/web/js/help/tour-core.js +241 -0
- cuju/render/web/js/help/tour.js +461 -0
- cuju/render/web/js/main.js +2513 -0
- cuju/render/web/js/pages/draw.js +188 -0
- cuju/render/web/js/pages/fieldpage.js +530 -0
- cuju/render/web/js/pages/home.js +521 -0
- cuju/render/web/js/pages/matches.js +779 -0
- cuju/render/web/js/pages/moments-ui.js +359 -0
- cuju/render/web/js/pages/queue-filters.js +63 -0
- cuju/render/web/js/pages/recording-groups.js +80 -0
- cuju/render/web/js/pages/recordings.js +1069 -0
- cuju/render/web/js/play/bench.js +347 -0
- cuju/render/web/js/play/challenges.js +1302 -0
- cuju/render/web/js/play/medals.js +127 -0
- cuju/render/web/js/play/play.js +1953 -0
- cuju/render/web/js/play/teasers.js +196 -0
- cuju/render/web/js/play/touch.js +350 -0
- cuju/render/web/js/play/tournament-layout.js +487 -0
- cuju/render/web/js/play/tournament-results.js +441 -0
- cuju/render/web/js/play/tournament-strict.js +560 -0
- cuju/render/web/js/play/tournament.js +2585 -0
- cuju/render/web/js/players/animate.js +972 -0
- cuju/render/web/js/players/clips.js +224 -0
- cuju/render/web/js/players/kaykit.js +162 -0
- cuju/render/web/js/players/kits.js +676 -0
- cuju/render/web/js/players/number-patch.js +82 -0
- cuju/render/web/js/players/players.js +2694 -0
- cuju/render/web/js/players/recolor.js +893 -0
- cuju/render/web/js/players/retarget.js +325 -0
- cuju/render/web/js/players/rig-numbers.js +174 -0
- cuju/render/web/js/presentation/animator.js +231 -0
- cuju/render/web/js/presentation/assign.js +85 -0
- cuju/render/web/js/presentation/behaviors.js +440 -0
- cuju/render/web/js/presentation/budget.js +209 -0
- cuju/render/web/js/presentation/catalog.js +1674 -0
- cuju/render/web/js/presentation/clocks.js +72 -0
- cuju/render/web/js/presentation/commands.js +301 -0
- cuju/render/web/js/presentation/contract.js +240 -0
- cuju/render/web/js/presentation/effects.js +904 -0
- cuju/render/web/js/presentation/flash.js +605 -0
- cuju/render/web/js/presentation/freeze-worklet.js +22 -0
- cuju/render/web/js/presentation/glsl.js +464 -0
- cuju/render/web/js/presentation/host.js +323 -0
- cuju/render/web/js/presentation/index.js +2066 -0
- cuju/render/web/js/presentation/loudness-worklet.js +235 -0
- cuju/render/web/js/presentation/loudness.js +740 -0
- cuju/render/web/js/presentation/moments.js +536 -0
- cuju/render/web/js/presentation/post.js +327 -0
- cuju/render/web/js/presentation/preview.js +180 -0
- cuju/render/web/js/presentation/protocol.js +174 -0
- cuju/render/web/js/presentation/reach.js +158 -0
- cuju/render/web/js/presentation/rigging.js +130 -0
- cuju/render/web/js/presentation/sandbox.js +157 -0
- cuju/render/web/js/presentation/shaders.js +235 -0
- cuju/render/web/js/presentation/stage.js +402 -0
- cuju/render/web/js/presentation/teamaudio.js +722 -0
- cuju/render/web/js/presentation/tracks.js +197 -0
- cuju/render/web/js/presentation/values.js +81 -0
- cuju/render/web/js/recording/annotation-data.js +203 -0
- cuju/render/web/js/recording/captions.js +491 -0
- cuju/render/web/js/recording/codec.js +185 -0
- cuju/render/web/js/recording/decode-worker.js +70 -0
- cuju/render/web/js/recording/decode.js +94 -0
- cuju/render/web/js/recording/episodes.js +46 -0
- cuju/render/web/js/recording/format.js +461 -0
- cuju/render/web/js/recording/live.js +348 -0
- cuju/render/web/js/recording/moments-query.js +59 -0
- cuju/render/web/js/recording/moments.js +149 -0
- cuju/render/web/js/recording/pose.js +86 -0
- cuju/render/web/js/recording/timeline.js +275 -0
- cuju/render/web/js/teams/art.js +50 -0
- cuju/render/web/js/teams/clip-editor.js +293 -0
- cuju/render/web/js/teams/editor.js +1979 -0
- cuju/render/web/js/teams/library.js +344 -0
- cuju/render/web/js/teams/painter.js +633 -0
- cuju/render/web/js/teams/surprise-ui.js +195 -0
- cuju/render/web/js/teams/surprise.js +497 -0
- cuju/render/web/js/teams/team-check.js +127 -0
- cuju/render/web/js/watch/annotations.js +270 -0
- cuju/render/web/js/watch/flat.js +366 -0
- cuju/render/web/js/watch/grid.js +436 -0
- cuju/render/web/js/watch/pitch2d.js +289 -0
- cuju/render/web/js/watch/player-tools.js +192 -0
- cuju/render/web/js/watch/presets.js +201 -0
- cuju/render/web/js/world/arenas.js +997 -0
- cuju/render/web/js/world/ball.js +204 -0
- cuju/render/web/js/world/cameras.js +982 -0
- cuju/render/web/js/world/crowd.js +376 -0
- cuju/render/web/js/world/field.js +227 -0
- cuju/render/web/js/world/framing.js +145 -0
- cuju/render/web/js/world/occlusion.js +48 -0
- cuju/render/web/js/world/pitch.js +703 -0
- cuju/render/web/js/world/scene.js +395 -0
- cuju/render/web/js/world/sky.js +325 -0
- cuju/render/web/js/world/stadium.js +1262 -0
- cuju/render/web/js/world/vendor-sky.js +367 -0
- cuju/render/web/js/world/weather.js +264 -0
- cuju/render/web/languages/en.json +4750 -0
- cuju/render/web/lessons.json +137 -0
- cuju/render/web/manifest.webmanifest +23 -0
- cuju/render/web/style.css +8399 -0
- cuju/render/web/sw.js +235 -0
- cuju/render/web/vendor/fabric/LICENSE +16 -0
- cuju/render/web/vendor/fabric/fabric.min.mjs +1 -0
- cuju/render/web/vendor/lucide/LICENSE +43 -0
- cuju/render/web/vendor/lucide/icons.js +141 -0
- cuju/render/web/vendor/three/LICENSE +21 -0
- cuju/render/web/vendor/three/build/three.core.js +60586 -0
- cuju/render/web/vendor/three/build/three.module.js +19719 -0
- cuju/render/web/vendor/three/examples/jsm/loaders/GLTFLoader.js +4925 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/EffectComposer.js +365 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/MaskPass.js +195 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/OutputPass.js +148 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/Pass.js +191 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/RenderPass.js +193 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/ShaderPass.js +135 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/UnrealBloomPass.js +541 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/CopyShader.js +52 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/LuminosityHighPassShader.js +68 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/OutputShader.js +103 -0
- cuju/render/web/vendor/three/examples/jsm/utils/BufferGeometryUtils.js +1501 -0
- cuju/render/web/vendor/three/examples/jsm/utils/SkeletonUtils.js +496 -0
- cuju_render-0.1.0.dist-info/METADATA +116 -0
- cuju_render-0.1.0.dist-info/RECORD +382 -0
- cuju_render-0.1.0.dist-info/WHEEL +4 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSE +202 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +396 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC0-1.0.txt +127 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/ISC.txt +16 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/MIT.txt +24 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/Unlicense.txt +13 -0
- cuju_render-0.1.0.dist-info/licenses/NOTICE +35 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// Copyright 2026 The cuju Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// A friendlier first run. A short tip on each page points to the next thing to try, until it is
|
|
5
|
+
// dismissed, and the settings turn the tips off or bring them back. The main controls carry a
|
|
6
|
+
// line of help, which shows on keyboard focus, and on hover for a control without a title of its
|
|
7
|
+
// own. The empty Watch page also points to the demo and the samples. A tip takes the first corner
|
|
8
|
+
// of the window where it covers no control of the page and no notice. The browser keeps every
|
|
9
|
+
// dismissal, and the rules at the top of the file touch nothing of the page.
|
|
10
|
+
|
|
11
|
+
import { t, tm, tn } from '../core/i18n.js';
|
|
12
|
+
import { icon, symbolOnly } from '../core/icons.js';
|
|
13
|
+
|
|
14
|
+
export const TIPS_KEY = 'cuju.tips';
|
|
15
|
+
|
|
16
|
+
// The tip of each page, shown once until dismissed, the message firstrun.tip.<page>. The tip of
|
|
17
|
+
// Watch waits for a match.
|
|
18
|
+
export const TIPS = Object.fromEntries(['home', 'watch', 'recordings', 'matches', 'tournaments', 'teams', 'play', 'challenges', 'field']
|
|
19
|
+
.map((page) => [page, t(`firstrun.tip.${page}`)]));
|
|
20
|
+
|
|
21
|
+
// The line of help of each main control by its id, the message firstrun.help.<id>.
|
|
22
|
+
export const HELP_TEXT = Object.fromEntries([
|
|
23
|
+
'home-btn', 'open-palette', 'open-settings', 'fullscreen', 'match-menu-btn', 'view-menu-btn',
|
|
24
|
+
'address-input', 'timeline', 'speed', 'mute', 'volume', 'live-env-index',
|
|
25
|
+
].map((id) => [id, t(`firstrun.help.${id}`)]));
|
|
26
|
+
// The line of help of each page's tab, the message firstrun.tab.<page> after the name of the page,
|
|
27
|
+
// since a tab of a middle-sized screen shows its symbol alone.
|
|
28
|
+
const TAB_HELP = Object.fromEntries(['watch', 'play', 'challenges', 'recordings', 'matches', 'tournaments', 'teams', 'field', 'help']
|
|
29
|
+
.map((page) => [page, t('symbol.tip', { name: t(`html.nav.${page}`), text: t(`firstrun.tab.${page}`) })]));
|
|
30
|
+
|
|
31
|
+
// The corners a tip may take, in the order it tries them, the lower left first.
|
|
32
|
+
export const TIP_SPOTS = ['bottom-left', 'bottom-right', 'top-right', 'top-left'];
|
|
33
|
+
|
|
34
|
+
// The spot to take, given how many controls each spot would cover. The spot the tip holds stays
|
|
35
|
+
// while it covers nothing, else the first clear spot in order, else the one that covers least.
|
|
36
|
+
export function chooseSpot(current, covers) {
|
|
37
|
+
const order = TIP_SPOTS.includes(current) ? [current, ...TIP_SPOTS.filter((spot) => spot !== current)] : TIP_SPOTS;
|
|
38
|
+
let best = order[0];
|
|
39
|
+
for (const spot of order) {
|
|
40
|
+
const count = covers(spot);
|
|
41
|
+
if (count === 0) {
|
|
42
|
+
return spot;
|
|
43
|
+
}
|
|
44
|
+
if (count < covers(best)) {
|
|
45
|
+
best = spot;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return best;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The controls of the page that a click or a press would reach.
|
|
52
|
+
const CONTROL = 'button, a[href], input, select, textarea, summary, [tabindex]:not([tabindex="-1"]), tbody tr, [role="row"], .challenge-item';
|
|
53
|
+
|
|
54
|
+
// The stored state of the tips, the pages whose tip was dismissed and whether tips are off.
|
|
55
|
+
export function tipState(stored) {
|
|
56
|
+
const source = stored && typeof stored === 'object' ? stored : {};
|
|
57
|
+
const dismissed = Array.isArray(source.dismissed) ? source.dismissed.filter((page) => page in TIPS) : [];
|
|
58
|
+
return { dismissed: [...new Set(dismissed)], off: source.off === true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The tip that a page shows, or null when it has none, it was dismissed or tips are off.
|
|
62
|
+
export function tipFor(page, state) {
|
|
63
|
+
const clean = tipState(state);
|
|
64
|
+
return !clean.off && page in TIPS && !clean.dismissed.includes(page) ? TIPS[page] : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The state after a page's tip is dismissed.
|
|
68
|
+
export function dismissTip(state, page) {
|
|
69
|
+
const clean = tipState(state);
|
|
70
|
+
return { ...clean, dismissed: clean.dismissed.includes(page) ? clean.dismissed : [...clean.dismissed, page] };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function h(tag, attributes = {}, ...children) {
|
|
74
|
+
const node = document.createElement(tag);
|
|
75
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
76
|
+
if (key.startsWith('on') && typeof value === 'function') {
|
|
77
|
+
node.addEventListener(key.slice(2), value);
|
|
78
|
+
} else if (value !== null && value !== undefined && value !== false) {
|
|
79
|
+
node.setAttribute(key, value === true ? '' : value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
node.append(...children.flat().filter((child) => child !== null && child !== undefined));
|
|
83
|
+
return node;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function loadState() {
|
|
87
|
+
try {
|
|
88
|
+
return tipState(JSON.parse(localStorage.getItem(TIPS_KEY)));
|
|
89
|
+
} catch {
|
|
90
|
+
return tipState(null);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class FirstRun {
|
|
95
|
+
constructor(app) {
|
|
96
|
+
this.app = app;
|
|
97
|
+
this.state = loadState();
|
|
98
|
+
this.page = null;
|
|
99
|
+
this.tip = h('aside', { id: 'tip', class: 'tip', 'aria-label': t('firstrun.tip.label'), hidden: true },
|
|
100
|
+
this.tipText = h('p', { class: 'tip-text' }),
|
|
101
|
+
h('div', { class: 'tip-actions' },
|
|
102
|
+
h('button', { type: 'button', class: 'btn small', ...symbolOnly(t('firstrun.gotIt')), onclick: () => this.dismiss() }, icon('check')),
|
|
103
|
+
h('button', { type: 'button', class: 'link small', onclick: () => this.setOff(true) }, t('firstrun.noMore'))));
|
|
104
|
+
this.tooltip = h('div', { id: 'help-tip', class: 'help-tip', role: 'tooltip', hidden: true });
|
|
105
|
+
// The tip comes right after the top bar in the order of the Tab key, before the page.
|
|
106
|
+
const pages = document.getElementById('pages');
|
|
107
|
+
if (pages) {
|
|
108
|
+
pages.before(this.tip);
|
|
109
|
+
pages.after(this.tooltip);
|
|
110
|
+
} else {
|
|
111
|
+
(document.getElementById('app') || document.body).append(this.tip, this.tooltip);
|
|
112
|
+
}
|
|
113
|
+
this.bindSetting();
|
|
114
|
+
this.bindHelp();
|
|
115
|
+
this.bindWatchEmpty();
|
|
116
|
+
// The page in view decides the tip, at once on a change of page and a few times a second
|
|
117
|
+
// for whatever else opens over it.
|
|
118
|
+
const root = document.getElementById('app');
|
|
119
|
+
if (root) {
|
|
120
|
+
new MutationObserver(() => this.update()).observe(root, { attributes: true, attributeFilter: ['data-page', 'class'] });
|
|
121
|
+
}
|
|
122
|
+
setInterval(() => this.update(), 400);
|
|
123
|
+
this.update();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
save() {
|
|
127
|
+
try {
|
|
128
|
+
localStorage.setItem(TIPS_KEY, JSON.stringify(this.state));
|
|
129
|
+
} catch {
|
|
130
|
+
// Storage can be unavailable, dismissals then last for this visit only.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// The page's tip, once the page has shown for a moment, and none over broadcast mode, a game,
|
|
135
|
+
// a dialog, a menu or the palette.
|
|
136
|
+
update() {
|
|
137
|
+
const app = this.app;
|
|
138
|
+
const page = app.page;
|
|
139
|
+
if (page !== this.page) {
|
|
140
|
+
this.page = page;
|
|
141
|
+
this.since = performance.now();
|
|
142
|
+
}
|
|
143
|
+
const text = tipFor(page, this.state);
|
|
144
|
+
const shown = (node) => node.getClientRects().length > 0;
|
|
145
|
+
// A game on Play keeps the picture clear, so its tip waits on the setup card.
|
|
146
|
+
const game = page === 'play' && app.play && app.play.stage && !app.play.stage.hidden;
|
|
147
|
+
const busy = app.tvMode || game || (page === 'watch' && !app.source) || !!(app.photo && app.photo.isOpen)
|
|
148
|
+
|| [...document.querySelectorAll('dialog[open], [role="dialog"], .dd-panel.open')].some(shown);
|
|
149
|
+
const show = !!text && !busy && performance.now() - this.since > 1200;
|
|
150
|
+
if (this.tip.dataset.page !== page) {
|
|
151
|
+
this.tip.hidden = true;
|
|
152
|
+
}
|
|
153
|
+
// The tip shows with its marks, its words in bold and its shortcuts as keycaps.
|
|
154
|
+
if (show && this.tipText.dataset.tip !== page) {
|
|
155
|
+
this.tipText.dataset.tip = page;
|
|
156
|
+
this.tipText.replaceChildren(...tn('firstrun.tip.line', { text: tm(`firstrun.tip.${page}`) }));
|
|
157
|
+
}
|
|
158
|
+
if (this.tip.hidden === show) {
|
|
159
|
+
this.tip.hidden = !show;
|
|
160
|
+
this.tip.dataset.page = page;
|
|
161
|
+
}
|
|
162
|
+
if (show) {
|
|
163
|
+
this.place();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Moves the tip to a corner where it covers no control and no notice that shows.
|
|
168
|
+
place() {
|
|
169
|
+
const tip = this.tip;
|
|
170
|
+
const toast = document.getElementById('toast');
|
|
171
|
+
const counts = {};
|
|
172
|
+
const covers = (spot) => {
|
|
173
|
+
if (!(spot in counts)) {
|
|
174
|
+
tip.dataset.spot = spot;
|
|
175
|
+
counts[spot] = this.covered(tip.getBoundingClientRect(), toast);
|
|
176
|
+
}
|
|
177
|
+
return counts[spot];
|
|
178
|
+
};
|
|
179
|
+
const spot = chooseSpot(tip.dataset.spot, covers);
|
|
180
|
+
tip.dataset.spot = spot;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// How many controls of the page lie under a box, a notice that shows counting as one.
|
|
184
|
+
covered(box, toast) {
|
|
185
|
+
if (!box.width) {
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
const found = new Set();
|
|
189
|
+
const pages = document.getElementById('pages');
|
|
190
|
+
for (let x = box.left + 6; x < box.right; x += Math.max(20, (box.width - 12) / 6)) {
|
|
191
|
+
for (let y = box.top + 6; y < box.bottom; y += Math.max(14, (box.height - 12) / 4)) {
|
|
192
|
+
for (const node of document.elementsFromPoint(x, y)) {
|
|
193
|
+
const control = node.closest(CONTROL);
|
|
194
|
+
if (control && !this.tip.contains(control) && (!pages || pages.contains(control))) {
|
|
195
|
+
found.add(control);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// A region that holds the whole tip, such as a scrolling list, is no control under it.
|
|
201
|
+
const holds = (node) => {
|
|
202
|
+
const r = node.getBoundingClientRect();
|
|
203
|
+
return r.left <= box.left && r.right >= box.right && r.top <= box.top && r.bottom >= box.bottom;
|
|
204
|
+
};
|
|
205
|
+
let count = [...found].filter((node) => !holds(node)).length;
|
|
206
|
+
if (toast && toast.classList.contains('show') && toast.textContent) {
|
|
207
|
+
const t = toast.getBoundingClientRect();
|
|
208
|
+
if (t.left < box.right && t.right > box.left && t.top < box.bottom && t.bottom > box.top) {
|
|
209
|
+
count += 1;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return count;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
dismiss() {
|
|
216
|
+
const focused = this.tip.contains(document.activeElement);
|
|
217
|
+
this.state = dismissTip(this.state, this.tip.dataset.page || this.app.page);
|
|
218
|
+
this.save();
|
|
219
|
+
this.tip.hidden = true;
|
|
220
|
+
if (focused) {
|
|
221
|
+
document.getElementById('pages')?.focus({ preventScroll: true });
|
|
222
|
+
}
|
|
223
|
+
this.update();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setOff(off) {
|
|
227
|
+
const focused = this.tip.contains(document.activeElement);
|
|
228
|
+
// Turning the tips on again brings back every tip.
|
|
229
|
+
this.state = off ? { ...this.state, off: true } : { dismissed: [], off: false };
|
|
230
|
+
this.save();
|
|
231
|
+
if (this.box) {
|
|
232
|
+
this.box.checked = !off;
|
|
233
|
+
}
|
|
234
|
+
this.tip.hidden = true;
|
|
235
|
+
if (focused) {
|
|
236
|
+
document.getElementById('pages')?.focus({ preventScroll: true });
|
|
237
|
+
}
|
|
238
|
+
this.update();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// A box of the settings panel turns the tips off and on.
|
|
242
|
+
bindSetting() {
|
|
243
|
+
const anchor = document.getElementById('saved-style')?.closest('label');
|
|
244
|
+
if (!anchor) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.box = h('input', { type: 'checkbox', id: 'tips-on' });
|
|
248
|
+
this.box.checked = !this.state.off;
|
|
249
|
+
this.box.addEventListener('change', () => this.setOff(!this.box.checked));
|
|
250
|
+
const help = anchor.nextElementSibling && anchor.nextElementSibling.matches('p') ? anchor.nextElementSibling : anchor;
|
|
251
|
+
help.after(h('label', { class: 'check' }, this.box, ` ${t('firstrun.setting')}`));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// The line of help of a control shows on keyboard focus, and on hover when the control has no
|
|
255
|
+
// title for the browser to show. A control that shows its symbol alone shows its title the same
|
|
256
|
+
// way on keyboard focus, so its name reads by keyboard as it does by mouse. Esc, a click, a key
|
|
257
|
+
// or leaving the control hides it.
|
|
258
|
+
bindHelp() {
|
|
259
|
+
for (const [id, text] of Object.entries(HELP_TEXT)) {
|
|
260
|
+
document.getElementById(id)?.setAttribute('data-help', text);
|
|
261
|
+
}
|
|
262
|
+
for (const button of document.querySelectorAll('#topbar .menu [data-page]')) {
|
|
263
|
+
if (TAB_HELP[button.dataset.page]) {
|
|
264
|
+
button.dataset.help = TAB_HELP[button.dataset.page];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
let timer = null;
|
|
268
|
+
const target = (node) => (node instanceof Element ? node.closest('[data-help], [data-symbol], .icon-btn[title], .text-btn[title]') : null);
|
|
269
|
+
document.addEventListener('focusin', (e) => {
|
|
270
|
+
const node = target(e.target);
|
|
271
|
+
if (node && node.matches(':focus-visible')) {
|
|
272
|
+
this.showHelp(node);
|
|
273
|
+
} else {
|
|
274
|
+
this.hideHelp();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
document.addEventListener('focusout', () => this.hideHelp());
|
|
278
|
+
document.addEventListener('pointerover', (e) => {
|
|
279
|
+
const node = target(e.target);
|
|
280
|
+
clearTimeout(timer);
|
|
281
|
+
if (node && e.pointerType === 'mouse' && !node.title) {
|
|
282
|
+
timer = setTimeout(() => this.showHelp(node), 600);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
document.addEventListener('pointerout', (e) => {
|
|
286
|
+
if (target(e.target) === this.helpFor) {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
this.hideHelp();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
document.addEventListener('pointerdown', () => {
|
|
292
|
+
clearTimeout(timer);
|
|
293
|
+
this.hideHelp();
|
|
294
|
+
}, true);
|
|
295
|
+
document.addEventListener('keydown', (e) => {
|
|
296
|
+
if (e.key === 'Escape' && !this.tooltip.hidden) {
|
|
297
|
+
this.hideHelp();
|
|
298
|
+
}
|
|
299
|
+
}, true);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
showHelp(node) {
|
|
303
|
+
const rect = node.getBoundingClientRect();
|
|
304
|
+
if (!rect.width || this.app.tvMode) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const text = node.dataset.help || node.title || node.getAttribute('aria-label') || '';
|
|
308
|
+
if (!text) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
this.hideHelp();
|
|
312
|
+
this.helpFor = node;
|
|
313
|
+
this.tooltip.textContent = text;
|
|
314
|
+
this.tooltip.hidden = false;
|
|
315
|
+
const own = node.getAttribute('aria-describedby') || '';
|
|
316
|
+
// A tooltip that only repeats the name of its control adds nothing for a screen reader.
|
|
317
|
+
if (text !== node.getAttribute('aria-label') && !own.split(' ').includes('help-tip')) {
|
|
318
|
+
node.setAttribute('aria-describedby', `${own} help-tip`.trim());
|
|
319
|
+
}
|
|
320
|
+
const box = this.tooltip.getBoundingClientRect();
|
|
321
|
+
const below = rect.bottom + 6 + box.height < window.innerHeight;
|
|
322
|
+
const left = Math.max(8, Math.min(window.innerWidth - box.width - 8, rect.left + rect.width / 2 - box.width / 2));
|
|
323
|
+
this.tooltip.style.left = `${left}px`;
|
|
324
|
+
this.tooltip.style.top = `${below ? rect.bottom + 6 : rect.top - box.height - 6}px`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
hideHelp() {
|
|
328
|
+
const node = this.helpFor;
|
|
329
|
+
this.helpFor = null;
|
|
330
|
+
this.tooltip.hidden = true;
|
|
331
|
+
if (node) {
|
|
332
|
+
const rest = (node.getAttribute('aria-describedby') || '').split(' ').filter((id) => id && id !== 'help-tip');
|
|
333
|
+
if (rest.length) {
|
|
334
|
+
node.setAttribute('aria-describedby', rest.join(' '));
|
|
335
|
+
} else {
|
|
336
|
+
node.removeAttribute('aria-describedby');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// The empty Watch page offers the demo and the samples as the next step.
|
|
342
|
+
bindWatchEmpty() {
|
|
343
|
+
const empty = document.getElementById('watch-empty');
|
|
344
|
+
if (!empty) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const offline = empty.querySelector('.offline-note');
|
|
348
|
+
empty.insertBefore(h('p', { class: 'muted small watch-next' }, tn('firstrun.watch.next', {
|
|
349
|
+
command: h('code', {}, 'cuju render demo'),
|
|
350
|
+
sample: h('button', { type: 'button', class: 'link', onclick: () => this.openSample() }, t('firstrun.watch.sample')),
|
|
351
|
+
})), offline);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async openSample() {
|
|
355
|
+
const app = this.app;
|
|
356
|
+
try {
|
|
357
|
+
const { api } = await import('../core/api.js');
|
|
358
|
+
const list = await api.matches();
|
|
359
|
+
const sample = list.find((m) => m.id === 'sample-5v5') || list.find((m) => m.id === 'sample-11v11') || list[0];
|
|
360
|
+
if (sample) {
|
|
361
|
+
app.openMatchInWatch(sample.id);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
// The Recordings page says what is wrong with the library.
|
|
366
|
+
}
|
|
367
|
+
app.hud.toast(t('firstrun.noRecording'), 'info', 5000);
|
|
368
|
+
app.showPage('recordings');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Shows the Making a team topic of the Help page.
|
|
372
|
+
helpTopic(id) {
|
|
373
|
+
if (this.app.palette) {
|
|
374
|
+
this.app.palette.helpTopic(id);
|
|
375
|
+
} else {
|
|
376
|
+
this.app.showPage('help');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|