././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.857274 toot-0.44.1/0000755000175000017500000000000014656360001012764 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749799.0 toot-0.44.1/.coveragerc0000644000175000017500000000005314465406647015122 0ustar00ihabunekihabunek[run] source=./toot command_line=-m pytest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/.flake80000644000175000017500000000015014656344035014144 0ustar00ihabunekihabunek[flake8] exclude=build,tests,tmp,venv,_env,toot/tui/scroll.py ignore=E128,W503,W504 max-line-length=120 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1723457536.8422737 toot-0.44.1/.github/0000755000175000017500000000000014656360001014324 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.845274 toot-0.44.1/.github/workflows/0000755000175000017500000000000014656360001016361 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/.github/workflows/test.yml0000644000175000017500000000136014656344035020074 0ustar00ihabunekihabunekname: Run tests on: [push, pull_request] jobs: test: runs-on: ubuntu-22.04 strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Check out code uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e ".[test,richtext]" - name: Run tests run: | pytest - name: Validate minimum required version run: | vermin toot - name: Check style run: | flake8 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/.gitignore0000644000175000017500000000027214656344035014766 0ustar00ihabunekihabunek*.egg-info/ *.pyc .pypirc /.cache/ /.coverage /.env /.envrc /.pytest_cache/ /book /build/ /bundle/ /dist/ /htmlcov/ /pyrightconfig.json /tmp/ /toot-*.pyz /toot-*.tar.gz /venv/ debug.log ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/.vermin0000644000175000017500000000010014656344035014265 0ustar00ihabunekihabunek[vermin] only_show_violations = yes show_tips = no targets = 3.8././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457520.0 toot-0.44.1/CHANGELOG.md0000644000175000017500000004145314656357760014626 0ustar00ihabunekihabunekChangelog --------- **0.44.1 (2024-08-12)** * Make it possible to pass status URL as status_id, experimental (thanks @nemobis) * Show statuses in search results (thanks @nemobis) **0.44.0 (2024-08-12)** * **BREAKING:** Require Python 3.8+ * Add `toot diag` for displaying diagnostic info (thanks Dan Schwarz) * TUI: Improve image support (thanks @AnonymouX47) * TUI: Add support for indexed color image rendering (#483) (thanks Dan Schwarz) * TUI: Fix crash bug (#483) (thanks Dan Schwarz) **0.43.0 (2024-04-13)** * TUI: Support displaying images (thanks Dan Schwarz) * Improve GoToSocial compatibility (thanks Luca Matei Pintilie) * Show visibility in timeline (thanks Sandra Snan) * Flag `notifications --clear` no longer requires an argument (thanks Sandra Snan) * TUI: Fix crash when rendering invalid URLs (thanks Dan Schwarz) * Migrated to pyproject.toml finally **0.42.0 (2024-03-09)** * TUI: Add `toot tui --always-show-sensitive` option (thanks Lexi Winter) * TUI: Document missing shortcuts (thanks Denis Laxalde) * TUI: Use rounded boxes for nicer visuals (thanks Dan Schwarz) * TUI: Don't break if edited_at status field does not exist **0.41.1 (2024-01-02)** * Fix a crash in settings parsing code **0.41.0 (2024-01-02)** * Honour user's default visibility set in Mastodon preferences instead of always defaulting to public visibility (thanks Lexi Winter) * TUI: Add editing toots (thanks Lexi Winter) * TUI: Fix a bug which made palette config in settings not work * TUI: Show edit datetime in status detail (thanks Lexi Winter) **0.40.2 (2023-12-28)** * Reinstate `toot post --using` option. * Add shell completion for instances. **0.40.1 (2023-12-28)** * Add `toot --as` option to replace `toot post --using`. This now works for all commands. **0.40.0 (2023-12-27)** This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line interface. This allows for some new features like nested commands, setting parameters via environment variables, and shell completion. Backward compatibility should be mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead * BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command * BREAKING: Option `--quiet` has been removed. Redirect output instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html * Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` commands * Add `tags followed`, `tags follow`, and `tags unfollow` sub-commands, deprecate `tags_followed`, `tags_follow`, and `tags tags_unfollow` * Add `lists accounts`, `lists add`, `lists create`, `lists delete`, `lists list`, `lists remove` subcommands, deprecate `lists`, `lists_accounts`, `lists_add`, `lists_create`, `lists_delete`, `lists_remove` commands. * Add `--json` option to tags and lists commands * Add `toot --width` option for setting your preferred terminal width * Add `--media-viewer` and `--colors` options to `toot tui`. These were previously accessible only via settings. * TUI: Fix issue where UI did not render until first input (thanks Urwid devs) **0.39.0 (2023-11-23)** * Add `--json` option to many commands, this makes them print the JSON data returned by the server instead of human-readable data. Useful for scripting. * TUI: Make media viewer configurable in settings, see: https://toot.bezdomni.net/settings.html#tui-view-images * TUI: Add rich text rendering (thanks Dan Schwarz) **0.38.2 (2023-11-16)** * Fix compatibility with Pleroma (#399, thanks Sandra Snan) * Fix language documentation (thanks Sandra Snan) **0.38.1 (2023-07-25)** * Fix relative datetimes option in TUI **0.38.0 (2023-07-25)** * Add `toot muted` and `toot blocked` commands (thanks Florian Obser) * Add settings file, allows setting common options, defining defaults for command arguments, and the TUI palette * TUI: Remap shortcuts so they don't override HJKL used for navigation (thanks Dan Schwarz) **0.37.0 (2023-06-28)** * **BREAKING:** Require Python 3.7+ * Add `timeline --account` option to show the account timeline (thanks Dan Schwarz) * Add `toot status` command to show a single status * TUI: Add personal timeline (thanks Dan Schwarz) * TUI: Highlight followed accounts in status details (thanks Dan Schwarz) * TUI: Restructured goto menu (thanks Dan Schwarz) * TUI: Fix boosting boosted statuses (thanks Dan Schwarz) * TUI: Add support for list timelines (thanks Dan Schwarz) **0.36.0 (2023-03-09)** * Move docs from toot.readthedocs.io to toot.bezdomni.net * Add specifying media thumbnails to `toot post` (#301) * Add creating polls to `toot post` * Handle custom instance domains (e.g. when server is located at `social.vivaldi.net`, but uses the `vivaldi.net` mastodon domain. (#217) * TUI: Inherit post visibility when replying (thanks @rogarb) * TUI: Add conversations timeline (thanks @rogarb) * TUI: Add shortcut to copy toot contents (thanks Dan Schwarz) **0.35.0 (2023-03-01)** * Save toot contents when using --editor so it's recoverable if posting fails (#311) * TUI: Add voting on polls (thanks Dan Schwarz) * TUI: Add following/blocking/muting accounts (thanks Dan Schwarz) * TUI: Add notifications timeline (thanks Dan Schwarz) **0.34.1 (2023-02-20)** * TUI: Fix bug where TUI would break on older Mastodon instances (#309) **0.34.0 (2023-02-03)** * Fix Python version detection which would fail in some cases (thanks K) * Fix toot --help not working (thanks Norman Walsh) * TUI: Add option to save status JSON data from source window (thanks Dan Schwarz) * TUI: Add `--relative-datetimes` option to show relative datetimes (thanks Dan Schwarz) * TUI: Don't focus newly created post (#188, thanks Dan Schwarz) * TUI: Add ability to scroll long status messages (#166, thanks Dan Schwarz) * TUI: Add action to view account details (thanks Dan Schwarz) **0.33.1 (2023-01-03)** * TUI: Fix crash when viewing toot in browser **0.33.0 (2023-01-02)** * Add CONTRIBUTING.md containing a contribution guide * Add `env` command which prints local env to include in issues * Add TOOT_POST_VISIBILITY environment to control default post visibility (thanks Lim Ding Wen) * Add `tags_followed`, `tags_follow`, and `tags_unfollow` commands (thanks Daniel Schwarz) * Add `tags_bookmarks` command (thanks Giuseppe Bilotta) * TUI: Show an error if attemptint to boost a private status (thanks Lim Ding Wen) * TUI: Hide polls, cards and media attachments for sensitive posts (thanks Daniel Schwarz) * TUI: Add bookmarking and bookmark timeline (thanks Daniel Schwarz) * TUI: Show status visibility (thanks Lim Ding Wen) * TUI: Reply to original account instead of boosting account (thanks Lim Ding Wen) * TUI: Refresh screen after exiting browser, required for text browsers (thanks Daniel Schwarz) * TUI: Highlight followed tags (thanks Daniel Schwarz) **0.32.1 (2022-12-12)** * Fix packaging issue, missing toot.utils module **0.32.0 (2022-12-12)** * TUI: Press N to translate status, if available on your instance (thanks Daniel Schwarz) * Fix: `post --language` option now accepts two-letter country code instead of 3-letter. This was changed by mastodon at some point. * Fix: Failing to find accounts using qualified usernames (#254) **0.31.0 (2022-12-07)** * **BREAKING:** Require Python 3.6+ * Add `post --scheduled-in` option for easier scheduling * Fix posting toots to Pleroma * Improved testing **0.30.1 (2022-11-30)** * Remove usage of deprecated `text_url` status field. Fixes posting media without text. **0.30.0 (2022-11-29)** * Display polls in `timeline` (thanks Daniel Schwarz) * TUI: Add [,] shortcut to reload timeline (thanks Daniel Schwarz) * TUI: Add [Z] shortcut to zoom status - allows scrolling (thanks @PeterFidelman) * Internals: add integration tests against a local mastodon instance **0.29.0 (2022-11-21)** * Add `bookmark` and `unbookmark` commands * Add `following` and `followers` commands (thanks @Oblomov) * TUI: Show media attachments in links list (thanks @PeterFidelman) * Fix tests so that they don't depend on the local timezone **0.28.1 (2022-11-12)** * Fix account search to be case insensitive (thanks @TheJokersThief) * Fix account search to use v2 endpoint, since v1 endpoint was removed on some instances (thanks @kaja47) * Add '.toot' extension to temporary files when composing toot in an editor (thanks @larsks) * Display localized datetimes in timeline (thanks @mmmmmmbeer) * Don't use # for comments when composing toot in an editor, since that made it impossible to post lines starting with #. * TUI: Fix crash when poll does not have an expiry date **0.28.0 (2021-08-28)** * **BREAKING**: Removed `toot curses`, deprecated since 2019-09-03 * Add `--scheduled-at` option to `toot post`, allows scheduling toots * Add `--description` option to `toot post`, for adding descriptions to media attachments (thanks @ansuz) * Add `--mentions` option to `toot notifications` to show only mentions (thanks @alexwennerberg) * Add `--content-type` option to `toot post` to allow specifying mime type, used on Pleroma (thanks Sandra Snan) * Allow post IDs to be strings as used on Pleroma (thanks Sandra Snan) * TUI: Allow posts longer than 500 characters if so configured on the server (thanks Sandra Snan) * Allow piping the password to login_cli for testing purposes (thanks @NinjaTrappeur) * Disable paging timeline when output is piped (thanks @stacyharper) **0.27.0 (2020-06-15)** * TUI: Fix access to public and tag timelines when on private mastodon instances (#168) * Add `--reverse` option to `toot notifications` (#151) * Fix `toot timeline` to respect `--instance` option * TUI: Add option to pin/save tag timelines (#163, thanks @dlax) * TUI: Fixed crash on empty timeline (#138, thanks ecs) **0.26.0 (2020-04-15)** * Fix datetime parsing on Python 3.5 (#162) * TUI: Display status links and open them (#154, thanks @dlax) * TUI: Fix visibility descriptions (#153, thanks @finnoleary) * **IMPORTANT:** Starting from this release, new releases will not be uploaded to the APT package repository at `bezdomni.net`. Please use the official Debian or Ubuntu repos or choose another [installation option](https://toot.bezdomni.net/installation.html). **0.25.2 (2020-01-23)** * Revert adding changelog and readme to sourceballs (#149) * TUI: Fall back to username when display_name is unset (thanks @dlax) * Note: 0.25.1 was skipped due to error when releasing **0.25.0 (2020-01-21)** * TUI: Show character count when composing (#121) * Include changelog and license in sourceballs (#133) * Fix searching by hashtag which include the '#' (#134) * Upgrade search to v2 (#135) * Fix compatibility with Python < 3.6 (don't use fstrings) **0.24.0 (2019-09-18)** * On Windows store config files under %APPDATA% * CLI: Don't use ANSI colors if not supported by terminal or when not in a tty * TUI: Implement deleting own status messages * TUI: Improve rendering of reblogged statuses (thanks @dlax) * TUI: Set urwid encoding to UTF-8 (thanks @bearzk) **0.23.1 (2019-09-04)** * Fix a date parsing bug in Python versions <3.7 (#114) **0.23.0 (2019-09-03)** * Add `toot tui`, new and improved TUI implemented written with the help of the [urwid](http://urwid.org/) library * Deprecate `toot curses`. It will show a deprecation notice when started. To be removed in a future release * Add `--editor` option to `toot post` to allow composing toots in an editor (#90) * Fix config file permissions, set them to 0600 when creating the initial config file (#109) * Add user agent string to all requests, fixes interaction with instances protected by Cloudflare (#106) **0.22.0 (2019-08-01)** * **BREAKING:** Dropped support for Python 3.3 * Add `toot notifications` to show notifications (thanks @dlax) * Add posting and replying to curses interface (thanks @Skehmatics) * Add `--language` option to `toot post` * Enable attaching upto 4 files via `--media` option on `toot post` **0.21.0 (2019-02-15)** * **BREAKING:** in `toot timeline` short argument for selecting a list is no longer `-i`, this has been changed to select the instance, so that it is the same as on other commands, please use the long form `--list` instead * Add `toot reblogged_by` to show who reblogged a status (#88) * Add `toot thread` to show a status with its replies (#87) * Better handling of wide characters (eastern scripts, emojis) (#84) * Improved `timeline`, nicer visuals, and it will now ask to show next batch of toots, unless given the `--once` option * Add public/local/tag timelines to `timeline` and `curses` * Support for boosting and favouriting in `toot curses`, press `f`/`b` (#88, #93) **0.20.0 (2019-02-01)** * Enable interaction with instances using http instead of https (#56) * Enable proxy usage via environment variables (#47) * Make `toot post` prompt for input if no text is given (#82) * Add post-related commands: `favourite`, `unfavourite`, `reblog`, `unreblog`, `pin` & `unpin` (#75) **0.19.0 (2018-06-27)** * Add support for replying to a toot (#6) * Add `toot delete` command for deleting a toot (#54) * Add global `--quiet` flag to silence output (#46) * Make `toot login` provide browser login, and `toot login_cli` log in via console. This makes it clear what's the preferred option. * Use Idempotency-Key header to prevent multiple toots being posted if request is retried * Fix a bug where all media would be marked as sensitive **0.18.0 (2018-06-12)** * Add support for public, tag and list timelines in `toot timeline` (#52) * Add `--sensitive` and `--spoiler-text` options to `toot post` (#63) * Curses app improvements (respect sensitive content, require keypress to show, add help modal, misc improvements) **0.17.1 (2018-01-15)** * Create config folder if it does not exist (#40) * Fix packaging to include `toot.ui` package (#41) **0.17.0 (2018-01-15)** * Changed configuration file format to allow switching between multiple logged in accounts (#32) * Respect XDG_CONFIG_HOME environment variable to locate config home (#12) * Dynamically calculate left window width, supports narrower windows (#27) * Redraw windows when terminal size changes (#25) * Support scrolling the status list * Fetch next batch of statuses when bottom is reached * Support up/down arrows (#30) * Misc visual improvements **0.16.2 (2018-01-02)** * No changes, pushed to fix a packaging issue **0.16.1 (2017-12-30)** * Fix bug with app registration **0.16.0 (2017-12-30)** * **BREAKING:** Dropped support for Python 2, because it's a pain to support and caused bugs with handling unicode. * Remove hacky `login_2fa` command, use `login_browser` instead * Add `instance` command * Allow `post`ing media without text (#24) **0.15.1 (2017-12-12)** * Fix crash when toot's URL is None (#33), thanks @veer66 **0.15.0 (2017-09-09)** * Fix Windows compatibility (#18) **0.14.0 (2017-09-07)** * Add `--debug` option to enable debug logging instead of using the `TOOT_DEBUG` environment variable. * Fix: don't read requirements.txt from setup.py, this fails when packaging deb and potentially in some other cases (see #18) **0.13.0 (2017-08-26)** * Allow passing `--instance` and `--email` to login command * Add `login_browser` command for proper two factor authentication through the browser (#19, #23) **0.12.0 (2017-05-08)** * Add option to disable ANSI color in output (#15) * Return nonzero error code on error (#14) * Change license to GPLv3 **0.11.0 (2017-05-07)** * Fix error when running toot from crontab (#11) * Minor tweaks **0.10.0 (2017-04-26)** * Add commands: `block`, `unblock`, `mute`, `unmute` * Internal improvements **0.9.1 (2017-04-24)** * Fix conflict with curses package name **0.9.0 (2017-04-21)** * Add `whois` command * Add experimental `curses` app for viewing the timeline **0.8.0 (2017-04-19)** * **BREAKING:** Renamed command `2fa` to `login_2fa` * It is now possible to pipe text into `toot post` **0.7.0 (2017-04-18)** * **WARNING:** Due to changes in configuration format, after upgrading to this version, you will be required to log in to your Mastodon instance again. * Experimental 2FA support (#3) * Do not create a new application for each login **0.6.0 (2017-04-17)** * Add `whoami` command * Migrate from `optparse` to `argparse` **0.5.0 (2017-04-16)** * Add `search`, `follow` and `unfollow` commands * Migrate from `optparse` to `argparse` **0.4.0 (2017-04-15)** * Add `upload` command to post media * Add `--visibility` and `--media` options to `post` command **0.3.0 (2017-04-13)** * Add: view timeline * Require an explicit login **0.2.1 (2017-04-13)** * Fix invalid requirements in setup.py **0.2.0 (2017-04-12)** * Bugfixes **0.1.0 (2017-04-12)** * Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/CONTRIBUTING.md0000644000175000017500000001076214656344035015234 0ustar00ihabunekihabunekToot contribution guide ======================= Firstly, thank you for contributing to toot! Relevant links which will be referenced below: * [toot documentation](https://toot.bezdomni.net/) * [toot-discuss mailing list](https://lists.sr.ht/~ihabunek/toot-discuss) used for discussion as well as accepting patches * [toot project on github](https://github.com/ihabunek/toot) here you can report issues and submit pull requests * #toot IRC channel on [libera.chat](https://libera.chat) ## Code of conduct Please be kind and patient. Toot is governed by one human with a full time job. ## I have a question First, check if your question is addressed in the documentation or the mailing list. If not, feel free to send an email to the mailing list. You may want to subscribe to the mailing list to receive replies. Alternatively, you can ask your question on the IRC channel and ping me (ihabunek). You may have to wait for a response, please be patient. Please don't open Github issues for questions. ## I want to contribute ### Reporting a bug First check you're using the [latest version](https://github.com/ihabunek/toot/releases/) of toot and verify the bug is present in this version. Search Github issues to check the bug hasn't already been reported. To report a bug open an [issue on Github](https://github.com/ihabunek/toot/issues) or send an email to the [mailing list](https://lists.sr.ht/~ihabunek/toot-discuss). * Run `toot env` and include its contents in the bug report. * Explain the behavior you would expect and the actual behavior. * Please provide as much context as possible and describe the reproduction steps that someone else can follow to recreate the issue on their own. ### Suggesting enhancements This includes suggesting new features or changes to existing ones. Search Github issues to check the enhancement has not already been requested. If it hasn't, [open a new issue](https://github.com/ihabunek/toot/issues). Your request will be reviewed to see if it's a good fit for toot. Implementing requested features depends on the available time and energy of the maintainer and other contributors. Be patient. ### Contributing code When contributing to toot, please only submit code that you have authored or code whose license allows it to be included in toot. You agree that the code you submit will be published under the [toot license](LICENSE). #### Setting up a dev environment Check out toot (or a fork) and install it into a virtual environment. ``` git clone git@github.com:ihabunek/toot.git cd toot python3 -m venv _env source _env/bin/activate pip install --editable . pip install -r requirements-dev.txt pip install -r requirements-test.txt ``` While the virtual env is active, you can run `./_env/bin/toot` to execute the one you checked out. This allows you to make changes and test them. #### Crafting good commits Please put some effort into breaking your contribution up into a series of well formed commits. If you're unsure what this means, there is a good guide available at https://cbea.ms/git-commit/. Rules for commits: * each commit should ideally contain only one change * don't bundle multiple unrelated changes into a single commit * write descriptive and well formatted commit messages Rules for commit messages: * separate subject from body with a blank line * limit the subject line to 50 characters * capitalize the subject line * do not end the subject line with a period * use the imperative mood in the subject line * wrap the body at 72 characters * use the body to explain what and why vs. how For a more detailed explanation with examples see the guide at https://cbea.ms/git-commit/ If you use vim to write your commit messages, it will already enforce some of these rules for you. #### Run tests before submitting You can run code and style tests by running: ``` make test ``` This runs three tools: * `pytest` runs the test suite * `flake8` checks code formatting * `vermin` checks that minimum python version Please ensure all three commands succeed before submitting your patches. #### Submitting patches To submit your code either open [a pull request](https://github.com/ihabunek/toot/pulls) on Github, or send patch(es) to [the mailing list](https://lists.sr.ht/~ihabunek/toot-discuss). If sending to the mailing list, patches should be sent using `git send-email`. If you're unsure how to do this, there is a good guide at https://git-send-email.io/. --- Parts of this guide were taken from the following sources: * https://contributing.md/ * https://cbea.ms/git-commit/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749799.0 toot-0.44.1/LICENSE0000644000175000017500000010451314465406647014014 0ustar00ihabunekihabunek GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749799.0 toot-0.44.1/MANIFEST.in0000644000175000017500000000003114465406647014533 0ustar00ihabunekihabunekrecursive-include tests *././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/Makefile0000644000175000017500000000166314656344035014443 0ustar00ihabunekihabunek.PHONY: clean publish test docs dist: python -m build publish : twine upload dist/*.tar.gz dist/*.whl test: pytest -v flake8 vermin toot coverage: coverage erase coverage run coverage html --omit "toot/tui/*" coverage report clean : find . -name "*pyc" | xargs rm -rf $1 rm -rf build dist MANIFEST htmlcov bundle toot*.tar.gz toot*.pyz changelog: ./scripts/generate_changelog > CHANGELOG.md cp CHANGELOG.md docs/changelog.md docs: changelog mdbook build docs-serve: mdbook serve --port 8000 docs-deploy: docs rsync --archive --compress --delete --stats book/ bezdomni:web/toot .PHONY: bundle bundle: mkdir bundle cp toot/__main__.py bundle pip install . --target=bundle rm -rf bundle/*.dist-info find bundle/ -type d -name "__pycache__" -exec rm -rf {} + python -m zipapp \ --python "/usr/bin/env python3" \ --output toot-`git describe`.pyz bundle \ --compress echo "Bundle created: toot-`git describe`.pyz" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1723457536.8562741 toot-0.44.1/PKG-INFO0000644000175000017500000012667714656360001014104 0ustar00ihabunekihabunekMetadata-Version: 2.1 Name: toot Version: 0.44.1 Summary: Mastodon CLI client Author-email: Ivan Habunek License: GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . Project-URL: Homepage, https://toot.bezdomni.net Project-URL: Source, https://github.com/ihabunek/toot/ Classifier: Environment :: Console :: Curses Classifier: Environment :: Console Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: beautifulsoup4<5.0,>=4.5.0 Requires-Dist: click~=8.1 Requires-Dist: requests<3.0,>=2.13 Requires-Dist: tomlkit<1.0,>=0.10.0 Requires-Dist: urwid<3.0,>=2.0.0 Requires-Dist: wcwidth>=0.1.7 Provides-Extra: images Requires-Dist: pillow>=9.5.0; extra == "images" Requires-Dist: term-image>=0.7.2; extra == "images" Provides-Extra: richtext Requires-Dist: urwidgets<0.3,>=0.2; extra == "richtext" Provides-Extra: test Requires-Dist: flake8; extra == "test" Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-xdist[psutil]; extra == "test" Requires-Dist: setuptools; extra == "test" Requires-Dist: vermin; extra == "test" Requires-Dist: typing-extensions; extra == "test" Requires-Dist: pillow>=9.5.0; extra == "test" Provides-Extra: dev Requires-Dist: build; extra == "dev" Requires-Dist: flake8; extra == "dev" Requires-Dist: mypy; extra == "dev" Requires-Dist: pyright; extra == "dev" Requires-Dist: pyyaml; extra == "dev" Requires-Dist: textual-dev; extra == "dev" Requires-Dist: twine; extra == "dev" Requires-Dist: types-beautifulsoup4; extra == "dev" Requires-Dist: vermin; extra == "dev" ============================ Toot - a Mastodon CLI client ============================ .. image:: https://raw.githubusercontent.com/ihabunek/toot/master/trumpet.png Toot is a CLI and TUI tool for interacting with Mastodon instances from the command line. .. image:: https://img.shields.io/badge/author-%40ihabunek-blue.svg?maxAge=3600&style=flat-square :target: https://mastodon.social/@ihabunek .. image:: https://img.shields.io/github/license/ihabunek/toot.svg?maxAge=3600&style=flat-square :target: https://opensource.org/licenses/GPL-3.0 .. image:: https://img.shields.io/pypi/v/toot.svg?maxAge=3600&style=flat-square :target: https://pypi.python.org/pypi/toot Resources --------- * Homepage: https://github.com/ihabunek/toot * Issues: https://github.com/ihabunek/toot/issues * Documentation: https://toot.bezdomni.net/ * Mailing list for discussion, support and patches: https://lists.sr.ht/~ihabunek/toot-discuss * Informal discussion: #toot IRC channel on `libera.chat `_ Features -------- * Posting, replying, deleting statuses * Support for media uploads, spoiler text, sensitive content * Search by account or hash tag * Following, muting and blocking accounts * Simple switching between authenticated in Mastodon accounts Terminal User Interface ----------------------- toot includes a terminal user interface (TUI). Run it with ``toot tui``. TUI Features: ------------- * Block graphic image display (requires optional libraries `pillow `, `term-image `, and `urwidgets `) * Bitmapped image display in `kitty ` terminal ``toot tui -f kitty`` * Bitmapped image display in `iTerm2 `, or `WezTerm ` terminal ``toot tui -f iterm`` .. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_list.png .. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_compose.png License ------- Copyright Ivan Habunek and contributors. Licensed under `GPLv3 `_, see `LICENSE `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/README.rst0000644000175000017500000000431514656344035014467 0ustar00ihabunekihabunek============================ Toot - a Mastodon CLI client ============================ .. image:: https://raw.githubusercontent.com/ihabunek/toot/master/trumpet.png Toot is a CLI and TUI tool for interacting with Mastodon instances from the command line. .. image:: https://img.shields.io/badge/author-%40ihabunek-blue.svg?maxAge=3600&style=flat-square :target: https://mastodon.social/@ihabunek .. image:: https://img.shields.io/github/license/ihabunek/toot.svg?maxAge=3600&style=flat-square :target: https://opensource.org/licenses/GPL-3.0 .. image:: https://img.shields.io/pypi/v/toot.svg?maxAge=3600&style=flat-square :target: https://pypi.python.org/pypi/toot Resources --------- * Homepage: https://github.com/ihabunek/toot * Issues: https://github.com/ihabunek/toot/issues * Documentation: https://toot.bezdomni.net/ * Mailing list for discussion, support and patches: https://lists.sr.ht/~ihabunek/toot-discuss * Informal discussion: #toot IRC channel on `libera.chat `_ Features -------- * Posting, replying, deleting statuses * Support for media uploads, spoiler text, sensitive content * Search by account or hash tag * Following, muting and blocking accounts * Simple switching between authenticated in Mastodon accounts Terminal User Interface ----------------------- toot includes a terminal user interface (TUI). Run it with ``toot tui``. TUI Features: ------------- * Block graphic image display (requires optional libraries `pillow `, `term-image `, and `urwidgets `) * Bitmapped image display in `kitty ` terminal ``toot tui -f kitty`` * Bitmapped image display in `iTerm2 `, or `WezTerm ` terminal ``toot tui -f iterm`` .. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_list.png .. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_compose.png License ------- Copyright Ivan Habunek and contributors. Licensed under `GPLv3 `_, see `LICENSE `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/book.css0000644000175000017500000000056514526070754014447 0ustar00ihabunekihabunek/* Overrides for the docs theme */ table { width: 100% } table th { text-align: left } code { white-space: pre } h2, h3 { margin-top: 2.5rem; } h4, h5 { margin-top: 2rem; } td.code { font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; font-size: 0.875em; width: 20%; white-space: nowrap; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/book.toml0000644000175000017500000000031714526070754014625 0ustar00ihabunekihabunek[book] authors = ["Ivan Habunek"] language = "en" multilingual = false src = "docs" title = "toot" [output.html] additional-css = ["book.css"] [preprocessor.toc] command = "mdbook-toc" renderer = ["html"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457207.0 toot-0.44.1/changelog.yaml0000644000175000017500000004530614656357267015633 0ustar00ihabunekihabunek0.44.1: date: 2024-08-12 changes: - "Make it possible to pass status URL as status_id, experimental (thanks @nemobis)" - "Show statuses in search results (thanks @nemobis)" 0.44.0: date: 2024-08-12 changes: - "**BREAKING:** Require Python 3.8+" - "Add `toot diag` for displaying diagnostic info (thanks Dan Schwarz)" - "TUI: Improve image support (thanks @AnonymouX47)" - "TUI: Add support for indexed color image rendering (#483) (thanks Dan Schwarz)" - "TUI: Fix crash bug (#483) (thanks Dan Schwarz)" 0.43.0: date: 2024-04-13 changes: - "TUI: Support displaying images (thanks Dan Schwarz)" - "Improve GoToSocial compatibility (thanks Luca Matei Pintilie)" - "Show visibility in timeline (thanks Sandra Snan)" - "Flag `notifications --clear` no longer requires an argument (thanks Sandra Snan)" - "TUI: Fix crash when rendering invalid URLs (thanks Dan Schwarz)" - "Migrated to pyproject.toml finally" 0.42.0: date: 2024-03-09 changes: - "TUI: Add `toot tui --always-show-sensitive` option (thanks Lexi Winter)" - "TUI: Document missing shortcuts (thanks Denis Laxalde)" - "TUI: Use rounded boxes for nicer visuals (thanks Dan Schwarz)" - "TUI: Don't break if edited_at status field does not exist" 0.41.1: date: 2024-01-02 changes: - "Fix a crash in settings parsing code" 0.41.0: date: 2024-01-02 changes: - "Honour user's default visibility set in Mastodon preferences instead of always defaulting to public visibility (thanks Lexi Winter)" - "TUI: Add editing toots (thanks Lexi Winter)" - "TUI: Fix a bug which made palette config in settings not work" - "TUI: Show edit datetime in status detail (thanks Lexi Winter)" 0.40.2: date: 2023-12-28 changes: - "Reinstate `toot post --using` option." - "Add shell completion for instances." 0.40.1: date: 2023-12-28 changes: - "Add `toot --as` option to replace `toot post --using`. This now works for all commands." 0.40.0: date: 2023-12-27 description: | This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line interface. This allows for some new features like nested commands, setting parameters via environment variables, and shell completion. Backward compatibility should be mostly preserved, except for cases noted below. Please report any issues. changes: - "BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead" - "BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command" - "BREAKING: Option `--quiet` has been removed. Redirect output instead." - "Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html" - "Add shell completion, see: https://toot.bezdomni.net/shell_completion.html" - "Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` commands" - "Add `tags followed`, `tags follow`, and `tags unfollow` sub-commands, deprecate `tags_followed`, `tags_follow`, and `tags tags_unfollow`" - "Add `lists accounts`, `lists add`, `lists create`, `lists delete`, `lists list`, `lists remove` subcommands, deprecate `lists`, `lists_accounts`, `lists_add`, `lists_create`, `lists_delete`, `lists_remove` commands." - "Add `--json` option to tags and lists commands" - "Add `toot --width` option for setting your preferred terminal width" - "Add `--media-viewer` and `--colors` options to `toot tui`. These were previously accessible only via settings." - "TUI: Fix issue where UI did not render until first input (thanks Urwid devs)" 0.39.0: date: 2023-11-23 changes: - "Add `--json` option to many commands, this makes them print the JSON data returned by the server instead of human-readable data. Useful for scripting." - "TUI: Make media viewer configurable in settings, see: https://toot.bezdomni.net/settings.html#tui-view-images" - "TUI: Add rich text rendering (thanks Dan Schwarz)" 0.38.2: date: 2023-11-16 changes: - "Fix compatibility with Pleroma (#399, thanks Sandra Snan)" - "Fix language documentation (thanks Sandra Snan)" 0.38.1: date: 2023-07-25 changes: - "Fix relative datetimes option in TUI" 0.38.0: date: 2023-07-25 changes: - "Add `toot muted` and `toot blocked` commands (thanks Florian Obser)" - "Add settings file, allows setting common options, defining defaults for command arguments, and the TUI palette" - "TUI: Remap shortcuts so they don't override HJKL used for navigation (thanks Dan Schwarz)" 0.37.0: date: 2023-06-28 changes: - "**BREAKING:** Require Python 3.7+" - "Add `timeline --account` option to show the account timeline (thanks Dan Schwarz)" - "Add `toot status` command to show a single status" - "TUI: Add personal timeline (thanks Dan Schwarz)" - "TUI: Highlight followed accounts in status details (thanks Dan Schwarz)" - "TUI: Restructured goto menu (thanks Dan Schwarz)" - "TUI: Fix boosting boosted statuses (thanks Dan Schwarz)" - "TUI: Add support for list timelines (thanks Dan Schwarz)" 0.36.0: date: 2023-03-09 changes: - "Move docs from toot.readthedocs.io to toot.bezdomni.net" - "Add specifying media thumbnails to `toot post` (#301)" - "Add creating polls to `toot post`" - "Handle custom instance domains (e.g. when server is located at `social.vivaldi.net`, but uses the `vivaldi.net` mastodon domain. (#217)" - "TUI: Inherit post visibility when replying (thanks @rogarb)" - "TUI: Add conversations timeline (thanks @rogarb)" - "TUI: Add shortcut to copy toot contents (thanks Dan Schwarz)" 0.35.0: date: 2023-03-01 changes: - "Save toot contents when using --editor so it's recoverable if posting fails (#311)" - "TUI: Add voting on polls (thanks Dan Schwarz)" - "TUI: Add following/blocking/muting accounts (thanks Dan Schwarz)" - "TUI: Add notifications timeline (thanks Dan Schwarz)" 0.34.1: date: 2023-02-20 changes: - "TUI: Fix bug where TUI would break on older Mastodon instances (#309)" 0.34.0: date: 2023-02-03 changes: - "Fix Python version detection which would fail in some cases (thanks K)" - "Fix toot --help not working (thanks Norman Walsh)" - "TUI: Add option to save status JSON data from source window (thanks Dan Schwarz)" - "TUI: Add `--relative-datetimes` option to show relative datetimes (thanks Dan Schwarz)" - "TUI: Don't focus newly created post (#188, thanks Dan Schwarz)" - "TUI: Add ability to scroll long status messages (#166, thanks Dan Schwarz)" - "TUI: Add action to view account details (thanks Dan Schwarz)" 0.33.1: date: 2023-01-03 changes: - "TUI: Fix crash when viewing toot in browser" 0.33.0: date: 2023-01-02 changes: - "Add CONTRIBUTING.md containing a contribution guide" - "Add `env` command which prints local env to include in issues" - "Add TOOT_POST_VISIBILITY environment to control default post visibility (thanks Lim Ding Wen)" - "Add `tags_followed`, `tags_follow`, and `tags_unfollow` commands (thanks Daniel Schwarz)" - "Add `tags_bookmarks` command (thanks Giuseppe Bilotta)" - "TUI: Show an error if attemptint to boost a private status (thanks Lim Ding Wen)" - "TUI: Hide polls, cards and media attachments for sensitive posts (thanks Daniel Schwarz)" - "TUI: Add bookmarking and bookmark timeline (thanks Daniel Schwarz)" - "TUI: Show status visibility (thanks Lim Ding Wen)" - "TUI: Reply to original account instead of boosting account (thanks Lim Ding Wen)" - "TUI: Refresh screen after exiting browser, required for text browsers (thanks Daniel Schwarz)" - "TUI: Highlight followed tags (thanks Daniel Schwarz)" 0.32.1: date: 2022-12-12 changes: - "Fix packaging issue, missing toot.utils module" 0.32.0: date: 2022-12-12 changes: - "TUI: Press N to translate status, if available on your instance (thanks Daniel Schwarz)" - "Fix: `post --language` option now accepts two-letter country code instead of 3-letter. This was changed by mastodon at some point." - "Fix: Failing to find accounts using qualified usernames (#254)" 0.31.0: date: 2022-12-07 changes: - "**BREAKING:** Require Python 3.6+" - "Add `post --scheduled-in` option for easier scheduling" - "Fix posting toots to Pleroma" - "Improved testing" 0.30.1: date: 2022-11-30 changes: - "Remove usage of deprecated `text_url` status field. Fixes posting media without text." 0.30.0: date: 2022-11-29 changes: - "Display polls in `timeline` (thanks Daniel Schwarz)" - "TUI: Add [,] shortcut to reload timeline (thanks Daniel Schwarz)" - "TUI: Add [Z] shortcut to zoom status - allows scrolling (thanks @PeterFidelman)" - "Internals: add integration tests against a local mastodon instance" 0.29.0: date: 2022-11-21 changes: - "Add `bookmark` and `unbookmark` commands" - "Add `following` and `followers` commands (thanks @Oblomov)" - "TUI: Show media attachments in links list (thanks @PeterFidelman)" - "Fix tests so that they don't depend on the local timezone" 0.28.1: date: 2022-11-12 changes: - "Fix account search to be case insensitive (thanks @TheJokersThief)" - "Fix account search to use v2 endpoint, since v1 endpoint was removed on some instances (thanks @kaja47)" - "Add '.toot' extension to temporary files when composing toot in an editor (thanks @larsks)" - "Display localized datetimes in timeline (thanks @mmmmmmbeer)" - "Don't use # for comments when composing toot in an editor, since that made it impossible to post lines starting with #." - "TUI: Fix crash when poll does not have an expiry date" 0.28.0: date: 2021-08-28 changes: - "**BREAKING**: Removed `toot curses`, deprecated since 2019-09-03" - "Add `--scheduled-at` option to `toot post`, allows scheduling toots" - "Add `--description` option to `toot post`, for adding descriptions to media attachments (thanks @ansuz)" - "Add `--mentions` option to `toot notifications` to show only mentions (thanks @alexwennerberg)" - "Add `--content-type` option to `toot post` to allow specifying mime type, used on Pleroma (thanks Sandra Snan)" - "Allow post IDs to be strings as used on Pleroma (thanks Sandra Snan)" - "TUI: Allow posts longer than 500 characters if so configured on the server (thanks Sandra Snan)" - "Allow piping the password to login_cli for testing purposes (thanks @NinjaTrappeur)" - "Disable paging timeline when output is piped (thanks @stacyharper)" 0.27.0: date: 2020-06-15 changes: - "TUI: Fix access to public and tag timelines when on private mastodon instances (#168)" - "Add `--reverse` option to `toot notifications` (#151)" - "Fix `toot timeline` to respect `--instance` option" - "TUI: Add option to pin/save tag timelines (#163, thanks @dlax)" - "TUI: Fixed crash on empty timeline (#138, thanks ecs)" 0.26.0: date: 2020-04-15 changes: - "Fix datetime parsing on Python 3.5 (#162)" - "TUI: Display status links and open them (#154, thanks @dlax)" - "TUI: Fix visibility descriptions (#153, thanks @finnoleary)" - "**IMPORTANT:** Starting from this release, new releases will not be uploaded to the APT package repository at `bezdomni.net`. Please use the official Debian or Ubuntu repos or choose another [installation option](https://toot.bezdomni.net/installation.html)." 0.25.2: date: 2020-01-23 changes: - "Revert adding changelog and readme to sourceballs (#149)" - "TUI: Fall back to username when display_name is unset (thanks @dlax)" - "Note: 0.25.1 was skipped due to error when releasing" 0.25.0: date: 2020-01-21 changes: - "TUI: Show character count when composing (#121)" - "Include changelog and license in sourceballs (#133)" - "Fix searching by hashtag which include the '#' (#134)" - "Upgrade search to v2 (#135)" - "Fix compatibility with Python < 3.6 (don't use fstrings)" 0.24.0: date: 2019-09-18 changes: - "On Windows store config files under %APPDATA%" - "CLI: Don't use ANSI colors if not supported by terminal or when not in a tty" - "TUI: Implement deleting own status messages" - "TUI: Improve rendering of reblogged statuses (thanks @dlax)" - "TUI: Set urwid encoding to UTF-8 (thanks @bearzk)" 0.23.1: date: 2019-09-04 changes: - "Fix a date parsing bug in Python versions <3.7 (#114)" 0.23.0: date: 2019-09-03 changes: - "Add `toot tui`, new and improved TUI implemented written with the help of the [urwid](http://urwid.org/) library" - "Deprecate `toot curses`. It will show a deprecation notice when started. To be removed in a future release" - "Add `--editor` option to `toot post` to allow composing toots in an editor (#90)" - "Fix config file permissions, set them to 0600 when creating the initial config file (#109)" - "Add user agent string to all requests, fixes interaction with instances protected by Cloudflare (#106)" 0.22.0: date: 2019-08-01 changes: - "**BREAKING:** Dropped support for Python 3.3" - "Add `toot notifications` to show notifications (thanks @dlax)" - "Add posting and replying to curses interface (thanks @Skehmatics)" - "Add `--language` option to `toot post`" - "Enable attaching upto 4 files via `--media` option on `toot post`" 0.21.0: date: 2019-02-15 changes: - "**BREAKING:** in `toot timeline` short argument for selecting a list is no longer `-i`, this has been changed to select the instance, so that it is the same as on other commands, please use the long form `--list` instead" - "Add `toot reblogged_by` to show who reblogged a status (#88)" - "Add `toot thread` to show a status with its replies (#87)" - "Better handling of wide characters (eastern scripts, emojis) (#84)" - "Improved `timeline`, nicer visuals, and it will now ask to show next batch of toots, unless given the `--once` option" - "Add public/local/tag timelines to `timeline` and `curses`" - "Support for boosting and favouriting in `toot curses`, press `f`/`b` (#88, #93)" 0.20.0: date: 2019-02-01 changes: - "Enable interaction with instances using http instead of https (#56)" - "Enable proxy usage via environment variables (#47)" - "Make `toot post` prompt for input if no text is given (#82)" - "Add post-related commands: `favourite`, `unfavourite`, `reblog`, `unreblog`, `pin` & `unpin` (#75)" 0.19.0: date: 2018-06-27 changes: - "Add support for replying to a toot (#6)" - "Add `toot delete` command for deleting a toot (#54)" - "Add global `--quiet` flag to silence output (#46)" - "Make `toot login` provide browser login, and `toot login_cli` log in via console. This makes it clear what's the preferred option." - "Use Idempotency-Key header to prevent multiple toots being posted if request is retried" - "Fix a bug where all media would be marked as sensitive" 0.18.0: date: 2018-06-12 changes: - "Add support for public, tag and list timelines in `toot timeline` (#52)" - "Add `--sensitive` and `--spoiler-text` options to `toot post` (#63)" - "Curses app improvements (respect sensitive content, require keypress to show, add help modal, misc improvements)" 0.17.1: date: 2018-01-15 changes: - "Create config folder if it does not exist (#40)" - "Fix packaging to include `toot.ui` package (#41)" 0.17.0: date: 2018-01-15 changes: - "Changed configuration file format to allow switching between multiple logged in accounts (#32)" - "Respect XDG_CONFIG_HOME environment variable to locate config home (#12)" - "Dynamically calculate left window width, supports narrower windows (#27)" - "Redraw windows when terminal size changes (#25)" - "Support scrolling the status list" - "Fetch next batch of statuses when bottom is reached" - "Support up/down arrows (#30)" - "Misc visual improvements" 0.16.2: date: 2018-01-02 changes: - "No changes, pushed to fix a packaging issue" 0.16.1: date: 2017-12-30 changes: - "Fix bug with app registration" 0.16.0: date: 2017-12-30 changes: - "**BREAKING:** Dropped support for Python 2, because it's a pain to support and caused bugs with handling unicode." - "Remove hacky `login_2fa` command, use `login_browser` instead" - "Add `instance` command" - "Allow `post`ing media without text (#24)" 0.15.1: date: 2017-12-12 changes: - "Fix crash when toot's URL is None (#33), thanks @veer66" 0.15.0: date: 2017-09-09 changes: - "Fix Windows compatibility (#18)" 0.14.0: date: 2017-09-07 changes: - "Add `--debug` option to enable debug logging instead of using the `TOOT_DEBUG` environment variable." - "Fix: don't read requirements.txt from setup.py, this fails when packaging deb and potentially in some other cases (see #18)" 0.13.0: date: 2017-08-26 changes: - "Allow passing `--instance` and `--email` to login command" - "Add `login_browser` command for proper two factor authentication through the browser (#19, #23)" 0.12.0: date: 2017-05-08 changes: - "Add option to disable ANSI color in output (#15)" - "Return nonzero error code on error (#14)" - "Change license to GPLv3" 0.11.0: date: 2017-05-07 changes: - "Fix error when running toot from crontab (#11)" - "Minor tweaks" 0.10.0: date: 2017-04-26 changes: - "Add commands: `block`, `unblock`, `mute`, `unmute`" - "Internal improvements" 0.9.1: date: 2017-04-24 changes: - "Fix conflict with curses package name" 0.9.0: date: 2017-04-21 changes: - "Add `whois` command" - "Add experimental `curses` app for viewing the timeline" 0.8.0: date: 2017-04-19 changes: - "**BREAKING:** Renamed command `2fa` to `login_2fa`" - "It is now possible to pipe text into `toot post`" 0.7.0: date: 2017-04-18 changes: - "**WARNING:** Due to changes in configuration format, after upgrading to this version, you will be required to log in to your Mastodon instance again." - "Experimental 2FA support (#3)" - "Do not create a new application for each login" 0.6.0: date: 2017-04-17 changes: - "Add `whoami` command" - "Migrate from `optparse` to `argparse`" 0.5.0: date: 2017-04-16 changes: - "Add `search`, `follow` and `unfollow` commands" - "Migrate from `optparse` to `argparse`" 0.4.0: date: 2017-04-15 changes: - "Add `upload` command to post media" - "Add `--visibility` and `--media` options to `post` command" 0.3.0: date: 2017-04-13 changes: - "Add: view timeline" - "Require an explicit login" 0.2.1: date: 2017-04-13 changes: - "Fix invalid requirements in setup.py" 0.2.0: date: 2017-04-12 changes: - "Bugfixes" 0.1.0: date: 2017-04-12 changes: - "Initial release" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.846274 toot-0.44.1/docs/0000755000175000017500000000000014656360001013714 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/SUMMARY.md0000644000175000017500000000067314656344035015412 0ustar00ihabunekihabunek# Summary [Introduction](introduction.md) - [Installation](installation.md) - [Usage](usage.md) - [Advanced](advanced.md) - [Settings](settings.md) - [Shell completion](shell_completion.md) - [Environment variables](environment_variables.md) - [TUI](tui.md) - [Contributing](contributing.md) - [Documentation](documentation.md) - [Release procedure](release.md) - [Changelog](changelog.md) [License](license.md) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/advanced.md0000644000175000017500000000200714656344035016013 0ustar00ihabunekihabunekAdvanced usage ============== Disabling HTTPS --------------- You may pass the `--disable-https` flag to use unencrypted HTTP instead of HTTPS for a given instance. This is inherently insecure and should be used only when connecting to local development instances. ```sh toot login --disable-https --instance localhost:8080 ``` Using proxies ------------- You can configure proxies by setting the `HTTPS_PROXY` or `HTTP_PROXY` environment variables. This will cause all http(s) requests to be proxied through the specified server. For example: ```sh export HTTPS_PROXY="http://1.2.3.4:5678" toot login --instance mastodon.social ``` **NB:** This feature is provided by [requests](http://docs.python-requests.org/en/master/user/advanced/#proxies>) and setting the environment variable will affect other programs using this library. This environment can be set for a single call to toot by prefixing the command with the environment variable: ``` HTTPS_PROXY="http://1.2.3.4:5678" toot login --instance mastodon.social ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457520.0 toot-0.44.1/docs/changelog.md0000644000175000017500000004145314656357760016216 0ustar00ihabunekihabunekChangelog --------- **0.44.1 (2024-08-12)** * Make it possible to pass status URL as status_id, experimental (thanks @nemobis) * Show statuses in search results (thanks @nemobis) **0.44.0 (2024-08-12)** * **BREAKING:** Require Python 3.8+ * Add `toot diag` for displaying diagnostic info (thanks Dan Schwarz) * TUI: Improve image support (thanks @AnonymouX47) * TUI: Add support for indexed color image rendering (#483) (thanks Dan Schwarz) * TUI: Fix crash bug (#483) (thanks Dan Schwarz) **0.43.0 (2024-04-13)** * TUI: Support displaying images (thanks Dan Schwarz) * Improve GoToSocial compatibility (thanks Luca Matei Pintilie) * Show visibility in timeline (thanks Sandra Snan) * Flag `notifications --clear` no longer requires an argument (thanks Sandra Snan) * TUI: Fix crash when rendering invalid URLs (thanks Dan Schwarz) * Migrated to pyproject.toml finally **0.42.0 (2024-03-09)** * TUI: Add `toot tui --always-show-sensitive` option (thanks Lexi Winter) * TUI: Document missing shortcuts (thanks Denis Laxalde) * TUI: Use rounded boxes for nicer visuals (thanks Dan Schwarz) * TUI: Don't break if edited_at status field does not exist **0.41.1 (2024-01-02)** * Fix a crash in settings parsing code **0.41.0 (2024-01-02)** * Honour user's default visibility set in Mastodon preferences instead of always defaulting to public visibility (thanks Lexi Winter) * TUI: Add editing toots (thanks Lexi Winter) * TUI: Fix a bug which made palette config in settings not work * TUI: Show edit datetime in status detail (thanks Lexi Winter) **0.40.2 (2023-12-28)** * Reinstate `toot post --using` option. * Add shell completion for instances. **0.40.1 (2023-12-28)** * Add `toot --as` option to replace `toot post --using`. This now works for all commands. **0.40.0 (2023-12-27)** This release includes a rather extensive change to use the Click library (https://click.palletsprojects.com/) for creating the command line interface. This allows for some new features like nested commands, setting parameters via environment variables, and shell completion. Backward compatibility should be mostly preserved, except for cases noted below. Please report any issues. * BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead * BREAKING: Options `--debug` and `--color` must be specified after `toot` but before the command * BREAKING: Option `--quiet` has been removed. Redirect output instead. * Add passing parameters via environment variables, see: https://toot.bezdomni.net/environment_variables.html * Add shell completion, see: https://toot.bezdomni.net/shell_completion.html * Add `tags info`, `tags featured`, `tags feature`, and `tags unfeature` commands * Add `tags followed`, `tags follow`, and `tags unfollow` sub-commands, deprecate `tags_followed`, `tags_follow`, and `tags tags_unfollow` * Add `lists accounts`, `lists add`, `lists create`, `lists delete`, `lists list`, `lists remove` subcommands, deprecate `lists`, `lists_accounts`, `lists_add`, `lists_create`, `lists_delete`, `lists_remove` commands. * Add `--json` option to tags and lists commands * Add `toot --width` option for setting your preferred terminal width * Add `--media-viewer` and `--colors` options to `toot tui`. These were previously accessible only via settings. * TUI: Fix issue where UI did not render until first input (thanks Urwid devs) **0.39.0 (2023-11-23)** * Add `--json` option to many commands, this makes them print the JSON data returned by the server instead of human-readable data. Useful for scripting. * TUI: Make media viewer configurable in settings, see: https://toot.bezdomni.net/settings.html#tui-view-images * TUI: Add rich text rendering (thanks Dan Schwarz) **0.38.2 (2023-11-16)** * Fix compatibility with Pleroma (#399, thanks Sandra Snan) * Fix language documentation (thanks Sandra Snan) **0.38.1 (2023-07-25)** * Fix relative datetimes option in TUI **0.38.0 (2023-07-25)** * Add `toot muted` and `toot blocked` commands (thanks Florian Obser) * Add settings file, allows setting common options, defining defaults for command arguments, and the TUI palette * TUI: Remap shortcuts so they don't override HJKL used for navigation (thanks Dan Schwarz) **0.37.0 (2023-06-28)** * **BREAKING:** Require Python 3.7+ * Add `timeline --account` option to show the account timeline (thanks Dan Schwarz) * Add `toot status` command to show a single status * TUI: Add personal timeline (thanks Dan Schwarz) * TUI: Highlight followed accounts in status details (thanks Dan Schwarz) * TUI: Restructured goto menu (thanks Dan Schwarz) * TUI: Fix boosting boosted statuses (thanks Dan Schwarz) * TUI: Add support for list timelines (thanks Dan Schwarz) **0.36.0 (2023-03-09)** * Move docs from toot.readthedocs.io to toot.bezdomni.net * Add specifying media thumbnails to `toot post` (#301) * Add creating polls to `toot post` * Handle custom instance domains (e.g. when server is located at `social.vivaldi.net`, but uses the `vivaldi.net` mastodon domain. (#217) * TUI: Inherit post visibility when replying (thanks @rogarb) * TUI: Add conversations timeline (thanks @rogarb) * TUI: Add shortcut to copy toot contents (thanks Dan Schwarz) **0.35.0 (2023-03-01)** * Save toot contents when using --editor so it's recoverable if posting fails (#311) * TUI: Add voting on polls (thanks Dan Schwarz) * TUI: Add following/blocking/muting accounts (thanks Dan Schwarz) * TUI: Add notifications timeline (thanks Dan Schwarz) **0.34.1 (2023-02-20)** * TUI: Fix bug where TUI would break on older Mastodon instances (#309) **0.34.0 (2023-02-03)** * Fix Python version detection which would fail in some cases (thanks K) * Fix toot --help not working (thanks Norman Walsh) * TUI: Add option to save status JSON data from source window (thanks Dan Schwarz) * TUI: Add `--relative-datetimes` option to show relative datetimes (thanks Dan Schwarz) * TUI: Don't focus newly created post (#188, thanks Dan Schwarz) * TUI: Add ability to scroll long status messages (#166, thanks Dan Schwarz) * TUI: Add action to view account details (thanks Dan Schwarz) **0.33.1 (2023-01-03)** * TUI: Fix crash when viewing toot in browser **0.33.0 (2023-01-02)** * Add CONTRIBUTING.md containing a contribution guide * Add `env` command which prints local env to include in issues * Add TOOT_POST_VISIBILITY environment to control default post visibility (thanks Lim Ding Wen) * Add `tags_followed`, `tags_follow`, and `tags_unfollow` commands (thanks Daniel Schwarz) * Add `tags_bookmarks` command (thanks Giuseppe Bilotta) * TUI: Show an error if attemptint to boost a private status (thanks Lim Ding Wen) * TUI: Hide polls, cards and media attachments for sensitive posts (thanks Daniel Schwarz) * TUI: Add bookmarking and bookmark timeline (thanks Daniel Schwarz) * TUI: Show status visibility (thanks Lim Ding Wen) * TUI: Reply to original account instead of boosting account (thanks Lim Ding Wen) * TUI: Refresh screen after exiting browser, required for text browsers (thanks Daniel Schwarz) * TUI: Highlight followed tags (thanks Daniel Schwarz) **0.32.1 (2022-12-12)** * Fix packaging issue, missing toot.utils module **0.32.0 (2022-12-12)** * TUI: Press N to translate status, if available on your instance (thanks Daniel Schwarz) * Fix: `post --language` option now accepts two-letter country code instead of 3-letter. This was changed by mastodon at some point. * Fix: Failing to find accounts using qualified usernames (#254) **0.31.0 (2022-12-07)** * **BREAKING:** Require Python 3.6+ * Add `post --scheduled-in` option for easier scheduling * Fix posting toots to Pleroma * Improved testing **0.30.1 (2022-11-30)** * Remove usage of deprecated `text_url` status field. Fixes posting media without text. **0.30.0 (2022-11-29)** * Display polls in `timeline` (thanks Daniel Schwarz) * TUI: Add [,] shortcut to reload timeline (thanks Daniel Schwarz) * TUI: Add [Z] shortcut to zoom status - allows scrolling (thanks @PeterFidelman) * Internals: add integration tests against a local mastodon instance **0.29.0 (2022-11-21)** * Add `bookmark` and `unbookmark` commands * Add `following` and `followers` commands (thanks @Oblomov) * TUI: Show media attachments in links list (thanks @PeterFidelman) * Fix tests so that they don't depend on the local timezone **0.28.1 (2022-11-12)** * Fix account search to be case insensitive (thanks @TheJokersThief) * Fix account search to use v2 endpoint, since v1 endpoint was removed on some instances (thanks @kaja47) * Add '.toot' extension to temporary files when composing toot in an editor (thanks @larsks) * Display localized datetimes in timeline (thanks @mmmmmmbeer) * Don't use # for comments when composing toot in an editor, since that made it impossible to post lines starting with #. * TUI: Fix crash when poll does not have an expiry date **0.28.0 (2021-08-28)** * **BREAKING**: Removed `toot curses`, deprecated since 2019-09-03 * Add `--scheduled-at` option to `toot post`, allows scheduling toots * Add `--description` option to `toot post`, for adding descriptions to media attachments (thanks @ansuz) * Add `--mentions` option to `toot notifications` to show only mentions (thanks @alexwennerberg) * Add `--content-type` option to `toot post` to allow specifying mime type, used on Pleroma (thanks Sandra Snan) * Allow post IDs to be strings as used on Pleroma (thanks Sandra Snan) * TUI: Allow posts longer than 500 characters if so configured on the server (thanks Sandra Snan) * Allow piping the password to login_cli for testing purposes (thanks @NinjaTrappeur) * Disable paging timeline when output is piped (thanks @stacyharper) **0.27.0 (2020-06-15)** * TUI: Fix access to public and tag timelines when on private mastodon instances (#168) * Add `--reverse` option to `toot notifications` (#151) * Fix `toot timeline` to respect `--instance` option * TUI: Add option to pin/save tag timelines (#163, thanks @dlax) * TUI: Fixed crash on empty timeline (#138, thanks ecs) **0.26.0 (2020-04-15)** * Fix datetime parsing on Python 3.5 (#162) * TUI: Display status links and open them (#154, thanks @dlax) * TUI: Fix visibility descriptions (#153, thanks @finnoleary) * **IMPORTANT:** Starting from this release, new releases will not be uploaded to the APT package repository at `bezdomni.net`. Please use the official Debian or Ubuntu repos or choose another [installation option](https://toot.bezdomni.net/installation.html). **0.25.2 (2020-01-23)** * Revert adding changelog and readme to sourceballs (#149) * TUI: Fall back to username when display_name is unset (thanks @dlax) * Note: 0.25.1 was skipped due to error when releasing **0.25.0 (2020-01-21)** * TUI: Show character count when composing (#121) * Include changelog and license in sourceballs (#133) * Fix searching by hashtag which include the '#' (#134) * Upgrade search to v2 (#135) * Fix compatibility with Python < 3.6 (don't use fstrings) **0.24.0 (2019-09-18)** * On Windows store config files under %APPDATA% * CLI: Don't use ANSI colors if not supported by terminal or when not in a tty * TUI: Implement deleting own status messages * TUI: Improve rendering of reblogged statuses (thanks @dlax) * TUI: Set urwid encoding to UTF-8 (thanks @bearzk) **0.23.1 (2019-09-04)** * Fix a date parsing bug in Python versions <3.7 (#114) **0.23.0 (2019-09-03)** * Add `toot tui`, new and improved TUI implemented written with the help of the [urwid](http://urwid.org/) library * Deprecate `toot curses`. It will show a deprecation notice when started. To be removed in a future release * Add `--editor` option to `toot post` to allow composing toots in an editor (#90) * Fix config file permissions, set them to 0600 when creating the initial config file (#109) * Add user agent string to all requests, fixes interaction with instances protected by Cloudflare (#106) **0.22.0 (2019-08-01)** * **BREAKING:** Dropped support for Python 3.3 * Add `toot notifications` to show notifications (thanks @dlax) * Add posting and replying to curses interface (thanks @Skehmatics) * Add `--language` option to `toot post` * Enable attaching upto 4 files via `--media` option on `toot post` **0.21.0 (2019-02-15)** * **BREAKING:** in `toot timeline` short argument for selecting a list is no longer `-i`, this has been changed to select the instance, so that it is the same as on other commands, please use the long form `--list` instead * Add `toot reblogged_by` to show who reblogged a status (#88) * Add `toot thread` to show a status with its replies (#87) * Better handling of wide characters (eastern scripts, emojis) (#84) * Improved `timeline`, nicer visuals, and it will now ask to show next batch of toots, unless given the `--once` option * Add public/local/tag timelines to `timeline` and `curses` * Support for boosting and favouriting in `toot curses`, press `f`/`b` (#88, #93) **0.20.0 (2019-02-01)** * Enable interaction with instances using http instead of https (#56) * Enable proxy usage via environment variables (#47) * Make `toot post` prompt for input if no text is given (#82) * Add post-related commands: `favourite`, `unfavourite`, `reblog`, `unreblog`, `pin` & `unpin` (#75) **0.19.0 (2018-06-27)** * Add support for replying to a toot (#6) * Add `toot delete` command for deleting a toot (#54) * Add global `--quiet` flag to silence output (#46) * Make `toot login` provide browser login, and `toot login_cli` log in via console. This makes it clear what's the preferred option. * Use Idempotency-Key header to prevent multiple toots being posted if request is retried * Fix a bug where all media would be marked as sensitive **0.18.0 (2018-06-12)** * Add support for public, tag and list timelines in `toot timeline` (#52) * Add `--sensitive` and `--spoiler-text` options to `toot post` (#63) * Curses app improvements (respect sensitive content, require keypress to show, add help modal, misc improvements) **0.17.1 (2018-01-15)** * Create config folder if it does not exist (#40) * Fix packaging to include `toot.ui` package (#41) **0.17.0 (2018-01-15)** * Changed configuration file format to allow switching between multiple logged in accounts (#32) * Respect XDG_CONFIG_HOME environment variable to locate config home (#12) * Dynamically calculate left window width, supports narrower windows (#27) * Redraw windows when terminal size changes (#25) * Support scrolling the status list * Fetch next batch of statuses when bottom is reached * Support up/down arrows (#30) * Misc visual improvements **0.16.2 (2018-01-02)** * No changes, pushed to fix a packaging issue **0.16.1 (2017-12-30)** * Fix bug with app registration **0.16.0 (2017-12-30)** * **BREAKING:** Dropped support for Python 2, because it's a pain to support and caused bugs with handling unicode. * Remove hacky `login_2fa` command, use `login_browser` instead * Add `instance` command * Allow `post`ing media without text (#24) **0.15.1 (2017-12-12)** * Fix crash when toot's URL is None (#33), thanks @veer66 **0.15.0 (2017-09-09)** * Fix Windows compatibility (#18) **0.14.0 (2017-09-07)** * Add `--debug` option to enable debug logging instead of using the `TOOT_DEBUG` environment variable. * Fix: don't read requirements.txt from setup.py, this fails when packaging deb and potentially in some other cases (see #18) **0.13.0 (2017-08-26)** * Allow passing `--instance` and `--email` to login command * Add `login_browser` command for proper two factor authentication through the browser (#19, #23) **0.12.0 (2017-05-08)** * Add option to disable ANSI color in output (#15) * Return nonzero error code on error (#14) * Change license to GPLv3 **0.11.0 (2017-05-07)** * Fix error when running toot from crontab (#11) * Minor tweaks **0.10.0 (2017-04-26)** * Add commands: `block`, `unblock`, `mute`, `unmute` * Internal improvements **0.9.1 (2017-04-24)** * Fix conflict with curses package name **0.9.0 (2017-04-21)** * Add `whois` command * Add experimental `curses` app for viewing the timeline **0.8.0 (2017-04-19)** * **BREAKING:** Renamed command `2fa` to `login_2fa` * It is now possible to pipe text into `toot post` **0.7.0 (2017-04-18)** * **WARNING:** Due to changes in configuration format, after upgrading to this version, you will be required to log in to your Mastodon instance again. * Experimental 2FA support (#3) * Do not create a new application for each login **0.6.0 (2017-04-17)** * Add `whoami` command * Migrate from `optparse` to `argparse` **0.5.0 (2017-04-16)** * Add `search`, `follow` and `unfollow` commands * Migrate from `optparse` to `argparse` **0.4.0 (2017-04-15)** * Add `upload` command to post media * Add `--visibility` and `--media` options to `post` command **0.3.0 (2017-04-13)** * Add: view timeline * Require an explicit login **0.2.1 (2017-04-13)** * Fix invalid requirements in setup.py **0.2.0 (2017-04-12)** * Bugfixes **0.1.0 (2017-04-12)** * Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/contributing.md0000644000175000017500000001123114656344035016754 0ustar00ihabunekihabunekToot contribution guide ======================= Firstly, thank you for contributing to toot! Relevant links which will be referenced below: * [toot documentation](https://toot.bezdomni.net/) * [toot-discuss mailing list](https://lists.sr.ht/~ihabunek/toot-discuss) used for discussion as well as accepting patches * [toot project on github](https://github.com/ihabunek/toot) here you can report issues and submit pull requests * #toot IRC channel on [libera.chat](https://libera.chat) ## Code of conduct Please be kind and patient. Toot is maintained by one human with a full time job. ## I have a question First, check if your question is addressed in the documentation or the mailing list. If not, feel free to send an email to the mailing list. You may want to subscribe to the mailing list to receive replies. Alternatively, you can ask your question on the IRC channel and ping me (ihabunek). You may have to wait for a response, please be patient. Please don't open Github issues for questions. ## I want to contribute ### Reporting a bug First check you're using the [latest version](https://github.com/ihabunek/toot/releases/) of toot and verify the bug is present in this version. Search [Github issues](https://github.com/ihabunek/toot/issues) to check the bug hasn't already been reported. To report a bug open an [issue on Github](https://github.com/ihabunek/toot/issues) or send an email to the [mailing list](https://lists.sr.ht/~ihabunek/toot-discuss). * Run `toot env` and include its contents in the bug report. * Explain the behavior you would expect and the actual behavior. * Please provide as much context as possible and describe the reproduction steps that someone else can follow to recreate the issue on their own. ### Suggesting enhancements This includes suggesting new features or changes to existing ones. Search Github issues to check the enhancement has not already been requested. If it hasn't, [open a new issue](https://github.com/ihabunek/toot/issues). Your request will be reviewed to see if it's a good fit for toot. Implementing requested features depends on the available time and energy of the maintainer and other contributors. ### Contributing code When contributing to toot, please only submit code that you have authored or code whose license allows it to be included in toot. You agree that the code you submit will be published under the [toot license](LICENSE). #### Setting up a dev environment Check out toot (or a fork) and install it into a virtual environment. ```bash git clone git@github.com:ihabunek/toot.git cd toot python3 -m venv _env # On Linux/Mac source _env/bin/activate # On Windows _env\bin\activate.bat pip install --editable ".[dev,test]" ``` While the virtual env is active, running `toot` will execute the one you checked out. This allows you to make changes and test them. #### Crafting good commits Please put some effort into breaking your contribution up into a series of well formed commits. If you're unsure what this means, there is a good guide available at [https://cbea.ms/git-commit/](https://cbea.ms/git-commit/). Rules for commits: * each commit should ideally contain only one change * don't bundle multiple unrelated changes into a single commit * write descriptive and well formatted commit messages Rules for commit messages: * separate subject from body with a blank line * limit the subject line to 50 characters * capitalize the subject line * do not end the subject line with a period * use the imperative mood in the subject line * wrap the body at 72 characters * use the body to explain what and why vs. how For a more detailed explanation with examples see the guide at [https://cbea.ms/git-commit/](https://cbea.ms/git-commit/) If you use vim to write your commit messages, it will already enforce some of these rules for you. #### Run tests before submitting You can run code and style tests by running: ``` make test ``` This runs three tools: * `pytest` runs the test suite * `flake8` checks code formatting * `vermin` checks that minimum python version Please ensure all three commands succeed before submitting your patches. #### Submitting patches To submit your code either open [a pull request](https://github.com/ihabunek/toot/pulls) on Github, or send patch(es) to [the mailing list](https://lists.sr.ht/~ihabunek/toot-discuss). If sending to the mailing list, patches should be sent using `git send-email`. If you're unsure how to do this, there is a good guide at [https://git-send-email.io/](https://git-send-email.io/). --- Parts of this guide were taken from the following sources: * [https://contributing.md/](https://contributing.md/) * [https://cbea.ms/git-commit/](https://cbea.ms/git-commit/) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/docs/documentation.md0000644000175000017500000000134714526070754017125 0ustar00ihabunekihabunekDocumentation ============= Documentation is generated using [mdBook](https://rust-lang.github.io/mdBook/). Documentation is written in markdown and located in the `docs` directory. Additional plugins: - [mdbook-toc](https://github.com/badboy/mdbook-toc) Install prerequisites --------------------- You'll need a moderately recent version of Rust (1.60) at the time of writing. Check out [mdbook installation docs](https://rust-lang.github.io/mdBook/guide/installation.html) for details. Install by building from source: ``` cargo install mdbook mdbook-toc ``` Generate -------- HTML documentation is generated from sources by running: ``` mdbook build ``` To run a local server which will rebuild on change: ``` mdbook serve ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/environment_variables.md0000644000175000017500000000116614656344035020647 0ustar00ihabunekihabunek# Environment variables > Introduced in toot v0.40.0 Toot allows setting defaults for parameters via environment variables. Environment variables should be named `TOOT__`. ### Examples Command with option | Environment variable ------------------- | -------------------- `toot --color` | `TOOT_COLOR=true` `toot --no-color` | `TOOT_COLOR=false` `toot post --editor vim` | `TOOT_POST_EDITOR=vim` `toot post --visibility unlisted` | `TOOT_POST_VISIBILITY=unlisted` `toot tui --media-viewer feh` | `TOOT_TUI_MEDIA_VIEWER=feh` Note that these can also be set via the [settings file](./settings.html). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1723457536.8472738 toot-0.44.1/docs/images/0000755000175000017500000000000014656360001015161 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/docs/images/auth.png0000644000175000017500000006327114526070754016652 0ustar00ihabunekihabunekPNG  IHDRY IDATxw[՝7Uo#MŞ;6&@J:)7n웅!XzBƀx<3^4]ܑƲ)fFc09sEIw(i"~$JAhhP !0 gO.h)iG\x35@?Ij]} *) |Ɍݿ,C=M#"H1tp{|S݌J ДQwT7h<wV,21/ RDH` ΀o[8M%W]ۍ[SݔyhӈFjw!" %[C)83DDDDDDS""""""DQVbJDDDDDDY+ekMe1ϩJݫURva@DDDDDg~ tz(ڵP3M!""""H€7>#OlZė?q}'-{uI+ӥOrMh#eV€d?qWCg.\xbl~ǸeE^W:K;Su]:_z7@5t:ʹov3Ug{hz5ݽN8 VOF ,ZN' ڵtG|x6_F|Ͷx(s A(DKy]֞9r3xR,_R|䚍p}w$~b 6',|kWW;;U·r }j+eFZ:jǚ;?DDDDDDɌRZĺsidAmt{ m}8}{$,+~x&\vvkJ¢y3l ^Htڙܲi#zmhi?RzV/o7@\@Lon}~ot{qh+j*? .7#m%""""֕KœcߞyW|5WBQ!e}vG4W~aԲm\ .BsK7`OY`iK` pm8->wÅeF|K~2ѿ/x-xE(s/yͻ2V"""""h֋/nmjZĂٳ WENhCo\ڌl( IVysQ}ar֖h<h@{g?7wFpdmDET `ρ#9u1{ȦZ.Ax=~UJ(rxD鼾@={%""""tJs@QGkp bl_&SY^(qw B$:s ;i*sfUEcC sӪ=DDDDDD%iq9-CC'1cTک;?yQ Ѩig•·r5A@Wkf,׭^R謖0`5a:Esk7 aמXb֮^}RzH$8*jyM|3+!~' {Y- ;1 Et:p^i*ut JHpiYi>AId[j V> -gonԢ8 rt鳡kg=j\2R.v*u}ύؒڸJF_w2GDDDDD4 U8҅ޘ| ^x}[7]48ƜhwcFnp_͟^#=^_>jGz75A"̮QEsk7~NtżV;pzPV(*0HS~?w$#r3Uowje)Fo= wAMelqb(B*TVm=㫱P v=]xbTWw ]9bӬ$,ZNgB)$Mu32.=4-0X%""""V""""""J X(+1`%"""""ĀV""""""J X(+1`%"""""ĀY 0Mp _.d5ZG@DDDDQ0;v-6?c46LuS&Ew\{,?s.p^Ǹ S"""""JӨmo?SOqE A^krMhTܢON$gYj2IRhOihlBDDDDkԀuy$ f m̅/g~[6]4Lt{~[Ko֥l?,~^h4#'KP7 <ƆXt DQtT yln~YhF̰F2;9ǀfLF#n r}a\v֮='?,֮~ww%湫.]onv;RV[6mĪsV)q8Ԟy3/@C}t: ZzwFAٿsWÆ+Byi>>wF8g64*U_~^}_ݟ_LF=\\p"#,8ҍW/>Lyyi>.ް˗ԣ$f#\n:ؒXfb;`4O} ,D~nNu綍zLRq9x!r A(DKy]֞rFM~ekaBARw/S} Ew}z8]^OAsk'"""4`5tX-cLJG!I֬酌5b{ȁAC}:b|oבcn}~ot{qh+j*? .7e;C8,hS;|ݯ_r$K!8o||[#m,JZ_:Ѳ03JG<yߎB Xص8 zͩy30n5~'bTMoCK[ fV zyt/q4ُnü9p8=عrLyuTKD*j9A;7Ⴕ{_Tj͛~gfUwbFd9&=𳯣}v;taQŒb\5U_~]Iۯ@ń< lڈ  """"IUK@RbţuЉ]-GMe1Z{3Ҁ~;/Z*ac'7#{mX筚7y}Me1WA_߹O8ȳ~y\sٹxg!Xۛȓ[ȓ[ }zør;z_xr.-6 7_2/q1oN5iws4 ]WQT 8ZqEhn7~lv7Wp5}"#%b·.]ʲ!xif`%#bOнDDDD9Iaܿl{y.[<翾")J< @mJį|.&/['ۆ{js36"""" VBUK;> XWdW NO:m  ?/sg$ݝ7_xy TAx=~UJ(4&CC}%rLztXq%> 2Ҿ:+*>_ =s:0涌d ,iK=:+o ~"""". /?9&=ض/fa[v`n5,f#wYb6c˵T*.siY6trΙUk.; 5(*̅Nt>eSҮdR9.C** ] $I0u0&ܝWv Xm[ֈ0`]rxuӅ"v=u6s&f6:;oшt&jXr]y*|+WCtXᾦhz33q0P&C%qH^'g2}E$ah4*uic?ķXb.h9-0|Ob3M/ so~LXp͊y2`]qs[31| +;Zo7x%AO~^z=v>vOFT!j5=S:%qH\$PMv\Zn;:Hm;bێjXhb--}?:DDDD!q7ͨ*AeY.|)?QPG5+ 8QP`ѨLvy/ұXj &Bo0y[s\RTu mSZEevvg\V@dzm\Ee%=&n"Qs1(?gId2;VvhɾܾƸx8ijG8,(gh5T!r=}6w עq̸W- ؕ`tTe|lUouų;۞DDDD4u⮶#[ּ`݈w;sHg٢ǯdnQCq9a4 gj .SSv$_FgV_2cn7>DO 3~Y3˰vL_&TW7nL DDDD4bYL7=}64t'-. i{Nlv=n|TߎƆTgz{ [ُkšmPU;om?ՓivWv)__p) 8PVr}J:ۿʲv%ѿgVSD7_9ݍ__Gpnŧ[/vt̟S ^Ri:مm;b_} ,?Ve>qJ軟W]F8/bZ^ހp'6uex;pե<&I縤2۱|lY1OT7FԎUPhEA-|^%:R|]@KTOus)Lng}9wڠ2q;tu+N& X҆]-p3B `D=h{5NDDDDdeJPhEoO8S (jyA}0vy4-%""""hԀUP`?~SML#a~5 02P[B(X=L_oףM \0x64\z;tMXLA[TݢG|8`QBJ.ug.Ώ/WƱ_Bm ` 3#/B_C t~("a?0WP֊Sr)лp0N*e3aЊ]lG7Ԗ i8l@ΈYIw]yڂ$Q_A7rf6 (%*xZ 1g|tؿ:>~8ohz5`E.63E p(- ď+BʋkpO?u䄜*H4AfaAS#>k焥 V=~\0T{?i!Uۥ&e$ j!J(bDZ+^%ڟ.KӮGثA%A 0jsvADȭF}2?_)J9e gb@OR‡BW@[qe:՛: "Rۡ$ raY@ۣw3Rz{0(lrpc( ]9;˛F~^,rp\kVXwº#W@5]0xa-Zs ]/y@0U}ڢWf>e~.zSt%Q|a?B.ڟ)Sin.vuO~ԺlPR:unk0TiЦɈ-r( 2aPvy4yAFA4Њ_npd{dPOEQN-}RϩiY:7wϖ-N!g+ Z_KuR==ohI%[/-BsBT]qK!! *SpFք%(WPÐBJ/KZ^'WPfJ׊j2&x@PJȩw#TEդoUP4owAY! >7T끡Fv5\MԗN6bTU]QO_vD~n~8t ]վ Yvq( 9Uh~:{)n@9vհ5ǽNic_YN1u,Y.Gi0HN:a ; ^T\Ӎև+ub<ӿ0~DDDD4%WЂ^߅^9ގ^ F-Cvk.zӡPP.TT L!(gzjtPIP|A?3 c3t?""""ޒk9K훿BsK4n"D &ڻTPJ&zh:&PD$h!٦C] oO^^YV.z$:Z3c6;DM2ϛRSM--ZPq<J&E~HƲЁ/b歧yTKa$ENADDDD4-8wۊvvY8>>]/fND=P H:k̅ByVm%&vUBRIrm>QcPa`` WwCewU W9~H718 TA_Cy֘6 * mڏw$޷ .Xi$`q/؇ e@[8QhD_0> % #1Rtl:?M]vԹAbhnpBW"*{ߧ:7jnj`!W4:Qvy/N=V= `BPIp4~ ~ yl[j&/K]|=yKitn-,@T^_! jKbx[ Ih0zP~e7N=Q)$ P!TA[BСRFJB.'Gh:0Aϗ./<_ ] Ma/}Lw^33<0z06x;t>J RRcK-^%TPtP C+Rrg= 5^_)m=| """ɓR/&\V[H :Tb1 V<Caf lσ2Tgj aFȩ so bW2B_iգ*)+ g|Z/AW懶$[ ǡ II1R?.E%z uЗ:aDe]!A{G׭EÕ_nqGC6$ף C]:+Abw%I[, WG*ai"ʮG)? n)@AA}[ 6R_D*y v>_6}B jHa&"""F¢e$z-4 @VX:!(%xZ\(g?*:ZS@;yțt&փ PKp3ok3YDDDDD4fY1%NfN6,шMDDDDDDQVbJDDDDDDY+e%DDDDDDQVʾU,NvOuK4͘JC*/T7 Lu+>{lD>@rx;tI_[ڟ.uRzq8횮Wؐwݯ!x0gC ;Qa`}?w1T\gM1}+M Xŀ"'4TTDfPhE9a@SR'B+3ѿki/EQVFėZ7¢N;(0މ%xmg>\PXE\dyGws]ޅPXcmmx|JQ@5xUk S VO {{_ѝK *dB}un(@LQq];T{ \ zuBCZ~eLunJXe%ú3oǗu/jQj^ʑ! R?j {ͰZ  ZrX9BN>z\PC],pyDfl;vz(D%Еiӣ|(`GLRMgZști57mY[@1-#o=uFt>[B]yڂ$Q_sX$kgAf2Tö{ B 9Kx!iG7Ԗ i8l@N\?L|LEr ᰀsX4˅mxY:cbUh5"gwccf=V x Q65`E.,3E p- ď+BʋkpO+\9 9UX5zh 0r8ӃF^u< K> ܭz07`+*,7J/CثK MyUSIB ЕQrQ;߉µVJ?]0(]W }J/rU9!T\)@`P c%i ^-x;pS?NDsTv I&?%vX9hE=a"T[ЕT;tOvC (iCPJWP^{+~B-.r }F89KE*n*&/[ O } e~tRS&t_&^+bN}Z :U8l9}bKqK (-PdWv!@í4ֺ1o;~WhsA8eGuEf v6WxR+}'ty3ѯ@׋G+ouG.;r,T^CYn8آӦGCzADZ+N=%s ]/y@T}ڢW2 O٥ ]?a4A_؏KgJT¨p0Ӛ<Њ0zPb@gJL ZaҠMWZ GV5>h u(,{JCu_jy ak̇;+e t<[ v‡ )TE?F't}p74Ƒ};?Y!CDէ:I=`]X7tDDD4L.2_~v.<1n"s7\'=nsLjCG2˂yS$}RΦ$XiSB$Xw+X_ւh ș{n ;A!~=hf5wcz>_H@s%WC9TA#g,Y]t5ycRHN{#`UöW^Q4(%SciINg"o з &Tc`L3ArӍ{^/j{r4*qHYnJiл0fW[~S~LӁ7Uc6ȁmsIS($̟ݭBs?Jx֍wwFc ; U~[Ֆ,Ut}2W)ZBJmQOTB IHU$h8]$K?L$QHx\|`}o:5_ A) B"Y CwŶ'R>&I=bn<7F?4-qߣMi)M~bPHXl*vNDЮJe}zlq9Wб6tt LV&Dde^UYS;6#AB5usJ\3',TD)H~Ϟ&?A%iH#_:~RH@e0չaitP퍮բŒ `O#$(CAgqT C:b`riO[t(PK8p ^V1sS'҇rp>4ֹv9[p [}XCҀՠ?3tZ55x;pݷK-鹧F9~(n!Y_mMRPMq"Pr`uJ)RT؁sWz:Ar}UdIJ/cs0uA%9vW?HCU̚0>&L83r73OLb"_2m +kNX[ xUq܇;ȶ5u^tau"Ju$$뉈($WЂ^߅^9ގ^ FrhnmV={T&9,04B.Ā*S(KE`PJ /q'5r{ ߑj$ KU&O p7mF:uP0e懘3js(=j<5U"̂C[EX!EVi@LKQӣc,J9|wkcՠDMgs, ҹ 4&|u_^'ٳxE%SRPJ%g:&PD$;hL*WxZ0ջanp'>3:V6=z7dc.ũ0:Z3c6:،W&/U }/C"c7T!hSCo{콙ƙŬ=ZHM^jKA{)z*˙/S, (\31@+EqUqH<0s/a,2"_dW_ۙ*{Ԋ /xpyxKq^X5H' @cSaQLDDDo衳ۊ6vgZӕb.:eDtDYc.γl{,qd\ 57Z:̖bOl![ cξ?XЈ*p# D-9~H7l}(01B[-?g@~A)1Y(^[&/6@?}ؐ?/yؓ/2AQ|0Ԗ 7T!9 TA_Cy֘Ͼ6}G-죯,f}M< n#ړm̭KWum+nujNvawX*GQˊԹAgnpBW"+*{ߧ:7jnj`!W4:D `BPIp4~ ~ yl[j&/K]|A;yK!ukf.TP[B{n'& XA8D9CSmjsA J]+mTPꇃޛkz+:~J}J[h*2bNÿn̼|]ZJ ?\MFZz^33<0z06x;t>J S[WxrG M~Ͱ,LjMt!Aϗ./<_ ] Ma/ǔK{)/'Ƌ+z=-բ1¨-P/a ^ׇE\xՒ_?U/_݁sX4˅zJXL!,A«秝]Ć9ut_3Io &-}lՁ#pLgk%p.+V^L(m|t-by jn%؞),e~Trϱ<!Sa&rîFe(\k),ӪG %TRV@Fz_fmIaC91/pcVuC't~]ڋK@Սu/!hSuˆ9.<* 2?BneT2~aT2 l7E~y5YRa\'ޏr16\0"Uߧ=T[ |0ϗWÕ$n-?$ j[y8_n IDATo.' XS/]b@-ࠀiCukp%a* hI.ܛ Yks |ȲԲ`1ܐTZQ P2K/V0uDUI6HZ*J(d[V|!zA ?#ANE+djQRPT5ĢDݷ_C\˺/wppPwn\{%l߹;Z`rZ|6t]g׿h +X0 ~B>oR2kYvϭKW]m}%K_)}5_[O븯` _~X+C:u0ncٖu9X.Zq&Z s;B?$ aМhz9^3SoD˛VC-Nt-b[G9s $ImJbQ,Qh"S H6'jBj=!uwu;0 n+!m '$łV\s(QRAP3^;$[jtUR'8rj7OtmCw4#g<AHOBRy#;d#JΟUaHļY|"qN}i]W>gvbxSpr"3,d#.OZL$ՎZaCz6műy +|AkYHW{x}z Y~ u~jDl/ijS\;%Y'_) /k5Ԣ$ւlƔL*gȲ fJB'Nj៉ው;Z hų1|mn;`pUNY`l_XèґΎ׏Y! ĵ/sH߼ɐI`TϤBOx{j #xyOt=skYpp 1T.ǂS(FQjauٳpE9,Z0!+#.'7+]z, =ڭaYu4 Qx;DXTCCT_y M!'+=y+2ry>ٷr^Tq/}?}]b,%s0N[[L[{wL`ue|:[tSUY=w\F"/!%SLw]I[{7즬$o@}/0MmߴnP^ϥ_Džg{"d tBiaA\ne6gsj )v T32?ElڲE)Ϧ7}FC4 L>m6 |fǗϷߺłt$ *M?_43Ӹ隅/|?᠍d{ B,eWωc=%s_{1vM5ۺ IQ2P&\;۱cx5tG3jN%ֱ碎F:V8_^s?Ӱ[c?"#An{tTȩaSgcuݳ~9ejxԜ81(:r Ztx[Q2JQ'dѽe ux._>%^3jn^ +h$!d[I s0ܝZw")bRb\ *M?2֊fiYik\uoa?So@Rx[v i)(]qՎsAgx]~e[rz >Fo~UHҋ:u'}A,E[FRmاcБSFՠΤ{BM"߀uʞO"Fk#m<N-U|戒|xxtdIⱇnfZ%Ξޏ}d49,So8[Z#JFԣr%r}cкVspJpH{m:dvf?\d.ħ붅l\ ~ ~`Sc3$>/Z<\>t?x9>cSYxk~/0:Y--{Tekxg>w2߼2OHvrZa\s?a pE"G:d{kpx?[%6A0}?%R;(9cS>S{qn ^RmV.ARpʑ=QGTA ~/V0kzEkh"g4zPg̜?` g"s6ܸIz'Cɶ_$wʵGge>Y5g_>ٙt`̀94\u,Wϼ6W̑/?h=y𞯢:9ԅ^V OT.~Cv^[:?LTw.ΏGRm(߽<;_FUä́%~!VJzL<3H6m7fHo!%~2`e3'd> XkW47]۬C{V^n&Jq:!>cƫ(MUx/?$Ibnm^?;ޗu{p:LPo[YKk'ZAe_c78].4UAQϢy>;o_OCL}?!,!=h~zl"G}Yf ^_c4IF} K<нaR Z`ts/rd׳M,0}RF{ O% EhyP2JBY%Z" $elzWpmm@ ,|Mg{B_<'ax%H -kmS]͝3^XL$`8؂aHO ӑyY|EWn跽̌2Sp<+.+3E;ogt*VQ3'~ 4 |r_'ǖpɒLEވ,l;?XkρH>៽$RŅi = %j^rJ.?e9lZ̀t_RHZJ'%-I6CIxĊVVt̚^ Obad^~jU6ަa ˩2F+U G!v ٞdIpwRV3W ~9\g~K$zL3xCͩD}ʵ|GmonnYGYEϝܚ `3)NW4ۺaXT6KȀrr3 G .#-USx`3fr{g^m5S)*jsܲ5\xlsH|~F9>4Dg6<+OVc8:gD%XǞ/~>y̹jyCJ;q!fy]SS;B^h\n]=ÆͻH8%6i4nOPNt}?%Z}:b.EIN4`x&n6Iw LI`xApu`Hd%Z2Ƙ.X{W gKꍔx@w4/u"s$#Mv?d5wKJ#[7ܙ=sop?/>AĂ=% )p;32K u#Jl 5rĥ9o0v$[:(л;?WbLl닯̈)Iت.0gv f:;f* $WyL?)ߏ:b(8m< : '[3!DNQEghb^"yN$aՒ%-n_#k> yLf 9eje;.>mPazϖ5c)Gg_4M%%D}t=oq/W~ pS;#]g~6XHLe ʊ`l6L&T I+Vmb#aX&_O;2vzw_Y4*T@>gh]wřN- x~y3Yl}>L895Uٺ}e%yͿ}i.)kT`_i/qmk R27;ޞq JhԜJRjn׾IR`DVp'Ƹw4`r-}d[r9kmI%Z,8w9OFI3tnwfhx#eg+eN<֡LVu /G`ԚvPs˽R:d6RˬloZI|_W2${8 \IDATjN%+^@Cd}ypl3IWc)7 _~$YC(AR,ȩy8P.(Z֝`XF?zh9ٌhj/V]ٌ:u4"I2rzަ-5h"3Z%>[%B+i)(e\:1$K:JyoަmxgN XL@Ca?22/@3w zbNe۸?ijX=;w9g̠f8;odݗ(%H y.Y2ήaԱΦm{X nY)+>و"Evkӧ1cZ:{xf 2~lI@fٽ,8 L>6_knn13f86o#O53#5d&E[t//{SpY1pnzSQ*0 _>@ @ %"@ @ %b@ @ Ix9s&"`@ 3g, W\q-K@ @0H>(V@ @  =+WD$.]ҥK3gX@ @0x^;<=@ @ r$IncXU @ C$IAXŒ`@ @  KD*@ aX@ @0,@ @ %lNr<=;IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/docs/images/trumpet.png0000644000175000017500000004014614526070754017405 0ustar00ihabunekihabunekPNG  IHDRaMsBIT|d IDATx]y\M/=MDQh 043 1#3_b%ƌk'„$l%JiӾ;-}^W<,<] (?-=P`tP ,@ݻ:BFW@w055Eee%ۧѣGcҥ=PF^^Fhm۶EBB.ڶm۴#VYqm|ᇼ}B111y&,X 35˗Ƴ… ׯ1-^˖-t zÑsssoUDJJJ fВܻwM^@UU555ӧ]HJJ7S6:u;w 99={7QSSGBOOfffĉQQQ@UU>KFFFBCC o:ZnІPVV[n֭[VbYmhԩ,(>44?lll0`]l… hР]\\7?yrZdd$Bh1&W0x`ڵkpvvf~߾}ܹ֭*<|EEE3g@ǏO>,G&LCCC| JJJ`T[dt)]ɵsk3ƎK}۷O#Ҍ3裏>۷oӒ%KM6H-XGGGe2z||SXĢnzzۖkGر#Ν;dzrªU`ccUVa믿->7m4ŋGii)b gΜaK!uuu,ZZZ:TVVB$L?/)TTT0m4 G!!!***۷/={{aٸuѹsg =zsxJJJŸ{.z@,Zhiiaʔ)066ƺuPRRUUU )) Æ ޽{yR@CC#TTT@YYRaeeDbBMM ***{Byy9{ܶ&;_qq1ZZZPUUetttPUUTWW׫}6BCC@NByy9ÇEEE0a݋.]`Ĉ OOOȑ#ڵ+V\CWW7BCCiHJJę[UTT ]]: iLHHȑ#̨fBHH"-ݧL"s)qq@l_yyL'ÇSUU۷֯_O)uN@ ,*..PZ~=%''~^ Dz ݼyFɮ۽{w200SNQbb"3O$&&gffF/^?^JHYYYl2rww'GGGK#SSS T***(#sN#hD,f̘Aiiil_rr2 OOOŋtyޱ322H~#}* `c~,GDo>0a]pm0***;w) bbb( f̘AnnnrO>+W4ۛ<$j.L2yCQO8)ڂ j*DDD0 N&}a߾}066f3MII @JJ QYY)3TÇx }}}Cii)z KKK\t :::PUUEDDB!k\vMnK@lVAnn.]B1}t|(**իQ^^K.>۶mCv񰲲ĉh׮zj_~R+Ǭv=-%%:t&SlXCmSҥKŶmpuVl޼MzwyyyPUU쐒Ç#::ׯ={66l}ȑ8y$e˖֭[ؼy3dHN4 %%%R2>L6 SNeAeeef'LII<.cǎz*Zj*,Xk֬iб***BMM jjjC@e=z -- HIIAnn.1j(TVV",, HNNL8JJJ077Gǎo ":u EEE077gG\878y$m{fc_0ftĬYXm0R`nn[ǣGx)zWWW3--- Z/Ʒ~[5)ƢErJaӦMRKvt)Fc555TWWCGGXt'CCC#kkj.k,׏)--sμ^}>|8 T#???255%]]]0`JQ]gAGӦM#???:~8]v ŋM2DtQbڵk+^w&g\II(7op̤VWWg}F={RCJatYKvSSSΦlrvv&^,:yd1??)8(ԩjՊ233uaٲeR!==K2z~~>u֍׷O>lLpF6sذa{Ak׮em ,I& mݺɯ?̾deeEZ۷oi۶m<"''iӦMR|N0zffԒ](Ǐ'4k,ڳgՀYǺuV7oeeeHKyVVƵk׎rssY$Z;88Paa!1ީS'zKwg@hΝez5%JKKNa۶mVN***OdaaAnh"2e %&& 4`6>|et###vԱcet&L|?&"¯JZl|999?t҅ƌCaaa믿/ݻwYN$}ۉZnbd1ɓ~ GMD|/҈}ƌV "OxkPMMMZ&MDt=K$9yݝxsZS ''wiӦѐ!CMQ>}ؘо}H Pxx8mۖ]F#Gd/IKKOw988ЬYk׮2qIE]$I-wED(+kkkޯSZZTk׮[>uג>iҤzctΎ&^ΝYY Dp"ѨQaΜ94f*))!]]]:}4YoNIĤ$Z>MvvvGjj*/ۼ ,%И$^Z_99s&x]y9p0yd/ /eۆ!@Qj*nݺaҥ-9Dp!?~^ahhkkkƍHLLDff&QQQ#;;/UUU.mڴ6mD_}988QYYGo۶mƆllľf̘A2S5VVmڴZEEEvI2s"4rss8>>LLL*7?$z-:'%%msYf+** 9{eL⧛ wf֛pY={of阳s=+QTT˗/`8JSAGG߳ fI022իf|||`hhCCC֙xVZIoQFڪU+<$fffO ???ykQoݺ5.\???̘1700y}򐓓.]666011۹`pvv Ny[YY kՉٳgۛm_t ٳgѡC%BfJԩlmmٶ@ н{w@rKSxƌ@@zzz֩S'8;;3X֭[m۶zb-|rmdd͛7>_d- ---!&&"aaa={6q8cС-uZj&(11455 g9uT3򧧧GgΜr)7ݏ?đC#Gy󉈚,3g8do}}}^ /׮MZ"@1coFNNNJ6664ydfߴiuܙ]Ɔ IMMm+++kXZZj׮듆<Ƥ}u҅rrrܸ54$y>71rhF/--}AֆG'" 5~@P߾}ݻT^^N^^^< gצ =zDAAAAR t1r {dd$>>>D$n۷/;v޽K}͘1RSS]>>Q9bcc[XXoFDD};.M4nݺՠqڪU+X+Wl媉TDG,NH,n̙3Xl6Ad``@EdkkK'NKRJJ ݻw/"Ξ=Kl?}9[pV/D\C\gϞ=˗ŋY8rCH2dLzVVYZZN$ %EFY}z"^]].Z>>>rqqRUU_TRRBĉ2u%%%Z|y 6DDwa(77|}}jjjԷo_9s&͜9O.ttthz/^$@@^^^`tI9ˬPF'2O?DM+Vx-Shܹ|rDT^^N CCC&VRAAD"JOOBϗ{SSS)))m;wWŋLyMf͒Zs3ӧ*8rqqɓ'P D$%Ν;Xؘx SNpţK^YWNC$^ 0A^[Ft6KeJ}Z9ZaǏ>|"())!!!w;wЭ[zգG?~xpUDDDSNEff&BBBwQ8wNSSSXZZwޘ0aB!<΂!bbbн{w8# @"BiiL=z >>^^^HOOÇyv킕ӧO1a>d>+Wʕ+l;88ڵCnn.k_yfhiiaذa ##]GG~)RSS~z\rڵk;_~,vj9*hݺ5-[KKKL4 ĉ'SV> j0TSuC[%v]oQQQhȶSNaɒ% ý{rJ ###|Xp!JJJ .`ܸqETMM ^EE???aHHH -,,vׯ3g=˃3@DXn pիjjj#33QQQPRR·~/q^p=zwLǞI&{u\f?SQQá'OJBUU1bW^طoKٱ7nl\\xyy9kccSWRXt)BCCw'N[i ͺv-碢"rttC6IH5jwel`` -ZNLzz:8p<==ISS%8MtAAٳdճG  =F>CڰaYYYX4hq&W}ժU)ښ}xQ;̙ۤ3ѣ^-xk3:̥obdtWWW^т;2il[__yyy1vtt4D"FD$-(?>yxx<K\ް&bt555h׮UVV6HF ҫW/*..&c͛71$KP(k]hjhypydggܹse?~쌅 ɓ'HHH#Gx}Æ ֭[@/%jՕ-]L=tPiժsssCfyƄ ၴos4TSSVc;v,f͚76p*^vHKK (++%ۛ0aDfWUUΝ;/8zx222h޼yĉ8GrӿoRWWWțО={ɉH(iӆidSN%A{S޽;YG2B/w GRF)/]DDݻw'wwwС9993KݩS'ee]^UUغu++\hqFJ*`?C iVSȖ-[x/ŋM4fڴiWMM mٲΟ?O= ۷/9UEDdffF{%#iLW">x`6ڴqF:CCCz*djjJ:tjj8Kʞ^^^ĄԦd?4&&&qB}rJvν{(""֯_OÆ . ښРA(""-ZӧEDDәSLcϏrrr¢E] >Q^^=xm߾\\\(//55W$vZ*..*HJDtfgպsŗ_~ݻw 'OĈ#󝹺"** gϞ&K9$ nHMMo+2! ###hkk믿fV_xݻw#33۶mP(lPqW^Mɓ'cΝ&n477GPPPݩS'L6ouD"`ooׯC]]8|0ر#lmmwΝ ~^^^駟"Byy9.\HR:K"&&.$$,XǏGPP@,6l> --JLL+T,hFݻ,A(˗/YW#OOf H?c?\-=cڱckoӦ -Y^xѠ&&&駟~b,Q|xDtR*nݺQAAҝÑ#G2WҜR^^ k ^cMjOҒV\ɼСCdnnN8yر$jftڴicUj$t]3}ڵ<+gh":t:tX .ΉcNE8=\E"=z˴!SSSݕ o57)55IWWW*RSbtx.kjjŋij… u*ttthռF||}߿O9]79F'"i-F!iiiʕ+)==rssVSSCM>>>u&faY(##nʮ]_6Ƃ^*9;ڶmKRcty=_zD"͝;Wn5UUUUf/,,l1fGJJJ51ӕ$M:Q䑖P(d?^\J_Pzz:ڵM0IYx'q~z,]G"##aaa۷㫯BNNY2,X ,@vЫW/ cǎ9 8::bȐ! ڼy3PSSe˖͍ij!//FFF޽;qYV޽;헞aϞ=22E LLL`ff~ Rae..];,VJwPVVƽ{BSL nݒRr⫯^G۶maff* : EQQ~Wڵ /^qAbݖ-[_XE&^zEG&@y,#Am۶/迳7[>nܸlΙ[pnnnnD>>>_Q3:g 7񄖖XwҧOzO4bא477,G\BN񨥥Evvv4}tZnbe߲eёvEcǎј1cёƎDN]dhhHNNNԳgOliر4}J xgHlC紣FFFqF""t7JCI__"""x2886{Qqq1@,^hԩv!5++-W\ctIq)1C]#3gZ8 4$XDTwII ӫW˗^/-Y|||>oС~z^fw9/>>F/_-߿Ovb㺺4uf 6ׯ{O]|ڷoO_Stt4"ZxqvvvS*,,ʫ. ,..&&ct"b*++رcGoX/R!t}]}{Dbf,@'={V1.99Y*9x>رwԉlج~I^_Uoonxz%СChr}:M4Iqvvvt);Vĉ~:>gϞ'pvvficcc| xpͨgpaXh>s^Bll,`bb{4Gرc<^EEǎCBB.]Zo"O>nnnlDz̙ NQPP %6;;pwwGbbzsEff&OZFFF(..Y\KK - UUU!++ {쁧'yyy:qh7͛7idii٠YNҒFݬ^IoM6M>]ȑ#>Ґ!CxIH6oL b2޽{I$Qee%u!22ؼy3*++q}5U{쁖LLL0`?Fb8}4>cX]vqH\~/^DBB"dff"77oPUUEϞ=1n8L6 ::: "ܻwgFDD}}}z gΜam۶w?k(((@LL bbb{rrr0w\;_w}MMM^gϞM6g}@Ücl|077w}x=TTTS_ryYea")))e",appp!CЫW/blؼy3ݻ}"--Mn~{@C{nL8AEK.'h8D"ݽ{e5k⋫6uT9ަDqq1=~|||̖W`& FDIt1qqq342zPqq1IHD" f^$uO8AzbhffF_|ϣWJ744ٳgK9?/t;v֭[k|ι2)Mcׯ^z*hiiA[[O>eKjHJJBǎKJedd`ٲePRRT!!!ppp`\PUU'|OOO|'L7aXjؽ{7W@ #Fd[BWW'O_Ç_~./^+B!QVVb,sex{шB^^tuu1d[[[\z :vk׮LetYHOOǞ={PTT_~ҽUBBaa![ңRvѢE>#f}ΪΙY1u~M=Ãq;{ǣ͔ozus?g^[*CzBoܑ)2 HLn8?mh)m{ZYsңb٧l{;GG1#VvɳP+p.R {Q,;]<wN$[?O[Uxvn#̮Vs}d#}ۮ`qb6K9dyC"'1'r ?~dv0ˆ\9{ك/>~8%1BDrv:ψw 3Ϧw=aQ rjm)|3VheƎ2iW؂)X/{ز%ks>W&A>#XBicȄja7Xs MF*g<|Ia 3Jg}kWFōcȯ"3"kZxZcDa =K3ÍgF4xƠBWr9l6wXgzc΃(1+x&r.`'>{\<}pf"'ɕf@ k)K.G~8{99sJЇp'Gy5PT\%n4=]C/aGnR?)(ڠ^ۙ3_1 * 3B.C92cBfegi 8XƊfdmDqyFiX]DPg"<1:xĪL%yj qeWw-x Buc؉!#{g6Vj? l+¶#6)2jSi (JZQ0+ʙ kލXd'&j}ޮMf ʣNj[v}&3i%WdB:LFd*kVYAfm2, Ko9-L1^_(gwba\gY գ q< I4$a]h 2(#?nB~D\;N5ᙤ0179G 6nťPg"YTS C}pۡY!JE x<٨;VLQ7(QG-o2MpeC66'wfȘYb`D᜺8N`>'ߒ@A6%3UG|qo Z.1]N3%?j |rD> SC~g~^3?c^{Dnƽ.6!|;ȓ>r+5ڤp$y* CY*@0RRRRW6TB #锧0aTZ02iI@ߨ\j7zE!%l(Bk0_HmG#2K HщdNB$pxTZ3c[>r HxW1xpܑIWpH l[E6k&79Ѡ0"!цUA%'lA͙r'<#GRQ @,k¥ i t|,5Dea!?A 7(5λ?% JMPa_Q n;[-Q*NcYXuP`@Q2ZSNkV$/1HZF M\#o^?MXe"4k&-N4 do0XhOX/Ko( euTsd&&'D$@јSقjJDw %nǐz!I[uV5p[@@[gİC2.M\2&!ZRKPZhN A&Q_/52C.T؜"W@UڬEsN>6efYNM I25Lԗz$5B![u'a,9W).Fm' s E%XQ65=Spj^؋u ed%~@FURrA5tCr%JUu_!(+Pc*a;aO F Q|v? "jh%g2uA ҜvDžj+E!LԡX@T2xhC䔠U@jBFTgC6)`F8 4Gn*J}ж&H`~\>_ݿ) 6W/ȽKJu: ے: bMkAW(Y ÷A0E!:sj_Gbͮ1T9:W[BUMr|v8Z!I bJQF :ROhTQ#Șm!z\,hHl IԶ Uѽ-u\ 1d|P*+8 IvPj SS'.H`(86l Q% v*"(ا6 nГo&y k׵[{j?9+McTBٖ8OՠXiZRI˒~nQ^I7!A$W,'NEZ2c "Ϲh6͚&/Ȥv2;UH^hW?D1e$l=D"j|C|" p<Y37ae) R9ǽxjɖjY'z"٬f Baح;$h u\jH;HrpM ɸw *wO"ش %pJ^bhlwCvEI5] @$r4H0J oF.>3h@EWG")[%$J4P3[<~35GSD Hk@zjE#j+fy>_Z R^ ĊʆDyd$kQb%P![+mS$A6)Iкhx|v<aD~G &L?-2»LZ~isJKV &j/93`z̒  vi؏ҋ (nf5-`z]C2531ߠʠ_-C‡CߟvÅDZD̖tC_vЂu6A䌄/;Tz u A/ R,X?/ę6uH,؅/s݀rsO8zY@!dDau4( lB4m\hcߢj&EHЎR0g,v3A}>7jP*Ѭ7lR uKU+d ̓*MPWdA@>ҏhJuSSPdž6ȀS ߖF't>@vc(ѧT28>3d1lc3@9"&b-U_&"0 V @>F>| Q7@U}$Ў3P9깮kٗҾ&o1Z(SIylSY`S\W5ZJ#~)<  1QԁDh})߀).jz.4$o"X)Za[{\5v u0`JT\A2"FD!sڇH)p)"_ -@KX-I.!{}yEç~jN~5q*ZPmPE,t\7&>HV1/xϤ !n&EջaRn}dzoUimkPm|f2y 3[]6dThQZif;38kA8\R =G^d"YH!j/D0[}EY1G--}[m(3!)%2j.ԏl hVZLjA-;jq) i9MPd Gp 1E3g55yՅǵLHȔ'DSQ(9خ@1Oz B!C9pɪfu0@\hs lk SunI ZfNAy}ӥ2%!ifpKGRB7mPʥ(0y4u|O6>}?K%aFN;>/:_/~~ _g6̟}Sۼ3juŨQT;h̔ලv(XPǧg@Z+MjjTW]e$]vHww@ ؍@T Uܻ+暏VQjDΪKG/J)IƇGO Z}66tˣH ?j]x%~|뤻+>W2-tBq}(1"" bnڅkFV@"cUH 1£>ƻRz{hmcSt`>\B,{ʦh̭Gil-w3?:"mst0:T2o[ qR,`y!/ eAE'*v+'Q#82 s!u6㦘v&*Qwt7BtR` + *."(OھHOMXU\U/3K *&M9c *Lj>SmP '5ހZ04]R;R+Dl8!ڵ h PTKS@Le Bݥ61uPӍF5zVCVYR#cLTڃ0 s̱~ 1f!<SO`+&vi/yzYѢ*,n2@PjS82) Ȧ@H i#!%l,SoP"BbHNu)%߲m,z:)e.6хUWXq=f0k@D5A24tmCIt]zlT QZv~}]KQs+Eu ſVZFkSk>w4!!'^Ne@6S6ydٵN ˁpl/ 1&B dUm@hH1:կ)#(Zሸ-ٍuvMN,bgir t!`)h l"zm+4[gF[QU U9T,Q@]DQj~^)[oxcz[(:Нvj8z HjԶ4+*iu>j:QgT7K;E -Zjj[l'v]Q/6CIdA>$)ZDf̩dV'-Zb^[(D%iN[TzW&JLR1>97'J7IC>NM3I{Nۚ]] "qnJlXchA3];v-xHhP6ZM2/O֐o6鴌JQVsA#t~yu_/}=!Z?xkkɪԹ,BgM; "뛼&μh(eBw(y*ݵg ڭ(Scr:NͶⅠPO)H/߻TUn#au5cw}ͭj7*'LOH=tz>^55Цo^Ԝ׶,BNr&VÑrk3o(-aUdAdy?u_~޹4*?׿<'=Ϧh[FGbhѬ.H5bhz2z"C:0![u: -'gMAf(7CV]3)0lVYB9. zNj%>gI"D'{υ`å>ylFL,:5ı:VKG#F$yw`d%YlTǜWTI-(u݅ ?,d 44ڤuİo @D>[fiYgFfXx"a KrpZRsh`1VbWWD=qV#)Ɲ:ū87B`C^aXtW5:6m txYAǂ]vQs<E5zxcGt4"Dձq2V;eȔ#HUVSԑzГP3;!SK u egD1tt.Dtbۨau G+Xit(Q*Z]lK;,3{G=sm{z YU+ zb 2>7jIDvE<-=ǞVL VN]4ḥ~Q:YTBC_5¶Z 9uZ;`L-0\ʼ վĭ ynSJW㪡馝܂Mmtx^du*c=ڍSO1eO͞N2,]qu} C@\)GR ƏKӱ.b<ԁ&I ; =3rU֍PH2d"y3Bܘ#C>l3CHyJ:h[:OLi0KVWpEm J~|K) kM - w=re;׵%M:垡?_B̧18-'okpz'jt`m * V(Zye".L%65ݓ ^FU uV{FZ+o+Bx(8D uTɍ&9V`: לYL\?<<=*2&E=ߣ#8iCCPICC profilex}=H@_S";tP,q*Bi+`rФ!Iqq\ ~,V\uupg'E)IExwq]1@,#xE'3 Yx{zYJdO$eaOoZ:}+K 9A$~ qX,ulSE(_ȹpVjuO`A[pf ,"DȨaXҪb"MqOK&9Q n䄛/1vfݶmy+6Om-r lmM.w']2$GE) }no}>Yj88FK=r$ viTXtXML:com.adobe.xmp y3bKGD$Y pHYsodtIME28f>' IDATxyՙ?~{ 覡YEPYM4&881&I,L4&1$31* ʾ4;UQ/Mhy MQ9{no{DYYLKKömv؁BP( BP( FJ 2BP( BP( H)th:v"oC{ʐ BP( BP(LJBd s6-;;Ah C 1 !χ d. D%Rk BP( BP( Z08c e4Ұ]@pH_~;|_of@) BP( BP CwGիAc >py.S5x:J! ,Zgϰ h;NT^i-J泥I9ڠG=?,(|7'hҲgQ( BP( 1"=TwrBG=nL[mŖO@a(@;;ON:QBk.Op9~YAΞ%bhg90`7nE&004M̸T|KyFں>;rOpqõiz$~O~-|t|G4ҡ" BP( BqBZaSqs*OFȕI~ ﬋uר-O.')%-U\r[ME4&H]|N6^)B?uxɋl /!-(Ly$>׉bІi׿ ߅BP( BP(' wѼseT/n uOfFnJ4rst3t>\6CiG^a ue3NRYC6Ghri,MߛoBȂf--o:qUtgB :$_P6U( BP( Ұ-yۆ:7H!@Ӡ(_]]axqDjΐt:Y]@kVnxwݯi*tR %kbg, 8kNz@v ND.v`:{OGS#lXTs H&}UV&A1T@v"ov6Jn3 +s4/°bɪt7̬5(,^cHј0Lcls Ӓ5 &~~4tiY| M=6njpX q `;͒K& ~[U#\27T=6g̚aq:S::$xLFߕGd t ;$HvL~LhI6ؼϘ̬)XVnhgE`JNN 2-X孉+aBƘ!:igli,d9TaDƄ!)=nT:Ѡ KaZĥJa 2SmnN~TBP( BP(XT 5t$ͻr۠QP01ύ_$+M`Y]f=3M&{`[1ʂ~+khvHVC_L/չkոI l5Y?fe%j<Kd=>d߹ǜD$mG΅1kZ C.ɪuQyJutMpeaxXB'0t?:AeI^[]=\Sieh_>WB4|4Y0+SUFmK7Yf7<v vU5.\fqg+*N\m:%y }w/xRP( BP(u`zƑ 64J"[¾fDm8 ?.o8Gc$ wS+<ƃ-a_Ps9A>jfCM;Ml[Rv :mm {Ogu4 Npq^ghj^&5XaìS\̝.::ml:`1nk/MA׻ٳ$#])*]x]1 =C<|vyhI[c]ҡ K'ê\H`KnB7W5ЙvKOaN?/ObL.HaL bz7cFz2G7+Fxb^˲5t"媆yu-bl#bǮ8? *6Bo\Fi)>^^tuIvۉzgMie&JGcٶNld:RBOK߂>G$h0ycj> 1#@vlԻ&ufM7)IO@[mض#LZnMQvuJU&ER 0EQ.93ʄQn6\-ijY:YY^@Ǵ@MK܇4%`eoDG mSS^` nt&i| GyN9k2c@ŎN̢FZ#7|V1.su!x'M9n#۴>)_,20-dCރ66im4%Vb2:U{mq;6Ghhw|tI,!5Wz8gaewݚNGea{9̪( #9b Q8lIuRܐ!H j:`;atX1lvj86Nk_NaP7)~Nͱ)1tU]P( BP(a-B.;t D䎔mx5c#(FЁwBT? 9 A(,~euB,`dꟴ&zݎ[G&$!4ve8B[MJkfLӰ'βmQLJ]T٨}}z~#_مh$f;EDeF)c FG>ZXiEHL"NoJDYLB #j<ZM~Yrmm\3ä.u2 ˩eil~ST&Ɲv#yr&ݘrXbaK6Cjp& BP( Bp 6qںl܆`ʩ^5nplZpMANLMv6ۄ"7m]`XLؖqI.htRnOo71^W#ɓztH\7i|Lο?²$y:%NE<!؉;E$jv]X3VqRsut n9__hOڶ%8ůr!%N1耋J=\Ưs!n= l!'9Fy}?Lgb)+$-1fN{nC0Ybp/A@P~K:xHo ’dǧ7&=}pA.+^3/Or Lj0W['Sl㑗xPR8CrgjTU@@m`x7EiJ9dQs~>״g@8&$_+dsڏ;~HqQ~MK:ZR}DbιBPRP( BP(_LOiH>Tx()rc K:C6,7yй`$x=N#mE(LEA%عb0ͽ[Vn3 4qa䗍=2geiNja1n~tmH܉t5uAc&a&{vs3^e;֬ s}n[CM{ͰR!%-6 ͦږTZjq"Κ_oŸ~ ۖ t?Ia~ٕE߼**!^?O~#a%Ѩd)>>O#5!;̤?cKpEg ;#j&C2)t.icZNVaA4h.sjK 0nKPQb.wF3 s%A.dxke .-3|x=G]8hg`MYG$:ݒƵ4{2LcX 6 ߼4ٓ<ŸNBP( BP(GB ֛?ks +I j:J{RI@Cs s5'mAsk+"ĝIk;pSUb$W4-!bHDmODuH:,5xsE(#* Z-L 5FVHOhGMix:9: >P&!EL,F$b֜}ZĘ8kK#b&vKChȄwV9Kyd:iowm|Tu.ho{xtA x{+stHV#gy)feͫx-!%3}8ۛ$%[V)ZŸH{L#mu0'S(1Mش!~|K_ } Q_fH$5EíBP( BP(yɿHEYMd6)C O4Ev$( g0h>(|5'8V?QP( BP(_XX#MS@sV3R2 ދqD39 ]fi9dۇm?Hlv|D H)%gNQX`PV`p"sǒvNԧ{ݓ5O5OT( BP( 7A~VmCv*ө|H?dgRFO,IP( BP( g#JI$6Cʭq.Da˿V wwqSAm'ZmRP({jUaH&m}v&ȣLj:"5ka ]]?zWIR.W( BB"/nضe9/p[PP(}˒3VH4EƻH0/osԫ{oo:fbT:3^GNdV×yWw{}vBP(/oo'öLxx<~P2 JT* S|#~ OgфGFpP$@8%|~M sX,F,qiD}@4%>az?36Wǃ::"GӴ/²,p yvՋBP3bD;"sf zN/;:_߹".uoA{?a{50~*;!J&}-Aq m ?JT&Q(> a,kp!msmiӦ1w\yX`143f 7i+yٳgsW$#_PL_쓲[}}=W\qEEE,_L:/׿m۶WO(4vۍ`ԨQ\q/2o޼#Dp~,uO>i+())ᥗ^b޼yI1SP(<Eð"t4'fKɕ>+n.(qٿ)j0`<6Ir/?D@#Чp,;^+ũOz@vAQmvWIW%Z>h ge}ӧ''Bhhhd˖-'mNEEYYYdff"xm )++#-- ]ٲeK )%TVViB}a*.dT66mOAJKK"77 =P(}[)KӞ \ 4C,iKÎv"T0p}IC8 -_ x@0؝t0 pW f0h#Dg 0$|m B!bF2E-//|կ~Ŗ-[`=Q10 âETVV2lذ_4͛7`n7dff~S񐚚J0LFQ\\Lyy9w}7>FQ:::F]i҈bXQFQUUE0P"㱨ᦛn/Kܛ B0v[Dtl["-}=oM N*Gzba= LꙠ))`XId 3 IDATZ@#D‰+Y^k` [π^r7j\KPJ`'X[% |pk-+?6-30ϟO/}!/xx۵! [% Z@*ІL8µNE$>^w[v?H_] G}g5k( [߱d͑}OW2Km=>6h㵛B8߿ŋ#1cP^^ιڵk ;M7R_Rԋ㘦I=o?J|݁B{w l現uikAֆivZ}G) IUNN~;Gָuks9gPѶ :6=#_ ODA+؋ ث%v@d ȗ˙2NĤ<&C1:^Ow_#%(ـf!'^2*L[Z$6D@97%` BD>9m HeQw$[~O"W& bʽ.c2AzC"78B7BGN m}@+" $t$6D v{U"7v/zIBR |TBȉȽ0$*F]`=&[J& 8G9G Nwt& ,0N8V1 rv-Jǿo`|U@VB4{3/RX/K{"j'Ց(& Ds>{sD_Qca7tm6AMtFNvq7HgQOP(įkJzA~P\\ @cc#w@isM7Q]]oA^^ꫯ/&#FɬY$ qA~Zt]ݻwsϱc @rU荖ӈl{%RRR]wEee%I9sPQQ,_y )**nKj>,iiiL8x<΢EXhѠJ3p;3~Kss37nL E]w&MbϞ=l۶)S`VO.m3~x&O̐!CD"̟??ٶH$¥^N=477Ǚ5kf¶m͛Dž^HZZ[l3`cc#ouo}[\yI`SO=_UnT:::X|9O=eqrYgϜ9s8Bg~a݋I&1m4B}v,Xヒ4MάY2de|r*++)..ga޼yz뭌9)%<䓜s9{nq||ߧ!MMM޽|x<nxzFYb .K?yn< k׮.,Y?L$ _|6owܡD(B8Y<;?!(=7yݗCJ==x҇K/C"q 4 =/MnG!K:%H *]`N6#ٹ`sm&34D:^*qr K@d%@w#V; Z@`@䁖 C _bopsi1Et!>G/v^}hBCtwY,0}6ODQ Z`HG` e Do jw$b,.՜섀*Y8 `Zp"iB8GtUX>5!U8#4V '$!DC27!Y RHmc[iθ@"w$ķ2^#w~u6e;cP\ND3(mU`ޛ)Z#m# ?hyk+QO-GKo Q ^~JQlQ" !999Iiii&p"w~Ũ{ax^*++Kzz:mSXXmۼ >뮻zn7eԄaEBQ[[iXE}}='N#o'x,RRRp\޽;ۏn6 A49r$YYY<F9x cǎ%-- )%ٳgPkȑ=B֯_nMM IFaTUU- K("61Ҥw]Bqr)t]Dz1wq&#}n7C I  7@aaaR/XCr2vdD\nn.aPPP[o@EEuuur3f naÆGjj*I>;v,s6lvSYYI]]>t233mCxx٭կ~E{{;yyy5 4yD"?SO=@ ݻVP(>shv0WZ6i[mtH{ 7o(yf/?)I8X}A:۸ 6NU M SQ[dBb)y w9ŽoB 2~&X/)"s" 5GLE&X%2h0 Ъd _(eq@{'/ 3K a v6Qt>{^mܷ8 $tIoj|r"_``cq@/؎0!;\*ap"p}Xn+$ңM 6t)`,ޗh Z̀Ei}IzQ"C lp8mT#v"D{n٢!7jĸ*ЦJU@#,1.^ %6q#jN0}S|IbM#2숸>V)0.y$O~g$*K%@)',~a6UUUɉ4h4ڵkٸq#l˩fΜ9?ONVaSVVưaXlcƌ֭[ٿ?֮7Ԅ"##2X`?ETEYYeQUUEyy9e|\tE`Y֭#FPPPԩSyWؿ?='OѣGw^lBmm-r)lt]3~xf̘Ann.]]]\2Nl2222IhZSSS2E1##: u38byw)Wh4=tꩧ2qDrssimmeݺuSQQ)SXl;vH0eҥ >lFň#ؾ} aPWWGJJ #G$==6"Hly:t(555 2͛7seQ__OJJ c֭1,fΜɂ غu+oL4p8|@gg'BGKK iii̜9r0..+)N43j(FaݻRs9~YjŔ0tPN;4֯_ڵkټy3n;y:.իWOٸq#|dggs;CZZÆ KgaǎTWWSSSCYY+Wdԩ|>"-Jk BPVN -t~q=ŧ(XR/ #D=Nj D: zz8{7mGrJ`&a7OIDZw&"DpJ$w( _ݦuz G81A+0'LnI!\*)X|QM"@T&"ڥqr#b9Pݪ0 Ɉ3@SL`KtE{t$tR"uJN#Š ",v&|k9!'Mddƃ >D~$xq"NW>(A$6@%Z@;"O8){cxvƴ^`^.Dpl(. َPv#~X% GҁDHn!#Cp"=@+JDT2Q"!5Jΐ0-a7UTI~miu?Ӵ0cڛVXؑ02Iq|8B(wy'irSUU.RJfϞ)%+Wp%0{lz>ttt^zfʔ)x<illW4M޽{ٴi7oFurssB?wM_O~~>cǎ婧s̟?￟̙30())IDX]]w]^oR\~=h !os3fk_a&|>8w}7/nફ2yd^,YgɓF,Y'|!D򓗗GYYfnv2337nSXXHJJ `\y\}dd8/5. x84M6.kNqlڴJ|>vJާ%%%ݧ;vg۶m\wu\q[eQ+{83!77Ak ;PW ΀} 0-@9GІ.U/qm $#jڲ-!Ϡ=v%| @nu|x}' :>)]n&"ܤ8}vDFJ"ǩe;NMw~v,RvjN-xt%ae;~ӮpNXV8E#w8hεE(:EGҴLP8vŻcDڎ& m3ĎFpIwkk+[n%55 b 曙>}z}V$ӃaضLr\ɂȭtwwc~p+sR\\$zh5 pjV544oCΊB8 Ão>"[nIn?묳?WWW#4MYd MMM~H+˲i]+!.\HFFEEEL:!444㏳|r HFH)ٻw/p8) -)F}T1"lۦ;v3ϰsΤՅ˲G.+iT,0duM76P0^_nZZZB`F^'. /LǓim?W. /}LFͤIsk֬X3BPE*H?(;XD떧i^DӼhۊ I\{@҄ؔɮD*^葈 ADdȘ@ pEhrzGbv&pg`r餫e6\&eÎ,B.Z"T~`Vbmd#"0NN0_>kX3-O`Mh*!p (&jQ?LnKحj>vD<}W;Iȭ`09}&;qQK; qtU .d ?N4U).>hPƝM4R!ıu$N\zHDMA'FnF&Lcrss={6-bԨQL2@ 5ku&O|HǓedd$D{<~b&OLaa! XxR#w ֮]ڵk׺uضm[^s֍>|8/]בR&WNE=izQI^'/ z/ ky<<eQYYiFjjj?{L4Amxeee%`+kR]]Mnn.ӧO'Œ%KBP(>f*=$ݎXR_V59Di!f.2"-0[8)^8>UC u D>X%r39(Tg%7{ 9s#53 "13)AhI_ili4S~ռ?خ7e X϶(R)1  rN 9sz}C7 IaU]@>}>gﵿַJ ct}NV"6RSN4!e |u]o)V͡ ]"%R0y= 컒@zk`+AU}3TX{ՈҢp-a]%!*f^ \H%SeJg[BAǰ[#`TAfY[@[ NE/7M-${uԠn![ۊVW>iLn| #ϲF8jE]A[ljP7۞ 0]/Vݐ.0)ׄh]Hp!߬纏J*eDD[t&Os=Ǟ={>}:k֬aԞ1κuhnnǏ:CCC?~fϞ>9^,X)S SR___Ni`ܹL>L=<3N{{;{VZo}{ a֭娜oo~sQ/qСVT=NDشiK,a̙{۷,( ٳ~^r=yF%Q^~ez)jjj={6vO>$< r z+ 455Q[[Kw}_cǎf~w~+W[rQWWWN1lkkcƌ|+`̞=<ٴi#FCkk+Y+CvǴi}WL/Uë<>̼yG>ªUb?ζm8t˗/窫P{8'8v===̜9c̛7oXYk+'>s=Gkk+{O^ZΚ5k雍qTMUeɒ%|\mtuu K <#T)SyfZ[[/jGDDDD@*M78z9 3ײxߖo IDAT~f2Y)v#?~oWX )W_RQdCԁR5"(g[(+~)HO)z'=GrU,RCCEL(|k@)2[HEȾ&F_ɐ R#tjdR #j6R1=!YnPX=?fzR r?BDž!Ef2I SnC5=!ߨE[wA%1܇?sY dxkRںLK6` 1>QR]gQ#-P3t<9kx7(7Cv_utBQL72UmP6zzĮU}gI(J:r6mbԩL:n 'NC=… Y`]w]ݵk7n-[yf.]ŋx%wʕ|d޼ye{aݺuJP'8Vb͚5Z>^|E2|e3T+7nYfBuαw^m 7YPU6mDWWWT#""".6C]NNގ^Dh`OSV|B|Dt@ WD3ر _`C]G>Yϥ' 'yoK)JTǍ)(v{k"lJ]md6BH0O)I=S'u+ V,?ҥ~-*j ʮ tc|i+i%]_QXȾ-~5`D8كN1QA#9FWbxO*Ij[]^u[ mWCro|Ч́hn>k>8rt*~J}UE#&߫Uu?JnW{,ZZ d{z|ݶ]_tvPwwwOo"~4Z};jCX0!=rBZeyCѽVS{卨0ݺǰ[DDp^}U?7M:N—edyde1R Z}}=/Gٳ=fhh^{fҥϲrJϟɓ'&*nvbͤiJ]]eHDx衇n`dY9uTWRݻw31P() ԳEb ZZZp144đ#G8|pY<9xsuttsNikk#I|Iڲ)Shmm^+ D)l۶vqqAnÇe``;vܶ/裏R(_2\L>nOSN>Mϱj*&MDGG=@[[ȱcQF"Bkk+GEDذaYq=}yfjkkٽ{7_9v˖-SNq- "twwή]ʄe >khh_*{Xt)K>}׾5q,C}H4#GR5LĮdi0}5'ciߛ9\oJyCƩ ]EM4DAQ{i 2k$)5ѷgFFk A+rKnjbÈndl@G?CpwV,u0F!Ee Ր|Й xX>~xDpvk౅swv8+Jfb>I RyXB9snc)/o;% M㈉O[Ʋ&6U'rp.ֶhvKtN~կ%f*pfSW]usB ?0I #۲f>O32/|ӧOG_3'{ֈ5TO;a>/H! jx.hQL7U{㙺]1?"ުw\ED{_)mذ{r^:;;7t"СCtvvm6~ɧ_I2"".t _(+VAz}C#F Tt"""sqLHC$Y1hzL!ոD=Esl\ Jqs+"7GYW]htvv't$~i_{)}6""""ׇ3>Tw\򈈈ߜ ZS"".(|YpM  YsEfRzU/ PD..5'qm&A}uXDEWBڼ~z3Ծ8Lf6/N;ɺwDt_R'B(^7IkxC@VgA[! GD ɻZ0zB7)OlO?p m=Q @ [~?>.< X EH#qr6/Ard͹z " Jpݫϟuި[VatϟvA,K0ԡnfƪOGhݴ\Qz$of,E,,rRԈ_Q\X"I].$7 AE L-Af ̑e}Pr$ x^QPZ&7 zRW<ʝoUh@Q_ٳ>13"MyC~@m~jLHnP5@f,1ஞ!߭h7Pkurme:-ؼC=6)vPNGG_?yBra w4)$+^_38_DDDDc{.a_}'>=Jj+G 5yͣ7xRjԂR3.1xV%י _)_cՏp2d0x +[e,ZX׺Pm(P֪ߋUo5/dۚ!RŎgQZjQ%ؘs1-ݣqo$n1fU^_DVȾ08٤Dl}7:o(>9[,/hy#:ғ U/*n SR>;TWy"_z aS?8Y9w ?A;du}:> ~`{ U6<\~qU3F&V_Go4\~_)QWUkʈUCǠI@KHZ4{H\==GC{/bc|Ug`>\ZGR3?y$&ڶu]zRư͐U!ȟT]$ B\Mt$D"AYdIc@)W98xe'MCCiŁŮԈΑgӡm#vTۦ#$w 5Ef(ZK} 1O#$w \[F)ۻ=n }[%f"J_r.#ڶ=̈PۅiV%S2J>(H-@.,kK3?-haG[ߢ}';$/YUm~1n X ~/Rv>gJR;ɋ!kh^.\ʹKwψWQ5G6>-1X ۍzLע@R{Gá7R˷$/ pw aL4FO 28Qmt9+Mhd a" v,jG@ bݼz#},,,V oITcv{gU4$MV Rj[鵕I-$A;~6(~Zߎ܁=c 7nMXh$81dP3] o[ݐ`i[+hш YusM>py["2e>pvQn7[aϔ09FH]㊞ɰIx.b,∈r¢u1 K\sqȝ6b3q i|kłͅFEY㉶y/\gG/Հ-bs]˄Z p TOBqڶ8moU:C{@(<`NHZst7"h:k/}/*OxJ^ZED%+L<ۣ3ڣdk~K(|mT~}%"dy2ݏL6G7'oQqU v\r GOB)!Kp+1M,dU8v~5G s 2XW?n][NDUPwFdb>Er+dE͖w owi"0Bf4x#yH-H)\!hdUM/0O3gjb8,CLd,U-"wf qBX}[ϕ sLz!Ⱦ 0ai߳o=z {w%PIg+H䓁4*֨} !Z;GpG邝*no7WIZB1Gʿ{QԃߨQpwœ;|#\$ydO83Qn~ 1|'A$mKOO 斚6޴=N[$0!U!]?z>lkew ,Ut{F#"""".ѺU͈)M&Fq dvNA⺬pcίՐjv;I*|a#R者i=HA 靐 " Ӭ}ɍ&XIbUm UmWcKm˃Tmm%χ}q? VR'X4Sqk9AaCa.SlaVn۴QC۴mg0BmEp->gkS-.1~G>płL U E6J۪!CEyWӣCUNU5 IDAT k|, a-nYي=wFnJ5U?no9bSD]c%CE[= xN3.Pz/0OH/[ {ʈaMl"-7vR΢W#(lA{Y MvlN%@@Еg~ e(͜M !ʛ幡 I>KŪ1:_{BQ7;?q- c diXzE<CM)KE;૤#6Nn0`Y[zl5; x&#K#U ~:m';FkۏnZ2Ջd7жp4{m1$!d,/JɈ>#)3l)"m^l"$UZKL.t-WLG,*o68In{]qXnVeeNÆ%Y!fCn P& UĩXDEG?sͥ[ (Dx=V-{)6BԢv>/KR-߭dSI+Vc7r*n3$TػR |{燱6\4vab7BO[znJ,ҸuLEt3d;,,4_XTswUKXт_^jBoftmA/7 !ۡai2I`rؔꂾU K7|B --osJjH1rڞq+FDDDDDI9uF8ތy3M%Է\8҆Yd|3/84\vB vrf)ϣ_/ g+,}X_b`|snd hԛ^ihrƑgϚ3j? $Bw)-Bz#H#gߌ2JۦBqD^VtEI%[:жVu〧A41UȢtB\HכK(*mڦ-tTȟP̰iUK8Jj:8h*E@YI.w͑s>tXV!]T[Y9+ANY@lQ_-O"&Yeβ?ɐ*Q_{3B])Wt hW#vC*tn5(B?ǡ*"=m%$׉دItbq[һ/Q zfJHM-]MȆ Z.HJ3-K~fB ޟaxta0ේP,Oܞ\{,v/6蠢!< K]#v<4b'd7!t 1oA=4_!d{e~!,7r8 )kLJ( c QM>!UX\IJwۡsj@i>He`5%)48$)0jf!;ű w2}A\c»NZ_fU~LQ9+b['"Bν{4H*!؉UJVy#j?z9ae˻idykd\A9Z$CJCWF%3F)iEPնi6mʐ)ig-ŭ4$/B[ѣXEɢP;NY1J۶TMn[ns쓕d?0Oнj;E#6i~?ʟ;өp!O YIg(PYOGmyAh5gze~=];z$GDD\irS)W w kZDVZ^z{ռpE-â` $hPAPIo5$ &%.Pг2wu0~Y{kD{H %q~`,]ȟUR&sVJ"݂}P|6a,-/.)[-wb]w);@~ղBQI"(Ni2QPc-k(6PK>#ዽx qsN:fG N2/g{/!"<M}Y)vW= ~7@AG' aΟTkB=~r aJ1 B0WkCS_)P;\iXZ0gşy)OC'\}p|r2OmkesFC ;BR_WM3TCUs6:RϴڗScB}^?5IB m51UV.0gvrt^YSDDDDx{?yաN:ZeKU""~T0{ !UUc^I(""m"ٌGjLK_;svԝ!?9{[voM稂W*;g뺺ٳ9EȻ&$Bqs:s:vH>($R"zmcL7EDDDDDD\rCmdƬ9yIDDįBq]nz[:Gύx+co[fEJ8Mq)"j;J# 7 ~wE*""bL( ΍_9D3%H؊pە|3:gDDDDD%T$(=j"" 86P[W=.s8y@@ѝgWowHfe""""""ަ('tl rx3:(~&*΁@bDzP@j@rV](4,--r>d08d/J*q?z$4@VbyJ=J= &ϻB"pK+'F68"v@Q/;ZR3r<@ոB'}?*o8n&-fmYQZsj}qw$.4Br?SQ0䉂 һ?Hh>`HEHdt>v P!{N`.nj<8(<\>Kw ՂS}jpYkeLJ񑀊5"2k t^$}ucuD䅤u-H"tйUTҰ{j,+@1u]O_/еox#^ }V}#B;-B^I'>dSZ.bk!{L`gp"dO+)t\.:Ki `3yހP)[~6 @dݝ "zONZ@4fF[Q)XK 5ޒpS [MEHoވDq6!N#["-  @~_"gjdX\haEO߯+jIwqGJoR q6 IHRK߉3m4яu5$5SO2ԹgT^Fv :wQ3i) mackВD0 dpK k2RG!! 5`i2Y̰czY 4_;BMdi8Yڷ7_8E')z4@{ `N^7fH @MJr 9=,d/N5piA*cx<[RӶW+T h6-y !1Qg?h:;*9HJB#$W :P HNYOr`"Yܭ˅@tՃ[[2u*2[w*J.9;.n(K#?PR;~ 3.!ӑu+V&ȞP3&.kܓfȷ(z"z˅]PܪãƁ{2FXJ?D/*2;|)z])w8;=MA{4{.2Ղ[-#JrT!j `Zxޖ ~[Tr۞='}?j}疀[*&2O/d{QpC鸥+R:+YZDUDDDDDDDDDDDDěO@%iJɃ ZNMB)ȇz38`'73iH M\JjF?-ߥO:K+Ï˷)=Hrڛ[ n]=,55*_n\F'%kg y < 0&9~@oT*􇮫1B&AaIK*"恻XzW={8*v)cOf?njUE?"!' 24CjchL$kwepXʡbiPt  a˾ig07ڽ|U Rjh*xz ߮wh7(YԞ4b ϛݧh Zx&N^QaWDYbgDOs GDDDDDDDDDDDs8&Oθ3u#ۺ#c2##pAe @L:E2-mW#k$DԀ^uB\ȗ)2Sbښ&T4i6v>F݈o;iu +MF 3:j8P]DOzh?۶(I{8nZq[1+Ǵd9(Y2"b!+q=oe9zlJp70 2R>S4eqx!oJ͢t:q5ޢMC=H 8.bW6`Cǣ/2bd#,dC!mȇj уEu-$˄|}[XPT: s;n Vkk9zyWԎB8VMadebm Bh›VM7̓v@∑,zW-At#wbF&FZ+P1po}vz!{n4W!ihVtJ*k(?w_k}9 $[BjVR/t:ig3ӱm73Lvj/UJ"TEQ.rkr!ד{X윓H~=O9s^|>凌WO)?,C^ Zq|6@bw-LٻC"TyI_h̝7 IDATJ9?Yo] +c~\|K$@:‚"}iw)q4_#XGd㖮Š>;.[)'0tHϡ KPL1^/0 EJ" !Ȕd׀a>Y;qwƵN2vJH6P; Jc퐪tUV$5ڭ e}76>KYcF"{pJ|L ~NЪԮ(F%i6jЧ2ӞJ/NG$%*b UJE_(QY\QL~ { ytfu5n-Q旖U /+q,!;tjQ,0>*$HSh6 mD~Ccǥ"cX8-$\:1H`K@D '[1rV J(Km&$RZ9+-Ķ nsFX,+kV $KCY9[ܻC)UήhV$D&y.@H2%W VH3lV.9Efgh (LU1T&X\ؤUQ'릃23 Z@Zj7=k#j,m$& d$51 }>XT~PJv9P+S✴>/K{3#$tU(M5HGbT^Q֧+(F:kEb:1(^wH TBvb,ӊ8>NZ+IwY2qkz @ jǬq@ZeL.F*qOX偞5Θq5k$Ga:jQ /xGEE,((,-Bim^K iv80 iv#^i'f'W v i"<:3EzLSJ@J 5=s~"Oefi̲4󺳅=ަsd{0VF~@ ?%0f8B*]=`rvA_"0 #ݏ% tvNIxFeK3. c֟c2syeof ?#Ɍ=]>,m:|;׸eΑ|L* ӽ -ޗǟ\ᨹ_o=ز p=<<<<<<<<<:N  Iٞ:ib'Nl3 ƒ=c-϶?PI;[ $3]dT 6yʪѪT<% BI=hS|slcu23~C=6>(4W"|mc1؃svbxCg%)ORen ŧ4B)2;{qφ+@b kTFm|PY*׿)=]5keKh{xxxxxxxxxxxxx0¹%"ӡ=!RbІ:L@6 \VƧؖDzUL @9pvw;>V9^LMO#cDjH{ٶMooa&x0Xa4eFJIOOԥλ9999Yvo1be5Cmp|8B AU\ K+Lz$0ry_l7zmjuI@7rkjd¼w,WV/~ElEWnaQ c[b(#v) M6C ;1&>}o.~ =\58?яH&U223f//{m#IJ1c ,`[)뛦aFWJee%9qİ^{yT^^Ηe4MCJm|B00.nd2ɺuhmmu(((@J5kO+V`<^Xt)lذ}yi h=/9cnhFFG:,^:voG(Q4.:f/plm|JM'5KW$h2Ew>hX!Brp/ gz#1#U ð? H !OܹDoO ~P{zFmOJTBG}@(5((71hB?_[q1vX/Q^^%466a֮]K2|[0M+Do!YWW}G4'? s===|K_kaڵ۷I&2x,cҥ̛7cfP]]MMM  PWWGnn_E-Zi /O<<<<<޻TokbpAZQKʮ4/kWPs=G*M`~R _ P4zx,z +~+ᏈىCJD#DboaCf!}%^@cBG26H(c@9L`|@ MD "_w;?hcv3+md/g燔4al`o$Y$2"7 ͥD"+dܸq|>z,F01cpW\RR[[KII T}l8twwJT*Yn/ΚAܔ^Kqq1MMMtuuD]ǞOb[P[L3(AdO$58@ ' Z:ĩ BAkzTB!wVYP&6*3D vsC_I* n\8WƜ6 TQ9^8}%Iv~<~yRO996-8HKkOkN3&i*]jh;=!$A%]QJR2R"3Ιb5Hi^cov2όYTΏ %=~fCuMLd2χitttY^@Cg(6T V*H$HR|>W|]RJxVii&+n6fϞͯk,Ds;fΡgҹ|^G FZ{j>3k,jkky׆ն^lfժU=zT*šC\1HACCRʬ8\TcY9=&zw?lF.YE i mR$4!%BJx.#h0"z|9עA:=AhmHƢ jћ`'`=tzDQ]nhJH'Ԣ:N*WykyjMhK$0#_+}U =MG<(&lO^ FLCOAv\?1@[ͬm@+kDv\`q{@9@ mhQ"2n6H^5uE?WA)a6K0k`lqQ#ǔ&*XcLUgO 8Y9}ZщC#Xsbv,b!A^6D@;2y(} jl v1*2 [.1~31-lJԂ6_ł*wIVTV]btvvRRR.\(˗/b8#gΜAiTWWxbgdtuuQ^^N8fƌs=8pW_}"@5]dg!ؿ?=~~zuu5<RJ,\KRXXc|wϿf֯_ۭJ}}=%%%]aPP}sTVVJ8s wfʕYt)SLaǎL0#F΋/ BضMQQЇ:u*7ox"K/Ċ+-TL0KH$Xj'N+Vi/|>O1a`Ν\ҍ酾8rwXt)TUVqqm_(0a[x1xayk\\s5D"80 {-0| RJ:;;4W^ydǏϝwɉ'L2M8x /9A=z4---lܸyFwP>*b)k TEЌgUO90'89#DOLc1(AVy)"e ϓ3:FX.z^i3gL)K@hjR„6QL h%`kXpD(7`,rt%c|D"wMӹ`-^xIǁS"^T> GxqYcNvN)zYV!XD@ k$ Q!' HHlS:Z9BpLE lKB |A;`9LDŒ)2}L0L%اY%Z@ rP9E`)Fcd)T,''v 3ͲA 'չEy%<{v3|mW >Ĵ/Z:aL`SB%)5` ?د(ʈϩ| QC9I`{x\f34LĶm8w}7~;#G@ @}}='?j>jkk4. |*Lۋ' RYYIQQcǎ24ѣ݅e4BLB8$''Gee%eee|> H$n*++ Nqq9"P}$ .nvF5n'?}|7cbPk)S//D"~?̝;I&KTTTJغu+%%%|SrŕN),,2ĐLJ-0}tb6lĉ3}tt]gʕFȇ>!0L83f0rH~^Q2*hiiqV__O<gÆ ?~|Ma\=Jw{<|>7|3+VnʊeYs3H$ؽ{7mTQQ3gΤRL:- ̞=}sL&}PzxxxxyZD*JOg3}u3gr?r*PȈ)_A4Mo6cg8ڿQXfO2rgU!z_h:rFB>r!J&ER}@%AvJ-rimzzIB. )k}bz]/nQ\ޒء ΍izܻ5L2fyRї +c='Im@/~>M zK"~c ҥpThJ3KY>R5ڇ$\1K`oX[2Iu R$rA_Us RHD|]b=%1^(M#$3R96% <H9M|mO2;[NL5`JBpڼ\_/Ы`qF'(2u;E8#2bS d(w>2'oWCR|*J'ЧXއ!P|>'O_Wv"??9sOs nf&LÇyGbŗlNZZZ0 G~~>mmmbbz͛7SWWǽܹs݅|WW?O\},[l8yf?[ؼy3=.8vӦMn7ܹƜ9sXd =+rVZE<03gΤ[2c *b?0 L2|#~ch̛7)S/ʕ+JGǒ$K,#L'ORQQ'> .\o[zzz5"z+7t``0ڵkٴiY6cpGy<ƌÝwyZTTč7HNNW_+VdXn;w$ rwRYY9(lٲ.?Lyy9[n'DJw܁("ǰ3ZeIhw"H">̨YC=iQ:!|aZ>f)i[X.=?}C}M $GpcvF.a IhC dT;`E,*I 8OX]NQ V%LkT& 6؇%j {+(ǫ2/X%֋ZPQmUY;rDNUҧ L(0@vV'~Ў=$^3hY |o/S9 F] vI}JU5rw5$jn.5V '"mv ;O{c!U<ĴNLSJ:P*6)L1tʱ`VYcx"xPcQ7$9Eeba6 D#` yD"uG;1چdEpH"??;˗c#Fɓꫴ3{lJKK9}41alۦ x̋kK/aY,\۲,p"JKK9y$H*Y5OD͛7O|"k'6۶ٷoh)S noo3f4vXFISSBq7x#ksa&~1cƸm>s EEE3&(oϢ:3gtKB܈#xX~=ݻ\"d :uH$W_MGGmmm-d &Ν; #)%͌;z֯_?#|"Zaa!iCss3G7ouʬ|Nw\7'H_,& RjdNW%XǫL ]($+ g$j}_h#RӴ wDs6 T~J.dzHh6Z`|@]TvuPbm Dd#fVs'ݙ&}%rqGNE%3EWR7,"L&9r$Xuֱzj"`GYYK.e:::\`0iM8fȑhCIRdWq- h4:d2mƒ%K(//a۶yO4̙3Akk2$OR<<,Zq~nlC Yϵņ[GG==wy'wucƌ8X?5M# J8pH.4^wZ|饗r3gx%4ȷW+c#Pm%HtaDHtŒAsRɡʹȭ\Dt:7i5F< ot\+*H)siQ)0s2LQ:t2wpZ:l>c Ҟ. #OS"8XZ Ȅ@}Lvh 5ݨ2(GŶ!""g;Dm>柜 dec9m*Ubu^~-'HD*QFr5$t:G~c*Sd:H+ЮPI5VxЮgu8.-0OIZ TqcNZ%Cc (pge %%`8I?isaAzxKGeժUw}XZƍGKK d˲hjjb͚5Y _)%;w m$I )))^`ԨQyJ]!.S42Mf֬Y $d˗S^^]oٌ?o/T*`3XmL0ӧ3vAŕ]%bF98mg^|u! ø1I#pBfΜ9+-E" OӴA34MsǫjN>3xa-@) zYC,D3Ҏa%ESc܌?w,TS?5}y]~zOV=H0 vPC$F8#t.OJJd%;Q2A}X`oj}@**Qg ԆځZp*J+J+)pLiɚ$V(R%tg\nvV;ix}96F  +t2sJsSvD/P"3I% O9L Ԫ׋mLGR'U7$> j}f#U /?yz8"(svUlW$-ѮQ",.ۜk-E{O i)M2-f yqgv9_2L!_g޼yL<ٳgfhjj*V\I<wwK=z:-[Ç> i ill̤^8!8pgvKZZZ%䐓CQQ['`ĈضMWWkJE<qu9z(MMMTUUpT*?N{{;&Mn@a ZZZ:u*3g̛7ⷕ:6mi(IJ,:N>aٙe^\\LYYӦM7Fv믧={vZ7^DaB7O4:F銰Bl&??mn @urss4ݍOA0X,FWW۾z{{vlۦX,ѣG))){ge̘1\yYu||>p؝>ѣGcYրsqaM'?I?@ ŋKw%(>ϣrze`Z9{t%mc/Q0&rGZ&Fی"Ǟ$Tq9o[#!S9'zONe <OHd#nVѮP"_" Jݣl*2_7>9׽cMV~Hj9*#.B]_+l'}2)JV{%@`,q^REVʧ|ZڠU s<Y`F+3juj|9vRg45W3WKD2aum g%s*H5$1R>Z>&}QGggCV@I?8n%(1}^b;hv٫y\"W D|V~KR'F26{=`|DM*KɶA%8Y J+E&eNe%6@`,s*L}Y 4SV% k!G`>#I%t HzAb=){x'ؽ{7˗/gѢEر7|[o}C,\|>'N4My:u*N4uRR[[ƍ߳m۶a7H$\ϧzzz\AĉX(z{{g?ܹs]  1ydoիyg0 )SN0d˖-<޽ 6|rnF,X@GGa=}Q__7M(ٳJNxG[BYW™3gHRo˫0 ,X̙3l<=ʾ};wRWWǒ%K9s&~;v__޽}`~]crYz:n-[FOOagi;vm,]M(((  sN9s o ,m)--eɒ%:%%%ض__L&پ};7D"!8|0'NxOή]x;{馛ܹɓ'}񱰰P(˹[8~8;q^:-Z̙3IR^ ;(@IPs0TdL!D6NTAۮ#$yU7aGmOWh D-{JvۺdqR=6H/mF2(MyyEB}hY"EDr6 (Ru썲o73*Jw3ڣ{#$hiIlOt`V+yGmE1J>aPLLH dPT"[d~#!&Be8uYo d*>e$}D^?ԙq$}%B >*}^_?d) L3?ȝQ;vU΢1w]Cx'dGV K`nR?R^xFL:14U3QנDV%H[ qn<QR*aM6>&&;~Dbŝ8X{QF<ReKMQQy. Tve4Ohu8o41&ˏ \%WJ<$ ;F pKՒ$:UUUA9p_=Ǐw=i…ܹ,^*ٰa---~II 999{A}8qoBiiiÌ1˲8vF9z(nֈ#G9۶inIZI]?Yl555aFضa<OsW`6۷o^p %i9rV4MszAW@hll?9MMML<)%oa0eʔaC4ihhp>)%l߾ 6s ܾ3jf̘ANN`Yw#LRWWGOO[l*@o}[\{7V"u)..,ٷoO?4GAJ~;&LжD"Wm۶9uTm\v-yyy,Z"LӤ4I$^43gHCw^ɤS˲0M /% 4MGՕ%744 0ظq#PkjjbL2m ${TN*Hĺyk9Rܥ3<pZP-HكLї)!A/ݓ@;PY:h\w)ݿ)DvReCez)e$;ﭷO"swϙٮ${A?7}bLТ;O̕JյĤtF9m>g7ڡX 'g5Ԙd}\Z2JxF?)G|/Cm|7 3ز a1t}~[*Jrf)OYoWLMnnRxb>Oʷm=*}H%tY*itvKNv> 5gN 5s_uWIK4+X% s[:kcrAR"TN`cu Pږ'*fd A8>o<{!X2Yqɒ%X6lpAf1jB~#H+.ˆ U'h-ܷi4o."b 8gyY?J:e_1rnq1C+C11J_=<ޣT jp|LJzL缼< N:3> ']}rrr8rXKWT}/u#捈0 q>"Q|GJ N>X,FKK Wf׮]m?` a[)o4<<}w*Iww7{a߾}Yed,~?A(m .#L5]Mz>er-P;3pGPۖu]c =<<6gԨQ|>Z[[9uTk&MN2Gѣec{/W]u7n׿^ TVVKss3nFԸqmcǎ2b-1J8y1h{3fyEL1S>IrF0gi|S~@Ʃ]ֵBGAѴة6/iRV# OKm`oV$(wJATP^R c "Z偾X& %OIw'@Q\< @Z`δnU,ÒٶW%tK\14ɘ1cg*++y뭷B_5j۷otM| BpWp-0sL4nv?ΛofXō7رc1 _WČoj֭[ݻѴw>=k3 lڴo|,]~D"?կ~5lsҤI̛7˲xW.[ʲ,n*++<×:===|gɬ^oD"xH$|;a߾}5[oyQRRv3<޽{]qcĉttt_eH#o#qdAфz[: i%9qK0㝜xv#ފ@-!ڴ;uhD)qF``d~<9xƔ(m@~yO_HЯ$.Ib zK8P $V.S l}\bMuiضMqq1nvmۗloSNuVO{̘1flB*So>vܙ_x<3̙W_ͱcرcǻƋK4LӤH$Byy9PH}'2';ƛoI*ӗBV!LTXc;DŽPU Gӗq/c8__lHYd ")*+oJ&TZ #tڝ٦^5nHLfp}@\/*3ɎJzɂJ833d_[1αܱ6 21HJrRKO8H++).9Os)2]V:;;ucRQQA<'L8.>;h8vm;5"+zb1Rmc6X, '}D"uM00MG rW擓+'{> fg5L峟,:tlڤj͛GMM e5+߿\n:if+'''p8ۋmn)奠"OΘ1chii0t\ׯ_϶m۰,kPAlsS@ u,ƙqI?9/u]GJU >gko wOyy9>_|o~̝;'cw8sE4oΔ)SFݛ(*A*sN(GcbM؅ (f@8e|S -G SG"$={IJVo)hF͟KבgȭݗC랇iyH}. 2V=1$Vk71G@PqQ"xAiH_+I%5HJR Ѯ d|DHhB2(|UbYVƿj-6JD}pe\#;2#,8b#iW %A,uXoJI b g d|T  ;T`A+*2&A"sޮ2+@[ '2G$$aĨ۬X)U] >G%2NVF mLg@ %$(ZVxk`7HgojdX$F9P|x@ujkk)))ȑ#f}:g~q ͛}Gnn.H|~ӟؾ};_D t IDAT"Q\gVURk@,$Ď`L0183vI7ټy^?yfϞMaa xxw2o2k,RSS`߾}Ycǎʕ+ȡCN__|;<L4 4ٰa3sLٷo?<̚5뤦:}n:~_|m-}鲳۷m ==<%@) hA/NB+;E:xÓ "ۈ-ʼnw5+.O ;l;h?QdFbpǦXy`/'}3 [mEo4Dӹa,Q8, m";\ ? BK({PBxr*п KPy8~8999cY> vd~Hx&Li8s B̙7 H zꨫN/_Nll,ͤxbrrr"˲xۇ[n_*|Hvv6mmmL:4zzz(,,dѢESPP7 Ցw͗%9s>馛0ammmdz|rill$&&R{3h6)%a&?0A1App%..n hooχeY477;}pi9t3|'4MƎKLL oi?~zikk;/qiuam1~G3gsihhhpR?5Mc|k_=))%_@ff& XŋIOOGJItt4NMBB°ꢥtN̙34M͛ŋR288HkkӧW$\~ rzzz馛g.\HAA0qJJرc6T233j~ B,h 1 o4ki w'u]hТ"Jp 8 ÓZpO䞞8q"1_ {(yJ"ӨmJΊ+}zlp}MҐ`VD}0</ ÒП}/ #XkzقK"nJ .\V'ʰ,Ze.X6\h^b~$q @DKfI"Brmq:ۜp/\7l>̷%FiUݣ]0N@MDCZx']+X_ y}i8*F騃"q@/+)u*-:uٳgfgƍů~+^|EyG2e G*'䭷bΜ93?itM< @ɓ'AMM wE۸n^/1cƐ{Ǐax]=z4DGGUV1gyJJJ3f 'Oszۿ/'? qqqK+}gq23VCϠE{>#"Q;2)p$j!0 GkDb_T:=.K3򌋏@cYv>A`uHp9} }v !u%jܹhi`mL.WlN;-NVM8.C.P77WsA ~K; dP"tquKN!$V'/G[ rOFq^PBPYYɦMgx ȑ#IKK'Ozٲe wy'? 68۷zٶmvu"v\h8y$k֬RSSɓ')--u'--ӧO;uۺu+wy'YYY?w}wعFDG#Giyyy<3N#BEFF)cǎa[l3gΰg*++)**;vPRRB||< *#\6hd& k=̄vol2nyg1cqO裏>"99 w$ H/l.hshlldǎl۶iHDTYYȼ͛7SUU9vm6rrr;v,֭űcǐRr!֮];/ק)/=ʌ34i&L ++ !ׯ'PPP@LL 粒j)**"!!A P B(+q"#cM=En H˛4t|oMh)a1 YP?cpIQ <"bo.-@PXDAhZ?*stMZ{:1N t|p_e򜑯QQ\CցЛD H '9Os!#, Bg}ڬ#F m?1iq֬< ;PPUǏ~#aE yVy, e !a8^1.?nnuj}ɯp]].RJgծɓ'ol<::7xիWxpر9s0m4VZȑ#ILLtuu]uHOOgɒ%YBնM61aΝ˳>رcu[~i`uuu<WV AȎp6B@!m{{z$rP K26X;$! V"<7 S?Y4g#F"i\x<-ąۢ ki`,Bąr@PDD+0 ;ZkD yyukdK$ll'# |"b7,dmn,TBWHaBMBBcxHOOG4ZZZ.r)Dz,s?7M͘1chhh %%;Ljoo󑘘x^t]w<.,ˢ_~㌷ptS(";;~YCEH_N-BĬc&A h8@qq1nt]'''4 0 \.fbBP()$;S“#y=|#& g5 I-gu/֭v#.b梿C'(Fqi@X RA$`j[jA^EN1?`vSD2o{&INg!Z3(]e 0ądž޹RR_jzvy"OکurGb]n]-C+nK^-hHS Ч 3%bOZؐNH?}mvm@V^ N` ?9jIm Yb fm\xcqba!Ff tj}#mqL8$RPs5kK.u`׮]Wd=Gkk+EEE,X,4M;w8|0cƌk_G!))a=z:̙ҥK&77#Gݻ^|SLa!qDSNLaa!'O&!!)%,ZFm6=)4qDϟ륾1KRbccYz5L8߿~n7111޽cǎ1m4ӱ,DrٱcT[[ˌ3(--hѣGB000;X|9 .$&&m۶]0B&>>3b֬Y3IJ, )..vFJIII 466}vƍGyy9>41 YM. u2Əs",ˉ;v555̛7[n )((={`B!FˑRRVVr <ȤIqľiӦ1rH6m4L\:u*XERRBRRR>\31tر̝;vybbb}].Ǐg…L2 :^`TTTʕ+x<:g̙x<dffyꩧ`pp+WR\\Z BP\PJC{>|q4!*q4'D%$!s3Cghmzax$a]D`z<B?@ aCn7%}-n0+$^MMkL@"ExЇ6jnVDD% ;r:©rZ>aB+ I <-KhR/R^$r)i僱H1p؂#Rv  3A/%>iAŲ=$ezS"W Bݰ8缴1a:{n#y4XΫ[l.1_O>ζѹxN_]HL="=®I+ZH5]FO7 B}`kJeT(! KkR:G[ZZX~=L6Si۴9EUixUWWqF(//gΜ98Jׯ'99rfΜIGG5)/7ԩS)++s;8)zE8Cu0{ln&H)]m۶QZZFM>I&{n6n8Lx<^ob9vUUUW% !8~8/2 ,p8FCC,Y{+::ٳg3}tZZZX^}U0aw躎[h"ƌC[[{a``z9/| dggwkz? QZZzhAJ=C0dΝlڴ3fb ,²,t]4Mٶmdff2oLoo/;v[`9sto~ôiHKK0 _MMMl޼n:Ϋ$hnn洹cǎ/*۷!N"**J}]w)2"QUUiTVV /#"lݺ׋?)4M8qBu\.ׯBRSS x!8tSNjjjx,RVg|Ŵcǎab>|RMC6?,~_3gptttpvڅ㡧_ Tz:ĉ'HHH`߾}G9iQ__իIJJBP("xɮCt4Uqe&SŅsϻ-ovoyN^Kw^,M\W.v>K pLC[%֛lc]Wg*Ɓޅuz*NPڏ̸;%?]?~3]{>qs IDAT}UraEȃռ.ʺPfΜIyy9===ɩS.ׇWs믫Jۅkۅ'`+6KAƞP(>[EK}~-rrw8ڡP6l͛?q.wcq]wtR\.A?yо6y衇())AAoo/>k֬9o5¡xhhhU>m/WOx1MCg,oP( &Rwd+]MW-Y)1bY >BP(lhþt¾FCڥ>#B!8|0RJiooY/42,>*++/^3 棏>~KFhF c\x!ѣL U( 3wox5@J4TP( ŧYI$iKyy>^  ݈7%A;/0 ǣ篝x#9.ۿ@1 >3׋BP(BܼdG>ANVPBP(*adA$*JCuIDJi躮A"\oBP(>B7H˛)#nrH; @ ~nGdZP(9C5bb>g#IJ,H)ѴHJ.>$JB`j0JxR( ;HM$D\UXzYBLlge75/"~s-BNB E#U-P( g)%&Lttvvrl٢҆>& NWW%WPrss1 ӧO T+ Bbv~,K^T$0链Ftbˆ)2WC;? Xb2ɜ#b'+sfWݕ-VBP(Q4M{\,²,hnn&--7|SEB]#Psr-m6^}U}bbb%>>Y4M8uXLi! )%YYYa@UIHH0 dgg3o<v܉i9#F@]]QEJMP( b5jN(O1NмUMFdΠy/3N*"aAZ`QGg'xK08'?X'`в h#@`U5HL`VJB0`KZ)K5A-}f} >5 Bq ظq#>(~ޝ+;w.466ex :;;Yd ?vٳgeeen~/2}t}QG)**"**֬Ys=Gll,ap뭷l2222uvK/DMM ϊ+8qay֭[G(n{eҤIDGG waݺuW%B\..̙CFFn~7ӟs`.3w\N}\jVb``ŋsmC ݻwS__Onn.> Ŏo|bٲeX oz>}W^/yyyXI&P>g}zK] BP(\1e#4 @TR1 mC-Fl y?Hi"tUE{埰g]/4ަ]$- vT+Jz@Y!r@+hahmm+ > d$$Ÿ{AzE(U1Muu( uH\\%ͮMd$$$`Yo^1 oKᅬ墬n&y睳S!غu+wqiii~iuv3w\-ZDkk+[nuS[[{cW]p Bqv`u&@E&|Wk*^n2eXf_Wx@sbDw$`8'F`=:ZXus}-,]Q`dH 1>S 2?X$XN{/ P(رcYlũDP(N>ͷ JDff)--E46nSO=Ebb"wXd Ǐgǎmmmرo1cy"4Ν;x<;̜9͛73w\ػw/{͸q(..9V555ZJ]3f 444|N҄ 8qt9*##N>ͫۇMȣٷo۷o&'NԩStꢣ1 0xXvSƗe/Q\\륵?Xs=flٲ^rHII!**Z{9GnGJɃ>Ȳe>~ Bleƍl߾4U\oQͮ >q^;.FL}ÓBg_A .qٳme/AțH/}?[ I]5Oh .:{ʲ["[mDv`m"h` RV=-@i z@dVa\i ˗/#..]4MM  :@D\ ƍw"'' R111Kcc#nA 55F?+sQ/СC9)~XD1y^N:EGGdeeHEEeee̘12e˖-|G OLL iiig?s(Bwݻ9z(}:k׮7\o hj߅v_3 |_Ɠ2h2g$*i=Is.oFlf(xc]=cq[|4MD6%$ DX q-H!Yl^BP\'sWҥKy/5IKR^ӏ9J)u}XYUeYXŖ-[b ʢ?[N =ztدRJ;vEOkhh'? 7x#'Oc2yd x'/"&WBll,x(((dF4;w=˲hhhcRPP̙3)))aժU曄B˶3<իՅP( 1a|A.]ʁذaJǻ0BVԠKL#PEnAwU;~B>;B`@3p %:ݝEJjw&00nr)朮M :fRzIY{U`#)Sɻ/|M+( ͕isM7Q[[;)&&Xz{{aΜ9188Hjj*FrM>@L>7x8q"RJ'E㔖2j(?~LtR~?QQQ;~@ C#Sv=z3Ov޿?wӟ[oiH_\ ߏWBP\G<,Xu):bdJ 5 >LYƅ'Q2 V&܍m@N"g޿+Oɵ*]$'$e=ĦQbdo;+ij! H`dXDS"' 1@dDVI[P h7=<$Ʒ|G"O"YNCO B(1113n8osӃ!**_m۶w^L7Lff&. &¡CAx,YBzz:tuusNn7[la޼yp <0n8 ɓr2gΜ9eY0zh9s ~ÇSQQEҗĂ hoorU踢zB!.\ʕ+9s P09r$PvZZZ;MôRXXȏ~#ZZZСCYz{{)))'񐒒eYg}#G `߾}h"F0 ^~evAUU ,`<?s=̝;i7Ø1cvSsBP\ Cee%zC@ H^БˆFL t3Q Z+7e 4L_ۜ-ø"-+ e!'-&Rn,Im@DDV,s(Z>Ea=L2ũSxWٳgnjVZmFAAF֮]ڵk񐛛K~~>GFJeYdմ3k,7nB>|UA HOOIGlnnfͼk bezj.]ȑ#=z4e9Y۷o'77ٳg3c ٵkc~ 7uwyONYYB:::@u6mD~~>唕Q[[ݻ),,$33ѣG}vk6mĆ *BP(.kY!ɋPFptO{UhQias?616@Cs ) B 2 B cƈ-BXtORZ}U. G rú򆣛n@П$֞!BN݋ i=@, iOOX~g}V] B$B<DGGieGWWxHHHvjWggGZZ>GNN>`0HJJ >}9s裏ƿ˿86TuRRRUts$%%9n OooyYQQQ9Ds8M匿 ))ىKRRa i477;inBbccIHHpL΃నE"##S;::HHH >>~X sMm BHKK^˗_r]& b\ɸ$<'-XMGh@78/C=uDhhZhE6C@_PՂBP(>!"Vh===8kjj#GzSuuu~2MӤu9srB!Μ9s{{{Ѻ_~Utvv @P( Ua~ƌΙiGGS׊f  _bm+ B7y+4:BP( gcɵht %>UcuX^ }BP(',Sxg↑7BP( _|}no@*3h:fz̞(@hQ${pSmpe#w"{m'k+~ \w Y $"%@+1 9A6MbUsU:2>SPP墡 ^|ųcDJ ĉ444 3}t4MBP( BP(nj fd9NĿGsbV2b&к#;ɚc O )-sapfQm`)- dy $H̗$" <:J{kP4[%۲d<c'`l34K#M4!$yO yxeɲY5׭{%-,Y21YeԭsϹUgP٠MPF  &(_gQlkߙP|ߧ&b'Nd̘1SO3gO?|*iiiۿ;w4M^B!BAB2 $hI- Jv4x?F':(%WQ2u2c2VZ8876^AGD֢P2\h';Qu^Ab&LC5 s88\ZFJ>Æ@N~Im `R8;tr _aܸqk.~a8_|1s̡,BkeYx^\.Ph4M|%ymFZcerR[kkFض'IZZ\zT" t{\xaնhx|#G^x!\k!Cre۶mY&U?|TB]wÆ 8է:? D"|_eȑhikkȑ#,_y4i3gd׳eON]]>,^h4ʔ)S(((@kͱcذa²$Q!B!D(/=Q:z,i̓i3'8<5ѧ2,cָ3?z1Ph2.dbI%X 'R}~PjLVIǀhgSUG6 D0 b?꫔w+@^\\̄ xh4ĉ)++öm,X)//筷" 1ydjOΪUJmլ_>|.qAƍǥ^J~~>/G%H" @ZZ:+W0 ?>SNe…+$ z ֭[YjepB^o&ǵ^ȑ#Y~=֭4MT C !B!=B =裇Q?5 &-oh+{vh922_|)hi&mu"Dk= Z$FN`^Pai7X)̋(#} BѠ4Na@  A*i:{5Evy9œ9s6l]w#GcÆ p!&N֚v6mڔ *CkMuu5&L`\.۷o'77۶I$TVVj:Y/q"''Ԓ`0uttRKba1?S[[K{{;EEE 6,T[vvvonR__OSS ;vXtttAFFX'NPZZ \pDQ^yoߎ&##'ikkرc:~8GfС!ߦB!B!ze)$шm86x'(K;{2k?0U4gQ2Tk=Hn>H0fư><-n5tT t2XxA85{Y?Ƽ$j] x ^|EnV.r&NȼyسgP_ǹ Yx1]tl"{ gͪڶzj(--e̘1D"پ};K.ĉ|:~?9s0{ӶmR}+++cl2ػw/>nS2k, 'N`ӦMaf̘ 7^HFF㤊:iiiضƍ_ݾ , G^^7xcqm`0(OB!B!dŢ!ZJ-:#'8} ˮE.?y[Q!mаԯ)/f赿Gk`n:O;w{Ec^@*pT[E2([NNgu/R__ψ#ˣcǎ%߶:q3g2g;ƦM2mڴ/T"''qj0N_aˌ3(--Çs 7oms!F8zhj{M61uTJJJ(..f񔔔p!֬Y_ԩSپ};G!s2a„R(¶m {_|qyR8p UJ!B!>>7DӉY#o']ӸEZ,0nfpkW^19~ oē9`>yeȟ1| $Q0+>nPE2`erT?% nfK-%:t(~x< PA{{;m3j(Hmm-aňFdffضmoDz,RnRhI$3p@ƍ͛S0M|,ˢcǦ$.W~?J)V^π2d999yP{e„ 17Gov*ok֬aժUqwSRRBNNea6b\p<$;;\ _gÆ >ǧN.x!C2Ŵ֩|B!B!ęXyE#0au$Ke |0Lx{coK)bDʹkē5 e@k'6P`lwϔLngX0F369gmL<)SpԄeY۩²,^/>ӦMcذaۿ|>V^?Ouu5\wuL:K$! 2e~cc k֬a̟?)S`6x~:U{ʔ)\qx^HKKcq.]mijjBkcra9{c2vX?466b6j*~STT7Mf̘k0\p?0`@.TVVYYYTVVw^:t(@ss3999TVVbse<,_~,\ӧ&tSSWӧ3i$1 ^xl"V!B!gdviҀxh[%,N*!m?NON<@h xH[VmDg&fAt2 IOը] I;|Hwg ZzXV*s(Ut{۶mlܸ^{-0 *++/ 7aRYR];k\.fΜIVV555\y 5"==fl²e˨#=={H$„ pݻ?O|{K-,+Wx+ĉ߿cǦ:u;w.G&##ݲЄB!B^,4c*PJh2Og((:'P*b%L0P4VGAKF]'nN@wOr:FԮovw\ӎc&iii>6 NZەevxbnc>oMR8OSXXaB!Yl;vsA!B!$%y4M,Y‡~ѮkGC0dB!B3/ !:qe B!B|,oZ&Y jB|>]  ##[ BߧkCS3l@w^ !B!.cШ ty L̴RLHL(L0+vwJ(@z O][C aE2]/ȶm~a***(//رc_`SGGK.r***x{ ^+n2 6_ѣGSq!JJJڎzͧi4h*vƲ,~_t7>0HOOiii 6KB!e ٿx4Rgm\?d J Rm2҇7X+A"Jlj lv'- , A8]5}Tn7P/k(׍*c$X% Nl$6&@~!C/~Jy=OSv籔RQ[[֚#G {lD]]n /u ~SUUaжm"9D"AAAnen"ڸGP ӝAKcz2щ(O O~pˇ-^@5Om/ǰ|.?m:Ѵ9wҕ^C KC}n%y@lN֊:Xd[lpv19ϣW4(,,q9K/ğ?Hvå\s wuCŲ,***x뭷xꩧ]Hzd֬Y|_eƍ<3 0D"|㡰|1chll'Xt)#F kQRRɓ1M 6OP^^`ѴiӸ;0amoYcra&_&Ms7|3#F ꫯ:::xG6mلB!lo[GGGƍcL2۷cY]tw}9r_A￟o4\.-b޼yh1 G}?=m֭K3//zchjjbժU<466/},2220aoF=׮]7J6wB!BD[qWcYnn7ַ;ʢӦMcD"}s>DǏ?)eee444Ǚ4ieeeX?g$["ir3f 999 >˲455vy'K PZZwAff&w}7\g6̙3T 7@yy9O>d2Zk.~1i$ \}u)''ѣGcF*#-++Yf&p|\tEQ\\̝wɆ Xf O<-0 jkk榛nb=@|3f xsr,b„ vm?WZ(""33h4 A,  ,^[߾/@$ᗿ%SN% СCַEff&?A\.qB㠔:iB!BO@'NPVV@D[[Æ ;mC۶4M k([0Ͼ577گT'ݝIGqN_<BuPd)qOVYVzg3hQ!-`=ߠh=|`zοO**Yj~#YskB@z9f3 kug?nϟW_?uѰaØ7o~z/<#,XcDzf͚szŘ7oÇg|ߥGy믿Yf/kyÇxΦ4no]wŻ˃>ȝwٳ)++n;zBg}￟;inn _&LǼ{G?⮻:1 ,\$==K"_:vJ)v?ݻ3g ϧ￟W^yɓ'3zhn7\r 'k_w3gdرTTTw!##[nz+SRXXxgx'xW;C=ZjyUWq%zS}ۿ[~f̘رcYl7?~<@|;XxÇ{xb~aO~vޝ Jq!B!>۽ƍZk=ʯ~+-ZDv IDATNNN͛7vZnWW(曤s 7cuss3W\{[Wr 7@nnn}۲e W+_JI)B|>-Ztƾ=o~a}{79p2Cp,O Cf̘A<g޽t,YW_Mqq1'Nd}+Ų,|>z)..N1c}O~n&233;w.O=TH555l۶v>#~mӚ,~?k׮eΝ/~g0Mv8@ZZZjaEE(q<ڵkhoo'3~xbΝx^~r7_ɯematk?~ڛ-Nk}Z/g=|cnOb퇡ǖ1Lh+R2Ϣz mx q}~8qPn蕜V0\7kG;o<*KUo!r3u6]Yn{/-GM݌/???+Z^^qP(ZFqZpXv-cǎeȑI[[[= H!Z |=~/?i ^x<\e˖#GcT 5~z(5?яpD";0%ӗzq݄3-==0BXG(z{量B!=lڴkzd&~e˖0 ^y^(p:t4 ͛YnݟݷÇYߖ/_~YZU6;;FrqeaȬ+L5[T)4N2U`x (YeN"|&8yIt00)3ꭞ}\@'wKcYVR_͛7w (TVVDkHW켼ӂXՔ:^vv6s b1 륱 kYMM m K<ꌅ.Gͣ&778q".rrRS3]@WH{\ wg5.eee|v[NNNtjjj瓑A4eȐ!k>sgϞ^WPPJ$yB!B)@љЗL_ҟ9Dtr%khc[ndsc¸T(?A#{穪-[h",X@cc#J)bRHnn.<۶m#--;ve˖T' ʨQ9qJ)6nȾ}XbӧOgĉB!n&R9rk?D(--E)+K-Xd cĈ?s璑}x9r$ӧOHfM>2zT}R^/ǎcݺuTTTP__ϼy󨯯SoQFqEJyyy,\*V\|_EqexbJJJ?~Gr5אySQkk+ 2 àקjznfnv{VK B!A+eZx2p;:;7Õ378vxmiK8P PFRϷmu)+Nr Ccq¼PJržv礔VWmS3rR~y7),,ds=DQ àP(SZŋ/~뮻qx饗XzufرclٲYfq]whoovcVZٳ1cFjy8lٲ%\o0p8Lii)@*۹ Zk.|>+Wd wqD08r/^z)?яDk͸q1b_1(j*ʘ5kSL`͚5߿Kr3{l tttz?S s+;w.S𫯾3gn:/_ݻwoSPP_ς 0Mf|ZH]̧?:I>gyqqWp=0M+Vpav;Caa! .dIKK o&nVZYp!ַRszرԜvٴiK,[noNwذaCmII >_{oڵKPB!B @zs5$88ޱvLDK0,vpǗL_2S9*b4|`=8qhy5Gw2(J t3؛4AvE4MC$ޢ!ZWvH^^7oN yS8J)VXAyy9ͣ$.đ#G׶m_F0MVvmYΝ;y'8t(x<y(**rQSSoƍbΝk 2F***Xlض1 wMee%%%%~ZZZXz5K.rq}]<O̯~~1bDnс455cq8޽ JJJI'^u֡>m֚bŊJ)|i}GimmeDYdIo;}ؾ};iii\.ك8TTTcǎRv_S\\&Hc@5\"۷ogɒ%a222{9~8Cʚ5kx뭷]Gp~FCkMEEEjDH˾|> VYB!35d8;vYB d-('Br53ә ( ɶN$y<#ɀR%|<}8qNy։{/7tyg=Fq۝Z:U軫٩ ۶m;x1MBeh4U0TS9C,K-5Mխ&O<Op;ضix<8xiojSpj"iۛSr2fN_sH$Ϊl&zO@J$=!ӱlql&xH$ض}ZSwpݎWND"8 k].WsҵsB!|^xjjjXxI=XƝI<GJW`jV "=ggݙt㬔1澿űsSn3GN%uSus9s=c~ ֙Z> 8]xs[s"B!K(:VZ;2"B!B!2r Ki9BB!B!玕mmuh-!B!B!=R0d<.P" !B!BZ+)XJ,D;{P晖i, \Y#ID6n c!q}27Ҹwd%nr5'cyοQ5g4:p}IA{!ɥ,P Pa x@e)t#(_g0 Z !B!/Bd੯* ҋP$z3c4812PJax!Y3mr|I4+#ɵcI P_y!q@'?`g`P"5Y D%;:B.PqTFC(Ht3 )!B!BY?C# \iPדU:G.|4%2pu^t'VZ-_nӃ'}sWuB+tVhSQ F~r靳Lu`B!B!( m\bS%\ a7ZF,"w\@Ѵ%NLqye? PaKnc6X ;3l 4:PK5jB(0@k @ !B!/A;3R(tj0?]p'x[X # Ҵ˛Oɼ75B 4}AFLt=* t5M]'B!B!Kxֺ8)ݱcO̞vchݪﱆ^s~FGgS" &>#>S(38%B!Bd( p8B!B!#h&h8Yh-B!B!ĹeX./U QJEB!B!hk@ q8hc~_/̶m"ezzE ]Dp8?q]bY|B!D0Zk|> `ryBщhr<#@ǒǕ' @ת> uO)EGGGӾ>[naΜ98Nhll{ Bi Xo$AkMZZi% ,X سgW9Zk/qעFkMss3~֚7B~rK/ i444pP(ĭ_Nss3Oi .'Ou*," RN993?™&!bZz"9{s_L&(b[*\zXx1:~N6J|>$Iڝ]AND} L$YXbx#=ylq#p)[Vњ" >KRcuK^z|r{= bϜ9[nZ,Y/hdӇѣG#IrǶaLL 9s|^abk2e :͛7c|>Ǵil{YݻwviN/ISN\,'55ߏ笠gݾ$ԩS)--e PW^y%!!!zl6(Y8p oHNNnyPz-/y駱Z 2~j.]Jyy9-j%fB0/"_y/  c= PX[,ߏQ[z$а0<.;Bd! ŏF14 Kt:A 1+GgA46Eb 9s1&uB_~ >~[$Iw 2}u*Fղj* %K(P\q~#G t8N6nx΃Ze$InQO)**Be+I"6T~z!G~~>>~}xdgƑ`'`00i$O(l6BCCX,p\. `ի))):tH9?884N>fFBBBh4DDDvEv= UTTTTTTTT.S8n&yزeJXSVv4JB.!'΂; :Nk:k,Mt#HDlL'(~8 dž;lq|Ϻhۦjk(ClsNVEFxlX,4 2/n͡E:NV?:zcxAӶrhO=9mp!^W/VQXXHaa!$dɒ Y2.8әv$~p8y7pI VUP$|>_ڛ3Ҏ -ЮWƷ(Ju:k׮r1|Kn!,ZDPP'77O>z A3m?_fŊ1~RnVF#O>O s*********箥r\3=f wEQDE%uFQ 1(ρo{ĶX-혖i;e rfV+Z׫sɽhݮQ1 Y؅Ё!hȒwI?nF4@C4~qQcBqSPD@SF6콼f fH. 5‘B` ׃|DF. gȍ 3@ wȥt*|UU$a68p ?}ݘfz=555v4iÆ #,, Y)--eӦMٳ lSSyyydee(yfnݪILLdĠh8}4}xr IDAT6mjҳgO.***jf6ö#0 [HDSSs̡o߾XV<GO>BKff&\s 111t:;zx0Lv&L C$N8M(,,Ce9s&u50~xEL O0 w#..́Z vV}_{Nin3f`02rHz)--e?~?OX,w﮴Vm۶qFV+qqq̙3v;}W]uqqqx^6o̷~GC2e 1118֭[wSp0n8JÑ$M6QPPЪdYfe.*׿5z~j2qDFA~~>FW_͐!CVxW&??ٳT<9p9."ϧwHDYY/rT9NjFIxx8,S^^7|Î;:8u 磨c ɓ'2d8t9M7DJJ ʂe˖QSS*UTTTTTTTȑ#IJJre˖vtL8ݻc4`߾}ܹ^O3f ǏgΝL6&}]rss۷/W1cĉ'HMMjRUUŷ~ˏ?=&L >>^YɲLYYwpaF#={Q\\̪U$I$&&2|pE'OѣGrرh46mDii)`6Yv-NqƑHhh(ZLAvt.;TdTߒ75!y4 ګF,C #~ߐ%7֘L$xj`L+vt8\5њЙY}m&L lZ \B7,͂~2YFHڡB0Nv,Ps2t=y$,Jzz:=z !!L&FZc…+gȐ!,]ݻwwI|ό3W[ѣY|9+W$99ŋ3j(z=1bl6ׯ'999sPQQA}}=ݻwgMtt4ǎѣ,\9s搐FHZZO=9yu$$''jh4@LLb))S8p;NK/*4|rTTƍgϞFA ''GXxqIKKl6c69v:˗c44iR@ffftR;U7ԩS fǎ\{ 2D/o&k?ŋ=zҧ~#GbXf L2xDQl63yd z,JJJ(**]yGd2DZZEyȲ̄ v$ Qq\dddK/qZXWv(z?#F`4Yb:aÆq7cZ￟=z(cМ+`0hp\7%K}V׷X,{XVt:vN j,3vX|A22279$K/tun[g&&&A=}4|*@Ǟ뮻:t(AAA444i~7ocƌl6++W_}ETTyyyܹEnn..1cƐN~~>v#Fb`6Dx ijjb…=F8v옲ѣyyy׏#^|EV+ vmdffAÇرct>|R3f !!!S]]͠A0`& VKffv:Z+/b?͕xΊIU2 Ro%~ A @LФ hGୗ F N2)g2$ VKTTǀF5k `˖-|hZ&Ö#oطo2I/$ 0_WDGG~zv؁Ncʔ)x$qQUUŊ+p8̘1C˧~x+[oCaXXf g&-- rrr9s&111XBù8q"o6dbΝ,Y=zp7sq>3%!'01vXؾ};ׯ'88km)JExWF;PUUhҥ3 p^bFŸq1b:qqq̚5 n͛jL8Ç߲gϞ (l߾ATT"\Jdbx|̘1?}]v;\s &//UVQ\\?ϬY9r$^#Gp뭷ҿRSS9rtr3l0***q4y !!!;lvɺul\{?۷SQQC=ĨQe^Opp07nT\x >N`ݖ^Gms4EDD0n8oزe6x'0. h4XVFСC9s&n%vZvڅj宻b,Z%KtJa <|׈Ȅ 6l۶mSı+YYY3}]=~NJ3nǓEcc#}uuu 8K@INN {qFEff&yyy|=z9Lxx8uuuDFF^VYJDTT|'zrss6l_| 4H 9}nhh@xnݺ7pa:u*Gر,FAII k֬ '',m_`CgΜO>a̜֭98>C***ED t~R@o,@5:7P %}ipɋ!(>sSh~ KH>7Rdɏ5|fͷ;(_%7 NZف[2䗛0 *@>LoS2g !p|^JѰw^dY&22(n7c0xׯiiitnfZ3gΐ4{9s)S "㩧fQWWǒ%Kٳ'Fb׮]L0J^}U "**$%B(ၯ .'N0x.OnݺqW`ٺu+zhnF Obb"+"iiiJ*y.L/cΩSX 3h48qI B"##IOOO>X,V喗SOQQQ4=z4}t"""}p dNq{mڵ444MBBr]NQ__Ouu5(Gݻ7wf|>vɊ+X, RRRѣ.??&L$IqFsR䷽$-!Q18A;ND% E KhM h6,f8n~!ՇVRVca} Or_'&\^M„`| 83#f fAlCt]" ;ř3glXV|>illNl&,,70$Irui@nQq~($ZlDDDt:M U ܮ%.KI}1tTLTTv~?\.?[$t:~ K cOY`95.K9~jkkQ\\djz)++ ..I> i ZyZ-=ܣ|YWӀ, 6lpQfq8TWWpJFEEt:?QRRBCCC+qGK ^UU_~%Ӈ'|Iii)[laʕ9r~qm1j(m;~?%%% gAEaXg޼y猭rÁ@ܤIhjjzK˝@Yii). Fþ}Yw ARuuuTWWc0JɸSN`̙3(f x2ر͛73fLBnn.uuu?~[+2n%=EYY Z-111x^p6O-;^C;}d?ƸltD'M8kiD-4"ѕLQH>Ϊ]yd/" 440^:m.D,gŢX9!?`p8 gϞ8HJJrp8qlذUA1'8N%9s&nGE <(Y.뜘d_}UP% C|8Nl6AiG^Np\tȑV%<<.ucÆ <Aaghhh@$1FcZ-Nϧ6_}C' ;>#~f̘Ajj*$''sc)))a̜9r6l؀$22Uʢ~h;׳a#.p;@WV`KSO=_|Ann.t֍ӧO=땲eYn\********1f5b`@@q`em?`JDcc#AAAtޝBjjj`0PWWGMMM_VKeIJJ"&&+$8x`+%(X,V1(b4z^c;Lh4w*A T3X(-چ$+[Z9' 9wqj|@gInM'Bgk/G8o؜I ^09 #2L6_+tmmm-IIIDFFraN' zhlllXtiE:$eÁba޼yʄM6C>}xyyzmFFFRYYɓ1͔o߾N͂ PTTDUUQQQVǘL.'os8NINNoQŒF#8pɓ'+!F)))ddd " [l+555$s$P`Kjjj8rÆ #$$DIRꊇN@޽;fyf4 ۶mcӇ}RRRB^^ҧ xdy<6Áh{_P3g|q^xߪؿ?yyyARR BrСN-v [EP\\̟'bccyUaXb .$<X,֭[ǤI߿?1^*++ٺu+坾u1j(O-´iӔ²2ϟ+3ڵ;v0qDyZ-ǎlݺL0 DEEq!v1^3f0b|>+Wv'On:zŘ1cʢZ_|8x ^T˱c !((Ç+"ߚ5k4i}?˙3gغus!<<I0aYYY,X/A(((`<̙3ʕ+y+uB@EE[n%;;<:tʗ`0fꪫؽ{7Fݎ``ʔ)7`޼yP[[$GР-9s&#GT4551p@V\#<|ƲsNl6444-[( UYYUW]Ň~,TTT?m۶fW_СCAEv; }%m7‡~< xqwvޝ;w""˖-weРA,^$E RrWٳݻ3zh)..fÆ ߉*******F,oA}}=~hd˖-;t{9뉋tRSSCXXN";;ۭDn$%%@eeeC$Ib,\FD"""Z8q+sr7``l޼Yپ}; :K"I:u~ɓ')**b̘1̛7nɤWz!^y[L&_}gĈ|8Nt:._|.޽;c?娯$69"&n&"U^YgAÙ}@<9AgUwPd#ω ߅^YӄQs佼f\%VhFKYg%?ͻF\+E`,\h-[@ٵkCRRRž={BS^^hz)--nO>(5=O?4HJJ"883gδJOPRRBNNi&--{Opwlf߾}Xbrǣl۩j#pf̘A||<@ҖGFFH,_̤IG_}o&[n5=|&*++Ss:>|q7iSSSFC]]X,Yjw@+<̝2J IDAT;$, gŊXVl6!!!H$CF?裏* UVYCe֮]ѣFoc62eRަMx뭷ؼy9u x^,:/=z0eF#|wvmJHhDp8 Rylܸ+V I˗/',,qa٨fdggӧO4 AAA:***hhh8{~_bرc|<3XV~[BCBBEQ  V`w>}vBBBؿKeK`e˖KJJ F͛7SWWG\\\+O^xn"""T6lj"Iuuuժ*?NCC (~o8`v#I֒fCjjj?r)ejN>)k˗3qDjl߾?۷#"555|'hZe۷NC+)?Z-}Vcfʖ #<<u&a쌹䪧w!t^ md?w4 #Ӆ9|h2sم4' o9)91Us'Kеo0?\wu,[~o[-'gKoۮǎ`8';[,2ۻfp@B9h \SEŃl7f5B Ʒ=?w=Pj[mfCe }뻔[eRԣ3}Ѹh[Ng!py>ҧc/:fKfsmЕR||cr[>Z׶׻lɳ|j{lGt4_P*******Xrf̘qZSc[]o^``mٲ@֨-mIMnn.?`޼y;ZMґ ӑHyqv6ls$ /(xjnGeFW kAgEеTO)AcE8]?_ƳQh!4uklݥV{FQGNtFsA3مj[s2/s(^P u{ <;ҧm$[\|֑s~8_~ҧ-K=W/^9b vs33e\\l******** ] vf՞]^ػF=9)3d聜R'OlS#JYgM/;\jh4_nXLOxi-oUTTTTTTTTTTTTTTT.7 [nرcv,ϲ^RWvl7S{=Ȳp\RQQQQQQQQQQQQQQQ\..*/>wm-mRMEEEEEEEEEr3 DA sOƊ m/AlkKߴ+չ^?/{Z6eh?w"&`V⊢fl6lx<,^իWs#(t:6m;v?Na]v+u;.iٗ.g}G2w.Ϛ5k[^*پ};rϟsN'?09wuW[SS<vbڵ춡ҹ/t{V lKKri"W[02r`;Y|'WE4YA4bMFtƯ1!Z\ǩ-\Ie~ kd}@147 7.Ac%,@+nZxv;˗/gќ8q'|7p "p7F9{wҽ{w>:*&c6uzp ƢjyI$I"44nݺa:}^>h4V4ZNn ..kkOj1L|+.ɲLff&O<̛7]v ZvhDA=tvq,=88bF?qj"窧%-jGw^- q#h12 s xr ]mQc0޽;Æ ch4F#zߖRqdY{]w {bQQ@uSQJnn../r-ocǎoh4r-`X8rǏh4{lEEМز3S**********?72 #rOn NŶgAA ^yΧTm%1sB'CɍG۽͢Us4%2FQ>L4ON-?oki%' ALdp2 v$It:K;<0^^G$fUMV6Mac:5&Nb4q\f:n+eN>1cưi&Z-&s"R9rypխvmI \Gf9Bʼе5 &Kٺu^-R^\.|>9J؞ fvyŢ dBtBVthVy3c ƎW_}=u4ֺ~퍹h'׋n5Z:ߘ;xk[t vjArhjjRv[ v;_eQ Vk"z؃v>DϪ&^LQ>ݸʶ"јR0oK!qܟ#,: %CԚ9_z>f& $ lnRiܜI鄳Б2=@dH\)2{ H]<Owޤ2Zg , 77(~?|$%%kŬYشiغu+1c\hu:\wu{|ȴiӸXl˗/gΜ9׿Vu:Yz JBB ,`ȑc0{\B^oQP :Ȳ̡CpM7q]w:>sn$s 7cC]]{e̘1nx {o"sA^z%N:J3g3g$<< >sf̘AAAjļy#::Z[r%~)z[oEzzz/DiyqM7?Э[7q\l۶E)Խ{wnVFhr&V˲e{w&f̘… %77WG~oK\\˗/gӦM< :m۶MDDYW_}U< 6P\.gٲe:tF{9FFa۷<8~8?ӧ+9}YoFa…;v'pq6l<#x;w.saDGGӧOn7wP6ɝw5\CLL $QXX|uq~X,***xW;֭,[|ARRRϧ~'xjƎ&uWQQQQQQQQSj hD9>eA jнT;{2 ,"hۀY3#,ȂShOt<'1Ao u`N&4%#'/7ہZl( 2%DHx_`4h@!JhNLny. IeeeJAA9w͂ ^ϔ)SHHHqaL&=z ,,,zũSѣeegӧO$If#.. """1(yh "66V=QbpWt: '11e˖q7wxjf4[Me"##ѣ,Hvv6QQQFRRRشiS01o߾ 29sPZZnl6S[[Kbb"s%>>.,X@TTѣIJJB$.v뭷r)$I",,^z)t:IHH ..ɣ>JRRsg+ufƌ",,$^~e~_uV}QxZ0x`vNGtt4}GndY&11$N'N{3gb4$22ӧ}Ǒ#GhZl^窫3{Ns!@TT9Gll,YYY444`2:}>|eY[ndffx(++0&"7t-bP__x5t:`ihhl6#2Z-ߏf#))f3LTTyyy|>”6j8wNrr2Fȯ~+.\Htt4UUUhZ&NHRR~oVV[ns7ӿn7 ,Zs"(*cҬҩFUIΦ 4uTGSxUC{9.re 0s*a}'$a8RkKmD:pH^;:K72A'y^»V7?x_,<+TnN*>B@?_D[1L ~-6@D3! -HR~QZZUˈ#֭~)_= . 222d"((ZV+މK7 IAAA؈V%22^V UV1k,&Nݻ)y'2dx<^{5&MĴiӘ1cOnujU?pĎm„ M2y饗(((Ce:-Xɓ'3vXLٳ'z+v͍7ވlfL03gRVVJhKJJbԩou]ڵkILL_~iʽ۪nhUnwӦMoaaaGɓqݼL:{ s[l6o&W]u=,+[lݺѣ1O>pq>V{͛nj3Xr%AAAL:DNѣnf 8p iiiL&|In^/6B̙C=F!..}vZxϙ={6ƍ#??裏:u* ,ԩS+vy^BBBORV^1СC g͚5L>'2ydf͚ի/XF{m;v,7|3UUUݛE)kZ~?eHDVV_=5ݻanϟ߿?C`0oy摓C]]111J"k xꩧ2e ӦMcΜ9A NOWki, 0%9w%:s$\/y:*.}̒&C=}ݗsz;΁ǁ^U-ٳGk UZZOaa!P￟F<?xL 0n8"w}5 0(++####Pcs(RmSSSc=g*zq~aذa0a455o} xoΩ緿-˖-cxܹKˑ#GFQvŦMp\lٲ RVV識|Yd 㤚O:g}Tp. {noN4eƍ]Ҫruwwn:9q9/K"ϟO0h4J[[HsrH]]=Ocmmm͛7yfnV***|;7k.ό3(..&//YfMUU,]3f0ydF*%gUU=7ndѤ63illLBJ)%B!޻pWz.ԻF]%oȣݵD:˗֠ue]4n;xQLDGP 5'F_m۶S=g9s^R-Ceee*SFkӧK5~I+_ ~;mh= aXE,OGGN1C;yWkyWu l&cYaJ4M,J]N:5u~ YijjrqQVZifm{DA>a&aqF~__R)jWWӟ|̞=t Oʆ .=D"T`r؈Rj|i]ˡPr  RGQb~~hZkRO~1\JݻwS__O{{}(B!{(t&rЄRdYD^((/C;qh7JAwV9 _ 1X\ZNa522^OCU`ήR38sIfBX੏d#)+o?ω'4iҠA8bPJrvF4p8x<ٳ &PZZRW^y咲)--BeeeT6ݘpuQ^^΃>Ⱦ}Z ws+dx%T0g޽h̙3yǝwޙbb߾}o)|C-3335-[xs@8TͲeشiGe̙|_>nmоرcr K.fR$ BE]222Xl;vѾzzz}s+1wܿ pȑ#>|'8Cr1o<>OҥKW_ݻwsINʊ+xꩧRP(Dww7~?P k+Vc J 멯'JBf"''q|> Dm۶]DQx8ׯg֬Y{|  (Xr1ccǎMeNvvvDeb1bXs]PP… tuu1{AYB@{w 6PTTUW]E$@I}ǡC>}:W\qק2ߦL•W^wAFF-"o>R3˲PJǪU8qǎ4m@ii);H}vMCCJ)̝;o|կ~?B!xo"߰)G˛Fzl}'ư|رn:?K<i*+2,y7ǟA ?V~KP(e/> R$Ws 񤃮;'tiMݫ&+:hT:8A$3F2L:~|#%mmmlܸ3fp50wTK/ʞ9qL0#GPSS֚"jkk/)[vsA-[FAATWWs]wDRwcǎ _BQq 7p??rQ|>?&Lb1 KsoR9e˖quҒZ5쟁qXvMYY fܹ :`_|_g…򚚚Ackll駟I&=ώSO޽6~x:Wzg'Xf w}7wy'+WLm 71_fΝL6 Z[[k K_n4x8uUUU<31oOlٲTprafΝtww/pBVXehkk# R\\׿uBP*EX|9~H$«wԸm&pwxʢ 6?40w\Ν˴i0Mqxٱcu]|$==>1V\իw3D3j̙8B!~_ ߟZA1//@ @yyE!BP(po{7m$}ӛ>OĿÝU2=8 ѮmÓhP5 ǝ5LbZOߩQxomG!sxck$~ϡ8:u>itр47O]Ns[(ט q ^ GXxؾ};]]]r^|IƏѣGSC>(` pÇOp \.lڴj9ž={X~=>}J,féMmm-/"=Dk{I;ϲk.yٽ{7hӧHWWXESSO>$===x^,{IC=Doo/3gvs)ke$y)^|ESA0Xv-P7mW_͔)Sعs'3 M6a&999qF}Q|>~ݻ˗STTj?P;}hooO]J) 0M~ HPSSCkk+` ـ\)EGG]]]/LNNΠ6mLʸvZS={1͛6RH`vB۶mg<@GGeeeB!v=z4pV R}ᇙ5k,@>h4ʡCT駟`ɒ%ѣ<䓼r1^}Ah'NKk͆ } àTal޼SPPu_/B!=GͿvIO[{wa WPu*z;LDP3C4t"*7,Jkdc>xs۶m/K~VS>;Pʊ(9[ZZZ*``㩛܁7ca tnnۍm۩~/gwทF:X,F<0 ^/P4Spg ԟf ; dժU|&裏r=7qHdlDh4:/֚p8W[o{uB9؉J|;"ϻGYgϊ8 OA11@w! uy/h ԝKA=:/p:˺kL79ұyP|~ʛq DSizFJ10p w}o222Xjcǎeݻo3fSOc7nz+W]u~4ihh^Ohrnf̙CAAAu){16m42hnry衇5;3ѣGDR+a֮]kFzz:cǎeժU[<g޼y\SNWWWzjo>؄B!BqaVfn1' uBZ$Bt\KBE4Y㖓?Nvh?'h "krմ)Q*10g)]58$eff2o`޽L89srJ8 eB!B!% 츤ƫڱ 5mE~ ɠ5v士v /0! fd^CkuL+GۑdK8N WmE'˨!29"?C o >WE^ g_KVMVV`%5iӦXn<x^-Z̙39x g͚5455i& K.'z DQV\IEE555[AD"6ȏ#Gm۶1M45jӧO'/۵^̙3b֬Yիټy3Ǐ{eԩ̞=^zI !B!2]FϠXTbו2NdqamC8J;J0Vz9۴hѸ21PyvWc.Lo6=ӝIh|[xs6Q:1EՑ`Jwӧ/ s/蓚F>%5jGa֭MFFtuuQSSرc F? \."RMZp!~'|r\.gftttpq9B$SXXHww7;VTTDff&ǎSr)(--eƌ֨J0JD! jF˜qv3^m3fnfҤI̘1#J< xGzz:{Ipr1c QUUZ0 &MĕW^_믿>"'Nrfb֬Y\.:$B!B!dzڈuhceѴDXP`+dsQfhw W0=Y1fD wOj|:Y`ަ0*rրp94`-6PYI5 @ TBen8M!PR5xmH$8~8\T(y&uN)Ł' 7d(ϪU3gGe˖-&L̙3`Ϟ=z|T+ѣGSXXȘ1chll.3. b_vAG4htUl $ &OLkk+x^~?~ۍR ׋㡷D"AYYǏz0]{|o|nxYYY,X^v}^R$&v[*"z{{J-\plCii)=zcRRR8޽[@ !B!Ȫ?Qfv( B ,YHZtJ`J3{lƎKii)UUUTUU *7B!B! P7nxbtJ/"{d.Lf "GiSjDY~zk_y'NVakz-_wVD%QrrrUcS$@k]k˜3Q'25&rS G>44(yx^)E"_&//믿լ_*rrr4MFŲeRmO? znPivZ\s ^{-}}}ܹz,bہ&ObӧOgŊDQ?N__|k!B!Jf=$ڣc]]1=(D1H+-(+Љ.On 7‰v9w04g;Im7'~;C"XK8h;T{ 41 i${7.Y[hd,!ޭQf @ T`R[[KZZ2lB @kMSSuuuaRܹZʼsz ছnj^xӇKWWWf۶mdeea`jjj,+5Gy[ܜ$3b~" >}q[C!B!D֥um43Ơ?x\p;oԣRN^g#Clk>:8:t̡Z ۷<9/LOOEKzzzطoWa pcxMCC !B!B ,nymۍIB!B!ޣ,?2$!B!e@#8wཪw`e`xsl3 tRS}eJRB!B!>7}UDDpFd5fwPNcl[]](Å pEO IDATb߱slWΦv/}-(CB!B!FaȪ458H"6wɓ+yloze/>6pZV"n%x:DC +蜶֔XL|.$\,|p-?E؇%B!BdMdQ32*( <j;HDc&1 D:pr1JpSe*S9 `ޥP`5 <۠;@g[\!B!o+ ?DlƗ3;ڃ:ƉP:A?!P!,_.N"R&p>j (C?CL xaS@l_4s$Px0&*\aV*0@T`gIPB!B!x{X.Suj!ZAƉvax| 7GGq(]U|yI5*겣$^5g$6NBjF7i3IT&8s\uB7i*@΃B!B!bV_o;K|F'0 NhfP& ɳx ʕM(3ٸLO;I[Qezƛ7e 5l!ܼt;+18!t8MhlP OqV2$Kh{F0Q8;4ˇ@!B!o-˛ F"t@*':]ǟYwsht$NN%i':OTJX_:ŕ^DuR\|'Q8hqh RPy 5M>i^`^pvi) 5Z1B!B!KjE9Dýv ,> ]Ǟ'-odM8;E~\c Tܘ $.H7n=?:UYxHI``"T!g΅ נ4CaPq \l!YUB!B!ĻQ0\R92 0.~ڧ3޴Z ;85PZ&׷mm&\̦R&J=kCv5.}cBqǟڇ7"0vnŎiyCsUg. M{NpL @#nQ CcRJfFf.X\*ZB!BnHc3LG)"P&zDQc# pq f e 7:Kv_m ;gN$/BftC=O%vE~B'0 tpS3uD:)[CEʞ*qwsq V0ƁQ0J@]62B!B12#Mv*ki$oʨ F_0 v"*dϸ_ ;Bxg!M{Aѕ$b8;x{w "2a0&*PFW> ā@@ !B!m0rt{|X#ر^\h;N Ǟ}mHDID{ND;֋`Gqd)eDGAGtP<R,)4? 'Ɠeyx@vy] r !B!ae!_@mvڡN)[n44؜Qe@y~__Int.f&$~!vγK43:4y_!B!BU\1D_+z$l&ByGsy 7VB!B!!eyh>}N\9*6fvz&ΰUx[wcĆY̰ 0|rpQ ӗNcrzh$BJul L2К.!XX,aɄ!B!{{'c 6qz@i;8dQQ7m6( 0N*w+\J®gpmb1l0 <e9:Ekm=;%xb/^LUUӟ.-B!c)IçXwlǛ3'wr`(+xs']"([Krzޓ\)O / BSt9,[STT@kk+۶m'M?bҥ͛}k<gɒ%uVGXŸ3g&MbB!Bp6 ͲLӕF'J>b} 2ȟb}Dڪ0] pgOŕYAX*vᮻbb1jkkZ3vXnf:::Xf͛~܌ nv|>ٳ-ͶFX3f kllz,K>WB!B!\c;),G4܋WvW4 4SW(wQ˗\`u= $|AAҟ%NeXN; 6^&Ug8q zci@7I%ڶͲeXx1eڵ|#}FA|8B!am"SfddN,=P. yLۍFkM___qDx<GzzH$} Zc&>x:—7pkVR ' yﻇ+M0U@en101WN84d` b@7C9 #V4eѢEضƍYvm*Hcv*pXx1ضMUU֭Øɘ1c뮻D"4440w\|>555<1n8nFMaa!a/9{{ߏ֚JnVƎ墾-[O஻b„ ۷ӧGkk+k׮eƍ5O̝;\n7G? (}_s9\uU|GyO}S(O~ŋXf >,\s V"''p8L}}=۶mO˗}tN>͚5k[(..{;w2{ln98x ?яRA|oR\\ m߾_,(?q&M޽{ / pضMii)7t3fqv؁eʔ)w}7B!B\(e(LߍJcԬ/5_h;;:7 xpfIZ9NǓ9x_x2NwF(]'_ ރ ״7ʏm]ayi~&jAF:=  :X`?it=UPi;sT&DQ֬YCz|%7pwuGM7Dyy9<ǎQVVcAEE|{0TfaJeQ\1KJJg?ԩS% qW3qDx'q\1e*++S+UTTK__Lj>#A__8CEE'NSPPI(,,L'-- ߏ&PYYɜ9s())ᡇ0 ^/^2JKKZ3c cǎ%+!C~~>Xw]~?iK]]]/**bɌ?X,R cݔ|ٳgŋ%B!B! @] D;O_TF,^—sI8;naQtqY|ezQ'0_d&hڎ`G3F?/|[ch\2P`HnGX7* 5VOuʲިIl֨"pf`W$# @EQ-["Hr\uU;^xG}\nf͚Ŕ)S8~x2ަ53skn*I(&E:b,XVtfau]U\ֲ"-!Μ;Zb_h̜yΙy>L>]e BP( Bqdp8_$fm/OS}8SVp>Op(X1b-|YE))b[ V)%ӼA>3yg?Bф4QKn nG>Pl`$+cI~ hK@XAZ"'_(;@N4EMq8۷/_R6l1cׯ}+! ,^ lO~t44ĺP(ݻ,տ,"3uT\."]t!//6nƍ \p\.RJZZZvP$aΝ_:ttb4H)IIIarp:aRSSIKK  saY-JΊ+ѣx|;?~,Z~?xb8!QΗ Xf `y1eQOP( BPt7h#oV;k{NdM "4 L)0-Ñyb $w04fu"#O c1<0@s' Í[ 5l"I\dM'(z]/:o7:DVL3FYw*Bw7!#i$q_B#LLFi{.0"ػl`…A `Z]i=4TbmmmmmD"RRRuXKK`J)x<A>}ի`0HUUU;Cn˲lq0nhhTTT" L׮]+3f JsX%v[b igy&J ~΁ɸ,fRJa!B BP( ap!Q:st@gI B8b^]AԈßʠ}iAs\ =TԴou oZ6Y{7yț6zLySW-+]H+i%s,tÍI;I;psYFnqe!g%Kn'wd:n#h*v"S? (#JD>Ғ7q쳃 PprssmdٛxpЭ[7Ll'$b}VjD}}Et$!ѣXKe@NSSу&Z[[ѣiiiTVVsNN8"&ol\`lv`%"_h}0%w8PQ§UVr)0uT&MD$rܹsYx1˖-;b.+Vi&+uGѳgO8 FiB!̙ŋ0`#G믷=(O?twtMoA۷/wqp0xx饗:p8ؾ};'O{,_*fΜI 2Hqq1qb1ڵaÆӟϸq0 Ogy7e^!)u3n83v3b_fժUKҥK9餓9r$7t477jt]M6eyI6l@uuM/Ʋ,ƌCVVBhF]]?guC%336Oxq'䬳bɦM2e 爼 BP( B_({6Ib>_uqd<‡N$2@Z d+F 2J:2p u(1>O{E)\$ {Ob<7\!j0~tɯk.̙ó>{Eу y^;'4SRRlDJi !~5-PJxl1HJI$9[$L-$xp8|uj:0 >ң IDATV򾒆NX,vP!g1YE0< 7y"9P(is=qqr'SYY-B}}z* BP(8999 TWWsE^o܄;ѸѶW[?vyV\BKL\[l [{h!whnpP0_lkѱk_$=m[W_q9N÷t8iYuز7!nz~8[}55 ]wu)"7 6 ёІ ނ4g5֛CuꆔRJU~!"++4lþd4L àoؚj>ڨEURAVVh柅LөohAZZhFmm׾L---Gmwqnve+ BP(-{ǛΝM1i²L L_l )zgB(>`i}n#++9so~g6}wYYY<̟?F5k?Xp!3f8lPzz:]tK @ロӧ{nN>o,3IJI׮]|TTT|)\nV>l̙}wTJ׏_7t,b\zYh~g\tE_Uw{sro(Aιub[e˖pgs'ܹsYbze^ҔZu,"at:q\p=֭[BruÝx0q\hF(Ǘ[mmaǦin]בRD@ pq"$%ja $J!ᑓ`0lN}x#T' ÕAe=8I'~lG] ZvOjT6c!-LHVD. 7!*Y$Y}8M[X^!* D¸<TD"wl4MlfMaK)9餓1c477ꫯ6~?saʔ)8N4M~^x`q9zzMFF-pYSv:Ogܹܹ!]t_s}^'ǹ뮻x>}:Vbƌq~1{l6o9眃0 N9f̘QlQ1)[o{F~J.]hii_GK{u]'RUUuw8L6뮻?ގϜ9shhh`С|87x#ٔ۷/W_}5eqwvzp8ptڕ͛7siK.>n_0n\nTVVRQQc|>ml]0`444Я_?.2JKKy;5,&L@vv6999H)IOO痿`vͨQ馛ͥԎUW]eY' RRRaՋ .,JJJ} PB6mڄeYx^RSS'==Lƌi455)--Lz.s:3f v.Rx7n7pTVVk'q]w1l0vMii)={/&%%/"^>}Ann.Fikkvs%p5 رc  hhh83\.z,1cp-xصkH)vK̙3ꪫ())%^{-K444P\\ȑ# .@m8p ^{-O>$qPQQM0M){ BP( [+שR_w54onjj܊ GZx2O`JZ<+7Ҋ |:jV_1D54;?+ cD,DvH>+{k$bW˘%Ї HMYh=!/X"J @|-F.+Vp- y'9^5wu#F#;;9Bcƌ!;;nVv$3O:~FM}}=. ##Á`yD"vm,]`.by뭷q_ys= 8R^^ީy|/#Ǥ H>}ka͚5g?n㤓Nbر,_Sҭ[7֬YôiLYfqI'ѿĒ-[dzuVׯ]ty _|1 `Æ q,\|~_ u_s:^444O0uN_Ӳ,ìY߿- 2H]]%%%tޝ"MF[[k9餓0`%%%L>{^zo~>C~mN=TȢE:2%0 !cY]g.2Y~p:N.]ʍ7H[[?8^x#GRXXHqq1gfٲe\s5PFg͚E>}??1=z4'|2K.%Aff&~[D"Aׯ?]I}NG8fĈm6Z6lGݙnΜ9vB)5MXx1>**)~;>ѵkCHu&^u|>BV\I~l!W^'ڵkxx^|>ٝZ~ߎW2cl,Çrذak֬#4ƌ9r$X2>%K0}trrr(,,<"*9Τ_PLqDe1zhb,\GCC7x_Խ{w[ܶmqx:suu5V"==ӧm6i@J9r$xR|,ZK/l;ch5uV6mڄi+lڴ-[:Kz]HOOk׮b1X'{L<ݵkH)ikkC4zYv-%%%|>Ν_N0>|8n͛7vZ|>я~DVVcǎeҥ455KZZ---ҷo_rrr444ʖ-[hiia„ ^o/̿E_n{w޼KQUUo /`zbƌ|߷uNL<~N-ˢ-[iO=999TWWByy9 [naϞ=,Z9s(IP( ׂGukxtHX):,?;>&،iPit&ĒViJ-4*A(F|M iAsut̸")iف;sp 42 @"4Y9PcJ߱><+W~_W^!_kl޼ &0~x۷/cǎNK/|HWTGݻ#lgl&^SSæMӉX,f4NR%06MM0Ks};Z_yBPQQ? ϥ^JMM 7x#^8]/x]q. "ꫯe]I}ʎ;LX,ۏx='C#;I<&8`4M!uuu I[[Մa^y9묳(,,dذa 6SN9/+_IҥKۙ4i'x"N`ܸq5_׶DogL^YYɆ 0iR>d^0az*0fN?t.BP( #-i;&QE;>Б㿃K XVǩH2A1N{Hm,bXX<?#4 0K)[?2zO!sv{%fg0\}` #nBCDI4K=]܁H ,G^"2"]"w@d$YW6LlOvJh|%KHOOG4.bnvFIXdI;Ϟ9d7f89ZJرcYp!]vWz-BJIaa!n={e\tE 8OQQ(+((?OTrƾ$aÆ첨۷FѣugҥK(۶m~ep={,a 8;wdӇqv.:۷ogvBz"==;wRQQn]'ʘLӤp8ƍ9HMM%sz._;dI=֭L8sرH$B=ѣeeeL0VWWGˣH$СCȠO>GEEO=o^Tn7]w?O9r$#G$Ԅeҥ̝;<)P & ]`}hԩS;>|8sU0a|_IV( B86Զc K-SґCt€POǛ; iě];]|E KF}K3>EwXg [@) U#OS@]A"`_W|v4iiii8N|>.qѧO<̙3퍓ivp8LCC]*#F`ĉt:IMMr1vX 챭\AqWO,cĈ_Z(4[illvvZz#n}HZZcǎ%'' 裏vy7:t(gq)))ڵW^ͫzItGlذ#G2fyBQX IDAT.>{_}{Ջ*FcժU3`̙3P(iibŊQUUE,Ct]'3b.r,"ڍ {J)7noG3< W( BP(@ωECF M[őOFd2 R"Eu5kEÇO%d.r~?˗/K2o<$|ڴivHjj*-b6J^㩧:o={dĉ?N; Hd͘ɋ/xDTJJ O<yyy?iii᭷bժUsK޽6m+V9sp3j(ƌc_kY*%y_t]gɒ%46}^yN''tW]u-;#F'x;v4M)--禛n+DQ:,&Md?oNB里N>N .`ԩaJJJw!0 k2o< 8 b ͛᰻j:Nhnn>?] n&MbҤIJ[ZZOO(s;ܳ30d:,fΜIss3Vo߾ٓ .e˖q}]kAx7)++#%%_4N=TLbzmFuu5&Mbԩ{mʔ)LwޱѣGۍ#h4JEEo6ywo2RvY BP( E;*D,a SҲS OW duuu?l߾yGYr%YYYeY٭;"sN(P[[ˊ+hii3~?Xx1={٣(6l8Hg>FAjj'~z^/cQZZJNNn@ MX~-,b ;QZZi(MӘ?>l۶nvpӦM466reѢE֭[Yx1oN8geÆ f&;v  /)**0 _KVYڵ력~zjjjbڵt:Y`˲ڭvYf p\Q]]}Gzz:UUU8N4M_dٲeϢ(V_|+WRTTd㫯֭[Yt>vZrssRR^^k׮y~t|v lڴur,?6{nܸo[vg޼y,\XR8۸qcE!p|%K[nO?tDYY=$---TWWۿ i&?ӭ[7;vIqW^yv>UX 6"ҥKimm%55վ`0H}}La{YlIOOO>QR( BP/^l!SmnJub~^\\/?\Ma&K3g>C G3g4hg}6|̘1C* BP( ~ /P]]E]dz%C/Cr #bNOL4!Z&Vc9~{s@`Ri ;`OVV4ͽ IIkC%RZ'DJN?~<{/OJ `ʕr-͠R( BP( XŨ,YEvN=_3JIDiS今b׮]||>8TTTР2  BP( H7ŝFx)E) BP( G )_'].dGedBV!щKهq J#BG%x/Q ZM::ޔ_ӬoC-QaO@uP( BP(Gm;,х09Ry3qL% AGWntJa4D4!8:^_#dj: rCZwRO!&{׶N"#JI BP( BP(C9"YC~δ[ԓRnLgF8"ĊH9,|ǟ×Н4I`K T=A;Q r d+28 BP( BPWVҏ?OV!fro45 7Ns;2_#+"#T)`dq@ 2$0{J$U BP( BPgat54Bn/TPIZw/I4 hDi03r]; ֬} ۈ5oͫ"@\A,z j(!"PP BP( BP(0JՄZީ_RbF0H'TD*9.~uORW0fh'p} oƊ3!]OsVq ^(Ed"@B`! 8A 0BP( BP( ׍!-. n' u/!t'H3%hQ1`%ޜH+Τ;SP:=[}?Ǒ4c4'g, o.ͻ>!yxr p PF5B}@ F^wjR/ JJ%hrZDX$% RA&0iD[OJP( BP( 74Սh)e"cGCi1mkfotK]>*W݃(&ڶ>gQp.V4ay.h+_@ΐ0~Bd0h[(6D`=d#m`~fam& -l2$ZM-~BP( BP|{q/ZɣwMq̖(Mr2tSsP+B^i"DL:g: Wf?BhH3JkrW Z*`5.'`xs1Wm}lmӺ}j-Q HBB6NfH2&l j+zEsޙ~sCsW(((K͋u4e蠩Ogu8n;S3;Tzs3lm_p>+CѝIiϽ,@Rd%Xwmo&eM87GhU`O鏻 #RČ͂lػ_F-\ -7hNHk~,m<LDw =O`"W@%?[jE)(((KmK4"!9k@ -PHݙT?#9B] zeܽ@hDy`kbRF1"~hiDϯ $7?,k%1X1D}&7$gIU2Kn9REQEQEQE!_A@Ƒ(0G^ȫ eKaS2Rɂ8Y)Ҥ vĻyg{Ie-ϙX!19y8[fu[Y7gnEQEQEQEQ>gYɌ#Z\p[WEQEQEQE45((((I-E((((ʗL9w-h -{hm頳56 s׹۔DUZyˎ6ϰ.f1MC](((|33%yHOM]b`Ct2#!?܁%L IinLνGr&hN\l6)ӖWS!5;~I̘1*֭[G iٸZ}g^F"oIZZ/EQEQEł+~Ou[;R)N 2ÞIJ̻"BFMYtM&Tmi蟯0hz (b DANN O QS&BX:| rrT B9$5Z,1az^Ih,D|5f0\%QXAF! ,Qoly\-۵Z3 mwҜ*;〝N DQNԩSݎRnvoСCy>|87x#k׮n9m>w϶?mvnmlhmh4J0@CR_řřk>S#9fu.44AÑi< ^'1 ,OP`!)`.m 2dYYY 㡇bРA8Nٺu+oc7|!%%% 0+VG=Yl@-W\An0 CoeN'w}7_xy x^1k,JKKYtil[@ @nn.]wrټy3 !z%%%OAA/UUUD"O΢E(**4MÇs]waZ={6-"55i!غu+?Oqx^ϟٳ֭RJx뭷Xz5NAxb;v,'NsFmFVViiihƏ~#nv#v;^ & `8pYfv)))ػw/keΝуl ۶m׿u_X`n;Vk+##~8MNNvǏᠼe˖~\Ng-"dĉ?3grA}]|MV+v]tTEQEQM[n @im7! }Q`뱀@.@aIsNbO3_򀉽,O`W,h kk:Tbnti`& E"@&%AD0cZ>uRpAt+̨sR2k,,YB~HKK۷Gg?/Ɩ3Bx>}:xHJJbرٓ cHOO';;l^/٤~{=nfNAuƎˀx衇رc 2`0̑#G˙8q"uuu=̘1㴾Ջ;SS3JN aa\.cǎe׿ѣo$55FN'ddd_ w4hIIID"5j%K@vv6IIIX,ӧO,?>wu}0fba2rHRSS 77]rw`ݻ7}fiUNS׋ng„ bۙ4i'O$77?999I  ++ ;5nfL:#FÔ)SVc IDAT7s=ڵkF'(((.Y!eEH3 WaFؓc,=`n'?"ÖMr,R}t{*e&ʷN`n[d$iTEޑ$Z@Z`&0%b>?$%%QZZSO=ņ (//HX,dggfСl޼;3|Myy9#F?t֍jA}Y.\HZZO?4K.%==ݻӭ[7NJNN.?<ݺucҥL4ѣG'{nN8!CѣdѱY./B544_rCLBijjUJJ ]vSOQUU7AEE@};w.ټl6oϰa㏁tݻ|r^}Uя~رcILLd߾}g?K.ؓ/aK1cfp PҌ"6Zyԏ&fo).uUb.1H"A Y x+P Ln42N+B.z HFLl. z /ۋC t cJ} raZٵk\ tw^СCBxHIIa꫱<ib:3 Xn~\ѣڵ+oϧo߾L4+W4 c1crؿ566|*L<}]z-4Mc֭x^|>Iff&eee]m۶n:f̘Ù={6[lGUU?qٳ;#6#PPP@߾}RF)++c߾}~iFZZ\r )SzDRJHrrr5ro7ogɒ%tMB۷Xॶ7R,KP(" vubrrr>}:N p$ BB`&plqر`0HAAo&L`Νx<222;?{,\] z z~g\mi&^//GfϳvZL}|tV$Lc(>#ʊ((rd$u09F D$R=,zG7!h3 hЏЛ: 0f uLӌ(ZuB^ϗ*;}XH9M Qj a͙H՟KRttWwpe2N"JdQK#Twk`;Yf1h}AdD->0;b]6Y2HAaO>:t(7t^#Gp!֯_ϪU:=QXXfcРA'OiC%'']𪪪*1jAX^/PXm˖-qWj<_>VܺoC a1`f#11p8|Jp\TUU+FEsjm X$''sWPSSn?hb|W` x<V+.ÇNff&6jW\q- [s)))j{nM{nƌH)ϻPu0n6 !GAba͝~AsiӘ3gÆ _~8N?{DZc((2O:~q8A&0@[Lhw ;ᆆn gHI&9oPw˗\U,Pm*tq$M){M[An݅Q̈Qwߙd\4BwߡPgD(07G%r7+ܝz!%:4@VK+K%u?XV?\Lȑ#=z4{C}B***o ࡇ"77'}v|>P4ٻwolVP՞={_(]fp8LBB>,7p,X~QWWGAAAl־۷g[Υu6J=HJJ4X{m3MMMnwgIIIhFMNgL.]Y|9={bAβldff;=m*%%{G>}۷/vGR__ϤI>}:|W\5k +st5v;dѢEL5 QMо 3]e:>1B8R; _NR^3}fGH96ðo6)řOA u&Y\\&&, =wZ6Hbl (55Y/z d6D?`jTZl {[}+''k6V8|8N6m֭[:t(^{-o6|L2K.;wj!hjjGyy9| _=G&//={2n8ܹ:԰sN7o߿?ƍgРAӇM ~q6mĂ ׿Nqq1 l6c픕1l0Lcƌdee?|x<1bD林*vŬY5j[nĉ!0 Ǐw:]0 jkkѣcǎ%//h!u x BtޝH$BUU'O544XV8ʕ+ILLdȑv, |>IKK>|8%%%fţ]M$b j2zh4MѣqWZZʰaXt)A~WfaZyw8r;w0NWEQEQET* NgI葋%dMf؃fOAaKQok`]>z1e BoEZ3LjN'5n^0Ih;S "zV a@]!(05,\ 4qY.<%! پ};;v`ʕKm߄~_1uT … y'FKO<~8p)% EEE̛7]ߪb<:6 `x"uuuTUUQYYVX,sXAzZ>cm;5eOɓ' B8ydq;p>χfc͚5<\s5 +"ߙ[Bgf*((\pG^djj*HIqg%#H9kOaak; 6WZsKXN "Ø0 BkO-4y`\`/ *&8s\kҁpQN$S 'm>q?[JV6XT-Kl53p8u߶w&=m;[gXָ wԯZ_wjZ ?Kn;gkW&gjLΞ cϘo[ `ܹ?%555v.|ɸ +((|Y\{0MUKbO`H)rQ4 ]uh4JMM <]66KQEQEQ_n'P^0\P`nYMTDPQɊ+xN{m ((;3QPlӀ8V6A<)xh{oiH#8-/kFARZι_}VBFu4Nˤ(gNqTEQEQ# TWM֒@Aݢw3M&2ELg0H^HW@s{+aΝٳo~l~nۤҫWwzy(((G= v'Ndǎ|'ڵ;vtxmӇ?O+\uUg]NQEQEQEҽ@5$ΞIzyؒ8nGXZHt'¢VpD~h.J~}M 0 4ȁr=0x`~O7iҫW/q(W_}5%%%X, `ر7P( /cλφa0{lzASS=Чo]p i3 x9zh@=߿A#43gNoo=뮻ׯ/7nJl6㌫x<ݻ)%vwxze]`׮]y Jbf)((\H(&g6dao@-3MtL0+<~J!4_Rzu?;|݁طo˅o|Ç9x`捦iݻ-[xL}4>K.?3o߾X*++cϹnΝ g a{B֭[ڷiӦ1bvڵkW\q[nk|ٰaٳs ޥ3w\yX,8N(((EKOJ뉕~O ipq?OסI̘tHb;tV Rb(`[osr%p @~ijjbذaՋBƏbaݱ[]?<۶mP(ҾVagZcKԷ`Ō7m۶xZ@Rv_8 /۩" v83mߖ,YrZߤ3M)e>Tk?nw:H֟8xmCu- Doj{g i\.^>A(v[na۷ ç՝r8 Vuڕ޽{sM7sϱ|rl6W6uRQEQEQ\8`w%^oQ^iF;s%%RZ̰% ^x.~Ez/pWk Cmװ=a_ mԧۈ,D`[/~׼'C :;3M6_<, %%%wuhGɓ׏sC;#rJ?Nii)k֬a >]vzj9Bii)??ŭ)--_~|3uѢE=zl_K:[Fq^|…7Mo}/ eee=O~ر;vL&L 8x`VsqQ?{5rHVZŮ]Ks`ر[G}RRR֭[uÇy饗x^,X#GXbivƍ9~8_$33S}z<=z46V/"v["RJ/~)..7}x(,,n3a?~zVSEQEQ @U쥲h'MuxihtP^3}$"IыpNBh.35D }+5DO.0l|e D|/tRz)nvzykۥ_J222  1|cYDUUUQQQ$$$a$$//{#F(..b655QZZJQQ>}ִ(I^^UUUTUUŽ_N$9kwv˩}KLLl7K)P^^NQQ08qEEEQ\\L$aڵеkW͛5e:t(sСXa0j(ݻ7F{R׏9sPWWG]]}{aʔ)@LD\.W,cbp4Krr2%%%̙3/~AfffXWWGII ڗ IDAT%%%>}(?9-"-- ***۷/K,뮋k= >z/_ɓ'۷/wu< v3NQEQEQ'e9/c:;i:ׯ[-ߌ5GE{FD@'\.-(,@ Iy$8*AրUϚЉ P(ѻwo=ʯ~+&M~ z5s1 ӦM#0j(سg~hNZp!SN駟jD?~<;gV+O>$&Mbl޼zBP|k۩YUUO~LѣGD"<瓟ό3`x^&MDFF>+^zqI{ؘ| YvyѤ$%%K/1i$F͛INNfnO'Xd SLihh`ԨQ\zqvΌ3Xd eee]G> G}}=> 'Ndܸq/aƍl޼9gɒ%5~Zrrr[V?EQEQ%vptM|!8_Iho' aBp^uh*(-O_BTB D=":0`8" ȨDh۽{C޽Y`'N7wiW'¶mۘ3gC  ++ ]ٴiS,<0MrkBucǒ˔)S;v,w_ug-޵kWkD[b?1cFI)ybE煪IF)9ora:;mM36 ,\n,\.MMMlܸUVw^睢((D%`%C\Au|k8f4񽗦avK\x'$ Ν$@ 뗓ֆީilpsDSs >3J?ZS@!WK =4.pc˖-l߾UVѳgOn6ƎÉFl޼uN;F=7o=z'OySJL|8TTTPTTֶ޽{X,hk0jm Dbܷlقj3233O DUUk׮駟2zi((r#RjtÍ;79S$+p&+=?~C[jW#Šh6N PBٵaܷr Çgɒ%D"g?uV&OرcILLdݱLp3g2j(t]'##Պꫯ.СC̚5/:JN'7o&vN'O̽Knݸ{X|y~: .d8MipI( Wqơ:]t.B --{={2sLt]gƍ1UUU1`2l0wߥw瓔;OVÇ'OOSO1c ϦMhbg?Yܳ l۶~EQEQEPY'aF;io&] e*f!$e_P!F8xtszZu KdQן=qavSXXҥKr<,^l ipӧsut:4 NRRD"\˗3k,ϟiP={zj9B^^7x#CEJjj2uT.RB!u… Rزe >ll3f&[H{˗Zk!5Eaȑ#TVVr72lذv}gTUUaIHH`ٲedee1n8-Z%FYz5nիꪫʢիWB(p:]d466b۹[q:A֬YO9;#t:1 9BSS6l8kZWBPZZ<ĉINNb ؾ}{"ۇka޽8q]g$$$ի{Sgytl6 Z*444yfilldݱjN8cc:X"^6Of )))ݻwϒt+++c_|"FARR)e,m.S(((We s90x'yԠr8\r%3}t_^yϥ &_nݺtR>#u2EQEQE9Mzz:/\s5?ܦNygq[BoLsr{Y,}Eȅ[;ڈXK;:HE] TJQ/i 2_ :4孷^ZIEQEQEQS_ .vׅ|^C@AB8ze԰ϸT]W钚JUm^d(((;!( 2?ǷO@pA .9Nؗ{ !(/,.L (+Ň[m]7T㺱7@]pe-dAޝGuw>{sN͓Tek%al!16Cc&}stӝN,:+tCHu@;  v c )3йgEDDDDh11Btr5U%KXi-N ) 줣cTF)MN=/^K,ȁeVHe>= ?oT[:970Sz]:ㅣwH}_ߐd?/yfv'%*9 xYƏr;ȴ-,`l׏Cy.|oeϞ%]&,T$X N%K }%z?H|j. ͳ)RZ9 O3{TՀJ|н#d[f+nfJj3kz 1X v.~2rӲT'ـ>KS^Kfpѯ-dwoU lMEDDDDDDꈪ"ѩ(52.f_ͼ5ȶDGJ{=)0(IЙ}3ӹhLBLK>vŚ{Ck -3N\69`p۞s = mx8$_[GC*0 4R H[5ۚ?g0}u'kac>>K7:"m4_@=C5Qe h]B WrBe?7A`Ʈ X7ŽrMXʯ{&}z`?q^^w!rM ]˘lK~܋ujHe+=u70sZ8]\\>I$b,oFsa܍rݙ%F9!j9ccuϿORb?[8:࣓7O^ϡv 3Ճ2؜ɶאem$3} 3,jʕ]Rdp5}D wg|h_^up'k3<|O~<΂[нʹ/lU4 _FR=F N̼r9ӗwc{ s;c/!͒2u(o$)n{`X<Lʢ4y qWL<$ \< ȎcF}Sۡa:zl,7lAk;">|xTqn`Y-I6ԖTUvs龤+gҽ3`x?wQzrkC]d0aƄ-4-jd<4Mk0Rd,cx)TfK;.2.JuT[XNؙ( ):˚-u+˺hDx?ecU%""""""f%*bg-exK]/z'  if~C/qOFj2?Qi-` .r1GlHgZ$aX#/8sj$ ۿu~<ɲ̸9oelﺳvöӢ F.!Jz@G #^Wq;Tv8G$Iꪫ?ҥKinnfbb={˘yXx1A0cƌΈg\ 4x 7o\]vQ.S\.gܹE6m^x!_WD|/~oh k6eѢE vڥJ""""""Q$F@qd'k 38 VPbQ,wIjZ~:_п7(@򜓼-l:G&%GXʟ$kcf?UR߃y(^׊JBTkkꫯZgglڴݝ.f<#gʕ|3aۏwfGJ%*סG=1BvMMMG3*:olܸ̈́z.\8yx衇xgrd2s?蜽iGp7B6.IK>`H{Ƿ2ĥa^= F4K( Hs9$%mX1vP;y/%OP :/5`sϳtRܝ16o̟>_y~<̞=yQ(x'җtx&UP`ռe466211wf8;Ú5khkkT*qF^|iڵky; ̏~#,lVDDDDDD͘.*Su_]z ϐDXaa]K[S} oe暏Q-04xB~Mݫ8kch_`.z͡:Tp7K``ut5C56697pw}7K,a 6lO>O?ݻS$Iꢵus8 ewWfŊaH.㦛nk宻̈e˖q-zjZZZ03۹KLss3r 'K03* \wuX??bǎ|ӟ殻:\)quױd>O1222w^^6l5\Ú5kK馛-^}!& `ɛ)J,Vld)m3 Bf3אm_H'N2ͳiWaaֹoLS ɛT=KPH W`Z:F'F+`WP|{.zzzk{1vK/Dǩg-]y###'-E>qUWO|dYR{;wr/w]^xYoJz׻x;I[[_yn`…|AYbsa׮]T*-ZD1g֮]/jKQI044DZ%qT"""""f2aaldqsdTuC;~RM1}APgI蜵+]R^ =Ɠ2p8 Mi&&'׾4v}.$!I0yKEDDDDDEѲgu3#Խj4uNM-EE HuJr ZET*{o0a3v8 ,ȐLyq*mQx}f\ H70{x\շT!d?}k믿kײxb֭[+ Cgڵ̙3swr[RT+KN:[TfGR$ (JK_rF; ,(_=qc{1|IoX""""""o*Bvv0Jurk-*'O3>&PbN,8x"¦e`cer n.a?$)`dCxR<Ͼ@;ޡ! Oi w7̊+f OM7ի//~m۶afr9fΜɃ>H[[fO|r~W~ . Bٳ~wlܸp`E}}}LLLazzz7o_2J3RpPe< ,< FZ[[y'xؼy3=fIDDDDDD(HD$". `v,йN:B )SP>(=JR‚f{p1yVg:D1d!>TqӦꂩ?0֭I7f[NV^׾5Nx |Aoŋ(---n.>>urrJVXWUFFFpw:::?q}qu}vmE{(/̪U>GPZr=կ~5Ui&>ORT8p:P"""""""oQKL2>wL2RZʃrڄ/jeOx@qd7An:Չ!IylP- $僯ӫi9Ic8XT+FL8vC$=;?VTصkk~]ss3_җ?q.\H{{;< ׯOOYjQqF^~~Ð~l¾}cdd͛7wZQ.-[vd2?3pw0o<,R^*Qeݺu|+_ᮻͶVKJ;v译iEs.db[?%{uW[g/Ovfk k3E2NR)g/9fDmM/:P1';˿KS1.S;4sh|rLex;bjzԱYD89³cWks""""""G&===q̹0!Oq0DPT`AsfAvi˺l""Vjslj@l =L (p:sG:vۑɝ3!͉?m}N\.ɤZԔ>|-mEDDDDDDG'PDDDDDDDDu"""""""r&]"C%gXPwѫ9;Sҭ, ҽ TIr?g(9($X7[J8/hlm{,O|~;ec$)rl6q\?r qTs,YR>xWa8#?+ »p©"$/;O/"""""""G`VcX05bAZ&g1$M3I5OC"f]~7Mo??GEvY"K̻ h]hAZ#|fA #6qf 9A؞G%s̿Oiw=ac9"I.d1OpaY>ѿ4ˍ`[[X ]'y3 \e߂≈ȹt|'J 8">0 QBӉ0cr8kOoM6 kXx$Zׁͱ6#Xfș;TzD$$I‚ fhq5Ɠ8NQ"(9M}v)~1Zf #ŝh''_VAJoe,<ڮv Q9 /QW'9 ZDDDDDDμĝJ'IThB✞62PSyݏ@3{NN .?bLNqj 9>aXa^k49C8diIE6{'çX4̼5Ąv0#2K>E5ee7V'ƈ1(f;?KF#<æ> SXDDDDDDDNhk?yLw2mfAˆEӱV&ao~ M^GL]gGw Y8$9qsm̯QBeaP}&'W[m0TYT""""""""ρ=G(:T[ʨ22dK?fC*> Ϟ_#@ÐF_?/"""""""rLPS]d[ }y9\S;3Y/DW%JTc+5.am~L ͩ[IRa&K_ѕT.VSuc׽Q6Dߖge?giĘau(J@ATNw, SPIʫznvots O&$YHJY%Y6 Zy}߬A(I\p =[&wKd.yiSxr!s$-sGg:aq.0d NE+"""""""疨\ӘLi$t/0 Tm,l9umݣL/1 $:6 27I\cϒ,xG4v]Dyd'MxR-œBet DDDDDDD r XخfQ\$sf x\26lIڵMp(Ɓn <0P]DDDDDDDt; <0T~#NR?s¬5!ȶT l}O)oD*@c >vFڠ6l> z'~a At,6UH8ՇФ49 ~ڦmlU^caO0ʴw8sD > R <&7+ /@z#v |4~>${`2HA:0fg`:  {~~clzv,rA2൙K1OvB>UpS?.K mx%TIjቈȹes S&Q\)Q)ML] 4q^<);6,2`gaZC]?JDDDDDD4 'TR.{6`cc\I("ZYX86^]&1#jjĩus$9Eg C$#X~!P>u;K*jGuq+i㥺Ԑ\$vŘWۤ9Y^6SPC=9 ȎcF}H:v`x/j꽎Y ǷՊY?D ]o״-p:̻E#J[x뚀SA6hjmFx, }gE@rc]` $/߉iaDH-_MQ qJ&;}b g&ՙP HC+KJ^'P1#jK%"""""""M5{1};<1Y0%M=$Şv. f$$hLJt2c?""""""""gHʅQEDDDDDDDD^ -'"""""""rƘyZZ[64㸆RDDDDDDDN20-WeQTx$HʝR6#9+U$9i3# T3jQ`2gv7("v>G\P8!2`9}uLy!M9%)o^ݡMo֧g~QT41:oƓ @qAXr 7| nQ߶4BWcڡS5xJibj!mODzMdA@m5]D Lv3E6C&g>JDDDDDDD^Qib /aom f;̸qoca dg19i' 7@ADDDDDDDDn͏5ZPA@pOkNKhvR[j?!%8 ㅺEDDDDDD􉪕rar&~a'>7wL qi?s?X5-dA-4djϽfyn[d@^'y""""""""A444Ʃ;Cǝ΅djK 3VE=F •M6K/5E^+4T; IDATZB"эMV#*_m36JDDDDDDDNQ-a'4SFSY|WXzPCPr@L#|^b$lVAxaP}Iv:Q ˍIw"""""""rEg x\fpˏ~+< W.^~^`x`݆t3 }N-0|W2@sDa B;bEx[NiX RmŖI RL $6WOz,pIxO9@f[-ؼ;VXDe߉YcXհ~n颖R%ΔS9-J.*,:3`OԋNhtuMc|dn6È79>\zåm'k( ALGf*)EDDDDDD CMG<{, d!~>u#r `{^k [އ?F6`$[!y"c P~dh<_=Z4q}<%N-EDDDDDD(}(Mb65-j!LpXX0sFe~P$"""""""gT41rHZ%""""""""S/6Q͐T3~u [#5v'ajKu^w[90h0;i3v8jha*᾽L&3B$8nF"hF\T{t%VU&ծSoR*p&+{6>F0N\rz!׷U 8PPWțOq;x6OXRa؊m g?YЀe: lĀajqedWxvAx\nXxx; >$O;/NmW/'^ Jfk{%~S,8 Ld~TCkcAHS̾_8H8mF0o6µk?ZwWG,ˀT x  Vbg4p xc}Ƣ,o]K4*W?9Bq? ULo3wrt|lat2|*BT"""""""r:6+;ax\bd/ ?Wzh ,8./Aщsv5+ Mb\ uޓY6[a DDDDDDDNS@a@D%Re.MqNˣ8Je LihAG>Ag|{R( '8 *vj'3 fKỳ$!X EFj\}1y6n&`@&I@?z^SUãó&rΩy[oV^m[rt}4WI?Iq혋<:f@Væ>^j3DDDDDDDεf9'Ejv]NkT 2`FMSٻ0ɮVVVf{JeKImlc 60 F.5=~'"""""""H?k(Xҁ%V_)VNfNlnV VXX[sl\c${ OPVjm /:a rQ'3*dɿP9~a[ ?0?Ȣêwʼnlz؜d?ÒWs#O09脽VNXԺAQ'bq/+? W[ >ZԅR$"""""""igW-3,tw0o1ԯcI|D?gXu!i͖sTO? ^2AyYi& TJKWŒʝsK򵂤΅T""""""""-hDDDDDDDD }W Y"""""""C_9I FDȀh);fJ.Gʼ':sk}YriZDDDDDDDd8J)f8V*rRmmeYv>X2Zj]䍩ԺTV]K1HcQ}J0~Ԑ6'%en@yFZ:7{X`ăUӞ]lg+%~A؛@/0N5l"""""""-@ (g՞bsҮ Mf~KzεrhDDDDDDDM`O󜞍7ѳ[h S{MܣF[DDDDDDDdP0uya6YۮRY}#X&QyfjF©NQi2mH( L^&vEDDDDDDd,:+?{.BUPW> 8`aw9yO4 p;p{(>}gvH3*iw;>ݚQ֡a7G_Ϛ= BYkMt'3Cxn֞ˬ2 b 35kX5}N.tVP]/8y(Y@Yʩtͱ'qorI g.Kys4NcFN)N-rb3&!sd{Ut?+)VMN|Сs| /"""""""cAj@eSȦm;x"@6$ē+wFGp=CC||2ZQjYPafN$r̻D!"""""""΀@23;[޸ǛŒf^4wە JmO1n #]w:M:k[ŗ}_]y_pTś9a`IϹˡ}?@G>ƞkf>I಻G.țP=ۿZ^س/#B;?L[OXJp5ir8.HȿpF9ks' .BR`~ܳK"&=²X z; xCmלء70]:i1zmZ_9+6f˺!lzʵ?Fۉ,_e c_9/yS^-MO0ԇV]y3@l&6l!#N||8 nm@8j/"""""""cj@=X2EۺNンc cb;9`#+oF2m2l/ jH>n$܊&όt`6BXoEqs0A'<Y@ Su8E ^VmkYCKf#mCN$@r݈/8ql ȿbEی!)}DDDDDDDdK}3i:\ZV&xIxAn8Ob)l2bű`a :a FɈ_el߶dLcISxųY<`sq`Hګg*ivSYuAY, @92>Nsz؜Yq^l ! ; 2 4#հU_. Ρa_du="""""""8? '_N*`+wqHnesk xu4ọEO@yS*7Y5}`qI;[ޏ3H\E{Wf=vʙ #A |ºS┻^H?f$60I}z{U*Uf{"~Y{%(878c[?Kwvo\Y3A*_wO\d3=Nc۠K@uB~]z `$7 ^lY`=7U#{ :c/K}x6gl%+k&"0@,}a ^0'`!&g s kmƊS@Ȳ`e4rg/}Zf%qM{ͼ/u%ZI ܀{HE% @S O9 LSMDxnFZl#ou8Ȍ/mx,yL,:J[,6PխP PϗG$Ov }[1`uak=F=ЀH߫ ŕ&N,5, tTy'%>X1FR|pAr]΀Hnc데^|낰bT*DDDDDDDD0oai'TIfO=Ʊ~>f[VL86`$0k /Sļj9Zb[_'2։y'ר59/Y"""""""",0;> aA!u'Ҙ7+,aL ?Ocy6_P5б,gVzAa l>b*zZ_YC5DDDDDDDdѤ0+qv 駮ܝ5;?ĆImy}'+LnEdk(; x7EU #gd84 DDDDDDDdQ3>6o<Εkc!z ?At?B脰 ?)z}zr|n HF5.~5k U_ ǾSXRa۷w~P]f l1OrװrUFiihQğ,.T~6vH!%n1%^jO%ӮCsJ]H`m~{=?JFת<yQO3 ̜~'YsVx2^'8) $ξ IDATuk5Xx * vXZ'X0+%ƍm@ǡYBvmCj쨵͂"oW-̨T+VU2GN.kI{зv;// 7EHo2H՟k=Ul*G(|ekiԥ(V%[,۶%TK$ :PT˽ʒqڳz-Y[uũz^DDDDDDD^:qlƟ7/sz7az6LϦ1s";tʫXva5')§ֵiCA`U }Na69 a#bT"{],RāT}mYı*L2f> yc*iן27|0,do1~ oB NcaZ{'J' qXvX锂?ِctszEDDDDDDdqbj$3V9zC_~wg8e͂CC_Zkw |L,VkĬ&"""""""" .8CwsSEDq"t/rtuq&^YϭaޖZ:|6mEZ}vቈmuٹ&5J@B%I;&8CGۮױ`>T}S+VI[^M?.:Jg'0zHmӧX uЉțX% tUӶTB(&k<|NǶJPeo4iTیcRvu?=Z@>}]n:ۼO,$6>v?͑+oF ?Pw5F!#Cvfz)і=Y4iw6ej vi̜|,HSZ#c/"ͱVl$`k |8 AQaA | >"""""""B&ܙlv\8!-jD }:4fNҷ[ \fv9N<>`k]mFh9;~$7EcK=g;~Xҳ}߅+o6:!l2|c6Ql0l_q/. ${ m˾P .rP?+HM:=q[al0<6cx>gEr+ Z8 C QXlQ]8踶މ22f$I, 7,劂Ber[ԁf KBJYsTPrH/٨Rj`A`ef.OB|ƹYs]DDDDDDd~ 5wSdzlhj@y>7J>< TVv2;tlF idn/η~iVOBK". hEDDDDDDd}L 9YV=ҷlV<УA"صNC~W6tGJS_bj4TYi}vξ͌[IQs2IG?IG?v@s1M^ f$ 6g++jрx![F!? lAuCW^۰AW)V6AW&ւ]Z("""""""&=}96nEwE-?É{ 0^ꟴ VŒN|G8~!#: V/ǿvՋpl&!-/|Y8 O~Y1ji|N )'eIϫ anl%&Jοǵ WEp%"""""""DRw'o:ODDDDDDDD@kJDDDDDDDdi޶\3֗iӶx˒uK-#."""""""K#yNl!J<^2rbl&8f޶]1)[06 GPK@E17:Zk:P("""""""R70JINZw#]&=h1ݛCîu'@!,;igw?07=%oӤs3w>Eǚ1 prn,oHj&>|ذ@ćLDDDDDDDo6*^Ɔ^,=g =n (KJtYq>):h4fi6f؜fט9(og{w'.7@mc79(~M pn$i]k"""""""" ,oV 0Y7<ݟb8kaCR]E67™࢚Q+AvaZ7!xC v$Ѻ^8įPTvHňG2tTvKDDDDDDD\ژRy# =)&"@RZ&^e+k66@[Qbr&N95 : *:ؠvZb5e )/NY@imN1=>d%""""""" /d!f} {=~0£gMXHWL,3[!Fw|50E=M[m϶;]xk" 졈ek(EDDDDDDdH=f9 笽cPƒ*{=bfq= qUVVmFAR+1l[1NVjHn2|iea6="""""""x濢L~=fabcoL2u/׃1{)_@cʚ oN|bSXkT-r'٢~?qsè, geN/{R,0t߿s B7gW~BGg0w;o/sTZzN/^W"2w9KYx ƒDJV{iRWT[_W4G霞K,BW ( UBcf8VTZDDDDDDD({vZ?ZYۦzKb=zZ]b-t 0;9DDDDDDDdIIp/^RYH*w@̏bh3q9P]sg&X*`~,g8O[E 0Yr%+ٛZڨзz3UH:7SK1'i?qnJ=lg82cE/!#\gǝ7""""""""oR6ӏ9][>5g㻨nÀS_gXJUIKXH45"""""""n٘cztQ|;?̚= >{βY%""""""""*]SӬO1/[,0s GEϱsRR\ .W Ʌ]6"""""""" ,ŝ$R!0l8;ڷWnN'ύq?d+~ZÀ8_8~A!#y*n#Q @#F. P?xй"ѻf]k(u^` ,1p-8HXg͎[ԁ16#A S6Bz{.^G>1Ǐ`ی{FVtwY\#O Ͳwq잟’[?Y[ 5+wVZSa{!}O9CvÀN|8;Ŀ56?jیx JDDDDDD[ݽiyb"ϡC|XV[ρ1C9`QH-8Y 8Eo-i؆ ԊߎK7)6e]im/˴u'GBYl.1!W3c5c SaEt# Ci)O[!ҏN<Ȳf%2[e}^rZj٢7ta@hUz-9t?t6>S_ ^ 7 vFz{ŰӽtoH{=54'05W3vA#u[|+uϊ~l j[qC ,V5Acqr.fNgN@xi4"0w` E5~ 賭XOQC_(X."""""""ҾCԥ1w~N߶̑)s'܉/0gg[> M?L?Y0p>tI{mzИbnjdyE43I+ YD̬X;WȽ/gYn3qc w&ܟLEDDDDDDDdLN9>a)\8,:dIIsfϧl7Vfqm[/m/VB` 2/@tUr~,-pi޶$!Ժߜ;x,,i Yl,Ij%bfF|ɳBBk51f,۴n%! *iC:6DoYV$Vmjcnvkŕ g0`dũwl@cؠ_Y1`$ g.ǟxdn-@|̡ HgobI[ޠҳjMB8wՉ,7Ͽ$ْ1. ~ʪ:g{yֳMgJ.gx=ZW]$'Jy'GO\;̸췁BDTvt4|*G: h.DDDDDDD+>z`|sYC%tzs8^=iB\q&j>5c@1OTm:| 4yPH]EDDDDDDh&1">/p:u4*9mΩ]asYcw8t}`蝆̀^75_p5$(pcf<u)Q0xVF+=x CVd#`Aim#)v8Oo"zf$m3*^XlÀa0 Oݒ""""""""GǏI1|%r)3?7:i7 I;,{td|>Y?#<  :a YTN8` X7ىzJ:3 50c&' 鷙~Ӱ(Gѷ4;?Hjs:xn:·ܻ e8 ̄-FI966'Ag&"""""""g@\IhF2B,T6@\^2,@&ʻQ;WduƎy|s KK_چJߙwdce"""""""rf8ORk='"|, o@(ľG~? tNh z Ǵɗ8$G.4֝d]'$"""""""gLosYpY0)E-.aq%,r%,7W!݈FSOI?>e@W}t `Uɩ%?3:SEs#""""""S; =q18i]G"|׏&""""""?ԩ&i؄QT f'8|cci6F![%]m$2Zٯqt}VoqADDDDDDFʡJV1C:TSdk;"|C]mQŭAcuШf2āᔖ׬;$Sg@Z?-g9ewS 08uџm GI6MLasp:PtGa 􈈈OQPNǬ~vj= ;,jf N,i%: ш.301N򸓮&yc Ů>zSByBߢX BDR9׵3o@NHsLDDDDDDD )8I}h쀳@\7TmWh@ j#!@?Hyo5fkcY U)܍Y87I;$ߊ7*Fc3>sBt+1H |ӱN~p VAFV߰N*73zlA @Y75S2wsOTq`8Q#è.\\j-cK!I؁ik6CSO3v[Bt8.i+geФE-M 1bJeA:#,tnsz":Oua'-6l^}k|}=DDDDDD\P6gO 䅈(j}DhOI1n#p̦\dDmDyCںô@!ZI[}8Y^QxyhmI{sO׉ 98T,jc`uҧ8YiY}iW~ᬇ?EDDDDDD&maīkh;i"mgm9ćnfpj"zt̾Ιo=!3 ?PIy\R YqtP".:Y:/9یxu ^B# >c`IהșصI yB߅72`gj6ШȞq0uu@؈m3ӋNgQ18IEFN˶P-xȉsvH,[JDDDDDDD4ӤAP3k>qh}T+cm3zyxc}(o({>*poP=S,E'N$Tr`-Y](r o8ı`!>u2>Ng77j =24 קwQv+?@V ?0- p"""""""bf/} enXJJho#xq'm}M36rzIAG&Sq2Чd ڼ_ (7><} O9NC("ѳ&[!bڼY|O][)iOV<,2_2ҷ)=g!1\#Q l> !3bCR]@+ xJw1ń8OT1> gݏ{Yt.xc\C8)UX, LFvq_,{]x׵sd[/vѨWiT+^KB,.M<3p(,gٯ1v#"""""""'݌LZ (:Y+.z_ tݔf]F(|W ҄ ~?muL϶][DDDDDDDD9>ϦQ2)YPb#۽BBf: YyJE[4ϓGw7NVG,ĒfdJ|v: I:"ISXxS콀BnI^ij&O!A?Ѐϊ&HwH!\i VteHt@S5-! :UBag𽈈L1qm|C{6fMR inN .,XT 4FuJ$.0Ǡ"ن""e/3(E@ҙШ;͆hed92U7QViXV3 .*_dO̕+]V^8P9h|۱E#6N2UFk$=ri8[#,4尅,#'}!uBxs ZloN϶򉈈f'5l#漃X4j@FyY!,?$kҾ,;[ꐼD{zI5GGO ۾GP""""""rN0 ,pC܍OS9,wҲހ@E쬟=*xBv N` h`LԾ6)G AѵFXjX1mrZ9X1zI޲jB}=m /6cq6J ŹB6Hh>;r1Y^xbr҉`f.lQ98V"u<j໲8Qm',qg!Hw;fMre@0cNx@9b3,LަGؿf!`x=.fQ J}3N5EVҵ$c?!dYQ_%ZUH$s$"""""""]|zz(}f?"aqFB {2˕Im'+4~"m>V"Q"IH m],*@,Go4$Ie$iz B_Zwͭe5%65?m%.;sCNz+p$""""""?w:::>}4MOŹިQkZ穷ZfB -UֵnjZy=Jup ΁$8 $:I (Y ҇ii]2&ˈmdy&{llG׿J 7OBSŒ&Di`Fk6+~w>?8xJ z $@l1X/eukiͥ;0I}8WdRu0}_Y`$(w{= vӨfc^V›:F7Qp ́:#̷lɣ7eAmKx4cLٝ"""""""rg/2V|`폑P$r jQD9w)}]OE97MR9)cntc> gɂMeH;c^{L}/c>4 җ P42]%"""""""r:^{^jR{{UXVIг_nVV/X)c?)?sS1 }4ߝD8k;hi?zi׫~ IDATcK}TMb@j"}fNJΉϊcM+ۄW<'"""""""rC72k:qI(B.""""""">3#hڋ[XmM[B[;}[)4i($"""""""g{xu)H4ewZѺY [װIk4|w@h>-8nv9@.gucOSjro^DDDDDDDd=f~-3Nvp d}٤]]- F9cB|:fRzde}DDDDDDD 35?Nz|etξ\i.X`tv xQi>)^G3O:6PTDDDDDDDDNC*`OR]@11,*Zg 1ໜjNDDDDDDDm7zJtXN6*^:f i:DDDDDDD/Kzu ߲hˁqx sz%!Sy<DZ9VaA|ǝ仞DDDDDDDDN8_:>BRf򊀷p:gqxZ 4k}0i"}NXDK ?֣A"4^Hy77 d1+ߥQ9{ ;O  ɏpyf'-Əh]bPt{ z.4giWZb|e/yI>"""""""rŇvo"UOg_o6LR~mPqx`XG~?znjHZ':Pc{Ngu@϶yƽϳzPf?5"""""""rfiR:>2G6m-5/NO2F2dl^jx} ئ]DDDDDDDΎ)9fIx"""""""r ϩhy k>DDDDDDDQaHYQ('0 `g:Px: )Xk5ڛ6MH?˖-y'T*sjlӧOgٲeO<~8wP;zm &`cx- j;X,.M< 8b ƶ}Gv̆9٩x9T55""""""Rt5 Z~u3IJX$n #M +˯vWWvxJ$[nailJFq~<|UWTHdiӦc2>>NVV6Q(~u;C{^Ōg(-8m / d:Gܫ(mT--A8'kf>%PJDDDDDD4e``O}S,[^:ĺuou^z\wu ֮]ˡCGx ./ˬ]ȂJGyܹsl۶|+lܸq\.ǧ?iV\ؾ򕯜2iǍǍmŊ\wu̟?'|J77h4O.IBgg'7t]v߿??>ioٲe\wuE֮]ˁT*ꫯfɒ%?i0TR/jd?y必El{0Z:!&oZuڞmPr u?}yk4\q|ӟfܹj5Yt) .//OHke{G?"MŠ3gO|իWS(h4l۶m"۩R}n|>Ϟ={`̞=}s ַzYx1-??=.B>zSmck4̛7 /qFFFٴiDTGGW^y%3gQq!v}9ke…<r-|cc``{1W\qͣZ*i= ]dr|'!v!=9eɁtKPjc):2/ͿC(j^IikNHcsKRarwo|>O \p'|]\z\ܹ~eB,[dѢEK<Od^|WA(⮻[V?wgIp-|J?<͛)'{N2 ,ke~u]y~~+˗Odg>f̤ԇ$(8#?h}37\:𦻨pTBxa}@zǺ5o>n^o39 -68WssGT_"7o?1\J¶mp;w<r<J%fΜ//pB|Iݛ}>R)r9O?֭[_fڴiXz+<\'xÇ1gfǎǍ3|bl?h"֬YßOd* \^z͛7EY]J{3>>׿unVBtvv>U|'78)e6mċ/ܹsQvP;{*TƆOY0}FeZBD:^eF;(Krbf`Uj:!oPlo_O }ɰ|vd4_'""""""Ήd[.^~tt~,Y'-^իWm6mƿbƍ?O^z{.gttYf144<ƍKdxxg}\.GVcJ%_5rd~i/LmѢE^۷uVlblwu>,fƒ%K"n>P.y{V4 1}t-Z3::#k׮}Uv=gVj#LZpj8!E2?;3}iлdjbh| C| N)%0Fg$_Cq'cF|-6HYPJDDDDDDq$T׹馛KXQخ??N-sW1fի !L{n.bJٳ)J,] 0>>=Í7y+Wdڴir9Xh{B%IW_}y,eFFFXt)K.\.C}ݻwL L" t9\א9xM/2 Y177w$xm'!ykN 5FXl$5DDDDDD1V0]]]q_~"{^f͚WU֭[7-˱`<έʖ-[Xr%kŋ={6z__o;O=wu| ۶mG駟fƍ̘1??`ǎ0444Q8ZNz{{馛5kvI/}͛7OmѢE̞=={g|A6nK/\uU\wu̟?N ?0wy'z~,_7uy׾Bo8{//7pr/~4}KU|nϦQ<9's1яf/1iR*C$ٗ{3cqw3X~NdUz[3f`Ϟ={tJzOGC>'ſo5p|[^'D[Vj.Dˁ|g-,'> wرۿ[oeϞ= z|Cb]Uϻ;zbȂ ̙3wgέ[[WUKzϾY!&03 0*fܾ#,B$@ $$wuUz4 &b5y?~@n:sϽuSF_$Z&MĂ p:k|O?=q.r~կ~޶zhm L2L&Ccc#vXΤIXp!-Z?^?jd2,<+ٳg3<<̣>J(ZE]Ĕ)SXj_qBp:t:͌38 lڴm۶>v`"%lڷ]G嬋 8.3/lꙌ>'I>Kߩ: uYr?K&vP|Zri_cYxD y+-zy555h"NJ2..h|Ͽ8G}4-_ Y&Nj*Mƅ^I'Dyy9hロhkkc\wuR)jjj,[5k֐f9Ї>Dcc#Xr%~;>,ro{vm188϶M> .%KUۆbÆ ̚5뮻Q y,Ya7L6|>o~gѢEmuVV^͑GɕW^_Lee%h^x￟T*u`E' C.$+6!"OFHo'96Zp @r 8 %:`xOYC z4JDDDDDD.dY8===xG,ZKWWZ*++K q^`|O:~nv2 -f|r~_m6~??0>N;3gFYn=o1ƐfbcX~=Z@ HJ.2JJJ;hE"vǏ3߽m@GyRN;4ZZZbl۶'|&ӟy̘1{xϷG.VZ׶~;#8 Xv-//f~ i2>v,,W3iO'Dx//h:k/`֓Hs #d>gO{a_`xՅ^^6,%Yg<7yxP(D.""""""w`ƌs駳rJ8 ~s뭷rgO5hnnfڵL4 g?ˑGGUW]_=~N-[g͛8餓8󨩩n`ҥtuuo}k.~_pUW1c N=TZ[[yGww77|3' r7H$KڶypBj8_=Phmڴn%KpSUU 7]wņ 7W_ƍ[+9s&K,ӟ4<CCCydʕ,]뮻nm---]::2 q DL&y mCCCirmfms]x<>ԾѽmrԶ'|]]]1fm{q=JxƦ[4|.z+0D #_y|>g>.~\A[os=wqZ8C>󍏴yk<G>q _|\1zد+c_=Xv XU#&{  xIC=0ȫ{!헿Vm޷j)&2:F٧ %DDDDDDDk=s_$6Wx89}]8h"> `U8`Ʒgl7:3!LaWSB"""""""" ȌcT_v(3ϡlig`=|+G4IDDDDDDDʭ4nԀy#?I>= x'q\E[DDDDDDD ^>WOCvw1ڳ֓6ʿc=Dk8`4R`2/L4@nv}㋈M j IPv(!Wk:USUeFYs9p2I@ΐ_a1{b3;4Y@%}B) Α\ ibEDDDDDD#: \TCx_ IDATPn?ɍ - цE|'v``j d9Ɛaπ)`B0S d-k8`<3rL3doϩ󋈈M%n~KHt?NӴF L:~lqN/t0CߙwL2؜~o|c!%-L+o7ߐ` + 6^1Z`ilJl.EjXs*ڭx, ; 2` [lp3P 0 bnGYQCgWF(C[(L^+pF6-dpip XBw \MP},g!ڒ0ΙipUODDDDDDD^_ TAbK "6r^R=O*@9V|,A3(>0̌Y^xr 搱Ѡ3P1p)SȄꐋ>_ P>=O?t>%rz39F'c0KdSo:-g-v[޼,z`lev!=\r'^6GHŷ3ڳ0xrR/<{גB򀶗=D)փ[X-*9@ww!0 X0@U1rpni$,x/cԑ0q'@8!0.0g MѾ]|와JN-/7cL0k=[ `/D;S k^֩IGJ7 %""""""""8^.K.SY?ё!;ƞH'""""""""R[|ܮTmP""""""",\&Iܹ|s9h4ƻ}&K4O>$` 31Yl ,(Z6lذ_m ,\p1նm;쳉blƍy_mO====y+%"""""""o\.ɓ9H<-[2{l*++l6([laܹd6"~>P,]hmKRl޼U۶qF0Gq+mٲeLZ=ODDDDDDDDOCDDDDDDDDdB)%""""""""J (P.sUJDDDDDDDD&RZLftc)`\6 =H9pl&E&G()6'J G%""""""""`TEDDDDDDDҚF2$x8+hl:hH9Z\h3NX۱ɐe( """"""""R\ϒAa2P""""""""R\OF'<.""""""""J (PJ@Ȅr|FJJZEDDDDDDDDD)n&R^򊆈VI'I%4<):' p!E Jd28Q2RDDDDDDc%H(gSZըix1c  D]]P""""""R4>EikkS@2nϳ$z0wr˗s'd,/"""""""1PSS9C"{!+0mPhlǎ׿srꩧyy睬]ukc觃] O֭SNaʔ)D" \.GWW+Vg% *(33myy;7U'Xkfr9?L|.Q()2n2#$<): r@DDDDDDDDx?Zh۱l:Ɵ,""""""""R,HC; ZՀshH9TXen hH9;_Ck$<).71ҀU ()Z=ODDDDDDDDOCDDDDDDDDdB)%""""""""J (P.|'"""""""" *)i%TR*D.""""""""EV5 ɤFEDDDDDDDDDYr!""""""""E&D Ǫ4ODDDDDDDDI'6?ƢS1i*#;0QDDDDDDDDDX#sECDDDDDDDDgKJ~ECDDDDDDDD߱PIhVEEDDDDDDD܁^>1 k<'eDDDDDDDD;PJ@ȄRJDDDDDDDD&xʨLPQ7hyX!rqk[ ŪȦh,JI%eDDDDDDDDLzp`I))x""""""""R\N)Q ():\.N= ODDDDDDDD)n 9Cf4j@H9p!U4DDDDDDDD蜡±J±JP ()2ɒf4ODDDDDDDDg$vR ~'""""""""DEDDDqlvlNm{k`/ Gf߄+l`sq{s4 [^zilB.x|k l>9^--8al;+,@-f~F (l6ˢEZK{{;Vuz_ntGh@:*CX qF:;;1oq_Kw76  4dg;:4-'e!~ML>E!^W>g455chkkSQfSMk~8LG>ى''-P R^^ΦMxƏ熆Νg8NaH l_&oX>/kd2 7=ug#}t?t9Ɲf2N=T"ɋ/ȉ''L>;K{Cu'xgV|555̛7@ 0d2>.޹9> q6y){'<_q@OO\;<8g]w"""oft㨣"pFZj} ѷ$:xͫijjc޼y/]{]]]<#'?}C gS9LFwcsC}5$U]FqXl>Ew-;/F6v@1|+o `|_]0/1ߴQJN?&{g?ԧoTҦ%8%u XgP _ Fڟ1npBҖ>ϱ`:zzzH${?#@믿{n,ImXk N}CPK,ki uqb o[8c_{mp"Zֲϑl/--eܹbeҤIX^M&k_MMMx<-=bdg\{|S, @4"Gc@ K@-^n<<|;a֭*$w>q\SD0wv?S>lƱTF[ɏvI(qD+`țL.Σqp]ļP.E8(m0e^؍d9줭t:%+gk9/J@ĻVq!Rwu>N t Fi?0000>K3~FFܿцEJnMmVOOk֬aL6Yfٳ qn=u m{l`ar R(xx<;v|Yx<ի9cI$EOdLZyTVVr)ٯOz`=cH8ۙf' ~7D/~rśmlm ^]@u8AL~U\ 1BefQv6NA*iu ""&=# L(?Ovyռ W8ǖc?>0._Aj8̮m~}v[e`]ڱmly#Laʕs=477LByyK7}6?֮]7Cضj7r͏-?la7N;=It4i9iXXkrxv< + ض-\Yc봙^'w_zMca<λw? ӟ۷faݺuc6oo xB,6ow=GZ6ǎ~ǭ^j3K PqmR86Їjg+36$TWW@ ~;ٌ9#> ۶kf3/]ڄ駟[nP(ĥ^ʡʺu~G2d``^4^I{9,e_~~ul%^^ eV6%Jq]wC<'Kv߱g&,=<8O<ۉbz 2y?wa#it< C.X}ns~20::ʯ~+6oLEE\r ӧO#fOwO0M55ml~={6=Rz9JjGH ld}O t,%KȎtzvݫ]I;c֒d֎ҹ}uKtǽs`y5U$=`4ޯO"""oB@ɓ'SVV3Fٴ3 D1>\jD\X,\Iy n8dF:|;^f;?i8J'cld_Eg0mbgo/_@ SR{$˿кФ4-" on!&ot% Kh8 Uqи4`&{xῧP=D"UHӤiˎU?$ }HI7D>=H  ~NC>H Zq%;/4,Us?0O2vo0[0v.g}62e @;vpw;v`Lt០/M5k.RڲMwh=^ZN (%u Hn$v^W| Mf{~Gl*2$w=Ȏ?MDןDqNjqp 4EK bQ!t^Ml*K ߶7᥻&ZK__}}}466R^^{jkk1ưfa$R&yR]qvnm$޾7O1دR=;W0WA:lIlG;wre43fࢋ. Lb ֭[KpCg3ҽ Of{~?ڈͧz;TN?P!`=ӵ+0nH a6vwK0wkZ1>\zv<F98ze ג###Lp# N|XTaJj,|u?䍎0?k[R167zI:,>SUUWU~}O|DQ>O~GI͜0nP֔ IDAT7TIv5M_ŸQU q"Czhmb/q&3{z6?|@ǬeEDDބN7D l>iMTznLpH=Շ~fjyWJnVITvG 6_ RWLJ GƧÄʦi8llr)4{nt:^v6Rm\ /!3` 6P!k- .{^{- Za[J/Q9\&lXb_I+ 5zk:˒؈n}N;?Gp7H6эq ;KzlGnvl ?{@P68ͧ|c=۟ ɓN8/| ̛7H$8L2/D"&@1i?6wNr]It{=3 O,%=p!" 6SR7_|f@h;!kˑw@6ގqM=ɧn!`s|f~jj9`i+vR1lB޶ʧKIn>*ǔ)S88묳x;A,ec}xpU^F_ c F7 SR{8c7GAJㆫH My S><6 Bc{kn0.&=\9,\Q1lµ 0G6qOǻqqF^ͻ8%7QR=\j|nYRM6с1 P^^άY"˱tR  =Ffx+ƀ_c4XNKm߀ㆨu"eSM"\1i2s7qKHm,Kh8FtLaTQ==˦Si"\'XǤ@ pMdG{N^8vކlb6ҔEI_'r<6f'ap-ّN(Uu8%%%|(v>HXj1JErʷ)k9lTz\XO_V81"u M&=e6C1/6ye}q(k>p;}r6j2TFYusکw|sFr.S&ĻVLȏQ62Y\%FǓt@`5u1ƐK 5؆Jլs^lbTy?N J7Q1\_V*S3#8lixgMtڄ?RGPtC=TURZ)%"""EHSSTgy/^LKK w?I'PzqY,T-gʦxǣ`lED!@MbS`rbM=/'սw# py -w&XO1Ԏ9o '`5fZZZr_^x۷cd"b 1aSc/v.KNb-Kxf^Hj6  qѿ?3H) Bßb?4!+&г+L:L1;`?D>Q絏 WF 7֒{/QUU?0CCCr)444pBN=T?i v'R>%=ce0j d ݻǠ$;)5'.@gOC;`Y g犧FNᴟ3Kqt0l;(m\pږ/*w2vM/)//#x ~/l2;woCxNIbF66m8p,Fi9PY ^f wb,3'\5|&N#0/Zi"IGS2,گv{v64c&!|2gs|;WH72ܻ Oc]v,̹bng~@7SN(ĩ{X3#HR4p# ՞Dqgioog…r!RWWG,Bbt-06YzxMVH P=c4/rm'o_1 ,KL&I@$|ntJ'Ct7n?S~D W\=ZξgR0/3LYlǿJ&Pk:EvBPz Ucdӭ,ZN8~ rMRUUũϺ$ nҼq{ى /s/riqOy` Dgb a/1'>idX,F8s@ s _w/ o6G~XQ (If.mt=vCkB! ﶾ]]Q==[ksy+x8Pt*\?,j߹%%%% !NtRҩʏ"GƶebhýD[ai?%\9X?v*|^|ϼՋg^$,%(=TKj&3,;>Hr&uG~i3to\EŌ3q m]FOeh:GTz7y'BߪعT@LÌ_RM5SR;LG℡~qCŸTϹ_o㏗OCߠn'/`hr# edRI '""fhhh/^8R)ill_6dّn0;l=d;x9 koĎ>?:\bX(9ksH{%N8J:#ՄYˎ 2m9(w-LQٳk3z{ܹsy׻ŴiӸٖkD&H8(#KzޕZ5,Yֻp1u?"ִ?ZI̝Kh:dIv >`1(&K&Poahr'?Nߦc ===\r%r-\r%1uTVD>y'ϡ_*M0\qߋmmmJǣלJWsB9466kcdW 6.bW=H~t+`ɏm#}IPpg If#G{xr4jċ?6̑ہڋzWS=2|UhϨ!-SrÛ)Cxc n BPHfuSI_ 04 !J f^So꨾*Ok'꧐w=\\\\\\f455q駣( h.2z̚5l!16-f~1#BphW9N#e"˘P# Q ?9ݘ0>)[0oBo>>OD'?'?I,X駟N*ݩ7{ BB8c n[Fp?-@:|z|u~!ֳ2ou2s`gP~OWf]AWm $J=9F B"D!mm9 K䲈b$X4GFZ~鯉NP4H U:ȂYpʕ?#N@'4=Medd{a*[v-~?Oa 4ͬӕT/T_:5ލv!zt?b[O}jBC"V%Oboٽ$daUT@b`+oʏu?$4R)˘>}:RJn1MVia2hzQA)ӋUHYG_ڕvLi̾Љ̹!lVy>\RbJSwM/62H@Ѧ.ثPM(rXE((M:JY _ _nx3*2[j:[A_]}a&r > {=Hi!l%6rGv`eRJ+;=,fh{ׯԉ+^-xT$I:::0 ۶+ B`9=1>SVRojۚҔ*dCv2˜N P64_?`f#R]OaER]vͧ}`"slj(Kq8&B/d\o&Rfqƴ| ,KiNV1^1+Iv`9rI P$3c l&ULM$ xb)b|5s(>Dۉ,Rp|;,oo(~5sZQ}!]bd1s#!B%T_y~8|̊-mpt@ Ǔw MӨ( H3xbF @NG ԗE F* 6S߀,pZD dDhig3I\F~peyh#'܆?4}; DDJo#XBAѝD6l'>(z3/IRH+_O0B RnFq9D;'?JtTV CM-yV6>|4NYX$2V)E7௙m`v)~\۷fB.@xOH⿑ފZWKY%g_Kt|l="@)73lF u`_Sw:i[I 2R6(zOuS^8&eCa Q!Ly}iSHo`G&'.:?'>Acc#gqq)Jk %CYΦ~IJ)%~*͇7: 8xx6Roڦv)l#GV X_j`dOxBHNgf@dBfun(P=·@DBEX1EQqy9&+;GZ]νhEN}gQofatK 4./㯝Ol[aQSO 7o뚴UHȦWlH+,%_OLW_'PsXw%6z_u;vMesǮ$ض;}@GGy̚5]X2MadDYJLNToz<R<mb[]O:sCuʰVIP5B|Ota51t _id%R2(V)PTmPL$ljA!^sм૞jA(3)e(c DӉv\6lYh>Ia٣N|O=TΝ(IlڴHD;.~G.W5@lfnA:gu9H-g][Wz#Gw0G(~jعfi"O*NHuaиRՋCrCcI4__$BPy> \ŋ??Q(hiimc=t+Oj N͌KKY=唗Fl/Χig>Iӣor'@,T7-V1B6R90v|&jO@" PFE~d3Ȼ]wiqE ~qY$_\x\}DQt]#Cir{eK6|u>!TWZ+/_M)˃RgEͬz)e5Iw?GqyL?F`ԟSxmS_ZDh~4_@ilXdDx[9W୚ClnE P48RY'vt((Bcc#΢4gaΝ]v@" N5#~WQ#D;. :"mwTePyy̆ BEZcU巠:H6,&y)5pJw%0V!5sU(O5lD~x3c;M(z+mmm\tEףiDM6_D"A 'DAP ylNOM$j>ד1#Dٳ qj\A!md|c/(4vWHlZNd)e[AIQڞF(O9B撏!"Fvd*bMlPJB veC LtOdڅNȫwx('Pe*yOPd;9ÕLk_Kb*ͣ~"Bz;Av^1^<|y4,޹OKad=~bs i)e)PѲ?PP4,6qu)M2}06GԞAm`i{~G(7Q< X_z%.rx{Ϧug(II_{N:@|SDJ$P;hǹŋ%!2]4௝Kv>'iyZ^bl.`Rg 2Jg[zSiɍlaxE!;[3 [p<-&'@xٹs'+W$QUUi[EO|X}/zDۓ2 KQ6ͥv5O&|j%Z/CzR34юsI-e( /8Syadp ɞUSb"N(dy$QH"{BJɍ7ڵkCUUR7of͚5u=o$rV%+7D~h fzzzWߌPF6Rb=ko[=Ƞaxz&bn#C5-x#y5 2C[Pu΂#7Ln`5D7``d}SRJJngR.m,maBzc<oؼy3Lm۶UdO:zAZ%JnJa2l3KUwշЃNVKu"Co#ulk9qR*Uu(0F)݅B)݅U%;xez[ad nE-}(>Mϋ9P@>-Zм tFHozlFSf-@ >J>"`Ν :B<4F.qؽpH7R]eCv/#Ioz28e{ ёfm&=٦,`Km|\wy'=obP}$=I1@9/ppW)Q0) TEJ(}R4ӌnpj7/&T o mnmo 3sY=VEcY\7L &9S1%s9~PUidCH)]rRJ O;8?B'n#9VHv3PΉA XviJ3S $[u9Vr{Ž` LoD*w#Mv]2*Z@;=J @wTX5QB*qE12}$=A|ٷ\\\\\\NDpu#҆\j\\\\\\f~&y"а ,W8-'e6 !~HPtɃ/of#ѫ⯙0_>Z](^BӮ$t!"?a[^P1w\"R{Յ( vxC V;0hdCq'!XK.fE$W_>q(=Li)JQt?2f :6$7aHiynIw?nlc/{!B'3-;`#6A(R=Ŋ+`6dy7LcW]@Rw~M_+]\\\\\-=>Duu5܃G t7S1w\t]g``L&s–3vFw=Ҧf% x|'n#(!һ"=4,-8jAׁeA_JϢd|EK -6iOT#I%4HzHcmނ:z^s(ȾN:38h4J__4559O~.Fߌ'6Jg]Nrwptqqqq9Tyb k\򶢻nMsR … 'i6\P} Ml?uRbY-͗AՑfRtCQu(j#-P:zi|Mn(8ѶAf}6DgA HQ^;O=((H4^ V|~?g}6رX,F]]ic6o\OlZE8vǐVaOCoP6'v "@= PQ߃m! LZiSOx6#`Goj-"K>ꍒ}O\餠=Dh!Kv1vzLakX TO _ÙE*#KY 1{ikkeٲea+RĕW^I8f``8vOzOfB-_kN:fvJwNUD|#aa}W#d2>~ߏE@~I-Ewltqqqq9iRqrahfxRdWgM.;$ %74-<Šh mt##H+43T/G^7BAZE n/{.tw#7L!_#[Q}Ux"(w*+,-3߅)#7hfamdpP#megn)&2!뿎hjj _K.gtttP,Yz5?Asu>jr"lC} 3Q_⩥FM*۾r澟E!PYZp̵!Uۯ/_Z3$c;`d-HsQ UHDbv1Glh4vz1]O1V5 h=P}5Q|U3QT#yYzs_"gcƍy:;;׾F,^>fJv%Ŕͩvzk8yR ;}'6}- hzhrM7!TcE#;Kηw^މ \}՜r)~oN:}QWx,| _U'Bˉ)@yAR^pqqqqqy+u-[p}9Q,zt #t*d7፴i;E%aK):tJr}N&P Ꝏ$%ҶɏDJ_lFw<@UOorOe y9Jq]'ԈU8z{Cff~]EU#oP |#_;A3kٲeihhhଳbӦMl޼˷W_JEZX$vaC)vnRZ5F㒏RJ%{_Ƌf63ɏl%=L!7OY4IB m";h9Ϳldm`'m[7wdwF{53C);H|cQ5yKU;)wG>D"ׄan6lϳ(R5B" Xebd}󔒯h}'y7vo\N)C= +VO?N.;p.....oj5gAJsqqqqqy 7˱m_WOz c{j6z12;z0c[o/_xj_2wc#6@J3où4[8 ~!Қ"DT!^~e*[4MCUU}ϒ~=Bˉvߐ=؅U֟C TNJRʐ0o$2YFBm[A3G9ab@RԲG,N9 3v"7(xv87JtIy %+* eiPDFH)P&ELh9G(cZZZkR[_" .d͚57`̫P=Am ݁DxJ6\\\\\\fc!]]]<)e[ 6,"i x ^Fr=hY ;&ZZ"Le"v@iBhm}w>Jլh;O)-{d Y-wr7̼ o[I]N'Pd?/>GD;e{ w>BKy#qniG} IDATz6H3N\:O3?YC)crUâxAv` N"R{ڿvf:(إ4݄'E(B P4X4 HFv/P h>„O?yG( /$u-Jf~3?RLg/qIi5Cf#N&Wl$&N7P#ʣbsM!=o֝A_n22Wj22729,6RnU3h;{XgB00VIYߢA59e\#)eD;hJaOnxCO%R=/Q3 h绱E̍?czړ/ 6o4B^FCb]$B!x~ىZL&MU${^$6J}wN[~;ѦtMzXxq/cWTOn<[уG^%7hwQ l:贋L<˗/gٲeaLd…˨z/%q.d#fsqqqqq9pܹm0( H)xǃɶVliH-r1#d-cХ_T>W=@T_ƣ1{D:`t'*jF;‹aEJReޡ<lF~E p LJ m)HhjehTlɽq 66%B $7KzB.H$@(c6ݒ5r8f yX֜9g˜>(Y r/#Jv-y=#s`;9fѻ멫-lvχ+vKܨ.*OjN"6W:?_d;BP|ѱA'8lܽeeP2AEsu8T~e\T~BPA-6G`odl >U BP(_Vtpt(]#PE~ bb܆a5 km+\n(2À:vvp(3&m˳6K+8B+0i-JsWP( Brd4utC/ NcȀ S$vd*=}{@Cy;a`c%73 剷24csG\rV<3ofy|۸1!vQ MWJI2%Y[܎iklѓhZګIrɝ/#Vlqn>&麇wV 4rhDsN;;Ҷ5 MN !%5_,zP2p5i}Ztmݧ(_C˵i\ -Rֿ}bQַ_x&4wd+)_GQ,$YQ%xdOKyw&|<*k=s7^G^L+lcG).X|q )KlKqХXg̨0]4md?y 6B@((C$3kB㛧=9H"Swmt=tyn#.rSnlpIHSB{BB M@.{#ó 8ivBO ѫD6q _;ոhu˃OԳk:7]ex?HHP Y]6m[-85_2qX`Jw:/:z|ѫN}y}yx^㒟׳eˀ:3'عa6w7džذڊ|_P$'LRU1oM3߃&j0wEY'1Lvooq]5uI#y^L>U( BP|y`$dy9իRXC< Y,[gp.n%Ѱ #-pe>+3Fv/eT}ٹXx '` ?0iR^}]1jHׅHHPQV&g%H !;E1Οv$UG$xq,Bsuq mU( ^ض%ט>ѠK.-at5&ɤGMGLkv n4YLmIuHY-JJLlKRU@[0 zv5\I]R̂txk}L5] 4Io1~H>AaB@KچA@?|<9Jo!F7sgN h8w6| Wgn-i%&@Jt 3OmںmԁÊa>p<1CjWUNsqwLǾj㿞tbҥm<:U[vWF9irK3y"?_KAE瘾]m6ֱrXp ~gv<F}'#dK^L]u\ڪ__a̰ ;::ºJyD('O aHTy5$q>cTz؎d}xr[EB) BP(Ib͎ +dK' SGt!%k\syN9(+ybx)Um(ڋjJ+jsMsׁ^S ZװZukzm洊&UӔu9V^?Ү k6o?6\V؄.KWfA/;4;JB$ عa.k7*]G_R~Yȸ&3ǚMqcLdN.W28Iف;uaA378:WO0Bia͉7x\43ȯO0zhๅp݅?-Fu˼~bҽM[ﯣB8ZX;sOKЭ`Y>/ ,sW_/lfTSqь !!.>.-;9LQƻd8ztMoD8#[a{Ga\PcC2+It]|Ak|p }zp5WJ0b`C >0c@=K3cdvۯ3iLj8BYnZ u_>E K2\Qc53=ס%]uI9Oi 2Nh4tɃ%k%%e"-պV5-A露zL3$kIn|d3k~`P/1c}+sdA}g8S'`R,|6ȅ'7fbn{78'v+ BP(_tzVtM0]:JtF0 ?nz2ABak/ ӿ of7ܤW wmj2-q7E)Wo KE5n;'Ƞ: &ariןd8BOB8(EQ.=C{5ZO59)@PyYV9s =52aL?ĺY~pOWڧ RƌlnEǚtj(y~nƿYK)Ṓˬk; ~zN4 yRRZcBhЧ=ߍ:!`Ś,w͟˧ Ը0F֖W{wmASLG\8#icBP+I5U'̞dCOMG\uz{]~xY]AzwшEnI9VE(BcFR]y9rLz_ ӧOG}c2mǂhH yog>{QxÍ7fM ⸰,o,Ÿkd8uuJR( BP| ]uu?V2abY"rgEqycAūm I"`/4$.Nⶅt 鬤w/Kodw?:D,"048ePFnyCL?.JA4 -uX~3Ab`@ %LNKF׳ㄡ:?*θA!AƂ i1i\CHG 1vHӄIiuӣDz6X#v|h(2Or]7%C t2Zj0jxMyY_@ &'ψ=<lSM'oLK_Lhfq7Mh)_/<1J>pQzwiHI^}'Ãs3u2?1A>N=dJ t,^Kt"9H ݟ;+L,"cc|#Y'R~-22ޞ \.Ǘ쬗;RgP[ܓGA\@Cȼ,"f5k?CRYux6>3k2L<ʲ£kTg@!b͞d|;]0zF˖ll/@YfO 1ɋsƣow,(˒ аmɚ2|=L8byjDFq)x|oDfoNO$լRG?;ҠSҵ ~ɔǛkmQ=p}ƈ򢂀!0 8~ˠ Y&e2bTp@i8M8hj%8~ZPϦ=LeV ~ PǠ0 ;smhˇ2>#q^̕e6/ 4٭INֵ/bU,:hb0fh]bcM&C}3M[_/J+g[\0uT ;hWWtFr!0*%㳍TIΛf0?giboOMJ<NdRu.gWW9An.k $\FeIG#x^e~4\q  LKjz9NQAuG*->lz=3)~?5J| s$?= ݺs\"{\lI( ЄߧSLSױݜ vz,gIDAT"ѷ_P@0Q,&޽6XswK/:{U.R͇Xw]8gzq#HO?ieUbWxg5B63 պV6or{gC0~|o|%uXZI^|Ǩ!A{1ɇK~Z͹-Zt<8cvl-X)'aol)T%WN0rH˖4^K<}|Bp͟ꩯtNP( BP%_a2f'%]: #՘Uy^⇿[kxgq 9$a ռ:TFb0|{V:N ~G ZJt=\%7U$]9.@շ5@-w9:LbpF"!AyRͬWydgk-\$9<|6x^Ȧm6[v9,[kQUw8U?e VHپ,pyF]}}dGG83} +wxsઋ(ٴ7]:~`,|sWū龜5Θo\0@K\rz.gذfz{j{_HeMnO]Suf1!"@"*0 Am˵w0zcÜ9XUqя9*}]SO?=` ~UnEq7΋fsMvOJҹ=+ clud+\tZ tS౷/D|A^Lc_+n{xu|o]SClb +~n/3O"r9ds\k>Xgq U}]%/"8('].lȻJ4Lzqvy䪼wdSs=)¾eK, Sp´ Ct+֩Kv{ 2#ͣÓ~j[>9=^@!@cX_a}5Lף.Q\h5_HXS)òHMs6{#,@"_g {Qh;`m ~W5ΌR]ӏ7[7!u_3rcVJ-yMy˖d0XH0 $|ճX'` vsNAAMH?,kKlGBP( ŗA~<I'|GՃ>Nuw WԬmIKHsHg%q_!m?)) `x- Vnq8Ddj4:5_tf:IңeHP@ vMNwur$vG7)%hLKv{X6z,\79sf>7uKӒ}lR9UyضVl(*ߊ""a^h훫\Q k!}e%E:!fL29qjm{l}{ )[/6C|NiSBjwV9l:Ѧ_¸/~sbѺ-Uu];D\oO߂\[a1oY,r#\k[mR*[rm{?E^ FN`vBVd<:kTyL`@XL#ÀSB:IaLW2GңGsB4ao7 99Ċ5M*~ڢPc:1}(` g|ҽx0J i^폷=VиdyMjj]~:kWe1'BP( B%`ҝ{`KtJhT7x6HԵNb/>,`@PpC8W"cFZ0cE+S -;vr)nHI0 ;I*+I%%rB(96 7Ä2]AfΌѵޮD: P@16E2 "aF^K9(Uq cOby^S~95 غۥA\.iM}cE+~&x bť6v $Vas]¸bOkS<A MИp%d6cAB8Wwʶ[w?vxU۞eDLxͲ5eI>B<<򢂢 $:oY8t_=mf}1Eq7)=8yf ;--ɋ ?u\d_Gc@o>:WߙQ%e6;xx>vyuy`ʤ0-Y!˺J^5 uLfL M ha09cF߽f;=Kk~4Y;̠{G'M S\1w/F^LS; .8=NCٗꩫ.MgZVs8\6\u_FeO{F!fM XPZϮڎ\ qiКVu/%by<]&+k$uܧe{K[v$QXd`pބW73O _-W{ \x#D ()(PV(BP( ŗHBO럀K*\`7W :-7p6!ϼNcKN:ǧҽIaƠβc[gRJA ba`P7&3(`\u 2o0|4ZM'O4 ZYE]7$K$jrɨAfAi8.LAδ1.yaASx&%IO\i&Qbp!8JIgekF֔: +d]؛Ԧ%;9L䄩!6tu2!DaBc UiHe%K2nA-݆>qA"cGYF P^r ~$;F4qLE˳T8nj= 6nٰw| LF7زe^`mR$յ.t|{~.t&ޚ"|ǁF~= ΘdzݕLw].u-nIeI ȏk_?}:Ǝ2&f`Ӝ6Y?>Hbn%~=.oovyzwͧs!~-6/ːaJ)X `CA]#kuΘh+tЄWf::@,% ߻ B^&SF4$pnj rwm6 FmIɉ1@ݬIw; k /n v Fww+)<&s5q3 6,Gۧ<='É8y1?*.h Nb°]~hהּ9}{8d}vW WzN↯FO|FmǦ,w{}iôfL sM%TXXcx|sp]ض$?kK-FN>:@ /;tM싅5D`2utOJb{l]B BP(/_3@T)a;}{,ADvbGU{lnag\6nq DqU']ٷ8.TV{nP(/ MBAer-CDBzɜYx1Ӧ6Ǫ~=l3ͻ\TNaq8w8}3=-Hq_./M F=P[/p ?&8l?RoJPUm;W{woe=mYrչaz:nX}g Y!~|.z4+yV.ύqh_&%cm3lmuueK_mȼ&e:.9%DX~ls KJlRT~ǻ.J4%iǿW0L?en>nv7Kn/2jAЄͻ]V>Wxf=5x3?8PBU,Y켦=6e^[w JMY غc.;/,>T$-E:1~/*K -BӇƹ~^cZ|MSFq]p2-c 6=RN ѥP4|$Gn'\~J/ Ec톬斫vϞ}z|wIn=2?O+-ɶ}OfEEn:si4B]VqbRfG vٰ)ö-mg{5q,,* BP(x92hjT^Νdd O_L <ߺ/ۑeUŪw^m 0 5n}_X&D-"~ #Didfk:y Ҿ)ssjLn2;mq<؁<_KcC k*Vm=x-]momn7~'w0z*ހ`J{(d@Wvۿ=O2rOح8_0}ɘzL OW_Rj.+bhVRJ)ɏ׏yDjIO[!Κǖ,WI6r}O \[$`Ɔ m @65_< ~͝\7ͅfk[C 3gj[ӸmMcm6O.,s?ΝzH=ڝh[`vr DF~j燳[1l5PXU\@cnM4fWF;6s۴:cC#>_qi? ӹ9jGvn&0mh 6BP( ⋄nUt,htF&UO[o±B;ܪ䉧׹v5Z$rc")6:h`#6>mnߧSk|}D@YQnZHk |$ #]>m kNz;t9͑Kbc>],ѹP( BP|1 H+HGF9A\RidG&heCAuO‚,)_h- *F{fxt[M,dwxMP( BP( 0 ǟ)# [ٺ0uq:;Ӝ>#5gb|ihb4_-yl) g( BP( BP|4 ARb%$lŶҟ(ƁbT(* BP( BP(:)jp`$L$b*JP( BP( BAZ EH)( BP( BP(*FmvumДE BP( BP(Gs BP( BP(X';) BP( BP(5;BP( BP( ſlm딈<IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/images/tui_list.png0000644000175000017500000232236414656344035017550 0ustar00ihabunekihabunekPNG  IHDR~8zTXtRaw profile type exifxڭkdsw|p\g̒,e>X*2D_WHGKm<=85~Rzo|z?E^O?fÏ׿>0򠶾b/{}lEI#Կ/gZzNa_,:{_Ϲzb$^הgIf?T o)k‚n~3߾ooV_oI~](ߕ|sn_?ſ>g3XjUoʿGO矆 /La'Cdnan8C^WLػOvb im)Kx_r߼o>+Wːgcb3 ~mH_Z,ZeHga Nx. &$v]  XC`!4:';JAƜ7-H ﭱD^vMOʹ`?57lhTr)J-2,Ybf]-jJV[k{4K^{뽏w< c8̳i6 Yye;m'LSzg\L&w-n;~׶`׮ŷSzckZGIўaDWmgvN{{+JdE{v'rÏsڹ}s5m9msܷVZo>^E ;Dݗauј=kN&ܽF$씩/_vNe"@ND22LmONSSi)a0IVϺY5mp g'K'g[G6ݕ 97}} S;M>l38c.6Qل[j76ZZe2؁Sw&{Jd \n%c  pWjXlbq:CcX$^_+^{v6$,6b[վc&_vۚ|v+ط:l/׭}7rX&c_L|A=Nqt/0y81ҖOZakȬ)V^_ǀ"G{a?{\]u}>4ƀY z$m^W31r擙):tWY@SqQ:xᎼnrX1|Ǥn] ?r݀ϴJfLLwXZ5fzC/{VVdh1j;|֐=9c}ˆ ~_p!綨e:uoMd^(6:,79q~|zOZxgJ?RPik<@[#c W777t#^ hslH\:mߖ0ƅoblcFHuS-+ec=R nD7qsܙ@ 6Hp%42ZXvlma , 0͛\S23 \|F"~-!kb{[|ͅϰJ\tXCߨg~nE27&ffT]U\z$켫nY8/_3bP'`=ؿ3x{TRc[A$'!bS&Q_-i{ 3PRcN>?=p-yv)k9ς]Æbַ|4q_,@0_HD r?a.DACڿݯؾ96#eg&, `^y1ERr9;XI~v 0] 47 2M7t=x]#A +fj bEɡ!-`plX Fx$+0\盠H =Ζ 0BCq Q@D/LhҢ.Y-'4q8i_߲ΟZ Lf3J>@[PʅAj0 ٌOz 5D#ʆfc(8US=u(HPTSgF $śF;bx0q"Ǽ3rc q`=6d#aJ%\%Ū+a\6?{?)ILR:d[͙$J2欛GLs+@PB60~z `?x?ZC. YPΈ\(`6a}jjpȃ_.Z9.l[֚'G9&#(ozލΗ*ÿ$[Txdٓg* bo~B; /~baA+.DPoh4wÌ~Ϧg8PqXpP ;cR"ъ,;Got/&OD&BqBxƁO䇤ľ&[@"Ҁ"5ӆ-_k7BZu/I a<gC SD Mlf٫ 1Z;3y)WgXqIW vkM%np;& RAاAxPXD3|LV"Jb}CS?54й:CbEDTH83#aѷm2Am|guc¹r(uc@Y A*i}|I>,z4=Dxp'u$ƇqSJDRhB9-bUz4I(L){/+#^A r藜 aa: tj"h ]Y2@G<Y_JrJEgT]kp'xI5J]2_& G\ǯ { *T,`|k1؂iI,rE$÷)]hD:-"PaO жr X]MO ^Ⳇo:~UKD∁-7V )d?{!*n$!GWD\dSr%ѱz pS/ @\3X{'4 )C k eƐ1Nmr3dT/ʛ#I/ Im$@g!|OWFe&O1&NhAC'ZrI*|M=xq .@ir S p|-A& Q*Ƹgi$XzoD:qI!`je4Wz5{D!@UWǽ_n+ IccP1Zrap(t!N:W6aU%ʼ!b:9Ȝ{kٌQW4lo:(X8ruybDW#2cȫL<~F˝/ KA8i&yu'[hJf El ds?S9jInIYvdаBa'%$v:(a*Q$J?{qb@xN{ oO&B PH9o)q/>L \qtTl6gr.n<ȫC}fMlt#r awXV^+hEK&{ߤ#>\^`5\]03fhhdR80"_R2:`ΰ̦,@C9`/vc"/NR-MW=BYZJ |䦄+y ^L:N%\_Ƨ$xg$3E),)MfZW)H06YD:O {L',VEnJN42i *,ږ&kڥ?yP$|>sYcUz gɱߩ1GDF+NgK ,pڌ ہ<G@ꅍ93ZEi 89#B6 CW`=3ȒQ:!D윀y8]UUs} "]Wl0#4Q6 [gp~p-I`n ٧jDB (|kYb$PUWx0 7f (c\􈬓si kR:.U(" {V%X0ny!JIDq5'_4^9|HtQ:E1$ E Ltnt/}AXq!Qy~DD%yQnQ`ŅR (:KVFѪbmmq7aO!la+Xg&^@Չc(**:AQYF!Ȼ!9E= v68oy3\.l31` M"#8@sseO-,X~*̎7"7 S;K2!38;vA'J2,Ut;OxT\7d~9VZ:t'>`S*Isֆh{#L#.ά?]]IAIuɌ[qaa*Ko(4F Xt<căv\{Gg<L9ܓELxb qE1`Y>}"c63B"=""qNI |;m_Q$MFsyI AA31bswH}PX" IP;7Uu2_ ik 5".I7TIl/nyV$+:ZRcit*),m_Ka1sS528~`@pfM̔/qf:;C1-7ظrPXdZkbAi#UW|Jt@F &xvzMb{DW?`w@3R)囷eR3GȑA>KB.g*RcG/h<[~܌/dH*s14|=<~ۏg3`Ȍ”TX*Z@HEqb - pg2<_<\ vhДΗG}0qO&#.%0Zb@L`x3r"zcnTa#DA+?V!ԃc:^4%X3֊  l";a*k* Dn%'N͍V7J:XVqMw\1*Bӭ'hQ&{E1$+  rtͤcJbxMbf. 0g$/ZS} *j=+؄*}#^e_( aj}!ij `wPz;-Vi %?A=T1k cP7:#.@]EPiIl˫sW#n5ܒTÃW`~g(CBmkp7@餖ɟFcub9_e'|sE>ߗO(S$PhxqoSU^ҍ : $MwNn)B|Ob:ހ$TSE^:BJBU/yq5`hj@D*| Y=uq#{ƫXA9ibxx/v&*7_E;Տ uS"↧¶+pW4b~9Siɠ \;p5_8 J:U~7zԸ+Lv5B?IdX gUf@,>h; xKR*w|0b'L`elJE?|DT.VwRQT|*jHZ0nZDVRS4O?{| ~3ͬ3~%~dg'lb 8uP}T7`X|p.mZ{ l gDyA9>Yiղ$ }|NEu(/tTC$kGYPMGE pF(mO$& Ce"0pVpjdnjI֨@)^yUD`eUm'BQOhaȃwRQlj2!DRRi&sX #9Nؚïub^6X*K"' N;[%5D dC ; _Q- z{;״w4Npp?r|e[2/R_ť` kAK JjN_j>8dd<(3ڐ+T`:^z.&ցЖfʞ"D֗qz67~aRb"xݳ?2= 7e jPXWVsez.XSkSEHEAZz\ƌ B_r&<e`ɭNŁTWhoi`q1M.|xs 娏4$8_ p.bӛ _pDD7B;e+iKm&(^)swtבi4WHSz2,nuUVZ4" +[iWTk6#XNpj]QTnj-(v?#*9TבU0-:-Y$R VzUY5&՚]t \Ȁ~ I.v@^?ѝ3-6VCA8Uc0 %X4V%Րo aB\TQv;ʺd ݲ>pVLȦ`9?GvG_?0+5u*_nd'mA]T%^^UBpPfاSYp@AӰтq@Է2fՊVCJo&!2Q.6HCm\ +ʭoUd͞NjG_-#*Xb[T vuE?J%׾X$3t{. (":*4ԓ]2]rtQ( p#k&Wt{p9ӱ$2O:xd<יz^IS8UHOhcRUs/d1Q=e)hVn EMAsF![~U:h`ͪ{ K6eT*yc+*uCn] DHD1xuD[>Y~&t+qߵ\CXm0DY/fv^Z^xr : L_2:e>-V +N!r㌭c:@ԩB9ă/ ݕmGCmMt앤Bw0_"]tuPϿ^PW]Y iN U Wu Q1~[]BhvyUD וPyNQk!T{BxeAe$CeAlkX*# .RgTQ 1]^fפOk][LNQ^!nt(|?{m\Pz  #]/T5l?/2!ݪ _RME֕@ޔ|([6LRxG0 CU%7_ڢuH=2UAEU_)@65)5TXPbRN]+pXU{.UkzS믯$_@韙.U@4 擑Gֱ)m1jQV}"c0vTe<宛u/"YN; .`Y/*BǠS@BL r ._!5}  Sr_*G}'Dhx7k s; sQUԚљȣ_J]_\&pZ57&4PE.'*}ׄ(<%$uBWE][1_# L`(ͅzu5*Hc1= 91-([#T)]SU,ݙ5U*3ah2qϝ( kc&Olؓ!KMk'hcS^6B!^d+dE:zCǫ5հ$*|붇w\UQ',iRv:!DGO0BaU_+xtR^q&tJɫ2 cH%1vUUkVX aZ$'2k] )Eۓ:߮ewz$bBn깃cIf\SˣDKT{W@S. H/NXeQ* >w|U3" xU` ~WP Ʊ|[#k#cQ?3ܞEτ;(-,CU!^? +B@;։"Hlɫ/` #H f^ @CWfށ×5Ԅ+ tt;ҏUX6h)qi$q:+E9 B޷6R34/ V 6^Q>U1A=EO4-MΠ;|u`0yT *cЙt!{k}jBh@gT5I@Wzq3)K*Fk B\n{J(wU^\5)p3e>v[ӔW_G+!&ղ&˰|ډFy x] KlU&n2Ylb]d!*Q$a8z_h 42s6S_ a? #J0+ҝɳnJY͡cm.;`X✡ꔩV~MUuN5dQCyX4rPAZɞ&n8 [W'A2y(T.9r * N(= (*RF-(MQ+WKԻO7Oܲغ R gIC塪+fTKA؋e؊ǀ+ "S1C-^>  PohbpW"ƩZS5&ŚMMEԥ=Z4ltXAK†rTsA+>W8y%^H)@8 @MsgU;؀]mRSCUyO!H-d=CS/:g=S5=ឡx>&\p` 邑G#~KRՉUS/8>bꘒ6b%$oUC [BGޕdHY@(|U%[J%u+ۣt)I])03Tg,5})_WSEzYwp?r /sjS7VDSSRP  b%lP= pDIHg,=5&;-SwYe5K;8uY?p2ݧUg,-yTuwi::jT:qH YL3$^0^ .jSS}.Up-^J]jQ!űwG^YG5* @f\ad Dy 3$NU?#s?(H$QSm1s%0 B]6Փd|/nLj;QEly]ڟ{DقW3cյb1u-hA=|e_ <(Fw̿q.Ug=![tܿ(Py$Z SE]C_]g:Ry6<e`s[&h͙)1jQ2ꁛ}ZޝnMܳWȊ˘Pˤzk]еCЂP>LsWIuS>S);9-"u ͓a[amP?vvC@i[u77]Yj88FK=r$ viTXtXML:com.adobe.xmp g;gbKGD$Y pHYsodtIME3(b IDATxwpyM 9 Iĝi% ]%Tp՝|r+,RJtԝ$KuӒKq6p`C7:ѽ3rOpo~ }yWuZ_llX!B!g aeiqd@~Q_`Ba A'!B!BGN 3 ;̸Aտ-񸌛B!B!>D,`I*"HA3vv mB!B!GS.A44Mm`}LMMؿ?dK:ELd||X,vW3u)JEt]'JTR!.xD"i}J% J)$xK(mի B1pei4O(bbbH$>XXXh0<ytttH$6hVB!RHD~r!B!w D"df^oO$i\emmreI$w0==͵k׈㌌F:`277˗D"8pu'Zsuy"ccc߿˲nyKKK\pMӘ U5SSSLOOL&!fYx VVVp]Iٳ\.) D"z{{w4X"kkk;wj޽{D"RjʵkטB!B!εPbX,D"oF4bضM 2XvAnV~iDQMY6wk?A`&ccciL$o:R[sZV,//i4ꚶ^vն`J.#Ӄ-Ek]m۬:m+cl{n:;;,kVc.l90{eXu{5MCq7 ,R~ hR5i{xm7R'B!BV$It]hP(0Mx<J5M%geV vSOOAP(p]h4JWWeJX$byxwCphyy\.8tuua|q0 t:M&i <6PV^F6E)E\fjjv@u àD"nT*\E)eYR)$i|o4T%I9y$bQLd~~۶D"ڵCӃN,c풱j[ov"H>[J%N׮]ömzr9^y<ǎ]q=DbxTXԩSNW˗`hhhG%R>ԩS\zL&Ñ#GnaΝ;tnni|<.^<$\rX,ƣ>ݻۙeOfffL&CWWB!BHuW<[H3IZ]p@ut]gvvfr9N>M4%022\xvv4 0XZZb``嘙i{tuX,Ɓayy˲og`% ^ٳgVux"Z 4P(p&''Fٳh4J.c~~|>=z1Rb6ۥpەz_~ER ݍatuu@g sq,w2[㌏333"P^zzzb;O.\tiz{{9tcccBv[\u]g޽B!h4ׇeYL=hPׯ_V[ .9B?׮]k"p={O"ضuD"}eB{a޽v9ߡC^o$2==.߻,--Q*(LNN{#N.öm8z(d򖽫"@lQuFFF4M|ڵk7 SAh466RDQ&&&BcccLLL8h*[ZZbnn:Έno>LduuFG!ǩjr9hj]vqVVVd2vp0 CFB!BQcxmj;i||X,0M4sss8N;xSSZZN T*aahҭRq=~}`bzNRv/p8Lww7333R ZHT*vmvo޷mnuv?Vpp5Rׯ`YcccݶԭUw﷎522zi08Dc !B!?O Ӳbijx˲w|1Pr'N˽nkzm<%ny^[l-[@ȝvOnkNfc2==͹sH$C=tCal*m4wnNgg'QhRR]v $$B!#0fDzFFFy]]]h~ܮ]0 j'AVuSz_$杔9CXLӼZA!Hdi)q<ϣ\.SQJb* zrx[{a~q7s|o;iٳ} 0ޕA4p311~VcumiHPٳj5bX%?eB!B|iضM>T*D4\.(HR`MOIUlĉ,//3<ov݊iibj .p%bO=ض /^ŋ ^GG"pAΜ9CRᥗ^jR)>% $kkk,--BZ7;d2=ԦSO^3??28\H}kkk m377GMMq:{,d|{P !B!ꩧ>oۍ Z233 JeӍvww7P;1hc_4]V WJms}}Z_߸VO.\[669o~Xo뿕:kw18f[Nl7;Y[Z}sn<6QvV ?<0>>.PB!B!D+ vM^qt]o?o7Fz~oMr}o~ZO7;ם~N{]݌ٻ1Nn,ov~Fk׮qJdصk- !B!5n7HKCe!FJi ra|VB!B n"7Blϲ,̤,3B!Bw%Y²,$B!B܆&C B!B!KB!B!)cz:72B!B!]T`W2B!B!=麆SB!B!{Cz@ !B!B镕?DedB!B!k8?ȵ9e0' dĄB!B!)W\1c@4OA@)"3<_`1]o- @DK缝>7"G1\X KVWUy,=S/h.&]Pẃi?T!B!>ZzR;@f;[ & Uӫmox!tmzm]ށzL|PkubƂ@xr=1;_' )0Dyeׯ9vl^"Veי\sa%ޟs0Ox`,wϖx1P?|A('$M^wqܛ`WW]S{XB!B!MIv'= +jWfwuCzj4'zuŏN~H0iqR̻޻(.S8A8g uӟ2 5ͷg,zA{qywLKؾ8_K)\~ @5xH1`ohphWq dϟw'ku*B!rz}%P g  0jg@UעkK),փ yH]W\^yG)b;{|_Ne?@z~@zAӵSHԨoko]^:]TkNl5/6p, Ed}@k[>G[s\[XWo2s'->w$8񈁡kx~:W˩݀zPWq.Uks}qs63yln-׾uۆZS]m?G7}+!P%sW)W=g7-j[s 7?\&eܣHI!B!$=8V.`xHnIt}K5 + ^Zs(_{=nN CS؎Oȼ⾴xEj̕WuY[Lqz-Rm ^9TPvIj^j:k>v?Se.gO9DRg"1tE X,8hk%Ky|}A+B!3&_ }Q4Mb'/2 LÝݛ#n>sU~F?9Y|X'C=QLCPjpz@+Y`F$!4MaTGtСN\.U&Oϟxxj$—fl FJŊI գ 'Cwf_eL:R|q_d7Bq\յ:ɗ)\o՞({cELɹ2+;W8_xWϭ(^:% 1S+u16#6P@r~DL `0$pNgJZfBFX/י9mnf Ģ&rf)zOs$[ :ciՒ gI\~#"!KIkW"y7S ÝM`+ye?hHmsgDU3hh_z(ACEyYF#24Pry^Ȓߺ?#:fqz@OM583e`: g9g{a*N@{*.yZ; ɸih8J93Edol7'B!BP PP]#!5l1jRt!x+wNO6_'djJ*Lzȼc pLҢPr(y1Ǻ 8[pY58]"鏡)rfqfWhV Oߗfb4ks\0Ç2i ~L-&MRq}=hѺYH_d?IίqiB$3!50T;5޸\} G|˿P!WratJ1(%/`_I,b41aQy: x`{hs6V_) Mrjqq튒yx uoi,gdb|U'{,&Tm?pɯ<8JŠeL,e/bWfb4 VydKW,=^C)_?Ã3R+Ez+j?DH\uy:R<'(ߜ_ >ɃP IDAT]z.˥Q>ԉqYϨzbe8=Y M1K*3b|^j^]&Wvx`Gt/"3n>RqW2_%퍐tE,=@WR\ n6ym%c i~XXjPȓB!B[\7k./ _ ?z~o^u9?z6|!G$,8͙tB-U/ g8~сNg%\/]St6Beۧz9j~# 斫o/2Sy S/bpG{S]X$6:_/7z>4/8]InC%Z*YmWlT|.Mٜ_u Rb'PO'-R '|?`qƿ"+>A,-'|MARWR F-_1eL-T1q7G"9\mB!B!>&>%/o l'ӡ5{<  u{k\N%%+kW*9N{#=aω El4 0je:0_p.!Sg83wX0Igcr:aJÜpC&oL2DuEGlΨe|szx43B3>qWe:Nԙٝ67Lew_6{`KW{t 6Xbc U<^Zä, U})ݟ am+3E:#,e?`15_e7vK)zcqk|.z&k\^mp*4C*k>Q]_*6ӂU&aO.f[RK-h6Yua?îi(LC25R1N!B!ĭP; S 5%{~lޡ܀R% d* Q}<\sYo]y>m dhj{x덟~@9^;яisx 87UOom"F8dEɳ%uPw}^Q3 _^s8<0%d5#tt2!Vl O_{$ͧaa滯, yntMalm>? [\ Ϙ|a wޗa(4=b)!k5V>W 솇jClb_{_+V[:h=[.o^4}} Coכ}(]^L` 3E¦)>H/芵bg_Z?,rPfߥ슮k>>GDLpH|.^KSW西+gs\z>v=B !B!r߯E'VotM12.5RPw\ o=;&1U4I\/nl%ꮏX־64E4ہ(g+:g kLޓ5{u4E.LVqѭb~jɥ\?\.1PLWήfD5šUセ-Q ۜVmSn )o<K9?n8Odu 넌fD4LC>G j]uᄉu6tᩈ=0g& +,>ў3!)!B!Mo=Y:=Jg.[Ru >uO &zCՁ)fd:wue48]0R{]A2j=GAQuH )K.=ά9t #&(JezvBGS[7;:ղKⰫ/J/,ahƓDWk,>Sw|rSv@Bs*ԩ+Gݛtxs5YXRPy4܀:Sdr3<3g*ˏt!`uYi芄dgJUf30:1xhObv&h]~o) IuX 1ej9Ct5h7I⛈|PD2_^cD$tE xktޥ0> jj^MkuM2i|Ibz̊hl#ذ,A` @@SYgMbeS5 6bO,sOL'8)B!B @eUc7ʯ=95[y=Y̼Ky>Ƀ2HGYt̤\u5݀άѓ0ontLk}Hx |+0yh'#1Njg㷊R޿FR˳eٗ'UÝCl?kL7NPsEbcIJgl&_90\Z$uJC9#!9"/9w& b:F3[tC!L]^*{,*v3F3ǒms~gmUC:L֙9|gN%(!B!,?8U;iqhwtL}JC^ѽI:S!|;\o4N1td'BnR(9W9}.Y>gtz>ח wM1/g] Yoʥ}xbI=(Kg+9.:Y^5B/2ݗiJNR: ~,CDB:ih|>{Vx"_җ=aXy?i?4^\n7|h_8އ)^Nh6,L]Q=K5n PaK#egWs_+\_sɘDN \]9P]Up&[o6;"3L ~ϧ=],&aK9"xu9 <{k6yK-]*Z&6E \ς],3iiT;o)pOJ̸fIXk/?ɻtd t5 M1Z5>|oNZӳU?G%rs-wRp/s-`Wy]\K,Yvtg L.԰ =ǩESSV<jd@jeqllo_;4Fy00tJ5yl!B! *֑ jAlzlNqQ`(EdF*z K5IDvOf4EL9ִ i7nˣToUAvmnߍcZjH@o^XS팲JךyQv *]ϡ5?jjsHϥUgIrhx f,.ԩm)*mBXfF{|n}!MK>VO`hhn7cW3ڶvcf 纾Z hfV4 PfPqgRt}-nw۽g !B!y4)72-+`tXo|[E Pl긙j=*!Gd|66i>)m;cl޷u~ cny\uëNF9o-5Xx),R1Mբ\n}p.o57o/;Xw92nF7Lb|?c!B!=QsxAFX4@iY\L|~xFH7m`)%i3BͿGJކGmK UUU( yVB!;bKQqZ4wgphSi||͗GQB|ȃ(YS/1[?]=]jf*6xn^D0dEڡOeP?Ap?)NmB!.(yh6镓}\w|]Ç+C nkBJqx]1.\/[F{ ?>;GJGGwLB!xSJdd(Gy#G033>`">(djj @ !{G(9@$~%Km^q hfBe;ف}΂o\jf8?c@A:zfxJ-steμoX3R=dΛ,%v,܎C( ? z xM;F3j =+T1榺q]W@ϣ>3Sk|DYX\Zsz~[j Zs2GB!>QJy(4Nm~騾"JikQ8Fwm VNɌ{u]nFn4r$A"eilOlW?ë0BTjf4d%[c$B̄0 72@'4:ӽ}Ӊa}%@Pgg }3)jLTV8'?v0Z]Nw9Iޕ a;aup>,!t<e$O9̀} J hOu`syNZD!68c`_\A}/.n? ]Pʜv-w?A M蒒e ?dlp3o.IӪ >}enN}RA['cFczGluAPJ@Y}>iX2F(8W=b<;?ϷpRPK^ ^p '$v&(KYj ]|ڀqkž~ 4k}e6i?3>lz%eIˌ1PT4#OM(_#`kkkWJmm-tK.ob 6nHSSH۶… |ǸK$kRQQA$",?+Ww:::(..fzzx<~Mͭ[yf8tRgɒ%նm:::mzO}WqF r99q"YVUfOmm-իWaÆ_&@cc#< ܽ{t:MUUX q?9ڴ,1:;;9yd @*oD/I y?(*Nz)-<;-ŞA/kN Tu3!G !U"ٌÐ* Y*EȝLd^h؊mN> U/ FH&hPU! Yl?uDZJJ| !Ԍj٫ӝ!,"_z!fJB`};3;%-3[_Z)]BȄ@j6mœZ@zP)H]?6knfb>|MiQ^kk>BĞ 9!ԸE9!w5}'J ⏀"i(X?zpIiSY jYÐ IDATi7 yjjj4Mx VZSO=Ş={hnn& 8\08u-bΝ<TWWcb&0>>=?<[ni!عs'O>$UUUY]]M__k֬瞣D"Ayy9DQZ[[mŋsmRT΅m޾}۶m㩧"1::J(bѢEiΜ9.]EO?4twwSRRryٵkMMMyrJt]ԩSdYxgXt)i***(›obO[[P .pqļ:t|+lذ*t]'000( 8;wgrEarr/RVV+BGG=R)˙d||VLӤW^y={p(++CUUn ի^gy]vҒ'n߾Mcc#?O{zFnݺEYY}ɧg}]vܜo~ۋy2L~ڵkyٹs'mܽ{+V:O4Kkk+apy>CdH @/RE("ȥ(MjU5dIϽ-LD[j# "Kv`~ m5!`R'  Q(!Hnɺ)Jr@M6/^ wA!f!NIPX)DyWNBdӐr E! 3uȝKe: C~HEZr7?3f?XwWIߋI I! 8 Ҋ$mN-rGۡZF+cn#`v9?, -PxS@)DAt4~(Kde7@ r!rWAF(V/wzyIHḨ&BR }B~vm,7@t'AogoQ@V(zBe?7-<ҹvsuPOMTpGNdDV6$4ܦl0C ii-B45d3S%!~22rcp3Q|rlBUU9r;Q\߿'x&&''$P]];}d2 yUUUDQFUUXbL[nqa)..&J8/ΫfD"ܷt:MWWW>}*fx<ݻwihh`ݺu,Zu?͕+Wdg?Y>u*PZZR__ϦM```6򕯰xb!HP[[K,C;p?8 LLL088H4{JgjjjHRܽ{dŃCk.bׯ_G@ia$t])**H$Bee%< >(0>>(sN\9o<'133C]]EEEܽ{z֯_/,//gzzj4MZ[[YbEޟd2$xKlzzKO҂m$ ;F=e˖ۜbbb(~{}(QHrd|ׂs]|P(f~ DkA%jCŸ=1!̞:ASm>!w0 [4.>Is@$:%7A򳸮cǙI1:TG&yh}Fٷo _~y`͚54773==G}ĩShjj466i&>Lww7׮]cddwbY/Nuu5K.&˙ĉ رc}d2m*/԰l2&''QN>i߿?O_%x0H&\zym⋬^:ogǡۿk_71 V*++iiiMӸr /B[[>,ߌtttɼ?3ggͼkPp1^yزe ӿ\Yݻw|rsW_eɒ%]!lۦc7n_fjjfٲeX;v033ӓ'Oسg|;! u?k__ݻwzj(k׮D"ѣGG^x{9JKKٽ{w(իWgVZų>OÜl=z^z_|g}2v͉'Pmꫯ֭[{.EEEtttݻw9~8Νcҥ8pZ6oO<5;&_dmP @U _}f)Do?-Q39&^zϱ,m]f9 Ŏ^BՌG!tA2F1c壅g`nJd=@^.(@WD)Q6րԥr@AMZ%R۹% a Aj fߑ7Q)imz͂~jXDlp%ss9+z_p`Ƅ(n~,ᙢsmIȸ;H+ZjIp3>Wɝ(![XQpMC`-[0FiVU}>6 0 vX**d {DgӐZb &p~:(tu Whl9'ŻϚMѾ]E)L~zvEQQRVVa(*SSSۼ۴yKٰa} Bb<.Tp8yiXB!,ʷ{n=vb{ի$ lFQ8477:q9N>G}Į] ]׉l۶͛7i<B{itt .p0o6|1, bLMMݣjhh<}d2۷9<\R/^L<<Ο?ŋ'rH&y2/Jib&\.OW/nY\4<]ٶm[_;vN:::dɒ%QVVFccc~Μ9Ù3gdݔhǎmsڵ\HZy(ϟ19"JOǽGym۶iq784M"099eY @Xsm<+I KLk,xZl @ioxDv?.Jk)#̝ JjgR|X&w m@Y?e@(r!P46Y#a(' xׅq;!UF#?Hr()s-Ec;~o[|F뿸. Z?~M*ghl?OM)Qc3v^|}&}SRWA+`Υ2Fp k7aM_??, Rs;U*{R g%)!2!mn~0@Qeuճ uxރORU:#Yr%>(N()<1R\uYz5gƲePU˲p]P(('TU!4\.=EQ浩(=mY؟Bg( mڹY&ZHfo2aAW_}]apppgBc6T* *_ $\.Z!~ga:ܧ߫<Ʋ,$O>ɐfMӴ 1>>۷dy.--"mmΑL&3oRT>ms{wq}6+ 3F oʥpARt _ND҅PDy%$.wC"ڹnHX/J ͿR'u^)dG?zFn R' WW|a垃0d*@&#R@_uNNmӗ j9Jڹ02pW،D5sR` Ir[@W ~%/`m'/6dܕxoW9%东f1x[U*@)ɻͽi0 (rj_|( HV`v*v:2<ׯU#<)f/K^_B?.s|P5aq/ 窫SV۰ 6;)ݑ0ǎc444i&:[oE"ȫ.y; ?11Kp>cb3HyPZZJ]]ab||xR۷o<m[n3F%%%1RU  @Pzq5d3sI$G1їvLH @QxiH$Ф.E”)B>?}.)#U#ޤ{ $OXȣYr=)m ;d H2]H/H;ƒT=/2>>Nii)P(0yrtOO/^dbbs9{*eYyl۶mfWGǘIii)6ll6KMM יg~[&۷o'q~?WSLNNR[[KKK {Gee%+WΝ;9r[nL&dܺuUVl2bofxxo'Od||l6KEE{arr5kP__eY 055ŝ;w, ]Yf [n1:uTJv$,T:[8z(70Mr֭[Gww7iR__O{{;qҥ|&O۷D8|0/RFTTopLv>ޓ6lt_iZ/Eȗd?6i%̩+<٨{g6]j2RzɹR'ꧮ) -u#~ ءlڝ]?<ΐ_i_x"/^#vN BOBdO[#d=V0?'$hc;Chs- yҵBdx/xKQo7 nw،He^),`ߖbzM)^2)8%'f =ۘ9rSp}ͷJ#'Y;st'lybuNH0/<Aх04փ\^) C`QKMpdEVBӐm<MXVh[}E=3|p W1^9+v<*&+ӨjPݳؖÚ?[ôd֯_ϷmsEhmmeΝlܸ۶D"xǵkPUnd4ZZZlZoo/###TVVSOsN***,\nfffô:477TBضMOOdP(Ě5ku2>wj*֯_T^^~qt,Yŋٽ{77oƶm(/40uJJJױ*?ovZXh?ݑOxsP@i}5O_\_1d#Gy:~#\/O犮[ɀ 55;B.H3gt<*MgJN&t\D1G;|$$6n2'u ;`Q d)iH@M|Ōf^:7#OZeLǤv;v> i]&-u2]: EW'gymb=7*=_9~Ic[Wnf݉WQ"s8:ًs>q<ߟwEi?IΔ_;YHU)j"G>7s H!p5=ߎ23U-&E2GYQ 2g ^gd~ wFؙumHwrWX˾0ujN/4_D3IpJ=eGI[`\,y]׊2/\]II%8:h:On06о ̯͟3j{LwR!Tm8W1M{~A,6iɤ¨_uquq]#G~hnnfӦM=z#G( {졭-_f͛7bjj#GgϞf|||loMYY>(%%%Ioo/---躎ض͕+WmVTTܷ4qp瞾orymnذa^ߛ%f]Kww7& BEEIMMͼԬ#Gr\kk}9z90kaY[nQ&%~ IDATx_ g`fFD)!)N8 : %C:+c/ 2NHl\(>=,J&` i9b7"*/\B> 3)z=CbRWBy%#srVpSŭE\8?ΟNv9^}U._L,1x7顶60Md2Ioo/wm^}UnܸAYYd۶)--ŶmnܐEԩS(Bgg'xt:$UUUN__i^}U_o3H8Nt5˲Ftwwi7n`ll|+W0<sp~eYq9<|M@QFFF㔔055˗~m&(ΝΝ;yB^zr&&&>up]7oRSS3oܧ9:Ν0 @ ʿ=D;OK~@#E cs^&@-;? *Wro7(64ſ")ίQMKzƒ,^zC\Q{@њ-*ɯ_4EDt]xɼ;UK>?ݾ`py  @|k <>_:']+,jOѻAn  p|XnKrqxD-vӅw*IIbN)Iԡui"7INb+z/c+  @_BoM_/ҐX$w008(UM(;)u"neSDט}Y ޤ}5;AZE)ᎇ"q*c87MMˈ ݌gب g @B/o|Y`}wE0`lh m]UoJřHdfqg`_4 ǎRU)Ey DVj @|ЗXIyyy/3)@~1p E?oT4nfE$ATEeْ6TE%dP"ř֋)@  < @<:[n_ Dvr`JeE|p%M;f7o ňPE:_ûOPaTZYU#\׎)"7Iϊ20BUrBh)he xEǕD: p b@ ҋ 3 <nP6mZ5/ -0c2/V0{/Z o13S([Bad% xӽXWqw0PQQò%DjU( `YKN 3j$O)Z[jKw/"Fus/=7Ct3` (m? .pX?RJ5ğc1`6J!+!jI&4Z rE`凾ſ}O='5dQ0{"y7ׯ%7pt%ۿBlÁNNp?K N]mĶCtj0(WȹYȞR* 1/s!o#v*1Vv3 -;}ُ\X,K^<˂eP-5<辶ׅʚq+o( VbF(:%lZ"lahbS-S>ӼNk=T%*o(j76J͜IH-}^Jyn+/6 4}s$E"OGʥVBkpKu[Њ| ~ vjD6&UAcMa^?Zm33? 0Ah v 4m@+TQٽ`@A0`45g^Ҡ}SL3`u} I/# iPM3RipΑOGR7jQ*y8J=Q)^bzsr6BR `~;'~SV Zy&q QjؼZf׿`@.G!~@&w]ޮ㠷  v78~n*so% -Z 2?.}nZly[pMVB2r>%5-<)>snC?'s?=rvJ%0W堕\ r7sGHjv@x "7昲R|j4ܔ~kɜw\UߟoL>Lyg&xH ɩ&bmSQ,5B"!5C#pwzds9N+RObG6_ҍBEAjtEyy>eeS"B8`]c) G7-mCȿ760 l9 -${~m^S}};GEQ9`~,L,0XoY~Ղ:#J&s0RMO%*%k<=VBxIYˣ%c.8ĝM2mHY*1X$7?[>ZJy *K(mo6h﯃sLde9^%.cF!]?9+|;[ y8|KO-i}M= &y&z90sq#AZmeu,Ej)} "4b^ F@^P`wo<=OZTIK$M!7Il6re (Eo#<+Xhrcc'ǘ#N@Y=s2ko r WB)Ŕƞf GBt(6͝`^[G0e*@!F!P%*$SK!=yP ;'r&Lm-^}) _|?=2h!On-t BD6A|d[A˸Ii6 'ArTŲB/fȜ e"4F͊EofoE0W6j'PkCB9I 52JTݲ,islTb_||W2H-rɛtg RAP-N_ %dwrBSu9㺤rPJHglT&P/Qq>!qY@2?ɈR; SaEvlt_̽nJ{ ȀRȳ@+[撹6nU5UȎ̈Czǿ˳ V>iY!`( yրnz9R|3{iT@% PXB)!񓢀sVR!SзRZ-$Zdإe]T7(QQB9>Qyt9d^eWWBYaӺ#$uiDhF"[NZ%~ 5K$X!<;vfL%Ǩq)%oL˘vm5y١?CK`F7=Pمr%fpޙOf5wČj3L{}66J^*@~??8Ϸ+(&QkWm(Nb<-Zxu,Lj-݅^Bq>l!>j'* "Q (" U#W5  cs$U%")kf"z<~Qsa ȕ¥fBV dkEկ Oͳ~_KүpF@Y-OSF@s go!dK`5l0ɽ-} -j 5ʜnXCC}\ _}?T ] f!uLl'|+޽?_CRG k!̗%?7OAtdN%}݀Q5hQ.=IeJ">鲉IHE}м$~.3n=`4D!n_Ob)L6Nr>QSIVxEp/'$}]01="GfgjJcRoJPQSs,灧F&BKʉEuQ8Iۯ(/P˥^y׿ݷxKɍnͺVR8Y?WWBrEVɺ4 @<ëtDA~~, 2l_-ϸ)y.FKR~mn.h= ;+!+(:(ϼ%NWQ}' _IJKBӼ&*B41oB=&{BUdV.tslEyrJH wDj~*m3ڡ]/* 8}lA|I@0/%)/Dm磻ha/sĪ @Wԉ;RI(-)y;=$CC(5C,CØWp3 BUP##y>1x{Do8wa戯0 o@g5PJ!!? 1&%'Z h4zK$6n2'}F:+Akx TRggO}FVM5,Y =p@xj}L(K6"Ĕ5 3?}R/@C~:'?A\kg̓;Ͻݷb!v H$JdY2#;$]S{:NgRS35$tOUmDzcW-G-k_hQR(R7oۙ?~-v|X ;w~g"mϽ9H/Y줐ܦ}(3P4}ԛzG9>*R r[P|]6É- J&dC>(3 VQQ3)ᶋcjE9ȿ wȝ*mvkqrÆgĪ!ZLiw &̭E.\(c9l[ X- ˨opHWkG>% jwxȔItMT$pn>Xi>E. M?E4|IUmbM= a'T0RvCܵÍB.ǚ5{/X!s_Rj!#,uϖ ϶ˠSZkǐ]\ʬOCB\v }B_e-FPմ}b0A?/lnՒQV"gZP"fȽ {;֋T9ct\ոdmk  ;JSp'xMK[\P=L:U\4i4]X}AF"[C 8ZZ:ȽN} ŋhK$fV5e.7ꚭ"8։5`quq'T/cM6oUB&7~vF|`ޯuQ{zD݋:fH1<i2|Z TFp\6zu;jl* ?9lݒ-2+<~\5F4$kҫʇ-UG^8%O;u|)dcBwY\و]牪ReL-bhr["8-Ğt\Uڮ~L]Y{2M*{鱠˲rhb>ߐgTZA[6uʠ 0LC*ifWRɧP ÐNpm0u$,JȜTڣci"ˁznm$-IrQbjzSIy*:ռ^yeU"@dחM:[neuÍBT _9 H|8D8>7]i}9+]/8wvXԖ!4=|-^4HPuBr:DN/AR>!Qt@_ /OBG@_+EZ7vJH%kDNM)H6<IJAͺޣXH\ -yw9t * '6F&H\}@˦8:_oFr#%N)n;GƾIT]*mkpCR9VVG?] KxnxLKʕMsU \\!)lq](r*B:ކnAWZ07 AX40(yжxmɓn5ai<cNI5Wׅmoٜ>aaR%֟q!ns_ SǕ3Ƨ5 u_y2e f+-1˼u%'os!QS3lK%?:l̟*U~.*h2lĮNZ6Bjl"*_#P[*Okܠvk [kQ!I2t %V!|Ruψ NL\r*u]M^+gPӗb6-'Q5m qUf95c\޵*(}b%6IR2뻱20000TeQy98DKX-}/*̞Fܴ3/ S]>Y UhH{N6*PW8 L`[^C#B~#~x垐F^ XDen1rjػXVgjnFì\ȿED{Ăʝxt﹄h u0ʌu*PFIo 5I{LQ'-nљPQ Qq+ߥ{Ayqp'r0|vÄJNUkYu Im.tϺz)O}7vwQI,PUw:E8ŬYin\@̶mQW'QwD$u@;,>1T$nչ'"(>R w6|(«*+ uiBg ,k}V;$t0h?D)3d"+sr22ﻌuw$ T,N℃&\x}}rySHLDyi#j+@X%GJ b.-/ܞnn5#-} ԭ59c#VmU[+n#ۭY,gp(35F#/xu h5 ;* dbKkei1V,rݓ X5]h```puPָXkX&٨('8F0px Y{\ɉA).K@xF̚I$6A99Ѝn+wxglJԱ0Iew怢]t uc^}Ol/i.!r k2H\ɜ54$Ir\Y;a uhE>ɯCG~ <ۯEY c%HŌ_3٘&fUcb`Kl0+9˅9ddV³L$Fsbe%j}2\^q CU↹fQCq3{ec1'eV'J B )&_b>@ܾ$poR(D_kLRftV 7$-b'A+Z'}8yq ]h_\gS]Q)BqJ0ҫ2%gABr@bMu+9' |>͂;2=!+qXpR'ߘ/sAOy,\}g%lVgE\S+DNYʴGY|Eh*Hm(>|}q t!N~^k%wj򌬩VZ*R R m֬/ߖ+u B )kGi/@Kvº[! q_ze]k&]^FUdSvv(Mc5#yZ/@i܉< * ҽk_ !#>J TM@Iέrָ/-4'#Wx!,>9\>(M[ _~JA|i*qu+lSWv)HBGq MlBF6y^wgdp^D"9Ge 1ʂ,[EՙCM0=2d_@閐8$!:$3NX7|Z$vEĽP|C.%$ExFRU'60X51g`KEK%L2a9f(='"qrٶRZ7F7J4a{ [Fʩ륂7KҖ#,7򡧢!`\KwN O1YsB61D,8w`W ҧe1qje&DFAdeOΒD dBO9$)sCz|;Q5f["PŒ?z£CNUڍ*|uHϡY_[./e1P3'zlcH,:ó:[\ٌ3 ny~> *qFq Zbr?&"iW_IU6#lL 0 Du)۠w+.ePxJjs|lWi}{a~(N."\mMg Ͳ79Iʍr:;:iDQAm;)_23|Vb}\w(}hk^-F_1q PYkPZ7pSE}?M@ ߰d5DP^_um9RҾX:ӄ^Q"2O${'A t6X6JFl~ H{JR)Q^o!) C&Y:dCS="V!.u<ݐ Qe@ ۯV9q::C?\KM-oT4QS6$X^wt:`5=Ɂa(ShI+# oB:,(k(m@,yaT(`)^my/nvku:Vc,_Pѝ^JiY:: ܫ](<7!q ~x&+F3bun"_c7@qSC5mXPxJ[=IgvhPTocȿ .{p^Ms  l:Ï&2U^v&?Cj}(G!z^"ͯ<{稸|f1p./pT|MduΟbWYyzM8 Dz _++ȷ^ `dJn xq2.nr4(YX zM9Ͽ{ DvtDKL@[ϟvA%:T— w 5.zәYPʠ#.:+rlPzCO>1 '="qI^#>+S߬ (4008:HZg3U ?`rS). j`B^N)\ns_MW2щ+|rk-LǏ⏟ޕ8M{`ZJ-ضydA5ѫhW\$+QUZĥ1<+d]J!_ >+|pd(iJs(LZ uJIk\9tѱ:(v ϛad`Ax}|Cg4]bc!EpUJjbYUg50000P;{!=N>Jܸ"Pm $d}CP$E^sr:;@eƑgh:B|@2QutFd4m{$K'G/٧$˧4[ŭC@|@)udZQ~c!xVYi4 >8uz/ϿϤR XW ( >~f'2"P%!g2/BEgwG׏Y g````````A6*2*000O,FJwk;p۰icoPo_=6R mK$5QVDy ~I,?8HeBFD:C[nAi$,H,4($?T3tZI&GHIfl*i`(K@Ab&砐~ C3|61 Jb3$ Έ(Vbo#9i CB8t +QKg/e"Cq3AP/^qXGyQvTb!=Y,qz|{?8-ݳu =l7 fvظn3)L BxG߅Yz.ςVU5R*Ⳡ@25r|u_N=x!ޫ+ M|O; ɹ'n'Y-< ~ mպs-=ܥԮV/+.Y*rH}R&wɳ }vBŪˊ!| `r.7N[[y``D{க~e%>_rf:z%i>>_H^kb?[xZ!3Nʗ߁E)bw'K%PǤcwCj_DGɉi}.@;9vSN&*#&哜3旝.6Ě'?ef@x%rح̻ P~'B?8 $B! 5$ D|@ mM>!FM48}Y#[)֚-5yrpV @5vCDM9_~>]QLC#liJYཀྵ3JN?s 㡸CʶZ]h(Dm+nVVZdJ@Bש [!s]zX.6ȕ ڪ>*ErNv@qg 3{.p@Zп: B ԉNUXP\NNb DUu3ڹAbohȞPY1b.>1!. 6XS!s&]!jb]L@-YOr.Z e._됺H.Y*nN;$! u wKNCOb}bn```````````pPʑ3؉z_$3-vˎ(M#zD'Vr/d#?.q(/vPaAAWt !W]iH !oUU!}]5Z&r^$z!9FMZFGc?R\ +qŷ :uiW8D9!< `,;.HjXy0 ?]&}"XTф b]^9I*A}U,_䂊ۥ#lH.hr!<vXBQgNf(KvV5 n; U^Ɠ *{ ν EPw;' S'EŐ2f300000000000ԥPòcNl#oi}NJUs)3gcI;$:fa'Mk+Ys+t{cfw$V!QI܂nYH+ٗ߉OZ!W D4q- NCarMLMu[\_!/GEZv-gw@iSPW٢|JDy=`o7H̪ME.u[뢦K$ ~v<}h$ ı9_rb=j9 >Ȭr6 94W@b<(ek;%qρsERwaN3U[A{D4=*A |!//Ci*Hn{c'`kU*Vo)~!n6 ;_U BHRcʿh!Kt )8]<יiP9E^HiL8ĸɞ_/W^CE|rb5Z-AmWq (Oh#׊5Kr>σ.DMp~ Y猃ʁ] f ꈸyo+ IDAT@bXxP*eZ2[/D,J+.=9pxx: FKp @F4acA5Nm]OOrVR~FBfV IV ??QHBg܈AIN@pH!9DNp@v!bi9R(+s2_?V3U^I+KqVań , ψeS+n~YÅlrKop+* y? +)pVC6p}3s!.( }"?OT": m"T#$F)PJPUG e ($ସ #|.4~ N@t^"{̬jONI/e](9V*&ZQܤRCRfp\Ox?5rY.s)p(eVB][X6{rTp'BC(c"?Q#0}j9Un4KBʫ> tQP. ?!]]n%y[baeBZ?9ÚX_ZETE;ى}ބߊ-p? ^/KΒ(=P?cY$U\s{[ ?ud6i```````````%PcR8JkNb%TE4G%!/w8I Xm[k$Y>zGpSr| '~"q}Ě'6Ov8udK;`Ӄ_Խxt'Ê%IN8Dq D7὎%.՜crE!<ѫPf%^HƳa}Cb/mN!)_yPxIsv!c>IH!UR9OVb ][f~bBȁ-i-]#Aȗ\<)oO~"S?Lx '5JS>$nV$1VɸV@rvJ5K3,^D~vKBEC_)%DZzf+ˣ!(*2Z:.1S)N&C> 4 IY&j $T_OoEFN)<ڢ·pJO-TSIPXnNA|o'!|C% /w2$ I9Cd/JB[.! >TUϕv [ g 6G7yJ)d=oDBTPDP~IG>*(iHHc3I[iCtrs/ P)n.maaC !R0%f>.Ci5$@U^QMtƼ^*O]biU(xr[eF:.*G3bIBr^NBiXI-u{ "rʩQFr_(iZ*6 ʤS[B$i+]ݹG[ %wPWcUTp9)JUe*bUËOb)iք^5}uȏ+d/wG%{_% ==S!sMoC 6!Q-[hG*n)ށ#PwT7L]*h;3- N,W :wueMacnHPݨ &bO^:MtteX: ghWwkVFRcY?`/(Nzݥ\v'eӤ@ sjdh& H؁0&K(f`4eNq2.q\_2%3{XBU=G\,,:Ѧ:(2ZI"FRlry@H.h=v! J |A ̸Zmc52ց=KӊiMAWT)Ӂp&Zj}`͑,!P:L!@ޓbzlB\+sXhk]*O C-$ڤU$nw> cwY`VJgI2/@Nq5 qh'0p}ӮX#h ,EtJ2_;DFҶXg2?~?~z;C0J300000000:iM@Ȝr >٣UHJH*)lU|dPQJ,bg$3^,X{1ZHӆ6jϬ:qS9Fw40000000000000 hӊ,Q'Q C@|aYQi/O5byY1bBb -S($p_}ԵBN F߇$R x?_KuAq x?ԛˇ>^v?:\nX B9~29@s vH ȕHNyA3ӑ{x2e {/f͢ 8q*=<;`s{a… СC?70aEoC ._*SL!Ja}̲,vůk:;;|Xdv]??a…p~m֭[֭[/E?˲_?us73w\4|{__RJ)otRiI{ao2{믧qp;wGaϞ=axEe?,[G77Yd <XEg?㡇˿dɒ%R)<ȷm|% /smsNz!7od9__PCNԼ;I\ <<e*L+u[-`_5bsN#ٓir/D_0Zwg^!ctA|?:h] N#(i(c@ݻEQI _!} gI@@Bb:DYzid ܉`J#ڨ H%MƷ-,XRBP!]bGaÆ L L<ٳgN"i ;9eYA@>'Ʉ 0aUя!Ut1i$R:ubXyF)Egg'fǏ|k_cժUq"J)ZZZmG^>\l6KWWǏcۊ+Xf yA@EUNX$͒J驐1g-Bss3l0 Ǐs+&ۙ2el_l{>l:dc}hhd2ĉ?9u]:;;={6bL[GGSLv6000PJ ?]0H G Nb'HϺx,ޛ65x{H!:¾viOIM_EЫD߄Y $ ڇ 6ݟz`A^OѪ!6 fY 5lb1Ɲ*җҥK`ӦMݻu%G*SX$"Rìh.\|l7x_yE?cǎxijjX|qaqmx<7 0dÆ ܹqg֭,Z(>rss3+Vo$Hi&y,ˢU={OKK ?R4O8֭[O?}GXn7|3k׮@ܼ/s 7֭[y7twwQϕ7ĶmN>}Վ;O[[?MMMcoxg[Yv6\*le,,y k````N/y%}b+Px'D{x:)bMw`ut"KT" }qdAkA{Vo=:n { $VnHתh /A OҐwzP55 b!16I(mC毴uG(nK~|M4_Kٹg!|О!|G,Hx_ꨔ !5RwmZH΅ oB p- 0(8+!\\ۂNˁ 7@&P ?r$@8:Bl%?_u=ոouS<@T56@ "vp:0 NAq=Doz}Q?Q!gė;劥Wp Jo@^>pbyWzV`D}EeuӽҫQI[9#S֗x(Vg@5S-[F<gƍ<?. L>RIɓ'rJΝ˸qp]bȡCx'پ};A )Lb . gΝd2/_Νwٳgiii<ǤIg<ڵn T)9SO=Ŗ-[}mN5\8;lٲrYz|_' C=~7<-u]N:UibQhqXb7p&Lu].\MXne;;;Ylmmmlذ'lڴVl0-XիWK*P(pA~߲g(T*sN&NHIX,\UV +e޽b+Vpw{n8'O&P*xя~ڵkYb]]]L0x<Ό3hoo'ϳa|Iƍ]wŌ3xym5kpsI|Imѣb|r(q=\wupQ~/Wøqf444p5װ|r:;;IRDQę3gxWx΢EÐ|>?e]Xdǎ?l #Gď~ʕJ666V&/+rvZؽ{7SLoN?!吝;3S___D) ,XP{jk+r&I("eh```PuubnJ@Ť_EBTB N_v;D=MtLLÔMf͓cHތ <ũk#YDTm vK:=BY 9۽`7AD?'C4p!>r1( 2k4Pb!> b@6ĚsEQ?]p*IJ¸gσ=-~B4+$Al = 88!JKଆb!~jPm R~ [șX'x+\OeY%#ubye7BtJ?g6..)MtXB>j-=KC.&m}J;Z'1#N4[X+CF_ʏO-hJnQybe7 sOUhJ&@VȬ֮&fJ_Lu!PT'+ v(= dipz!PmY q̥Gf|㴷3~xطo_|qYٱmʔ),Z9s`6ea6ӧO~߿B@M8+WG.#H8xlVX9spwb B0 [dKr[nEȹl2ٹs'tJ|Çw+_Cm|Qf+[4y/7>'Of՜ޭ /Ȳꪸ֒OeҧLL0yquבJ*cƌtwwL&njjj:2 b1<u^j}f͛ǵ^{&L HXx1ӦMcʔ)Rjkk+MMMh{Gkk+>QN/"7G}4֊drT9~a(|(Qajm!>ˉ\8JYY,v,_LYJ" &M$!Pˠ:HԵbRxI,Q3!RK—/VJ.pw`;[?7JbP|SPwvsrr"eA4_ Tw'$CZNy\W}9檞[Yju՚Kr[-ɖ<`d%.#uoHnx 22ƖeK5Kk9|Y ;#P =fp.!Lf<`3:ql(8\/}){[j~_ml @䶱sS|:Ls@|dNZmk%"iwEoPqHHo抠f' u a%xʘ} iJOۯB,b03!9}G!2xpa02{!7gUbK+2voWAr6 N*דw!\[(L  ??MӲ<,^P((G^`ddkfGCC{ttݻwpZ())ܹs|k_8uuuTTTd+))a͚5;{UUU|ߥ5kְo߾0422O]s?e۶mN|̝;f^z%P(>8/Yz/h:H͜]x6i}"az-Ǹd2L&eȿ!bQ1W1@!&8"8G?EH֬YC8Fu<O6#̘yzI3͍ۛ)SRRB:&7S~i{N~J&$ 4MZeĦW_}Gf7t^{mI[ZZXj${fBy"^ק12lؕϲ6زlR~x<O<O[]H(VAA188}lٲMӲtfC͌ زe S^^>Mqz]x<*JaZSSիihh  f=%}>s{/sޟ|>P7cMuu5۶m{`0}$7vȝG[[[6W磳sƐ& !@k>'TD̳ğ_?xe'b@ʏ()4iѭpUpE%7Ӛδߓei9UaIڜ .ae ?l"yZΒ7r"-vM[?!8h1% ~)霌 ;r,7-3Ɍ}zcKOdAIprד*NOmJn' AD2e˓ь-fXio1=boDZ)D/2+M%bW(nX,]vqzIռ6mƍu={pŬaÆkn3όw#4Ig6^p8LKK eeϥ:###B l>Sffm4ɂs۸M͛!3//^Du6lg'4il bR30:;52 l3\QQ6l6CCC4MxyyѣGI&vmlٲ%ۿ[[,XCk144 ͬc^z^9x ǎchh \нkǃi8鬀7[ضm8pcǎ1<(=yyy|+_yS+ {CLD/"Xw m9=pVY*o9x[(ɠkdCnt6/tÝRLxE@@D+I|Kj=v80]rqJ#BtCP >1$IHvE|N¨U2\ 7A"%"KpTFT崐u|#"&VG!Zo 8'DIwJ8="Bڤۮؒ$ .:xat[O{s=̉M;fvfW+6g*u` @|@- A+e~1;dj',XO9x}YAWHN74/S`.!mbU[1/Ιs*ZdAN󈠤5)sjN\Hw%+6v{addSNi&֬YC=˗ufS4***mEQ__R)*++[b1:::7oP(~? Wy7`&XEYY6mbhh&]Yhͻi{vww())!Jߟ &~Kf v2bLGGGY__X,;TjǑ#GXd ֭/JKKzsA^J:~twwxbI\ti'alܸaqm1o@i,]C}}=`pFb||D"A${ٳR)g}v㳟,&LRPP0mg),,dɒ%,YdZX~~> gE4]שz gKӌ8222l{QQ۷oٳIcIntl?oF0SZZJ]]TWW3o޼iÙ M{]]]6Xf33̛P(#5+bCh?U~5G{H| !YBV~_Y#p9 {Ocvh t(&L5%@blO@M< EIqg*!$_ty.+EU<|^c^ |@f-x3* (^PvLD&r:'} smw0R ͋~IN@9OMwܱ!UT3v=(WV0!=,"\|=^5r1ݹ"K^MkYx!9"-Pr"%: q+E fR薤 P >^o m6HiT3B'%4S$U~\&+uBR~E]*BRR@`3)gJw]ֈf֒FԳ`)#Yb%%%(//'Jeuvv2>>NCCԧm5kPRR2]WÇ̂ g?˕+WXbeee<zzzصk?0wu dhhg}s]7ntݏ|o4ĉy,]O s)񬰲tR?i^zlٳggʕ|K_4)))CC{{{v3ZSS|\t]>{9>ϰm6-Z`Jعs$Ê+XbGYf PUUo8x 粒%K/~+WPYY /L_666ɓ'ٺu++W$0::y'߿?+8} _cR{칩7N9q7o[okjjf.3gMmm-</^dll{*%ͣ۷|8NkllϟOkk+455QWW$]fB555~xwڕԩSlٲ%.Sl'{UU<.]bttG}s1::[[[bѢE~}r}uV)++ Pc_~=MMM$@b>Ob<ӓqFjkkijj y\* {Kt<.&8V2[1M1/b>$I?Dd6-`Iu#_.f61#c$pRq7T'Nl"h &|lnX2!\SBȲ̉|>HS1eV~B\*Ȭ!K?o~N"/W|F}, XS~uN)c3c+t}σ"cF d"Jss2!5"4=K[ɓT"eʧ/Rj䴋p#Ո `_R#Lr!|#455Q]]eYꫯ_ [iӦM<Iww7{eժURI|2^IdrƐ"zRmƭJKK CCC>}lL蝮lٲ,YB:fhhbx|R^˲H&ٰl2M3IL&ٷovuxD"L&Yh$IN,$Iɶ62mf?3ǯ' ^`ݺu F0̮\N<ɞ={D",Z 244iooŏ~#iiiaɒ%x<9t<]]] #rmi&ypT*_[2gΜlZz!ѣ}xbZZZhii{&ǏgǾpB`s̙l?7l͛ȑ#{b/sk^k/^䩧իWpBbX68wzqf^WWǧ?i8СC>}_<yfFFFL&oڵP(f97v}&pD& XjZI/$SbYH; 8A*W8#`ԋHul^>nڦU'!k NbENB圸띕l3JAvמkS'%p:5(3Q2|Q1$Թ"ժL\C:1A[ml׶S9{}m$1źƞ?nb~U|~}$߽|4 e= \|˲uSUUE0̆x$ JKK줽=[&FFFhkkfsNʁL&IqܹlyLձZ$tuuŨɆݻW IDATB̟? 9// RPP@ggg6uTTTd+rI,0ln-۶F$IJKK蠣2줳+VR>SYY'OlȎaETyuF|`6tuMNWWWGmm-a~0FgggvUTTPSSC$4tuuT륱9s4T*09sd>efa/>i5MӧOg܍:UUU466288y`EQXXx|^>R)(**bll,˜9sK*رc:|fl31 a.^XTTDcccRf*bxxbmmm p54p<00ӧ' ,';G`M̙3 eeeNk|+..tRBf~~~cmuZ@YY>0b300sHR455QYY9cUO:eYL&fΜ96J Bw`!. m׮mf7R0 ps >ЫP(;BP( B~«LxW,Őw4Y>j0 mH>GD('%7i5ED BP( gwa>Lfc tuJ!=mZխV~@4ճhYk7Y=ڍ ӀC`g0¾dZ8 ޹0f066W zךyy!YfkjCRU 0 &PZ5x 9*$r#wɼXc` $ |wo(#דBP( B6v 9֋*HU@mjLjBoy5lFǙ}W[+6-C_G]ޗ8#0KQ*  }$ocpӦ1o$Dwmy) }9D>:p ,O40ɡ.oxvqP'7g-9쮖;' V v 7P |w-rP( BP(ae%H1NyHfC4+C+G[ -4tI!8fFӽ΢a_ q`_ރ3҉q zoq :@c3Ý`ד`ŠD򔂖7x@6U5508eOX$^U$_s?0r{m2b@r'ǀ {\B`)nrƾ'¼1H97+Bh%-s7s+ݐ 9B9>=`@<鼠+%Tě*y ͉@; x Rڝsh7B`ICRO}0&7{@)tYS3 NYK 3xdVGrƞDrP( BP(7.@taCcNjBҊoLj9Kb6OU\Њcx *W-Î [\8fo<U!t0z Kz}%s8 [X"j@{9"q;Ƣ39% -Հ."bv-r/*kHWH@\ ywfKx:m3"@"=(^+"H`ovZ@ϱk?=DFJN0?j-K-[vԽVNoyLBr8()}hOn4+S0}=%0> \"ȹG Y_E":EhtႮAlȎ=-迁sm!|#x R&1j~9w&3$c,O cC꟧BP( BP) z.B0`??@aHCa &v"KWRG3o_7MpG]݇5փ^v1#y)Ho'O!Pat@ꨈA`l>"IC TMrL,WL`=${L<o$IB-ث: 2Д:^pzrD0[DlOgY+3WF<*/0HXqN^7]sC 2?Q'P!Pr7ӹ'!e.E8kG[<|<2g> {"JE`z ŋ(#\/s!eqtCC6g3k3VL? E*%!Jɓ"2g#@F5]֍SΓ7Γf@@?b0g]!_+6eU9Y~y8HDC+kHӛRBP( BPT`ig0^VvoJn-hɃ!.1*v,,l2pl 6M,cOKbX}as3~q$Di)HEIxD%ʻ_a s<@dЬ!f&b6{6iSC<8wh]z{˥rźahz2sy ] %@Tɣ`0 7_B K yu+R> O{(XݮV[! n.570+Č,蔹ɻ U`/gbμ9sC+`\rD֧QVg_YiI5AϜcVMT( BP( [+@[JުO"Oߘו-W_<G{֣y| < opJ5_Z$*7!} *kCP( BP(75(ߺ%ogE2f[ ,;'PL`n3h׏/o.&} qsEgix_]G87!N[Ky#Rf\c;36^d^tB(@jv"sP,w4iәESD|i੔:qƻa=,_fvKNng!\4@MevCgSr$ٮ7,[a/] چg/qDxϡ*? fĎz׎sֈ1? g m@z̋!CƟ>x\1ᬔ; 8-Y^Z[0csLR-MJwޯ$ `!9 [%2pN!WFׂb{<4ւutJP( BP(nRD رA`>[=at<쯰wn-[P"NIPDmU})D> 'ФB !dBϓ`1#N\Y)ʮߧNA$Axx.EdHvtF:yuڼ,h>$:R!3GD-"` s{Y. 0Y.O޹F[ @’/IY\ݜQֈ$'E !u2V')•B9#37qeRPI!Y?y K1elyff36=C+Dt;}l$ys']/;v٩K6Gpϣ6/3 V\#zgch2Cw1|P*K{\F(6KZ{l >cbW\rIxb9tOA `Lr)N6N47f&^C9RkLj9B7lOgݱTV0vkSxO0ɓ`{03B@+qϩݹ)+Z+"u X_y ;~ɩnt2?Ӷ׵k~v~ʺ\;uצ׵wZ\er{l{3蔱kz.NC(sۻּk$3|CEGg[_FQ!xѺD>?R.XM̯_}/ވ}f(._Vcz| FS*MUd%_n^+:gtXĸםwa,mpt\ۆuwCm&$͹y=[v)IP( BP(f7r< śX@⥒]vR( BP( }"Jq e T+ J1=LQP( BP(1ڵk)//WP(j@*WBP( BP(>p~e BP( BP( [Wu4M"Ӑ̿)c>q{ϭ! 9VyRN1T߫+}L|v=4_^wsSsnf#wS(_hD"`JA .|>!C!H0Q7Inb`Pknb7xMl1/o=g/LuNOg4̍A8,s!=wx\t08ٞʹg$s믩wڞ ǵ>KǑuzkf(df|>Yo麌f09m#!>w>1&g:0>ޫeYع!xka"q~Ɛp7%P=\{c{N?c]!JqC#>ru?GJ}<.{|~k!V o_5h0:!4Yk^.Dɟ7h{!!pwkIx?mvf".K~腅$|{{o~} $wH9B9uJͻ\I޷q_*Z0HWo| -[+_!~=|?xYY _@//ܵ {ǎ%j?B_Kor aŸI;h~YCG?zcߺxjk<"oc~[ߋB1H$wo[~VRB'w/o} Uq{nF OM &֭ϓyeˈ?Ծ}~_} '$c~{"$o} oS}~U&Zs3/eD~7~+>'xd ?MgΐϏ5kxH g׮Iqb1?w#[%~]_|9'az_b> ۷_=oㄿËw< IDATU<2G4cĻ+իW'W*+Cx`+s}m56㉔N ,F]zݳS JЂ` cC >O<"yJfSx\C"wB`!I0E^ <~ %% ަ-~烯 b;B>"A߸…h0$TT_ߪKK %K⌎[X~TOBa!m]P~7M|;wPoU JJܹ{誯< BLBL `c0Oک&{7m۷cVWm훛6 NjClllyu߰?t@ؒM^e#yaϛGkks&'۞EEvhBq0*+Q--#F`v zi2ֆ{D mРokAFYzqҢEXajnF1kXYrs1*+Ş/mm=|T29|8zi.#F`?XcbTV}}!EƏ9RJ6{n>ZD!F-/GCpBӘÇk |4f jjG1w/_g# .\}Fp z^ލ}4dc waR1c F" >vÇ^z ITcovt }|E1k͂i&{! )rУh@D̦^$R5Ha%z-Z6x>| Mҗbm:FgN}# T wV`OD&$X ~L!! pc" uR?|j!1[)>FAr.m FABz=3l5"Nw ΃ z!e"Kw;E^ߒ--*CA_6NCsؐs#!} 2"S :܍y"ks-򝑉Rg|[0? :̡"ݯI[G ְvoΟn>i.S BCV ֑: Q ZvҦXHː n  x!^ \9o_ IԄ1~dAGլc]7fͤ{ojԡCh+WbϜ>hmb1cH? jNѓҗO'kFM !Dx/ڽ;E {T4}6v,:T.^Ĩ45'+Çeq s84pO&6J!٨z9aä۶-|[3}͕9"}߽\[B/(6 =7/|toK!-^,uNr:<G,@/)!hk;~Qq͘ÇލY[YU;܂br o2\Ja|8Kb֊=O{voMC1 /k&'us>sHR6ڟ ?J˿5WTȅe2/;v|ϺFax;Yڿ?ӝi0{,"T:{8gEm">J&>sm Q"?_UT`|8&N.]"v( /uidp}]g ؞6^CQb-[QSK-[׬Ae]3Ğ3g=S)ܣGe]z]ay$p0v̝޽}IO,Y" Rx/~ 5kzmauV]zn./]{>Tp9m(Oqp_]#}7J S_ fxϣ *kD{; 蔵w&Yƍ:x0ӟY0{tBT2{wߕqèpX?̜)>${w>>,(+ҿBssw܁r%ĉ]Hz'ņjjp, 0ysH i_0 #;xkT4ۖu>aJ܌ji;N_NIgRC]Ȃ$׬яd>*.FCO.YCB+ݿ?F8D|PG8vwEEDA)RF3hsbw֔)DA۶qDﻏԶmpFQɗ^Wysr7o0m7Z j hMk!ht0 w;f0T ւJ0+p4Ap(vE-~ebrD;ThO @ e d\_(:IPf+- 5bW/e_@E@!Dh9Rdyd~ZywH`g s=CeYqMozأAB"RI/HnJ8c7E\(Ne3@ v5D& PG%ϻ,u_&ah VK^V5xMYD6Ovvv*6[&H`iБ5u˄=g =/n ¬(`Ԁ_n۝>]+09a5r@7rܰ/ZDW sFglg,W^!e ދ/b=(uͲ 1h&MJJ0V$ws0PHը6xNM˺~+Wi:zU*u@C 9s 0 !H$ַ0 A%/ÇI?.^׉y'ZN*C>fx1Ojz1cz@  |gl2riQI.niӧoYDli) ƍqtO~^>znF"<Z$>hW^{4b':;rs̝Q\LȾ:΢ET =A=}`4^@i3g<0[o]gr}T* U\L 8%&͜C7OJ#2sr0G>4 7;kx30CVr]4Xuuh%+MÞ<cPobB'PhDG/*"~/4 "Kih}g2otucO `LBdcDXĞ2\.hCSO*NG(E;r}rO씱\]5r$v]zq1FyLiY=,o|=3x<7md/[,KYTYY^\L$ӞeebtzbaCΣ .Q(j̡Cv:d,\˞ڈvh4@ d ٤!hl!H[g"hƍXuuJ4f 12e_p0{-? 54$l*УQ̚2]]!M,Xf=c6L|tp/? F%Q};9<1hPG#FL37l̛A@Յ%c{=]P {^@&c| !yv髦Adh\E!5ԉfYn2a499hjh𔔐XoNbovʕ--h99wމ5~<{I_q/Ǚ>[}e 0(-%k7CNze;;'-WI Z4JpED$Lög"bzQmpoT }h9yaGuvbG;̙Czl˗3!JIRw/}ȢEىd hW^;pȲeD#t)~L$HDULl_s0Ua6<=7W@=J'kk>0Vki="e Ac#ʕ8ӦaIH˒%8s65kĜ0w?wH/JK?>k28 :0`vμy}̛S6|8Æܼ;sЧ0Fs4!x{X7b%T"AHjVF ТQ `͙7vjJbDS 6j55}ԦMr_{B;wv@9{d\};ZahuueĞhƞsy E"HPA@jzW}7v}=ĉ{sO#ܻRٸ3/{>O~{GdҾ9f eGڲ͠DWĚ4I]gĚ<-{؁^T$^۴T7eǨ;u w6O~4C =?Bt3R1'Nѽuoozu5ѥKq}&#I6 ޗjkqs(7~,w݅5~<…{Ɍy&f ]襥~?ȊH޸cĈԧ0*+۱C^6;u !%=gfUzuuwkXZn.z~>zI ޹s`(ZY;vԐc L֭ϓf/Ǟ9HxUSf"x1…x҂Ԅ^^.{me%Ʋe6?Ę>]ا.<Yy81jsq}TZdpH yG¨'MA잹ýM4\k#(##BL] _O\rH>]x*;ˁHWGEwrq"Hn޼tUf%~D >H=W81K 9aO, {Jy5fe }R¤9pk ^U&(?#MNBbu> :jH3.GHu6qeԤ \&^V4CbfKU RHhY%l&(xD4^UVl{3{>?AfEE1&1˅F ujMiaqOǂ{v@chg a\Y(,H1r]b1]?`ΙC#zқ7o\JPǎ|o} cPCH~ 2Ś6 oZ0 Q7ϵaYU1}:ޖ-hfhz#X'/~i~ľ})SȹzoG] --Va JI=Xdۖo>mah-p./?KY)%!/ O͙=m4͘襥Gd-6 ǚ>]ۮsLsٯE{ĺۿMrF;?kl$'  Lg͛?'bM曽ڙں'Qǎ ܲs8`~3fs==}W/{&N̼B>qЋ%_waXRt*36l@+.fRWdKQYyˮJ&I:D0s>{>|˘S`Ϟ QTHq!FDg?CjjR8}:/~ yp 0JKѦNEm݊nX&`vիǏǪſrEih O΃n%s; @''InmS޵Ku01C—@1/^}ICAA?.Űjj)W_cuI!߰?[Jd 9RO>7}:чB%b~; "]bPL H?9CLMz=SыImNzE"=5k+ H{a|쇉Dsh:] ȶg"!kƤ6a֝wbK7C_?/,"f:{OMNiwpV9ލ|Gh[>q韠tEq⣺:Ν᚞ڲW#=7|kL^0RsXV?)/R)?#1cp'M߳gwk㲟_c-[9f W_%8pc}C8fb1Ro hG;w|W8>dG"7N[:;-"/45oڵwb IDATkΞE> /!A l쐶' 3oǎ=#8zc`aΞn p0r$*/裏\D3R/n@%n]Zq19+Wb͞s R({(壔4f$@ #_ŸU@(UkdG&wNaO%,(ghE^ԮP7heu^ES\_(ʵ(0xi=B@)`Yֻ?SNF$T y :vPG72A߽0 81z l(R%ڽ_偵ro(JiP.!7r%_F[>A"oh%9H#G? _}U?yS{]G\KJ\ɭ0DͶъQ M#IQ{9#/ C>΢hl$xe+Q]]x0/gƊJ&3]KhjxƠAÄ́`joGy^vr=+}:{6y][9дSP! oh w_IHɓ} DGDٶ,mDB@K yee?NB=ǞjܣG'=aرe ̙r@ںm 0 ݳ}r>{%,y*8{VB״S/.F2sϡiJ) 4j;Aon0+5 Q?^P `~Ç1 "d=ɓkע6nvn{.̚x/|ڪU /iaA k3' Ӟfeeo{"krE}R}D8=d- 穿iس0c>=t}|"ߎ=y2?[ :copPN݆ {(-%cO{5 >~ӢA6wn[pڧ>}[,|0ǒa$a^\, tuu/RWT+…N e3:3{6;: _;v,3}L"Ko;/UVszQw)O͍ke5JyRs'Nс?dȏ)Fa!.A;Xm؀/<i" bYu5͛%o,&>#N$9ztf,dYޙ37d>=}:G}MT @G_%{+Ac~l ȹ(rEnn4gT3;9(92!_P*h!0'gZG]`+5 E, wEC="*z3 :S%:Of9tBwʿN%m~xn[Cν Cj26(eCU@oGB_d>紬q0V$Y:_c ~,7k'caM8 6za!8?^9$/75[$gC 5Wz<H*ǚ1 {Ts&cbף9Β%= Ѩ~oNN=&d^ͻec.S m[:O+* 2DW"y3.aLDޣGb,tkYu3{,e{}ٳ%=bKzFɮ6`?Ũm ڈ.b(Wbn%. 5^ah1xg%ђ!=l{:EgڰL(+pT4+X'f^_OHBkj@S hStz)\|oR\B&D$ۚ %$0+a˜1 h:.,(gdKuY>QG@I N`7 z>q @@5GĴ+$. ԡބ0¸rv׶3ٗ]nI{{^ !nj[1!hu浇be`㵋:a(,(ݜ7sGX/+CuvJLI qV{z.J)9F#вXUR_"!wfқ7F[&z}D%Y^>f}=ŋ#f.*But̛^^먫WE(0G w m#O^'~1 q ݣ}`694m` $Y{\8JKQ'99-E_BX;Z03iUސyg_jkJe~-aav:S)Z-(Ȱm<LJ5n꒗':'ϓzŞ'ljZm&ҐM/ ~eB|9zQ_}۬)Sz5MYȂKKmǿ7AK #/Sw҂:{e;r ?+hݶU }B֋%]KzZYy+V?TqqOj~s40Ĵ`TVtt,X[R[P0#l,FLj6>+۞ --lPk&BZAAf*>gس=xo"Z8,.BM\Ɠm kq`4=ru*As3Fy9@?\q@Ir)ؓ'z9{n8 ѵ1CGiE57Er>*G3fY~, T^{k--[&>**_xի3xqq0 msapG|>F r-Jⷴ`ϘQV%$Ov̚G |bƛ5^0dُT{;~s3FUB5~4P&m4dl(PET:X-gXV:/v&hR;C@/C?Aȭ>} ~kb>aB$xÐjpGXF+Ɲ> rK] }-uT"x4CpI'}gZQ6p,fHrܽ{җ\!NF:E҂QY>|8Shh~cY93w.̙[JaZ^T{8A٢͘>yC"Ap A{;Fii%Owѻ<kD"KxUG]:~|onU&M[ XTY)P۶vzIG|q''NLn$ޡC@ Y_mKHKY./襥؟,nNw{vt{L::JHzr>ib?18f޽&ٳhrWU :wN Qk+^Ch-YkY`8wߍ8>&8ihtU?&bC[oԔatV]UDr:Q.я$GeeD-è"v-ƍh}xf:5 k,r //~}R7Kx]7X9i=}=fc1;S]Jp$ SJdChxǎa.c'Q3T2)bz+ 56߯\p 2ȫ#?ނrhVfP4M'ngR0!DAJ(F ܖN׆hB涫]'s3&Dɬ{A "tMJ.:=_XNVx4} 7CE(x?Rș%[z5.ҫ 2_@*z=/>]s1dE!}>\DC0;`oApJ\I@l=̇4 ac#lU9sCFZ2pWl"> ;"}Op>!VW ::HG۴Pf]גBU^^{ y!FQ(_h9o#ʅ x',ӔBOtH^ &z .\߹S2$׬!شI2ۅV{ںȭ]$T*Q^.mǎ׋xKĉX ~{[Ù3ǞzYfU"{ z yKAxnF q |w3{bw +NMii|͛qg"w~A>lcxoًb1'pNř;W{ΝxO=%"uΜy}GJ.Ԇ DVM0u!o}q҂w08˗QU%}AGdJ"#dޭ^-2Æ{}}ݶ o< If?1mz+*ۅax]30Gž1/~Q4sƍdtli߽` 3ЋXۺUЗ.Ŝ1-?GcoMÇ{u}(˖IhȅB1}}aɺCڻf=_}ctћ;K H?y^w5嚟_ycHS3 ,1oqfF/-%$e O~˖aL*!Cp :tgQNڸsHp8&z{D%ge%zn.ݻe >}Œ߷;v ̪*FIܙ[38 *1'߻u> }(s0b O-{`SЗ/a޽uʕ3cOU.Gt)-"k[ott[;aVM c0{esp)SP4ѥKe/cU[Kc R$׭#ز?ܠ?ԩD[~/>`Y8*HlCjF 4A^y `WOxd8Vq`:I|8~x.Aߎ:Q~yK%bؚ-,a"(Nu[y] "f0; ZVGGpa7|0V\!0A0T4S1 Aw6:?SgҪbQlrfJDgK=zL=}uKҐ>(Ά8V@]h.䚃hVAJ0;&B*%70 am(?DBb/^;ve3%̷U, !8wQB6zP# }"vh &S±+Бۥ}wӠZ6\fawSG 5,7򑻳+FS/\;y2z@jVMp0MqOTK \#lfu^BE̵{=:w9aD4;;IA{RP ǎ B#8x0s^#M,Ki܌4A O!!rD/G.ޙ3$׭Þ5 ! %hk#\W[o,*SqR;w"*8ubWⷴ 9{Ν+'3 ν pž:t!?!}Y+WZZD oJb{Ç{.Ū#2o& oIH2w?qSDI -`/#GrrPvW--WPJٳ&M tޠA['M"rmؓ&a?rs h?"`]0 YFo;>ЀQQ5q|g{QG˞!P *ke(tKw~݇=kV]]ƞG=׼f bϺ:[Sسc=4 IDATdw!:H4ʕ<_{F"Do k$_7+Ӗ:uJY^.uv[)dNӨNI&ulf݄,hjPE \;rDXlP*kBw|?3[[{`Zᘷg>轮75lL|8"cO,gccTKSyDﺋTN5ÇGCdy 9W_{{SxeeFM#hk#q#03/Q*%>磖&z<!PAx]9 k,C C/-;}Z_7ʲJ~jY7\t f8ړ?s?'::^|Q邎7яzYu$އ/^{萀['O=g_'aY/f}=*DAKkmM|<V]]ﭭ$~[7N={D7-R%JB q9#b+Y1T%fkd'Z0TW3(-j=R, GN ڹ^(b B^ٖ,AJe5k@V_ۑfZPB[dQ`h?z=e]Z5 ܪpNm*kd?XI)j-^^V.i?ٳOn_7Ւ--B4._H$$:' N@iE22@[drݿds__Yꅠ[JHyvbT ==X՞@/Y9,{)bE>Vk?{Vd-[&NAe"ۂX˖FƲIoiXM g17'OVlkZt +/}}_KMpƘ|qK6Z=Oz2)/is-K=qF#Ӷ[{]BJ%iH;)uI+_ 5zHaRɊm)ХtTˑ?$ @t֪US$6>Vg 'Х3 MMR\8h][[APRQ{{5Fg}\cij_+1<+kRIj2곳vUlг,T[DnGH2y֝;W'/ҿ\몫D򷩽]T$=Edm>={Vhb"!OM}}u`uq)nmTeY̙z=*q<*qğq$.ݨ!]ؠL6[7"Oq^ ' ohyX2*R{ ~~~3D##Lr{ߠlH+jXmmnhd׿N/_Pȴ?qn#qmx=>'}}`\ \ A `5GuF//Hr $qHx'StnOȄC(~|20000x_ PQ) 6|@W[~zk{op7"e҈1)!27-37]7ހT 5:*Sgn1Ax;<} EPI+ CC|]Pvj`````ao2 .r[!ٌ$e*RȇZd'KvPqGdhEtY@XMƐ)w+M!JHlp+D/c;7B)'d48pk)È٧*\mX\+ AgLE 2% ~32"e, zz&V2i4300000000I@ wkm_Ƞp Ń/Q~op'X`g7A>N3gi VC# &wۧ[>>~CȂ7"vhf O 6B wg`7:F`ur| h r_Ty\mkJ~7[[)?$?O۰Q';f2GxWOs+3 (+e1>aqMXxނ[3?#/uJpX^nae!V.r wޤ{0H^ ɵBf.?u u_ (o@b=$U>΂$i>Gx e}'zYxaknH6lqW <~y5k Pӳ{H=0jrT@SaD$֭[ূT\|8n^}7Sl[_='w-WN0zojy2j~.gOP`JP;s` ͟ qNybp 7%1 Nvi]'P B$( v/\dpDa[Dt$ىt)v6K)09gVۿ%lo]৆ }e~ RnJ5a5ujZF8!!TP&؏׾vs4Ȃݏi%ѱ -@|}@| R7CiK[ L~JJa3D5nK/P{oC(>(>6 d ˒:w+PzmӔ!cg!KB]I82AՈR $y*@*j=oΐzz] [i#< !z ZO!6%{o!$UʦşOn!(mq; }'!ap: 6J/@#`[!u?$ %XTMuu-$-!`@.L~l("=k$^Kg}RxRtA!vmP#󴖹]lg!}=n!{ pՙǤ$Aq_:Hܧmψi(>]hJX vd֕vBO P97% }A\uȟWp-#q֭{ypPF?Oc[{lVB3?p0 Νh=>F曱PQumd~w͛gH.YByf/} q?qw݅aHparRN\#!/Io5`e/YiVl'I\&rC#'Pn ^&9,˦xv^|smK&;j!VC@AZ6֩k5ifS";M o[-2/]˓;eߐ 5ZiG&!-L#& *51 M[z(_6"g*YzH lYNFܫ)G<:MOM@YK!a>vQl]&& ~]ۍʪ f8'DeHITQ~H^ \@b-[B:+ s7LxՐX)JrҶB]){ Xo⛅C=x /k "+xoN4 hzT^%,+dTTC^[kp5MBL>kEFЧgd/")pӡXvRlYg&pDTv3&VUYjgu)=tΣkx@#rn4.v5a3+MM<@{nT/bwvn瞣 ć `qk& 8s3֭_$0?A##X,e8l<3t_҂+Xk֐H=0 A"A۱)zK)H$/&q#ģbx|Ք\iT V`(=?骀Uw. I>@MM}b@fͽ$:{)xhC6v? ~C]O:i> x[!8,4IC!k|TWJy"uHBb zUQ(nAV\7Dv!u&,īǓR[[֬A##O^?N饗](QwA*8O۱\wC{7No/'yA3N'NS #-F5P~E P ![$m&5XY%K*%M Sk q אl .:juYJTN?] wʻ%-C8aL5˕kE aMK; VF&v-h7$;a}|t~ BĊzD~'tW RF|zSO(:2RȮ)2dFσ: !3&@<_ Wr.M]Ggn)<'i:,LΏQ?1!so- C.J[e2SM [Rq+2M? R׈~!aA%B(jn(5k;:_aւd?in ڶ17ވd -gA)$X$sϑxIEv>uR_O .DUg?O~*ln.k#ѽƏ2qj;WGrCGM,wGG(D1*6L #_S}ᭀWHJϡ `$#SJ˿v[ TKH%dV4=֜WaOI!"aMя!d XJ!U!g,6ig_keBvڴ5EskGcB$k`OK5_rPMy%*!}  Yave,~9bO|THoLAL+YPTYѸIӊL+`P2k>)HgY灚..&rۅaUFkvk+V2h3| +XZ-U+o%(\۶cҍ&H\oӦ)8w[Sl(A#Q LJ6ν!!7RBCĎ5uEr$,Ϧ]XJT4]?{u2-d^Z0v`ةykUJU"Arhh^& rA5햌4 &8gY/>=~y윏⮦>!:(lP$ON0_ɪޗ c<3ԯGS V՟ Rat/~ ꫥ#`yN[W-[.,zU}{s9TJ&55a/\|9+܌re ӧ!?e(&Xj#.M d;ph"ӲRB4״j|n! Rih ~z&{@Y~sn]*~=^gubHV XũnބBg*p*|L~:rKEy^v5G8󽸬z TA787OYl5m_Ty-da`$? MIasΣKb=ұ,NSqo=7Zfydye4Y.x0hNE!lHHRB>y=BkIEhuIK3f@J)J  6n^xtT}/Z CgG%!R[[^KeَB$Jg,\ىkXNW4":vlJf<:cſ eI a&&&W y*Nz?MyDԚ{qZDQC)g{X^\([)DtM7O_~)#!a4C ċP,]XB둒 =u Ii }үȹZzJ]OʾBxLtZuCK9VuǾE>)O㴃sD! )F4((!k !<]vD [_"zY/׊5V%[M.LYΩ=,! UN? k?jD\pF&VC+%l՚`Q'!<"M?e^ i-n@VeɄK/,&ǥ?謞\"TmV;R蹐|I%ւwk0J˯g=%|ORܼp6~7Ξ;- IDATGN * qW$_,sP{Hɓ+W8KP=Sp + zY!܃Ip6:D|8+V`_wX]]؝DG 59iH@uuuߏ_dȬ{yqq 4N"CJ&|+"~[|#^j@F oA 7W{R+Rvg!M ^yFYQzR7k94 BdN{=SqZɚ5VmPq3LB'Ґ:FGlQBEk=wCy-oq.M(.Ie B)ɳ22x8[%a d6Q(W! A/4}HzW2d6Hd~;䞓im^`O@5K0n"#{ oo4CWd.92P$Z'=N(KfXj kqi15B5?*k"38҄#$YgK*7xSE9s;S ] lO3MΣ穙1qZhE z[bUx]zT6 dڟ.^~hٳyqT.Gc..c066 k79IDOLlJ? ̓OatwGi8ZAR=$v=JGqQo%HyjRG'SagX_&xm7eLno^){KNIrٍ8M 3m( ~HGꗩv&-؎4CouڠyR?L8{Nƹ |*O&2F(7oq#G] + %Hz%--|M7IbO~B_ {q,dՖ- ɓ>GM_ă$ w ,q#GoGU!jbsPYqXM/7  3eۂjC'  ^邠0$;%m"o"b!SL4jLlum M7!aI/Fz:]^_I_HVZ ) V.i+ٕ!xlstəBT{4ғ^d5Nozi?Xmν: NK&U<)s|[i>rZ3 xֶN2Lݙ;b-w}*nbubLJk1=#k(S*q_1t˄ֱ Hv|mjh:v^K<踛?G~Ͷx{Y~{vf?~m~m-GF l&&`||sۡIΙ85ZZ 0jD,k;N _,%K RI~<ѵ0sfSduDr\\~&#>(Ğ04 = a? Q1c`ڷqr5*Jo6k|7K^H]/R,){m6]7Z ^SB?]5zQV93jWaGVJ.w@ A-jO蟩{I-̡_ حS\L|d*q:rs\ܴFۃr8wW4v+뜡!&&OR>B}8~Yz$veKtYa۶{ )HnOŷq˻%BO]ReH20000000000000.ZL&c<^$& A>w%4DG J"SJ+ PR('KP=Y\QԴ\TV (#zR,7n Vjp< }2`~hiTvsA Q#V+fiId Z;4u)H#Akxb@Aub|">꧱]IP`-o,o```````````>;s$yǯ}}8$?C˔?QGk?̂G~'N~3^;Ty"Ih(I! TqPx-z_A`g (= Ab9Ŀ^hd!v(>.O==!S8E493P{AnSdOa}$@&}yOfs?7og1^_] kH0C(y1<ؙqx)7Vȥ̍\qUX^ ;݊$L ! .!z[' &VCN_p+ N+X)(U+#5?v 6ݗ K봁*.sl-,$㐼Z⠔qOAHV@͓zݸHih`]PP5 85w< fPqҾMgv_$.Mb9>Fzͽ$@c=jxOT[H`ޢ5Ƴ ? a䭇L@Gq׀cM4[̎8XMP iԅ*YYp:!A!8ܥ}ˬ'$# A.l d + ֓Xmin.#jh;-ߵ[Y,{UwYq9]shQA;?;D**MDBx.w!MUR L'w  8/nj!DU2wI彐 $VIRn(Zɏ@f( O7!zyNSLy+'>-Ohu)=UۏRMP>"Y:RYy?=CHߠ3QtP#SGlhJۡS?@a[i' A,̳OIlAHeHpNF)=Ksd,O NBU=S3Uu0U__t>nc}H\逶=m߬m^Gu_P[hJ; n1(ni/VյlgOa?6CEV74Z/˺ $iۃ )(J J I500000000000jϞ= 7~7zRnrDՍ٢Hs Oc]`-\|±>=xm FO\zQn!ns;ゐr)r:g!nӯ YX(RNUL+ٹް7B"EBd!_ۋ)%Xr0Ѩ&*N`OB㒹UqMS<1! n I-q!_˅ S*T$A h pkhTHw&qP&ľUb))UP>јINk%^J 6%1pQ8yL({=4=1VR#gi_{Te $c\Ȁ!h~X Ok):Bz%hLԶB fH@ry?.F#b #OC5G!Ae-[அ1OJɜDN^s #Yk]e3ڲe 5n ?!d=VAj]}Hj $)P1Y}9C<eMkdxl SyMZ hG6@ ҐyH6)>9Z@t}X$ '#H撻c3N2Tx ⷪ^T821@㐽Obdkwv _Bٝũ* K2$nR+tF (|6>)sFEyd5}D .Ov=JBȡq!W?{`jjKQt ˨EJ֎!dSBvLSL)Ki'~,>`)k (~_BB!ޏ4X'&Ƚ7NФ[*~*Xm.HR|7į q4I>M.kƠ5!6[7,:5 nNUN콐X zPHi````````````PO@EQ7UB&+GH>mcտ!;Lʠ{4y`&u.4FaIϡ'o9_BX)^mi= B8 $-%B˧O錙|MO Wt̞ vr49 aU&5fMj]6vʻ]wN6Rnd^K17焜ol$kP*9˦: Ѧz9VJH)7 )%m0.BEZ|IKMҰLs;$>B5?{G僿_Ȳ @1ns-L ݈ʚ@ QA^C הVRG-Խ+$lFxK[A]m_ink>m]i!BHN%/&%FBYWVłP5$gNU@rd7<&:%]贒4``piqXf ׯ!GeFe .keҥSP'''y饗rs{a߾}%3JMs>27nHgg̑6m4Cfoo/֭{n_' 6̐s߬4eڵ̐qF.\8%sxxM꿥OS2ǹ2w]d 6lh;vСCs矟!ꫯꫯ̍7q^L:r;vp. 6d3d^}ܾ};Gt|o6Wąaܶ+pD m*,;e4-r$߄{oմ(1.fz ^?L|GJR7@Vi:hOB5!m1 “eJI+ {<)UxJoȣhp&>RŬ+S@d %AzMS{mB5 T\. i +Y9?Hfʋg7Yɒ&y˜͟Rm?/Kge%w<2 6L۶/ٟ}孀`8Ѝ❋#JOvu!f*[m|!:eskEV| btC:F͐9郕\ ђ5gOxnT_xXHSwx@yS6*(_ng]{PipSN71::޽{9sr@ (-sddd6pRy9en@,roM~W'=8 k|^UR4F^% ͝f(i܍*:K3+1 kzQg!u?/Lߡ{G( I &b|HXG[Y<5e]jƀ1!EO{5qjXDJ{ 7e`0uҧ k&̙3SX8WL<#svccc]Q2+䨑9QFGN.?3ʬF2GFF~^Y!2KTp/ޫ2I^}NK/i'Ol!UQfkxDB~P`,y.:iWԔ (Q-kƪ}b~Q9(nl+o4wH)˒ z4ܴ}5Z籫CepJN1(g4v:J@'Qy3s5״fHCu ;BH/ᚆPzˤRs\̚:#_B$A~'SܺŗúW,QRஂ]~\]N)n9+䌻>$*A{2PK k95Fڄxs$<]$MkbICIj JIZE5ۡ#rQd1@hs+!>tB@p\Jcn.jDBn)ldtԈy0Fr8M MQvcQ٥Lmȴ{1Mȭ6탤c``````````NUTw\SIuPztYO#دXǒ]ҵB5MR<7ѰJ0ff{V58DŽ:|@sY52j^BN!YU{#[+5J\r~Z5:ONrɰkM֟y|s4N!SV_f%nTgs6L ~k /FγNo-O4cHc\IDM> m˿%M|'ܳ4{*J[ff````````D&!f *dCtU6lhywią.QZy<Ν^w6;cog>:uju z &~ RJi[~ 0ߵ~ޅsO ; Y]3zl300000000000{(zhf @LKH)qxʼnWr]n{߬8c[9ؒ,-$!bhn=5W~xwUW @#V u[~]Uϯy)dSRjHK/"m >HY@(xtm "xrjnI%%R nKI%%%%%%%uM5551c 2 \rۖͤnNzyy9d %JOT'hN]TQJrypFp/|Rj@Lpt1]A;W 8Ec HzჍʉ:HA#z)e; {sk+JKN'7^Zw .9JCLvݢ 9̝|tsEnE`uꨮF4bW\appEDٳz ,T*EWW}FFFx'yPYyy9eeeض Ou*3f#J} UU=XQQAiii~>W555Ikk0S4,Xtvv󑽟UUeA\w~sxv͹sOJU?g=FtO6F:IKOpNN E_-PJ$ՃroP!('xYqȾ< j }zc#R`@ Bg.c X$ `O_bؔ {``(aTA1 `3o|k'n;q/?vF7~^ OfCE_c(80<$o&usⳟ,[nsx ~_o~y( k_{7 -ZDww7;v=z d2 @Uݜm޼kגN9|0< ?UVV/r.\ʡC>=#lٲÇ7ރ[n宻"Lraz)2A۷ogxxo}[tvvNjS[[__ygя~?/OS&'%%%%uJ% xPzi='NEQ ?FYJNvtg#!ZÇ :>P`E!*^ EWH@  ȬkxD;5jnI=A48IPBੇFa+s"`5ꂵ#+ 9H̏?.h r#nܻmr{렝<-/ۤn4~?EEE~bA`"b>o\|Q "?TUq,xtz,VUU"EQU|M|>:t3gϥ6 P(먪(ضM:&c7ٞiLiSQ"###x^|>mz)..Χqt]`0|ihl66( `&DT*5nE! 4-nDd29%@)..b6X,$ `l6 J0 ]IbN0^sUUǃi8(B*7EEE zX르`0H,#H2Zb gϞI{IUUqgy08}6H0mM0M4}30x<±z^,mc$jp8Gsc5?r̘1O?BJJJJJ#bj9V&5/1(_TgߍFپ6(S>LYb3rE; (d[DjV֖qט d?$"(NG ֽE@ ^":ʎC$$wRfQ0j.mBq0ng|tgEڗ`^g+ s"qm,HBy >E‚ڹ഍ !O$߄s\t&nDD4ŘnP!qPK |N RȾ h^֠H-钱![cFZNFUrϺ,_?ĿvF=. sC1;!&F>[ YDrLnȾ s&5/vv_jCX Fد0$߀p'xf{΀%yԊbn!̛l 堕.s>Q#'PB!ON0IJ,R~к 6yfϟ]]]ݻ;vӃaҥgF4N>=e$պuԧ>Œ%K(< W^eppR6nȆ QEH$8w/;v츩qlw}L6mݻwsNϟW/_C-[p=L&y/~EҗD0 EQ3g_׾ƅ عs'Ɋ+شiK,Ƀ,4СC<tuuA݂ xG?>EEEضGaΝ9sf~@MM ###۷gD"~an^Jee%Nb锗sYv?Mظq## dz*{ge``f|ͫ*t]]vpB~?Y~=D{cq[ӍqA.]4ӲeM8ƶmz{{ygx?z.Y|+(--!-Z0O?4/n:lْߟmkƮ]hoxؼy38Cgg'xz~{n(eeelڴ͛7SWWa o>v96+¦MhlldժUZ#)֠Xl4i`'E?5~wEV&ڎ/- ZX`$O4 :d@+Qb?xfOԩRUEm0r PE;,>9F?$+JqSMCc5.2g Πf Z<ŭS zXk0^ #C`/j=LQ"(Dbھ};'Yl>/3m4jkk4;v?ʕ+)**EQ&u2 @ l B Q\\(**e˖HӨJUUx<z{{9| Etk!L}uMzh!  QM1:C 0<$Yt)'b&*++d׮] 0o<ܜrZ,gΜ(qz466b>4 BGQQ]]]R)jjjضmLy8@OOt3fzm_vGQ5]'Huwauiۋo),98#zG0JN ̻AF!(*<%"R2灂h ]dMK8S^ ¿QE vy8dCZ=Xm'GxH$5cxn|TuX+cO@ۮ $yO@h= 4j] Bܵ;kļOA1';|-{m; 6H>J@&1v  Iχ6DԐV )BR\" )WD)hsE?WE$uR7u*s/ď6?EɊ;xK^ButvR)g}" zbA{raAJ(nTC-H*.`>z( "I Z!e}@wtN7߀yaˉV{Ľ/;@|zeYTTTDr |UVtRTUѣ9s2VZŴiX~=o&,^81z-?8p%vٳg?>֭iDQx㍼Ӯ( sgϦ%K2Mkڜ={6K.?1/2mmm,\;3T*9{,tVzzz8}4/ l߾r._/2 twwB}}=w}7UUU,_Çؼy3-N:E4&%6K9v555XJ.\mQΝ;۷3g2}t***;iiiDWkk+UUU\ӧ~z><oA?6mz.]ʱcx饗8{,/fʕ$Iy'544P^^N:Sv_ON8rcF8pz*++H۶m@ _|r~zϟς 8}4WA|IYz5=qhhh`ӦM԰~---l۶e˖e~_w__XbVX={>Ԥ>Pu'VA Pc^:g;9Q}aG{k mw;އyU#X]0(Y "RN dw# @U,7>+GMeX AgS\C` mP+y?sWsm0jW%' ː1V(TPt\+ބ`lD :in(u)e/Jr2xu1$dL}^Aȸ0OǓQ*4?'Wp\qF&EfΉ  FpC-pq\K^$lt 3$`)`\+"츀o.pz(pLG$^MP +qntϧ1 2OD).MȦ=wӁ!tN.ľN`5>3Dk #8PQQa P%S\\(^|k]ihhF0oӟR/bR9,P IDAT<ַٿA9B6j4MC4l& ɍ*6ѦضM8l6O<߱j*LӤ|;7x#022믿s=7S|̜9H$Byy9H۶X~0Xp!`D"7Yf `+W 7*B&ʕ+|Yd RQQ000innqN:E8ܹs>}z˧ij<544 ,+owxxcrJTTT0<p:SL&y뭷Bqr h4g8rMMMlݺ GzE"jkk9}4uuux<.\c5㡹98̞=a^z%z)vŪU(--e֬Y<22B<z ÐJJJJJJB%T5T}xWXcؗi\w"2=d^(K}m6mضEP"J#@6ⵓ[ΈPH!5{E=*i)Z" ]7Z'n8w';XDXFTVE:QJ~x }խrm2/ oՅ =ְ5F 8  e"\.*g3VZUy!F²LHuiii>ҥKҧ!;Z^X;'+!;ޫb/ٳgb8C:ȑ#|Ϸ)--_[[?9- ]7XEo RTT(9s'|{TWWX0( O'dǎ'?<0 c觉b1Ϙ1r~?]]]޽|;|s_j>DDK?mSTTĂ 7o{D"._̛oy]t+ x<@:??}v%,b{~|!k0mQZZJww7*w7ND.{IIɸXRR8T2 O?4gϞ,L&CO9"@˲rj`\Nzf݅^4 s '~E| }7A.h33}rNHCȞ|PFLA)!X%nQD38] `]kKikݚQaPV~WET< M4TذqA 䢶8Ψ8.L)ؼ͢UڟsZ4Ln|@oQw}4ǚ:*qO@_Vy Q,<_,-bxD0lbPgBp Q|GV"bɌ$zj? V ""S>Qw`]uO ow OsB>Q1DJ*[}MDD̫g~Sxg'0X{>z[:$줣AJKKپ};MMMd2B%%% JGGCCC,Y—e0OŋiZHz0hii;t_~D"?I7%o~3gzFfdY~?^ٳg77rPtt:磹}{$IN<ɞ={ôd2m԰uVVXAWW"Ni\)˗/"@Կ:q---q]wQUUE<v}]uԩSttt`x7o^Tbx뭷nxb_fhhmvww322BMM K,}\ $]urr)osi$I0uuuTWWn:/^L(:::hnn~j*jjjgϞ+W:AO/2?A8gϞ RRRRRR&x^uKыkqI;yP}KU}W;^`X=YM[[MMM̙3bQz6/_L{{;seܹ̘1D"孷_^^NYYk׮Ų,Ν;snż __| d***6mdH(,AU3UܿS~TTTBCC3g̯c4e`` vk蠣&̙͛3x<?~KtvvRWWGss3?85c ~afΜaTUUz};&Ε( oO[jPϗK9߿Yf'> FFFd2lۦ_| .\H}}=tUU\x1_ PA.\@{{(%%%%%P~T*5W +I5[ĩw; ԥǵȋdNa]Ɉ|:A2=gv@&9p 'IoŰ sQ*xN/$ Z J_Q6.Rr{qFvuA@/mO\A~x"m%v̝U/(atݾ 0d ,qFD$=R_. d'!$_ X/B/ 1Msqb_zgcNd.BeȬp#qH\VH2fod=Uv0 25 8sR!k%8 Ɉ׈QN-Ԯ"_ z>f8qoM{H\K2Ηc\tףp?:cED-I 6S 8}.7o sר]b='zb1r{9"I+qO@0.nq7s%5[I5g0>'$_žVxGJ&tuuqy&9?a^ üyy;;v^z{{~466*.\  QSSCoo/}} ՕOSX'LFx"?ٴiS>Soo/mmmTWWfLGe?͛fww۷P(?0 RCCCRկ~(4444|dՎ;f#V.^H(|dW[[yG;w.H˲رc\  ;3˹spNb?vikk˟~xep~wI<s kѣGGQ:::P~jooGQ.]4)eq޽B!(N247\f̙ۜ|>TWWcFx ȧ>F"Fŋ\z\;֮]Kii)W\4M֮]͛q˿NO?#|A,oo9q-%%%%uRRRLhD]- IC*))))C/ͣe˖gϞ Y siGJ݊8z-咒]PRRlpNB ~PSݜ(8mH$%%%%uJEbi~?D={ӟtRjƌr!~_ %%%%u[K()R6PҭM"NvIIIII݆:z(gΜAU|2 ?y4 qo9e'UFd>'fPBswRO; p:>PJ-(Šh Wy#8_P*K4S>P5Kt;e8iI}K%bnp:[&57{00z]oGGkZO`_ Yno$S8oYX A/osG:<!~R^K P\XN*wA`xA񂝄% #@LQ}6v){l]2Җw `A߁y=!  #O@;VmHLk]/V =|/5TGcDSο ̻s2Ɏ0Kd;7ύp$GZ`IQ+vmg/[`^a,Z $_*{#cuϽ)%%%%%%%%%%_<6t8Hj1jUs,13WS(*plML[Y1-`۠L' VBHN^Pfp ! Z0Dږm. !`P (CzZCf3"P ѧDϵR70 `ON¢.`48_ꉈ3Es> )f]sgien` # #_~e/"Ay>!xgCbkivTf N*@!n}JDVat!Hs-wx5s0EJ!ނ)pF]p2(PJݾtMXk4?O9뗓{սwiB XAn̤10,q..8O@ 75A.kOfs{vV|"RRRRRRRRRR/d2,kg_ToLbAFpas_w׸MG 'yxփ˄3d {hGEP` g< ]`+;!p!W9e' ]`>W{|K!sb+A-)f ΌUWlA ,-">;i,悖4d.Cb/ϸHS Pd.\K𿀹Va<(lՂc[#|R;9_0 Q-V΄#-؟ 1!^ȴBW rboN c C$@X]H!kO@O5 x,w^rs6Czz[_7 C`gs||yqE}*d/[|Bk'!+u{3Z;h n Bak Rp >&ߥng ,СC5J .(z8;TQu(-ceB pwa6iNی^R'W#ׅ&b`O\ Y&gO#KCwg`60`^Y.4Y.lz!y{0v,w H\SO"r1wB0d΂:ĕGD ¹,~0I鐘)N::*vCJ]2t왐Y a0 ` Aڍ0W\*J`w}`i1B/ ߔPn0y xVM!Aks⚁4HL)w"*p1@#.."ì+"%N ])d10h/LHM9uLP8&J|kEw%0ŎCWb !m<$UJxe zy )ΘzA@`5,zω{Few)V'7c&xVBqik.H[` xLC ! #B083~ZɞFPniw H~8$^#\lcM =^JJJJJJJJJPdEsT߆l"y峧|(E/G/cF{P z45ͤKzNA̋<b'GLJ]("j#]y^iv>*.!z\P&`Zv'#AՖVP *@QVN~iZSPf'uXg*xGv6Ԁ%nju aQ([ҧxih׀^Hikp lw}SԴ&"~5*JP+VD;zTlv z=1yH0'%`Vۇ]X:>=<(,q44a6BcJ)i[wM\vx,X:ڙYu{1 ) @-! ~u/r8rR(dSQ$(8@4no眪{-?Yynѐ(랩*k;r ZJ=eqmlwZ @/.4_ݿ3z%Xb%Xb%X&PϓO>9;iniV9~B{@?g{[8ǿ@^[cx!E8}"s炙l.}ݝu7P 樖X?ETSV($#UxU&RrYs <%jjܠ^F1z,.gφß/VP%?.7ͱ=5 ?)W՚.wO@^K^36}6_F$;XF>}Ḳr-T#sA80|_(њ9?ʏ :r ̾cmx/Xߍsnu+٥m7yay:_wYу4Tx#}%Xb%Xb%nZ|e_ v=4'wtiAXNRgRZ,q *鷝$w^cP d4)>3cCèǡ}Xfʳp2 Sz`0d 5wb }H\b%Xb%Xb%nRu]wSO(+T0;ɻ~iV}}&R鳴4zx o1ص.=2,1%Nۣ203y&ףHm޶fz娈FSC_XH,J;gpŷ@mn}o~Ñ.fgC  >z[`oȟ@l?ua0䯅 9ո0_#Ҕ @#BzwTVooq>W͸g~lׁL>8{voWA=J}wU.xod+zx۾9~ms5`Me ja}x ߆I^mvwVwO8W |%Ջ._-Sÿ >K$|^^D:~ᷰDoyM5*"_Xޤ1(h8R? {ރq5`u+H+_fga͕Xn?>DPap1)T(C,7:**'0<Ǫ׫?Jk8(X=/Sc?a<>?axԃʕG)'>g7`sГ&T<ÿ3`[˧SL|"RƤ|m՟Ӱd)l_ͯ)m'=`$]{ Zk\]UUMoY tCAN>T?EL}wv*c*Jp %hyO/Vg_u_jrXm~jVM& E_ '`x4{(zv Mk|o?0;@-*HhktUum/Bw=,g >|o`sJyĜ/ְ~wsSu~h10Թ|0R2*vu?{l_)gqL Xbq< :<~q"l>B:tMv/ 95L 0Ʊ< Ǧm Yz4|vP~^ Շ86v K,K,Kܜ!>я. H+\0? {A컠)8ခ06* 쏹au%O$?^~~BVGܪoo# 'mPgOӖXb%Xb%)g??7oB#BTw>>A(v$R,_]e[hPK|݉tK,K,K,&>n%n@'R6 6د \%7E}-i%Xb%Xb%XbOш Kc_bщ]oz-Wsrpٜ+|UlPb0-FꘔXhYkF ucJ6FtpzF `p'#%vٙ$v[ܤdQv $k ;WWpip=m֤iVHۢMMF 9$GǓa125lg3g;^ \ /+bo\-k l PJ!*S Ob1P[56V MRRDmP")r6씒)0ئv8nf^>})K,~p%3@5nzyM *-"hHJIѤ ..> ƅx$FIҤM *@aIhW(1J.Cf񳗺`r˾nZihW n];4Q+wqcq(X \hɂ ;zw-7XVl(U2 &;a'ʹ[꜋sι:[q [/d+AG(6 *J Crfvߑ=M)1I%Ih@Fq:1N n)%sn16.K85i@]*hximJh֠ =PZCA A B6/\# R'u깣@;gpz8(g;|p6!+8O\Ҥ8^I {lNRXFIZ%Vud$p)Z1v|`-lvpN\=Wvjwَ';tkKK,j%Xb%^czΌ mN-MY) qC=&AD8^< }\P~PfgJ6#@T_`EhSI7+pGByhrrkqkq95 ꎺ~fHh(}4*(J!Q\YYú4poK\{.}ʐ:8WZ grPE*TU5*1T"UTm*~T!J8iR,qQUB*x-ncEqArP̈f6A:9OשĤB[+KkH*i@w"*IuRҀOII)U S6L4)ަn0q=V2V \ 75V$>A:.IMr֍p9ǝsvWqǭs(-PYmqZu(V-)OE(.X(ZkIåbFX]P%I$M18  VjO`#4}o=(P&I-КИx( YiM 8m5!{ &H(dG2RSp*!(XժL :4J xlF gT~gυ\hB!&oJ)q~qJU,1}6HwH9Mʪچ UTq37 -tpº֪ UmNٙb:Yy3-j%U/C/(?Ǟ2Ax3rrҮA;k͓,.~!o}#Ȫ^P>~ ^7G[O9{e^.ķKyk@n=N&l7XJfpÀ4˱NAap˸T# bx$EaBZ) `XS 3H(IIH>j:_I\ Z la56JJЍhR*,X@\)D&xCTI P.Y 8NKCϵa>0p3Wq58^8y_N;ӚR3*iVq4O>5i4}">SgUT SBGTN ̙%s:dNwY=p֧SNϝ %\+ө*JjO5H<#|,gc[(dX U$7Ps+ {,fHm vb{U:˵ P= +*Q:jiLyy?AԨiUc^ӼOBZsUHlAfeRy ^xUBe?( '(=I8l*|:5[§8hʝBL$E[M1 jͤ4@"J\XhM \RB#'C4 \B5UՂb*"S$U-1C,oj^aD#FO3Yy B8EBNzhīWӊW$WiyQYA|&tc۱XcXru)5H4j$RZmF Jm/<qX>$RaS84F[$Rk2M I ̜\я9;V(9]q FK,7 D"/go { "^Y];xa{YOhwCj>ߒ}2?#5mw@pJ0BsFtv |/ķU}l0J)l͖ݎ\P"` H\7Аh%.Vhk*L~B t)b#@JXIšSB Ɨj5L$r&4"Bi·iPF[]lFCŰ$RpUЌ$K(6fCjAK,YyÁ([Gl(gq3Nwml\W7τf\;w4_5M(%X!y> .b~TzQ^0G4#fJ 1ů"AWsYwdL&30b~?ץ7fRET&S(FS:1otQr02U 7~ߩ1Fu;x(qDXptD Vag¿k@CQ[.1?Tg1mT1s:f8F䅄pŴdH@Q*Ͷ XM?Úf1bhOas&T y3 ^w͌T)" "쪊8炦K[ӆg~cHW)$w7t5sbV?),;]k4)GN j&9慡Xl:d6)IR"Vh0o$tLm hB HT39Gu$H:ǽe/J(K,%1<0㹏ï=3<;\5D?+߄4J'YkaxaPpyzeprp*$uۻ B-Rc[AnƊrmڃ`_XQmK]m}~ =N >WW6 ;A{miOUpp>a@]z3rv%!;¹! 1 BKj7GbOϱ/ķ`+;ڭm?ppr$Zhж +5DrT^*0Vw"ua +%1FI㞼9BUX!QQƸxe.+CSS^D 'CDY'vrYr01&J1jL ęc& Bp嶳7-YK,T(9)6Bj1=) / (ݫBY0}/{u7jX½j'cVn }R|VUU 9 7q[8j sZmԧ.7!W^3ߙ^UbG)[T6jPT9^)RSM!ǮfPWWڂ$jLEb~ZQ0=1Gs"NQgШxU% ݨpϨn)2TMiUFe&-$տ@i:drxNfB?ma ]b0}'PVE[RԕQlW`lFiHSuRWH)z&XITKX mJpٕShY7-:<<{x是t!yy>}!Ňf/i!87?EY{A7?y}\Rÿ ݛ Ɲ x ?@ͮ$l?T}ay lh'58!h Tm2اw;v~gCs;l>ztPS8~z }q ~ ʿ7apo\> ٗGG}]U5Sp!j{=$9 =p~@=ohӰ ūֿ͝ Mpw`v(OJ+_1XwY!jKar(al|z>;ˤ(/u}PIŖ0iT6h-ͅ~WS5ECUMa۵1 %w[\Fߜ0EnuKiskp\9!թF/ /ֳ 3 H-hOOUjIdd56D\ v۰6k5x\:vVN2?{GiViFLOx(xE+14Tl,!; ҡojR@5<|DNid+5 ^gP/*c; 3Qɤ2PRd>>(JcJ?5Jgܪrg9kFE>SgWrq{u*1c@PmDzAMUcڽ*INJJu]؎J(s}uP2NIxǾT3s;ꥦa4NI܂ILDzZ2_R=, %+5Zr*ӽM2 k̔`j"4@̻̓0&/0yAa4?i51* ʨR䩄)94%T^[1FiڰJjFdԪUVo,7&'S ۭ]9'֐뜮uR3ΎW`4M}&fN 7<C1&aP9s k%)qOO}GydvG(.OGO\.D/Eg(W O!= )=_`uwrs_M.'g+ k! xρ}9>a}nN"H;x7+yܠZCcp7bãT<駾 vvs cF*3|<ڼ^8CPBV_XY}7Ng*?~=]y <`x\VlWÓKwB82`lcn}7Nw!]OE!?t h@-ķ$ʅ*k96CO0w%9t+`ЩЦ%bFźԤM\ ׄڇjbX,Gn0DxeRK͘GNUai%ČiH]˪Ae g8%qV?F*`GLu"3\^3JFG*ƐH)Ο -Ed ܾ}/[eS6ΫNN#s OKtF8ҾXkܕQaūK]*J9n}Cqӑ\!⾷~?[iLEm:1n>d6.O/< V=ɔ9hU/&cնQU-"~K hV/a`gRl42*tv:V mrJ0YkIE$"G>7U3pţ"F@7#D+SR .}q F$;lxi4j& ԥ+2)F%ݨR!ű3΍1q2RF:V7BqIWo4bN&Ya״TJj1y셚Bbûw^߿ ԝ:χIS|giv>yI _Lr𬚥܍tfica,~̽FWNS^E{Zl2ž&T/(F 60 T8)yJ^y*eM]rOJb}0_ >QIc}?,n Vؕ  a#R&%SIՓ > Gv;RAK0$Ǿz m[Z F|RZzz7H'E0,PDqlRIU)BSOQ#æHFyz< &Z{^FTWog JzM̥0QsPA3#T +n*׫.|)4⨤a{XÑ^X$R%[i9Y5n)}I[b@ v3dm_s6>֯{==O{[h.I:}wNv?5Yl{ʀ~0;}m|8t;hW3 o @zt6|0_wl`3_?]tN} 3`G꡼=\mT <73ȏe =%Y >Wk{5mHː^#] >:ut߯w3FBF:=L{r-ZY8P% *4BH^YkBU/ԅFIeGzKυ\!L%NZyG# *znU$`(PM%(6 x*qXAJF Ȱ4Ol-&3V#ƆNyhL"@Հ P%K,6V(r"+:/$I RڴApcapdr*iRͧ}(tҐV^;5(uNJiĂXO ?L ?6AJp(KYӥڬ C a " >UMJiN3%t5^RuujT>** 4\e1r+\hSbm2ZMQv/ήD zA4yT27QQqc(/ }&Y@ŴyϾƕu{(cn{&/^-Vj&82D/#q$ ̗iZr_֪wQA0דLFZv:SwM }H|'% V3>۽=ژO){=Ʌ*f.*j0OUf2% IDATVhlL?d`:d~vCj!QL)@OBV\D'X][1 1̼g\yO? ˛xh<>hB4WtF/Pk:fUA2*r,E%ЬHH]JF3Q]۞@dM w9xUIU6~UQ*Tɣ/<"p ŚPi(FozrU@̭ŸYbV!IAZNQ+ad1w aءGJwQHo\5H`y(\As^aA-v`ϿyC|r14ʶ;׿tAy1ں%;B 0*.ҭ)g#uy7G#~3<i{ݐ.~o 7[7-;˩-N*)^vw͕ΜCrkw~@wd贁lwXJmCa8.?M9BI$;X5rJU M JaJ ݹʫ9^t+i)؉E`$B#OB5W13riPSfR*NMMR 珋h#@ AXn9n=.Y, eRk sg(ƶlvaW(ypf~X(ڳxŻQ8U^gB>HAC-3)kSFTcqU]P}S&f.JS;~P34'm4CӬ;mHXGZM#(SYU;g9+IHTozM҉*Hqttck+3 f{P̥}^H9\Z.`X&P/%Mkdu}fHL"EE9,Y[H8A0ɏ18@+ 6m1,DR%^pfȹtsj[+?ڻPFO}S}RִaW Y3}COfڝyg`Kw[>mF(}h-u4M_7S_k[x]Əpؼ3/@ = i` NX r~O,g 0ȫ(,.}_;c^_@2XJk x9)iDC L?`? WhX?~ڂͯ7~8~ P DXN?y:N4>k67yY(`8OLc0JTM½AxZa_TͩR'[} V C8jZ`BPӮT-Vi$ڣ*0U7rܬ+ˊu]eTqR3IDS**~e#inZĢTY@ʻd]Y凟rrGD(Nl# nN]W|K3;R qT#;YMy(l?[UGKf+?7,[Bdھ/?G`pvXG8.~ڝ8g|}YyL8e?~~| bpC@ӳp.Mx_ g}7P~tvylq{owƏPhG.?&n}_C}9^axu4ٝ3_Ba𷗗N_ %DYQlw'}b58ϼ.c{ԧ}v]l: ~./E0A:\v9QycEוS9v\ֵTo lb {~!'kT4h&\`r8/M?[s_{Pϱ[`~+WĤ.2: ?]{*Di/jt7h}µ6~"}tiq%C()ْnL\*ʸW;V´EjЁMT&z0y`+YLʑ [A &CJ]? K fHEk͉P D8QQgE&mj!cZeB0l OrT=#x wQΏJ hD7G%YⱮͰfX7^7r׹6#(b¹wƱњnZA9.Y[<<˳ٙ#d=1AU:Pf )9G\Y*3#Iz"r=b-%,( E!E + B0q'zgqOu͆, 5ݍ]6gݕ] NϾ{Q MЩ {E`yOJFW~vuY?,ZdsP͖fc5C;lYl3:0r)Td1f]v >ZcpVfaF"BU HX9C;d9PxDH+,AfSz}՜itƽ^`:*=ŲIʄ%\B2&-T8ZZZz7J^s>Hb|/ Z x:C5iI/wws}={o\w߹vW>ðuSy ~ .oF;``~S}ߋWvqq7(rNNK%}= 8Ա"huQqJA tb̂cxRe-*/ß#*6!" iqH;}&V3Iwd*>biG9y]^QΉG3ޕfB`,$\W elRdtI,<ǃ5 D~*02&mzG„4o+sEOC(, \UYbcin5Yq}ɡCCȑm]Ծm98 g0ݏ gQKDgBui!/O uc$7˻bvOZ-Cփuq?UJ9rA,H7>Oބޔ[;hŽ\<4c.):Ĺ[v4;8 PL95̿C0B00\}f4N4Nk9Bܑ/Μ-JY)qp.ոt欧:x(JӽKtlVLZXQZNJ EEg®F%Z+e].1@ kF U ND32B pH@#T9m ~10ytJWHXաbܩnٌ822s]ža&L# .wǟ<4<}+n.BtXdvh%s HQ9z@ٰ!cjccvoR#sOt-=һ'}<]kpj,ڨqfEIp9ɒBDlP"f L3gg66Α3+,O YڷDíme:eZ*h~ j2s`M|EQ#' H#>X#̜6A A+~z$Bz .q٬j<)Wsװ3py\,C8,Y}.9paPuL -"G.C&tYd&Bɡe !=<ScR CP n]͌0 PX{lkMhЛ10@eeKQos0E9vј*5ViƿYv=m83"\"}30ɟ9m8z(kayR(2;i\ucAqWn}AQv"!Xc&WF W {u B ^ A uqB֥6EJƅJsGSLCN$١7"rTNNZ JXu$5!zNrV#Ԝ5NFnLsy;cO rgt˙aK6\N3K 4;E!,%a3ӉA#jZ!lan7?r-JYF_zsAXshv0{1wuRaf|Vmi,,{⓽pǽEd0) AجMMq6Ygdah`!8"h"F+GqOY NQfFd!|悅U찱xw`82Gg:91n7;S6gv %G%N4qcDz}zd9Î" ,6M֠3ݘzMFﱢEAU5"qr 0s8&jFV\Kc4uNLҳYz"!6Y03Lc DyO4N%%@Oy'N[4NŎ]DN4N4*P] Vz5[i.vn޽^MZ,qGJ)=5^*2 )ia/ a]UbksJNޭ(Τr& +jBx:4RBЩ;.ҍ[84"b!HMӪ`HqjTV+e]XJNP,V%4wh89۽s+ܹO=#Ok|g]O+< 8fQpb3;'[8R\s"sDVE8qs%\XjXiJgKQ˟Ndp!x6sZԍC3zn6)Ө"nx@{ 5IqS y25j*qq/AyꩧNiiiqOĝ5L0 ُ|rx؄[lFNAE#ұ,jJ`ʡL+U0UWN BA(jkYuX7)0,E)ZhL6 TVY|RRCTLgCqDx1y[gם}^ 0 sX ܨp ͡5'R`[:_xO~SƳW13;Qh_[M:"T F_, ^Q1ѲOs={2dk=g+C3C-ONǯ;؊FMhY;c- 0MNĻ|qo=':5A& Lw˱`[gw Ϟ <T9`JBxZs^st> CJK_f]<܃b&{IG3c3anv2$kvHRIΠJX=DGv3LώGĵ0)oD)nqvWKz,.<{Kи`P]_ޣ"86:wuO><{VZ88!\Bs.&4.zEN+1$&"!"&N]smf5Q,;G,%(ӥN?TD%OMbn+%7ᨊV?-GP-bu4:Hg}$GQ<3EKxh;pVΪs>s>8փiD\5XO$;>Y$]AM N8'=aعL!:BgL\)w8O ط"C ǢgG]7&)vuӳsR.ǍFZ"FАb~O_jy) ͸ؔ\-3oFo o=xkN:tLlV_ w+w.g.w>8_5Itg?-R= tNw춦EՆzr-Pu ؤ(xS)a*8F H4N,@x߄o^tcؗ>kǟ}7ؽ']i+]JBVo}PA4 렼 `uʟ}9Kut4}b9_s{u@n}샧yw÷@.?-Pz[h \i/t(h)PЊYgQz? {Vj;:uV@i(li]98w6udM\[`2ao!! zj \ e4q uJNעlJ #AlluEEb;(CVJbbZí3 \k`# z'7Gxh7ʍI8X:JujMb!'eg (%.sALJds:5w|xY4\0:3?,CTma-D6ϡT RZ I)5`>ɲD$,&&ˢe| 8 I$rD0#)&š%.KX)F+F/!~d3Y6+:#G河 $pi.E,@ թC0(@ f,YsHa /n#Lb8x4dbll؊# DtOg&k铕d)!ڹnCQKJū*̭N7YĦ=t,F3*Yrk/ = S8&F B\x%KZ1`0` ߧӼӧ>}0*L}bjnWpҸw\\UO߃˭p>SgQ©+Ц F7elw7a? SQ`T 5{H[40$j4!j *QOiKXz'HZ~_wea]ѯ笾sAV`੏4N& Pu#z&-D 1A'gMXp0ffX̲]-84ۑq=BP(6OGA̦X+#ji[3ԙ0%9J.Gq1_b|)^5}JxO:ERRϢ XK_C3ɍ5\KCpBS>Kip4qvb\~vcIf?c4 n)tf`~pH:Ayf.P N^U3h.L5_BČH]괅Z(XXG^M}#0 Uz͍;c6#&tF^\SL1NԽ+JN7 MCCD܏HαwvTq6v"TPK9!! 9w`cqY"If,*>)wRG>֫D;M(~?sЯ0}}Ī-?C"ÆYZSk{ߑC_r οC,>,|GLdT+=} U ;pjp_טWtG.\8/8|;_z8ԇTr/!Pez"-?w4qs׸WV{O-P*'\XpޅTn{vWVS,굷5$gMnIlS|qZK@ *L[8Q ڱAPY !qEuv )+%ftS fޜ+}|_:8_ |o 76ʍss 76٬`PǭZX WRY#vTd!,Ha,6Q"|=q9ETƲ@w X`a]"UYKROM$P(PfDqV|B-`c[)-9Mf&"]o"-Yn96Hg(ON)k) P6uD!̒WD7pD~{o?Z wE)RSSbKB4f`ޅ==&'#vg/^C%;T}"]8*!. bS0OVoI&=ž®;;] ;dFN¾ θ [jNg2"ϵy %|j,c"z0u8XQ)t])Uq >qu(ݡ >aj>/SͫUT.88 P>g>ovT˧W)C?w ɴEl^~[V_*WwC}K{z׆`#re> &7'} x&_p!R;w6om/L =̬tUBx 1B`fW+Wz,{b.#SD<}8Cy+r{~<py;pNA%+gҙ6|cS=vnGgLnk^1Cmpdž>?0}}|n65/ ~qO<2y)ZG} q.ȧqdTLtfwxG%>fFiFIGMH\m_K9k؀ Rʦ 2޸ JzY2d9ՄBF˛+:Ŧ[6! - ǽ!4NEp&;E}hW M,r}¶+kJ +XXجu<n /;Wn9k:w;nnGe:fl'g}F0_-q[pPn$Z1\l^(Y^0V4 dl-]"ΧAdcFzI5-%PEBX.GO 3f؃}K]<ŘxE 皠3 ZLJU:~{J)$?ʡyhhgh2~ !͌!_n3 SH98ye3 ѱ`![M!:oξ;;sC"@M#a/q2rܴVP5K`x |oDRKrf$,v Ǔ%#!Lu'ٜ!_*0VYkQkjz3h:҄X8IqT-7Ga]sx05MU֊1i؅+͹jIMop.wv'c?:(4 ov^'3cAB6F;> ĵƌCEe4z`NA^PstP6FxYiSb!, ZJWͪXU|o'iIzajga{vfyŷ2id诂M>?㳠gQnO;d u V/}7i^;c3psϽ凭 -iL*6?7x3߃0>>#n l_ڎƏĻ?lz auʝ#"Y8/?gr9[.~dIQun_Wݷ~s}< \ npM܈{}ؾ}_ArV>#3xp0f|w=:6 _`n1pgw{=[CyW =`8REN<8b(PZ V3 :bN80PJť uhYw ^X! +U䮬qlL;$jN{P8]wOFDӆhSzX{AtZÛ*=t+^AEy`G\؊ktaߔmQaߝFu&:6yU6թR(86Օcw'v?RP(>;$擫䇦?q 8wEuO⦲K:) ZB|A4XzMW\O R#0yg;{L⸖pΪ$4!,;x>R=n9;9's\?OTmz$JU Ȑ(| T>O[V6OG^5#BR{f1/bn\!x͍v)*vw\;nM[]9n'fh)e9"n5`T{ׂa8C|E ]. W ~aqDZ?8Dd~`B [ZoUXi_;389+(IȢ3GSpIGze!XG/CBGlBBG'{4.wpu#V==+:Œt(̨m"1'7Br. ^V) o3'JHF";K^BSN~z?8 Pqm7 pڽ'zWd|F>}[?_1eaE?g>e@7b|#M]!Ƨ?C}9?K\?ƙaDF\N]LEL"ПG@(}3~s\C)'@ ho? ) !X܁/NP7O fC o QΗ=q7$ n7B6υ+5oX՟/k~ M>, g?bY2?%Ld++}lSn}߃{B;@Xź&O pON8 Y:!jw .fZ#/f fC&ؘR$C OXc ީDQm^4ZI85];օ4&qbKN(جHI?88j/-^(VYkB/G3Mlts5Q&W .v3ƺg+lƦ+wgScB:{mmW:\Y85ggSCi"ZXn ueMDCJJ78ꖍ/~ A,EqMȲDc.%v:iKUrvc֫yጫWͽг1v{2lX-Sw=mez\~(?柢zK~gc0Yؼ)&ȒG|ip0F~ >Ùd_͟aTQohnO~`v|O yW*GXw歱].#3a\^`ksۓ _7݆WAyK8agП` Qox="M4ƕ۱~-!>\h?;aBy{<[0VdGv{?> | .c/Wsf:68܆ց\=U V!QʼnvGk1ԞMc>\UwԚ91yµc.a$¨ yOw}xTzw [tD%bˆ[Aue#B &MBWNS7RFp-s>M\\tkܻ>f \.U4r)TI)GHV69lhqBEB+¦TjAMA9PTCeP :3C(NTD&ヱ~2gKY0m;LDТ*N_4k G$<8L),0хlp%"HIK6KPyFzn6@xn."<]ұko؄ Nhvl k&gƶ;W]b޸`ߜ)cxPyեG-c~8?Hk1@lK5rfi$b.5M R3oن7`,džrY| !h082gEF^tbZi 'VA⛱A йEpa ھ;gsclv'\m;ƕ^ 8O gOYY8mE҅ID=DU:]PRi d7W!.P |sb{)奠Mϵg6oyG9EN4NԱ 22~엙=>mq7 ׿,_Pd IDAT?do"[lC'?mw?+|]=m5O{2~u D.0>q/Kg@)J`Sߕ9?ߐ.`۰G Q S"{% /.QU^yV  M\HWC}m{s9m˯& 0#qN&?O!ԔGCؤO!ڽ(tO~^呈/+`BX\}oVowg21Jw[R㘴mO8D;HmB6K4A(Ba)+`G"k5#*@RɣvXygphN-'x a%uu➈`z| hw 6O1bL"hr(wJB95`fhFO`M`4?MpXLb32g&)]U250+Y8A25OU ,hh<'PTs"=RK,?I+¦VKeS*9LVJYPVCa5 QnNzRܘ#wI~ٸcw mFgerFM0ŵ5nNM2bb@94%;.!{!NK!f&tPvpyY9Y'[KnK`ejOE& eN8IAq iFl J̰b5Q|nEPqG/:O;d#]}b3py+'v\V[yk%C)Lڤw%Z5}5]~z*Ĕۡl~}G{6‡ OP#v2\7Bmic}.{cwn0g^sAؾ;"rн}_L@>ζ=W>SG}gq3I\觧zK[u~UN"1V'FzZa"V\< ldB-]m$,WGaᾸZkYiq$~nv;Q,*U' A@.%X-[GЭ0؄7|(K3yók ͻs6F<*M.H Pk:jB=+K=nBP"9Jg.asnvWΧ_WJ -NJDLægʸM(ĶC!)F'WK~*cJن@l&L.5Me3.n| ָ1kŃջҫr P6xs J[)B0X}$ekq>xyA}eaʹ!Reb*{/Tm^${ֈNW[9SɌZDZ%wje 5\ o!Ti:hDvhح٬UB{B,i2HѰdn[ -Ru3j-LZL䩹 3)| ~CY4P爮+Nm!:9SK/5]lcqQkh>Z6τ.p Y:ȷӞl^F}; %lOB_Vr?R8t g&bz/Fn'}C,*܈<wėK;ܢ! )Zigfs8UppOdHvӁQ=9} b0_7 4հ}Okk0~bL*?8;Pžp[}0zN8˾(gy ~Uн!gʹqW@yC 5qB89\{>z*KA \OƉ ҲIH_wT|j#8V&8 /B`9MI4`]膉B1 %L"zOV\!lҫ Wk:*<㖜8+ Q'b"<.gĞ.߀]:vxpEcCclM鮚@p<ڟlY"2|vPS8VJVV PZ3&HUJ O䍢q+5uk+w4%'sy#^}RJ_±5scWl8,=/i/6d@:Mc8 K\)^4EsC6բ!G_P]@T{\LN1qp&쪲.*"fIFeئu/ ++8 + j%JPݩT_rsO(ˆ\",%By9i!Ȉa]itcncLc*Yǟ!OE=2օ8)+,Q;VˆeUIL?͗m4 C?AH54#UCo4z%iN`a7(cR'f`yfPhҒU5Z"5` ޝ;] cAgN[FSs73ABrͨel88z"u9XmHf>ŏ2~O\+N9}O;{ӔW}qK=Sxԇ`0>1.8xggu{\f J~m0=s4~1rRׇ5>D+}[N%;?g!ҌOFKܵ7/#X ~'ه؞OܞKG@^r3dA'+{v:նrMMr0.O>5N2[y@п)D ?|x˯b/8^ѫD+`g)ma ?`fc0ȣٸwmƺqLaz-;X{ P_ AQN~>x PHRpuV`jHqJCd q1If.&PL7[ a:szuҨliPETʦ;YаJdBB%"q-]!q@ѤMB"Z%+R=ag tXE6̜!idt݊;)^%MxvwKZjGWle vZKӁeX@MKP*.RYp1? (J׈eYc.me!t%UE]z`͘4R3(Z m ق#t؟f/~εisܯS fRdq--($=pHɂ9f:L}Z+l"D}q?ͮy֣ nl¶:N%e&Lcc);r0]Cv0HINΟ^%ܷҠ̢w+FpU3YE Pqm`eq0wBP-` gY-LE [DD/)0; t{_֍e%4MXY x-ΌEn0Lf +\)}bm\&ak#MkbYK~8O4$<}4I((n\̔N Q&JMi3qxl;8 PWfrQ񼶖{7p(7_N7=EJ3e_SxֹDmf8b>A}Yh*L.Z 05Z6 g5JOg?TwrOfY}W!Z? Qip2| Ξ nS#x8"b7Fl# \z·3p9/kDnG?"̭k DOBY}g8. ??X/jI8/w"xc>:ؿ\{ϣ{X?WwidkSJP:> ||W8.X}{#?}u(t/QD SOvCx?s.?'?mw7![o0= Xqc({3h҅k7u%8x GA٭ hql&S q}"IPFe,%ҢP*eS)^ ލXyP51MDY0,+%GQ!R_C\X!k;^Bx'S~ń JUxMɹs<y&="c< %ŧR(PJ8J]k{4e{M'e]<'4U[xL>P4b x@)E >֬h+I45ƱElX{nt_rsa`pZ,:L`inES*x-_T̝66*YD,~2#w_*ٽbD(4qHX<"cݱ8@ ku֮E("Ԍ"mdkh&p1Z\35H 𑢶8bP;;'E>>",@e 5Qξ&B+TI 'cqppD,5S5w=ha\Ju%ꞅSQRӅ0^wֽaɪqКf'gGiwz͍)ƎHGDnc g-RS2 GqQ:T .w~-wm`|Ivx7O~evWi#7Ctl?>c=S/c{4Sι{73ۓpCO{{P߃%??p ϲRx"ټyӿ)V>Eؾ XOoӥfzOvahL ÿV95~{sc1sx~?.]p8"@yd ˘09bܽls20E*䆴s6O;WhѨ9I);ʺG6夣 QѢajh"D&F𜵵6aSF&)2ӓ,`yt]Q 76.RΘTkКpbJZ}r=³ކwYxМ njZ3#PP$}V} yQ5!Te #"q1 $4g"=KqdM֘fK-ט4-$wĹlb0L8f>O6pM8(JIB<ŀf,Ӧtpjd?~9 ֎ό}uI62n- [u*V=MJĪ* ݺЗj4V&)th!N>#Q4ht%"hGR~=Sr܀KS K4ayt3LffABG;tUPd¤-/4X8$&5Ea)'ZFE-lخ/MbV:s`=UNg{aVNAW|[uN_a غsL\!-!`(ľnC0첰//jc*Ʋ L_P>pq/ev _2|ud29{/Χ+~`| 0}z_ w!?dqdp"4or|O'wi GՋax|u~/+ϼ."oN}M"'=UN/|/+Όu߾k[V x q{k_?{m.?Pr{H\jg+Ri!4q.(%kHHI8@d-^d*ESRd]u B;K^gi̶J7='O416-B/8w!L4bIGBpdv*X.EZ 5xoCJδ"U)T.‘SE4yLܓ!tBXV0Z$T(f Y_}v7&6O>oHwo A\ L6;<2>ϊ-b9 9V*^ 6 Ocث Ғmm!_{|s݋mSՅeL ;ӹgT$+nFoZ IDATi8涻% fa7_/Bp\ˁʒAYEu~I(:`KbO/Φttb7@&a.S2^ 3jb [Sc>Bm4[-*osthqt7CcqQ:o1@{+?'+f>qqq| k4w}hѢ% DSK*ЪMILiQ=UЪt:O CҜy UE]@`(xkH/QM. %xPXkLq ^Ԯ݄թ"GD"̪DcJڠsLƨ?lFgLKB;̕͢<7lU9MS"@ .2X\70Љ'Y9 l%Rwbus[{amߦh.75E4_N8Z'Ve!CנNi:Q'E'1wq b"+oΜ),]\C yV BtrEld>瑇kz/( ًPZ L0n\ґS82z:rFgF)A \q:qH%W\͏.gc& >p969mh)a2APũ t5H=-p ň0x@/lQs.HCt$-.& ki-\Ra4uU-.4FO,.fW9GqQ: -qq(z.y V1)%EμJEoԝeQZ*BМl8:."F#IiL] !jLh%8 k]kR7cv HMG،-ZoGkCpv w@Pf(ٺPD_%p<ħ`.v%I)>w׮nu簛z|P#Kw8ܼkoa jLCR[R|7̧AK٢4RR--0vwF1FW6LBёh\&nhXU*JLZF1N#>8knBV #}RzZvZ%x^Lεa+lp~].w)V9o^Csder|z\F@̳+$Av]x衊M֦4/G") ʁƗhL~7.RGHj,7]ųm, qMw 7-ɂVY$5,OL 4f,%^$/w9Kx3f&TuUM pXhE)UE;`4q^&p^l!wwf-=fJkNkԌF/Unµ9Rg;)"x]hW(p ðl&4oI&'dHh.dnviv@wK=i)T g@{VYbF&xBQuFYehrхmbL|CQ؊a,J2cd V F T4G)FL6+tn U[4:[b+_r9nT`BB:&@dd`mqTD*7c]N[h &\]Ykhg$ Uoqֲ?ّuq888obrR[}ƹQJ&PHb6N0l(e:H`TӇN3U(S^d\%g 7B+w"TԢKw!yWRhS'Cizqr]3nwRVV(l ׶#3 }^9]nnuv/VغQ ݪb\;v?yF`hfsRykWkm*!VupWR %+7Y89eۘ-,q-o]BɟFk5Zŧt se&'/ TO ^(> P%&{8y ݽdi&r=AZfTESCp yOйыsZ7:_ 7µNp¬V=:S&v q, R5p z=q́~JhFB b(mv\\@LB|(uX]a5i-qFg!*NqCQR\-;KQ7c‰j IB)<- Րbx5y0إ~~Y}Gx9H[8NԣPzNy֔ќќ!EޠZէRؙ-Z3)05Υ}Er{7XM,Mo<8k~Fׯ|#/~ۯp3ǭ͞@#}VoY痩- oko]?2ȷAy k+?z: Y}tr_=M`b" ~ ӻ_u]/pxnK)+H-1M5D1 i,qD< hWw Ec{Ԫt%4[b&xrS1&u :QAE)u]QdV(>MJk%E,6~qP5qpDTUVU٬ V+zp%5Ni):fjV;=x =|瞺ê uÇk'[/3hYFGNIyh~kEiQ^?Jd)4"L3? 5Omw3#i~,B-䤙 ,D t.TsJ#v` Ie'ف~:4"  ! r{y w }$~֋op/Sз>ا?>|+G~:ΐ.'͠9" QߔujKs2&Z Nޅ5 $!ZYuү eL -6u NS*'W ^rI'`)UtVlR v0NNijMyΠଊ)( m4iЕ:TaS*"ԢV1UO579t$t*{G7t}cwy7ר}ǖ[|#?~iefTbf ү{pgll)~^ZT@BrN8c'RWٺ՛QȎBh%/]"^񅹴gA-h-u^!NbR+KMK$7Kǁl+/w9^DI>,\x2|:q6qZ:]wNWp2NVI'RCbPD/}RJ\ Ҡ`&y pfqw؋3D}\bb͡N㐉~9b .IR*vL@mnvt)R(8+7RR陨4( )wpvb<\;8'6&i 6FPRI6G`rTP 86s1աhAJ(,XICm%6 C X5 .r`CVAo2801k3Y8(@qG=À/p}D+>H;{Y'q'?>#>w|pi((< 'W\0 @p>?,` .?ih:+? '?ݫCh`; <_[8[U Јn S('dGcF@ti:C2yJ IXwtº/j"!*4'tnLjRë%(-`isGAhb]/V<,KT^鼠ahI5ӶF*46s稠ͽ[ɑag 1NHMW }-7IGպ׎7u'8OϲkiYyzm+xw6scBs?tt3>2Yf~[45TR@ׅkNgg;8dD,/]K--BhFyLEߞPsԘXj ,0$4y_x6$2Rk?sB_s2~*:m8s\4~^uF}g_3B4_/̱XSXic]`]MtʺVǪsV*Il TB^$HYl+i=\BB򸤩¼Qmd(4 R{.D(c'\j2hFʪՔB'*-D8]?`wns˜#fN2a5AלڌҠ40Α{ژ"4eEhWe؅ۭWe-$%]L/FhaOi:[D% CY89=X^3,z7d B_i<8,g0\Ƈg+ʷm9? y(]Xd|88Q{{ ?}pXtPDWGqAbjЇ>^+@{wO;`? <jdWk 5ܣIЇ܁}D*cw|lxD$rrYHQFAfw䶼K~+E]/@1b]O@&+XN~(o냟y(},?l:48{G.' /X6>l~"TVۃXCD@r,rMFfeeZ*B~-1MƍHDA. ٺƎ& ]dBtv2 /51US4t]կVZLpFh lp6]ad[`ptWLњD&h '}-tҭ M^CF\/xo?6@q׷P:W!;#/{mtse^p5N7wn)c/c`E֣SAVOX}s_~B-jޘtȁ`c67`d>UK8Rl< 4QkUV%jD^ܤ&#vhI:w(>W8{sDP4#i:T┲o1K$0\okzٞ_y*g8"kcU }5No jQ/et~BБ|)IU=L&jѓgAtR Xk[6ܔ;Yl0ɝf{an1se_>9&-x I4 ig\\pn{cq*p::A8AXj eu&%23Ƥd+rb)DzJ4{⼚8BH"3u@0j pӄE7k~ ~U]nAxn$G`/!> wPvLqGB|8| B=k?|p 7Sn] ߯|/l~osu \do݌yp>_}0}.G^3 kpaބڇhu> _<>>8殳,# IDAT|;`|_:pVߴ yx:{L{/]nlq> x4pGi?>Oo@E8> bAw}]ktdZ*ZNFDd`"7Wewwޅ.kCi_nRsgh8m#;D!'%' 3'˨ =ѷڂU:9 d'%xqL&Q'^ hlp9TƩyT} 02M"Z;nᑗM~}GƿPζNj6U>OY_x ;/կs%W|O#2Y5:ZԾC+G1n|sO!ۑrk4xK3g0p"Ykb`{I޼E#YW }:PV&Ã{Rv$wP($ "ShA"nG-+<*_R&c>-P|mNʿ_Ǖ{ge_fsp8R=#h?BMƗ}󣐟rxy8]@X{`=%. kڧ`3 P#"@!R^c3Mw5(dU! M~İ<`bDWڹOrM, jD5Ȣ,hVR+HႊZv"%R+`ں $GD܁6FNq,0t>:ˌ) V$a*s,Gβ@M9tڂ˕t+坏~CӋ5_FCo'G4Ÿ⬇~n>x I81t<=*w1%*҅$f:nQܼ}^9Y .I3  3#DSZSLJ M*PN|R/)\dtoD5D^C 0lSbi+LrNm$O$JO̵ivcv9qV1q,8g'D7՝x:.|'dJ ,!qK 'cVl%o@B5$eֺ)xo[$X)I Kvsڬi.L,.`8jC645y"dּC{ƌV&V=rXU2$wIUCRrl$e?f(+Q>c!0CKuQl0k['N6~>jZi<76BB &)ey2ɅF,X_-~#TG5!kЧE$l@; jp(-T\D.ZYK  a4~~(S@-pAs C3𿊉sK_6o~Ι#\N=} EwQgFUt} ?u_P[uFw;}XPsp}Ū0~6g_?.n;f]pS?? &/_w5(M q6"tU_f經.]aMßG]: 6~m|_zN|]Kmk?2O|{񋪃C@#~~5>:4GsP>>zSc&>O_9:QI9v

;]is?򸤴 3.$ /7]TхRϹx!@)UV\HqyDQ ]U,m]Awbnaitqym9ӛz#ҨʢȢVa@IiT\UC[% &WH>eP&0:941YIuF6`$xW^#S\oFEK[.>M ;T=v&Kɠ2~` V*Z)LQM_'iieĶ3 .Ƣ6Q$4 w$~lLצּ`L=c𔠊NBJ*FE۸[je2PBN/SPmc;sMrT#-kY5NgnqҬ.ĘE2yoe&g>D[pUD7͑j~^q&RĎZD ]0l669G#^fAvp Rap|YX&ĵʧl@.FI:cyS_A(nBx.Uz;h~JK*-wyuuπڏ PFyOav̟0wu[駢bÒ/o.2;.rmڟ +lXStuZfA/CNȇ.H|XG Lw\d/x^c.4O`]Xk޾M߽a h)vqyHdPT9މ5K>hq7}лI༡*eIB;>XوF/ac7$8GWg:ԛOaigr嫱π~;P:-obҼ1~z.@ԚcL>)d?~TA*R̂Ԋ45=9E^b&鏉oI]L%Zos'lXQɫ6sԉW3#ދYj,¢/ƑUi TKX8`y,Ē$pŨ%цg0[7tCf(0Rh2`{%%V5s6 bljf~Kwθw&q;TqK" ɹ"P8sfO%qo U QVKY+4c^1?2l\cAl⾰l EnaⱲaõ=c'Y6>ods^S^O?).iiQJejUDh$؃j0T8RJqwY4,ɆkmX[I67RrwFJVkۼ<-5Y00 k({贉uLigg[4)TJ1j";+pMT^( 9[q$D&OG;d4(L&8х)m/bFZQɐ\ܽb`$>>Yn] II=~hvӒ7;N.]񨝆;%[m FSn2tIF7[Y_9FVCb]2mR6#I}RlJK MGX 76m|`>QU󠜑$ YԆӾplX ZyɎ3N/zN_g2R&yzU)c&]2ω|,Uz53X'aiib[fREH<}ۤĬ:8Gak,'>cS"\BB*)SeGd aօplsCLZ郵Uiwvs_1Mεp;.ZiR]e5rC-x{ L%V[_xg^:ylnm+DCKhYbBIM;csGJTJJ[ h Z7n=I$L;^51\44J5փU/{aaTڬF $]KƐߛ3$DADN+,lnD:)z V^0X0JZpj15Iq)dLj6Vsqg0_F8ǩjZ]FqD#hLFji)P)VYkP3YKeBSS :fBv.E60%g Z8yDo]6"KHIEI\}[.XoJ3yڠS h-xB$_- q3bhT.b423V|~Fw"57C>z]ݧ|?/Pw+Or%uoh1Oz巁sHq;u:*S}M. JO=&?j~e(ۯu&ב\Oqh[y#~>4_X.4:=]F'{4.-[PoAlү\!QnoT- _w9BaC_{f?_I&gl3c~SW}FVKrDegC&8s2B*H1lZLi+@=& MRabb:e;wUYXahmWR}D[Jjܭb1D=$& JD{JE*kfεce3FM(*;#mqc3O0kz[0p}-zWl=rY7P j݇Ih>tCl_OBGfX\CB$IX\ה~fwk3ڧ ϭ/BWau݇{Cn73yc4)J@z;g>quпףo@G|a9w %.߆3S?rsa8'}R,z9Cz욯ڟ-Y rLucp])X1l1+sVF\ޔ؊%ě͘@*Џ0T,Đמ@l[.j̔J< )&#n.&lbC$$Iv@⍂_]Lpr݆6l'{0:Ҵa'x6n4j#Z\ē`THEX oX.'g}UV\МiOlEgk,z7QEķ<>) 7VQpGUQmBn#d dvT& K7cCy Ř1C0:]ӮPtP}&6BfqQ1'7pl%uIKcWxRչl߇cEk74*.zцC-MӉqƶ$:ŲmNPL+jJ}o?-,@ `W*1{GBF5?ǰ+z=4c4V^X}w[o3?3ULfc|_+6WnAwwč)7usQ.?㗀[~;hws$H=~?p!] w:8VG]0gl}s-w߆ =JY޶y\ÿ">ßuwuk*a~@m@_(ȟ~[ວ9gtMqڿwq8äsg]ko ކxa(߁ !]}.*j ?Uh_9aO8C*_}б6on:,_{z~(;E}ǏPLJ2.2`Y&I o8Ho|ٖLZs H? CgzmZDX!ܿ( ` q5:QEkxmU\r*4]Jh0Zoja5TCb>5.Ps8܆s fӻ#mWz!3ROX/+y=*4;3.]i(1JHb+\|ѵ xbŌᢡ92si`02ÒEp2_yk<1 3NWZ+U%K"J%5 >%;(熔scDjсn ka PȺT5WӍ;E,oCrNEdσŹOI)g8^N}O'/..lgb & Q$d,}i˼ ejjSn4&e65VqTI@,.\ 'SjĘ0uzcu3(mrQKF. , t3H3&xdrd J#jq£ cMNH'ٍnZ, iܥnJ14ż.XXdKkS{x ϴxPoiS_җx ?%˯3ڻϓ @"÷Yw? ѣn<{Ds3 (_c[)(O;{tP掜xsA,D$w ؿکc` V,`vߝounByŷ~u =]ǰ\owFw^ oksygo[U[\v /`U`y{?chnwԩ OLQFTsQ4N^YTcRJHmsΑrW!q sb8*UY #0&!["%:[[3&_Tuh+hRL$$ 4&tM4MPCǢ%. Kވw8f\rg=[rxG3hX EAjW;\,/\;w\=s_pO[j.X2T Z &˔R]ëlY]Ǐ>N2% +>r.stر<]sƣ(<[s9yA[*Ѫ;-j7SBR>pO=Q?]|'Aeg7??st-+ϽGga_9i|#"waFWy }_|< y6Oŧ_'%Nob~_K~k_MmG+nzm΁> \է#g@?;=_rg띷x ,>,v?t?b ęy eZ0x,Bԅؓ2Hr熤M=9o'92!±\aZ\\( }brr1r,VªƢt*&|$LjgMٖ$dimZF0hXaſ*Vn^ Ym~|ĬM0YO͹rG,5^KV9yo-, KBtĩ]|K /W^rxT.NhgeП̚C^>[pW(8F^8; 3OW.QV#[g ljl6/ӄ|#L&"ވ4I.R*0RJ?jڢ E]1݀c:"=Ž&=+oIk찝ݔC)o-\[X4L 4.n2qQRwsj).@U-0=@Sf=gQ A(QGc8P]4.dԌLBF"XDU;ԤE%Dٸ̝ H5lPiɢ8' G~~eoIm? Oz1:q70,A7bpP67fci?iP19I}n\) 9&Pkaֱ#ٌI:fJKYR'vH#F\4Ww'mN `ܻH-a>sp]dq`@aS*|ƓhD Mi`Q(Ubf$Z&Sl JXr\>>f$X*9-fmo蚖*WSiT9& gm V66ņ ebݢXz=8m>blJ+oXPg 6C4̣"][j4A4)dpa~,,̝(yG%97ՅϺƸMpŤF3袇p}0a4a씮U QOe*fYMX ;B5LNYR &4gehSP8{"cx`tَF؊h6:6ubRDnP]_EDMD; 'vChw8ؔ,n dBXl*ۏ؏+-P7Br?&O؏/>m&&ZL:DVJe8>=9E#aQe!"3P;fz{?t,!RCJa,lQf,awgK,pbW/̓7*Ƶ O4xrazY2"j!ڶʹmܭFy,/:׆+)XCb 竁u?F~6pmϝMhH;a]LC(.{I&mטlP9'$Dp )AC 3XBSŤbb\LBBT4T?O|0kB9Ʌ-8GY4퉃-q# %\\}u E]103cVGBȖZ55]M`Ur6DT6q8&ES ITIs>PHƐC~QDeG q0ٛR%U$o]ռuHRhg+"F_wɜu9j4Z8T^2k5V1qC*aSfTmVTwv+B@𼕰]T.jU%YjQWI"dy^K0l!~tṨ!oQ7~7[a??qG:؏؏"0J j-f3kNυ.gڶq&&#"X6ҤJHHJM<uʸ4,R{ %?mvIPx1"5J 9 mѤ,y<+W%N ԔrKU͉öary=6kx4~&rrQ#WM/s w8o.k0ce#6z*d(2TiW cV:Ŧ'_8{\nu.^<}6 4\[.q|au.`ӛM4mCfRΤԀ$$tj&P9_,=X]m6V&2~av3#E8 L)̨iHP2C|ʓlؓd'F.GAtkoK]eLN1gIGb[Hn!0K[dmgR^hboŅ&X Όyc4҄#y3t*t Ŗ'gTFU+ToD(m] T/F 2Ewqyӝ9R]n͢\?4bUnXZ ZTTjv&pr"T1|j4YS)󯘎b,J&ܜnn-1/0/0kMт!RFH"(i#@Yo1b`0&YB2 )'/]yuق' *ޫWT 莔o9z`~~~~R9fTH -}_Y+U= \qjmw736hpqד& Dka5 l(<[ZKG cM_݅qo.+W֙A'Dm0[ K5QÊhcy[UmLLpzh%W9pћqM?r1iФzYX4+C8ӷ~;\w7| Ǐӏp\ RY WVT Yr?ΐ.Xahպp1 = ǑöcN Ƿs]'Ul. y(\E|պj$eHnp5U@ ՊnoPuTR0r#X;׎N# <B)UtQV8 N'u4bpifMK[^Jlŧ$! b;Q)bsix^"ٌ3cp ?_DÝ[=/Ϩi4`fecY=UI4% 6vX̢Io'M;(.D) r0p Nh0ۘeca.PwIMqacmEƊFIٶuzMg bmc #FecҀ1b!nO"id}B8+.OQX?)(AJDt^JZo"TH62BG SBtۂwU5EcUuۼA mW*iLF. ǟj!B1Ĩa7&0}?i'j HK; :l⋅;j&yqڏx PMtG樶`v jŲrpx2V(EВICb.byxHlM)-*2e,źlHbwz|o2^T\Yd-soJcGF]!,QY:TlZO#OZNW~m9] XVbQVP mhZi(^* 'W_#'K$E[|wG4i뚵*=3[x=/Θ2h:H­'t-W.GzrE.=vʂ$+(H-ɪi 3"pRpH4ĩ>TʺYzFX(CwLibmjrKUD5%%,L'9Fgg|]׌W.u7I;1L0fL oyi#gg6(Z==y+ Wgv[)~w;vD61A Mc=5Pv]yzBض;p]}E3C5V#t2oa] ]#Ip1cQ%9yOYr*R4ZHGĤb)Wv@Ro*GuiAۊwCF+'.;:WIPbJi]+kUBTGE2tq$  ⊆SLɥY!q^ ~V(/@ z8 IDATwBcP`+BSln#z :sx{ u79~ɓ̯0[޲CKMȏ_؏@`3EvzX^]Я?W^X-2fϤyS=*g: 3ڶUuҢsJ^ %*߇_prr87NO>#aSy//7Zu_Y,+C_iъVHiFR2*׮g.,q9g7 :~97Ts (L@b"H(N)nvXp+ko#:܋"ԶBZhnSbp$@ 5;s>/s_f(2ODFVf{n4D\𥣉40F/ exȬdwV}m㥯\g_"r9ތ"dSY P ނSfEE:&holNXU1TǙr79Y_ 3J³uIٽ8;2?^;"h}"IbB\L)[YW8K.uC]缧m9Ɏ~)9b ѨhޞipEKF9"0F~'Xy[~CybG]8/j 3:V yVŐ{KJM\80-p\mxF]f-@5ʁA13X;Y>H&u1Q"eU Q(Q*J" f)ImqjJ"23it)X~ 2(0,lyTJ0`(l J6"SS'5= _:h48PF0 UVެNܻlʢPs|ɓ/!ER;X|Hh+? +!H"#,M a!5L>:lhT)/gc{E+9V~a0?X>:[ ~=:F>'</@ dk1̎S ]IHQQbY./:6w`s[p,N5|8ɀA|Ee&#ES$!E-:؛u>$zi;XVɣtI]$6vc# p(#&cLǃ*1&.1+ @J@ y"Dva7|d-Úc_qjԳ]W᫒ҕԳ+#:vcb8,(1w :!.:.XqRcM P>Ģiix)° mAb> )jf_έ}~c㸐Kz b^:L$ybj-)%RRD.'Uڮc떶<\ef҃ >D\AG3nanBMLVo{s̈/F^{5@ F (_rァyg/@+5VG*^_nIGSec8{:07n#{ >eW'ׅ N͉E$zFG޾K߬ع9K(vpxP5}Rx8J'*ZeUrurޓ~W: ` EKʰr }\KK,s)"ikĥH#E4cʯS\L)UPdM%/jPM*2˷2UdYB9pdnh:u,ND pfX('\r)S-L@B &aZjma J)P`DosWRo5 ޗњl\6mQ4o>k좹byD?ǭa34V>e~uG q4[7Kڳv5,BZ]0罌4 O\&O1z࣬<;tho 7:O/Bpl +>qߙ'>9m)4xpa L ǿw ܓ0x?fAwfc_6o#d@/Lȯw4LޗE/-\})Mb~gw_-מaudE-Lh ě}D@VB߲QC˖i0i?85Y'n|RϢ[ݎ!5RtAsEMbg}PeUkm!loX,%tњM%%$ԫ#-EL:C1WmgVxU\ _T:X;FP'GNz ]Cq9F"X[w)O>۟ l'[ ʊIbP.H0 `Õ[=Cy߁2';\%fWC5dt0vNPqxMDIx~vef..#3˖1r J)Y %R$2*``r&~rB^6+pHi(@R-H@3HdlbYjRm1\v=˭NpJY*eV?}Aal()Z @ڱHE,R&ШeRE\V@Onҥݬ=&Zϲ6AURYJ`,0zSiW@/!}(ZC ^=7#[݇B"p@^=`{0ZF1p6j_n?8X&:oCyWyݛ,mSH'ůѾl{ȘcLjB[OCBY!r4/PE5 ijш-Eˢ޼Nn~؝{vpnzGQYE9QA=B$ga,=@mVdN'#*$u;rZ ;s^`T)%ֆ2-2]$H {3o5#5偋69:#z}WҡIHMbzn=9n09B[ /fݸA YY#v:n]b 3u1eR&%B(v 3(θ@j0`2BAItZtJM$Ͷw ^8Y pM$UBzenisMrSiڎz4mSV@n$%gO[Ipc8Ʌ'mDH>;9OU/~e'ߵUsǜ>E.xVXSTL axg><ǃw9ǡ˖TVs0Nu@ X֙]IU!Ce3 0Yۖ!Q}2!DB8e`aT1bj!pwP4k+.F\HpÀA tRR ϕepz]S/S&}fKۢ+$wR*䆾eFU:MjAԱHM%I$d=Wȍ:j ~w*{:>`%E;F^8y*3uP/B@ThJ8ll4D{R{rm.I!.zKRK[8G@+`o^G}5rZ-4A bq8v5K|)'糧 {_=ri0UPy`B( SN/DguH<9 `wa^SU ͠}æ߀^30 X)W`"LfV>|n= '`!(g`SCC}n%Gaԋ7aW.| uB ͡~.} O[Mf3BC3߀72yMg0-Ӎ>߁ $O~6wʏPޙgZtr Y`[x0$~@yݯܮ ?G@YVh[6O-*RsiǵTQ)RbNlO=kSa4_ xsmhc&MqIH BsJ琔r{ZOfHHѳas?15!'(1;ǻhJĐ]<{ȭSaJm`VYRQgԈV+S> NR UO9{3m=-ͫבũ #ZImVPߜ2 uORJXʐ[}:e`2((+OP4 ۆӔ(D#;EJ8 `j/ei_r65_67|CNY7xiup<11J.$qBQ=Nb"DkhvZcM;6u@NUr8s?oay^ok +DǗ)=InXs19kzU!RP|#M!4+߹?n<{&mQQZr 8ėÕL.͂0JpHH1PӉ5O$CPps<\re F54'|TLmxˬw!T/:\^j(8>79%H"uPPz&;qNI)QfPDQ $$#RFЎ30/zge%nnéB`qt *GU9Jb (D &h5 H.4d6r˛=;gC:t"=t %J'TN)R98ea䕡SN:y¸TT%i"fmP@QCWe@p̫rez]Cy!͔ЩWG}yq4(>^}UWUAw[,^__Ыd WgGXy?gqYMؿI/_AJԗw2$ߠy{'c;PG>rs=A|eQK0ʁʻ@F]W?anu*N:I8^p) R40zA7a)@AR=x PoPho2Ϛ giCwՖOAǔEkcY@Kս0x. UG/Cw֧8cր-{'m5`2|;2@~@U /ٶ>;WaJYg Y)]ʛ]0U7ly 7 grevaeۖ84^B q}{{oRfW}V˔wK1WwX1G|"[*wc܁' l?Vv4Չ:6hl)JR0o#e:O\ #2k`{OYx ( I) %@$Mbw9"0 !HDWnu:_X%S'<L)kaBC)LSD2hae 2*`v,351TGmXu6Y{k / U}kNe-XX0v[ 7k{ v-;gxvɩUF ׯm1,H$9hX$X|x(:h|fs"t*+O V!"LEPt `€QrL놫{ּ!.4 &[pFC^X7_ J(k=0 !uҔL-re6o @ŐH1e|9")GbIpT]EMptј{kk|/lmW8` [{tA[,J]pc'O)VS v[ѠUM: aWGr#|:lra8cs.]ͽ !g%  $6)p`+u~X#e-cE:%,)Q,@\=e%k ʈI#ʄOA48S9Hr&YmAQ}1 t(qy 3%kyGQ*eUɢ>s;HV" Z"X.%MGƔ$H`^xV7Rf ;-tB 8F0r c_0t CoPt:?pRIBR| =v94`e#r2zD(pK2 Z';596bʠGPg?Y:ئm4fJqۙ<[HQ1W17{LF=mBm/}⁄|7b4K7g_=<Va3O5_B] ~,w.+NY \)Oo60x@u1X OM`m߃ _5pRޙ{^!]wn 7M547N2*pǍ?bj v4w⦼OA/Af 8cPinf,oO,}l>na=P| Ÿ@$ 1@h@+< A| %_ Po哖cB݋w^3K)OU.۾$X|/>5.e FOmyYmzge0KVYa]č?hǍ=Xbj٦a)(G GgY2 Ebn+vkEaw܃Ѩ,#/o^'v;JӚun| [{+7[϶'}~MhBPYL%hjX*MkQTɍM {[ҁWO@.& -(EGa/ .y=8=q3pC$;hY*ržA(*ȗVH~.g#ǎBVˑWt Idq]XBsI,TU¬HxCK+>1:sr|dtFc P[,2Xhs9 ~!vEf[4X+D`57kM,Rކ=l ) eh}/ %cVCC3wZDpOG_CRc' mH +hP8eI4 e] LoaeG'|[%!z1 ] IDAT/ $Dl3k` 'S= / Eh|k&f2xSPML,Pv)V,ME% $$R!%\rPD[TYi?Y.Teη?/6CvY9mfCI.mG1|HiG awDL 4i t-pi4T/HV89&P@qְ\o3ԯʁeMdi 1U:SVDPeT8(zVc.tAW4A]B22#;k8%:́r}&Aa݌u]VGh_fu9&ɡaK?{f R?cO28~_>28%/kT`!nF{ТgЮF9LhDhopgA]髦)3hpO<~$NEIZ^ i־%y7 4isrաKi*-׭9gI*7]>S=ܲ-(|'0x 8-(Bq;r}f7 =g[ӯlN^ *4? ~9H 3م+/RK\~~n62[\ynހku>d_62Ӫu[A)ۼ="zx{.BkpP^0d՜}&_1_(WVRyqc͎kS%8o66#(-F$FxFðH`oE(%mlnXNflnH9&YҰedRAU ! 2kv 7x%vWݚi$Nl;.ԜX[Pw~ސig]q[e%]'C#Zv颦NHGJQ Nv^WHD#tDe;)͛s+nHL; &XWP&F^sz4=sֆ3XRTUMhuy3d&o9E&>EbLx(<>i QiH]w ٬[R%{2۲ m"nPTpzU"4[]Lٓ:Xrg1_{ςD*vT+`ෟRW+oPSiGזĘxFΞ#t?H"&/AY;2,]]PX/ل ~xmk-?bsfKDEA,Cj!}w%SG!;!9,D-MtbvրP9ɐ1@T6)z)ɵQk&2d-ib9q}@RSK![j~M%>9|R W $"J ҹH+F: 9)*o'v3Ebrsm`NC}{_!P8D)@340PB-lׁn`YwPTegIiQ:s‘Jxx\jCz hzCW.r@Jo9q4~K/vAo~BkϿ]w +O~yG ﰼe"B>;睠{' MSd%sKfj>W7An9rܮENN3At# ĸLEC8!W2`"SZ.CndAn iA\VenVLuhﱴsN98)Tin6Wxiq5nָ- 1_5E[1mJ߂_Ǭ^qΧ^sS _2FS hY gV8Q"@<=ފ\J9HfJ$u*6P&&0GpfkplLJ!v +"&^y͚yqHN3RKTG2;ةa\'&ť^㸴ٲxSܥāóB3s>Ĕ3fJtw_QF\ӒE!Y1\vUH!3$FV+bq y!1>5< %4ՊY6؟G6?:n^" < Ow20ۇEL5" Ǒ)Pb3Xέl e2U€sȣBLǤ553f(0uYN T R ԥ.IL%S<5tt4i]$,)&UP'z4%0-P5s$Zl;Ox,ˁ`SPrwbOn M˯_CoִlŅJBNWxwz! *ن冼5_ B\'>8sGhүZ"\~| ċ_Bw^7oDq'2>'<i-WWOyH+YYs MMOg4́Ek ~k;z@].02wqb [м`ÓT h#vq wyVOB3n34D6 Ǥr, P8T;!r~kf/~&D N64v+1mD. ,J9Tyt|o̧d,Q YT짖 P8+A,f.gX$t3wEz{d4ly7~ydQ4mwx>[eeܸۡNBKR]X*O<8{vܸp!+W\HHRn((.:hdˑ5y/qYdiyCo<nk;PzF} zu.1:_zQZ4>SH0)@*t j,Y DeTYhD.ZEfhKuPՑj0l N̟:ie*e;BQ @(r`J+uj"O-$IKsil?'=F(Pe !@[\yxgYPUV:^v"C!$OѪ@U*&{l̰|6{\nS+YJOr~] I2qLFg[=Q;VѤh_n5 \@tZ3WNgYM V`0)s;c'<.;$w:6>NۦW"ԃa9F$(zz hbVA:T:=fk߀%L&lֿQ _9*My8wfviB5󷀒:[00-L>PAZpI.Y;\-7ʐf{_\:/+'MVݕn.WMQَge_3Cl-nSXXtTb܂7}-UXGxGj4pk* F){n:x/q"brz"1*ɢڋz阔B k3xՆ͝h069T-[:zGvplpj!I)( Ɲu9dCUi&®:dm2cI,yk{=6Es+*Ь и`FQQ9 tUbX CF"7vH)+cL[D늟) pKVהњW#ap.CA<'N6{"oe*E2Rd OV("ubh"' ,Jx<{,<^<DSR mi:б拚y]Ӵ !by['YaY*$TQs0Zbe( rXcmRZv(qu|{%D+hjSg7oN m#&su8eg eVTJv}@\¹ Bz8s`uԑb;>L:TM~;p>NHcO}X0G\q1qb<1%ū&!eTtΩO%CMDGU(*T$JU*H 41ZE.e5+R;Y9c FL)W@94]1TS@*g!%ke ic%ҁخ3/&㠚,L;DhC b;>YZ#&9|r[]W>𝀐6ZӠz.Ps3iL T =SK(=B󯌐& \ P}rk&vU跇/%PiP?5V++vFvw+h'˄˔/OwquF|/ߋ 6@_𧐾kCesn"Q=.2'X^a[?oaW̾/kI-}w[|k<k))ǖ<(;ݝ0۟S[ ?dN%\]:> ( .#lwAq  uO߆0x צsc Zo^@/X*sy_^ǥ]oP㶽%jmYaj2]U~q4: NkyШ9Az{y2.O&M%@L;߀ฺ)94K坚^tS~x56I:yB&cI5zjo5SVŎ5I\b0 ch"T*[48URt a ѳIli I,f@5MhK^["´#8TuJ/A++RDd,V_I YO PUdX/.Ja!%mT#*u6YuژPn rs?p}Q  rT!Nr3).ݬCH3oh5@Y؃P,/ @=oiHL6E <<B+a1Na-Q 7n6L7lm+ᒭ+ܺ\s QgbmOZFsH[GpjMt}@ztyp^hZĝȺBpde *un=bP|y0Y7&YNOa  I_my|ǔdAѠMv`֖DT$\gB 9L4@3 Y4!j JY@U0$* DP?j0 XL'ڠt_ M!eL>Ϗ3|)RJP)P) _m1O q{{`TMX",@}M6&b 'hپ\ qR* <°T9p٢ᓸm-3E%S"9O)GX !N_N-rÕrd_˚6Y nuW5Shhq!d~d;ȭn{_HDg( y3`~%`_0a]Ix"-(VgW/Z.↵'*?{-G)Z܏-ݳO c?x߰Lqoۓ߁տEmhsסT н:Wa[}1=Y `i_ճMXu/ .0WZKGM0܀c{ UYfrUd4vؠ? F25ǧ  '{2m_Ů3 0yۗ2 ʀCRW_kB ⷬp [;!v}AHPt8|C%m&)&S $=mow]D9+HW5K%UxLU(h4ytOvjq&4>'\_31ibQ S S0 Wߦ]31.>L.{}ƒAA+'wW_H%.> T;4m= %eꅱ~+̈́1K1 ]YGF3UcMsIQp`ɴ0Dz&x_ pEIĮi"ոD{47gW8( & јx; 4*)eҩw1iPp43D]f_zR$+N{ܷ]iD %< k }$d ֌2 C썩Rz.'837 D -QBr{7@DCc@DteV$&!gH(r)RRz<3oyM{E2*Mb"DSӤ2d -⠪cPDmԮswe@Ubgפ8A1"F "@U0`L }>:t`BF *7CZ{PNQa?Qn(# OP 'xՉjt|JH 2BmK^-t OyȣeA*<- RlqYfXb?ׅ&Iן%@mvlf%ݍ3/] ?@ߢZOZܤ$2'.nYҕ/BB"oK[/?ig_ڭX~_}?G|+x)z˝4>ogO ~Ml-F3Z5]ipÿ+@7-Ϫ)6*,mc|ֹMyP? LI IDATԾSPg`G)wW ~%릐rcE6߰87`YY{q1/>?=3@G䢗iwr گdwMsס|Cݟ%+/3S/+)ܮԚES }Զ_Ppnwm; 5S- כ@ia͹^lڠqރ+,-|d[ߩm+~^S۱?CB pGM)μn>ut1G'@"붗0W}JݗX;SjȎIh:eJ0ۄ$MD1 !heUՠf0E;) M۱jm`;nF҇X>>z#_JFrf tXXz…Y=s_S{=2-;g \,Qzsl*MwrdM Br[XЇ3ĪT99wv {LEEh\QJ=*uW-2;nqM 1h dYCpbZOhƕb7MXٙ !KZvǜQ׷JOH\av6}[ȅ}bW䁳?{Qm.+IDj \Av<, RP7@'d(A4ES܆ffOD_){vwRPsXStq&t)ʪ5(GJ̍Ɠ}6:`*> !B:!r@@!J1Ry y(3 \[ք.W~rkb"FMF J녮tt+P;w$PNwAas-wM7h:MIMM؎_~0'~Qk//z}u׿7?;m07_._Ƃ7?ungSlNes_ʩ3)LWC< p{9#( oq/5oznq߁GPm6wDJq2erƓszd07,<i=d6BAu겒ZXXyފ{zY'j; B^N{#s\Vl;љ8lSi#rc,q*k8,rSUB ;0# ] ]"hx2 s9!NAㄶB)SX4mՓhV@9;\-ᘊ^*c;gmBZ܀Ǭ5VP=t{lvl_p8W GQ EpέAPDRGJ9H6sCV?D Qڝ$gk 1{l P)Dt ˆ WwII4\y?\-Q1ۆ88+p8/Ş 3=3x #?LEԣ}b՚ &P& Sv3Jbd,7mb)"D1je4IJhL w @W ɞ)gvv+#Vx Y{1bYʲ-a႓sNnPY hoAۓɘѨܰ{yl2h wRAWI. ^pPBWhl!XM J{rѮcWJN^J{}~!)KtvOBTLqmߓ9о7oWr9*AÈ:#9l\w/=#Ey`UthTxjxCF-ԙ[>9nN~]$Ev|;?2Gs9RHaA%|WKL#q8OscYV\Δ  A *-HNh"&TA^rA ][Fզo8!JP}LPXFѠ 2$.[Ђ !Y.R0$魭IiZ Tl{!kn :1 3rNE)f(,?e9DZJ#<@t J7(Dhz帅pK9_$YyivNC59$v=[vl;@o;O07c@vlvl;@ 8oCյ(30*vvR!Y':ܩ&{!cP3Ẍ́N4 ]O)"Ƞ|QkJK<(;䦘}!oD~)r[SwܸsqSrr\t'.S\ŠP9(%qF *$fbMm¾ri{[= "19F.Ft]c9V8m&7UξKh;W{Bșu#R7QJH\"(DQ UA/yYsxsєo!dѭ8`YDUNBR9/FBךxDjjV:3!ï۶4))̺*X%KLqGFUG=U+a4OY01vj(U;SiйH%JMfԩ*g4)l0d#(Ey۔4AQb$80qp"5ЯFZ hRRߪ wP!f!c4 Uo֟^p +^ *ޘ~`'KH \bhh<#˱JeBzCE橼aJ腏>Ç)e/ RhHr8q:Œ:n\WGb Yi۱Pg999؎؎؎ %xSfN+$)J5fpDwg;(Dɭ'Q>CߧDVGRH,%D>ONC$DrK޲OY/Ķչz}sh΂g#'.2o(r8?7[v$M,Qe;KR7BQLO:f<(ljj X}̠k==OL4qfR0$xGMnoD_,q}Ź(TvDW os|s ^y&N8)QDh6MkS7 8ujH'[ֈ%۶89Ne 0IHJF%5󎙫x{6 T (B# 8d-)Y+ [XH_W I_B{AUJ,)v Zo ^,NN=O|CEy qĈqBpf3CJ1>!IKT24+'9Y]!lT;V)ڠkaJ?5|t4ϐ$l,w"x}~/C0{4bvdyMvNY)M x*Em)_-x] "269W^J*"lCLCJYҩ?|.a1l3c;ҥKL^ugUþ۱698Sbin2XpJ붹uKvf9sQy52|Ljh:m3v442&~hu+0PbN0 :7UA0_,:,83p`٘x̸,y_; b`=9;v",Nr= )F+X={H=NN>ޣpt D!%$~qSO0u;.[c;%o+?֭;T2"K0 TGN!%!1Wwrf?N$TSXUnS9h!)H PuJ}mRTCЛ) ֵ@74qbUٲ)jtЕCi о#-nKh H @㐕G3UHUO.ੑ!Hĥ#__z3Pe0#x:u#DĮ\8_ E *'%BEۖ\}SeVvuDIu(z` KO pCURa<̪ĤLH2EJVlpjt.+e[$*kXPӚ)VNZ4E k 4~Z$t~] 8,^BH9t<&1Юp!ɦpbxʻdX7 ~4ESJ$9+ʫ[D)H07"QV%OĦj,XNjZD]M}w* l}`; op͟KG#\w!4+_pWP<({?_ &zҳнUZgwCuOֽ(y;$sw H;X0h ?nv}z"i}N7 8lYQ0%'d;\P(x&\c"DJ%{]1KYY34 l/IImixܭRfH*% 3OKޔsUMw?()cDž#-&GGwnR#6["I$tV8T}3GU6B"D"i q09˹ 3y^;ܥi#;sJ)ghD*a3bggtR2RCIO[w$ `oxߘ eMAQ:hA)gD-FiMI+b \p>QEK ҵ3Y )ӖpFwvBrn03ߠx:AO51=(vv \!S>9ֵ^JyB I{"%'Y6fZ_xd̯t_i*Y/(=O=.ثYpl?qYA:X̆&2JVq 7̉BPbAsd@%YEE>Y+k` u.D!$ʤ$% 9ߩhjzjuQFhPwZB&(Ml̥uBd<һS}i.F7Lh膝5X]՛4 )`Lu_,{~8?u([_Ϊ/%,  `>|)"@RM$~c;wwi]>/>nzt7~ eo޿a(9:g~G_§H7_BoyG _q 5Ga0o@Me_<,'}:t? ^POZbP۱og(JI DsNy"O)9S4;|bm.C^N+anmgTOwX Rf0ER<$WGbFYOiJ$Z oO1&B8H>B3?%Ny6´R86i[8uȏhV#M/yfik '1IrPӗ$1?AQX>O ]gh]肧WdD`( 1xbsQ IDATQC'uX`V9edRA TY%,c*Ƭn\rFyDg#e Cb1B ]'t=t9PMK&%Fh{+;{  BUQM taWڭ.!g_ A/jԻoJǙ%W }䬜!FsBb $;#yD߫Rsۢ1i) HvMm!Fka Vb6g`NւNPa31s;S_y=5f?ãþ۷vl:UI1C\[ujMx$CS2܇r2`(ϻ؟(EA 7W%Q:dD2-k[PaY &09JI)P3%!z<1dxBT!lT;E'9{nU+Ze¨"+(p @ja)u裣=ek$kT > 8*+C(9-VrnTlMـ1Phv zeY:JFޔ"BPPHF>Mg4Q^點HS1^irRB Af| G<} ~޶>t?Zɇ`>շ ܽKG;bw֖5 }Xژ\)}'=E;ŬH?Q"rdow0h,'-oe?{o_OFA[W Vݼt[@>F|r}TK _%K[l1VewKl' Ɏ;ug{]Np>7ߝR@9oM|VYiΪB075[ڠ0+匡$!S>%% 69E҃+R2+;eϘ8)EDRquх@фu]r;D'+޼q%V~F#^+)q 9d^G (p >}}a WhNJvIz@ ږR:Eq ě,9Wx=P}%\*8sg[cs/]-O3vi'Wa\XfT P)ea,l}:O !X)u̮v&nPO&4(eNVl(BrR33[[LB+ڠY M',{50̂6(}TkKFkV>N 6e]qd}*89]duLٺOë 6YR0m^0q8*JYnTO(=У &B] -?DskBumܨ[}؎-)%Uof%7^dK}͜p g}hw~/@Uh|(~fՃ0@o0y{nj}V;Ȟel }^{08tw^心_zU~UaGנPܟy_=էag2$my? < ;5@?04=0'i(զBek'Pw30EXm'P>FOg`:}H:_8yOΛre;PAwN5܊s9ܱIBZY'I7;gSP!TnrDAKfcϓ:ܰ#Ǹnh#'oDpܙqx5)jL;{,՝%;♎vqUK'J.$I+==ں'FjxQO8RxL /=I!)jVYVk Q vmx1`&2w]ĔX.^z:keX+ t=)tƅˁsjR|8Ǫ YN JJހ(H$QJQ/-)XǷ"˥trdvnBoΙM]$buW\UG=I8AeT.9 ЫV]G ҁSoUhRj> G==q WvcPFHPvĝy`G{ F;~˼`pE(v=12c]N`/)ⵄp* hk(@oKA*y-,'|/HZ[OxsK d:dwTC,jaTz i skA,)xitvݦjqԦB)'RS[fHoUQwU6%g9P`w}2SZfKޕR9a0dRuR*kj^?ݨ"PI+v wLg9v1 j™"74&dpDU5 &糬>L:X-ZVEPۏ؎-Poեh_&{ե>[g%{R>iu݋!9a//9M?gȀq+P=w"h+(?eP,hߞ{Mvlb_\d}f" LUӏC$L>q7*> A /5EN)hc)@Ov 853?Mt)X> avYwl;*i,xlf\@ SȾoN2-k;>s:=OE&gzoCU4wn ̵/L~~ \ub0~pJ>h@k[>-8z­MqV_17WPp?|^[?ͽ釠 ?77zv?g c;C$HʊY 4En}}^7)*F;fxGɍ{- "΂ȇ(,3DrRCUYOCff]-ׄ#>F5H: yW豐ǹ]%LnEȄ Y)-e% C:<IΓȝL#c#~ڄ;]᪌h@g\Yp6ᐾ_ =9\?F M@M7S,_|!~2'cVN=֋#͸G@TJ XymMȔilUe2ݬf]&{IYE*9⿔ ţNq>%C.~ 7/cTgP6ij%b_V_̼s;XF< >ecAŵ)y"4ppDsZ{Ylg]71S~EbI܀~nnbIـMZjr55+1S;h<1Lc Z{p>A2ZB |pmJ@pb X ~1'&..G׈׿Lscg~|/fXL>io}4#?O|t^G_w2>b@M'$o'l^]c;2O~`KhP\uF?IWvn?p '@mҿnY~f+}C]a6Wa@u;<1J1|$vV1yaJ-@,mae ^oү~/b >a +Uk ]0{|L煁] ??/ٌQ1_,v/S#O`" yOGp?a 3"g j3}?տ0ߎup/m@nS9G?A*vͅ }Ǽ{_./!g`;O>OhLhʨmUIX"LͪOv4T"gDI^J4#ZBI+z<Ea58%N=gK˲ vQnOk]]=]h>MbR|ҹጪ$B5ʠ |{Ksv+vBS'jU{VeF>'N%|iVrl}<1i;*nyuE] xS2oWov< Wr[y雉'F) UxnRđimP+G$a=1Ifsg2 46#Iy1cL;CG_e0, aw+ bh]k-0(IYQ=;>1ϧ?]󻿝Mm֒kOA0_H)3]WSKAԹayzJ.f15J&@PG3w "m#nV'J\x<:)$P ޒ${!2Vh|{'1Y /+mRI626;sĄm*<\W~e'$Oق7gXEy㑵e?5Zcc{{1s );n3>+3'R zF7)Zg;Nkc(dyQuQ_Moo=#׿ 4+AcKZон ʓ  y-?k8; \ ,t3QƘBHτ ~gHjJPxҀ7 8=#Мi"cKeWă8 l?*MMfLΥE2lkjiSE%S]f0&+ o۩oP$!7UHc{cwM׽^<grη7If||o19w-1^"px/&;g(A~N9/9.]2Z{@Typ@n lr1SvQ﯒\TbIU7[ 5BM"dqT{RJRz"arޤOΡrF%D#0Mi33#b\@KZvVۮS{g4SC g>̥# ЯܸxolYZ ipY̏O8sB?S.:*ekDNz8]f9tms|@gڹVK9̔Tq4q +1?7_a@!#g.up2.]ۣxڊӓr/,-'/q: '-bv=(9opj&8P2Š5O?Y,wwjpdKFXt+U6;5I𖝰.6 RmߕDn l {Mrfȣ@ C2leiEf@:ol[& Ξg@^ wcRޙSIˊK&{mklNImKDIfsM}-&g".+ոRWx .3Agqˤ?@t#/(yd}nkyB&У3iSs hmehf3Ӧ}dG;ve2H {IXfS&Xۆ>rKɟ3n=rǨK+;_'σ?pQ_xe,8UX).+Q=ˠ(iЩu7)M\0s⹰&RY,9I% m +"h\(i IDAT.2F*^Se $%+,W ~b"ġb+!fĸ, _xxxUqR<,gvUKe^:I*s P*bۓTX29Ym|Vܟy#TSF#Ɠ)Pqc-8|Vc8~Jp}V=7`?ۀ(csw~3 w+zfBN JAu!]>WJgN /SGqWNd\ƇV=. Îx">pӀ 1B27摖zH'+Q# ]y1sxFJD!t'IHCǝYMZ-:~e=1G\ ZL_{Aڌz@+ЁyNe1pP4ª3 hFq]R-J`@~ hr̛,mfZ .|O>{Ej\b C]gdLE,QB&S53o$9xARİV2}1)OPbݡEL{8]TV1G*^s}vx5ɼbڲ d'E$/$I i3nXWҼf5h݇n/IzR'EA 0I;v+a/xU`Sz{QOGЂhґgQ[ R2ٲdwLQkuQcwJ\IԷGCh?mt鳇Gm=<m΁ xS|[.w R6m}~{]p+cL^v7 1?fd;`@?`~'mOoп`l뼧 g0 I$f&ѦMWxo oUX}v<uQ/I!dy'TImL&%dr(r2x*4VGBїXv]`I8*Zv*)H1+)%repǽ $yvTiVӡ K&ᐋoQrdo>s\9\ޙ0޸w 8\9 M&P:2.dƗvp.;!1|*'=_d%* 'D+I:3z^yXv+G.Lϫ\XPkq BS&j09FKH 蚱(&@@:\uj$Z r޳"|t'UNU xJaNktap;Q=4 >&TRW*HN4;xX:AG=hA:[G*po|Ⴁgޑ^;dRadqM8W>hIsUdu6J/J.eX'&2h2^ˬBp. m\p1Gb F^cP(1Xci~gkI}W۟\c{-uo]t*PѨШ00 ,ϋU/7@Lŋw7TLJR T^Ng'%{䤛E3g[h fHNIiZ`*gOv 맋s=Ec_č/'k?Aѣ.{:7~CӖ&b }g@'j򗷝V7Sĩ>+I3VdeSP300Za5&̧1mA K-0yckxA8]th/*sVėǩyvX}Ow E?c<nk#md&al_9 4EoA,$w`,C7Ey/@WLwkX}Xp:~ EpQ@#a0aLTY)4By3͎Im3CLJO28c&oNa/ Mv.,9J)b.Bg@-uތt빲Εl.u*"ǝr:}y\R,VLewԧ'\fotTY?S_['G"RL@ʝEGsHZ*Fm(G:R%0m#AЬ:U&ya?^0tC}OD_e"KRLҒ8Q AC]&KOM)L-t:>⅛{Fo~vB81/1 07Iw 8rzs锽]3p402 7 i=F=]TÍgE9H#!εeddD@y©"uGE\xT8M=ԙT)-x0 |xnd3 H€1=Y}=:tFJj2#Mwȧ'nԙX8ddeEr[+Ig#H+<.tbr:ϼŒ#I`9{*YJ@Ba28D TʰV2A 2eĸNTIYzcuYĨڶX@$3?DM2@U2b 32?(3>Oy@In;~?B?+u`a@>+dPula0δ*g*S)C28GAp j0xN(~,-~LyDl] ?MJY&XKd=IuQ~w7g~ !Հ Mos&N KkoqM~'=(2!ZA`~2uu{~kLßz?.p3lb=xT>EΥ꽗' /m85EW`9|o X-L~?/<0?5F;cR? ͇=W/"k Ϭ p2pW}U_2W;F^ȵTK~L_c?ŌԥrݤX)0)CLTIHZnYOkfzWFs9o <.Fbw$f9xgǓqÕ]:=B,n͙n]qteF~pz._om:Cxg2.q/fF,^Xi>͵TV;n_]Vztyԃz A)9St t(T(j^B)>gH ?Cд f!_yiIlOG?CbU.3C%gǸyvh>\SÌ W&RxᘏH?GZ<9 Ǟv$xp5R+D5 xeXR kق+RCh 2[ǭYPF߽Y79͇s!)ti'"aR263{ΣԨoK2B R t4CU΁Vѩ{a!=;]Ƌ-=2.+ Y?|\#v8$Վ{΀k dWv wZ$v0j`R+cϵQ 4ΖZ jdUpQ!`>hYI9ՙ4;;e/jnřkWaN^.ԯگw; x& G?-K̾o opq6B0C`1ܞ))fgޘD)t S8i9+⋠w/z=YK'L217ʭdxd5d% ->[^JY h4mh ̩/Gc=DBNbut#iZFzFRg|b-[`=K@ɢ]g֒jX '|SIԹ-wÊ*%zQYu-9AhYE%d@<#QjFpؚa ;BtDZ4{5~4!Oy(cķoܹb޶_4S ~q/^FHMJZE|} >n0>px6>?F⤰O$Abi&%hm?Mԍ+#[qgnJރ?L{xp+O\a0N}C$~\vtiӥL< ڳHB4Kˊ)+?(즄+r-=١R!BdgK¼pbyNi52DZy>3rOHW!ёr$ȧ+iTtET% A =^[A|m\SdB b@5ChO3'IqQ +/V W?Y*9MTʯ%2g +e\øQ&`gLH 3MQu19o@cX#F/30\L)[ y3=q&oNl FpaŻv[;9K*k&]hD!21QR 햌%ll+<ϝBl:!J&;,q̀J+lTzCV#x﵄|7Y\?ƿ"~_h|,}6nGUd*_7ޏ" =={GoYK_пgپ·rSh]8?OgmQ;C qie ߵ彟TvHUnAـC_ݷMk[Z!-Z3p,^_.̃i &{VG &vkL+n>gj䔈rނWc>up70&fN!;!i%@'r@:yG=4CuZ ڐb T gt'X3}BM&E%ĵ)2/s/XW#TT>4O^% I 񁬊L*t4bۚSD7RdC"!&K29e;O+| 12٩S;\АkOa ah[1ZsčUd <3Աqfd:|1vOBE1BP&΂S'm ))kyFv9V=㸴_(ڔ]*X`8'QȋGMYl5;T̾{j= ?N+;$g`I)tNJQNݟdAŖ~Ykkpn_989ET.t/uQֿ\WCKPI6 Źk @nxOS@'/ $ ee@BK\1BP9ra;piglGP_,*$3_3ʌ{ʊc[%or4$K+: :9i-yg$3ZL7mY۱P6FAhfZ܇ػRȰi pDCkYD!}Owý9_1~YDz<=fQWQi,܎hk,giA7 ān^$&ag7PGÕˁW|ȭ7g҈/ g2$qf|Ͱv|# ^AΛP?&$vv*$2;%o:v5=K'A8+B:b UCQvt.Wh IDATE1fm#Ny\c&`P7eG+dP_ iOˈk2L©I,7ҏp!O!%Io׳832w!&05A*w|Ô!:e{>׌[ tȠu."/Шq "_r?J #q"}}f1at}K}V2&y4cBJ3љ7ڛ^100I43ϥ=cgUqΜrV -fZ{E =5=,ŵGp3T1_So83tGAZ!|o|Ft"t+e0v}W8Eg۝_Sz$FGOWvV⊍AtZ UIdc9ɘOTT6њ(j֓znex1Qu@]Ee.rqfap*Ϡ 4P{!x%wd?l)3j".3':%q+QÑP4f|V*LRKy9]Z fzD=2 yi3h3)YdH 2EDzI4, U3d"%q[{VbTAռj3O&؁q=;/xޚ{>.R썁GDɃȢsTNgs֊, OHޞR9! \S=m;cq{@ Ox2ztVTKN:<*H O?"fL&mCbw+~}vdÝ1\zpEr@OT͕L.9Ɋd8ހ^Ya՘k(xQ#ViLqʎgw* G$ j- HUIB3i590H16;V/V8g bWlϋÑ (޲w)2_;>!ܸ]σ;GksbXǨ;Ai[ǛAJG *[vL-Dg &GO a[z;;)%f`=u0[Rǖ)={i=fea|8j^pU:E{o׎sѯWxLn^`71FQM'떶 j$D DJ *rhDxR!$uoO\u1%LH^K/fN:g?Cs#ѐfS{CQpcŢ︛2U:&Jcffu[ Չ=}Bc k-{+18P1.iN]uXZͺD | Đ7/H2&B"&Ai3 MJ, .3!q挻ܮ1%6٭Dίz.{| [hVt;*$R haJ%~%坷#_Œ'Qgw@iK~ƽׅW~J(51Y[ U%-&%TEBd#H.'{wpޅ9-͕cb%4D|HJ&H0oCCf ܎ɒ3Xppvv ΊeWP?Evv](KCYRV&"7+&bccܔWUCd_]&3mu3祍.eI1Ұ #?'s)wrF(0]6־*sn S8دZOzV !tUh[hrՋB  I|uK s޵+jB|DְG~8*ha¥6}_Q݋ܽx4Rq7\d!̖, qI=ά hu,C 9Py .qHdWԐlNvLP1 :Ͳdl}Ιgg 0f\tRt`*W//x%1 Adt>nL[@ +>/J-X%⇊np3DGR>AIJ-J>%>;dW%h"^#9*3cW2S7T6/$acjW[_x eh@$'o(ߦ pso6oې? ބ.ծ!1BQXJt 0VH*$'N9 5FRmm1&G,|˂n= C eD@Fv_K=o9=&P,y r >(]⠌±_Y*^aCJ"]o"$`(> =%@#E\bj- rGVmdE#HYܓ1'|bgN7!!pqv gzծ!PqYʄOyd$ڬuAjFqiǯWyS'wŁ$NNFLM"'뀉O=sq$Ne<q f&e}Sg D((gtMGFH5w0& J1ad 1`HA,/].qʚr{i |~m CfAb1S.'L &"'g(hpĜ` TLLIb֬ a /9b~d2+D 4,Vlw06qÞ5Y&X*>{%k^8i&M0)2RJed FBWf/2l3iQ 2ծvS@/2Mh-X4t{.qv{ЈV;MIq[߂>~F_i0<>j߳P\ǨmUծ 'c3Xb6H ^PV1Yz719}ٜưMۀH֓8鈫Ht|ww4b &[N E){íY#O'n^npEuF{ wNV י\F4QUc5`h!E.ԉ Z~„$ doE06PC7yx_%03hS4'1̝* 8ۧt%Ipf0ah|MpDRWN RNӬ[̤*+sG%a&%#x O$BqTy~3.>V!(,uI&{@-%D ULB Rnf AF5V=8Ȝ`=]]6SڸFҘۉ6|0!wZ駟fooG{wwg?_0O|/ٕcmKܧ[ᓠx?E_M: `)@ BR`>o{%/}LM6;-`s`3˟~hCm7^? ķ!~ ga+P_'``. {w@f`>;}93;>/{!pW67~d Qq[}̋1 ۖwLJ'L9zw|k"gOF9 c!*3b̒1̐JC#),9k"1iX\:{lwyRfIƀtW%xmsKol&Ej@ x*wXa g~b9`% X5UܺR0 HIeh;V;1+\|6&izOi&rZ͡t|Š+O o=m1e tz*Xc(\$H ͣɓהш MU~.Nck!KWb)K#5e05cO|+ 1E'LXLzW W.c~TV E *F[tOP߱^+}Jj#Qf|G›fMg20,!R*iѪgRTgi(˄ktd/hr[7yHH\U΁ <!j\rA=+_2"iur#DbZ G ҆C0JW̬D ZE45A%Eq];l:/ٸG{#˒Z\8yc 'Ev} M2$LlBۀ=&3.G>[nހj*I6 )$Rcس>0\zbxl!\<dS L@XM [ IDAT NLPE2vEL1f`|'e"M teOmN FdbeXIhQ2I lwb_:ޭsJ?8R>Aw)Ė{e99iw0>H.) ~ Ë%̄q3or@ OgV7f`egKP=oo2׵N`}}h/7],Kr /gn7|laP>_y?u1C*eys2I 2aYdF<a3瀲M?C۠+P~0ejRzϋ1Tkpg5y"OLy |c7jW?1[DJ L%LoRlțN00BLr)f1RlNϤtlss;<>=2rɎ{Ĝ,g#`$b$ ⶩM4+Kx"ueŪc\ Ԭ>̔jhU9j̽\?`< j!LMji>Wn=yK-It'=+#cpXhRΦp'+bZ3mp4H |O_N< k|h2%3/8LUڄ̈* $$GDYfFy40+u k)F#48-ًJ* $J6/}'2J6*s|[ROٗI$X!x$$@jpo#ZqNAIBZ(# {@3P}of0Ǚ12ɯdТ49$~;VBU?qn<}?9P<.܁/@ٳizm"܄?LZ ePC_ʟ~ysݼ]"O1A Wwoy\?_0̋0bS3ށ0ռ-_@1 x$!wUcjW?q5LR<l j1,CsfI;p:ȓcSyr#GBȌx)*>$b/M$'m ;i(ڦC@98XL-<3Ɯ4'dɹT`X硏gTR\-V|k!:v\ bdM+QlJub \b-ѐ#U{qf]BzC*fW=aZWφ`ufϾ7=BOp'1{{,16 Ajb!ɣ)#u Lu;rBȈp;.i|JҧH.0MTmNVUfLǠ_+!I9i-9(r0)#CN F^D[ 1JYdG!lЭԙwٰǮ[ׄ/~?S3qW{@x5Y˓&VY$e6FRUg!5h2z,ܩ>} |*[>vRR!$GiRelɋ%PXƔ((jM 3K9iX舐b~6N%3v]:99!Go?`sޓDz a~(aUfN @z*~ ῄ?w䓠_ p2H&?r?W Ƥ`p>ͯ'ͦ#8߇6o&_gow3BJn],"In7D Ɵi7~"3,yiE߆ꟃfC7.3cHSۿ ضd9s 4K'C??=w oPt(>|0އ5[fm|.~s}ծ~"Hi`1OfU\Vy¥(D )SҎ || !ч1Kbbf?Ř9 ,49Oڷ0df=M'n:GS{79yjӎA&mB23{.^,oʊi)ZLֵ4ZUKT? V.i5S ؓ AҭM1fk~DmV0F*9J=^\6/GAzQI!zC. FgschՎSЁFʺdbs>\ޟ3kf3Ėedi8LI0Uu4(m>|%{O]UslU6-~HT%*R$RJ Ä[[5[!C֐7%E =n.vLbCXD=t݉gV ˮCk,JZ 14+ y\H ZJ%`]K[@+*!:E)3M 7&)+T *`4e"4 FΗi0oܧy`ِ/R #N/i8,&z/3EI)">3fc\XJ g fc(~:oXuʪSN 1678 Fy Rp>ъZ1OYI*JxlN8Y'sxO6L;p:OUN[n%ܽ8+7/BY(݆o mf G ǼJREAb"C EF6$er"PJC(z⅁!$9v*X́jW?ԏɻįezN<~H1{vŸj;̾DwT|ۥ9MJ0ŏ7A_ˆۺ @+ {%kZ#ModIˀJRM@6hYI9x$ō `R@3"#@x #+C w>Mc`_F76gSn) Zi-"qk+"/D;6 %1QxlBzJW0g)rxc!\;3{S"Н%Rؒi9:.DR&tlap|Q+L׉EJǬp`BZB=% qmy Ő@pP̸W%A4 ikcFkO+^1tX slQ! Q1xUg4$)vnO iC2xphP#>lhv'35h4;[TcHB%h)L,O8~Kx/Y;Hʩi}>H!*䩫xKIzKEJҒ4DIIlx*4GJjD$#E)Z`甔-"X eSTc8$,$,JYJsD7ҨP>~{3rhYI0G:b"J[ntL -IQ{ 8 'Շko iǙy9Fծ~b*%3GI$b0%D*(ւj i%a l}>*}Ht}†OĢyK*'eEҍ1<&e2yNyB} uOg#6` S1ʽӜ g %U^mxYuNX2"鐣R$J⽦xMLaYtw-zƣ{G* 3#!R#Df@Im$Dz@i+V0fT"Iհ|mkO` I?: -G&AXp` k{sJ䄱2*,R;y@~шbղXˆe_jʙaVNyntбĔxt"<%"dž&v):V}3Ba N}ÞU 6VVLn{f&!UI _w'l4qĝ5צgz vZDb ] YC]Їl*n g!h`o#wNiC՛('c*4A.GFlv\@&SYCx{ I lBMDm̉&eoaD )ΤD D`=G8aG=' YF }R1+骑zw7X1MŐc"U 1"R9RF ȑ"YQGSu%$0G$e[0eo SŐ`vnɉg2_S9'ΰ+|3 )ڭ$M~ EN(E֫Ш3)lFc"FE ̜4P`U)ʄ HNg}hQ qh=y@jW; @,sje`C߰K56LU0Mչ6gd?höُ gGiD]~$oπ L ?CQ~ 3+p,@{࿞YfS0l6 O+XH끻u#:-?zv)xOhmYG!ep'cN*YKT)剑Q `h""1:!i!b"> FPa`4\dHC[LY5?4pII`$f0QI0(0tڎƕZ$1ˇF |['cMZF5z4e:CQ&KUeElŚ,1$!拁EXMͯ+03uckbwE9Zr.WDq$/L;  }v+ qfPVX/{\Z%W9ώr9 πKW$I,KSǥd8E0mK Xh\rmefvO>ڰ8^:]PT5x7,yݯ9GAYqus)Kag8]< Kv0SEGc{JY>{/DF `JL5}Yy=>8qw:P>Rɡ!G҃ZsS=jR)IJďXg. &o:Xtn`"j VPg*uޝ~ۅlMz`* m&R"@`ԥ`RQbY(Fp&'IU >ɚO(\b1z@y ^0!qq&"@}腾W'/}p \) ,,RG Q%l7ZjchRf9e jd+s!b+̧mklm3,cIk>jW;oWrq幛=T3xXнTP|8Z,M"ً Od`܀V/`VՆ6Am쉴mN ?6y*KԸ6 9}oˉ2K2oP})Jkd2OA?_ei,x~3 ~MF?LctzvӐ^/7hWO^ٓ,}ZzY0$A@S4IC[^b pD?a7A$ 8 m`;vr9ɪE@D#tWggeVbCT?JvR*mt-MyE 8!X! ~ }M #xJGkݘ^Mz>~(J& +HԪDȯX}9c`5yՕ[rk/Q-@USbXփEN Hjm[Ҳb_oxrL;UM<#|L Tq%Lvb'ܴ ^}sQ%T#:fjѰڊ*r4Zr ]3,, T8)eOZ|ȓ$P%1DyL.܈B2"oO08-˔䄘Yqd94'-2_[6& Hl/ӠT"b'nqO`V<*B{{U8[f$ :dNjWa1 {nc$+4gp& mBJVeS9KJw|i\N%ǯ\Jb6 dޝ6J #n߱* IDATs0Ε:ӗ6i6s$Iɱu"^nH`KRԚb؎OG:E}4<ӄ;^M^e aӟ)K`0v~o# ëm/yfo[@HQ<`%_*H k0Kl/ioBxXp/ ȩ~@/ps*A"/JC|ȰliyET{ ; /{*RPJ55 ?=SUq{柂tOok$4/@~w~bc| ڗ.[좼m;1RY!f Nק sFQU4d3$e K]a`l+#e3r.í,J\Q?]qXu6=67eMz%YqcKxڄ_qez׀U*޼f6يeQvٴ]T (䜓 1#E#A<Ԁ$\\ܸѰa8!"bĠF\P#Ļ!A70G eUX/ɡ S,Vwaݔ'fS{6)<`$5i.bi*0eMNV|덷;<}i,W+삽jΜNਿlֳhO[ Y8Psۄ`v編rK&]>Sv8tLŹ'mZ;:pZEw3MFn#gmYœ/iOˊ3&3$%łlڙ06^rA  ^Z0s>zCܚŞ TL>Gk@_EL` 9j\]EE+%,Jp:G^ڊ%*GHt='/diϢ t'Nu Đ(椅8[*iXX9(T H@2DB NB0:  &S"%HFp=k=5;Grq:&)p|$U 2tOH~C/.b&}1+ůZ</L Y$$2h(y^.ࢨ*AK\< N4mvlԟdO4;H xǯBq-Yݎ[0ˣ"p ] \A,D`YH/)aRvW._#6}m~M(?ﱿ'ŎuSч~uJU=jCQ/?"0/eС ;^ו/h?g?[B7rK &ρ~b\(#X}=]gEX7~z|RQ=ϰi[~~yߎ؎0RPJj!`aCxv+wMǻꎱPJS0ئ!ol Э@ w(Jmf5b>*7F?:d+H6$׀ѕ>9|&wkWGnŊ6D:Gi)jgD HU8<:|KpWT"$d R[*sc?7y `H S_Ke3rF!]JBC:PQYt #phyϏV+Ꜥ.s4nLF;RtF%YIogobZ1BWNiOi$|OL Φ-ly~q3_u@͇Rd7rrqU^3;\XG=LV.sj^E^ ; 2ˁ+ f䕓.<@# dڋStĊxj]BlfcƀOk:=?kLOLVrmT0#T.ծDz\݀\>%kUtP HUrB6o}q9|sf.%L c[_ gfy?yVdҙD<:T^Zے5\IǜSYEn75sU @H4΄Z cSҁ1dv82/e?hxnv f$eis!9QEU^%#xE9r|3-H\H)3/JUA5eKSr\Ks^C!,L 7ʜT1 t,XG*c0ys"Fm(@݊"sێ?HogGH7Yt+N>~/_‘w$67=\33d-b%K߸ oyS>g-qrJ_=?.v >2/+S~.NgA:Fp/ \I(0/ @zUKw%ɿ t.6 MM>aZҟ>4}XKowEM`xe?<|V8*} h+`.|UJC.ڣyc)09xЎI$2pуYs9́ OrI<ðw79.(^ֵr PP]Q… q:U%TلL}ʢDQ2kS ^ZuT2`Q156sy۱ ~W~__k_vߢ}_z /msŶokQ/^nw ~2o~{n?}7Y?`TN!:t_||^>o/o<w ߎ6)ɩǻGVRm^d$ɘ(9% /7Ǧ~(N9)le1M.ڒT 2khmukͥgM"ѕьw~DF]uE]_}@JL@Jv@LboAu(y[Y9?m{UѾR&Fgezȼ%  3')O~Q^YCCYEE+-^Mm`ThOGӕ0VmTQ*N/F&H5bv4j'=O/Hov>Ԅg)^09;J wg,.8É3,3fgQgd.y9:0w|`YU|Sϗʄ'Erf%ɐ3&+!@f,;zUsgasCZX =i>Մr`c@p0W+cމ<-~ͮ",Q%Hp|XAjE8 GB2@ApudRH Wd荊hp&AzP#{FCȈZy:,/*^{cu9ן)LOs3Xب0i3m&XMckBiM 5R{,oVv$BB6<)gM9uϽXۏ؎^?|ssi}?XHVrD cKgc;QB]!RFP"l0U0֨@j%vHB2en(c[d /n|@x{_Q: 3+INuMn_Je5U˞M})֌ˮmܔ6kmfJi 5s\|{#̫Xoqt,sTJs4Ċލ6r_z}z}ΎQGT؇__T/s|Z0x)U(^,NrGs&vȦ[$psO8;tPZÐi'SZQ9QI`RW,+OzmxjMU1ֵH]sgÏ>3xĤ t 8F#*tlI241Mj+I %0e $'pnv1X4mBgb4y-3ՂC!.[v;gO !|V1v| |i^s{u 11`̊&gp8լı t++\+<@2DsUd4p//3SG'5 rB#|hu Cui5rq֑C J`Ǖ J;0Y|Ko3m9bX@H% V5wYuEWz#_P}i>Ɍg_"k.r0"k4n; |TTd/rރ,$nUCq !_kfw`]e$-YyOڨ"\. ը) Y D*(G$Ǣ"BΈuF]綒[FAl1@QE~jw‡&5ӬUu8z hIƵ\-k2u{ \_BR$80x2]׊RPl1(1*U0XNhPQ*oEM6ڀd|!*xQQx]U٬&0xƘ=v _f,/}lW#%gdFT gV|4YfW HLeқh$.YQ{h=θ]͘U '8Y;Xy'Gn-e8XBԐhP!ĺTJS3|9 !9] Az'K6d?"r@ZF2IˆET:8y{Wžl6`+d]*T70adʜ ;!&( Bpza49q,BQpGak<^scv W!MxaImTCNbk#A]D"RnfTIJ/wGY&Lyfj,;3g+<'kTc(A1@aR4RBt\,2cPƮՑna4>WŊe׋Q[J(LvlvtJ۶|۱۱۱CB,Q/+.D7F xș6 `N 0&8xX6B06.Fbbh}V~vԹf Ryt]s U\Q=]}@+̯reQn9TՄg=g˶Tɯ!]W$Fo}ʫe TVr`\hN>aXĜÖ݃ NL΂0>| ܭ-;[8d1ťXdq,!@UG IF RQBQIXqC+ڻ'6eBcEj`G*!01fD+^X٭&ĜPwo{diҲ(~p꬧:aR39[ ʌ6<'ܙh- Dw>5S"U,%̅Μ`?;3c9sS޸: d?oFڤz2Y^#$Tb 4ibb^+-mZnhT>hDŽ+h;l$:~^QhY~c $s$ $a'~;@;fݎΕK9(A i6'RPuH#_p+b\ Ve#))Ha>]®,/7 (z_*F"%QW`fmy<vqF 61O@!D "dt) T.{ IDATډ]4KFS&HJ_ڛvEAQ U~kZJJD/Kl3VhbYY眱$ʯr.ZO%s)R$Q<D@@MdvZ2؍Fw˱p:*ta L`qaLښ8 e`rh 'SUzJ&BGh$ٵIhSuUOؖ{pFiM%F] xrmb+eNGwzZx춳;u&SVxeշ3_yX&yXLz9BE"Q(J}l+ :;LgBU'T+TU  9жp TT:u*TP4㣲!'gb01hJnvlr |(:݇<_#i㴦ߣyt<ÿ_G, ç_`~&[AO mȯ?m?g > = }]O8wheRyuWi37aA|i]^B$?:/&틐~Oğ棰4K _pݗ Ga_`u곐v`gsJTZ%+D*c 0rBdݠVZ@ 򺁩Xl]PWwkX JjÓ\ֿge'~㿀&)֡ךJ5 [,W$)2V+),f̤ L㕗yC$fr.J;F:D-?8ո \+ 9?`rX*SacVހ%rRdT6Zg(FN;) ӳEX%W(:Y H$8HR/i WjKfVLfB{g> }Gi20 {Z2ˁbi2łLv+Oɝe['gۇ)=@8< uKIǤ Fk+ee- De>]&PeL2}5Dxg5 Lzq愯Ό eS31香a yL_Hm*8hTͤ=by)AJJ2)n(L-rmPcĩ6/ z!U䋞o:Q*@ \G!SKjbRx\RXfz`2 1 x%nFqRz* 4"qAbV nT,LPlHk [Ʋ#x͊%/Iں%\ <)i= NZ1x䆰{82LFJ _m)Tq~Y[" cA%sq݃!# m<%:5r ua(J" " S5b4F$BoB;8۱[}j깟cgKRMWox@Mh$;OAd oaC<[Wn`gpZ * $=E'~VwPqg}P_ su㝦=X|.~tgwJw0 os!})t(i > <S >R.|mH?} Oaxy(9f > `YđPBٜCFpgK`]$'X(F:Jp" ZPp>a_f\}Z3pو" ~ .^NtE2ZL\eY~ţ&chUXS\.H%7kp٘%&"d!׹X*wQC6=(t HP.QoZГc!;v1F6k8nIɤ5c`&ܡboTFȍR,#!c@L 9>ނ'H]A*hyc'ҁ d O9qW4`Q0Q[y.}㽁Q# Q"W*=~ &?^Q) [?م_/| ~ PԗGaQ;or<sX;߃<_/|3ص_ ԏ/,C H}o$T]T|:@EɌlThťvWQTcYU P5ʶeaZʸṅZ%Wۍi@{uBdR4,6VN(%=OC(ڜ+;/wu8;_V" Sz hiRADz(0@EC /zu}'J5G{~ēwV0IC&w ]O#4#u.W"GH>d$%x>)=Áw~g䞢)FYQU1UxpR@1-JA2!Yic@EyN^-F˯;yH*R UD;0O (5llSO$Ldu\i^d 5*fq)7#2GW߄׏`<]A* | Bĉ̸Hp Lg0 PՔLu3,Ve`BUC΀ZԨ.O<1:]>e؎-AFHwG_Ľǿ3 }#Jď=Oe&O+Y!4 k>Q> uϗ _*͏AQKPr\A?caZM˿mMyvE_9~IQd7!~G1!z)/ŧ_/z}  7z~U*? w~;ƆT?,Ǵ{'Ks2zG PP$UHiG[Î`ۊ qĈ /Mi"%..ko@7dܪ[eu" ը79yy0UX4\| o7x܆)ҫ \[p=tfm'7< Gyzw( B hD Pcl|.b!S@)v+|٪A|w8 ƃxrg_OUUVٳ l- Ax/e XyqM;1t~ DM4G?~ͮ:NXjQ=?@jA2!Q%S\fBM̦ b$a *V-3 #XMpW[x9q]g 5Y--ɵ '1^ޖ2l q[US90ˎ 84S\!pQגchQK)-.Va= rnnNq1.̉kGY`,{;%C\l%ѡ0,zZ qU`3C5+lo†q4lEx@{@_29voh94h]On ٜ)L;SVaV+ˌcU Dhx2y6 XVdŭx)eo!V2 FBU+e""h;VNԛ?H7|[J#B$(/V80Sn̔43u a5 x*S$D]L PڢHz J`E YIGk%0+Ld 5<1])q~A, Ar/$duk횪NP2 3;D[4sODH!3h̻<^ ^/:J6̏>Q{28-}՚ќp<F 썎1|@ w(YCO =.r-,%l@WB<k =0{sV^}v/LfOm ߁ H $]%O}!^g,-e 9oe /.ӦPga[>nP N0LUOʧ{־^Z,Bܚp-Fz9fܻ`mJl NZn'? fd} 9(w휧7lG]Kٙn7w,LA3*Z?n**ZA϶*3蟁%:QXW0]YϙEҟ ys1kvV?n\j}`:缉5OnUhu IYKci@65J]{" Yr!R25-$6V}ΉkHx.6,(}ҁn)^ JuKWȵ²hۀj8YŦPaA:BMJlf8:Av@UPCϪJ)H)dLg\-vKG,w+fypE4B [JU-4#}OVv`eOa>U!7§RriKb .(W U|W=* nvgusjͭNNVs&.nY,%&^#'3 #V]`M#rbhZ͔kqGr RYH 'J!mĵ~'p/,% #au<*&k.iHiNQ!%#mڎXHZ\0P nC:\`'%fP"XhwΑKlOqr8J|.4>XVU nȪzJ[ liP  H  (NaV͏"hr^Y\nm5 t=ng9V<'Ш\eB̖i"3[boQoR; 9ף Țïbd .΅~Ny-g!LrxpiZ o;>("l68pbm: xQ1} !~?V}1XAZpH̪OW\chgQ;joY#+0L~ EhNw](+W7!oY6K5O-),:h> k&ja aSdU7-~d?6;3V>j~˂ nkWa)U~ W!aº ,+y`ohrrM5 㳦*3*TrXfp0}W0:cpG:Hm?:؅x-h]P r-Gg%`LXVR_?ZI4ză߀_A?mpa uM<k,t +?V2ʯ٘}|`Ipڟ4p)*vo;k j#0kg\ JAz`USm{K>qfY/]q_sNyUqǡu뿼YV0~_ﳟ~>cs[3ؘۭ Vݱ*hض@78]ӈz㟳!RQBuYX-8`MpZEE;9[xt>-ALxRU؇O}>q  (E}߷ CP,*XU|S]9'Vq XYѵW_!DJ5N}Y9gpJ0kXtʠ*H񥖍zavrB.{p ҤS,X]bAn &7@4jSe\mE}jd."l0U!K)VP,->BsIkv= YyްF\3n#-ހi. >х{:sV\"12rmtNL̙,kZfFkQNiÌP^c<I5i)<t"pmgykE7{xinxV!juxox mB-m:E 2rh`UrLHLg @&ݩDC2++s% 暈T>fRLEŕLЂ"g={bC0%k2E)D*u<ɬM͙L՘TeFe iVkqM`֦4͌a(2/23)6KQr(kĒn;53HPdǪip 8%ȨI͘[<,玭kq|˗G:E% IDAT$l]O t<*'.UI# G@$YȺdlB4.0C&Jv}`Z{ˑ;_Pgҗs=KPg?"?[Y_OF6LΘjeS1[HWB ++0#m|g_§2!5h0<]v]`!?Co?öՊr]͗ XDyMA^>fPf K潖nh#Y0 Z8-a[OAZ=!>c0}*߇OL+0(toɷL+vP-Zt 4(}XO8GaM]ć!0 2_ og tM單V:Fv L:>V`7oL~ sPw񓦆 3YO/ r8~ fN`?cf|&yR @ht׀~upTplk"r,]N䶯P$rNlURJISRN 05f j*=X,9,`\j .sHlzH\n 5J!PnfM6qV)*;O)94/UEʕ(&)). @phV4A3 Tə$f%3AUsE2%Jsi7u6@Y[+t ;Dw3:#o 3M+]En)r>e8f_UxGg%ꋍ.s"~2/9e; usH[GwqYw+ĝ9[ Wmn&9Ҍ۹go8OOW@шtFt~vDiHnGL<+eNd-B<[y@1g#Bt±q CNx8&m5Z;%={k&3v*]gh78wms8)R2:E[0s4͙7AY C]A}KG%uBP' uJ!$;(M8qc䅞_0!M>B}}!*d5`uYV'GRHRH%32iU百PA!*U 0F"dED0)Jtbq4jY_6bv/ t ^UTqj[A̕ 'j+ :gamUJU9 Ƴ=a6ɤ$féπղoIi%?YxԠYm8\jV,3Ms橧/n A!i\$]qe>Մd>*y>ew:]Q d BVxiMp'$ t-ڀHC7 a"ċz:oc#V/aRȉw细]yS/HUKxҎriƉ9ϝG[1y: aA-X"ՌQ<`{655`)+ŠZ+ )ѝ$o(;š8ơa)̪xAٌ몜DM=Sc'ږBeYa=[!][,RMGۍə4Q tC(pha$/`jZ-R[3)n>9{W 2SY*`2ZVʸ. O)Dfmbo%4-(q^v0WGƳ(v+#ƫ#VAdj{tLi?/b!WrVYR7d{345aC䫊6š\~\b>:oE319fI <]uQ}płs6+0J܆cc!,r BϝTIV' T-Yp&=e( J*2Г>!G5Q{^x_a)hZZUd9"Ggoi+e@~Ֆurz3K ` xy9X҂^$vh>h =b}Qy@SA)er`m{,s oɚYppm~~Ÿ 3{ 5j΅ʖ'\+ BK 8Qڮ)K7 S,W?'w(i ns~| ,/]`U;_{l>1jN5Osl@~s< l> Am{KgT2$IF\ѝzL(PFtΨ]ŭ[ J{dp\CAB(Z$*ⴆ~C=I$ӭ&X0EH" ^j yP9{1Bi(FhVP(xl;qzXs1H[L|u#20;R B-VU#D!/KZ *^' mhb6c):GXP|.acZ:9)"!q5$zt*pTXe0Z ip85墦]F\L L=e֠v,a\)ӄ۳~[ګ^NL:}б:VFRIL=[ʕ=Gha7+[B^PfeSC psm +qWQa$'/SAPd hUn ')B#NT Hb!GO‘ǫ7ܗCqGK~Ib L5Scfcn|O cy?G}mwXPv(Կs?yd=>fa+|uB`jy[8Lf/ծ=n)s< ¬|ܔA EN`Qd 7<7U[3TH\MYL5gn{a>e0]=f߹휪ecw8 B5TC}Tm,d_T ~mU[l~x[1*?az_^$ߪsaˤ|PAg2kyެ . { p ~WstZ)ma'x@-r ])y3MTsd|(xgd{F<9,% ( ,EլfiaeJZɎbhjYD9- <@r%D&A %wJ JWf3U?X@0JY(X~P#~Y)1s*@a&wM?S=)RN!G o nn!v},9C砃GrK?2B `9㤵[EM5YBOCސxTuTh4+/_9{$#w3Dn~5V ɑBN$ 8N@#ۆtdɎQQV7^P8}^om&!.KOqsdu@W Ä=f;7{&W+Lo'dT0dQ,"rF4UP| jAqSjb,TPu BT\ z8yVI}d{sB5䤔Y\ xOGhajIUxY $,@;9uhR8f 7HXHXܡ394ABNt+9svhTu :.^q|id%4x|x~J:`s݂Cٸ0iP|uN 9e>i!$qQ[(J"%gjG!}UvޚҥK@)̰O#Zq z ׏z]t˪  {  )^30sY!2531=g,h٭޶0d|ٰ? (+-'kms~I}U1fˮZ MS—&6)i8o)-0m[ިa,2n[ܗ-n*f=jLfŚl]Bi`\֚$Zu61ԟ#`TNJ?ka+1XSOA|=\aoqSo:(ٛ^U%m`'u"ޝ F3W=Hzk 2 >n;ڸ0<`~PٳL&yްz2. 4:7ny=z>fZO Gs=Gn_x._628!xH>yY$f~fgS|+:[AނU=B*-jdaq[@ Y,@T~s̯=NɈR)H ]$zV`-&N3."'-^F>5FX?iŎLb̈́,9$}($pD^>ZA(DͰ78E <=gF=E yg\83ni۰9q?ɜ[O#q k7ZW5v OwZa׆Đ= ӜEcWȬHF ̂H'2ƍ1M AaF-q6rƒVQxE:*k)'>d6wH-t7ޥ|Z2}CPf;B 0R"XO·Am1Gs @s-g޼w,L"9a,n jD Pf"?{  3S˔7  j^M=Pj`U9;]5|eY?l*cBtnp`kcJxB%ؾ6Yŷ/j)̟X  vZz~ S3&>d;n >SJi}g,0=~=d]-ܨ];7G`E 4?\i??s;!(h*_e[M`u}Xug1&?ER5gJ:"I? \s2^L3\1{idN{t9+eV|aR(4$9wGI@p ANIeUeũ\1N *R#]4E\q_OY߉jҊGY_]sН`tړ~:;O_d$$rla" (QhBX[Q;)˳vJ8}o0Qvn&uƭ9l1%pco714W&.ЄʜF( &4T8N=(8L0PHTW'˘.1:,!ڣw oiA־Y$I`' o\[\2̩ u0Wt, JrR& K JȢՊgp0 3Vh{J-ou4Bi`ԑf2N9ȐB=Z{JpBHjOJ"|QT@# "V5P*exGQOM1-8HI wCċ78 ٱcl$3C_>ȵpM3^i{G@h062R:EV/bU Ītrd:1{cVH.1@9R@v~ >?n-ßDs$_"_anӃwj62oVi2졖߅7`Fe_53z_سrV袾2E^]鎩m,`v?kkg*i7,vڇg`&m޳cmIձߟOtTcwU {i&.hìOC IDATgGM4Ue;8 X繣m ~2xV?f `4< +0V? {\TEjkhe8mЀbge*ѡϬ?YʚUClu+™~I0tK9̋\V|ߪq;aɃ}]~B*͎:zvÜ-wjcǶ @J%D*Oˠxt&_P_ WaAhL8V~*;]7j5)YXN kKmg1=+s܂so:lU߆V̾?<B~ ![`OwtMsc}:B.>Fr-=V$'%RV'br;WҝCΦ8Aj.HB+i6.j \-Bme5[(gP Uya+VNO^E5qqhB G,!f>;ȰŷUVM3ǚ̽Nn0 {}z(q3" 芣@QҠ$f>$'1$qU5PL.Y3I"EUGHS[dJm. l<2rʙ3Mކr$)@s!u'\"+ NrFgXٻdȏoZS>?%oͬM5L 6.z^RN bXVDre+̜pGg]ǛF]0h@ZӁ'ȈSV%y3%V YJ %BZ*i6UeϭqX:jחUb[ƽSy{!7 ~jO!7fLŔ.ޛGaj΂gWyZ;_HhI?I̬Enm/!]nim1YPB4j)^kJJ/TY=؛;y_S& 'tm7ݞTUg0jYlko_Ap{ 7az MYT z ÏjU h ]^o/B|*L7 ~Ϳ)?3TA6 :A?6e?YeO-aܱ?Zj;Az @:4_R~?WsK~jC\^`^W ^ܛO| T=Otbc,[hwnTq{w ʞI`Ï|@-fEl^W-q C2哿SL#-ҠΥ5ˬ#DJKQD2a Lކij$Ku׭Vh.f3%L V+m >V.PͰ瀞CvZFe&LF6Óv*!(&7M]%,a{r'S5S-#fD `٢_D5xUl+d !yxҰЩGJ%R{X'UAҳ?F/NVSS|7R؝6S}>׍EyJjk%i%YfHR6 5;t2z+c4ߧU@4r cd+nNv^Ɨ?|,rq}'?(w^z}73ٷ<9(gj_"Ns~S| ~y=euf_ڟE7GќI}'ryp f~?~3C~i>}l{>EشoCx}?/=k"[.Bfy7,0CcdU @OJ[<.Hbd*p3SQJHUnUUi#@TkE`jR\4m]w$!ږ\r&! a[Ųe˕ ۶:Uy" Fm;7%™^/E._Ys{BSGZGd kaM4mSeRxj @xL u3]@:%iJD4œ;Re:Rꐽ lJYedԱPk6[m,ۅ{J j+Fo'3N|.Ģ;.1B$JċOcZ$x]7+*E(=NtKB#2^"&H( bQ)R$3aR 5RGNF?UR'IRku̠ZmLçxdpET[r)Xq[&#Djzu"D*]&|yjfݵ ވ41p~D[O nYv%ru_xb00QW00;,8Ԅ{0QP1VE."!9)lGuw4ɹF{|n2!^)\YFMIY3ņɅ_U]Q{0_\L9yu7}/ | L3yķ*}QR̒y3 UaіLXL`uE"qKe\CV}.P uiJ@cR,)JԞRdwC$Ɩέ4ƻ +I]؍3!gPm)ЮGJG=<|/a|羒7N"]G:6miD(a BA|k5Ta2!SJSDS X5LɂMꜫ&@E:K6G9] nNڍ؍dW_pQ &_'?q8$zݾ؍yI]$Ā9!I5.eNӛOa  Kaq Qƅ\*(!\D7OӖv͛c2--vD/'ra=eVSf̙R[IZnkBq0*|Ł{g'c܄TA&HZ˶DA>N::KJB 1(ZKO1,mD41.B2T VָSJ&=Bq+"{z>Ώ|k{~oz4 -/o'LiyU3 sS:s7F%EɰPQ݈T4Wj1bViEI ! X ΐǂ7UI{mL1o K"scpG:#$_uc/L|DV3I-xq𭗍/<³o #2Ѷ ~!Tg7b לm&YŔu1VMEmzZ2!y/YP+qZ͞G W:'nνB=S[BsW997.]Hs;pLJ? cV"͕5MЛ316aq,"B1!2“_f7fs`ywtҮ ĥqzBAi`uW*LSBHh]2w7Y,oF \X\߸ϟ#qQSM>_g]s5 }"u.&YSՐjXMTVs Ȫ1r60PܨY)S$vÅCWSe5V "Yb8nfBjl_DŽAA2$  *Dް XNȵUX@}',qmĘ u_jbd&Gta>n/k+~#0VY3.v ͫc)Ι)>THk ZQI.EW~Gn\g0\xk0T$@<)ӪPNWocK$.ut}5+d&>5Rȓ;Y*FRZ)fL8˱$G2z YIEED̄ !?O\FщpyC*<8_G'+P c<~3q_YW(:3ZHjaޜe'<॒i\7TYTxJxmuD "!%Zpגt`wAΠK A1BaZ`#lW&nww 9kx󈗞m6; 婲.KSj `RiX`R:d@Zxc[07mE5:<(Q!Uc\FedQ5љR9ꕜ@%:Ś"UTY(hn$)KXz`1!W螒Vn5^^_?e/zYCïy{\ 6530;xwnI 鴠 5gB ʸʌzlszZ[]R!0T%3Y"èuGQ=[OIuK_/m Z) I[D](>V˅;㱽橺 q. P[.ա.[6tp#5V^S&> .@<oUXXW$ I%O3i ^d}(-={d-GL2V:SSd*JUˎ|u<(CG=Y,׬W aecұ4E, %r|VX=g6KX^h"p0\"qccGBRI%|:pf3HȰ7 PO~~kv_ ՟ Pe00-| Oxk߆x߆O֧~`;j7vc7FP;% CGC5TƑ:ISjٲZy FHN2UV`0ׂm۟Dx1[ swx\=sV&]FㄼYDl#9upl]@^ڙ]0P٬1p5O/z*gΚKݶ`A ! H7ݸdvh)8|^S+/|C{[{kưW +Bd"ItNRUɽ5djextrFzwL2XQ@7Ը+3XI<+8{{~d+ HBGU1Y){s%uf*^ C:D^3ja:NM9-dʩ竑tdSD(JEfUxr'Ue=5. qHXMtpP\-1qGO+FBrUEjlQؐ!ꌩ&n +y=s1#^{y ޖ'P L!%r*V)PZm[eAI36 jFV_ֈj`9DKi˾W+L'DBDRą"0J pL#DzVTC`*[Wj 5B:\؍؍Mj>^t<%]}j9w`׻;5HC916c\ŧK"#g U>݃4Li4Kwg<(tuK2|g"MC1K!ut)_Q2j'r)k]Y`jz}l 6ع4YЈvP6 곕L~(w O< 2pXtٍHF7&HyjĶTSPy nAXp (YG8 Sº}c;W맬b9eA'5qUQ 5*UGU9ǛBJA% c~)Q8; +2pШ c@ ) Bam3wOr0p}TͿ<(-rId)G@h.FO7I\Vĸ=0)(U8_ b9k<Zc[P q{=k~HBIg7rT9Zܝ2"zBzsuϝSĥ ]p6Uj,KBa\ G:݄[`Jq2fl1y(t!D1$RL$bqGam.3i~kgɘlݞ#(@~{{ ]/tO~nF%#L)w%;?;)!,a]Я?pݓfп%)?ݏA? ρ؃Җ,Ŷ$mq|7Q듐G P>o` нga"H\*{,p3_i/_eKσ~$L8[}M(nO\i̅x}~ O>p.=am9_R$y߄Ƕ=sP? q? { 5mȟ {O.?̡_k6ٛ*UR2+k')%yǧ*ZknOAEV ;AZN\iVujt;H]HiCkVqiMrވs6\2t\[TU0eos/6B{|л)&Dw?TSR8yb5MnS.BqɁe(4^RlIEJuFTD4thXxq^B෾Ayz+'\<:E!\5tSmAo6$2)8(w(#X0M]fUYXNyO9 >D͘uKΠL4ܐʬOG;.<, 9]aqh`q] 1F(|νGwފ\>$@+l#ADaܭ"XH$yX\:~+MioL'5$I+Hfjg@qo3rJuڛ,9f!AVȠ<>s^xuL\|V(ǵ"(5V(ٌt?0O`Bg8^9vXd] km$8@53:V`~ubTEjȝrUD¹DDnZsc:$}+FV"wA`Hցva"*qPµ,yPS0B։;QG'ܳ*I!4@0Q03l@qn ⺴/{ռ|@̗Cޮu'@nf{(DGKw'r*>o Paa^UB颁̠{ l`\4 C<<9>ѷtnR4G2a1t\o:XE2{@S8 ?. {8aX}gO߄po 8AO?~!a{0mYOdޜZz VlQ}肑|"6^O #\/x ΏBYs;ʶi:wÞTO 80؍ڨy$ykAѦPRQ[XUo;I3`t)09ڃۊt3z#ٞA O PTs^Ý(H@΂ !%H]p!BpIھ\2 TxdF6d&[EN}ㆺ[ڀ2\qo?$泞l`1sSHtqJȦi8ɥPks55P+ DK" r~ T W@!u>u.`pGo kb`f{KcRT @ ;j4.UF ŊC%V4f#zIBO?s!;fg\,j谕J\> 7eQ#<1M]lVck)0 ]M}]w۸+h9<_+v^wN8&PM裠&U\\ uEo|4Ԑٌw:!R"P Xz,* |K;S &n8ۋLޙTi$L]IlV%L'jQt1u׊ Vg@_sa7\ݩ xmG`s..M߆\4? yny2 ?e߅Cz/}<_P^ Y@SzH,0zNpqaEV⎱Ǿ1nۏ` `,օ!<|ʟ5.x7=%/Q5w zˉvtϹ+=a~/I{{}_rq{Jka3п˟cew北?o񓾬qѲ;nnS8ёjLtFugFy.􄰍/kJB{DĪe.ZkFD<#7 G6Z'lT7F`o1䊒C7˒ &#j.[/Ϛ] CbޱY =a滮NnAJbT](wc7~2'8rYț3Bt$$LG-hP(]d;泎̅3zB$@D|\+9oܧ81֙6BZ9CE @)x&{ ՅR6GEշKBG7tmY]ta* )FoqQҥD'ڞ葡%ߑG\ARER C3?+tE)9<ѕB2T5dgc1p*9b`e#4U-չگ=Rre@kfZo<̌WQu#S08qTB"kAP4v|lރz};}BOpΔPv[r᜜QEq|CUƌR2fV3bdTGiD0ՌR 2ip3IG, -!Z\Sy[#.܋sXTQb wYB7tW IDATxMs{dyֻo7n(Bpo PCrS3t<&R -ոe=lXJIB9kůuw?wm1x<3@PwL>HBJ'%@L1Δ2Q"]P*H~#sXܕE)ѣLB-X H5 y,G2^Pn=VeTj, d5GZ':FX" ?6%#ߟD"tݏ(3*JFpDf'^s0 h} (y~ ?oϡᨻ~§uL`kxUgM߽;^0t_u?{τr zܟranlǝĤvW}kr\W,@UL\ SuJp6ͧ\!;:F?)zK?^a[ic SqyN.<(vmTB ni""w-֦NjEp.RJ, 4х_sR=)<5xǸpIQ_KK }rSNC;B[i[2zL#cTg-_4tctS"]x H}:1A@F b{p0WIL A ,(Eٶv7~A] Hn.Ѯ;4EgD5$WcTYL=*ZqTnVYFx~E$|O\)J@hP͔1{G³ʛw3uuޖGep*?0<\b:6d !rqs\Y.)esT^Y3"+T(cᇢLp0>'{|l'c=~XYB[TA8;dpD4J.4޸h97 w~̺L蕰pfFL[\y8+G<~kۧ.D?b"Ve:^TбRK&ke8{4kr"@* 3TI f{Dc jBqU(=O@*Ho.;u aq\ Rs̸53U܌)]sm\??6N7))2 DU,g9FjF6AǞMt#@.Y\F j`[("Zƀ1kԢ* UX'BBEG <8ͅL9r\}Yn߾8Ku7!p1Ԛ~0CqN:']NztK?Nƹ;PVW ]x5(x3g߈ˬm}Mv?qEF?h/[_տaPk YeMcgz?? ςf.6p/ȟCnj?PD:tahaUuzkM,FޱBdFQBaf_n^ VPLBu7Nzwa0 7q+A=WC \ f6q@`aBB*68?wj4 ȶsV"g3Azm0MXXPŁӓJB1ɌGwNG'NHMhuR2MvR+E Fm*i熪,M8@# "žԴ5 SV(IXw~wҗk-<̖ ~*H+Ϻ:zRyPvI]6tHF MfQ,zZsE ƦLߚxEkQ+ϡ`ځ]d3(C| ;}.oyNbmī0u~5va[̷M>n~{ {B [mGPgd]yllۘAv_)tomw^~??\7} s0rۏKr\q*0q#IZsuuBkd ]QPJm@vR]H19YOʞFg8[cj1j5]ř c%/<%@ 9ЍqΠ^!hd_*c!EEsaW6WJY)uUY*E*B5mUBeF PoGU=[VH5>Ҡ ](-ϥe וYNjcfλ#WRgHlQ'z |ClPq໯o)re?SݛH1P#\A]Q8]OUuXǀMvd6 ,9#shz?GV/ PJHJ wȼbPD:*2Hu )N't?:U  '0]yS_kT!;D#tUoe\9ՈCe 3a̋ptTَ1R%2'P̘G4iF<5ϒe ՈSJ (Ө=..0u}E .`% ch:wOBN6pnyƽ;QHY(u,H+e,Ml mf**}V Zo’a?&@gFoFg.Y+P]\\7jq9>oo+Tx7}WI}?$]}ruo+{3x{Zk{QvQM(%~Aއåm.4L߃uaw2{P2ErSz~c~ 0~ӇB'oB=qG.]ԇ 􎻏v; oz,UpQMx\hs} [/0O=5Aah?W-(Yx fV7Zӗq96;9B4(CR gq.yx)gJRAHс*sv%n貸i W3VDNZZw o8 BOL..:!vE%w ؚģjgS/_ƮB7s֔f<9k;ۆד8N)w~`.%ɿz)Y#jlj LjW;M@ԩ"cREdK^)2 F$8cć@k!fbdqD2cz|Mn*(XL*̫뉩* ľy=LlƕEa8=5, x|ZyB=oSx=䴲5|*KE֖} Xd ։|ry5Q Wiu'G9|y~gIE E!M'_+1Z[*HvUBWh _XYAfBXDl04{3C,2z P EcO^ן?sʫcxX@X`ynGdrfX@k1g)Bᾰ C03cK#̔!@7v0Udc"PPV&{LW{Qf , {Uu=30JFR%YaD9])Pp^'w sAZdOdqPa)Y]8 bd!"^'߿y< Y#I'VҶ SƂn ')AJ10uqiC KI *B#jTֹ/^r\~mNN~q8MY}c7)}zkk)V Ry^T ӯHRAy ;~!}_x]D}Sd[;ϱoq-`!-*l涺 08k_qaM o8p]᭾6O}E$L:9ކֺc8s3X;f{`cX_q>\jtuk~bS gT)3 f6@pNmӯ/g?ۮ߅՗@┵zS Ӥ SęAՌFDL J }JRJf)@)Mfx;ڋCAob#NYsUUJvSjQ*~ސŧt&t.¤B|yQbk÷IZCߎ$Y+.""^DiMR4$ 4>K]seğV Fʪћ黎3,\]ůOCz(`o>x /Dt^*|{ܸ J7HHQjXDΫᇝ#8Cp9ƌLFAׂM9FMF!H .uFݱ h0#XO|e)hzѭr9 V*w Z n0, ɲet̼vhH]g>\#w IVnΌT.vQ:xJD}( qx"!7Z+zFAK = R#+ܮ"u2h`2>:aʷpZ^_|3|$̉9̽X4R1-uY#ta"R:T(E8ӕi`ۊdUsg"d* aS/,ݞG.GYN&rUlqڸ?^d\B?s!FcLLp[6x+pzq`$j0lZ/QO"7ﯲL,WvI: @ J DkMY ~>&(œkU(ٰmuWgltz5W"IX W^ ;11~T5dȤXiSq2l&Sll` fqf" A@2ª:rw9.Xțo / K<$v? z`R;?s$7|y4vO4qG\{?LdE y4M*E7n lqu8^ #g0~]S4(ښ?&Bg2o8(<+.f[pif8| N}lnC{v~wuqUf8{== G#wsv+%x?ԟawaz[wA6m{[(_"9^>?d~TUcKFJPO. |&%&((m1Hؤ6P(&HjZ(-?}7HIGκ'ֻbi"rE…sή\Z;پhV<vZ. QCukSuEU1cDa%ST$8E%Erץ0tm?4Z !OKd+זk> K<}}lV I)RCS{&( kEFC6l;a+u6`:o*JɅ<:ߦuS!*kasi+e {ߛaұe[6 eC:jD4 .Vl=Eq@"E1`́w~f1661[) MGO&FUj=SY8f:iN#ч- G."|_3^{Y77<3rf~ُWEt8xbT͈d!:8R!c̙pŐ&BA"bsL&j0NĢز"IG0I`QccX,t7O|'#}:MYW v)R/\9{ w7o{{x0Ufnڛ"UXa8uӸuhh {*y#Z):&}7pX2)Ao1D:$ ʍG?4ɏ*?S9XffB,( YGdF# IDAT D"(RΞHSPޜg&/Jedp4atY#<]Ylj%0K@b pr9+;$<Vٸ 3E}='cLEbp"H a`3O#Ō,BRϰ3vv\Bd햣qg{sx{,'7|. KZdKhX63ȵsȁ0b=Ԙ {(YƺsG LD0#j!=xo'ǼƖL#0)ƽ+%x.^ ػ{b q3Z*V+Hey%yKޤ SN(xQV3zX}ÜŅLğ&RٱBsh $sn4z;!.Bź0U1@2a K[nLoZx <\uy{n߫m$ypWҵ\#qEEoSU*!Q*BQa@q"QP LjLdV!0!) 0IefJJ.q9>TJ/$@]?ѶC[X 8PF?Cȯ9<@|\q9>멀)VrLREyksR\ݕ"jdUUw15FZŝ4J4+R"4Q =-ڦ<$Mq6MrsَgBHs OyNqv?~DfsUg{brSL11RwwI1eX S)dC >SAK\;MωRo,8)IbB`ˉ}jͧ?zed( {݅y i0 Q8Iu:ŝOn Jq0! 2w!tB p4PݒPRKf ixpݫSf\1zbpPnET JEJl4PrHR$Pmtavc&f53d&8lG2Z ehlÌ}5VE٪&)@2d}/}m>O̵%߽K`!\ ןW9$y9AtHECjȺ2($w-6 2+_vXpIl8'{?7GO%mjMؓ@4pp:/)nfL4 7fJKG`ep t QNN%McphUTQXFv <(A1%ԛ߲)Q&F b""R6pňv#Fxyt q~Cq28,E9@YRY,S[1"(@UX8E9‘j18-)wkY`0!(.. Po7a_vnQ{2]kg]Dr\9&HwJ@U1W J5Rs!L5#[eIBm]GU.SZ3A]X Y+ >iOԅ3B6ZJ~lK&2H2z uT&Bb^sZ_'!.|T1X &c' />DF7 }s75NzC (Q'fV8pMQA^4Ew М]D!Ոfȣ:`5C І1vH#!r4^ƼpɣqlF&UeuH+OI࿷)0o~)>qx|ho}f=1jd2!2!둲a+ d_`!ؼ`A RpPY|x{a>׆=Gs׬p ! T;tJo/Y4L*E!$ܹ)R/ĉ̈́z $0a4c``<'-r%*Y%T*c,LPa.>M\y!)V"T fpP 7&xfnl K`Gp1X(,R^ֲqj ћЛ*h U1" ^ t.$@E7CǢd4 2Ue 1S5b\:..r\7aX:6X^ꑑVG)^qoʈէ+OPsQRX$0 Or`48g9w/}Bv!gA9eD5KsT=)+Jp&Jgk]Äk}@JʤLJu痴X=TF\SƲb" M ;m4@mRQ-t6߽{g/޸ɇ<ᜧfH *faWBP 6$w75uy쮃00Fk`oܝ H)`TbM3el9ͅt զ"H=W-yVěoߎ$$&Q J@Ȓ@?Ƽ0`Նb`x 8@_FV"%(fsOڡZ9٤-5=S{UJW3k&%EE@Q2G D8* >&y Q\HlL*Py;oIO=cw_9!`A8{V+p]XL2QBOJ H%SfC 9P JɹZ-U_&WfL' 2|9Sq9gJ-}l)SWկ5hrI>Td9VޓɲXs&ַ֪PTjS4&ӺݨRmqX+˜u #'UL١Y)P2$2 5M -D\9(po::ރiradқBSYЋXD`y:E.zx@=\N!@#ܨւr|\ˆ*WJN$VٮxD7]ʀ[Q/m \acwVR WǸL9~UwiT w^z~Iy;Eq"l JO̙slǖ(UQ4*nk_4+;j:,, ZVA+T2,fJ#c˷6g1xƳN̓;8>|yp7rxA; x@q~%2,{lNþOV(V큜@,A3[UW:^Bj b^ʕn*:G*8|R h.h(:ԯ)#s45*@A+wҽ"pko+\Nln&fdai,aH>P! ;r-εkNnȸ劫1>QGCѤVI,: -üv\g+lr4Ukk!Fv_7Woso!nsnŽs4"V,3`83X<  =gԁs⢏l 4"fJQL>,~p<[~,{vuGn0;cw#Ǵ,,dG?$ zKhv-,/n\&#hF/FG"eJ`wsxQ?r4u qcTpB hl=ٲL+žWb{fZɇbF8F,WJ,T P,?T^dbSA/ĥ1QqXVI4@JǪgX /H5SO; ޝ֊O~63b۲= l¨UfW'r2T.\npRdN :&|6RΣ;[Tr0 1C)Yhd['öBAй{ؤB\WHE)y}d+ۡrSR9p3r 2! tL1-7C' A%;81QLO LqhٳX }FRZJ&%SOFO9k2 q)ۇ«YJIkqƔGU (G;E,,M#YH,DWd*\TLEy;o??^|7תoP9S'jJk,j={]7gGW2P!7CX /C|O t8Ik F*0<.0 B$dw s=<,>8x_8ss[y{NINËCLU 1Sb`g?# >}?&P4!|4]!x1sj3T8dX?-wֲzcw|Uݝ*NC6An'ZU+E|*(Eɵ\`*~ ?2eXUBw6fMѡB!XǗZ$RP-UF%T[)868]HH \`T J }AZ!Y%$+- M9*e\ NxT 9etRb祫c!s7iF/OMӲ{o\a=|Vn):"DU l)-!s_a( (# co67 >3B~DհHˤQYg=Osy4a0r/BmF\7ܺ}$LaN\͞[q[K AԠPq?Ṛ]ݮC@!;`\y}Sln Yb2vFQt8iǣf:`bdF`re{wr {.@n#YqYNF^6S IDAT>it]bX)[A^ y!ii|)*9PvE$6$CW(9E""VͲi8/a BWr=7!E 9;>!,VÜ&cFOcͨ @`O^""*ujVV $WzQVX 38߭ۅ!h n«}BS a}l}> mEԖ6m3l^`P>zf] ~ ҿKC|>;uݏ?uǪSsOcͺ/^3h L |3'-Y.? Qpchk&tz:eolL7m. ~>\2A{H`3(=ǭؓL?{ANp8KsZ/ءuw0gk\~̱q66jpV,+RIERՠ{37=-&&zlB؍'\)|S!SڶWc2e'p)9ș5tkXV?s]ONO@:l'4k]O^^Q|0+]DX⴪zω\,LU N@cU%%U` N!ȐTuL5sCfb`Lo(a>:EV`$UvVEAi@ "w =[!Fϥ*(ԓ3CfVb(pis.Yp-w:nI\PU53t zV,gQWЈCE}?O<{FAqx٬״u[څ0EheP>};̧_<X}M5^ +چw`w <.&оf?a@,ooyurpڧ_nbk~ V F lZ-_|&?fpuo_}Я? k(  :G-O'l=@Bٖ b]FiGJݞš 8F,/@n}6ZmSj̿]egy.܇a*eWPIB?1ߵ7糧(0*_>1ٿ=1w.͓0NvBT,(ҵKP1TVTJU>lӓJp*Kt{!@lb705M9·h/`J{}~1*Z&XEAT߁A%! 1V`}p:*ԐtJ=>(> ~Zg6c0O!,nmC x|O2xBh"! {o5^)!}O)n hʊ+#Wo{Ǯ>`:9B]2B蛽DKJ=XH͊H08MyX$|ksq="άC@f5HydsGaw#.;%͏Y>{>1~;4(\m0w]R撘MӚ*&ܳ_J]  x׷[v ,βҕQфiJ,V] }G)A YV|8'/S̰byN P@ 1/\D"x1V ^W!(U. %:z)dg/:4IFf#GJnK+X nm=0ApB˸eYu^X 5t~a c)4b6ET醂  %xh"ru\rnh83𝁤eN,rR3KtRA,f#rK,EYЩ2h!d=v~@ԯʯ|_oo~0z~{Q7OgN @TUs5a&?ß\99<5_L^s˦ u߀Y? <|7u*T?{_]7# 6JϘi PS95_l-?m@՟C~jw>g#@Yѿ4mo@JF <-̬[9A1{_aap #5M&-:F7]Gk&'v<"bNj#L4d_g?T@u>jȐ=eH`*7>F3;S!Jaht`؝'y 67`A4 (I8>qJjHrԯ`bW+܊.2D Xxeի/_1r$9&iF7X<k0zZz zJ/툻8g;w9e|#1Le-'%!( ,a&-8(;ǩ0QN8IIKF}fYk :ϣ{mݛ7M^\popiwQ;cXEY-v6ٽx]4l{H=Ex,wX }T,JR!ռ&0P%$]C(/+d F-_[‚l$Y;hА2#`#d%g zz9^B:Ln M4]-AuWh+ts0ÜY'ʮePu5W AzJy r,! B^> \<}z.O\]юHR6PGX%0,^ h)|jweޣ+3- 4JBrHғCB, ml֚gHd4єoc#4u d-a9̄ϙnrn}%bnI*(2E]$-QGImCLu\ @y7" UOyynjwG/f̍y w_".d*gBUals+׮1ܹhrA@2 _Ș,aߖ\veZT_W!H T*\z<lZu0c{‘G0RbpCi8`j`\EOHq:R y\ CR3b1;b_PaXS2 |` jHg\Oc`Ԏ N"',ItYH5\2kb 5Xx|dȵ.x!pyDX29  ^q ^pŐ(lLBΉ π2ގbR:p=G%1Dń\jO2^5h(P9TEYТ#0xY,) ?p Gp"$JD!G 7szDxg>xn)YXt<&Mvƿz"zS[`ͩk^ ,"L=mVO\!>{03lA%C2'㘍iø -x؞F,8@ ^{QK0һg4_=LqgIX.\Ջ׸0}ʷuq\+6F|6쌦ܾ{>1Ga0IGqJ_ d+u/gVM-{muXUPkǥ캹5Rh  egۭS芰Х![U7AZgLSb(.zGw ~|.@oǾ(]!B^*ݡ-n)Twiͽќ+Iv¥Ӧ4&4.ğlw-.%$#~N)JPx0 lJIʷUCNj󮜂ju5\ܭ)W&AzċD'D/3VmO5ӥD{eJ,w$8dfz PR% 0!e.*DsiL:@q|4=? w 2gofDXI%:sK3˫I}.553e<*ɲZ:[Xl̳j%̺?lg'%j3N_2 RLVC1*Ù,B8CNr1s;h뫯ٗtrfp=Z / wuu?]N(P:EO;0g?ejXSa C!ž7`u:%x_cmU?5d'W-iV;NM.D֐(ZLRr6SYu i8~3Uj߫$vעX9w'aV; sZ= :8$I8HC><>;G x.uZ#ԺBYge\8d̸۠ z^ٛvgꥵJ2XFxX`CH传7I]t%H8mNFVPM. 00 m2a yR!sAd.5#q+q7YgXNT-N\ \VǗa@1n2m2xGШQBRZSV9ҁAMtZ*,EYYQ-uXL hJ.)>)RAcJb伝vނ'^_Q<'Wkj`Tr|`A>𰭪V+x{z@T1:dk&(7yJSqр{k֥>yf#SOeIx]* `܅ Vu_xLJ]f=9Ҽf_ڨ!,L5$pi0Yh~N %pn9d9\-K}5`Ϗ }X"{,OQ-|%LsV3CRaHTr 3ǂ;/NB9D"sR*pUK;~UqsBdS1Ћx IDATڇi Ӛt:RZ}H pNjND\PjvRR~(RrU'N{k+8WUQ>nwzTYe9$rJKJ;=3g6EocSs].SV،S [/̘1?xb R%P$ZE2Jw'\hQA3$x99j" rA%+8"rQ?u H\p8#67 Wc%6A=BOv Տ~ܚr:ek:] l%.ꄍve83I'GF11<`}b&E,K21'rKf W=8`&4̇0fw4fg|ү}qI/1m[/r< ٜMXĆIG\e;J{i>PǮDE!e]iL( GB-ZJQZ q Qe]v˖lF1`@!d:s ^5iGS+-)(ZX _ ZG1 &' C-rp(t 8>Te'2HA0Ra0 Pp+Sp WNq½}F1BXCc })]*sNqJfi W ހf#{m]Up}*͵ aRz!*V`Y+sUS6$D[Z~B׎IUU8'ɂK:q hۖdHb;>r _vƒȀ@҈Ihix@pۍ%9]:ȡ ˳HVJ$ K?_.L:.s=IDP0C2?w(WlLՠD#%8$V]Ǟ$:<+Z&Fxo)\w{l;Ʊa ͵MAfꄍ 岰vƬ};Mˎ[1 , Ͻ@}.qWbEzFXLcv'[{G vQ3?1v9<;ܽ}fMeyaYYUgsD$(8!6Bd(ɡGx Z?7]# ;aHi " 6!>CN 9keV!@b2s*=Wwr84#L{77̏:wѧْ5s *j410W (T8f[H7Dze$%Ҋ)X-"-L;G{16tV)r&V7^ӫ!j5 ˍ2 _W oh(bMTNO3'gz)\, *hZʳ1f(y\xč kڸ m2YrFYzgT1La9 6 6{ V2wz%.pf $ oWp>12Mܲ^KkӲ6B+ ™YXJ uat#e̕ !r,I1AhNd-ͨ[+c?Է3+?E"}O⮽u?uџwrJ#^ LVY7s|o}SW"Ud [50|M(_";0%oedX_mo_Z8c*sx*b-$IٯyQQ!8v&?Y_|gyQMR?|#㏔0^XRʾ>Wڗ3Q`?+t2.?yn'uo]i'/kCpx繟biD̋4+pw\z~G#?.-yݏ@J ' ~r|L'yޅ"D#E먯煁 SZ~ 6( =q؏oqhR#21A*3mpkbM*Nr+ܧ2A2R0jF Ѹ\i04S#h],o+0]w;7֎ eTpnm3$j[銪S'enΕmvwO\&˖\+ًkVѫLmDF@gpb(nl+-gJq0TDڧvLT#FleY1Hi 3jD+ȴ->q`k29zFȉ@$M!BNuMi(\NVm!cp'g|JGQ]=_pCkzɚEQ YKbs87)t9Qb9l<[b∮3o6 ĚI3E8{ Ksd9Zt'ܒos yXе:KHӰݧ䌰Q8OͰ!37J#/pN093Ǒy8<~v߽K7)3cǵ锏/1>{HbWE6"$LMg5.):a+?-yO*r'BJHK\KSZK{d'4`=hdF7,"` 4z .{2F+~?X=?ZOK^է XM1WI=gK}ubj*=qҫSbVݏCG8=-bK Ek?T}O7p}t}~m'`|:?}pť$MKWak`R5sltzˣ 7X%/c~ VY߃Wu6c?T "1I"8@jNXZ6-[SuP) mV8fl Ϥĸ"@fj Tp/c/QtK5Z')V'>F,R1g%"d뾋u{TG%2:%̶^.>EVK (۸eJXv,QjTtu{'dL11" efFƱg]S>nϙt3y0վf#'C҄ $2c7] '1? ?X.OfpG~N^}#9$F4B*X:`rhBzl00x=ReF4&¹q-'hQ[p/x F6p~Z3C㻼]1ߢnY1)_W7Hg8x\dCXݼcْG{4}2 6 a> }G'|˷50kWgzfG=Gצ-)#i-ض%GF.PFIL'=bSZ|mLB L`a,GXg@rWUQhҊ!Dal,Q&;$fA:Z)6kk kDs/?h4_HDA\CӀE5 mCN=_}dy]xt,aT[޵ Wь m)Q0۸qY)֕5m24ⱘ\@.8r$hf̐Cw"&vuS2Tg؏^9!b݇~w\HǤ?(O _W7/o|on?3XTqӯ¿_(&LI]_w|ΞU>Ҷe~—J4K)DJ.Yq}^)$/A~X\<˿Iܶ={pv+I_`Q{ejJlvpɪR߁o8Wzl> ^U5,*o\Ӑ޺N*=}rOnUR.ѳ_?/17i:2a_L䷋FSccEUuJ]}`k0q?s֐>A$5:ĮKSxXg-m +=~m7? e_V5 _k> _o܏(%D3*û2@Wu)\*Vy'<7֑$TbR$ju])&%ԤB%rT,9UJXFrɺm= w#F ֿ'1*Eʙ21DRNu*V>d-B͑)@djVlDd8[s[[BY+Puҷjʘ뾩."PAWNUּs65AttgȝS֏5ZԆ c@:A -fHv &H2dqCo^x:d<_c14IE/vczl̈ D5člK#MAԖXcq]O Y:*уlU >9ΞC}v ^[lpx}xo9ade3XL OyG46飣X.݊;3nuYã'!)[̘`?厙 7gS:}Ëƛt/'> |o//ۿO|[?}kcxY`P5_&ֿMOZ'WnA}xu_7+BޟJ`3?SXRO4_ː|7O [~ןQx G5MAiJtH9~kj?w};yuٯAz/x^wQo jd "fSB&Rtn#kɦ:VJ,RCbC.*FbyU"oA$\۩R&\DTl]sۘ[u V1{h`J c)--TJEꨢb,, U{EK 'yB-ԝ@5󸭵7v*UҎZ9Zr4Z؞v<#ĢP8DV#hQvˏ[GOqӀ1{VK۝]G0 7<8 :wl_:MΣêiixQ p3}=ung f0m l2SKG"&$J3t1&K*k$lr.nm0Ҍp p"i&FЏ_[tb1pΰ/7f,,ijq:l)~оa<[Цlųwnrdȩl<}#x>&lӔcдYy0Ǽ]θ{YSv\5/m&_y;YpEڛJ{M >|ϗq\ŧ!8U/_sV*`p xl:%VE|,"Q1Cn,x&+ȼS )AV,V ĐK]_`:zUfAQ1ebMDEBnp̨rx47eX0ΔVc6iUe[*ս04L*K~A;=wnu<><>\-cVtx dJ F1k$G6)yKo=FtXlCccsI]5'EΖ؝ͅMpg\opШj#+8 F6׆s; p&q F4"~9g9Rb~[wY yVZ̴BZ 3q윳!sΘqɣw^vSޣIq8-o?Ouk,rKs#9_0&\3!G%HTo1w;֮֜{T C/ф4oXj6xQȃO"4c"F6F*uY,jFj,pMbNn+ʡĤdH2oa:5&$.s򰦗c[Z5/̽"H#+mXdž'5V%QnRR@burg-XN'63koeF ᝓ>xp&lrfwbm8 ˏDoihlCLS0D2:GФH'EQ$l90| IDAT B؞g=,@=yf>Q?t@ ˗W BQ"p#)~tf\Zv5e,yZU.\折DeԌI[HB^qlmzl'[畠ِb1B*iBQ+%c cfHي^90^FވRSH? ;DwjdQv;2qx:syȗa* wVҼۯ?u=zQj^gȩjJh֨jYƞn0c, ^ VP(jj(Kqbe@ q 8Lx'iΎ9hzümh%Frkiu 0cģq5,"id29`ղ$Yin+ $RYƁǐ5ч0C 882JkZYX2k->͜q̸0]qa]Wn_q~*rd:V![O7,,¹kag̺ vaI < /_Äd44PFŸDZM:1GSNx`oLkƒĝ̄w0o)tLNm R\\N:rUE-rlP tݔ~>c%7FLay7 L!bAJ#SeER(m|۫S7C~zq3 2&)sb3'9́wtdYĆMjRJˡh+׬-X6KrxMάJow휨GHmif#<8HIHa}rm8 sfć~c?3U|z^y k?NA}~w$Uy~m(bTө13!lHYI5R@YvE~j1ZR̥/D˜ iS+!deUmFE  R ) UUdcJg[gPiZ9rCݓw1.HdT[}8o fRś-\j c2Lø*>-TPq,Ky)[Sr.nJjxמu9_ӀWUWV$h5N=o>qhŸ3nxHB :9Ă6 }O~zE )MBM(̮%8Swi]1[Nb88 0φK87EҢs3\q!53iX%a7.*ojF$XqB,9dqI0ɑ%''twM92B6GOO軎H 9i1^vBX^r΃S"7'd^05-lM`3HS]n[sP餂^\ ROs4!䩇VWP4*l.9EK(6 ɀ&fR,u,NKax#EiDF0l c#EgO > t#O$H{d'#⸐M1&OHдtbu@i|oηFv6fz_1/Mh;ݴJBiLf=GmCpB` Fn ׌9xfIGk'8#IrCq0A1!cb¦T )MIiT0l BVn(S߳~~~wdJ<i  `*6my+(%oC ,_~B[PUKuؑ*nGrT"TKr*LPR}RϺvDwkbM\VZ@Tu+=n`ڌr)%BH񾸤tX *HXYS7e;wm6e;0rqma-2YrѱD\bH֒ʖj/##%:7lB" z9TOl8]YR R܅wԡSMk&ф)[QqclBP!#jHso;p㙚N~œՒ^.v̑"a ! 1BDcB⽥s A>İ hԷ߫Tl6˺̿͗i?c?0FV!b/.-N%cJ4D.nH*'*meTpK&=jLOc T77Ԗˤ-VhƵTxKTUT}q,5Q哖Hqm[^J bm"FW\/=Ą0^OKLt~UEI-zC b͎sevnS8TH5>9Ih@Ta!Rnc,|85/r\0mh9ovC̖lpsN Q:FHx#ϝ7 B &,(ׂ:EZіF}D7ikK#qM&7X"XcFe l$P\riOy%:j`E)0h8 +r9dƃEY(q9\ ,LX 6z*ߩo4Fad5F6:E12Ȩu-t!D*&_Dw~M|=ÿ;C@|7`SLG <|_=W4߃J% <OC 0eHKXOl?0/r忆nE ~ ~7to5ה'12V *@UK7hĕvk\Pʗ1ED1F6ں6kҦt˜5'l*Ci+bT&xYZTKT7uLI (KI)HzRb*n"c 1fK5rc7;+%w "S|' hvvn(nIrZ%UES@M\(Eh .5D.,S` H.lLb̙Eu6:~B7mi:Ϭs--f%L M SG.\³9+JgӢth;Tb-qR&.u\ӐW0tp#H,DzIx*FzugVYY_'1azH),[9!:#/ I,%9[jO#u1ǒNJhdfx)aOK4a8u\ysŹ\Eg J[D1͔1iˏqJ?2t dy 5kV,b/a67 ӽo3_ps;kO? PjXϏB3P>m`05:ZZt&0wqh_ P=λ7'PjͶ51RM@:xo Kj߻ oSHWN8+Xsɸ.{v2IKԌ%)cDnd'Է΢(HJr&|x"*SX-Q`[R'ƀoS5ŭp)IKGPDyHHƙ1 +"3\e5&:"iJ=I Y2k(:CYwRD8KHA#!*CJlaGϸ{hG{:C#,Gd$֡AH&f6z0YɭYc6\Q:U_$faL`(2oxt\3[yy` G ms۲жQ&&:NRѻgGڸX0 9xi 8)OY<[p8f Ei8iGI\i4!l ^O+ ǽ7_-X~͢0,)uҨIФl=Gso kt-:mH8|kq@<<НkKEE0KE\ODbrl1Q&Ǟw[ƖɆ8BLmfe<#C{ψ6bqDD-SmB˸lL+gF,9a`#!pC 0RPVfFM&BuG0(!ɩ孇'lrV2$aC@8 QcҶ4Xڮs"4rfL! PNj(Uc`#/'|aW R )9IcMqp؏ P„80oaMsߏ~~ W=c,]u5m.3NioM;$x+<[+-La'Ȱ"tHӠ68cznPo>^bĸpteT`7ҩkWm*W*r.oжs wo@bd8[2"@&j!3My(-xD\?:xFlEyKw!4,3e2`sɉn ʑ\,\ :c0{Y(l"9tAٌp8ˉUY$(bzL\S5tp`Uʵ3>0=!94,gZFq;8O\MUJk\#[nٰcsI9R.16k=^ցuLbdEda8̓[D)qHDJ&ʽpڏ P٧SW͔B53>"㫿>hrC0 |߂g#A-Zhi 擐>_\] u0~2G̪iyHA2E߇Uhta #lIeh&k9,ͯ~Ga݄`|c | R@v yӷ`m>$6 u ʗmߩ#g[Bpmc[0ÀNnك<#h1Çv.yUo,yذx[݅\.-IS$* *6صWC퀏; g *q(= Gp U$l%ߥ~H dŞ+o?qg-,\2k=%RQ vZOD K{fsMiVC.U%me =+%K&SD9DDbD &("=!$/jOdI ˓ʙX%脮H7ohHx{yM(L)1 }߳{W+)<;n4ƛ"Kq]Mt(@8:&.+^;AI|P̙i*4JF=R2pѴ ѕȋsB)O#Z2:ĜQb4B*,z}_{'xAhSf'Bq'  hae$#W_'>;hÕ ÜSN'̮ AXŮH+׮]B >s^;>ůcȍ'=㱷RJsTs+`WP쭸A,lH ގ~_MlOYHC@XIa(<EJt<S&O3LƂ;Eh@PphA#\~>~VP֎ l,v"q44ﲛ,`o"ϛ5@pNP~~G|֬p?׍ IDAT}HZRE1# ӫoǡ} pg+0{)ˈj׬Waw`-;mas|`hסyu@0U[rUU9f5X_EB/a6>[A>J wq[|Z ssKmavG: _N'$y.E@yGD|'ɧd%U5l}Rָ\G|Vd󄙚dJ] ι0{Z 꽣{娪u^%~UGf} dA}S}to]Zo7>SH%Ε;XM  BJMg*| s.Z@6Ro.܄!NP;g]EZe &DEgl=$45}!ZJ.UFfɦd(cO[sfѦ4 (ހB5ih}fh[Sͅ,lm]qeESh4x.8\34@qf;[qƝi͊%A"b6B“!;$$'i⃮v>ӀBA'''&⁌;h__z>c"t㨙sr=fpit-nȸAD QљP>fC}w7&rzq8IQq`X'֧=[6QHH5͹y~"2 y1!D7OXRrl񹡔,Xz%n#99w^ZqvBѨ6>23Ô0!1C#d$W%AQ.ER^ȫp2ۢe*9H?1FW \-r MBDX^+-_ } Q|QfBмdb 9gh ӱOLOlj!%69q5qaڈU,jbj 3ʐ>! Eh-|Ss7o_ëϳg(???-k`,Pikнs;[2Ix Ga(g`? `S@ VAޠϫY⏛M_Wyk9s@/Up_X:& }+*oMe߅eUaY [2s5Xkwjx3-ȁ1OyS ]~̖{˶K‡!>ml~w$; */gUW{hԹ a t60A|74o5l\L8_W/\i @y䐔U!ѻ}NQBQgʥXq8R'תq;ۚswRՌ"_8@S/q]0KZQM|8{vƸgZE-h/{ V?<+z,U&R= XR!aےpo'ׄy;O0!>B76.|ްX-kn\f|OŖUPf+>)Gmwĉ)%i-fz57t<3y+ 9Ouu3Јt[`d H&eNW#'#>9&-l%3FXNx|2a(aVhg!}l`,Y,=ʏ őW -fYF7:J.H)D1WBR%D)mlb,:p iG8R#,qk32u vaANpFp3Bęǵ i ,d n\y ngVj֌C$1].u}8EKLB20OL)6S4%9Ju)ǂL OйV3}ԃ_BvN]Ub9_+@7~uyQ]@ 5E +szٿs$)jPuI1RfR[趰Zn_RϨxTLŐǦ;+Dx(}P7r.(}ڝvL;TIU <fE -VVl,63oh/w35ٮ sjwJO8ԠY0"pn_ pU2 +S2E2PE "4ɓ7u(eL3E; 8@?l-~ W2WCi}43`P A$OS{A7 M MCL6mAQ6Z֘#՞D̍$<>yEb@E I({^K킛^++n!hq^~"iW4A&I4c#gnc-\i-tbthP63x  6HS 'T=ݚ;hE ջMx;;^4kyl@rDSqr{iDq{[|jFa'V01LUh0R],B<6&0N>f\SbBg ~D4EsĒ9BY+ygF6xRv(ˌ;;\(sFfpKKUD|O91>Vq3'u>W`O~TK݇jVMX[3s9AP]uorŷlsLO3./|e^ܿ8S|q^gP^ YptA]6A+M1;Nc 'RL Zك;+dhSEZPL 쉱0b W][6HeϜ A<}*lrDN;Bd}'(zzo"-Qp ;B&t?.rƍJ]L(EB|E!]@k$E:JB! 3Ϥ&vΪ,|42-ӘIS|SX᝗2rY-:깽Vf qA$cMZ0@ >:6i)$6D&64 zx)AȚZf刟ML;7J0&#Yy.]^@PT̎}זF}C⍁WgLa!W M)duwG^W 0meO!j5@{lcs=*L XB, XWk4p+grl@} +zZG@@粀ߴE@7(XaVZG}8& Eh?preZJe_HZ܉E ;⤘f {RLTrb9͂sRkTQ bt4ɓ݌rNU+ڽIUۘBG2ȬfKdd7ohU ySu᪡n_O9CuGU=Z2=||.Ҵުukh@" ƀxTQ # 2!%nnZmD:֗v\|Sr|BS2YYJ!zdrq>ӊE|;{]HQ#pLlp>C XӁG9S`k'm< Հol_-SE7NVUWЭ2EZbddJlL IBVޢ KM\j"_U\+^h0eAQ@O;cG#Jpwi+vn#fۤ\ٵW,Y$2 !2dV('eB%BY^[__>{Ł[|V⅍' B6Yoj[ W/;.vgcI!WhQ@$&dPg|V @38R&BWHHfYsM.\ 4RޛjR@JYOcբ,0`z!hG@=x?l=\}wp&pӫ1$PmUxo54?kBz_q-t? ۵)s?ӭ &}^LG1puWݰ짱rMZ@*܃x 'uha>l73rl W-isI@gV=+HyƗL %gJpA.ǠHXTk^P~|ƪ#(~=7eNPn`nTzVL|R!mK_} ]Ao{/p]~l7m]eL}o ܹ8/E{椖vV[jZВ`J-c:R"~rjU*a=`9!@ʘHHk:ڜ9+/u4W{JU0up:SLλϫovvڏJ}Umj/X̳m`Omi@ә tmC56'xw%5YkR'{ԍ&+l Lw]9uo1+* hҊY$"q&ܵzJI< ѫGv:ƅ=n|9:La{j}aPfL]MAh=E2. 7=4x9%ؐUxB78cQihhH-?K$׊ A(c*易4 D$Bp m8** WMm&}Z6e)BW7g ,,xr)CYJ88Ҷ!huU@#mnc,I)}PJPǙ E;7'o6%kg% IDATaa/TFnۯCAV/oyB# odzyL'0~m~1߭n Hc*P> ρ޳1ǧ Kv޾*v־j WfV gЧܯ.gj6]K^-n珠y'0!w ?jq`|-/\7X-H_0p΂s om"5=K=.8ˣë5+T|JɄF v44αi9I=hҨ˺Djtޅ cϋCt 3FZfT"Z+xo؉$MS%? 4g3#9&7 )fiPҥKw_=E8 y"8e۔9>2E0Խզ:Lz 'UAȟt ~G|'vyTn֥nY_ [^Ai?N͔l5˃ʯ<ŧvGt ݾTU`z V9+Wx=b۩ (ӯffʪG셳nYY&Z:~6|h;L'Mg_~d=7`n}A8 "pph͞@2%½EX9 wTʎEYl@%ENA N@9`v*!Ll *#½Xt"%C] RBw'}8ֽsXvMxQbkũKPu;7EXSǛ @8rڮZi&Db 㝷*$NT4!cLdK{h4*țUΛOb!ZIFTm(4)Ӕ0*4P@qԍ<{w.2#!r@р@Q(lw$䰁eD:SӑMHט<4q2MhNYe,8nvK"h)|u 04md;slMV,:GIxYqG4hy3A'Sژ E穣%o5t%sPV9Ms5PId ZB< ?@.e 4&saHB/.P|#>y@S ͵<0(r4͹.q%#ge7n7}?PƂX LḆC.p):ɎF-Lb"8a,c,t-LJuOrBrlO;֡.5K#hd"1ರlE0wBԦ$SK~kFF:jA3_ihHe@u MuE3U߹Zxcж͗iir6$:?1:CJp}@^X.3]LHpHiR˗ToR^Aޤڛ˷?[0>Wl*?G-l? +>[$~N_5llm=> Wa}]tzh߄?߀Ez\s]?{2jk7/g8GVȀ:λEhT9ٙjtйbͨUarYj*qb7;Tʷ;pϬnꥻfW+^TT-v{@3t}d;k%zsxfP\(hpV=}g?RVM>/Z]a%7nzvӴ 11@ vUK)ɲS& ڏh܀H&9)|߻gY`h1x pd9v\L i/L[+^T 2|݌4Mɡ +p3)uIQ2j"u(`TQ6Baq1w=JᵠK%Ӯ#t({@,]̣%f*9DA8Ym9\wm\^Q/WQBQGˋ--wp@ԖQ%rQwBLa̒wOs`ss q FqΔP׎qGu]BfM feycνNH9|KcQVs.+tZe6ܿax0QJ7.9hrZ Tѥz2a:l R48pش̽sD!D-A{-*{a< VTR#3eaZ+Z/6;Q)g8c8at(ѡ:U$nmFW@ňN  /DO 5X|yk#2I on͢FAPW tjDHNrfe}g95~wMj$HɲIhl0$y1_P "%Oy3`łvH%#QbHȞwM5ZyXwIMQk7Wuԩ]?;(NM:蓢MSvU&ja UqdC4}UR7qc.^iegS&l|Hg,Klv=8]9S=ӽ-6)9&nwHgWNټ}] iP) .:90#<o)'z,@*i%Kn3A6Z,qg'㝍h9;/Lّ2sz&7ub6t$3NwJ!yR֏zԞDL˜P+u$0)tʓ51/}Bu #9Qb^ۼJeoUf 7y2[~E^qI%蜨Թ0 {s0ԗw3)S_j,w g l,zFwCs*PwJQ(6Qki}&cA^dYזL'[; 4VWq@qeV-Շpa: L-"`?<8Ğ̨DÒ9{y$T-'Y[EɡQܐTZS-ת2:*hcNBX89)Ey5W==`j{u^۰-xфW}i@Xra\m[e$ Tm?̄Dd<c;Esܚe|9qRf%C֎˒F3޷Zudvߊ`717yBOgiFdrEBr!o`[ ܅;*[`qxS VlUH9#ӀS3]<9W۵V* }'Y]^{|w VA!7\D&őѐѡ8 goX,#Hf]jAĔdzHn}n-e ׈v|:@'Wmʌ'(<*ph;==(EnVm*$ON1 FX.lu,;f>0Du-)7^nqNP2)'rkɀT";yMt]w9lbD[Ljq$QMN ~ǝ||s"OTܶH7!]O-*_$)h: 2 %$' ^&= cv)系Δ+{qs*dHκh1Km8)bT&SD'KO>] .Ho­c"Q>ZXVej-h8<,rr>qg.`:rclZ&U28~J{]$ #خƑo3^A\"Zk#T EaHRG;\vɭo}>LO9 L zNNb)D0vN/wVM>+` ce4h_⛂5's/'6ۑº TIB {LhġYR28^tijMLL2SJ:.ABoΝ>4ԕ[x[uᚨRIEC;G(q7ɟI>ꫯqqq_o+N {6Dhh_NĖthMdBmMt,LSbӂ8q} J 2wQ}mAV~ F:(R ?)95;iKoN8I ȕ-i6=B5$.ESJ'4`lq>k,(fKL IQ}ԄN>x0wIu.UbtV;JꅲIEJl8%|'y䗩aWpX%f5LcThIS|&5UXHw2r|<gH>g6nyj 3br :majwR'#얠726⡬f\fJMNBqVeell템N^ll$us*lTuFUU~") )xS6B'H0 uV+AA ߮p!ȥ@w9)s7ҩs/Efs6ہQ #t sb)^q&TLDʔl%w;܀eшos )-Ij6_) IDAT"w$]&0a줰P~׏8@}#W_(9N?qqqp1itt|'~kTZ*Jc%\ŰXWz QJc%%J,:v*8؉6k沏$gHiF?C2o-|feUbN,N)1fYl3t][N$(-1Cϛ)唛zT)c@#!aU);ݯE "xlҨXLHTiKU`,_<# quC}gZl %Dzn eGsc"is 9(0g&U誡iFBq3'VNͫ!ɑ>REܯjDY-̜4C\A2RB9@J'Czg~ blhe+2l詬z+<{ /fMWQxǯg">Cn/di sP媔,q,& *KMǟ_xb/ݒjeΌ-l=EA%b+J#XN┋śW;aLJAsز t`K%ݪ#Ѐ@+KH87"$Nrf'ƥ:9S~BUR "jM@H' z2TCvT:ޑFBaӔ÷No\8a# cŦ Ţ?k/ &4-NaAbjPfR \hq+P>P{=:C<~@J$ Td4i[6S؜D%D'r}b:y<3k@j>L"ȻՊcK.A!ȳIJIg:p!IjG-NʕaPΊ`5S-QTctT2.Q1 N(WZGn6H}v:t 'G|iu>+0=k_1uP.2<*PC @zwG`0|GHm 2s_PE/n8w, 75(=x\SIki4XyuRʡ1/vĮϏkC`@dmY{ai F*k/)Mbj GtM ~~&|x?]O !@c]ߣ^GrO<Ezu{T$\]O"ނ*~^`goa2/|cUÇaP`kP^cOC}l;kqdvo uėMj'hTSM%IJՀETb DHdlBiYRqM 9ւ7=s[?GI? b3?GwWvo|"G19tq|y[ϣÂKo{_@?hw0.U?꿁T|1gPAw`@@tÀEwtXǀ{q?n!Mg`?=;ax?W`?^?3@^ ,QW;a]M񼶿@Fӟ)?\BQ!H?JPvaqw;?Iow`$ې_߂);{*s2;%kO> wCmc/} ]úq>q֎WF487? _v/gn9'8W]bAJTҧbcީo֒Z5Ƽ^ÚNqjNKDH)§X $DƩMHSqFTߕPAUL9\>!J=7AN|"'B"(=$G޶sNC:rnH}rk|D0iC]gX[ plSŻ@STZа2T)R:֞gdk*w =. f̻O)5s wN8]F؀Ǝˋ޿̓ߜ2-=k eJHsESfM:bS@ eÐYI4fǂw^w<ٍҎUrnYCX'OрOŋܿ5gn*r৬|bu#ӈ ܟ0YByQ2V])uDs&;>p=wbR{.`) |G9{DU袡 5KIcۉz6CW :"jRije54OqφtY;>|ހr?t!#Ilt2,9Kj8Y#8%aSWcϴuf @F' Og>1EJ<V)׵)J@g9N/@W8-[ \ wr^1 <[u0/ #aA-˫zm1[y|mg*v YrO5 *:ssY{!YN& MoVmڵ(J)nVZTV$^ahɏyqߴ&ӌAewo [tOfevJ.ޏ +0IO`r_-ht<' WO6XM_&>M `>-C=O }Xdg ? v~x.=r15`~ڇZ_? ^?~xz*pG!qu r m녰/4^O'P𽐞_?#0pr ̿/,׀ԭ]Paׯ{Nп;^[hq^8W8FI-SK5eY\#< ? |6v;O'8{o2րQ-l<ՀPjhRNn{N*J1f&qEѤ$Z rPD%ԠUR)MUjbjwkmhҲYS/~4n-+Jܬ5hU">c#&T+nD׬<I=O9v:.)Y;2|-VQnMԔ_qE}T$,tYXj\^#9scA($IYsF*|íwXtzqم7փ[Lej<+'W6MTXZ_] Ͱ,>62xG|*؞TNed*[@iǰ蠫 h;5Bu%6n@9 ^YfɕqgY,`w I"kpsAr3޾xR,w ūQB SW(h/xxx#"&t؂= \ހ@N Dց;;#[yl)o:m@%_S"`Y4H{T@6ضwA- ,r) UdʈpKM"Mpxaؽ9X _ Řknݡ`/qXxJ۸vm`g`z5d M4B} 6/b-~o v鉀|gp[p u1m>_{ӇIwa\Sa=_xx8q|=CJsH^E;5BxXd6E m'BA%),i刧 kYF&Pj)fjLFPPFL0%Hink1TZ>#]"n~*Tr݅mt ""gD㼨F ydOJ-DeC~uT1j XVݙZ]9m >KGZC_hj  ؎E4Ǣ*aEA+"nMMy"=T;NɨwdIt9 T aHU?ZRYz7/>l@6ܞsR7ayzAŚ`!]T_ Ra [ųTԑϿo3J3F1jGjN3Fw`W"}\D3&# ϰx1_3^+wtg9Iwv4: ~"0ko?1hQ+i|"C(#r)_{_qs]´G#iAmV.Q]av6f}W+ä]hW/ #I-eCV t f[ RlCrt" Al]1 5sAibSʬS'Dc3~BȲ[l; :L\DA‚vh[:rS4W";w]?nhMm>HXd̸}ֲZ4quܿTE1bW\Zc\d-f1Iy>˝B4#rR e*̬җ )5z86*و[]vT;aq_!59X#:8o Rvo|GEQ? 2&~sCۗ?3CgGn6q~TOJ|v xտv0/Cm[wrv5^/7_߯o"Ŗ̛x e }=n|u.~p@Ϳ7cr,0^Byvd WK8Q?پHv] Tބx{8'84'J mւaCR(&ՁsC$.AQaڢX߅NGbJj'7*}kTY|B8z[jkL4w.ut9)@HzM\)poښ =BkT9Tb )"H͊H{s>m^y{v2]\L]%JHn5ĭ/Tٚ IDATڇ]ejT rAt5*Q(_qCЬ_)'|OЇ1R㋪ ZY!?bsX=buw#3Kl-`¼ c\`C='S6X] <•3NV#_foh(buy1eNcE4]Ks̝[3ga 59rB:^  ЬXݐqn/o]n>itݵ]t߇ P>r J";{;ncmomDKR>vO|;0~Oq"`S}\ L_n7w}+E_q,@Ki_},\^yFrdU*h/Hr vVHwzW.*Y!ٓ$u;^ʪF,&hHQc3͋WzAEf2dpI*9A 4襺k͌=GzdT%q:22#w9,3gDN|h+ d!jc12[nB4$,9t =|LV!̢d ,Ȃ$>h%GsPca#k.h51.U`,8ćs/5f)FOn"Zk` VE+rȭck9w/Xn'x( +w͍u- ṃQb[Ѷ?dv\?}N>az.ĪOpe)/?$>*2tcqEJD$+hzH)ZBhl'g7Xnǒ{^NѶVRCٛ-,Тq.bwoҢmmefxJW.(+16! G I!LZtoM p*hm&<q!@U6]$Zkky>Q TᾺrc6Sq)cIrP%,)D=s![&L ]H뾢nqܼ9L-c[z.+\i MOH)&7睭,YN[bH\S/[t ZtjpRn Q@ e'#XvZRenvxߵ'3mVNNrBj*e0 vlwf !Ҿq֐k wi{О?8=݉٨jX!q-0vmqfF%.֒ΚT [,vHHDte¥||iL λNVKkF{Fvk NFg=q999+!L)ώwMf)"dpTµ>qsS=ZeVzC ?"m) ԡ, 33˵~áBv\;p쌳ł)qNW\J )}!FաXc5=JfIu5Q6{sFff,' *xplȱ'¼ӏF79}1]WA >80 oK t]˗Q:_R>(C-6Eh>5 R~ TkM?Zަ_) ^x47;| P<\_ 'g}%__V~~6OzvqK=%wT,l5{@>7.P~ .~)3d@ɗQwo>\,r/Q_4/Oix|9q|=sumoyIJtt qUB7271)2GbRSEVAgNa- QԐ7qOyi5+buzb*לF5"p:ۧdf95xs4WD]mP`\CJ,viR Զ{kQ}4;0QZ_J4ĝnFD0e!pg*ni̻&M]LSfHdISka*t$4% P#>9!p)aҷ%8[&xO畮h(=el9\Ԯ1c&D 'xu>G/2&&nVI$ v[DG[nv 82Rr-eO믿{8{7w7޼]w?w`x+'ZiuYs%M c]/>"wSpȠ7[Lpʧ"N\`d7c_%>nHo?\R3}̓̒Cz`=ջ-bL, z$=Mق-XG^Q9r~l= An=7YHSADS,&h/,j;>va* o,Bڮk s@&>OC\a|q(@ XMwl;))Urgts%k8fOh]1W3y񽃩P PD/3jUǘ` hܘHfFҾI"o27Iv)5l-OULǔh;$An(I Z+TޡsBsiUØz}C@1myxAx0W-ju¢Zi^d2UL+jNhC3W6,'ړrb RN"@虨X1G:5V"6 \:]c~ 1K Ȁ{Qa+_02 ,XzK%$ΉdRȷ°gF]tE3ݮ*~rxRQrW#DA,!t$çÒxx_ԷOSks_y g;XgZDTq)"$l:V<)OF"$_ ^'|a12,Cj, tʐT&Mu4 J v]\~< rpRa .u) [с2NF䤋s~׬S0D cg\*-Ոܚs\U"%xWaaL93=+>qvcR$^+R#r+ai"?0]H'=3'&XSm4|T Ҿ[40q#[q"jW4K8"=oM UHҜ[*HjD]ggppݹjGQ7z:4pZVGAqd"hXP?9 8- P=?Nw]HYпL?7[h|c.\EoCD){o`p)/Dz0ғ~8{/t υ,l6vC܆?>'^\Hto/`:5}Ovs(w|BQؽ'./mO𗰽?!Zy a ~oUƾbvot@+`4Z|u%Bm;(wC+נ{o%^|?> pE`/`@Y"'=U^;n8֡ ڜ6xF$JF+JߍTV7W3j2>/X SI$8ceSnb $wr5:Ss]7!m4x<޼Z U۝$hV$+)k:Y[V4%Ƭ[3$)dx"D ){ 8`6۠F\d/DA%hjOHZ|-&(~jWvbl \ZJ9ntKe*0%bZUHLWԺkYsT7P / -"N=0ʆUf.mx =]O/Z ôPk4ȑΤ4z-Ӟ}' N C"\H72| wLy.buFͰT=k%s1$Tx|?yibύB1r ٻGJL'rHCϐf:חz4rN ]qL[^Z`P]2CɃѤ N3]vT~UCM/*.!.wUΛsV*- d[x5NIz ܛ-jAgXEy_Xn*&8ȓIԕ~f pTn%#-"*{s1`D,i+_gOn^XUD,붮$N;2a p2-Ec1HK.Z OSS“c&LuHtm7E7I0-&ڦjFmM!IF熌q.mk- 8<%f 8-A,#DwU?cR_qmJt rGP^FO3򇢵NWW3K^1.S*&>ٶM߁ N~2bշއY(B x8|ӱu? YBoNI3xe'? k->'a9^w XKwswN?Ws}>;"woa8}oC/[ozQnLn6b;i<|tcں7KV~-]N~8ݟ[!@ٟZS}w>uјqq.,fșp>~QY, ]WQ-1*HM1wJeo3Wo'}$! IWRXOx8f!IumOl3*G\EM q$UASM)[-G튇䳞R0RITbWy`j H}̥!۵X5hZUS5Η4V0L1촣딮KƀfR o.v;2n V Bm̀ )SJt]Ǣz@槉Z4Z͝ryA!H>m!}`x9ɚSJߑZ}|*eJ$(U":1:ǻB5[\ -5 \6' \2~0'6/*Jo`6~Wb({ ^f>j幗 _]U^ITf|_s̵V(2J+ypYswɵԄ .,2x+#t=ei<:Z2xpC -iNwLjͅb`O9xQ'wd do) &YiZ9KHEk aSuizS uE h3fE&#]? <r2^ O,Yu9+HpΝˑϼ6pw:Opd -V뵰v,n, Ȉzc$Xn5eRE+aZ#$VY!KnudDj.hͤWr k Nt+pPT39kVQ+sg\8糟[Ogwgcxe˩hhnx4[p3-jX ln+ƈ+_asٸφ_ws$Gو14&d.|[|n?to7m<=v{W`v~&mf )uG܍)=nk.;a8agUAo~^nB+0qDpe/cr𹯼ga# ܗ=_huZtlaGX{ٴ}=O4Lc1$PE eBߏB P*4"E$V uۉ L(($U: DQJй6({y9̵Lܴ~rڄ3;ҞSs:%ORRR1 I37Ō|\ "h 꾑d/$yGLZ΢n}(4`8^f y5='wժcL]@Uc8S) ㄪ96'F+Fx¢ψY3]g]fʙ)c3]CY*Ś04!n~[ew ;^sok;:{ I\_ӥRH+]ȭ-< j%Ր,VT -\'#dA*~b)怲&>6%zkyDu Ͻ{8|ynǻa IDATXnmuPހUT(9cb脇mO_LOQ>@oeEpʀ>l:C]axD{M l?ܞd5bpwEt~}%p!E@ 8cb!Kˎg Htᙂp\s1vDkEC&xYrVf' n$yn[g+))\.Z1# *l$^/yxIxz%vkIOKw~yvS7;Vhb>uO^OW t|?{9v[gJ_sѸ7ޙ!$|5?nW;mÐ cP~kǫ!&|+} Q܉fk$'g;DYRy[(!, _mMΉñ*nsq_Q9F\2e!J&dk17*;hJ!ZD"jp 5*D[gj"Ō^am{Q ] < V6O轹Nԥܧ|ta ܀m!LDQJ]J1J-2M=t/^5 :7 Sd*xȽ:CP|Wʞesb˜;NO{VLkp6zcѥD!yة 01N#4b^piD]Ψ*Y]t}&MʤU9Wθww/9 KwԪP{6{}vS^8Q&Q4e耗LD]Զ3IgP~Rz5xg< t yB+],D6f+ASH7 ۗALW: |erΏ8v1 ]iE.R+-sڨSs0UgWQ0ERRHr+KU˕FD1\]:ulK߱Lq enEmO~{ȤKVP u< Es0 *28Hk8B,e^|ki5j,tɽ];w9CoE(>:WkqYU P{R$/O4DќqRDϮOMJ(m5V^ iP mre2adƺw(r%ԽX"Yu,XM"{|Iz\AY-{ˎO! 1ՉÇyxɴAt]h"'!ezaNTQ5.u .아UM'v2Y '_xȍb..{/qlg(* ;׈唈Vݥ ZɓR'Mɩb s6'vEuOSOn57-ζ A Itt7`zR`9e6ێj]e);5e E)"M{VUOBJGac]ėSj@B)67*}."%`]䜨Yl@O2\ kX"-"N(/QYt--mˁuB#HˊSMAROrfH<՜: {J9n式FlFrTcf723F}ߴ6?;U n[ZF˒(pEǢ޺bJ-T# `Hvg utOev&*5M-Bk@46ΫͨғEtn" '?YH0(pVk+"VԈSR ^JO4ztx-I\)3LњHo{MB6Pzc"qo[NƝpk<骑npRN{kUA&%OKA%xKY@o(]_e.Xx1j1(0ޝTLf @DЫ#Sjhuq888=\3̜mVy&jjn g஘E牲4SrZtϰIJ+C6 MMlجpA=&>vߤptͫ!@E#%aw}Gg~~!o.ڮxAWBATC )a椪k_^蝴3ag!ˬRkAHa%tF܎9 M>5HRf;pްlji7RْeJty CՒ:T*8X;][>IC5r//,U 6woxVK"LfL%.gjwgjUj-tl)ZvyӮI,5DF_]OJ2kvM;:(cyc2Au+"־4w(م`z7&zfuH2VGh Z^#;8 PomYwZcWQb56[qѮd_c0?7k쇃}7ov<-xN#tܽw;[S\eĜc*Hq7|nTsjsl=/̫-JU3.FP+Vʹrf!^: S/MXs?UެFw- d1&1ߣ5~t@q:tAпd3wNouOLEض^tNbkqϿOv02+`w{?k %\_ 7>Mݿ^`m05_:tg`|G{{pQ`P~x9 TeCxE.^(H UNRR(F V1i-N+1߯Gf*(I4 [̕zugD)Wl|"ӕqɛ:[Cw 䊧%wp]#ܞy3<{'l 6T }fבh#%IDipìbٍnzawr"*ګ0pɹ`ˎd줬uKF/1 v] + T2p+vuj<$Dz. &tG;I k- U͙1ʔ*]oF|)Nl 6݊* \c,y}nAWtw6\rDm{ "@~=6PܜEf6u*RCfU|ʊH4]]D2Uq>Ѯ9׀!xqFg`dՙ3oP.w-эeR;Z&Dqr]XA1=XKrԱ;άbl@- >)8ggn-zGys1+LnNw*p8@?)P[-a׉[+9_qޏ;tbW+p*~kc+jQidIW̺=\zB͖0XYnuжx=nZ呖R Bi=Q\s}0b&;*PqߺT)_5;~ )-ȋL>6'ʳǽfн`'݃wB~U 5}'~0> ,@->,6e䟄Bm!ݿ7,u'wC z4rW..r/Wo\@gB50(+REq&@)ΫVSN`Nn-g=UQFD Bh Nk+F%%"{HJŕrcAƘtTBoa?:0UxufLd^({d^%U\Q.wk-f91T-"k}ZgZLav @ UBXID%OZLNKjNj5RDWJp>HOBr% ;7Vgl[v>6޸OfN-Md+VcbR%S KꔩsseWaJR6 &)øWxo4u(Fmnɡ{}Xrޠ%۫s;g"Fpks= `m'a=TCtw=)]8>eMwӤ{Tg*0NݘXmb]✤JΰqN!T&':LK g?y^BJꙩ$\š9C 5Q´1 6kʃuX}/NI+%+;+r,Hy) 9 Vi,\Jjc>L1sg|5jBNx}>vTW`J`xfQR-{~"txς7A"i/VǮyUHV@'M"sc^D(Q^Ԗ+w6DQXpEQ:~~~+;+擿בŻl3{ӱO/{A /OHUt<-?==Ymɏ`'pwn U<"(?D:G`(;xq| P k4Ѩ=N(E4#cujYD2uLIIb5={4%d,o I'J+Cucne&ǕHuT) )G޻=ےu~_fV>>խ[ 4\u`lp`&pL8~ vxbpE s`IZjuK};}g_ZU??2jmӴ$ʎ{U~^m,:V"AE:Jqw/Q"#۸$s4K (6bP-&J%!*y%lY@p>\SH* =FޡZJYRb3D$ bL>Cf~?d87lVqp3$5iMӲtj& $  lH%idr4drX` M|zt5X;NU:謢thBt@219NW\;\ܣl mW5RNeV qR (Y}*)8Ӷh*0OHHC~l/8Ǣ!7I9cu =8$O>XtFФh>:c=X'pR^g>5\h3G8Ww`?qgg=^Ϝ=MmH,P Ҍ:sU,A0 IDATATɃrz w='5!@hq3ڄkꍕT0Xci$&GLo9A):ºEhpqLƑ`WKu&y s\m!wRB"J]L0hL&vZ$JqYQ2AG =j'  Tֱ\GPqyVT91 }AWnJ+7Svm^sͯ~_ Wx\^w}L~w5^+c,6_x,o_Ϡk~Yoa-/g /w ŠcCvgrz BC `F?i%mVLa8?Y/>?dp 1럁o~LwB}>ك.@x p=|Oϖ}? N+a[ @yGAÐ>\IX8,2WC8uwk_ dO&PGCh 3xqx@Ii'h<2UP;$-wkb]-Š0ШЊB@X!282{2w~#Q5M 7J#Wk42sw UIޘiPB!qᆄΛ`C]KՏL ! =)GDLR'ggs%e.LRh3)g:J YJJ,o24"O\t-HYXx:g39#%>b"{9kOR cadyc/mȩ^5!gt@}-:/pg}} Ghehr0s?܄8ʌdEN*2Gy䫬zXO]EK0s)ѹ0أ˾̹MLmh@?5p9cX 6Giz&:66Ѥg)æ I9\AxoZH=S/zn+)FąIޢ3PT :ҙrxL >)i8::6S*k |+}A.PK1_)$c4G ʺr^ .aRT3y*z{:&PQh\;?It]M+妁(uL"5@N&@4O$<7c`Je.aoAg%Cc, o#{( X, h I3X~Ow)_c4z<?2 &wkx8YAr-MpB6o[z,iك-0p3es #Iwo!p :!~ؗLFǴ,ԿWuѽCs lq4," 3iVc TQLgFcuJJʉRe[OGrf+0kvdđ&:4^xvJ) knyK#' Z%&;LηXء)fYg79Mx]4 eCRz=^AY_k>|u짯yk{S,}XX_#".;mglG w`_M0^cЄpG[X,C2KolE;= o6i0| w $9CH.A[p韖4tg9.~ ?aayL+-x/n}#ӳ1Ro\?m>OgO@ p N~ހ %,O_11߅llH;6 ?bJol]O,?&&U`'݃՟?斻l?{an獕w~y{߿voBQ3);z6mS\ OuJ-  ):)L;XN!ay(] +Y#1fB%D< b K'Wg)&3I >U2{_NjdS)@RlջRAApb&mtoNj1O@)ƴohԣtj,"rwƕ0>\2+*KXwdAKQI= ]$պG4)S#/ _@&T!fD'eB13 C&JȢENc *xUD3zNV89;C=.yV&N׎? \Yd udzo=gCa:֔DQ9lPQrWL!T|_$xxj"Ody!Є 'O)zn<)B886- zdsd /p50N8 PP3]4& dΙ]%8jPփ%YVZ`u,_чܽxeѽ[s<:dpv,\'95+Hۀ Vɍ!K7:f$ i 8l6x?E4q`nL)A{&یiyə,Mҕ毫^딢W,\WA}FG\r.Lrgps0|CU&Ռu:iRƨJ`%Blv5 @/! ڟA,nyo!([{>oEa}v`L ?/<^X<&W]]s&gTaqE“м p@lMFϒ|V&sIes[,1Hfׁx)~EKZӳXCXLtS*/qo Ͱ|4c ~.J;] /CӞ^}n|'aɞ/M2m}'L!>a^Icŏ Y4=x߿A_^}ĨO?x`%;Ӯ}߇(Ǒ΀r׸^e5fULl\%E;P'6"]cu!೰+{<|ptujEw,B`o~^snݛGoxiI9q萗Vgu6H-=F+e"Ðh>5^p,9{ASFē͒GW| #v qqy;<}-iBud>oS鸡Ss֓;%宝@ >4G{xM5w3):wn 52dA T8qd<Jųgq6ѤRR7Օ4An^I-#HIYm_9Q.J({=.szWx/ 3=''NGGprvX<2\pXOvT4Bfp.C0O/le %,gr] srjOreJ 2b5%NLLH&粒nFP%KZ}&6U\X\ћNIjaC?ʺQ:͜v9Tٙ* DX,[w]ڮf=6͗Q +JZ3 1{Gtt|o}a,,῵*&$;<{Io4#I|ҽm6Ѽ 9@ ŒH9. oS? ß>X[xy3AY+Uvyn Oxg;l?i[w`M7{v530* F~ @vʮ=P Q s4j=Eblw#I$+A2x>ФqHv$]FCeׂaEXO;xɶ-`EHzb{r~b̥MÏiui2ILfd)1B%6;VRmŨOhV-i4f?&M[em94/%3ZXu7>FFͣ/LT&cpaD7.')\勑銓^@n{*OMXrrr̳ݽa?IvaXTP̟ l2} C.BŖOY,IMCH qpn]E)9yNZp|~rT9]'V%L4ROUvhGQn4Ҵ0&ϧ13 q f2Y*N9c2mr <V3Ωr\\4\֛݃А=(mx}V%M܌h@Q7 f٪){'8Ywv5'~Gy=GuR_Ͼ YxwZǍ{'xx |V$v&'wi{sGB:7P4g) !L_]N z]Rxz2['wm}ڷSti *}~׹>Vܫ2wCnK>Xwi*3y/6f}:o4n+k`6W/X?xmz~eڮͥ__'y[hO9m0`xi4)Ί)&TtORQBNTs}ȇ\0_evHRo)b2PX]#BGIpemcveU RA)f TIbi h\1<'Z2bR)W Td~ٍ-Z/)d,cM9yrjII3OVKR|̓e46z ݍm̔՚WR+14b!8O4\?&k34FDh\+}r !Goy o|q#*; rFVϝ11bo?h"g>pa(*ƠS ]a#N O?p43dNfPXk'j,R&V6K~l8g玟1P(xjچ*rP}Ba^g+9xĜ-pHt}bE0p7,]Mp%>Õ K8Ko@RGtN;eHXH Pf_^TecxLx;!5G+ᩗy Kᙗo/{^+5J2" /ld ǞeJc E۳^. 9Z:j*ko`k :y.\r+H3šșyhgcZVfJL̯6d;JF'FVJUFb>]nJ9U|Yk(#Z)`JDY+π0wmv4 jޝxkLCYHc2?R+5CW,7J{CX*X/ D;'\q-/~z*V~~|5 Wlooާܧ Ik V:GaJy٩qp& _tbI6|˶Uk 9fxkؕ awar>B ~P(9cŜ2H~=M x#C…2WEʼV NS>L8[pgn(O]KlCR>%GO t]MAq.]63_* IhN/, ~ͅ&>&* 8xrtEFO#SMޖ_x:]yU༁FIa֝/]/2TwSwjy<ө2Fr!Xo6cXo".ww]f+S %G!a0i sꤾL* g1gihICٗ3 SWu $u7L ]CgOEiCB2'Q!StNgg8 fx>~s>@h/3ECM}7j9hĶku06Vr$J2$H]۵\(?Agp{ W߂pi6eζq)Nr 3X}@{:3`]G&;,`vk:t}G_ 8~6MK;k4Y>[{q{v|b|-&`K:kuϷXs_6|~%C& Ϛ}ah\LUO-k2$} _ go͇-`񆙽cYZp+)\2d?D>1a1 2#*7 qհKطaeǽQ50@(MA͠4Իfb7UHΕG7Ec bgYoctlz֫ͺg%V]d<7xX3NB6#A.p&W*,ƦAa)>l*5ӵ""UȨ4e=,r6I9=iyV>]TZ6g­{^ʜn>=QI9R:df> $=M}a3{L+s7i Ĝ%i /D\'J&qm N}9 5HG3;7)5Ͷmmݹ$we3{C዗CRRa黶k;jY}Dțc$\y< yCNo7S.;tXF>3 )X1/Aұ `k 8MF6hu1_~ o7OyH0$\ pǶ'h<&ރGayoQ^uOY >gFz[}~bq\VMiSH.C?=>Ѝ%^ )n7,p}T\ CN V~|/[k;3i9'Y ~uj`G`ۋ\.\39gl,cc79d̹kݯcjӾ/BX{`]ٮG58ÝԮk^`YX v48$gsƈGH4_1%E.8 sDid<5 W9OyȟtXN%04ƴPS\ Qz3]-E-*|IH.PykIPWӚg𘹺}v, 2vR. apX1y$YqsY!g$7gL T:p*M"l |J<ޒV2bIh \9xkGpq".hNOלKE+oGM,?~bf8GLP3TYi@eOI9{B2v".H́hB9MxoTnr XP~oaMmϣL"L)1Pzc?6]dG@'W0U&_kN}l{zYg?:9yϖT-J\/gf~A1rj#"=Sj/(_|S( K2z >r2(ݛ y LvYAgm&XDqt4VRL?)I3kū/xZ/0nN\s-3RFY r\ڻH./C1]HZaoj Y#*K^JȾi)lӹbd&Mmu Ӑ.V#גhUV䘹$^7 Zc&al[45vW2v $3chR\dH]dXi%HKVGs4)D7Yem:9y|b6P!j*=MXY<"cd-2%gN %4+1z.q-m2O< _q88>|bW>8љ\.Hjc>+)%33 *L(nd=ud^reM«׹BIDg@ֶ*^ Se`;2ܖU0~odG&4LjL]۵]{P|;yg89*+ ڍu'ϡ'@x(v>~L=5 L͹*^«x]2Go^ѫWt N$ӝEeѐ`d׵]Z38nL2Ό}69s|n\^mu>¾w߯7fys4}۹kuԜE+"Pvb%Kq35K]b'cdW…HA4ݡ8[,pD 8P8TLvCm;IQ9*q,YBGBM8yU EI%xZX. 3&6Bz̃j}~=_dzRiL׋xqT|ODp%NϧZ](N\cqc=%s*ZMN2ygi(>""}t)2i<ՉU"E|ewf@+O It)Ε~uA6kᩗ^KMp=z։  x2ͷh oγj8S&3ޓ}"{Gr _f?sqYFߩr0+ky4dNwWA&V.8;?f &DݞkU vԮk~W~}cص]wѲ* bf!]i]vmvѼ@(K18+tkTaUx'D;OZ8x207͞(9¢%ITS)GGeG#C%"Q)I),2YJ\Mܼy%.PR2"UZd2#Q QԤy-ەV9LA<Ϙ Z KIyܱJX YZFeJ_q }c{Vl:͆y \pdwώvr̃@۶, +I)sIʣw$/d8՛SE0itL[hroq|[ºky7Ag)%!̋w 4S 6nMgAe/dx)IbTI!stoe6FV𩚥L::v9! U x{@ ͓.S} HqoPN)tkvĤ1rʐ$U6gA;o:y? +pd5nngLj4b?}eOLʘѨt,KtlXe)놜H!σ9Fƥ2)o+^H #Otǀڵ]{-PSO=]۵#0{aص]{66DN@9(1@f!YEg HCLhĔKaɽ%qJA' )*gtP% nhY̾88h4,_l;]Bb-G+Z3"["qh.w'[ehB`3B9>>ua<;G~3r8@-I0zwTPC 0G)0\Ǒ2;tcR[].5PQe' LdCIJvm{Z3Gc?c>brwsyGpH84Ed'3\{#?hG}@!־'qO{a^_g/ V_ wN/";ax d6 w4]Ư۠l8iȷa!1$B:Xm - :DA@ AMR3r6GBũ'`fJ$Id l$]C Ĕ>q"2Ȩ¬󠢁)D26D(:?u10 VvX+Òu!Sgɳ/fSp/?bO\Z-&A O:6!D#`*aڮVZT㢾fAJpu1P8 2qy!"2k$623S ȕ+ǼÌ㊨ݻw !9# 9'ST`r#4V|/bʺY*a(iǯc!܎ B-6뛲آu_B\;DyVR):Weh{L99xns!/{8t[>TzQL2\֖')NGtw @BeΤ@ BmeTƭ|^%R{ CӦsEW wӢ\2UN]EY+ F)JܚSֺ~Wwo&ft6i)2Tr|k]\;{_?d͌k <5J:ԍovO-/Ǒ^]{+:"c?츳‰A.6՟1Es8F[cЏ|Ͳa"گZw`` c? %Dh4#)і:D3Lb!&bb d2ƙ]LHI 1ln1tTX LJĔU>Iا^;maf*KәghIɥ*_`.bK7 .(E19MAP ukl?vZ2gZ&-0s?0&r a΋mQ@rf;la\qr*]T !%yf}qf]IqaZ٢ `A)Oa­$=Qr ]S=צ>E<\ZM76U7iY=XcIwԮ+N2oX[5>K 1Wv"ogʔ'5jJ6*#D {]N.zOn[d5 4}:z%"fM~S$&i8AbPc9EyS;7r%V˅wܝ2R d'&K/\)!Ro6Z纖*?kd|7^z\oΣ:F36J(ٿ~ ?#Go^}Kpx{7W{?_t`=FX}3\|Ep=03Xl~ =*  $s( / ~y7܆mf6mF 4 _nw\zK_ڲƿ '_0}>wD?n_Cmc8^H._kw_ 79ȷ`a$hu~~C6`E9JW Hxݶyp~{s(=ya(fP`!͚~k l.~ʇ@ ke •`0'vL@|dwհ.8xnaYI(f Hx'|G|_I 8+?m;li8i`pk0?"[p#|?x1,lȬd35ZPBdfJQafPM*h* 9b&D2(U⒨beqJftH0D͂mVa)!FW@ ѲsxÌ5mް!3TXfΖ&V@ L)XB\,ryVrSœ̠Tf&XG_W;PWJt\QGyL,`r{"[jVW5I~@=SNV/"㤇GD8(`_ ejrnaQۧ>8.;嶁ïtLP~y9>rVo/`(u`r? 'cϷ}]}J8ʹd87 W8Ǘ^?%2N@my‚S|e828?o C`|'lhI9XD(wa_q gD̷mNcw~ʩm;=j󔟳?xCƷYNvxC}wm6o6?߾pG`ݵC ?ZmOӲ:WۧrѻSͱ[f(kNKÈD}-KV7^WPQ&D 1#-Y" @.f$Y`mJJB0i3%HjP"K2T"lYƈ'=Ʌֻ-`}A+ V4~ b7ST= m{gd}}·pb ^P`o\zbfmכo~ G&ۿ_s}=gD\>F3=ydi^{~|)(k vMl!}b{jvd*,-dŭ=*-i2[fUYf͋V)Ie@Es (lRBS$ X1.FUi043@ %P3TP4Sr~Uǵ0k[`X|@LbWB ^ CcSOXxd5ʭ~2oDgҀb`] 9ZU41ĦI")\ #V'\Yq5s.sׯs*8{vo[7yC੔jUEshE}B1E\^wپep5OcۋHkUv(m ;oɒB/OZN., }bU4YdXkZ"~khr,?Zv}Q5ێvY,pղvǜC*|p6Tޓ?)ôd]b'ߔXݵZt@JM/|S. mo_";{G0Y8 kKj$)m㚡||L0ʋ_fK˷LnSSp6΋"ivTߝ αk0,jR\M[.9?e4-gl?Dza Jh^*LvRJ|E#Ƭ>1EI%e&3Q-Gea*E$Q)Fћ44 Bh o#XY\5i%wZ]Rg*] 0Ji6y-ڀVe^elꫪR;T!.lyPi֪l;5#(4薒JB I-xԌR/C@L!h"a\I^judl@Japt8$[1\G%3Iv|R+`s~o< nݻ55+>Nrjg%ΞF:y]G}ݢtاEzBͳnDwυ"f*նߋN;v ;xOc?^W~Wxgo/i"~k3Μ< [zʅDprj@`2+X@ΦV|PS_Ƕo*:J4owYЃ_/vZO] M=%wăշAiZz ߰3~_JU3,ʄ)#*Д좓-> <2"3BF%SJE`Ve ۽e0! ,\<",DrЍ) hV1J[tՅ][K/(OY Jg1pa9BZ@խU]jO}fSPf yYJ!DJbڊ{G e(%\Uȋ@рĀDU=G̫s܂]^i@oCb)5ӛ<0;pvqgxg^1wOrqqf W %RZ., 1(A oEeɣthI@frUX ZaKB??=hHɏQwv^\+RlsTv lYzN$x>~U4չ^lPZ^W%Jۧ֠疿Rv?Y`KS8SVp7PF4ǵP]A~WE,)봆w q-3fWy9S)ٔ5lUT[;+SPNҝ{tM%hvb="ovBoS~ǫ@09(r흄M&p'{۟SM2[G59۳fK՟}MĿ 9mI!SdAC~*^ReYvbVtxX+J[,R%*S+ju$=Sʴs%ۯ"9 ȥ TAB{Tʒ-fjwv`a<Ѧ M]P[c;mLU2*z]vcN v`Юڬly5(Cl61MvQ?Ig'k ;C58<Sh8FdЩiӚhKoI E]R҇[c?^j/@ŷ|3 WnV;Y}l_Qg~iyrlO Y|w0\sW@zw$x)+k awB8>cʘq*'^ @;x#{prWΉ]|W uL0L: @Kmt' M,EyAQkyh@'Y ï5X˳䯚h$l~߁u0`xZ}śf.Y9zp|}Υo+?`-Cz@M27ؼOl_c'Zy9zُ/1|,y;*뭒7) !)-K{%t21!" [DޔGCv1@ 5(2z&1@)K#ID/w+|[Pm.lY?%_HV5d-bD$,OSYٱV~ǫ@}5 0B,ǯAk6Wlg/0~rH$ts|iݜFkʢ|0;m%)<8 Mo%oš}v^:cpS/9z|x@~7hj)klO|.~ 6{m[5`9F5lC뿅K3[* wN8zG|"b/mkx+w ;@m \֠oKa;.'??  m_^z2}0(6W7`i:Nkb _ۜ|߶M0?lSΏL|No]zW3?NKjka~탯sg}~~|)^%03|^.QƢQ kgꌒA&$DKPtB%C H4R$ L) Bz/Po~Ifi#PUZկ]eЁl jGqI!e^ٞkOUP  anHՂbkj-}]etfk+^%b "39Fr̓geMs"u,DL,Fak`l( C.:r@! "y≧ra8~ _k㩶60#bډÔ@UeEis]dACek7'PRAY5Iyh:EJ6ux\O<ֿk}a9.ٵ0:H+aTKQ \ k\ g{ $Oв {sOdrxx>U/a]ENyaΟ6!t}6+ ^HDϞ2 ˫yM/ Oaڿ_pU-ֆ]vѮx3]cng?8pm1ku3.,`Biwr G9ɯ`kssk>Wu;,i*[_:KC8VxbKBYЏ8]{"}y>ɏ|7gy=f.B0,i(jՁLEg~.k[rζϵqΖ ״k7wTJ؏WPw[w^6Ϣ^|Z8Z:(Bhsvj{ePG;dJUT-\'S7 Rky6Ml͚ R(9,V( ҬŕhϗP+ɮ~⦾w~@~O6Llau&XOBmP_b[ܭ  ,wL<*р v+JDR2WW{MffrGϐB n7,څu7VB "JE)122 +V n]s]b^;wspbl zC3*GW[W0ms[˖6` H9t&̝Fv@Gu+kQ@1FW$VXo1;P\܀r:eU[bq4D2DiSL9J(PB v/RxͤVݱ4:TPYeyQO:1RMU5^m[k6Em߾O>6%RZyCq vs=,Bi3.~ZCsfyo *~@~ը =؏W8[g>gۉwlN/8$sc%\͖*fgJ:l¸V+au)13-D $e84@"[ @Za#ׯ{>->j΍6T*0R(춾m>bMaN@ D,!0瑢)g撑*:910>J`DHbӈԯYZJ@I nw EUy4Q R2*0#LbBR(ed`C 9XVM]^ܨE-[Pqp yIA )x$_0W8r@Jp![7y\ snc^͊cii٪LJ7*BF;W!@v Ngz5yOҘ8-3֠G*TP)`g"b r5Xǁlz3k_UٴRg!MY"ې!X [i*\ oصk]T|Ts !T & :[ [x5_XkVt'$w@MY^Z-K4صX"tM; 9,ͅz ?kbj b[Y$'-SFUag-vGIvpGuyyK@BٖKJKiyԛgw~~~%i?c?^uEO9]>2g"Ud]8ܽP8HU,'jBr`$BUn`N,m.6ia~ROǮ ǀ>F.f7Wh Y h/!(Lh)ʨFSklժq@̮T]v~9{ `VfS$45`Wc?H5~~ßBN^ iq7>ۛX9~={Ο2qhy[3%׽,v՛Xx|Ai+7?%>9 u ~>#9؝/poh~Ǘ3]̶m5ϙR8GCx%ZrRHm62pai L1Ձ8 2SFIhD&\;)VSfUxxG/6n8Kn[앝F-UȳŅ 8!PRaJ㌐0Oc 12*RJD-\)Q ~ )2*"$0e*Y-}Jd (@JqUy{ˊv.! p1%i{gU Ғkv ~)!2l5BnH˞-' bǾ(AY 5YaiSaru)nZ?/9's?_ȸWneg}c̹眾Jjn$tAP0`cHR$<8T!NT*/Hű lbFBnu9g_֚c|_o9ӭHFתg֚o/B$/JF MP9)ow"W9> 9 W? c U|M],n_˟'ܟ[? z w7P^ EBW}_w~.4 Z>.'΍(\]1Ƥl211 NP+ܪͦ@," m4)+ئ8ΫPYӵD6*8xbKа9Ph [׊+! KR2P_TkЧxkͻ&%AJr8!s+PTȘU4<|%{ aHIh˜FX 8]4i׹[ʱ@fi@}29]Nzh޽vp[Ϟijɧpwv@[c7 yQ|ZUh.PN1~cin6NL\jR$Dn)F%,xaV@Px@~ϓ{  P4Sl8l)lk֢1Haϧfu4`y|~ϐkm;W ;kdYJ@4vnm稵h,AhRh6Jv)lqUN灊?`-^?TPt[=k,%*S>ۧɛ`SGfU<7{[L@;|  )[/O4N% }{ #ofwRFMȫx))^A_긄@,_@͢w9P==L t v ׂC4/syp;`=P^MpC5|7/͐ϸY|?/z5lſ\4؋P _  m>`&.UzD>DMwR7`I1+K͔]pc'axvi *RAxSVzݪ.~5AYY|e$aRd[ o1tG9&;=\fȮ'JiTB.Pm,nM,@̊l\d*,U)&FB2&T)1bĘjlR',6TG`O5%4lhP!`6@:3ޥ,8w2Ehdsȭ[76;;~6v`;0lO8=SW8aVAy#խnOTW[ Hg,7y : .0 /,qc5O4Z]GX=?jo 3M) $woTIQŘؑ&y x uNdMhڪr4/zGjNͮ+5! , ~T!5@UWjUf7oqyy+HrPF^vLW7v3sCyߎn*KHvV<KEGe05̓IsV;Ia6U j"Pm!*%0RҜ+㜵Ewnտ:SQi+V$Ƒuҳx /(#E,`[;xjS 8+>`ފ'Z WŚ 23)l;%EVq U6ǤYL\,}J=8hMųrZ+6[V3%rky|)CkGnk%Qף[}ޥ Sa Σ[oJUN= CK_mխT4N vWWWϕ)H>hG׿k#=ĭ{ 3e?~䟾w| w@yU#v1Yh܉kg]??̐ݰ{'F¡ůzQ_?ς}a|+y1}psKpw05 oavOByI~/ݟ`oC\y5z?7n~+-o n]M.5pٟz 欬yξlɷ> ?/O|7{a͐oŗgꃰ?_L0%ؽ ʣh忆?ٖ vΕHi:rxoAyY`<#~2>ocSFW>ڿN'P=!&*B"9\|5P1\C+a'' K tN<1qjMH}2خlVH%!go++'tv7`Bέ/!󱡸TOXv:[,_LC(#j^#n;%>P[ MQ\Ys\oKFG`:B(WE5՚ s7'νZ[,¦'lY:gfZq=}tܷ>mu$aslZBWfbZ&J7JHVJb>IG~W~ tk_v8d6દZjFq}"kC3~g4͘4rj8t v[_eP+gZ=&Uظ7rpim]ϰ:xI/>mE|^)5L}StAQn=)wO yI-vA} ݦ/84Os7Bn߆+7)v>U?Wv+Tԧuړ9LGXM2V!&|EJ( IH/-P`< zٟ;s7$YWVˠvg9>5Zz'?۹=k**k`|u'Anxg? ywShOU5p/ 28: 7?z77,ozjxsG}yàW+ʃpo]N4b._=%VOrBs Wo/rPZ՘\Lnf7_2LOJ;^P U ]V0ń\l N()|JIH=/K /{cP-74TPI_ZAQ}EpS.*q)J 4:%qBSBj2 Y\uV -Q UjH) SB<,{<_[!6&$ITeL :M8;RҖj3wr8\q~qe e];t2~z oBiQ rVDHLsNqORL[ݘr E{ѦԀgQ+`{ EѱnY57^"]@|]|4N4N6bA+78<o> y~=x߇8O9ȏٻa 0| osSϡɳ86|\>jQW,79hσZ@߁`JyW~=1ҷzl;|W;g[}>ؼ}fpG\9cYME[!Z 75 ׹2}_Cncw- ;?l )pvʫ߅_qލ:{VnO{ׁ~{#}ot\Fdɼ<ޫʧpaePUe7oqy>Hlxpw/]vcsp=t˥݅jnmp$x}an=?\ -(O[Ͻ-C"3QJΔP8:)瑣hRLTZ4++RJaToc!c=BSbd-v6," Ca3ld6Sk&%e{֪,RޗR(%oѦPjB)etيٮTW[B IDAT})#T $έ~d &9}ȟiԗbO|?7S;pCl,Vw(dtP<A͔Z#l=Y ˝rL?slN @*]WP&ʞ5fNͪ'&2]?HdH%32vTbg$hjQW J|CM42#a6f_[FFFul0 35uI6L~*9ybr%H:)a]U֠4NSFk!v39Ure|wUwaT+%c_0 !i12cM;?8rq7migUG6#L6 K|^ kNvtTL~ՀT_%6n|g[jj}] p3uʑڋ&G9߅l˒}U^[}[A{/d܁/lYVƺ=w>s7B9|~7wxi#%ܖ<[Bmd6Bx_O )zMX4+K2βRD>j@)&DxYuKZrꤹߛYumVi=aJ"v"2Bs-KhD=yRa %ArM!PH97j?<ZIMy9c%C ۥ*Cv6!w MIi ws~{ jpc7P h=۔ti) *$t5S*`?/̐h5_rƳ2]&66ҡ!R@AbEh'\ scmomku Gw-"VnêkVUZ ;׾Jd(%JY)ȘK2*5 _ x ! @A ޷AؼBms"2|Z=r`9]b8<%[0+gFn[\)11_m~\Ev]WPm[lz=M|K^XQs܆?gӭu?q^26gXs_-:/ji߫fdAղi=ⶰ鵐/pj;<|-0K :K6n%@2WA&-­͛}]McgvPvvmq[ HZu;^/p]gLi|u2(;>uPN,udSI>P#4Wܘ6o ka*Z[_ܧn;ji>iljDhs)vM<%-,D'WW(%|\!'\-O-&5$%Lb6 92TքJi E% RI{|kFQc"ЩyZh_sa=[IT#.FqنsqqyšqB 8ĐLR$Y;jn v$b5Mɏ=W@FJ$QmΞ47?䊺8T)iZ$!ϡᔌThUMTà$.3闳B?\ue:`!-MNM||q'% y譤WkHHz%F4߁iK19H7! syJ}ҡR~ c`>_c0}E7ĮvC7m7 Qؼ/@`+pu~J'C[v8{x_gegXm^?spUvhv_C{dٕ=nUkJ,!mqS߆kwoym.~ ڇp_p8(+ o: <|OCCUp}9#\-ҭuhxzO4҇Z QX%7(`g|3I +ZWM%93_\[mV nk+g)A̠y˒X4>&&ٕZFop{Lt9Ya'VO0>h3oKۢjΗ;ʼF][$!YLSLhY35Ky+CsbT<ٰC<}&3pmpv,<7j+>vg #1 Ųj \Yn[㤕2Ut(rri`p4#M(zMj%48i´2Rr PpʥV6)32f.1LlFKB*ɉlLV:\uK:,K2ΑR(~r׻po>pxۅ6h SmjP =14mŕJT?IЫgO|g qs<4, T~U-Wa?Oކ\ּ18> QW?_keN= =Mx S?[8aqјs 7%p M`#vȟrE[ꓮO.FQ^>O忆=B7h{CW ?w;}DzaYX6{z׷ы>חas+ѲAxhof;y:׎wݖ9ޛhmCuԞ -q[bǼz8nox6esWmiZF,܈*^gJgRoQ/ Mcfs4O C\ v}-?V;/-l-PڢX"d*%Zʝ)MbSdjɕ9& >l2lޘ",#+K`ZIxu%PV g,%L*HBP`^]9D;<.scj_}眫/m:MTvnh&:H9TL<ɫI~?&Y ˞ۄm JIWuSZCrpE*D>(zU 91r8{`+|EL +DC[ʐJ'ܒ^u5 "?k&9!ip;aO6?SV4ue6~^=*.uV!j 5_Z^r)ƜXm1[2x^ ,сr/; cl.kŒQ=K=XJqu<[,W)m9`raU]iʪ4ΏPkP h3d)QJLk rZ-;+#bG\挨ߚTTɀ2̰Z4N-w ~ʭ{ܿ-K!d~=( '~}s$ յwֶ`Cwv 8m=+ז-f5#z ~ V>*:8D,wZ07>39D_.nٸ$&U7;?1?}[>|_}͘y^5(Նg5z}n` lv`P6alfd6Ɯ<͙2޼dASX6ZkX'(iSLSɡOD7ہn0 lv#0 BF$ ו 65l?a7&leSDZ-E%AŬz<ny S44ӡZkP9'-BT%EMa2.'P7$Td;)䱐M?|ܳN4N%7r`zuZz ۧ/9Z~&u}\}_!oȻu>Wx`6CoyXϷ}^u\| 9V_ȶ|>)-iW܈;j.F}o_듶"%eȔ?n f`܎l68M|)EjTP6]rTomVC>h,+,o51M5`DHrsAL<[<,KMITWJelڹΡH!dzg.XT%}?",9WYRs!:zf  u5ɬPo(M3~L)d%c2fqITBiLeȰ &8 $rm=$vuZ/+5r-Jyg=nAR ' RRa ZbjF62Hb;fHHʴݖiP+LN(R2 znY4w "ȘK0K6u\y/"6Db(kM @$5 y,6t,[]O8HZ\JB.n됺g@42+$3Zv+5mVB.J K ~5,Y51Ϝ-c)q, e0FU~Ky.CL+oցWJ5Zs&l-VRp[)il^SͿA9"sV~{ _AS+K˝zS"-EQ6O*%ƁF4&~^7(ZoUi ʐHR +D(43^dm%KޏbY =[J*rKY0=;ZbgUJ0Juu`(;'[1G[yzCk+Q6џ`3< N4N4N+fT`prXl`qq_Jz+cp+Y*9*?d*c<&rVJW29YgD dLSPDRr׃}M(f '9sn5#0khJ{̳D.5L&%VC="[k[R濛D$Q,-Un9jUF)񐹱!-ǭʅRj t aSW̤5e!If8(JPK CS) XM&7UҰ{% C0d̀[R49Ȕ%~ 㔰CFz"w*(J&¦ ܸCKܱ:y԰V2֩&jlD-R2f$ɔ:!aSC'H-Ô %1eRm筵4hRE%hR W k 0Do a>v;{a0h^)9ځu sA Xf<>%;| O ?;/i! TːF9%(AYw$3_ h-t*ODHeBBI)uЬ&2j-3C2asJU\tPyp P<3vɋ i[ KxiiiW8ii|u G~W^$+B^YչIfOØꦡkKTNހׁT|jNTOJkʝ47ƺɔ q~yh]ֿhL' +`Pj(,ּySVoRHY]-2==lITYdr971c}NlDy bI)e i&,1J4FUh[7ʹgD 5&*$Q,itqɡ5ymٔ)W . 2vܰfVsu hiZh˧CFm&_n[X^MF8(z1ag>=۰v **`Q2`'ӔPkXr%dP%؎ m#cf ÀQ&tcS-ME)CUt_iɛ)&J-XhRv..yn LIjBJ" :ĉH e~qZ=P2R|?VЄR{ruq rNшt[o񲲎ť12N}62CȒȧ$䜤5o^$</̳KeyP.v@ f Fi"zKFDe$,4K2’EvRȲDSrL5&ܬ m̃SAP$`V@,"hGZcf LL2aí7؎⠅ )1FɉMEUFHWR';Rԋ5f4mkx!͐¥9 wxPrTXʈVbsqT-2< %'JpP]Un߹ Yhcr%L+i_-!]+JK%"X::vuKv)SJǬDLA0A,AqMԚ9䆤'sUPKx%[BNilV,=z0,B=n-ʋ*gW[W z fmsH{a 3E=Yx)ɔ b&L4SM={{aS\ؔH jmLyyXnZu֬H*!gʶdD9{-w/}Y,`TZ5hߟ"۪UQ\(Vi @}C`rWðCO!\>j7"<=-!ye/u[8 REz _O oiO}ts@/p } ܗq q NwOï7A+;ˑca?=iSnd3((%-s '!kTmLqab:x jǘ+ƒ̕R懲`"H NjFR1my9ieHdխUإ4RySio[\JIP;q[Hj$ (SuMY]M#'&ָ۫!uNYUY۱p*:L4m}(aQҰ JuED3![ LM`,h\U&IKнەPcDÈICkRTf5*)!4qm1n̜n?rcwi?9lԿǐɌ"I}ٷ&ħ]Ԃv~XNBG؏!qHiV|zyj\=˄8oZ=clwRGWluXy#0[dRrdfW|8+ Zfh<ރխzpާd5JSU5Yf)W&&>iV=՝ͯ Ûe(l-8[EaLa'Ifh?EIs!\OU='k\W@qbDgqqP_H[7(7(fdy޷ޙUu4@@@ A)Nzl%EOCAzP_A=A88)}sNUeʬ:o iv%['kWNUK@yDcV?p/w-L'"/O_PK? 읰LZn2 3C?\v`qDNJ#[ J0D-/uPmH,KX@5 f,7?5Um15&! ό$UL'`jK D|6rSr(pLp/g\,9lxsN#]strM`]Mrf֐f:͂YhKO?,41MX*n4$\͌B5R)]sWSm[)w"f1-*4M'Eh955PJ񯚶E{A$&rʤ( V-qWCejm .]3e\(쉄lV1Treq}/fW`]H,KkťV _Y3wdmpzX…()wx۰c>K<9|7ћ<dCpK7ɒ W(_Ͽv:tt ,Q" n^jkVě+$lH67ݱ03h,h8&f@n|r^bdt߅ N9f@c57ƓAv(27`yxq!Ȓ͙PF!8Ys^lAwt;y]Nrm)6s-*ߙawMđəႝDQMpdzfdžY( "tM|ӹkBw1چ/ncSRt%sR=]aY u9}ʭ6#ki]OEmt+,s~KByK/FE'2B7fuCnW䬒F>^)Bd>/" m I"tEŢ#B 0991pBfk.v% *PXxٱDEUg[Jh֙ BEJrkъ yZ|{!>Ypu@DDKP'^X3&xRHmR/<}{l6?Arf 9QL,"˒{jH$4?0%%] 貹F1W(Dt5r6tSi,N'稈wh/|cمڴAJxWqk 0N&ovAso85 %Fm?;bf4Ӌa9, @=e#ǍmHi ;{eqmK]5C36M+`yPygbS}}gg0N*ƹu1N}7l> ?Ӭoo#? ~)(ƵadSc{[c]U.4dl؆UűrbV5o_fuI iZn^Y. ] N'kwV?oa4IECU^'T5Z|)[)%RV쮶,S$ 2iHs5ʮRjT#^N)ei6nOkjs< ]^ڵl2B \R󫄲jࡩA:?ߘw3w뙤 b ˙,B_:#zAS% dSs iv6R*QFD[ Y-v]vri283Ԝ-:RVKL)}͙{Q}Mh~[qMPf{/3KB@7O&{,B|>6J9==,digG#o'}]_3>x3ep? V?˗9= ݰv>#Jx•s'ϡ{n܌ g?8 V?,P8M1^cö_fmxJϕoz{( how9|_nAqdv=S ~=_ō-y۽`fc X /"LzK3Aut/]ē%'|_6`u e׋7>ΪzϺXv7=5|'z : glK?߂~Bz5~tN9rXUTwd8H{PwS}ݕRMB7ߕD-$ϱ87yn+02#uqmc>Ydn\B+i7H<1(%Z2`M=3nLb9Ih`=*X"V6GVb-y[]RH7f&ݯ;WTK3,٦ nc-'J6Z.V4$X x-' PYjI7m]?㫙.5Y-0-Ye.f + POuPJ˅N2Dj{BF *.],8ylXjjhQmoE@tDDq6T)b{\gLaFٜ9*}[tHZѲevq*%z5T3㠰`N%) mVa4aUjmf-k&bOȒl{lYSAvظfE҈Sof*iQ]gv*, ,>;y\ F:sU StMIDv;) ylLwkTəYt%FJSp$V0Xɵbce8hg]+B&T+ +is\qhteU% +'X6T$m\?׋Z[r\~=;c?MDJͺЂ7p杘8ʮˑ$k{c:捛|cn̵_Т)rwH/!X<ꓟ~r!^VA"Ś7@}%X'{-\$k|.}#8g.Z;YqB"~؁x/^ >ݧp!썳{GLǙmOy\iOL+m(.sP 5_@y\z%hv.-Ӭb"%c.cBսc>}a[:e'>Y3p .(Jd|1|ң7| P9=/]1iWO~<|H=+? X,o bEHf$R)35-\]N9SrLSyȩY4f)cơ2nG2 O-2)i7Y`nMbRZEM}\.=9sJeYHɹХ,QTLӊ+i3ƛ%Abcc;4XOP"sŦce@i53ZdvɛF ˲dV]Tm0(UT݋IXShfIPU-?R|9j.xŗ\9Ϡף|AD4Ukc z*46*3O0X"D_FL?M;ώ'ΔNnh4MBovcрg)(h(~X]"T*Nqɭ| IDAT"Hk#zUYn [5ZR/hwNHK#OI,1u%kTJZ| =kO썹 K<ɷszynEOgwl}t`Oχu7,cټj'nX޷{.W:0n9\ ]{|^MX[WtthH@ @?mv.~ux zWwٿs!~^<^w_cЁWz xecթ\rf?)s3wB|,'yrX7[5=AeDegu;sG` -'Wc,_碈tV=:zA-rzƏ}foՓ%>}> ÿI}TC QU^bJ:׿.|?p|Ì0&8ԏ{|N=.PQ ,p>?϶Xf#Vzbe7r?Ws`Bf0 ''璥Gtp?g'п8bi/ ~0?o{}׼uul c5s/t[<Voq_+]t{w` ̓-G7m{,]:$]'}!`.ϱT Re/ryk&ncsj[|:4yH1yL3#hj ]_Ps;IJ.j Y#dzuk"sr_0@Cs Aٰf3v~+sT:7MU\2ܰ5WqibkI1cJڇ<[pGRY)/tΦ P]} - d,WEGqܲ'-ml.c̎**]|H 4yEH&/B ȹ`鬝""0E5 dϘ&<`x[= mhlRC.EB#65[C!wU/EZRucUFUVh.@-ЗfTFFM[ͼysy45S?ceK[K @YPEUU:6:(]ߨci:S;aâ ҃\h36*)P`) {Ir]ū4݋d 9u8DbI sJ'E(QGf CGڦ6Td[av[4N*ѠtՙI- ڻ4wVo-u iepYzpZ45כd;~IϙHEنFCnbwN+]/fZm< %qwOOrϵ{9tu ]cu(^9, @}St}{pmgH>!ĸQ`\O"Ve!lh$0~ a-]%n8pnƹߏq..3uG?[\-XSn?c ز>O.k.us9 Z8My**bkpatfT}?펻FHug1=.vܤ~?­ml^|_:wlM/ 5e6wp, wc|Ɏcc1e5Dyv \ q_=n=t.@M\BIb3fIz"{<祼kD áuݍucނ-^.(QrqO)EInp} ꎖԚ ` 2r>b"dk NxVŹ?1jdBwαOvm\wanjB8nO6whqhїL4EEH]!CV"tGˎe mcm 1}W˜fǤl[ ]. W/DGt$kH,JO -ǝ gEfZX$bNhH[|7). Ce)d$UJo:LZe bt)ș?DNLmJk1+3Z1Pm3k9((S`څP<1n/5\RjGXf% }6JnJ4hT[P_R4~6 v7‚SDyFQo gaYX~a͆avՊaE1.xXa9P_js8[ <؝OwQW~:&=bT 켛}4;-vq97}Zo 18,<t)yqNsމhv~}[\$m3bz7å?> whv~Tϟg :}5Km#Y O/U糧:h|S.?_)SӯF=)?s-UXtg뺧 Pd \عm-&&ܐ4EBYH)۩G2sdA9M\6M;MҦ8E3[*6uydЅ t >[#'#OxK#˳R"LfhlF3؍M;0gTM"Ir\Z"C/Ć-}1$+%yn; T YZ4bn UW2Rnl m6J[Ķ VJݑ% YatkEVy;dJ:*Clh]}3ZYi;s3_r7YtqOKMS4E|8qٹ ʦ JO#[m;6[d=`ہaKGZj{;35`.`]ԝs,Ћ2 *3A5XUEU[l>I޶9WknWA纫.vkft|GL._ň{f?9jǁ'nla9, @};`8{ow4D ^N0݂d ׯ S @}c}5n7~_vLWpӌSdEәwXyۃ}qT&t_g1WX𳭻TU>!ȯ悑,AAᒯm?gg}XkrT; t7o9]^?֣ά1^(tn::ί/n7`]۟|G:±$dy]|H=+$mx] p䙹]B ŅMcwaY |!D-OFn ~jUwɓ;<҅>Oe9jHIDK %@fKf8Zt,JЪ C/&|1 *mŜ)H1o5ƍϔ<(EE HȪ,:w,DѰ"M$u8O%0ۭL(V,s\Ԡ1hS ʤ# >pڶUg8)ٌd.U 1 wC%Kb(5!jE5"Ԥ&"9޲ieXo%@BDgw<=5%M3"f,n'Qw(|S,&}Ϡ7?~ݰ}򰥟Ҿ. t?]P^-]|sjh L}pX竰y3q5aF|-/pnlg t?@ʧŪtչQ.Vv/A"X!||y>t%7A?BK ;|vFWsmG([O(R)oꂒ݆bδ~HډKwlc@y#{CHp}ЭxmFo? b{?e<(?=}puKK+?5o[~o7}}cok\/\:.?~DZg-~Ə{̬3pX) ax{޸@7~`(;,Ϛ%%]=EO.%:ȸ49(eAJX>I8JihTr%?.!-/S<V}X>zG?ٸE?{QӜKޓvE?~ؼ'~ !|ְWuA -]dE7g{}}:ӲO8\QM[ܣaPo׿cn~'\p18qG`п$i9 i;IGa|'}_ ]2C>w|2߅kZwooxZws{wOxYy\{ sT 0|ǔ~]~.N;]pp.{A ؼVۻ)}y64IqJ֯ޒw1Pq2l"w>i0B|d}.&ד9tn)1m%1A-`U5ж!HBF=wndLm|νQxLR31$j%3x\]fq5O](*smgPOqä=ݝN9 pU`˧/@iYFֱ81Ek<еZH4okVRL 2yPШG5&Qf{yͺMKb!68}tEH]BhAXbEN7.^T(d)RPD5}?X=KIk.CuAsV7O>VUݡ2Y]PP|/ g i ZNENJp)P}UlN.Xg̏F!?U#3lyk,).h}]fy xy"M ڀɼk |:xiO|._~ w_2awήXe7[縐xu{`}7/$i7| o g\{>36~\s8nPwv_oryA8McXZ~Nҵ\{04:\(w94|E\=j^ף0\s /&Xgےr[u=/% mKҽiv>NLz7hUN0|4㿹D,/OKcqozw3 RkjRs”G^spN_NSj6V9IC V{>p\+SI4qO651/oR7Y\ 0 L˒9;S&ED1 |2xPݵqi3Ҩ!g gloF!t}+Z%5ѯ]~\ʸhk.r̭^. ֦ BBW.7 97RN#/{_fSG2sc{jklOo3l[ o+bB UXs]64ܠfiێ֚3d[=@xF [SZq(x9 UerN,=lOO&j]S<5G,H19A:HgJs 3Dgɼs0`w ߵAa w:2G 6"uR(+5QAU&``4SVc;4Aꅌ/6 5[w &JτR|ס[:&fXXLe׸Ag{s7q6!w/裟y -03>ư 5A:,|uZfYa9,O<)ϸuawa-/<,yOypw`a* /x3?" W%KyK??\|7|}u>v.Xt4p5]vvp:MTl_љ4 NWNtrT.h=TSe햱i!Bt8y$YӉ)m&EM,-y*²P:!ulB[S>`: `!LQ>#&n!ViPݚσ[Sj S*?D l Yi` ѧ>>)|.x1w:f5O2l=!pimiǴMΌa[#m!mnTV-їbU8:)}"!Qt#q5#ΏΛZG6bO.;DDrI,X*l7z˰zeY`Y%uT4 lו_ɭgN+l+[S1(rFnjVV6Oإ Ogus|styɥ+//8>^q|`uԳ\v,2L޶暍R l-'7lXg'[֧ar˽rsI8MJ9:^ZJNhŹq;P<_C$Edv~-k#D1s6%wh.*+ ݒklM;ݲ99e}rprF[!l(JTzEfllζzSu" h4к3"SB'.A/Xi@.X!/u7_t[/:;1؃ok.6fQoDp3ڈ gH%' v ضNNIn;N٫u1/ b`{i#';;#6rP0fL hJ9I3o~l$1JN,JfUjlFw헺v`Vmn:Z G D$^3z}tI(06P2,QQ#S{T5oߪ1vH*;GMZ%R[bFV66Dw2"zz3$t,ZwZTq-pyOJΈ{pYȮC~k$L9;,"Prs(Hl*'-ܕffԎ$;pM9.M9tlɉ&;L*kxnoI[1]NTjN+Y[1Jʆu⸱<6RTRZ*F7vfh,1-x,BMᗌFnI?f4P&2D\E9±Y4$mx!G\ GGG7c08䶚 {usт\luXayc0 m{ =߅}Blu~ehkxxXߔl3npU !sX&6L:OdD*j-Z&1&"%SM{R]CQe_+Mө!@ :BorišωRSR &) !ɮܱMa@C )re".2s5}"/7K ]fNdĞJω>(.H5ug W;(:0gBν1-",H,nȒ)t-}ETimml>% dei855ZjlѰ5bXme;Z "\:5F85e:i)e.4ITh!S !/:e6{/IJjTCHK7E+i>3!Ms . IF<*JRaądJ2B)HW,a*tM耂7&t1VQhjuEP(^.s# 5 䓣uv1,BM͢ז}XOMACuޛYqfg޿̼UT#B $$ݻ31b&<m;Dxf=Vj6Zj $hv$ *}{f'oֽTVQAq+=rsi9ɹeT%Ssj n} 1m&ʺNvjb{4ɱPv*L*w!wr$TYE&cl,GlMAtmEv*^YiXe ` DD3t J^,}#I3d3Rˇ@H"I4lr+d-l (leٖ-qðt.aJp KLD b @PG (%ɚuXvSeHeMmu4$65Hؾ,ie6)[l2iۨad4Y 3K< H[QLP:P:M2%+XH Ӑ䶲ݪ)p4mDPYعլr*Qf,TO $\TlYSYl&K:%9$Ǜrһ鮲Q7Rְ ]LQ #m5dN-,G+@$!e \(Ot I2\7C3$5vW,[Xu9y9nYS`34VWH ] =x 6#ݐ$2wsH1˴VCֈmS)vm 烜sC/qCrayyUyǔ-#ܿ>tTFO}DQiW\]Q`qZrJ.4򒌏)=`_* We:4Nr*m*ڦtCI"rGVUBY9.e9DHSLר֜yؙ6qL801o;Y`&PSy7r\W-{P:苁\ؗA 5p>n`}vQ)r1N۫Ş̇OxZE^;/{f䳚 E2P5`wՠ7ڳxG̈́] #NP>d;@dΈ7!0 Fާжેې) B$~\7W ߽뮣&kmH^1Vo,0ʯѧ0h%@j a<|+J؝!`2 \z|b'\rg2M 3I밙kgU^;:;d`͈}fAvRdJ-r/nRKysJTH՘f(Ƥ[N[3^|Ȟ,TvlY3wCM(]fȱZi jb? *?@.2)scW$L\G#8}|r _ﹹd45v V&ר59̟Y?c3>g+׬`Ī(sP/K:+o4})6Ah19f }WS)[h 7Q&י=*%N8ħGe9vz!SHԋ9 2H䄶"?SWqHN'U?BN͕v|* W(FnuBN;oM \u~ֹz9]`u:\H͆w$q%^#JP7m[!yC0`TB_YC~"U[+SG oPجMfQ ^"D5_C:! |Be: M/ΛKMl R\ʅ;># T+h!t7DnA'8z D!0u\ğBd'k^k qD6Dy5>|[eBKO_zT殯g;d{ξd.饲Gd/|5C0_Rh!Bd/Q F!Wq=ze_ !T 'j0x;հoPtNߓzC+o1tN@h1#dQ-@t8B-hFuFq4 s>y.Q%BX%+m~!= =DUQ!+BEL 4)QBl+ .m*%_\o8j|w@cB2Z!?5lk| [۝uՒ"Z?Le]{;Q*D/EwY{43{T撦K^z )MП:g=Pt WȼVǟ~ǟJ֝Q]:n}Y#Dc]梿 >5{EEnj8:َIt6Usjn"G+`/dCt#A`.:Nu栐Y|0 'ZĥTV BN@bzq_` oV sH0s/c(H2WK;ymB,^1ǿzgėieBBjiKN?Fg`c,h^-s&{̟5h~-KBh DWAh-$DK&VIh(z\qJrB=y T'Xt 鐮5~̶Cf)%!8|Ur]IT"t?\kv@4dEy=9;aBp/~pmR)!v=]&o F(y2 /BQ0*L;؜1n0 3\;l!=}̗e,2!\kc]v⯀=(UF/fP\t c!!X}`-'9+ɑ"WS I0M@Mr6s |i|B Պ7Hx\椐2ƛ agܓ2=`/>T IDAT(JzT?=%j>Q?0jRrvLJI²6 EPY_h/Rl%-$4C@e$R0ɷyU`TI(QrvMn-,}W}?(䀯.=*dQ,y{ːD,ȞQUqH>('K~H0מ`k0EVrTzI9m r׿=,81ockCU$ O._UTdx%,'!jǜy$9T,A  q*j!ǟ~Yf^ȦO;S8fZQPM{= 6IH/:$zȑ,[;@yXCNWGl_y&@}$W)oҟ|2B.sڵ *3v)^0W;ﷺ2J H=&DKL=y;To5N0XiWup}|hb-oUg您WHFXRi= zI3kmMg޵ڑ.V2Ɖv:r>*}y[{Ww B dB`S>ʶNt 2).Ԧ8z*)a9/VoaX`;+G. q'ʾ"Q7SJMgHǒC De$캐*zq9~V 's1R~(bloLm_[2 `gDmGF duV`WCx(M@aI^)TBTVenW!x>93r~)HAے+)BHA_,0o#y6`X߆q_+K?:*$E0_1zgue]DK؝5c`,/:lfSK ̎'?IAe;~JMNd/I^"="YM&*%.``h1wCn\촓IO VѤ*WOmHJ_7Ah擜YzlrKi^OE\|B˄P1dgOL[ ɶD 7/+a˩7 `*qu.go B$Q`YU`+bw«Z`rf[\-N ju=x#*ןnu;m2on!Mw\ XꊼAsȾ :+W=cŢУ@`u$SJ$E~_T+HSZ/$} -xE!uطh6i^r6Y;3`:Q]eZ K5?=2ƫi5y㣁sB2ejkT)+DŽ2Q߆X4!˔%Iadϙ#I\æbjCI6 KKkRRIp4&_9X /I8N9 ։2N c3'O9!Qv9Us5jvk҆8緊;}$g = CN(lngc1GXVgCf ~ީʸ\Ty&j%Ȝy+n#/тLx1>;.{)1~<|0 0P ,!VP]$;P%TVB{!7 S%(9T2 6\g,zErDy~#?=x |JL *㦺~tlc;iŤR="wͿG(//禛nbttgs~?ǏDz)mO> z{{ioocL6=J6j͛G,رc`euuu̞=R|>tN81ܹs)** /ߏR!ZZZPuONyy9\x3mO}}= .$r1ͭS]Yp!tuu;|(;PEQ__C)---tvv^ӏs!s>S2|>VXAii)mmm9scGCCg& q?YX,Fgg''Ot| clls7C)SW"1 3fPVVFGGݟ\ZjXVΝ;Ŷ )MMM̚5 Çt_6mf͢0H$qقk={6aw^\ ,Xt]t+'ӧ3k, СC i*++3gXP(J Ҽy󨮮&9 1c jjjF|>,bpp\;u]СCmvs̡@ RZZZ >gΜIcc#N*]ͣ0d2tuu#3f`֬YhЈ"͛GCC;9pُPFߢV<=ZCS0|Eхw?35[AP)'>0C+ G/X~0nG#b$~#@ +Wikk㩧_{-[Ɔ D"?S޺u+/<G[owߥK.My|ǨG?oxϖ-[زe MMMB!x'V\#[n[###?iQQ> 7o7TSS<z+H{O?ͳ>{~<Ôzt:o~̙3_vZ~ab޽{d2z_^ -?Lii)?'?>))Yon4ͫP555{̝;_~^xSiߌ3MCC<瞀Zf z+Tc'6n}݇//9pMUUU{lڴ /_^@@~ lڴ۷:8tmSRRիy衇@ >?`̜9cTTTPZZŋ;^y啂,]j0l6^z%^|);3{9|0m: r-l޼%-… <pzjǏ8vJ)X~=۶m O"üKSs˖-s=X=9R/k\~3$%ʚWN2c0|i'FChF;U\e4 p3w y] _xd,J I?".MAS9 x| [!rCNAğ076Bp6XSø Q w!VX- X F@OgϞM7D:&Oy]8fŊ̝;3W\sk+WdҥdYZZZ$jjjX`3fĉ\pD"1ތ3ޣ!{ 6COoo/9.gttt0}t6mD"s;V\̙3iiiSVVFee%.]LHDC7̘1M6166vMѣ?s%KPUURgXh 9rO$Q^^*D&;aR#,X+9SVVM7D}}=GLEEm?.\ȍ7HW+͟6m/2||̙3ǡC顯cǎM:?Mdtt(;;;y8wgfݺu Mŋb;w]K,Z3fN]ghhhYpBW-d&ѣ}ojjnò,Ν;G*rUL&ylgee%o9{lA;7n?N4H$2a_׾ڵkd2;v ˲?>r L3g`Yv_W)))ԩS pB֯_O"sŋ`޽9sII XšC\URPR>O}`ѥj\>*Uhsj"^6\4P``^MREwȚP=uΈ;%UZYǶ7jϛ9e}+m(YZrڙ+?@F%ԔjbFڧDŽ`z!}B!* *bKaEU;>u|j9!-T!0hs^8Y ({L π}RB06j>A]FRPZ|0ym*To[J^'N+~920f^ 0yF]i3agYgޖp8LSSK"`ٲeXE?.]B)Eqq1~)))JKK]ٽiRUUE8mh"t]\[[~WJ PJ(PXN]]%%%4Mò,FFF.H2 W"p}QQQcxצncŊɫJGG1w\(k֬ϻ.QS[[Kqq1===<쳤i q~} ~ g"2 |Na+a;Xj^/vWZmdYt߫`0ضM<wB8]EEal& 2m4 àkJ탠J"a"N;VS__Ӷm===}&add.,Bujjj{n@4JI  qxx}q9w.]|j 6L*_.#\s.\{{{_N|9UT6MF,#HpY.]ꮛ,7s~m%ާRlڴ%KL&ٹs'MF44Md˲F()ͥh4=6ihh ݝ?>hwX,ƅ x'%s본@ ண1zzz>3磦6ʨE)R)/_R1Z[[6VVVRUUEuu5l]innƶm]⽬򂹔Nr(.H$XΥlll$288X0hoo'ͺ{J$q=H`۶;$[D"n;sY<}tHO&"R)wi.]i~:hi 3)uVϟ(;vp9s&%%%qsAvMoo/===?]uuuD"ZZZxꩧpϞz-ۙ1cJ)W%5s@l^\\'\xT}뮻aǎ$إKx8}4Po.v%N8A2uVn'/200@&= Om۶1o<ٱc^ կ8vgͅ XnK,A4z-y2  hllD4VX dܹAV^Wϟ… 'm۶1w\رcG(eeek..\uS>t(;^9w \#WTMɗFnpX?NCxwV yd@[!VH5/og(yp˜KN^ Z;63(=(&\8_*?U!5Ӆx;A9b( 5 b k}E쿀ZF @l q2 |#0"BxiU`A&H6HI^|BV(F9~3̷8%_sl3y6:6O&o0"\05A_QM2^|F@%UN=*[e<3a_t< 8F2V?7Iy)# RSSC:fڴi_555qvO~zzzذa? .Db-i×eN(X MxGx衇?c IDAT_?uH&I}}=eO<HRΜ9sxXlA2w^ _z5k֬Au' 2 f-[(ޤ*%JW r =37ow6mPh4J4eض͞={?p뭷Cl2ꫯ< = "PWWG6e```RՊilݺM61g߿'|}lGeҥ `|4Z֭[͛]?R).\+˖-n;s}СC?ŋ(\ׯgӦMWYUU'L̟?Mhii?1G!ͺc#|rd2>}]vMUSSŋȑ#:t=677s2< /. r}n:f̘磻{OΪUw磫3gR\\ܟ|I{ؾ};MMM?WZ??N9y$w|2~e˖pBJKK4}K/qq2Ͼ} \YA(WfXs=yf|A̙>عs|vCC͔o>~"sttCqA֮]K$ahhhR򺭭Ul۶ M |jll Ԭ[hqZss3۷={Xr%MMM8pK.[OFq8r###$I;w.x_7x%cq)**TtRػwHZZZHRǡ}lߔ'edzΒYgތ2z+@%Q>͵PL2_ru(m1%kR=8 |_Q^/$wbՁ+H*(zAxDkC0<eo}1m̋rcBUB- . -A_/qlb۬= ca0* X^Ksr+`_A0怼i(z(9"P _vRG@@hm.%ʵ%Bd !|ؿ EuE9eԀ=F$vA`6DVMk3\7UI3-2vz JkXMs $v"[De B Q{䭊- 5 > 6OͿ ɏfTH= ^^.m5>Z!AH+sW9{pTlSSi̙իg?۷^,X@CCo4M.]DOO###tт%ȶm())l6Yn?X,ٳf;wL&CEE .t47 .ii!.JzUe2|_{8DQ7Wi̟?|;,]N8ᒕ9b`׮]җDss3CCC￟{]`tt"˩-P֔:xOΒ%K{imm&˹p555477{?S6oL$qUT}݌SOijj"022dժU~tuusƞ={1cK.%pf*e˖o~f:::p~Z~¸ro k׮,]]]ٳr֬YC&cv{GGo6477Ů߄aRgglڴ`0H{{;eȶm?9d+V%K >gۙ3g__Z{+B{3YrkpժUnd$9©/}K6i۷3{lp'gΜɒ%K0Mh4ʟSTT{__ݰ\Z-ZƍYjK.7XE,NIS___@X]Yf100Ν; R ֺn_+t6y^yIP1/82َ#Ey;hu7.Nqt@lo0(D6 I>_ qā{H=_oh%0ρ^ MBrW@éW8T[!2WVb/C=!h#VC `sHNVh%BW!H/ʮ|G4[>r6; kSX!y|XAITc! alX5,Vr'0 >0SbJ%g}XeJ?e2Zx~+\e, -J+[.17ʀRHitRzKvk@_N?j||rK6\ `Ql4x$iz9G$~,!*'UOZzq]wsBFldAI`2EbRV_͎\rMݚ3LֻfVݱE1" 4ntND#rY`w{ҽ̆ x<={0n[555oA(~EB OX,B!裏xORRRfoӦM>s>sAoBAff&A-F:ϴV8RFF555Z[[W,fffHMMݻwywq ~xذa@J0T*X,kN<ɁHOOWrؽ{7Ǐg\~qRSSٴi>[n*sqq+vnݺE?opyRV544`XhiiҥK|>:ٴiSb… D"^4~`Z)..' ŋ ;Ɓt- ̤OlSSSIMMe``ݎje޽266ƥK!## ȑ#lٲi>CFFFFdddhĆtoλ}6\.N' #Gp1JKKIJJZMFFfɧlBff&w֭[qGZJ?`ʕ+b3b~(N%{Gt:qe^|2dI޸rQjha^J__ߧ͛7P2Fgg'7n܈# Ԅ^'))099IAA+۷dx<:tCc.\lTUUc.\ |=Czzz_Rɽ{Vccc?^~O~Bnn._166ƹs^Q{TUUϓ'ODȞvsu7f?~,R۷2F#>M6q1Z-.\Ajj*ETӅJf``3g0??/\2r%)) HWWv"1wٷo&L;rرgϢjF311[hjj"33f'ꢻ^zjv;]v\⽔Aee%K.ݍZj277'B*b/^\1?ޣ;w277͛7Q*qcCyW$:߿*o؈V֭[ܼy@ ?/+1^L*++YXXŋ jvvvEٺu+III477sҥubJ%333z蕔{j&ବ,;Fcc#PI3 h4ܼBMNQQ:2ǙG{Btuuij>ˡCػw _.JJE8DSFF{Aӱ"Z5ի+SzP(uV4 `gP( h{k9H~Z#ʌR J"BZ$R/)HtRX/)@䰣H+vHך"I #XxDgTA$¹XWWgKDE`Kedy@8()&5A^,r,BtݪR"aIYX"&˾늑6/e>ͶXb:(#+ْݑLjDч()߆R j_FK"LA@UHyIIG3/@DU.} njymIŤ4TAFɗIǴZjIm&LUj I"ML5EHc~/:M'VoQ`.H߈` H%\2 $lFCOO7nGa xx<$%% V,l6jLqqFq:455n' a0oL*%''S\\X9ճc~~>Πs)8~8f?5gϥKVȧO<$5)0EFFFV58NJJ^/Lcǎ399;#B+hll$==Njr5iD"b2  bV{RTTfgYYP4 JT EXNCPؘӺ: LMM PٯMՒƍy9޽{ŜO=Dx«JUUp7n2`0 o5HVJ (lf\!n.^6l`XVڸpMMMk'׳HxNr=KJJDo… |竖w^RSSYXXYRܹS4HD*JJ)J) bjK(*.c)LI[qŔ3шz)M[i UB%]W(_ )+^HW`X 2_"ES@$yY 䍩tR>RZBdE[v]|BA+".)P'z+:~ $_ƚ[0+U-ӂTi5IZ\Fl2:i#%,+M9d5JN/ Ia_ $-dBA__Hc0p+6fԚK7^wLV<,ݐh4t:NSˡm{졶M6}VΜ9 8{'hZ*mۆnk.زe V{q5 d db||׻h4RVV&.orss#-- FQyjZfsfݻwS__ϖ-[/$KC] t:x<5C(IJJWO )s4.TSSRZZ*#dgg(VOO(3;;H8frrrI;wZ?&4ƍx^PB\6,***HOOf駟~>XO8pV+Lz=EEE¸yKkT(LLL92Ngg'ϟ_17ظq#H5~ӟ~40i^^OK^^^\=Zb<߼y3YYYDQ&''EXb_ԩS[bbfB{{HNNEEE(J2np8ضm0瓖B"M^OJJJII!??yY| IDATlh4222PTϯR&#vysNbfgg{ff&ٸ\U7a׮]?Je n=Ȅ??< BE̤P(D[[ۺbL&6M̥Z222D"LNNbX(((@T288H(&.͛7 zdddlb 8N~K(N:53:ZVN<ɋ/dƍ|GqG޶mzLaΝdff@8'Nѣ$%%qm>s(Ǐgǎ޽#2<<ݻwٵk:/@p8cll,nMRTTD}}=:U 5 HF6oތdm#XMP(Rj |.]ղ|XʈWJoYbKH9PhD#ny>:q6Kq;8:3R:GRh$*W()"I R&Em^+~/k\u^JQ(cXiAAqZ<G҂rj9N;f80O ՊcnnN0 LMM'6mM"ylXpkz>Sb,ou:SSSV7;;KQQpAn  ++Gb6V.ѣGqʕuÖ %%%jfgg䳔tή"%jZVV&B)W }fgg),,| (Vba1kW&eTTTRdbbJ3<֭[E8_>LNNFPĕYXXHNN0_0] W\yf(..f۶mDQzzzJ^Bl6Vq8>ըP`418ҡCعs'iiiru&&&_h47\yZkSx X\\˫zT^o\f吽Ef$۶mYzzzp8_L(h4q@"ZoooRTxvMNNt:r dZZyyyAҚ,H#}aa1WO 8Na\tTUUj[5'55U/Eii)< 555 rU[oQRRWKKKٻw(ʕ+9f[1e]czzz{ᶲĄPjq: \Zw.YVT* ",Pq?zHɾ=`p8غu+& b0088UUUtrAII,ʮ~LNNOaa!%%%t:A8uu9cؘH!JZwM&0 ]\*--d2 %S bhVe,oS۾t\WᵞEEEKb /럖U1b3 [QI>GuDKtpT(@7c73,&Dy ID13BP7@x WW= $d A0sjG`2&-kJ2vl_$[$OWXC 6AՒb%KG#*uPz^xpȩZ&''DžPNq( ˧l?j{W\Yan0f%`ˑζm|GO%dc~~~ЄjY1118Z>VgUb' FEvWHh4u Bܿ_8p"ji&~?PØL&6l؀`)#V<SRR`Ν;9sQ(** AADѸ9[/ni69vf>:;;6rfffIOO?1Ǐ'//^/Whw/֤VQT~:F49NV|o.'e/`p8L$app3gF70MT$''޾jvdq8n˅fttt+ ͛7q&xcٳg M@.?u,--%%%E(BZZP3ͭzxQVVFzz:sss={[nxfЬs='I ps ##C3=z􈚚6xA099xF/wg}FSSSܸG"j4X,"{4Iyy^륭Mc:u??K۷q=L&29OEMM )))BUCǗp:-Ù]FKKˊtⷲ| }};vhxSOhdxxx]SuLjjx,Gee%*a._;Sao.Z#d'++2C, \%3??9<7nD+DSSS]ZZQ<|pUjPT8p\fffP k=VTPФ@/i# "xe#Q}Ry@2Γ62%$BwId`} M>OJFjL ޔ|σJ 1 OȞ1 HP$rLe&Dļbad꣱2R?" +O]"I"/:BTfxFܬ3%0T$=7W/gQu$H 5G"ՙR=AO S|иШΧɃI!j+vѾxRH:iN{J AȾ+Ndiiizj5)))߿8cccbhĩ^j؈`ttT7|ɟ sssl۶Riiiajjr~@ 664wرcLMM)''|;f^fH>8x^^rZ!S&|L&dee pMM ^@BF`0PXXȩSp݌000Ç:ỒNff&~$ RUU~# U~vѣյ@7tGדKcc# p8P(TTTt:v%Z~dZɓ0c^\\DPCUU8pL&''LN>MFFTh2<<ܜ) q:8NV+"&B8y$vYd"t211A]]Ǐ'''H$B^^*fA8X,"H\X,FYgggQ(b rnm۶ r 33 +сQP(Q(l7 % ۉuGteJN|W3JTR\HZ~"<ڰM2V%'vBt|:3X! v6"`Jc4~R!8-e|N0zQ c2)-k_C.2).Y42·$rw{%2EW#tVrbP@GBHDxzB!@WY*CLFֲHP?$cuzMoV2f |E2aHW|x$9mߔ 0Nuh b?^Q>e*V%~[Nyy9F&d B lؾ};,.._!@ deeK/{./a6qx^}Y":i.]ؘOUUz`0H0't+TWWS^^Nqq1@`d2QYYI}}=65 oNZZ]]]{bc!Sx̡Cz'N2:: 2@>b֭466R__/_f``Agg'YYYlذA)Ǐ(J222FՊtx<VfeeQRRBiiPSĸ+?޽{k&6[|'LMMlb{Kw9LfRÇP^^~3 YYY( a|1Lol9پ};[njsuqACCtvAp[[ra<Jǝ;wP*q)EHdbӦMp8vo6<^y:JS+S555|xE=>e1 LRR}}}B!麟͛7sA#oل377L&o̙3k5d\8&''G/n݊`nׇ墣Byy A@J'LMMq}6mkѣGE5!橜QΜ&V_z%~?_԰}v B t֘Ww)(,,ri1il6EEE8s0Y_GRh4B!l6Pˌ҂je˖- E𰘟wHMM3>>7d_('Bͱ?ǩlN'###TTTsq?~ٳgɓ'DUU?}>=[rkjjDeU]ssPR˙T*=… *|M&k0ذav$vuu T*v۷os5***hll$??ΦMFPDhmmDeeeܹRÇ4I3޻w7rQAsea5BYY=zχ`SEZZ %Jbff .ĭ***hhh@Pѣ5OrvG)V ôpܹ5Q*:m<:BPAӽxN.QLBk tb!IC@ s7&W2vpG t@Q$2&ȖPLq%Im Xax$bUaZ)DD,1sjDAtZRˌgX;2IImJ ʳ9rIlW[cdRޗ3]qK婇:Db.YI4(H Ʈ?*O"6ʔX8eTR{E%Փ:LIo/U~ B^؁2mLRvGvGfٷLDeFwAg&2%r0a~ aµ"9X(?ԟqX-q ````R).z?s  O0P(#ƕ+WpX"099ŋٺuX ?$SN0LD"N'<ڵk+Z-200sbXpºQ'NNpoo/sssfǑl ~ḰYMszXVaֽXl߾],z=ܾ}9 0D"ǹ>׮][r۷o344"q)I+W&%%R) 9iǎ@ڵkbaxvKsmq\§ܹs(Ja$vioogbbB,% W3&V(tww AVz͛q}]|>“HN=fANlذ^/ 7oncX6-.$MNxSn7YYYb&ɓ')--Cabb?JE]]P$-,,!|nPp=*++\^YlaavQR9n_6Յfcdd FGGR[[+Bn=cccܾ}[Ͼ>~_zFTۋpą9taׯ_ ^VMG4e2=BȂn޼I$!''GKӺN._Lee%& B!6r688ȣG())K-=yO>R֭[E8nMI ^<OwݣK(qcxx'O0>>N~~>]]1Dy IDAT]ܿ_<~_xزe B'9n!Y=244č7BXVџr(RdzzN1 "fGG׮]ct)RYP NnW TTb~z^X:?cddgt:ǽ;_-9Z[[ BqmmSVNMMq֭u q݄B!8 F88/'L^Rh0333+CѨP,((`rrr,Vpk͛7~P]z޽{)((I,zF~?"xUV?[Q:::Zt:(>w7 i> ~?\MdRy)k<H#Ź $Q SB'e(|WP?}:]%Y# Oo}~qay7~޴ioX?! LNN,1<V U?1uuu ??ǏS&~ӟRSSÇߩɟ ܿOOٟQYYɝ;wۿg7#H _ @ H^QJ# @& O |7P7Î;@ $@ $D$: @(B :GB}@ t:yɪO $@ f/cZ Y׮_nH m$a"mnH ugK u `0 *V#A@%@ $@ $@ | \d[ h`0,Hۈ@ $@ $@ $-@ $یoH@)PUVЧ &4J kBhʌR"A"=k߮)2ƗAY gQ _AW:Bײ/jA (-@,gx(7 3(@w#|> ?G$?`PD$O |gBAii)eee"edggS\\LVV& JcrruO)..f҂_qf3`0,84 [lNW"tuuk.DB2oNQQ6faZEۍFc\ f, FZ(]]],,,k޽uy}yz"7nZ V)//'??T{aa\EEEZ&033CGGn{q߱cEEELOOںf=ϧdnܸ7NSPP@UUIII|gZL&(((@6maa!UUUL&ZZZ$騪a=ze466RTT  ǥQQ7nbhx<<~w~gee~z+uoذҸӴgN|RRRjai ٿg Foo/Q\\LFFFQH$~OS]]里 D׋Iks͛7STTDRRH>00@"IU $-#T&ԕϒ TVTIi3!>O ;$iq?e1A@z_°H˷~I/<]RV(DGӠJ;m%A"؆ `٦1_m (%X\R" PA4$Gz7`< k]\wJz VpA ɯ86pOJk@TRVVɓ'ygZT*qnvÇQjxn"++ӧOsQz=pXlx'''9 N8cP*qcV,,,đkW^yݻwEoo/SSSw#ڮhptwwVikk騭7ޠ@l6_|uI{z*J4 >3??OOOO\=z=*w靈^W%aN}:1LD"&''v2{9KAA:Ǐ455JBL&^{5vI{{;}}}ׁm۶cZ`aaP(wje||o\zhDC~_<ڻw/*8N|pIN8sXXX$r8p ٳLOO%99ÇcZtl6Ο?JJ<~7rQC^^ZK__f|>lݺUɑH1._W*~Y{288Hcc#理Tj5.p]țo,O<\9|0ŤF̙3bn544pIoߎl&2;;g}ƿ˿0>> H oMAڜO$U2X@S TP&~}P!*e!₈u_<0ueGuic=Y.Tl-PgJUApᶯOB+P vA^X%wD R|]Dg`ظo6oH ==Ǐ /D>*8nؼy36mbaaAJ%%%%444 lb3;wd|8I+۷Oww7>$|>P rssdzzZlgffT*F!--:^/oft4APs)$?~Jml6=zDrr2F Fnw\(J=z$-KǣCkk++;vyf<ϚNرc>|4<(JOOghZq:dggS^^Α#Gpx饗8vjZ(***%066S*l߾͛7vV% 255ԯ(jB!&''eei._L__j5'y1++-[rhnnGT* F#팍ay_O8NnܸRTՑƓ'Oz*ݻx<*++'0<<YYYڵ RI?P^^ѣGY\\Jt8s۶mX,?v;v`nnfhh`0(cVKff&ܾ}/jE&=(׮]ݻDQ(:u RI[[}}}+ŽD=;::tR͌088"p\~]l򓟈 urUܹ#YPPSP(k3%%SNaXhkk˫ɓ'9pYYYvzzz'&&avvߏN//ZraӦML&nܸ??x8z(o&׷X,~JEEjӹ{.uuu 388222J{ r}ܿCTyfV^Mgg'7orvZ***0L;wCy8p*wߥE0p glu}~ԹD"5kѡF>|%o>Giii})..&--M}ۛT3n0(,,$%%ɄN#OW~~>YYYXV0F\dY… ٻw/fÇsU&&&0 Ȳ_|Evy8{,٪K.}f5w\RRRHbQǩM-̛7YT^07}%Ideea6 Bx^ $q?8x֤I@E( DnDll7Ɯ({\ۿfЭ0!!(}@:u% @93%,&I}=~`pe#-`@xB4H` XV@xP+ĆsAy Ax΃Z i$9qbp!{@~ 7" |,UK@ @3@Ɣ10u/X 0Y 0ԀuFI3A0<aD >|LK*wAx|y1es`]B |z;vN0-`5b}x{򨌥l18֘K+U\W @<'!$/V0UqAlLhNcɒ%,^qܹ3R__hLp[IimmV+>/avnذAmc6MiUU/fll;wd:زe 466RZZcbbB<\.N8naonnCtJJJB!2ccc3r ,^Qg3))]vQSSCQQ&qn߾~3߿bݻwˣ@[[G7`ڵ8pŋ9fǏ!amZ楗^b޼yfz{{|deequ~"k5;FCC @8Joo/x8s / -[ݎ$IqM~CѨBNN~Y3cѢEdeeQ__ϭ[f3n+hǏӟTGζmۘ?>CCC9rEaѢE? ''ٌbaҥTTTF9y$?غu+7o͆$Ir ~k%n:t:oSקf#MSSxY[?ǎcxxXfMלN'vbc0_*z ;w.֭Cen߾|eYVa9]wŋٿ?K,!99`0Hkk+ǎ7Mœ<3922B}}=?vΙ3/,^XZ`v=o{lذl l6)**ҥK455$!‚ HJJbbbjjjtjVgշ-.]Fe233 Bܻw444Yl`bN';vCCJJJҗڵkIOOӣ~ ӤIӟ>K%0UlY!s.(B^ 0/P/Xl%?#>`(}ۧ ya?0́061Ǿ6M i$x"g)ps݃C3 ;Z dgɭ ݆ ER<|i[ S|D) %(!ch m0&1~$p!KܗINᨱ>@x '9҂!2*%(+.2/wl'|(H!0)qT $ֆS+AgPR7e D])֊n @%OD}b7y(np}!0(ַ.tv 2SbXVl93]|dwAeiӖ餢NGKK TUUqm5hh||<}Y֭[\Ow8NCrr2uuujL\K.U78 .W!ӧywpPZZbtꫯٳ ںu+k׮nsQZZZfʕ+;73Cr:@gzF1LXBuLwL׳>˚5kZ;w{uOMMU]E.]vuعs' ,.***IE񼧸7bddDjoogddb*++y(--e͸nΞ=p$.^8k;sNv;B!̙Æ p\:tH-_:, >ܹs8N~گ.Ν;G?UUU322騬)((ǏS^^Μ9sLOsMIZa$%%a֬YCOO/쮮.n7Ryx7JL&~ζ***x<=zAdfft:e˭[˚9rȌڵjL&'O޽{8sIRSSٸq#Nׯjj]AANn7FQ]OCCC @D)**Ç\~]u3::JNNUUU<̟?u166ƙ3gE"WUH( mۆjÇ~2_}3'##Cm x,[ EQhoo!2K.^~HMM IWl6:::x<F̙oBn7j:, IDAT 3<< ~d˖-HDcc#rdfftR"6o8N&''~:GŋsKe\.\p!v]&)##"r .]b,Yv uSnU/bH-D[M/N&ށh/S "w| P`;SG! KP /J z_ŁIW#EgT>;ap<KSkc{x<Df{&DApMy,^ǜ< pI@)v\7E/ ˇHZ 7 \ I/cxBo|ep2 s/AG=#&ʔ6 h%ۀ1 M1 U,CIFHE7 PgX ڥ}GBilH+b$fRT$^IL¥gZ<XIJe41G0- AeQ~hj! ÇղZb555TVV2<<̙3gzhcUUU166Fss0<]댌 ޽dܹݻVFFFZC|>`ٹs'PFDUU ,ӧO'l\Ziʕlٲ 9šDii)FfF#K,aj;%F-ZDUU.@dCCCO=fo>6mڄ`Ν;ji_[[W\!==˗SYYI8cǎqy( xn׳b 裏8q~g}{`N'X,Ւk׮qY >mmmtttw^X\\̶m(--ƍ>}oΝ;9^s߾}TTTzd 0 rEΞ=b FrrK.сSˎlݻUg['##>dOiiiSWW222(//Ge9qn"##Pe(++'N$6nn>h4&@/"I۶mcǎjߧ(^OYYׯ's˗INNfժU,]P(Dww7v7nDvTKlذ… 8{,lݺm۶|r;N\nP[n 555ˉ'd޽JZ[[9u< 99^{"***1pZ$I {G4 IKK;YYYj={X|9:sq)RRRػw/ׯgttq (**RKY;::b…*# ofL=/%sӉΝ;ttt`0(**Lzz:999L&z{{9w/_&--e˖p8Twӗe9r:ϟb?)4iSPH]_R"i%_xKN@[%6XWp E}K!:oEDy 5Vfn ] `iDQ_,r$$S/>gD敏? OߔA@aQlZ!puɴv% S"(sEX(`$@Ch7 L~B~s.K슸{ c^ IgtTr7C޴ QƟIBFY B%y!ڦ%eL̳dsp ԰zUI=GӁpj霠+R+ :DO1(E%9?a}*cSn~h())I}|ppITP((*ƍW?nvޭu/Q; >L[[*Yp!---<~7nߏ%׾5(//;wdժUt:jkkinnHOOg׮]* |㭨K.e߾}_q}\2,`0`r}lݻF333]vp8pW\qb͚5|>}]^/DQ5e6{nC233Uبf-]T͝?INN_WsPHuiҤIPD>ŨB0`voIlj'E <ͱ1i3xbWd\}:tςy6p]Z94XI-w fNܸ1/cH҉*]fjC;H@A焤=²{dh "(?C i(hekOQ4 כypJYVh ^|?)Ei`%LO8 <?JpR2e(Й EygTszGwc;2mM|eL{p[]f9o#2V$f8{/_UsQ>w,Yܹ3㔴2vz~3eȲ~_>,x)tl2ܹs3覶3sV\ɁXv-|>|84@{e,_\-/:{ FIpxvu11 LNNDp:D{.XVƟ!EK]3vB(E;O& @=m1m#(KP fiD6.N:%`(xN2F)4f})R>Ld>:p)٧u܄\W֟ QzbSSY?)I]7sr瀱P!Qv (iy6a-cXq1=R!M=X;'Ɏ6~ʄ .< c}YwCϿ&M)N$I~&&&8|0?O<|:{6nܨ斸\.=[o5#&##{$jkkh4Wl6III(o2ó+]SSҥK Ü9s7̎;HJJٳ?gmڴI ѣO8ɲLjj:O֭[Yd `a8]ƍ7f\gѢEp8ؽ{7;w`0h:tNRRRxyX,\tK<૪(..vo@ 0 ۱\|yvHp-\B$IR-33@ 0#iJJJd2%M!Ivv6JII ,3:::MЌzJKK1L֭[Yr%/ryX|9jl6?mܹrn7NfXHKK=^ƍZ.oNee%Mvbt:qm۶+V`\~VZ%^eΝR[[sҥ044[orBЌ޵kzZSii)61Z[[x< {P(Dgggc׮]pyJ?>===|TaVJJ@[[D"! R@G}Dmm-[" ---ddd~zϟϽ{8s ' o}h4R@QE(]}"WJJJ z׫BsΡ(xHJJbѢEtvv\`o,NW=.==ǏzQRR``xx-[j% &KZZ694i"@( aLۥ)^`ҧ{If0dN$;Ddm#['4Kb'} *qc`S M7Wl% |7B3SDq'J89z4ݦLR x "ñ0˖Sk-%^ t 3ĉtћ U 'Asc%c[%g]'89OZBD1?q @s<[̹!eR(7VAc3R{>V@fA_ɀ&MIwEd2%O\Ç s= /ܹsѣjMnEEK.466VD_"Ch!* A)gAlЭf~L%OYKȅ"$\NIdq%l8JPH*>e(jO)l!21?ZJV:'ŤX< -I}@V̙yfq?+AeKBtXd֞wBjjjXh˖-###f /gϟ(mmmf^}UB>^ںu+III>}%SMMM SVVƊ+ իWc6|2=b޼y_%pA~?TVVč7f!mذA= tvv8uVW\Q7Kmܹs ʫJ0ѣG\~Gzz:555$''SXXbQ{_~e\A7nLhҞ8\g0]|y$ҥKjN#99AOOFYIOO' G(fQ^^vbƍSjrr2a Ȳ`bP^^΁ѣG Μ937n$''spBhhhHp@0o/!Cti"<2G g%sLA©"קq>r;xR'ISKb?Nx▊~clX֔)氱uhOd &CLn.P6>M)=b YbÏn6NiҨx[)_)t [T%1Dz@݆*Q*!*N7xb04O1<$`JU\S66! OyyKI- k-:7 F'k)2sb$pHRb3_7-=! ~v4ή pQ8`bI&|Abr>Z9,ZLbfɫ<$?P$~)} mJS̻ |] j|4=]~_=*|ʕ[p8NӧO'-X%K'mذA}r u3pB-[nzjf墾,xeI?_8",ihhرc y+555Z իW=# nAnݺTʕ+j"Ia#I|=|>EEEKb6q8t:oNgg''NPԖ-[ppUyp` Oͨ{nת(BGG׮]cllgySPPe̛7/L Mkk+ׯ_gΜ9X 6rJz=$c?88~n޼T@CCyyysAu9gRSS3gYA6mٲ|FZs'?vdO1::;veyAo,j~АHNN&-- ߯V8p[nU7h8C[[K,aӦM^Z]Kmmmjfڵk`xxe˖l2sG%ʕ+X,׫ϦxIVjj*.kVPHkk+˼y顮nȵk(--M( n+Vn:U UQQ^͛(uuu8NU(|>ZZZԲRnʼynիYhFEQQ!=z۷oxM6n:E!088FݻGEE/2=:HD&466咒]]]ܺu4*++grr8[Y&M29Fx `!<[0SЃ}2-9b އH$/X uܕM^-6 d!2.he~L c?#9ވQ'JX $p 1('N ZA)L@g;| v%A,.8O['1SYY-\Q 2$͋ @˓52y3`5-NT$}줻%b-{`AGYOB ^p|*AqNdA @` )>cđ>RQ 2(JbNr' B- ~<*M_ p!&&&Cex"GQ_OCC!tJ$|>?&PTTĖ-[p:\|P'>`0D"zzz8uիcD^/=󳆓wuu1::ٳgg)**ŋ?6x< }z\. c,?$v{̙Y77l޼$.\Pgy-#%jLM࠺Qy&Eii)f@ @OO֪͛7p88w܌銯e˖L8ZΟ !066իWy|PEuAQ3fx7!##O4A5< xzMM$Q7W\͛:H *48~8n[ -fttS__?#/lppG211Aff&!! jϞ=266Ϧ/^Q=El||zΝ;7%?Nܺu>:;;g|;vL}ă'&&;w.7nPAC4۷X, g~8 %X544zwTCϹ9RCCC ǟ>Cq\liiСC<~|f3pX&?8D":;;qsƍx^^/$%%QQQ2Ǐ'u}(¢E0<~ݻwܴf^u\.zFGGz*ý{zȲ>}z񜫫W 76?- ,XBW{" 300˗ihhPz'''u :|0`sYhҤ+b(~ze:`.6c!6MMi`%W]/1khOm]i3%Z۞d)Dϥ897N 0o[~OҼyWJzz:.N\ WBZZS˖>KYQQ+Bjj*oNUVV/믿΅ >dժUdggݍ㡬{p88r?O UUUtuu橵G>?x677OOljj̧ϋRSSxln ~^h?秦O Swm4gyDe̙3O=}R&M_!) rw"#D6,ʃ>(!sI* B͙ԆG&M4 t:1n]ƩSyT4鋨`||\ -ܻw>w}WtM;iҤI&M__%x4iҤI&M4)Iֆ@&M4iҤI&M4iҤIRMJF^&M4iҤI&M4iҤ 1JFʨDɚdA4LG(2HJ.~2*\U NXb`455"ovE:2?ySGN)x6Mk2 Lc$Jؚ ZcS)i]7 HOS b=a`BvT"BRȹKr_r At &!֑%1AQv A3M4iҤI&M4ihu ;QP+I8s$k{WoB @CHz𷀩10U͹2ˆx:Y `Y.[s<%@TXjD)$ 8D*CL6y,<8 Ct+cE bu7 ^} L[@ҥd%y K\}BWbp,d 0g; ip&)@Gg rIS 5$"NDPie|[lMĬ{|WoMa\ f4<l/:q%iN!@&M4iҤI&M4i=(|M)`]&\qYsMDс (_%TLO݉1f"fwwgjeJ*UJUR,E@A }uLd j7!}o|eкDqL&?.\Ft^} "OӲi삪 LBZc,RtHD\P:$U06(!?/1 EAkA0*⇢p>&ɢU+E,FĭՀľ)qZ? z#dN@-=|\͌@}P8K@G)*]QW"Bc!+@iB  "VvKU AWk/VQl{`ľS`mpU7^(q&]ڊ~Iğ܀2yTMzCN qj߆9ܟnI̜1J;Sey8]SgӇeCa+AېJ兀5\]3z/[,>e-z{e7\q`>$$~ Z+`B @vTp./:'W?w3趿_]'syw}"[2op &X灌B`U1߇ׁ9ݹ; `]lP{TtHAST=x׏Nùxu8=N8=N87` P8`'QjVO'w}Ap'RvZ`^@j)`^{xduYȐ|r[y9 ǀ\9Vk3N|{DQneܔZB(Em-LiiDET`/@-=WޑsS.'QrS`BD kJD3%(" -Z[gA+k{HHqۮ {ęV *2쌈zǮpd}LISO5swe.ka"Z~tS7S Ҳ,@ _AIb|AD/D-%XHd)K@\D+e _5E}7-EIQ/BiQIAuӾ"a@؋P ǑS{Qrdٔ 0>wfE!O!0$uԠZQ*L_PDFIwC%NND;+"V?X/h麴M7E\QHPP B1F0(7I%EgçӔ ;>r(|2y5UKPgK-Iä<^vt8p"%0zUe@Cpo,:)k,\YOH-(37> CuvÇ 8i\zw}F>LWWWiQH$x7X\\\G(W\[GWW8KKK,--}|p;r655,L"#g?8_ QG0#g__%E~Wp~z{{9|p˗/ꚛ9|*_BZhQޟEN0|2~*ξ>:::J kwGoo/t}Ѻ8Ul4V---޾)΋/rĉuq-Ϟ={8tЦ88j y> Vhkk|W( s޽:tW<<'O#< aT89ϝ;ǩSVq9r Η_~4+8F__ߦ8 Νӫ89BKKKsnn_~Z>% 5K/TE(b߾}8pٳ9sfͶ^}sUpW933K/TqF99BSS`v)rD"ݻԩS?~Ӝc%|>ϩSp9gffxWy ._|GΩ)^}MsZP88KQCkͯBY/gsUAry* kvMHʚ,c ;+, rD?,)O墊U\( ^{ AeGp D4XDu Pk<=K~muO ̜k2!LT-$KQqpp)_8-Z7g+9WY䜞.vxrۮz3LVlċH$Vq ,}nnM6iqh%g`PL$LMM6L$8.mo977+Ukf9WY|JΕ+R!8tފvqq?wU\ZZbzz!ULfmg1"_s% økΕqRam{9ªj8m۾-Zmk}˵?✝Ų {% B)΢Vq~'sޮ?7y8ggg1MBľ۶kV*Y6W-neΈ%JPNmIRJ+4\aE)7e-ScT-)HDИ}#$([<#i}W]qWOnTR %AɑI+\J`7!vSNH- Iں5[ ɝh΂o'ݢn=&5qw I,8*4pS /Ht':8:'N 8yR `!_q !>I=-c >dY$"T̮?Ӓ~7m/Wq8vypLMMKZ\\\sY΢^s.,,48s}᜜8gz9Z{3ͮ=՜٬y3ɬɵޭiy\Yt8|Μw#vQU?LdƓ/Jc-^EEQT;PESyrȝ2ġ-jV rW_$U='F,d!!FKh2=9MssmŐ)k$!s"O /nˉ{ciQ1! BG9te[IP91<ZsljA&=NKm*lD~e$ut}--)tJ@U /nGPO`\\o͟h{x[C I]0V>IpqK[ 4`@r,U-؍.sy6q Xry)|9r;kPJm{1s3 Ak2oJ+ȋZX`Ge:D݃[CR+iDXT9ÐD5#il,ET"\瑢HcBZANX`>&m5亘reoB^ 9KR<;] _Aſ]l7Ouy{QE Sڬ9qB_ k |߄ǐ{u2(*I;Ŕ2x+][( VlWsz.)lּ8n!OlIIS'D2n+؋` 2zΚ[ {R`~bv $ECqQG%EOQ"ܗy8nw 0' 68VscZ_ƪ54˅+.<WWĭsrǽ\nJ9ȝ+ZXs @R6;^:(A)8_9j9;'cBkU_]lmWoޜʧR=E4#:@21?VڽNC}y/T?VK^}'890~ Dh &d 3n9U{;=$b0΁Ϫ.ր9x9F- s 0~9A8ԋ݂ Xvb s,ĉMhAoދ9a@a!^H 382I|Oa+Ԍ=+1G}~+?źy{ ggsOY 38' u?+sW |i[Bkރ.3w "܎j%Ct~< ZQ|?٩9̑#,i#nvv 8sZZZhii0 .^@v~v|ׯ_Ƕ"FEQhmm`0#q5՟@ԟc =0Jss3pXns,,333LNnC[[r9._|몪& ( msuoICCtWzK<|( ߮:hzp86q3O!> tiU{F<PwB>vZ2?F@ CH~0Ak%.` VoAp@I<K:YQ;'ٿ#5=3qJi'E"c[nBQ<"Pugj8f@WW#}jhazΝ;MJ bYg٠:wf֭B! TD"A2q|顡ԟxԟUX 0m۶󌏏iu۶mٳD"q[^|%NԎ;عs'sssϙ jPUXY@n  F+p@ A`dr/H@oA#~O5/ENq'5" w8+v)S@6c`΂[vc9$^PT籒hU:9M! PJQ^$Q!cmN WGv97c}D~ K= ks\i9F n;1{| ;&?ai{t ڎǩ:UMf1nhJ0 v(oJ͡Ś=--2Wع%E+}ȫzB[A V;XUh:ݏbɇBc8V_VqFճSŘ ˂UhXI^; RTn`6jjjr Lf]khhV癞f˖-466rfff?~?pMHR8FHOO---133CCCMMM8p)?os3L244eYTWWLww7=V|GDQioo_w:fiiT*=+=x9rs>O/ZIգnp!=捆 m h"Zh5t,O#~ VP-N !שSWA.b3`_ @iup36ɵNPFPR]gT(]&M$δOA נpA86d]J.Yg~83+;@Au='T5zWtR(BվJ W!Ign[Q1N|I"N'#"Z8#z@tۭc4QVƳ88j˧J?eUAx7"pgoًY$&zϳn38Pub Bqن8.QF5Lwmps,f6 oз?@n^}ĺ c(J^r{H lCn%qf/ȃpU6O~'2,8 ?H;%U5޳~kk+ղ嘝%NSN]]1;;^cIӜ>}!zzzinpO"+'U&wEu8p` 2wNڱcuuuRRݻԟ$U(J)qjjP(đ#Gؿ?--- D"JT,L&tNQZZZ ͖<@>ƍuj,W^mʥ466RUUqR O΀!8.SIn5mT?k Ja <y( !o_8ZXPX!hԘPXg%.\1-‰"߹TSOzmV<D|S|2I/4ޒ:X68TH`D<e"~X Xg!"DCtw_k⾊<mr(}5P,}a'!έmnۏXj4+jk'qF9`-H7P 8t@+~i834F?G=! (nPWA#ΦROJ4,`u7.nɊ0Xe&^2M7! uR!=2Vv_($93=Bnk`,]('q<aP5ٿ+5ud?^13sN8 r5rΈpe-{Op7Tm97y`&)L^ߴۍm`ֽ$ж=c!9¼nQ}(qQ)Eɏi^Muġ(J)o#(A---100,xZ[[ޔDgg'`)_iqdvSLMMm8b TS#388 xjZZZqlص,|>O>'Ja>EQزe G~+W*;vرcrIn޼yihh@Qz*Ν+ͣbzlݺEQ,7o2<<=ill$8l!.]@ oB2c:sY[dwޱl^R!@cݓBћNYýu=c` EdQ3K(hUMd]l,+P4?K]H PT;Ҹ, 9 aЃh-84]_@ ir7>"s?Y?m-[.Ľrv@>B앟$'7s}RHpÄ{'?q;~qmp,̅/NYܺnF nܸA(355ئJ8fppbQa& *|zjkkIӤR)TU%_6ʾ}IJ,1 ^S]]]455~] t:(Ϣ JΤnQ/~?`]d24GM4"NUUt]_ E ׯ_'dbbb8Rdmnnرc4558mS[[K8&y~|}MWv>=~@FWLIqeYqf~%*0cC x a!8pCGR[( jyng]!g4k(| VY0߃V42sσ 'W |{JL?||J9&'F.,I!%WyddYCN?E %L#{e, riוZ~mq#q!.}"eYJLb!̫@w IDAT/̷MN3_wHNLI} H,۟gEқAk4b.=ݭ$~Y k[ ߄.'ɟuȃ32;xBy3&PHؗd\<(B[H{xOyVjزRؖA= ]PP!U_-+.^YfB{E$wCkp;JQA ;bt"Eű B /?GnHl?jj( D<]"G$u=,?Tr7ڷ)znȱezu+AZ%7r+~+]T]@!tB&ڵk\rX,ƾ}ؾ};vb~~~lEaR(,Ŝ*)@)GeLNN2;;[UՍ:x 3339twgNW@QU\.GmmmO"8 MC=֭[gaaEQ]FK8}]i%۷? 9*B:fppjkk9p޽yr%-:jjjhjjZP(Dkk+MMM,,,pܹI~P۶[ED 5,B3))Ksvzq*uרynJz>[}c"2X3C+ΰ\keHgV1MkՈzUv?-+5h|LҪE$%e/Bđٿ?Z5Ue{N9 kf)`q:E%g*YíTN~jǽu)b"Te~qf֢+zDڤ;Hut,5 |S1rM۟j? ܳ/ޭ_BIm)ًXωCN!T[(sX;KVː[O(0ڮg]_L?P8G*۷,ja=;(zO(叆 2oEq(Al+j^=ބ&0TrōיSyAԹɜ;tybPR} ,IJs/juk\Ư䛒UmOȟݴkZI8fz/^vj'h9K[PȀmDPԟ:F ևp&>qyqٺu+X ߿a۷j ={6~?]]]lٲD"Q!LmT(1p3Bu?dkN⺁Ez"`Towo榼D (€VrGX\qDqkq{YQ|H U~"/{F A{;!>寋pX<^5w"9u5*^xU w}Q.1 N$꫒WV)kY8΀+inQ⸪"R`O!*K+1.,W'=kZZNY|R*Wťe/A~m41q *mn!;S߇S}s r[~ւ?v^h#ZS},* qEHPU-[*d2\zuCNb (\pqR7"r(B[[ܼytšiܹz~? ,..{nb躎444PSSC*իN,s166F2d۶m%1eQy:Fk} tq |D"3 x[m ? g ESrxpPZ=o7v̩WiI@ Ű~ gs8Fng nT"5D|/B(/8K bz%s.8s)8<-#;@Uu׊'K8*{jy}PضkϮث~Զ#Ğ {2O7Dԑ9=/oolcmW)&c/5}RUUBP贪()!"N@2DUUeC&avv H~ &&&'x7RbJ'''iooΟ?♦MCCCi|, oMK8lٲp8mdMb\pQ]Ȋ rkkA]E!v捪i:۶KD>A>Q+sl—Z_PAV.q(a0zkE)q鍔\IJ |M+8Md.MEYČٕ8 VY撲]x8}IPvmq _uWե'BatY-5CS㒆kceIS6ƃ4U}^[;C^_%(K8Y&Gmڅl%Ԝ˹e5 N'5C{X{(_V|[1oPɸiVk hm[8ݥq7=i7$?qQR4cqP]A?T_N/8P#DB2e U[=)m-,܍!5:ʚѺ.\IVهDZqs`\^sj YZ!ܦ w磻 .P]]Muu5N7uիWuڒȶmRԦR' ( aYȷ[%bLww7ׯ_g4brr˲hnn. Pmdx"drSi&HhllԟM,K8C:^5^eeaF)Xt<iښؽ{7/_RmrͶ҉~M6-e2ضmV_ 4M{g.@OjtXU@3>ɻ8K^OߩK%\?d~DX.]_[|A?")QN `Ϲ"NAߐSͪ@D'#"*[܀i 9LGp W607Qkw wB֡4ևvR_K3JQefqT!j;MsF EZJ) jIr66NԉCP8 2>%QP@镺]{j1͙YvR_O|m^Eȓҏ) +(+ݿSDrN'~?vRNk!wU|CP tC w= yq?HWȓ dWP#">͐=+Gk}nJ*h:grS\C3Sa-8$y9j}_Vz35e/0yC ?mS䟑:q7L!˙Lk݇u cX7>ęBC/ԱQۈ@m6d'&n&>LpCT=܃qf14]m*j+=Ohh:Cb _3L][g~">Jk;PJ3=_|u]X9 WߺZ7vP4?m껰n`^yrn仨Z|5(k&xw_yұVBI»0O_CfYR!Kb%p,_VG4+&z>N@T(Z6!+Dul#p8LWWa0224xcɏ_$ZEAQ_H1R>)xN? IJLNAĠ@/[X>:'~\cC~70u"~:D(CvuzX@N{!6e֐)+~y)idu%)f,19Wl@cqy #"0)wi{z@ZO5,/ B*MŵS,dZE> Snb=x}-Ÿt (,!o(\\{h[,">iu&[ d p\'ڬ[l>:WRiW1/?qdAE[]"hȜ| Yw-Vfʭ(2Z+|ZbNCMp.B#OHا`?-1+67*WjNN4aw h;CD|i(o#]{(+Oһ,o` $hZQt"(L;uOgݳgwٝ9mǴ[M5[RKh"BH my_Y韋#^% C+ǃbf}݈|@p ܑCx^t&pPbB- pGxG*+1ci$lͷ)s# ݍtՆɳ+ᓪF}S8ul+%D߮Ձ#mE]٩W"qO}*%: غ#2}𢻉| #' X8UrM }!D,>E # ZEe!x735U 0i3jg0")B- !pYgz[Ms=ė?3@bxV rd2{dٲeY+:w\-JŲ,UgL23hT.رc_1(-BJI\fllT*ECCe188x'̟?  k.]eYŧ a2 OYfNijjbppŢnZZZj'3,Ǐ'| /W|={6ͬ_jZ&t}RX ,_4mMbY̞=FVZ'N066eY,[BD"BwmΝ;T*s-ZDSSx\UơF|b(w<峻ȅ=R9X.|v ţ/"Dj ξp9 jc윛عJ[}aE -v7π2Z៨fb(؇.#ɣPT9RS}Bw^ K"v,Ofz aUNQ%z9]J0&|{$Tn?XK0I( ;!B4/6+55/MEP1L,uC?ѝ ֫*'8O۝P`mJe.\2p\k]LQAOgn(K7}`*j  UŠ. 45Muצ*C*/קdz@ Sy{]~<z/^tcoO)_=<-VK:I~/*rRygXe]t(`uuS:jA0̋NGO#'.d|LZ$4w-f$gN`P`V.ms42w,91@븥,VכeDZ MZ%JaJv2eYJ%њ4)!jޯmhh1Ν;WIjx922Ç?HWm=<9|p-sRd">?00PTThll*<:Dgg'X !uf&Nga||&,eG\֔\.eFQ ѨT*WB~‹U֑k)R= ?U"+>sܭ" Y5x?&ߦ5y?U%B0aއ8_u ѻTihi.sy@?B o/b^G[FHx`uo]+JLh4͇5f@ɏsOe;!oT^Ƨqy^gs~r`i߫c* _C Gi4AJLh4͇G@@dUZMT6DNTs`&Cds'dKVUSl _rJ݇ݜ*zh4Fh4͇4~FyӏK$8AOV(l.>?^-Fh4Fch4Fh4Fh>L>&JSh4Fh4J輝1MH<-+Dc%Na#<5Id1p~+!x#doFtL4Fh4F&@l$u1ixxq k}ʌ]r?R|gyP bwB!O0;A ^ܓh\0C򳪉urFh4Fh46ʌ`c`qr}N3٢ 0Fܟ;!r+>|Wqzu)uh4Fh4ͻqmTuJu?c,&1MḀO`4$^gr✞ z01 DD$+w <FW IDATJ]ow;3 U&fYY/fssUևǸPbqs@@@:ep؛Eu/F.xn& ^ݫ6Aɇ`-Y|)MTѬI@vM}JcS{ zaQ5"~9Fh4Fh~6J:PG9H)٤6`NP}g%>%*+B0S2{B:A՘kP~- !z8}huCh`@q;E#^/9bY/mpFw :HP=;W{b_hJʺ&!q / wCx1gK MPtTXa)~ TvX ބD0bhUa9p~׼Fh4Fh~ \ yCZR;@ ;Wj[JlZުĊN3I؉h[0ʦI<f33ޘuQUK}Y2#! 4*ǡrB7Bh σH)qWW"UfR}0y`r ^,Qpa25&pgeԵ; v+J2 ] 'M@y~Q́fEŸSFV*arX+[(Fh4F\>ҫ k(ۿNjԵa݌u=C"HNU:^S{[{耲7RG2 ʞrP>VC+*{(t/TF' FB .;!P3}p-t,y5ZT!U%ṋʘT1@(<ʙJ:#wL*꽰T3]%a@Qei4Fh4Fs=/dp<^wZ@#x]da#]Iπ{d?PQSDX3zAɊ0۰ r\@*6ͅ 9*JLT_*⎂}dRemy=} Y^^E7*x}#>b,lE┐\ޮ2>ռJ[ eLMP}Fh4F|<|[`)N<_Iq,aɐP}̔*J ܣ2~ۡ3WiCB3ǤjnH>NOegFgP/wq'g67.WwD t/2<*3".n6}z{Gh4Fh4끏\ gpC1Nu ]xӔJ䙚$a`jm@G }ݳL,Y'~iw]T ] B@}_Mu=,%T p\^}_H6\uYh4Fh4k0jjhF<2wRφ o?wBp!s};5 ] :J@a>Ma`:^Y=׬8ΨQU}{!x* rO[Λ G|*CJ2M YeQGy7a3^7e%Fh4Fhq1w#E` ě \Mw| =#}9\ iv[ݼ?T)rՃz; 8sWGx!GZEkzߛPezYc[T'/>Kť*Yo~KB\h .@{(߭P})s0 1G0.Nh. 2^^Fh4FL[Hupa#ė܏TGv]:5D i#E0&xq =ǑP"TvwvQg3%P ̓PZ}fR -RȪYѵY Nk+VvC! rD1ͫ %v"FRPg.g ئL+z?N5Fh4Fsp3tNB8]x.SᕳXjuOP8/`wu?^7TE`t~cJ*XSB\hp7^ Ǡ^uA~*zGqrO6TT3hR=|>( UfW>N~Sյ/̩:P>d>g ]ǡu<ި]jAt9&AaKA6y5G݇[ `U K;T*XT"Fh4Fh4"JWӃR]?i"SMFZsra@8_ ]C]|Is68iCۋב;/tAKAh4F\\c? OC;~/3坴IYh4Fh4뀀S(WmhuX4G1h4Fh.TxFNY7h4%x|Fh4/!(/K4Fyol(m/Ch4FѼI՛F ;clB@(\zQ}y ؔ;G0}GF1f~0qP`ߍлefSSq/@'5t 4Fh4{@I^fe`-+"h 65b.~aBV0&-FQJ.d#:5H8_9dz ?FE$}Vw U6Om0Xb 3 ^)@<}u2i0W1.8 s=%BI\~~yc0t E"9ebyx:~H =ׇwu^ps6 <6 FE<ҡv o!\-@i4Fh4ͧw@ʨ5J"!dW&S5 b H)%OY3PReۘȔ1%KdHhJ)2 KJ$R+iJBfB(ROJ,y A֍%͸4GH%q)(>F!"<&h{E8ҡ 72+:@g+MUr9t);eO8Q:AA )?e8e>ϑc-fշi)r[KBsu7JJku{.s99n¿vm|bOw[EPqHuKr񀪿\?Fϯ%\DWd[jąI߶4Fh4u-@7Α& lK6o0:#cca`\Ǻ&Fqg]t{hĤ*+ٽcM<D;̕,."e$ !NzoQ/:Xl)9)1F_ ٔ%Dʬ#$B ;C;YzWBJs#KCi011 vG*~k"F!g9B@({e^-o!%|,N-l />Nn⫭_c~|=)56:| #Kt"gvPp Β~h8c⠽"jKY@'aF")y%޲_'/k❁pcpuf=0M}h=,/eql Pů&Eիp:>{'9*sKi4b 'VX:W$l/!Ϊ_M9y:Ag7%FqͬBĈ04YD]|"F[ڌ:0A t;ȹ9iDq:3[kfc ٥Fh4Qwg'M8<&__.8 ٰŸ#LS8{y1%"!̓_eS&ڢJp˜ΟbK=)Bݗts#!ްas_ 4+W2BΟ MEyW5Ʉc_f? r9ȃɇrW9bfh ibf99{?󋉷50]ds qWmSd &]. 2\Ǔu_塖hR8NȜc{xllH[ S(%NMT,~y ٿM}s!"l)r cqj1>? Nk#w*̉ͥ-&4ss0)Lڢoay6`pq<+ͻy*566l=ց)LV}d+ƢE㳱\,H,P(:EfdpCn?B07_gm6 (BsOȣ! `G]-wy#g wXanаqMjs_:i}H 9+ǩIFpƃ`Ɔ1Iʰ?5B Jͱ{o7WmQU{l9\`~x@gOHӣ,[N"V.ved1?? )7~ՌTFX4GZH!xmU֚y"$>FgSEKp˘ӥk4Fh4WR3VV^H!@J,L-d}[ x}Eo&># !3-$D+_d0Xu[>T ML¬l$Y+C)vrw=W0!3JXӰMb,&YKۼ}Fv2U&0|nKݎfCLd EH<#=W?CV`S4G[sDKꖐ2l+negGiSua* <:?J<g2FիPJskZ$98(0AԌrcj!%HE n#hؕEޛ%uK1Y83"θ3FW< i &)9EJ}T*~J^_v%< ^fށ^hnnz5 ky`PpQC u_{H{$I&_x,3h p&X Ƭl 'zUʲHWy_QzylCEۨULdeza36fo3bDކB1ڣ XڣljĮtNIx?'YӰ[%[6,J-)x8?)f000Igp#kRu#|!'SuD(C]h4FhuuH$G^̏o6+FH+_T0y"6аprxqW`|cY,1r="tg~h;7%o=?VYX@<)yD/?iz"ݹtlfm6%ђoV>n徦k#Rϐ fy ïͶ|'P͏[ᥡx6=Ü[גYZEwuPrJs?᱆ǹim Cid ռ9Kn䏋z\ \JاhS Vl,M@y_Ȓ% !RodS>47թ aL2XΆMԇ)vOpro66eQr!-hb{6'rߑq3`^z H9ѹDF+#lŽv,YeNp.97Gays;5wpÎ|o;9JV\Æԅ.tg8R9w!4¢b>c}hkïbyo<ދ'.64l|f9Z9¿Cnn|/d t)vuϬo7 #H[j$h㷰n®]b]|#OUCܟ~+{q]3~1n U@bnH@)`x48wSg#ϱR=n"Գ&TCF[y <1U1#4zy9֧6ro> !C$#& "b;G']Qs>&|MY  }|*^{G8C-A<^& ֧6p_LajZLe`*S@" !FEJ xagR)lQ@ZP CE|iF aM͓ٜ+O8lb6@SYΟdo;R7!o9v<څ{c椷PVP&aG5~l}ܐX@}(͜Z797K071n9U<["+^Q6#nh8T8ā~ڇ<&Xkn{6}n81XZE:<;=!qPّP#B& <ǶʿPc]z\T. ޷y(wt_O'n`vx)۵ syY,"hIIB"  -F4ް_$Rp++KJ] ?VC诜vs5FBFd_f['شd0Fh4F PׂX<<$ZDx!#-s f} t*)7|Wk%S_*9Y&01_uqw1"=UcOΧuIA LN/єfZ ?;6!ikwZy9q99DRq+D̈o b5[s*t'Z? GRRQK-H犒\LL=dp{JpsX^&lQF,R⳹1y# ^O!~'p3FB mR?1gLӘCU3QzrG/O-&CPh4FhiY;}c'5S6Cw<0ҫ؜|^/yO9:/@ޛ<  9fdYl7`U!xKCDq5#LXȈi#hH&"DaD&L#@G`6;HDDJ(*t. e.&6DDѽ4Fh4n4f%&̺ڦ)le%"ty'q>Jppe&~bHd[yZ"-̎͡>Wk- n vb0)akyV֐ $1')% lZpͤF#1sfٷBqQSfngMh-`6݃d IԌrO> ynJ̌l`h 71'6d.kLֆdqd)uv=/ٗwmSi 4h:U5Q̕S%9+GCuY_HcYb F+VzXȒ2~^)m@ew"dh I²"IqNXG^hZR$mf;MMrA*h d)qXY94+R+XWH24t(fƸ'q?|ձD)Q9{pu+D5M es2B_華 1~=-iP̟?NCT*u}U,H.@JIO_ vGI^[Q$_h4Fhk<,_k&KSK AurP|9;w'~Zk+ĕ.Ǭ#ly9̊ p=[4oyuub5*n宆w8pcm+vrpp#W{?P)^ No1l lnJo3ɢB qvU(w73+<ʼn%4f?Y9ιY}Exqnh;aʱ$ fޢ!f~r>_)Ly+#/SѢspũܒ}ƪjfW&fep؝Ec9ő.u:No>?'Z>ƫ2Ph J-SaBuܖuqK&ssRsuJ X:llĂ2gvl{{生F&;sK*b7CAl&Yv::ns?˚09t wrsfmW6˾ /?QzNl,& a[%h593-mnoP"U}Wa KÅkOeBuE5ceJc?"N 4H Z>0y5-چ)d,؏yJB@͐2̋gnb.0`y7n2h`imVy{|?/m㈳!-uvpqpEux"uF0Ar0{VV|vhyp#>fqNlLa^d@?1{m?Q /_aԬ+[I zUm,O+\|1y)9m_LK[hn!m6DH0\‘` Wi4Fh4u'@Cշ>y'Pʼ]݋~<#RJ+I@Od {+o^v_F!$i&鱺)}o؝+oS9v:/P/s@ꭃG+1`s:HcFy[7HIli1A9pp'$B %J-r&{W)xs͐hd|\W`u?Vz3^FJv?6S. F·9JHi40r9JYf7[nh.feo n٬9ҁ4BK{l ]i$~xi4Y c$YM'HĈpz!yy ';bsf&UUԉjf7ohyf{u0uMR %á<'ciM8p7&0DGd 8漅Kg[EzMDj]tY9`EbM ǜSdufTer# xݸ8q^w8X<@SQrcc?ެv1;|gP5N޻E]=Q#l>W"an|:"}V/q#aFe WX0~#Q#Y ك;\kDٝ'f< M$]O?G@ң9:@"9Kh4Fh4O#"J1 Zr`LF,ʕE4 #")My-t}O6Q?! ;p8Y@No{/E TukWUsdcsSim6˘*;@PcR,.fPb@XKkMB"_Sc39."&f%ԏ H0NoC\4Rxw37>3k$ÕCjum-,.r?!bk+@88@lFe\gT A`I3a~qpsw;P+ aN!@EV9 ;Ozs'9H#H0^IcI-wkՖveZK %˲l45ҌfP0`@d"94o|o7$ϯ {{9σ:\! B(EQEQEQn2HNOpUsbL &U $ =`N_ 8̆3[ ^G՘U#,V7ip#웄G @|ZZ@xĜʠ(((rsܜ^)6o)~ &׺k[6-C|w)7tW\sBlFNwaȞ-B{/kKl6X*GȽ%B76;[rxL);{YsL.捺x BREQEQE-I!6`𗀓>(t`W_ d<'' b w?]D3P.7NA(> dYsZ-2`/AE9J(e׾~6?sl~(,= cRYCxLC zD8"o(>,MB:vEM"Ya+)(c _KJOoP%k*$G%o E*>٥P Rku'We"gcG(EQEQEQ @!0mv|ud. ~G3l{%'fLsr㭂#_I#JJFJxXa1XwYمqԿ /%kRz ]!\+ s8[E"YTDinȭ#x2HPUZNEqK"+!}I.*=I)BQ-̪寇}a>\Lls6NX{׈{(I6<"!#emyW*R~ W38ZԲ%K~@_ q({@e4_/<8R@)X.N ^Z73Pex/̿"W?i B+JG405یEQEQEQ*<;F$xںq=Fz/CɾI >D7!|oT rK  n:%n"NY@J2{Xj@Bu=͠&e@%52OdK΋s bc? =VLovkyF礹7v uIIf+^1GocrenI_ĜpPd%sŞ"> 3d[HH? wK6SwEl/@.$XAt);OJ Ï[zk! 23>4^#LQEQEQEI P&-uDI,Md pJ Ft +=1S_ ![aŠ>+4eE&Bۓ`k4 <.285o8 ?+YJ)J.JvR{-[ $+"VdC{+s&yE>-k^2..sx6{IiPzJjvw}|7Or T\æOJ%Z+RyPV@U0z(((' PjO³NA)qg@ǖ5MںD Iƈ= /= N$BJrjۥQaB9 o\>Eʻ¿S ;9{Dj_Hɘ#' XQ$:~Hu7?JUعHf?s[^x}钅8 ogJKXf>"pe[Bqs#G,%Z]{Ɍ± xVPQD]"n OA=~57D$B|3RBo P(((E( 8Aq$q $۩qI|ҩ8w1$[) ΤQA YpxAY)Mu>;0e[U,`v 0ԳZhlg&9d.=yLUc.HD!!,OTch.gmߣY2VрQ'9S85RM_Fc*Sj};O+Nω.\+Y.c ̴ecF_ 9oOf郔!9c쇴!%`NI ޤAb4:OPd"(?/}r+ |-HfW|ў7P)ƕi)TEQEQEQ#"U0]Dڻ}d"2!G!X BeC̒AS<1.J"hH3܅ホ )•'(5)ʮ G灻NzGIIcq˶$^-3CkwaiRuIh*: ?#1 eAl(n+ﵝic DVMiOrtP0s(݂\[۵wX6Zbx^R} gN :3 $S*{4`XJ'A2`P(((Kgm/fK8^ EK"v3{n1'mcIqL/fA#C4 I!+'$@=(%4& vdrKCRe)Obŝ7`(c喁JyX;,{(Ѡ~γr}~_y7*-R IDATICmo55ڏ alٖ&%?Cz_詝 'eOlB;" HL#Ҝ)(?' Qs?DϵDVd+T@c}sc p;YGZ* :"n4cC-V⃶/ulH+'+Gr r%$D'%_r]ri*@IhfBc(;_+I{CYSHe攈0W%*>"si~0xY3 '; Զ@x)j#%a;;xo,H@ -+OD>0Tv[cC]FF0w5v2T tG˘mefھW1$ r\^[g}tE uQc+d?_tp-qk8?v5CMؽE"P!::w\]bAq(((E^Kgw[#r̄̓Q>S6`w)@vߙ*xE'H߻ܹЊ?]ϲp_Y!v&2li]w-|Z ϺW5N@?_Ĝi%Ï_-pq OlR)((_HmU|Rn)((ܖ@\6YEpz4RXɋ#q^K@}2Ľ HCqJp?$k(((TR>T+P{Ξf[B7[CM*(((WM((((N~BWEW(((+s|]^|'$Ar/unJn=4KֿSiBd@ۛ{ nHN@'_ǤP{rM}6BvE@}DK;>oͫ6VB5H?g+M(ކrBZs`_B?WN_}m} @[E}W+((r}ܜtHi9|8GPٙk).{sz6Y =|fTvdCMHPz I``b;GTȯ/wiYM} ۸'!n` NH޸@v#dgC5!@͔.?x~x]rd ѧf| EQEQENn2D5 ~LΙ WnRLIAzBNr2nDw$>Sk3n =Py [''LUHKs=}rKOÅB'C@杳ħq{na O# +8Px]g,&r(((rsTKƿ%|wq?Aۣ;~O޿926~0)^~@Jڲ[D!n(q٣lm2·쳟Oo mIR6}gxdn^_9<%7gK̔05pFg! 7ARvJ ClH̯`Bhlt7Pg1krG}d}TntV`Vi#&.!upeL74-)7<$ z\2|ئt;3h'Yo| "XJo\Hu7BJñ<ɖg]Zs|Z6z.L#-j@v=ȺJemkg>Q2LԲS-O\lTwSEQEQ .@f3>6ӳ`,8A4lU[^ ~" t?P7CKP|237֦P9Vq "s߸G? w[ Hq#JAtF6^F{h(w*VAvl2D2A#ePx"RlP-AѠr fK|@ry`_ͳ9n'"?W7`.؛fψ#6m y`cfۄ#}j=P0e[Ģ̥iTrwiiSdH\]+%yI? d+E( JV0吿 :-%6vBOD5o9_"DW6> IkИ `.IN~/nQb$3~Džz5aɔgjPk+"$^/E2(H.$61mC=ñuZ-r[]@n]?\ ?Z ?V,c:peӦ ؘAxĊ}-ZWڏgSOC9ѷŝ<*1̒X5=,*((qԏK[dgm ;}Exx+L_}-˃F}́j)f8VX2"";ߐmgds_! ɀKVU۳Aa -[ ~ ;|I6ɹx?T߂= S_F!+e}PYĐ.w|)"DT߇cgȿɊHL?FKjH\.$o:@D>𺡰ADvB ȭ܆a['?j;Sfē2݂|_-H!6q)g%#x-RO䨈9ebzL K"f: 2Y}|כdXۚ_dΥBfށ"$a{kp=dvE/?sb'7 C:;ܫ,m5歏Pxrq P]y@D Ta^|e"12ɉ% ]P;c@f5>-c!:`׾ r(o"-An-lƨI+(( P7(@5$~@W;#?;Ĝe*l&`KR&Ҝs H߱>P6B򺍒tbvw i_t'ԻE:%%.C<`η>o:8ETSIMmҨ9$R*7 0Gd(4vOF> mP"E-2G!9"{>YxjϋC d4[vxLɐ#cŴ#Rh.sDf7{'lFR0E#IrMe(&,l:Vf$lv0KE]2f}d83$ɫL4bU!65>C[W2@/R Ghس0dWArh@nܻ45-(X(v19}ҜTNpV߮n*oB?__}N"?w5 Qq^gIb2cHէg%!J2,=&BU"@((( P7B2xԋ1Q'(OAۓ9qzZʳH!4i!|dq"͘ğ06+l9Q㥣l/UYxrK"nߣS"^lIT_zXn/$z݌G&f&9qF>}ndބ'V*lel#"QSHdl'; [&ɟYز]ĊtPJR+5¥V,j^EKR3ʽ H&[飴" HN|Kpdŧ'9d3ld ?O6ﵧ2!H,(^V-vic˘%!;C-cWħmk?jcpBzi8~W!}ZJE'ؙmU:r7z16ZkWTL$I"zAQEQEQT1q%-ۤ4]xzfq9hlE+ *dT޶y5+Ÿm)Y)K.HXi7F%"4Lٿ51?u3o}x2o1]Rz`ϴedONVJwAn_ ~SҴ 5;esm"k:7?_&qgN "S)'mβF '/BoT='>|nN4/ks;kM!1THgXQ.F8-SC=f8j̱|tx@RY[ȸ5! A:q>;AR^c}+((O7/@88l NTp\LZVX@jKI&7Mʝ63Ŗ JV ;_lF,Jv;5y w| dSNp 8lM L=f: 5ܖ9`Zl2wK JHKJٌgm8t@npfH_1g C/'9e/kY(6ԁK"0iifOϜ\FFD,(qd/6v}v5RΔɜ3䲽n"",)wNiCJܵbOw cOy9䰀"8rA(م+Z,ca[n: %7D I]賙o goX:Mww84Rwd;[Ζ^QEQEQTjq ۧ>Yww =JGj[In;/I*+ErWAQiT=7xfCdC ;s]$VdYW i4w-D*PwggOn+n'\Ik_f!=8K_)IKj1->Y_~R5H{WZ k?'CڒźckX<9%-u,'F'LΟ*Mړ^0)8rZElKNۆ4ݛ(%}aOT,wmȉ&knj=/g\ aS[2ɖf2K%+:iPCΖk brPk C>,Wľ _,oE_AHv +1O O$9 7P~(((OO[AiWq%/'~L8A ;1&4O͍I1iyפ17NvB]SzF4t6d_kҜ*56ڟ.9_mJ60^ṱ~5H?dNaZz6kQh%rs+hwSd괾rd ;aglt}"X2ί۵wFȘ&i[PUT'- \_%@Kr {|n* Mq {]U7J%Զp=fkokj=Ty/Cs*⊓~4L "63: W =6ZY" +N B*(MWm\eO1%)"lk$ۡbť|y;h۴awc˱>_r+C_C뙊x?;2v>Cm; F(=a{xt0x2Fccbf%P|^[yJEQEQEQT~:}I^вqҁswImɝ$6^3$Ƕ7̈́ M-TN6NI6%Zi2_JavCk"yeIa.hHϐFTʈc2ڏ!I쁁듽 Dǥ}\|["p=VhHSiNAG6Kl$dݤhl\HP 1`{"V56Aʗ6M3%Z|g-S\!~/;Y&ks'@C%ھPL}_įD(KBZ!<շw$*Guvgb & 4gW|d89g.٪B6 r>aP>wHvys " `UƼ"kniz<4vCҒ ~#㐏F!1яSi 6k/JVrp֞\00 WżH3D0>/Qkf2lPLk ~ ӫ%(((ON>7z]3g,o-CeyBm܆QRL ڏGQͺW5N@?_Ĝi%Ï_-46-5((W9xˠ,df#u*1gPI=]~_)yNC\}((((D;I4LNQn)}`JLsEEQEQEQE*@A;!y[S#*3_NK S $p'}LNs̅@ &7{/d28Qx¿.ĸ=[>߻s'/~i*(=^9nI; {!)}渲2g!N9EQEQEQ~d>ޣF\ 1 XLIS0ZZX P}9'bj\B ;> ^~H%SCi?$?'NwZlIu;C=,krm@t1 %(>x ȩ}՗d[sYug>/j񱛰sԛ IDAT7pڡe(9(((^oÝm d zbLJtѾa;óBSt h f-@p3$ Bt-ٖ [Uu1\p+{.?lAd]F!: bp3 @$u= uiH.onz됼"ЦYy(qfcAJaaq] Gm6UU궁I :δc.cF_5f!v#BQ٠_Fr1Pm/!TRI{ N h?į`G i/p/#HÃ䢝g&n/A~Đ !y2_qA >,lO\.1e%| }b0x,lwC._0u8s.2p^\#-BxP΅XZ]Ǔ*'M8rf.rBaPP|H;(D6->7;іεE\&Z"Wn Z)>,e7ݼv'm _Cҟ<7DsIawbP)@q ȯ"0ewxA_9y.KS~ˣ9 FNF!AuC/orEQEQEQnu*CV^!7g#ٙk/x+|\ͱ?ھ )K@̀Cf 2Tߗ,"$h7[E4h|ͯ]ҳ@A0rMH8. |[6@n ށt/PC:v^.c( odd@m4IHaC.[?#RZ;Rϔ;1ŖET.6lOhEƏ! (=!!%yyP|\Vs !;O8x+DI}c"BR&AfGĶ[% wKT\>h+=mq*%.!@~#Cu+YŦn=GG \F g e+9kwYQiӰ㳐Y*`aB<>o'dYBȮ`wA}/9,NalV+o@w80 鐹_ qO~`0DX@ݜ"\B@R~ bd&C#IvշWO[>vgIW|H2Eғr`#V  sQcΐ sH֍(-'98Tz QIgZA'KH!)pD,W$#n'yɼr&Oʛ\AOqZx.%Rvְ9&?Cs3E)?/`]EVSeOD䄹x(.@zH7VԜog6ʯAs4)Bp~0 ~^3_]6C)ݟi~7P}h~ :)7HN;y XCcH05ҭPo\sdTREQEQdar/&d 8m0'M>)@\dUd#M2:$h+($= Zyl" C e85 Jΐ>qV2*n7nIzS'm ~xxcI/$-}Z8$_re3 }ŖYkY98F^c5ŞiE6M!}֖-O$<!탮ߺzW')-z7`:D uT{M˽dpD[ZsV߄2oX&bMfxnQ_dA~fDzB2^|}!!HZW 떤)w}7ԠεC1\:X3AF3UEQEQN&3rW`d~CPɆe,ʭX1 -cx"T5SOzgBcxkPͽڍo7q h\};5ip~5icXL񰍩1MEޟ1Ğk'WO#+tؚ:+ȉFݤc$cݨbBA:0lDp|~2n<_#ljΆbڷ~om_i>BgfgWHVz+UBbyMdKgg.ǵAIZNL!R4oQQEQEQ/OOdKyXGzxmRZC6@wTPF="97F FDmZO(9=-"=d鍔Vy-…cdBY CL$rGR"8aUzdgCs'H7v#%K4{\Cl.'1^ɮq4W}PΨA' nAz)]-,yEX̭GglxH~8VIh$?"8e)I},sJ]`AD5/1ҰBRr`aQRV{~dm|Jmkr@`p(U-ImZ8_5uy"$`u&gr*bZ>Pb0kE{J5 tyL?WN l6>!w@kds,9lgBnGm 3nwky֛k`wCY1YX{ IYtY\gK߁((uxs!K.8^@~g]ܜ4)7+d8e^q91yGj(>ǤTs儷_ڼnɺ=思dAHa>9я. {_AAdSE|m?D Z"P Ȟ`7Ky D$JFTʻs57ϟ" `1}"`efKf?Q2o b#sPlڇ(K6w=qϷg s|]H wiU. \caeG5}+(( P7"@MYJq>{&iM|fm4$!&y2&!b FٹQ쟔\-7a +BflIqpvEȯW6EL&LJ7/{*-&lL;~VKP$6، "\f\ $!.MT:8["e&5_FC}cjl>iL2V!IeF# "׉LWHcj* {'\|V.T jT9( ߱0,-/k%"PDP*/|JOH7$6"rT{WC?A埂.HwA`f|2Z3RHAeؠpjm',k?b[S ߀e\ Ƚ[P(lnpkc"\\#V[K-b/it*/JSs++#F[0B̄|[NS*;_x' &bf @V+ifڵZ*km-qWKKѢ)1A<19t|8_=T=;sϽ~_s)2Y7r^&@M^5= r:ߘ~heT65+(((*@],[qv, AyS{GDx_ z+A ?ಟog{% (MuFn^SMg z1|Sh8ɮ0@2'{ hSi{Cd {?xPX#.=c!h>*'ڸ?lsB NBc6ҳ)L2auůmxH_P?>܉-{#$>3ĉE_Or=Ðlwc\/+{ll/99᭼{l^23gT(߃e0nwZSOJdBK~Jƛ"}x>[0ݎkP? ]f۟מw>h> v9-q4h+d3j8ta={t{A nՐu1 {kڲLA&B#5}ܼ;3jCdF=?v^=))#v 2Bw{927{dS-VKEQEQEQLL\fk1rw"F$GTI2gj/@j> MKE2t}߂);P{h; \[~똢(][6~~籓G|` ASEQEQH ⹾2gͤtE;)1/I[3$Sx1-?)(((eB(ÝW{3fr\.75D(4NwAs\_C-K ~2EQEQEQA(僥ҟL<Lpl^dKmEQEQEQE4((((ɯH2+]~EQEQEQEZ W=@igU |;ȝ(~)/{$zȟ+!0? cqQҤ$/Oq"PPXQ өa?ӓ|Wϵ^XfxP}@MHR^ Կ Tn]';O(((gjTpTW<Ԏ껟 -T rH:p|eerPYN,K^{rW64ȻJ!뚁7!~Db'=Ou"H&wB8Lm[A|~\< \QHt;NG>2CTz0l*((|Ҙe3DGa:ӗbə<k'Ì̄Lr|eeP{-@ i ;F tB=v{dwI @E(;/ %$Xh!HmyfoCE5M&@ kξL{DQEQEQEH15*>C-{WRfڌn=ꕽ/#r{!bS? ً2<2ׂ y K!= = fJvB/ʭP w!{grۡx /A"BpV_,Ŀ-F.3 f@I|;O D/AĨ| x.@C|/*PO2!P\ 6H~H_ns 6IV粄vA4{i[Ż! Cre=?:,5*=m&M|ޭg=EQEQEQO0SZȅt.YEеmNgCa.@^q2E0% weJR!'eh&ɦ"`AZCEhN ^Pu[P<Wm9 m&` WCs&Ծ/η5%S)-#+!7'فxBPX.esR:S@03/G \fIlm y҇Z.Aa Tn "P`ᄠ! !X Ş\ <A _\@a3 fIe4,@ͅ=PͧePkX¯8r.Y$dZۣ~'Eh%vsd"(((&Pf ^j;dE7R^z LN^ы< X ퟓoCXM< hoAހG!}G]&CmheseB'?kD|JC̽^2P "UnF-\:ɬ)^]D(_gG2 ˥lnл 2 '0-+bԄL;f!؝`Cjeݐ&-02(oG.b_ kdMkAzs\#-y̆&YHNgEBs+5.=Cv=:i~dAɺˎJ<׷)GXQEQEQJCc;-Ob7ӗWm${+z1Ay.K ; v$cTL֓.S0.(("D)ӫ,€WqcdP,"Kr~-f &w Y*WK_"pK?I񶱢W2pds hDl|ѨPh'o~{DL*%Iy❮0G`Ok0[PM(;[`/)Kп倁OK&T]z8׌6]Ѭx01vg^n$](EQEQEQ+þ`6t|y 6ib LL]aاړTDNKFS0}d䧥lJy^dgt?p{ʈha*cHl$"[R &%ɛ#^zȉn)RVu_dۣ `'ܲzdd[wqk|K IDATvt~X3Uedfʄ^Q~{ad)(((W,7X0;6Μ!D).uBj?>Mß+7E:I_pc߲ j!>$} ,7{e^%Yd`OA]PJkEkqfH$.wa)7KRZCP됞Lc־ub) 0x]ߗrdsWEQEQEQ>\Fʎ})Z4#m⚉WE|RKvx&?ҷz; ?ZgcdX.gʭZ}B}͆ 'Se-FN}ss#Rﻓok۩/O_N+2 W~UJ(A2/h?qNUJx3hőAdAgwVv=kY##1.0ҰDA>N~Vk7;PJS޲ PEQEQE1u<ӽm&x7j`w?goLJ)ʇo{R %`64I6Je#46Xz"\ A+Yo|/P?w/CNjf7dIkAz.e0ԯ9S))+,?2T6SH]Cag'r ,j/@Ai7H9\re?%obiMҸlFHOAW-Y!{6(ԭEHlY ~J dL>S ; 7B e:$1Eh gMag'Z + +T${!<@^G$(((|ܙ/Dy*=?&KIN!6UeZWa ; ATdσgd L3HFLz3qycM%UFJɒCMi|զ[^W$oAv@ۯɵC< 77ôߔ&]$hN@<ꫠ0һ_|Ɖ/9{>7ɉz&̄"[=cSp͸F{-KuHJKkJ3r&Q2PM*[wrwҞ#*Ӿ֨S.MIwVe}J5^rY)'4zmxASٟ1HM7P\' $K˔βHR-9FOCž(((r PS^B<^S$ ; 6I<%O`|lw#6v=VKM $#8#%FLh=ZBaoP.Kdc.hˬ] Gנ!Wz6F' :їef(~;QAJ93%5"6@ v7$4{O@(CN(ĸH PSP{AHC8UX!Gv k exV^.nBe,l "'UJk$.OvDB0S ʞNK P(((Wl{ӧL3Gv ߢGo;(хR1EQ<3=m|c'i M((\O8E}M %%E56WEQEQEQEXTȤ$-{_z'y t(((($*@]PIQEQEQEQOC((((|(WEQEQEQEBZ W_ 嵟'tC3xd_xH;h,F)`.${ {| _Ba?Ȼ O9^yjO~!*߻4f.se@G*((|,)}:" b;搆%h=݋([GrfF#N(.FS2!;lb*Px+XHO@Hc-&n n(,+W0[͸m ~ ((({)}:Oiœ5f.bpkmsl5q6L 4~2 txୁoC6\\~' ?cZZX~62+|%Yd@ _[((('&@%$|o- ?Kϭ(0j͉"cu5$ )&AA? ٫BreA rnB1$ zHI'>3$[kҵ|8#PFgQgsi'ŜWp1 ׉]k!;_@&PJP~J!9!}Gvri3WA|_ oz/!}96($7!/P LՉ ͡B#9P cm!&t^6CV!#"c*w {i)7Cq߂iGFſl gw-op1xeSz!{B/{G8MP\+^: Sr{'. EQEQEQ+KjU\0w A|r'^¬?0 ˡz3DG0 3!+C*lt6<(gǀ1җ)ԉ; ^2 4&cEj&؜ln4g3!w<,왡emP"؆f@^" ">7Ar@SyC~r<ūd[TwhųbTvkx31Pts >#kbyV|E9sE(3! ˤp[!VhWMS66Bۃ.Aa)@ џnʧT`)? ։l~t"iHo"V;"ٟCvy)(((WuWBtl'%P^riVauE˳|^Cs+CxdG)Vn֛!~ʟMDNAcE$gA(o,x/p-S? hbd 8'ٜ ͭcB̹ʭPX(voPTAo.Q&{ P ݮWlDgch = "D|Hfi!_eX9w6LbB p`0WKpe'Y2P{NWoǸ)"W&t9Im|y!A~>NaoedOџ^4垐6,>' _ :?cKU+L6d>y/oCrhܸMPߐӒq̆p=[[ebFOSZ~ $cT Xt')((([>\B6tgDBS"FT2BJ)}YwRәZ{$kū0R.7RfW pV@^N̦)9.Ugs*n̐<ʛ4ƛ$]"b3ƈl QU )!D,ޓV,h~ } sBۜ-Iv Y줐ly*"\~H@WNĝ초oCz5"X59pĕG!d6u="_͋[tp TEQEQEQ>|Ty6o,<]1iR&7.5RnIiYCۧz\H1 _]m(<| \yXD yyP^G;oAvX?7`'<\(%4a_2yZ&7S."⤶.6=V΁v^>\n/׋ ZK٨Wk%(((||coL;U. p'd0ε`-v.6cTX=0踹IdO8S fA< /P^'#ci440IL̅ :7qPO+P->JASl٥ψ`U\%8K%e ; /|Aim y唸_ 9]Hp Կdz%iW%F%!F"r@nTD :C0qbURfE K E+.it[ߛԿ[yˡ|\?=&š?]hx/M t-]24@N HxSp'6nI?P2bWҷ!^|D+= V\-⒙l/eھ${1+S+((|45}1e*aӈB3RɾEI9MF,&&t5 vtM#&k1)o M&jU)͵Ҝ:o2T+ŵPYĘh' <#V\{rZ:ׇieX+R ODFQTJkDdOAr/U2l(@~0Op=ydGp,6a<4d@ݕ7W@iCgPfZE􆲱Ӹ9 WrMʽ{) E3u>~&L!yE Kܳ9X#0C.qHLfP+ByHZY='KN?s^PJDjv(((GS.mK]1K0WOPIl{Oaft`bO!ř+0~@~mh |p}L">0ݘ`],&/%';f/Ì=ĕۉb0sH>o 1}3(NT;ABMD od?sq\ ooؚfz{3Z26WJI=ZkfGZRrR]DM `'M鑘٣C'xK$fd'=]X3"v.A=ƎObb嚧#.FJ74&}{Ѝ%SũD\0wJu{I[)DJ _tyg~o{#N-DMQEQEQJQ>;#Tr)> 7OEQ>$EQEQ4 P;fHcoۀ}ɬREQEQEQ壃 PǓ!ߔY6iO(((C(ϐREQEQEQ壉!PEQEQEQEQ>HTREQEQEQEQ>PV:_ 蜏W4@8* 5wQiOH!7 %i?Poi\4T|=(((Lz=O筣v3L / L@0֨_fNf>̄p!%2o`!=͗!CECEQEQEQE9Swo0k+@+6lQ_=H\cĥHჷ_plM^/,joFr=PQEQEQEQ?g?[r7ABV/R/}p*9ё4;t5Pf4)hO@0 |V[9(43!׀M,GC6DO}ٝMP~7x!1$Gۀư!;#s),%A64 Bq=4B<٥~ ٛP4_]07Cin+@uP K@l { 迈~6 $ݮF~xmrn4B1t+(((|&@EQΩ@g-ٴL_N8}1EoOW#B!"@1` = I6(,? VBr\>w˰YZ-˵JPBa%0!nJ  526;#װ"ع(MPneb#l "%Ĉ|c6`|V>C..'U X*1jt_oau KDH;E(((r|3GwAy٭Ji4c]ыa΅|EBc N`;Mp7` 0s MܥNhd'VCed;E@AzXUj839x"_l?V_H4h V A~wIL;$Kʦt>Bv IcuۄڋS.ҋ_) %'1="d W;]C{ߕ)%ÈbcvzjvO%#r$#'=nVBMf3!"OI ++ ž)Ctgƺ@ #ggq٦wkYQ[SrXJ PQody%|Ec؅rq9o(DOCmr@ IDAT-.)(((ʕ+@YKV;#`4*J*pdwAr {Dۉ2F %iDH/*OJ P1"^$LIO@vM 9Dq(S!(/6H~II܂YYoˈ!|mUaM3=gR EQEQEQCr[uU28? H[0]Bs @e*乙dL~Ϲ% Ay#xE={@͕MkD pJ$;@d #'`4S {>7*وKvg7d1REQEQEQO(OX!M2МN]RP_N\+/> œf1}@Qꬕ#^Y&f ͜ST1̖0dY8CNKKɝ-1\<`WAsp୔kGZ/gEQEQEQEQ&ei_{&(1.%7ÞxGT\(!=lToGaTnu&ej/]0EP+i}2 ke}P$RXX ^7DBq WL`@\z0e' ?&Mؽ鐟lpr|h xtr-SHst+(((,@y7Qb"eQ;Do-.}HEP]g'E C-R;!\("?]m_sbnM瓄>͐GVHVώBvJzTUy DRc+!ɺ*նГk&9q.;Y]bgHdBǃW7yl5Sel<~Ax?}xYÐr1_EQEQEQE1eGqux*C$.x!{B$kɣQA!m5YMWb'!=&RaK =N7 kQWfD),egf yM2l$c)K{eX;dWHUug` ;EVj/BzѱX fCLTtZI[|j0p'! DAή+3 4rdh9%!KHJ @2&P¹gSȆ;ẎLbn5&٠)(((W\.fĦ<3}&bc:BL:x_0݋3 9seFu!' 7n=i`m@؃ȉwsG홙 ]'5t>.rθaOfذ'LTn-ݼ6{!}h3Kws5]| 8[]Vq+BgztX23RхR1ŪDxg~o{#N0-DMQEQEb%8=t=0,< cOlUp # &#O=u8{G#zNI^]`3|!}# ŕ /ljkP9o9c}%dƅW(((rhCG ,9!۫⎢(((|PJxR* mH`Ē:EQEQEQEQ~|rEQEQEQEQ>xEQEQEQEQdP&@Bt1PA<6qaXSu;zS!DNKl}ٯꬔƚo2K\u1M;_pv}-KUEQEQ#psxEAe× :UC̟lqf~P$F\a >+A5t_{nH/T 3pI̸% P(_ eч+̅d'?YP,d08qOIS~c = }z≗(((6S ]sP^r6K0*&(U:/,3 Ԩo<>!WW\P'LYස >9I f>ŕMcxHc!u9]kj>;%٧$H%h ٌfS*(( 6lcp!O)-Ha1\JsUT鐽y왊(cۊ6d|[JVQ> y=^y|"lUs.B+?[a<ٛA34((|dnir4@o+%wZq^+O+ߦ0wD|>0%fH!} w@am!~ayͿw !(,V]I?K@/"z; \xnfnh`^#w:? ujkN`ʍ-~Vo ƖyP*za7CS.Q+AiJ(]T)Ԙ&EFҜyOBP hI0mj*@ab2|Hʍ1VV_*׆69 fB ݳOq<>s(, {~fa8'ڤ N,+p$[Z\|0Sn6E+̗!es" "@䵉PI n6")^:o_#YpeA۽_6Jyb%zn<ۿR8yۡx5{#0Py@'3, WA0G|+H e{:a vkr-[x252ˠtYlv9$itARK׉vI\>x _6·tѨPjo"so1*~Wd*`kOY"$'pDćzefiYX$k_+C?f((|ŀD$>US^~'yP\{^п " IrS&7Q|z($Y`w1ZK&L"U5ͣ`9EyT6IVΰML,d4_v,ĭԾ+>w T%h3= Gt@ۗ_ }GBz@5w@{r7qxE"4 ! |WAzj/Av•]U= -aǟ_vϤm9cMO@a0%I(1:ھD+薦.[6YZ(lFW3XI]Xi7HCOC[mP\  >  'W77n܃q6m)/l)OV"_LD(JWrj@~" $^ͽ5iY'o^|o=.+6j79$Bh,%R曐Av\^_=(((*@\n/|{l ŞuxkɮprkW̊aRCL ؄K6SE\h@i_ kDf};k4%d8<'c C KvxY/Uޔ9)KJk&.ٳ='E'?RN {%IXl&SFLᏥ䭰O=<9asdԟ4 }N(Yddeg ;-%wa?bt2AKO,#eIh̄:cO[ށO\yXW0-}1=?z!/㋒]lS;" =H7:?;?XDl|?; ׉0==Duk 8{V"ֈ":k2EQEQEQjXd6i)T1N]֛fB){#E2'pfcBB.*vMr7z9l _n$K%l pBΰ%}qxFlWJC( 'wy/|;L3䄜3H")J2I,˶\V[VVmnr.]oZ֚)Y(1X D&LΡg:v߾5ݷ|܋:? _!"]O(|җ93BƾN+xο֥*KMH Iyޜ2S"=nYX7ɽƗl~҇vM= ^:y6J4ml2Qm"d'D(pTzH(^\s`|~@@i `-9" ҙ?g%˫3Y&3?(((Aֱru6Η n2Zi(B">6K=3):XI{虡{T1yb\_-?ׇl:c,vy[α3Bx(;37"σ񓐽62un[g':.vldZu?O׮g֑Qv|:Eu_v"0LY4cEޗqprq/:9Aue_>b@v\R{>7~ZQEQEQEQ.?%k!ncٽ']ҔMN3kiѷmk.[ɞr/$~o\՜hŎ6ڰ;Ej B/QgskcfF!'!z nm1$ȉ[v0XjS0E"tUhiM4%+ADxkikL&"sB-.H4Mj7ǀdLGg{MצҺ6[HeM:}n`VTJҗ"ۤ< ㏺%۵W]c0 ꧍ 9LY&RSɐx%gY/ؗ+UQEQEQY Pw x<<ؑdǶARH_~=l;q+{x~٨vonMwCÝWtl%;ϧ67HyQg 7ieQ) W=\0e&N؀!#~uuάL۔2fw>2!x\W -فkd'9[-VI>|.%WD9݄X­ϻ eQ|{M?A:a('VU=EQEQE P(_[x^91A/Btl'_~ ` La0\l}IvRcEP$ʆ8d'GHgBN+W_ez6ǵywى 7rGW>w>KU0 H 0Hw`yB~L~_DJ"${P4i`e @6"‰+D^oʮ` 靐[ϋH[!Y>S@MbV e}s!N+*Z1HTSEQEQE(SO`-^~B6i kO%k$uxQ t6//'dClcBcP+xBv7Zs8i=AM5?Vyls 6e 3!E"ld hmڣO+JJadٔ&KOI)"ضK $q0;"Qy٘9"g0jt\g>,Z̹c:&'eۊS"׉a&kM]?"JG?1'hB9dޘ̝FN ["@D@R&C9f;v)%^Ve-7_ݼ?LͬI PX+8&$ctmFNLhsgIWPD蠈6pB)UIHN~h@5 =|:s[OCJ\WIƘ1.·'RL2CKzXu9W"A7;.c-M!h Va; !˩((()lbEdx Q;~DvAL[? ͣrWTcˎR`ȿmi{I)o+2)cձ5H]iM;J;Js}c]s_h9}DV;Q'aMQ8n>Ih<GQoI]_ֲ9-;_|4Of]{ &ͳT\ݾb[G3J.C{*|}|TC# MQEQEX2{4vkŶ6Y;[ga:Zn|62􇞝4f&h{.&;q=s^f$Ȍ1<߱u"Llg?#H:>%YUNȠ+8q0r>M$<{ןOz?'YY)c ]i'Ͱ8zH:sq77C:ڴI' *((|Ce(qj CP3(FԶn`cοr0vI&e!>}](((2*@]$~㲠vӕJ쭎rٍ\cX{!k5QwC,EQEQEQ.eT9!@eJݽ9&-1-WEQEQEQ>zxEQEQEQEQW3*4xr9Sgl44hg(2qAN̺T }5հ}zZ9ouqf.rZ1.޳gTh((("UN_'[W iSA\)_S^wOP74U`B0Ԟ7/.` D!%IzDhvMҝ.?"?S>6_FCؿ *9;p,EQEQEQCNU: IDAT'\q0~Bccz}Ҁ[Ak뽴5@"_rx`@XH)kџϢ

+Tv(|5N3!C15p;A $w'%d~ 0 /@Z;2d{ӑJ7BIlO*]W/.G(C` 1 A~NNBQ(sTG Cu~ E,:_ߊwz#AGo=\#3r_5̓yF6CT>-qD5hketAys&(((rYPʇ"&:3+_|l]Hy'KM( Y I2l'6eR]W2C -,&mr74JPchrCrMvJ5E^2bHMħʄ"h%$xJ(|w9Qx5}0ڂ1^yWȵ!ze' $%Tߥ.FQH_nȭ=yP. _+Aĥ.+O#etaNCrlSDMK65W="Xe5hHiyV9GTz 'oT`LNNLP o9=-Uk5PC6 U8*UM3>+sc@I7EQEQEQ.MZ4Lt kn>g=6D|ƽ A29\牐ZLx'n@xD*24߀N[(̆'ۍ5 yLAz/Ic7A7`+DQEQEQE(Gt|{ ZA’k  *@89 47YV=3P WDjΓlp%4IyUp;a8-]ei|Z1=ɣg_vmtJu/`33( D]+!0ހ{gUL51B!7qr&Ov6WpYoDBEQEQEQ.I>Xf7860~S$l d;BӒ9I)Ȑ !ے>@2|]@iC LxEW7 9'D323U<4'oӔ0d,MNJ~'并.%Y@& "Ja*),uc? y85nsq2MwM # 7GN$ayӒRNwAzȾ= 9 $#Gr"ߕ| ϻrR=yPEQEQET( Y:wt6Cܧ@վg"EJ śԷ6hBC"dc%R̓ruN@6t^Hv״l#&$SiJӎ~4 zb72r`ĺXint bxxIJ%P}3Ů! lGn-C4GJ7KF[Mh$exJ1v?MEQEQEQO:vB<*0PwgDOɀ+m&jJ: @Yx`kȩoMO̽E ay:YHK9]0px# &9$q !-sBQHߕg֊z+!  ,YNhطm25SBq{REb{JѤW&)K|>Ax''`|}sd1)×l%1=&Y  {bB^Nh[KuBzy{Uu& ܼ K23-dZpI&JGM~7=DA/t@N.@]0y+((|(MTn ^o &,PZskmdc̮E]tƝKd .$ % !g|-…zvW|CɆ*rD_#"M:"Wixd7#=O7WAv|9 @W^;SuFl^9 HEk z[kVmq'7~eb5)u}m"T`θ|OqX/(AX|P+w.EVIX٘i"oBJ*dBʤI|dҕn[6)p&CPMɿr7@/W痿(X'Ej~=$[.avD%|oր }E-EQEQEQ#"@b/AbLXf1!b^B| SH&Silsd&W&kl/-pD:Ԟtb#14̨Qh&B E=)'@&>Vʯqh#̙dN;[Pw1_urK(m,H [:6~,M.$cD+.bLC|#,Z91_`> ܲ5kw}Nug=2z/dvBqd@9چ5( WH&ii2",IlҔS+)\(X h(0T,1#]>C]jOzxd׈S[ȯsbqUĤ_Mx6&]]= PNkT(EQEQEQ S,mڋGބ(mB<"f,~251+"?3"'Hoܽ/A䰡 &adANˋPC&Ƅyygܳ \[u~Ym]w1q2ә@=13gkJZ˒̛t̝wdOhṾfgxy.sQѭ\5%VTvqfY#Ou;9ZcL-n8rcӌ=u:wi rnsE9߯eS~蛘|~7ְbQo=?vjhxD_נ)((KfoByץS@40%&-,0cux3>g#Lo$XOy\}!֠]!edrZؤ=CD9s^=糞RD\<߱qJٌYӊ(((ʯ@Cpyc#נ~U镲lEQEQEQE(TI/8Lr1: >+AEQEQEQeu3 vXp\L((((iEQEQEQEQ_%ˀK !cMch`G@v66P]K9`XYc\&IC`^1ʃX9)|~>YVfPp>Z/c-N=WEQEQEQR]_sdQZRۤ Q1'K܃.h;ߛECs!w%Az:`J@tvO|m`,4}Z=P5 ~y@j}qm ȭZ B?(((f%@PXu'^X >Mc~:(txx*^y^Qɭ9A/MC5h>ȭ*Ϫ6@~ȯWNC WɠC<( Yg<$nMQ@~5׿OkIQEQEQE@EGIǎ&znś n <;orYCWܢXkiz+Aou6B&h")#lg{PA2B&?   +‰ :!} y@P#@k;Kq9]m>u !1+!w7o+%GEH~ }POͷ"dEomPǡPI 9(n?<;!X,e^6ACT>^)?K_m#߂)'6?OmPi= $@llo@뿀. !|w@04_֏WUgA铐hQ? )P{WϜn`{ ӗq o-)(((E(U{r%RilK&9}6Dз!WJF)㎏g#dHG0jHw:vʭNTA!NHO_(((?{3Y]Af+O#= G!ioZc4v?G| k良aoƞت3 )+"" Jh P5P_vؓAHo&7eB/dET1xeh  1q"= n6;ڼ$A2"?V 7BFQorV;Tis0G$ω ͷD Bւ3PWD2u"J%G; iƚ<IX0_aNtA@ o@im33G{PP2Ao OJx#܅PM2H?1(*Q]> @*`JO!=$iܵ;?1$&.z=n-حlpBEQEQEQTzWEσԷ=NzKP24_vmLLa x ZR\&"~gh,q](>9fvAZɎ"lyU C q}})$]U%;mqAecbɢB2Prˡ5e( KNɪvA',gS'S2|I| DVOcn^!.Y]UR}V2vxjRρsM3GJlv&93|o%k)VBt}TEQEQEQ> ޗ!*h}/ȫܩxƀ1FW&3.[Ξ]6ZH3Hv=[̚ [Nڴ6ߓ M̢ÌN Q)cO~[k6\x} M9syA zZ2rwʟ#d~l#\{-݀;2pcWEQEQEQ.9f-@?K_|Co2̿!kgޙ<1CHj ?20!6Y pQEjM)>p{(>ZjCKD9S4=^5Ewq)}Vw-IVTcEd"jc;S`qtOQw;滝A=@rdxwq@g׉'R'l,7!=(m"p7CN^?)!o^)~O̬PlZ IDAT93?'1;un74?`M.ҭ.%&brEPK٭":Qhy0w\0\脕y;O3>_LwOzBnE٘O^Q;$Cl>jdOY]ɳgݵ') *IxUQ^?+dWE ]L_䇐+DX vהVHnI?[;yc\(((#@٨F2rU{;v &W/,&9Lt|'ȡK#'`rei d EĶlSH =>Q)K#<1-y C(-⇵sw}Y7PyWVgJǒNf3ބPMÃHOOiɉ(op/Bm D7KLaO7wJZ90$v2+Y|LcdG%n) HV#סP/C}RByH̗'?^(ނhD~g#2oYW/' =)cO)Q?*kx7}2/P1d;܅-H"TŇڙcOc5(~B&"NC @y"ϱ;!/~ jpMsU%'n't}ŭP>EQEQEQ.ULXfkՃIwT,"Gz҉75'eREy#v}~7ְbQo=?vj(Wh}5h((u;(!^q(/nt+9G|EQEQEQE4PJ葂 (Y}v((((*@)M2`ĝw2w(((r'A'EQEQEQE4((((ʯe@< `+ŒW,D `@k36k.ȭa(ζ4<;0?-gKrF An7WaK JBv KQքhϟZk`Hl[%+ل`H b۔*`ʀD H"6 ]{Ql,[N1Sp}/eP=C۔gWhH&lĴ PH'4It\\|bSEQEQEQ>8f%@e^u]Ήlv&[_)Nݯ̣CnF:M4[ұpJRO:Wu ]]o^0ĉ"6 {E~m @D{ NǥؽRf@FJm}@YhO yh8̀!ۜb2 J73tPOV(\ e"~M5,`KX @EUO(4ހ+&]y([oؿ`"K̿ L)@zJPn .ϕLV1d_BB2`1[|DBs+kA|Еs0EQEQEQEU2;qlw)\X+T!KHObO2Qd j$]N!KscV-$H$-I);^aHyߖln!Xҕ|&*7hO ׻Ng 0)+~ !>&2_D 1MF[[srw@..%$$sUƚ[ : W\_C|X_6d}ҖWVKŃbF8$%`xW:j #%##_(((C~6fzWpJ{HN-5OQg_>?K8j7zU@(EQEQEQTo~e>w_cOmgdͱ (!!c{?KYJ4ѱm${t> c5 P WA!U:wE!I Kw~ɒ091\_|gf[Wg#es "Lmɠ-,YCeQRDI20*Yg)Of.j)vŔ"e{KIa6Zl]2=]"Ƚ_](((r>Z=L!7";PivHwxϐ&IQfj7JyV6*lg u"̗vfd~D[!l%jD41y.J4!  IFt›MϺL%l gÄ1Ig-T*)Ka"s D ƽ+!FcertKZT|REQEQEQ>hf%@yk|oSX~a__Zdw Y>x 7WS6 dr] 8- :KxT]>ѰD`z Ε)2Y"3+eadvӐD$jU:d:s+96r_􀴗IP; C l:2NGʢl\ KƓrp1*g*n<] ;.m =ҧ9O2ϱMY3~g!w䯘,QTEQEQEQL-VނWkR DP80}ii4餇)HTs;^_#3xR-dڃ;xɲ5CHX(F04^PÐ[-3erMa3LAf䔵z(R77DW|&DBnf 9~m\ZO"b/.krЉFcr lC5yUw )+m ?AL߄J($Hoxwݷ閶':"񈚢_W~ 9 K]`TG{ܺ2UQEQEQEQ~J'w`3`0wGVS{' ێ Kd'?'"O0(" FN{C'-"D!;!d>ℼA߬d$Ei=Nbq}b 'BNAQr]י9'th3ADb J(,4O3K%Yϳ[!DIL8[FO~awVZ ?}]=m dx;*'{ 8u6AC&.ܳ S%0-) #]Y]1Ⱦ!I;wP.+tKbcRF6Y7ؐ$@mwG|]z:ȼWBH/.-\dK` %PB %PB %PB P($zW6 "APbR Ԧz5M֭[Gee%300#G8s_Cf1z箹:n&L,~.eω'8=4*_0b,d{\:m%J E\x>qvքJE(`DmZAs9.\H{{;Hۦ=cdd͛7NCvje";ʥ7jz6A)_wΝӉD"+PYYIWWmmm x &PUGB$'9E{{;^!l߾zjtD!w7[JGB We )eQ N]@ +L<駟&/5zʙ].?N]iYfI<'pivŲe˘1cNٸq#edbgv똸`mwofx,1N<ɉ'ˣmh͋(rNH.PW5~n #ZEPt/:̎~^. z?$2.2~MۮQR"5|mMV= (XC "  :PL`p6Qv]2`FɁ9hPSXOc:\O.BoJ*VB WVرcO</|Gʛ܈׶uVX??B mw橧'qFox-@=O^Ѹn/pf;?=d5- vĨF'^ߓ6-$D/o!b Gy|(˲ؿ?ٱc{(/y` #.;h ?=.yinnfӦMEjҥ Kկl( `ieI'[1|Տ=k2}ѳ]i￟{/~]g??a={rرwGze;cgϦ8 F^/~,[K&رclذ'x 2c-ezy#j g~I@ sN[u¶+ M_#pPPBoIjrc++cQB&ܣ/Aaq0?{dX'L ,p(P}DA]9PBv+L: 0 }wRL{aI8iUJ*VB B466JYYJ{{E3ټzkv4U] Ba$B@ @cc#Aٰa{UAQ73j1AM'5|ѿ:YBь©:D5C([)4M0Puq݉<MP}?S@XY^*r͋˛(=umŨeٲel߾Z[[ XEoo/lIggQXvVUUu]QUcm"qRR@1 [T碨o=/K"رcEk\T5>5aB! L6kFYYPɵͩ\=ʂX3`p88ضTWWSUUEuu5===G?P(%e g܄0&W (gm=FEMYۉ^7xXGQ@̒Sdk9heDߋKX[Hb#Cޏ+ 7) Z(/i PJ=țsS!PTd"/+%34j1P*_Ar\3 J%dj8קLwJp䀾bK> {' &P#S"w緅.> $0$_o/Bʬt =Ji~;A\ߓ(c C̸/ u ܷ1 M~-T6(?RJx_uDAuu5YD 灝E${NLZU4*@7Zd΃ *-* OgsSQbm.bCFJy,7qF>AҲ\Eމ}5R;%\ jFQjr'K/K/~.:::XtEQ*B!HNx[kQȝ:j_ƫȎBw=.#ADU]µɟxs((F-m+}Z8O{{{IR'Ê :h n\'-o@T>lKA>hdG٬a IDATy \.͔{KυBzRW2TTA.!Dex"=p=k$gX.F>gΝ'?)v?ɒ?~͐4Z B~@WPgD!7@ JY38yh7G:hD9e|&^/qďW(f!AaLQ*Sd~{p(JS[H ³/nYzT#(>E[, yQ \saL6l3^\۷SSSCmm-m<9k@3Io{vet<ii_Δ+}O_+j"޻y'ٱcPUVqw`fsEQؿ̕~_7F'36Rʛ!;&O])xGR/(uR(Z9yR:5r5ڒxa@}}=Bo|/ 5x$&Cq)ӟV92SJ(h'; MfM@ @mm-k֬uѴ llE=VFPS@IFWVo$w)]/C7ϕ!zCC$"u9}4կ{arM7_JPjwFpb}O L?#-(*!K%?Uaj<se?^EM*?)=@^ծ>j^wD@ygc C$'>;9VH%JaM}q4{]2;HnOqy FU;ŹDԛP&p!x4/"F'Sj({TzI`vV!CJlH?+r@·9M^ mDYnЫ! @ ̋`=!H"ʺ)3}uj[ |RN`<@З ˹}R{v;қL1M@;F 1PCC;5,QW~`Okhn?35'BmFg@~((P^#[1!z,_Qe_$x΁ (O%!Kp9oBa /K syvBN$"e9BK~s`o'OnKz(|uT(x<^ 顬JXb[nbd Yp?f,3gpN=/76W&wUr۾蕡ZaKQQ8Jz˷p?(J,"BIl_X\'p 6+ۆP ܚFM}… D"dY>}6n{O Z{?CQ}O]wY)uIHBlݟጞC/G86=g+ɝL'NڼlQ <+uS~v`3T!;Q4~j a_|yCD̾go:"y9B<#L|@ PEq$n˜{ƨpqRe{1bpڈ=eV{.\g܇R1O-^"NX;~>({]/=7?$o $_&'j\™m(Fnj0r3_]MxgȟF5u^ OQPHgֈk-'ض5Tp-#}wWdYbӧO*,ԩSr97S@1#I| xo3V)g븜ä~wKoQ?!2߿ tuu1c _3g׾vIՌw>%׍Wz~)%lȝ|-Ǩs%6'٧3o#h=ev=xh+,xՌpp}dl 20{0#<{ܹsyGy衇aao100I2[50c+_ fbҥNEEES*gu T /Ozvos"]+QCA2۞#ixydMG\@R"=Up=OQб՟%h=nz'>!7 kqaJ3*M R'\ 4/!, r$~ 'C6?Qp%9@<@jBj%## Bc'=+y pz$FiNUL u 0$Fd'Ys˥gj >^P_-e4n!k@x匀B@:,*8W{-~{;g@ bdd/u;!r#I( ̔YcI^S7ACp.uR}y;Q|q9[#,^ZvkeY$El06 ޛV{e{<Ibhzwץosߊ Zͻ;0혦IOOTh4JCCK.B@-`Tb?bP spOoAQf;oFXGAhEӻ6"!4}5Nz¹hjI+K!ξ.C8BehFS1h*yZi}jΒ;Z`2baDO"GQqBbOo9hXv6QD"_L$L v"!~#Nj¹nX erF{$IjXGQ4k86f,bK>EC\Zp筸~@ Qz"Kzb5Om=XgwfRQԖըUDߋ*0t_n{1>O|rLDAuu5466fyH 1Ė}TxIcWQ+?Mi۰Nl;Ek,YD(@1#[ "Ԋ6x6V~gEk6/F W16=A:j E7==x7(kBxZ=I9١k;MXpaшu^w8ZU;Z g,^!3-y?'v-ZCy!>3JeFCzy3}P혋_I`a&J/BTKO@ ˱mF'T%EעXͧ1ʛ}/7 iڙ sBo''FN95#ر-R!CEAQNoG j-Da v+0_?$ G" $F ~R)߃aRP-hKjDB|9wr'O+fˋvze+^.O]M\i8#0e#8RO;oMaA2D5`P#M  \^)Vg^!b0k;/$Iϣ$E$#8çr 6|][5MEQ(///k -ZQJNGNa0+\qIS8w?YqM qaG1(FHԨB@>'0gB(30'~gPTk(±0kg]q$%]Vx$zE1?|_c;oA ۻjZ9C/k$Ա 5TNw]4֭[WdY렔wb.񕟖ko7µ0k;-I;\c`-VQJn2 8u$NHydH˧0b6/ĦaA/k:h\E@]8Uœj1AfOsgws'bVw4,CmGsy=RUx"y}eh(M>1#/Wy#ʂ & I2RY> ح\ ^ F(o]H 0nj_lgIF> 0p+Y{ $2M:՗Ah;ӗnIrWh̙`|Ie^(9sKm؝԰>*% ցJrl<~8@%VV ]yDk F^GΑ$VpSYX'l|OCdl<8!TX/,g?͒2m {e' 9>1uI7BYJ7B&l?gJ:!;CP IA àZ:::&;VaTSGz}((jo}% X7!\QccsZ IR@7z,">YۉuVw„ȕ\L](xZ"h5fm'Z,#_'Vc@ (C~PqDnx*rwvZTU%k/,f#4/7Frv7!AYwb% VGvH%O;\Ђ w}@R ;4j"i <}DT!\K-bW %VY35pv׾!c~(f \F{gNmofŨʎ;xי?>֭c_n$w}-\Ajz\Т̾`RY YU?̖~%6Mp"%v53 -GS!b}'V71ЧNIO1!܉)؈ȌV᥇! M_YmÿxGyŊe$ ~᎝ #rc(z;.xq[l^^نҲq5Řu]@苈y%f݆*#G ³o'ض {]x1*.0Ĥ}-yU CdGQ+&d{ϱwY38@nQ*[1fA`sEa[w5^Od[X1/^ՍNbttZjjjhlތLm۶1:*a)kE3%RK\p?!)h‹$زGOoIavJ;D)@5ݸ=($au޲`TNCk^{t`j(dHȋKVM)=д'5@j˷qF+o$h=f?N@$c$Ɋ@ܻοi>VDNbTc$_ܹs9p2K{nf\+X~1nXi]}A^0Y$zIy {OZ<+Cf/O+5&z{{9]cij2=wU @ zD>d?wnE,=tkۓ kQ0jtܾx# agu0⌝p%8!E]# QL!Ly#, ^$ܗƧI\/uӐ 텂U{Jnf>gg65(@5J&BV)I&y[ɜ = .HK:Q + 4\J#2N}9^&/`(!J$1F x$@ !VI(OsHM% ey(t?$٤U!JpzW J̑^`JI@vH\[bz:\"2u S#6%ѥ`;Zi2eKH H=* HCIy &oPJS OzaBw&%>C]]+W$S(!Hj*jkkihh0ۖl@8yb}(|&frP9W(xxH%;W1BE 껰|ž#wp sʚq1=;"<k s~+1 Mr^LWدկm ɺ+(Fen.!S9%~#z0)Q͋c8^@Y#&;>OAfxfʙ,kq Ϝ9Æ җf̘Ass3sΥ %Cl͍Q8we݅'^ֈ^ۉud0?•C'lŤ^}Bڸ>Y(ֿy FtT38@+o6S8gNYlÞj0NVpqo~""^*eee$dJ 1؍y3),@ W卸`w3V6p5Dٝ$PBqV(o Q^^βe˨öm9{^A\ʨb)|k+i мn6֑qFNh^V3O$ f @hmQqSo>vPѣ5mZ9uu>DX`'nf|"uo4[0|-\!ǰD Q`T4bwMTьӶ\z]G qF6nF&>e(hUN r=!pңprOliBW'ɯIL>#Doy 9u!&wL^o IDAT+Cw wbs1[`F)qݘKſK3G6$=]0[ARzd]z$&ѻdN(g}<je+4u;_vʱ^)syiR~"GY %PVV1MMӸm_WWǪUؼy3jFs8={&&)6byb%&KR0&JIJ_  H+6:זa|y2\ax֭[9xPb/_ӧs-044Į]P'& Ih&F^~?p%&w5yJ\̆9#I'6= OlF wSI|wqCNٶMN )4AQnvIu?y$CIh(~8)KCyI]=Ϗ.}'+3TFˎ^Q?PYV 9dȮT,GubhT&a֭."̾/M"E Wz1wUR4W"_I F1øAMXF5{:%p8SVCMz?PP9anF0* lڴa!`) ;w۶/-"_P* H9H";ua U:|apSwa%cc5y m7qu>Ru y[r~k0a?x 5NZcxu' u-nTd{: ,\ {(jm@5^x r6̼`r-KVk(ٝ?b^$JzO*zx;A 7fbFsxۜuAtCh d/vEՠZ +MQLVrIdw)Zr$F%'O4Lk+gpJS&7Z.o\Z?^U7N#X\QV2%"H1a̞={uuu,[͛7˃ oA>Q1q+%&N&O_(NLu]L:<00Qn/6~D;v}k۷~4440m4:;;'0٪&t“H?yc'/={{cTM#ز1vz;b_eF'>gBaTcgߘrM{8.:BIIlhPc>eHiR۾7t%RMtM(Ycc_4[Em9"ѳW׶ ;Ù3g?KuoG^23oƨ)IGZaTaf ~y~y~N0T]q0ZU]^$Knf7?ebt#rW%100@2$0}t*++r;v|>mrKw{("ч7v/¨l g0:[ )dn ^+B lғ/+ȼQ1ń%sp#m\smu}Bs^笺&3p >A³o'u)'ʝ'che ߈$&$6l`Ϟ=twwz #\|7ُq$pSC'&E!2aؼ> D/л⍜Al#vcu߂ 2xgiiiFnvx 8~{7Տ;OdRT> ]ƻ+I/Jf+>^"=Zw~ (mG$T3܉d^獫:Ycgo gܔA76HqͦLlBIDF;^ .oS^&琞Q$]y-e^G$i`\ gJU]$g(ҳxr^$԰Ke*ܙboRo9)7-oS'0j(ȺY_ <ޘ.Nśiߪ2y)엘I'!CaI#'B5&CEs`l$.'UKˋaH$=;o 4ץfJx?Oףiwܹs˴iӘ9s&3̺Y7;pA<=*R's1jgv`QK|!\ht_Z8sm;//ƘDhZormE_=NhVLL>P˧/7 o΁'-lNx[ )؃=G: Q7;b>Qۉ.GNLBu2 "U'BP^^7~3ijj<Ax$Z|j- M,_ @ Wh^HxZOp%( -ߐ^ h-Ei{T/'#gP#lk( \s˫zAp%YO@&>Vf߆^ބ3֋{5pE'"HϹ Io̰l^n,TPSx&:y˷'zo$D \}\~OO###E2:3::ۋ ȧ rM+I=_ʙES~x$ ڙz v_`侗q>*~˗{ (F5T=t sx^EGQvL*KETLpy14E b9aYVqBoG"+L=~λA Mq([z`D[.zqǸ\__?B nfO|_OJBII8Pވ5R!+MZZ&; YF*z*nQ\eƍ̝;[o3f裏el+'Im&e;QBݬf/VAVK;.58p|]KW'زP͠h;oJz hY=zhKZHWx={S C@#ޘ4޲S2{d"'æzp΁YrJ7}~XUh~}>Pc|G/! F|9WOOMr2*9 *Hb*lV^(ZN,èh?iDv}]Dnx|Lèp X_= 쁣!"X0jg P8@ Ij Ae1܇Au e`ڸY٨("ч{MOUUYd _Q3fP^^N.clL8GoĨAx?aE}9E;HdΝDC c{'j0w̅uލ9c-J "o#+dТu(Ml:x1y+T]΂eމݮaíWuϡbx$8bp=0w\&LeGֽ" #z>xxlU;z쓄gߎsWU I yKbǜ}OjG_.)SC%[cS7Z - Gς]/= LydT4~́D6xDjfPc[#Xg=?,,&֮]Kee%w}7< MQ8FdzbQًGWjNazwdƗf^Dvp f޹S4T?]z6qӈѣs* j[ƺ)tkXT~ r43Gr6`''AQP4x7$o:]WI#:;=Z&a?ZIYU W~^ VȂ'sv#$r.wO+H}%OH[yWLF@u DBxEv i*c0X,D %i$`'OIvW[@J$󜬔GZ dW^RRS80+I) R/K J#X(yR2OʵpJVcyыSlG|$)0ߓ?lsG[I )*TC#`(TG=r|oU#+.@σiiR|\ {U&e5F>~]PUU5S=h9r\.GCCvl߾ [|vk5uεe'ӳܱj#E b/5ˉE^KnU!]ا߂~s=j(p͜snbzT" џI;L|Vܥg0 3-Ȏ`to;=0% :9]]&ȴ֘:|8W6>`S Vwٖٶ8S Ý8h$7~GdUAםs6,x`2+)[3)E ;@I5!%ƭ~RG֭[)//GUUQb .]*SWȜỳNp;[h-ةS2Za53by5cUUQG_DWnP=+TB5NBz)VJ6̣FK?Dmئ!X"Qi=۸bw~0wrdQI鿧āذa7pmmm|/O?O/DvmXxѐqPkI!H]?!(n!Az &كJ὾9/9`Az)vf~-r2#P#Kk˰|ً52+Lf)\8>_L"[ # zxi!qe65`2)E* {#Id]&5*I'%#SSd>d$qLM>Z/+& 0\; /ʈyԋ2 kΦKtNu@֫X pq0^m`lHĿ.WOs %)pa02/P/#o8!u`We[ë$%,YUpO\T^|d: _?Ψ"> Q/ q3fQ_گCN͒^{eC{^ʈ92p_\K'T_48+a000={ܹYa۷uVRΝ#TSmE p-kb5GL D.(9|[Xuv4|w?!=)0i= RF#?C8V4ʃD/e6Q!\=q5-5%}~@6^A -9Oon!-vj@FFb }}}߿_d2)VZ;X#gFGQ` DkH'`V苮ǚ<J 5t#lgϲ{nB===ƻ1zI@98 3=уq~i>?o8S/^ Oavb;)Ɉ`';~MEe4p^']`U>0x-VHk^vFt2fT'Nd^ IDATOQzzzzaM̒"7Na|4/+r81k>ZBv$%8'(E "%(p|EՊz<ٳsoz 4QIY"+#X!Y$5H{bDW%p!X)S#S'P" y*՛G(3k:r<*[P"7+pM*'zGu]B;v`ҥ(B&! ZXD!r+҅"L;= >:ؚ9҉9tP]'( 8LZq:![@dRe잃8Ǜb>WF0_C>w}@ėd(AQe|9OO>>tYt {V*r$zWGI[QwojO ߨ$#_kuۯ>N}֫kZ;ߐXkckJ+&qp-k3cD@}g p{g"M!ÇaᏥg~Uپvko:[ħ2ϯI#? UiP*@B_ A<>~mߠJ& )fu|Ç>|o<0XԅkvM5YM۲R|ʇy N}Ç>|<(PxSx?a@Verf'||ɇ>|Ç>|{^t)]35>|Ç>|Ç>>I|H-jrYH2pø1,?V-}O 1PWZN7G T@)wOdQڌ  s .Z n/g?L\Lg,J(UC˜==EwyLP(xW7&RfDut(2?HwJHmڂZՂK8GZ+-(e )7,pjy#=;ه;t #2H]vjw߀ְ758}ä6߁ZE Igq=wmPk!,ՙ4EQzb:ϟɓEm %R1y'wTzhM{pZX95Xç։W Z&~r^"PbAtd_}%(+PQPZ$1{qJ & CYJp t'H9*!7A%0*0ʂuDhf5k8Ar_O@?0SSS JPj*[IxA;&gJ@>4cpAQ1zypjP_$~?Z %fO=wY~nnOK6ۄEW 5]CW~eQ?$z [D@;@_GEf1vo{P(0Ư1x'{I & 6n׾5:*++4I}]{9^xٽrMOf=Oc3ȇ>>Gz׶Z@ 2 V."|Z)#s_E h!- EzY ^,Ǽh z;Z@+@ƏSv35\ Bz@?, 04|cN EÇF%M6/ =q YDE˛(݄83'J4BB4Mltg "=޹kE@Ո ݏE;;qWG_`!T؊;Qk\Ld8|JC )ۄbO] ߹U.!PRVބhA{tE T;?/>| cPv2B'Q([[ԶkBi.Eh"?8քiA%ya&Ji(~{I{T@2r .HvPF1:TUhAFW'p3<jJ2,b7yӿPMC9϶ɍETzvdw2ُj$)b(wwղMJgDXZ33h3(8)E~=Oy}z%R# >=LF!^v/ s^.g"(͠Th@qTJ:^2Rxי{q[<61# !giYE>2/ *"KnD86Q8 .J(Z7=||/G)kDWA҈.0#A<'7x#%%%LNNryFF2uܡ2m*\Rވd:]L^peۗ-[FCC D"Ldpp.ǙO QJcO\S3)hn{]QnC%7NKXupԋLH%Z-A(j{76Ō(.^5)]Lh=bu vPTX9J0;[4*܌7RF-R*皑B 'A c,Xf$SSSBN Z7;fsOqv}o!rMhBOSTQ]z+%ةAc =z{r)7A}}=MMM[n0;iH'0 j4>|#Xٞ$PX[GR__́d||o K@}37!/AȽ  $x?yzOAP.әijy. ·Cl=m@MޒwJ ؃"azzJƕdQjts& _8 yk7C~/$-‚C~:r2ZxܢLwAd?ȾP; v;$"r`?B%#r6pZj.#2GÐ}m0R@ɿ;iI@2ylW=;k:LDqBc[ |DCB]ayA^EU}8 Z981NB%pv-l@{Tp?5/\(~;,G@=E 3A'I U5ڊ{uYȏ;v{zj\exx={PQZo { ԃdGɟ~cfm+!Ҹ%GXy̑Nr{Yg0NhccO]mv%0w}pϼ7Uٟ[ABjt_x=ۿM {VGA&_C'u_$XьN pq2#OY^'QWW:WEWWBRLhoJ 1;c%p>D2RGp>vOq%.;ed}(J}PprL<Ǹg@)YB W?D|BIB/WH@Y|B?6WSgf'9ՋC fsse3($ck1\-5;aܞ<|+_C|{rwկ~M61::[oa-@j#KHBu_ EqOٙjv$rԆu$vxm_-5R3`%VH)w9p/2Ǐax\WesI-X DZn|>||()uQC8N ɷty8 :0GNc'XȢ @'CݺNIhXv{}AP"t|咔vȃNj(@n;hZ$.| ܼ$ BKa;%ang8 qDA:/)p)De*_xGpBAX# f$u"%cu ĘGps^ [A C#*,Lq#Je)Qo5!Pf8\( !%"*9H<$4 g+KTVB~,bc"JS#S v P-GH5xg";ò3vN<*^V/NBpwB4U Z$&S2RI+vjK;]r|1PK 8DH|9(FIEVKm}N1飒=^p&b=>8IUKމp sjԖ{IȒS% /\&SS& RRRByy9%%%R_d2I2da8&EN?&%uבQEKH܂OQ; hJ,Ǿ i FlfFVJo:cp_6839_W%/c_HG($QMB $>$7ԥlވ=u:"h2&'za%Ȇ3D7[BAP^v;G%.]?#3>jFQة@Bhj5NU,B F1GS( $:EE$bTVVL&DQ-[F$a׮]ʫhodÓ j!DՕ6D+WOBHt<运>O TJx:\=E+2Q uSzh!̡SFP kᒿBp@!fP47Ilx>4G15օ܇Q#I@A?';Jtf>v|v>(d&~˒%KXjxN&&&0+ U^9-pmHb8L,"xd,|GDxmsNn$ @Bka^8""c8vhTTTp9¶m.1N l`e33|E@]y\ExfMçq7 OGQzv~ +Pl+|Ơ4Bh1 BGH x)^@0xYƥw]s/ih/kR_'#l/:$KPr/Dn$DnCnINk_݃)v>œNpJB)qQ!p@}hʕEF(#EF*ٷ@6-~ ŒTS4{RiW;<ސYZ edB?̳߇>){%n0!sO4;H ?X!HFEoVF7Y2ZIxJggC#lSˋ ͞AIj:]⛽kqD JLPk@lZax7{ACR7y%zղIH?'2JZyz {!qЖJ. cA@@ɟx'BiNvk,7< Zz vw_N$=FP=&f}i9>EfS#g$TZ:.ބ)ٷuPKjup=rO~)쁣(rb$v/5O#O\}k׮e˖-]49z(?OhR芻P%d/I p-Gqв̈́ɍzopǺWX6+R8ޢ`@? ꥠh&ƙhOv rd{ѳQçb~ {K3F ֻH}P]v"3<Æ xGݻwF#P׆@AЏp.D!rt'pSk#2} W?-#s'^A)'z̵_>G߫y #Bo#Tۊ"?O`alORBSH MB!?<? ܸ̙3^J:::F455QWWG>g޽ D|!$P# bo'xl[aPˈ?)!sG'_GiM(,ڀyݓ8ŃXBAMPzjBs*iUU3}ve7@zjR/"?OKEFA-3|g. z'5-dnX1r}X͸wcUK~ ((Ui?o0k3gQrS%$#B2pF!887A40n@=(e2I; 286z\Q]@j7րGu!8`Dq4 jK)!I`Ci:=P$N ,Q_Jȋ> Nh:)KC8^1w?8H1(2GZ3~ ]@y׽`UF0)3E5HEMJ)w紝gKvͱG JG@]V@dlJdjX3Who~weԔU gӈgzxJp0$i#\[/my`U vvB.c*_d ĹX9Z+wLB 5у3lBrB+ ծmꄠз01wÓh*f`׮]lذWUZ[[4)x p+Ě='I'!J կ\L.eF){dx5^VلsUDKwιwdw՗\S"ySS#<ʕ+Bk3ϰ{ WµhZ Zy# (>|LPZ(+8ك?:sD뒇k rl\[ cP2J n%TԐR+="*)aigh1Ty9)-}&, (aHGp{-UmTfl_*vVsy?"G @%d$[_Uy_KEc) IDATEN .fD/k+"EӪoe!$AI+"һg.l/^m+AFct/{i}q=&,jk}!,ȃͷ,G+'ҴHFKnkB FQט"-jE T)`O]-v]D-^%#S. f0-%h?;^¨RXJ |KOy|A۩d֭,]B@gg'Ǐ'_ORܵvQ2dٲeDQطo;w~@ʥk**JÇ=*JnZW~1v|Hn~U$pf%W7כ{VL}eU.w՟q&dJ[iAbx1zeV u<1sU*]ww߃ldWpQTG@ynNFjٹyiO+t|<_NGŚRR EH śS0>#ĥN ,"I^Oѻؼ<ĴީV}u;+Y.Qv۱Z@|OG%ܵP4S\Ua.Y={ɴ0t!\N `O̙µ{+w=zw1}Y;>*zP*I6 %{mB\ڟQ 3/eOq_#6}0*"͛& gA -o=[KnSM . PHFbJ)zp9$k4mD6SS8 z'v.}aO|SڸFk,g#O'_!7l& U.FWHbֹ 3wXHnXR^b2pg6ĉ[z֯_Oee%T;wJz lc&]yEHa O`^y l*bi 17_-oaTt#ZX^S9V>|P5_"yT4wn'TSTxb4sCy2DHl\*ٗvd:d ;U;ddI^$Wq( Y.`u@`UW>\4#Z(ffl+݂1ϋԱ#P_gJF@."lO pVBkf;s^*K pO{)(d>@f/Ob[ +f윀@gK ~I^ITלQ`n|4M8Z>:'Qm%Dן̨<*k#E|8Y k;3n4BD)JY^|P\() mzDAIḏ DeKd5X[:NY1m&qIZX]f?*#y.z{-Izj|Olo}+ *@JHzjA:g//yko GI7_J4 VX=Ŕcؽ9t U/%[5p`M+7 ={AO &Sh*+pW\HFuQqJ-X=Տ3u5Ўk^sqr)ߍ(dqE/ lH% ԭ^Ip}D6ȴytd -3ډ0s={D)%z75HQ"%k)3p<#&;nZOy- -AP2\3qfΆDm$TJa $` zBD~(Jlm 6hq36l۶{믿 4'OԩSk_}jzw;w$Vbwzz~߉'hN[vZB!CsxN~OKPFQXd RZsZI$4×hÇ_2YAq-Za$@/adG#/Uȷ=򩆪VkeF`rd$9C`$ӗK"G9eEpkIB^{^T=qr y(>IbkXEmNAa= LJ/(}t_p;neS`%}n̐,e1P j0Lzẕΐ{uws˩un952$ &)Z)ږlxvzMvZ誝DVIZ5={,݁[SqO琅Q+&3xk>ӵT/k3.cDH_Gn5G?܅9Cw}RyL/C"GźV$Ri]At7`tKթwO6b+[r/\^9K $:DX3gxܫUc``!( t:?/[9uڡgP-ͽM 'Ga]'6;hb}Y~nyBmyfj^Ә'_z2Ͼ-ݍ`{/8K.eڵqpDQ~9 @?k;;ymD(~كKzo&hƎPi=;;j?SO}x74:SwJȏ9 ֏#VdݏO!/:y_EB#IR)m:? } )Yj@A:J7/=}$&h~e=~ʥlATAhO"G^䇠~د /B̌~ N{l^߂J`a(II:Jq/~F1?DʟkΨ/ϔxiȻP)2zӺʅ9#&1΅?ލ*pNx@G,R9 QS= [Q?s =!̽Wހ5{'07IsY9… ikkرcITde;H3}rC_jPϖ9Wa7ꇐ7['FhsRΝ>rrBQU)TUfp'CI /- B0O}=ys7yB]| ]ÚIS8ӧx{TsgFJ/7ZMD%j_řrT,OSڏ[N2qjoZ>.j ^O ϫa|bVC5:SxӗW6Fs~漮u(FtNJșc_ϖsz ni pwyj&UEKu&fB= %$MK=|cS:K4Igox<湽#9?ڞíLC1bxVko@nThE5 N99}M4 ,FGGy箛gWF/:T^>.Vk35ei~:CaDqyp[MOR(ьXbt|>޽{j O9Nu  @DiaLa6`SJA3P}i[ ~?|u{2COO!~_T\'wD(W8A)cK:-ؠ&7]u6;B 6y헿e֮]ˊ+ظq#J'|Dz`^þP($y3+W;u'I/[0u"|Flxf;|01c0ex\' ʗ pH >L~e͍:.(49LFhA_ ?AX,@^@՗ٹE? !)`4!ңp4W.O*MaODh^q}3Xйs<x~>]g%j!Y~9y0!c<5W$AhI~~h9 A}!jDH ,l=!pOc})Oxh}xMMb|p~?fw-_ۋX75|  @q{q&@ @_b[߆ʅs+H?#ꨀK5>ȨB{_~peOݷ0_tHbhG]y荱aqqt%%ׁ?G#Wʥ& ȽdmD0, (vZ×DPOѮej@k?\zI!0^ޕv7 Я.bö]6`ɋ{12%&\:x>g̏nc/]WCƵ\žqӥWܛ~u< @|pPV'þ)2UR26g.uɛuNXH( XVj3F/MYT>Э V'5˩f’1 ,¦Lԟ %F*5+5RTݻpgF-+lb״ͬ`E\;7"#EKp{ZcaJ#f(H`2wx7E*Ki,Hi5IrUC9AB]%=,+|y{+.j֣ V%5*CAP\~$kݓܶ ?{֦0{szuC7"f/ڂ^=2+ t''—%#9|wևk!tOKJr+LK8[ZiɄiN-?)ϘL7>+@OX7oiaeOl:D"I0GX㽵Y2-NkhBt8q̷wHI' 4vnkf͢'alƏN"hR`gw6'AP:/#%L:ŭn\)S3:Y*Xf ~y[%G5T!(lN Wxz L XQinːM@t8|whAݻ8ʧoj%)eP3]7N1u$ v~O/mYPTAhڡQ)Т \_|r!S/0,L*x/ښ" yHo,JТж,KymF<^Q )eȦBhSEޓ牱:Z81l_D,Q%gFd^䇍2Ь Y۲tEQAh[`wu%XswvkB)x3 U:C|v[3+{$:'l) IDATi^|yLT%l<>M8 3Glmѹ_+3伟!ĊP؜GzU$7ehk  ̱/#%V=ʫ"Voҷ4MHW~uw TrVE̒q M=&fj=̏g9 F&k;^@"IF4FJt5M>Uٔ5M|Ng2v|/ɽ[[)Um^7NsW$B 0ys;Uw)THq,c$QUОɦ f(\m:A*,e3ܳx<<-V6d^jkc*6FߒCl t|jk3}K<1˙;"]拚`QvRS픪/ퟢTuٲ2-볨g9aIV .έmMx ]Vd"zf EmOD5)VpV?blWWkH)Z3a UȦu18U"$"*`(Uퟞ+~ij%ܾ|@y'74ez9O}پ$b~z&VPUXY]CtN?9ĞKJlZ Q9>XbhM]wtcEzs>醞(7j"R{5vܫx:ߘeբ$f96TaA6ĖUM]!O!?i!lo59L5M|b{+֞u=@ /u3u;SDo?, )U7G#Y  !MЫ D2!-q0=~ߕܳbl;Z *@ "OB8Z$7 ~}S_MUT<)7._ ҷ$͹ CwkxDcxsoO'TO@*o?Ţ _}c7JN&x|u{65CE=yܓn][2|.TU `6nԂzw5źܳގF<7Ogl )z YpPm]|rGKzpM{zl{K*/T୓eehsC[ݧk @PwxPu,2I05[q<:ZTMɜEՖD*Ŋm{š~_Cz;bH)1 LRޭ>W@'B_G젳%ntFɦCd:ώ3k{TC*IP8Gtq<+ 2ۢ< єԙ|v]eGM-lXFeMsCr"#J,tE]L%Iu;'Ƣ8fݥXTz:bdB̔lfu$(Eџ4Q$s6GJog)!d(d!ݦq\s'k:[3G4'-';cfAQW28ȫJ |ߚQI,q[\P9rեՠ=ܟؾOqbKIݑ(Z1M0T3Pb7J{eɒ}iBʁ~2ajC*e?:֌F8;UGm1MPw!0J5;*9TL':`>L +%q\W$@K&LfsòtB\sӟ=y#C6g4]S|M6 M1o*Aٱq=mҏN42EMMpVtUӟN*q Kܼ.{ȝ/ӖꍬaC! k]hDr<4]b\MYǪ:4)dSFHW*.'T ˒*q[V&14QtޡI<;?M?E0[9xI|rI`UJM vM}t+[}SR=֙ Utv4ډ2#M0>cS3 At(=v$5<)韶0MDT-q/ @ꃠ{D|xO˷{IFU>yK3E$Ǧ,T\Szqhȶfn‚(HW/PB_c;,=Xcwol%Jh'Zٸ2êW.wsƅ~=(SyuÉKV~Qr:/=IHGnne O,+-M#%8=lY-#ZXcNJ83 k_-)vv6drr|D[s8Bi ss;=p;jrT3yÄ́5J:]d8oFSgǙ`oޡ} .{K wmi _x1LYTiUtB'UAzR0UUQu%Z?z+F| :x@كOThIs,PH]gybwQW^APq.ϏtI<uE"ҏκmI5{[_ ˑDC Itxev5A ,v?cQh*!T\'8>XIս Ԃ|a5GBƽy\hD`UA&iXsr! B`Vkt$WL>2J|ONW99mxXn;XL 7+ټ¥b:|L@|`FRg'-F xP'&lY-miK$:b S%Ve$Zb][]"!tL# ǫG l\=-Wy5p=dEO`(JPoBAv* up'ޜarKO`eOG*c [Y d*βqݜh)GΖVnY'/MkS|~)ĩWLitFnp{_#&#%?zbH",Ueق8kzܓ k$7DžD*doB^lH!J8O6Vk1fl 33Y_ސ3n^c2?|"6l[p,5Iv-^a|~$ Q * v1%meB\blt{Recordw (~t΋cGj<~g%tO?;U#\5^!H,KJ&Jnwl'쟰SƵEʮ5l`ɐs!3%̂[{lY%"d(BS Whx2]D"d֖ _RЄQ9Ģ:ePa}9W^(4¶(]-\Q\M^ąi㷶j`k<#%Bl?Z㹼&@.qf1 I'%yMM +b*ۖ%Xh-);uEgnka E /[ @*{^㗶44CXh-^ෑʳp[v>DCWkKH[ܨ@S=ͥD.Xu@J6՚C&i8H |OW/iOhh˜8^˜*TjA,0,HF51]X٥\uHō<$UjSE [SD~;XV~&)UqPjgb=t]%riJIџeRU_*ᧃ&6iɄY-"3791RD#s>^mO: &Ӯ#*ǣj: n3f D/C?CkLu'֥~3tY՛IຒɜIPǓocaLěyb`emY6,OsGJXW;6/,>x.\lcxF*KOҢ LK:'"'S泷s4UcU ~|d,et_ۖJvUT-燣Dtnnq=,GŠ&5./+19Y#.dž|}6_,ڛ#,lT(6>`.Ͼ5ɿڕ']ۺI7i÷&ز2ܹRYܛ9gn!v)Iԡ ==[[--Q:#*7iBUvWS0tk/.j,qwO1ŷO՘t]S)7}cu!>- +3tz16H3=[/~8h٥' yh]ݳxlC_>;O[<*8rd @(h8w.dfYG*+"-Bžs GmU)aiw UwEiNTm< "9_n?mQHaYz̔_D#_vp]IPٱ<:DH'9–RuBJX祃9ro5);75繭)̲X!$ Gܼ !0-MXkF3K1 ?M~'̯ڈ;PH 52N팑Mt Î-OW9NKI+LL#MW|e$2\gMHaiR scU~}'II*y$#eϓ 49|[җZ MX;bMTualuJO+*ho LMΎW)Z '\?ciKj ŏPjTty` )US۱lC窌YQ'!j OuB&3ԔMo7G*Su|hҤ#΂[F$ fj; 5덣Ef/m_$&mQZ:]`āGZ b_<[q0nK؟w}+GA`Lu U$Ttxi@5?xpVI07W+}\4">%c:e#5GW RrW'dt%T碆BR=Hs~Li#uR7BL5JPPp3KG&J`rL邦/Pt  tܙeӊ4Gyp!~=lɢ5 yFj3Q?6bǓUU$>~:)(Yd[odLg\V? @>*)#uZbE6,KQ:|oO8Eۮo] UЪ^xTؔeòtDLgx‘UKHmau:y%l(EM[[Ve/1jeprD֦|NtF  ':^O28]gxBwk7i[3!횣 uيzX]`uO֦sD*w +ҴdBģ:c\Ïh(шdƱ*3Xw -aRVwEp.oň#1TFud\qm0!/r??ʻu]ʞ%6Ȱc]ѓض_bzAdW@gvS]"_vҳ݄Â;U5X+{kBP9^tH Ëش.NHjq\=[ZHz-AC\oR] L1;gs7g KjyHJ`jz2E(]PrXeߦ8tEʚ+$5߼.>?ma eV}Lp=S5lm-dl8{Stܚ4X>{"tkx:1ɖ\B&9?Xw (ERyf[;"eGL.eu9%v#hK gh20|Z$agMn|޸4G2о2 Cs褅5#!6tiOEɗld߯/ H}ynoо.Ԃ+*Cg{lŕ639Sk\sbSImJS`)Ǣ.7ƫ|jW;D`jWG_LK"B{*-7R<̜2>מfזdTǷJc&oqRElv:ZMeClgfH_/sR5jrJ#Ot|E,"IgL޹X%,&jl|n<щP^rrXE$OLѳ>` v"Y>e(,?7\b}Wo.$s%Oq^EvlnϾd²z9JQ jǩ<ە r0m۔dWkXv{5r7&:"/}z}0[{P}e\_/u]=[yGK>h,5oq7i)F z:sxW%ǯ'W=XeU8 N':Rpa;eH4M4M IA["d3Cw.0qrqشəQYwMGsDÂT"TP<;Yw.fL2)3$c#fLpy?Pd!)(V] ~6fUcv@B@_ȕֵEF$WG+dKrc~?p`ǔ0lw*[#Ja;/5 ^=gsUڢ;Q?޻3(#o K cpawKRpI6whI ?ra 7^g9Q kUNv̔\n- /iO_1:cR'(^?W`sSpH0SN/1zy XDtq@U(Ǟ8-aa*f G ~Vdŵ{&-ړa|m>攅k{c5o;lhu?>n2Z a'EGK5iÁ Npo1}``MۍK,Iq +/p9D#ryc闭+.Mh˳;F29s#UMZ3!lţZHL˧KZ_p= Eå౑236 ex9 e(W=~AwnP1]Mƫ JTLTpHPyLq={Rs,:`%ep;UQp BK `td4帪Ct6\٢١ &mZ("@RR ABtf8h38q¹S`.lG [v::iiƉDBj C6RGˇw>\?/Ld$ )p K(,H4KxwnS )9!Ak˲U/ -!E' )eU𨦧})HJ-uP3|'׫-PJ{9[(V\T<{|WL  Mw%5l OvvFJ~bU]=Ku l®Du3=<֓b`ޞo6 ~oco|[[xp'MYYkHqZ0>D#Xv Mާ<E.T4VIb7fK׼kԼ%IdbUTԲ4R[Ҙcd}B=k*8qqSR씹(Wu mR}ZjYc݃2UEhg`$ V<|P_r Ƴ ޒ4-;|ϯ ecD0G@L >CݓVj#r!xOG"rNwVS5߻@u J~l{b}FmrINeiM4M45$J3Ph|;|l;wˤa]Qaxl<[Ob20IOD, j?lv^ FV.uBtG Sow#5vBR -`h֐XwUj%eta)>=WKL@lI{Jo^ώ!)-> tYHmL>qW+pA0#%AEOtW#kB5>VLa̳cr-5+>exsly1½n>hqR(sc$Ʊ{jFwVs3mXUii/Wx $I81Pv:HNLf#&6nVl1I "+-N\hJ4%Xa;>ʼnE8O=` 1Z)F  OTMsce*w<r.?:+9\OџɸbͻjiiikBcKKU,^[OAAb鄖kSQ_4|" ~`eF՗ĄQXڞ"_=e6roYb]4M4M4MӴ7ֱYiiii}4iiii#Piiii#Piiii#Piiii#Piiii#Piiii#'!0IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/docs/installation.md0000644000175000017500000000116214526070754016750 0ustar00ihabunekihabunekInstallation ============ toot is packaged for various platforms. If possible use your OS's package manager to install toot. [![Packaging status](https://repology.org/badge/vertical-allrepos/toot.svg)](https://repology.org/project/toot/versions) ## Python Package Index Install from PyPI using pip, preferably into a virtual environment. pip install toot ## Homebrew For Mac OSX users, toot is available [in homebrew](https://formulae.brew.sh/formula/toot#default). brew install toot ## From source You can get the latest source distribution [from Github](https://github.com/ihabunek/toot/releases/latest/). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/docs/introduction.md0000644000175000017500000000313014526070754016765 0ustar00ihabunekihabunektoot - Mastodon CLI client ========================== ![Toot trumpet logo](./trumpet.png) Toot is a CLI and TUI tool for interacting with Mastodon (and other compatible) instances from the command line. [![](https://img.shields.io/badge/author-%40ihabunek-blue.svg?maxAge=3600&style=flat-square)](https://mastodon.social/@ihabunek) [![](https://img.shields.io/github/license/ihabunek/toot.svg?maxAge=3600&style=flat-square)](https://opensource.org/licenses/GPL-3.0) [![](https://img.shields.io/pypi/v/toot.svg?maxAge=3600&style=flat-square)](https://pypi.python.org/pypi/toot) Resources --------- * [Documentation](https://toot.bezdomni.net/) * [Source code on GitHub](https://github.com/ihabunek/toot) * [Issues on GitHub](https://github.com/ihabunek/toot/issues) * [Mailing list on Sourcehut](https://lists.sr.ht/~ihabunek/toot-discuss) for discussion, support and patches * Informal discussion on the #toot IRC channel on [libera.chat](https://libera.chat/) Command line client ------------------- * Posting, replying, deleting, favouriting, reblogging & pinning statuses * Support for media uploads, spoiler text, sensitive content * Search by account or hash tag * Following, muting and blocking accounts * Simple switching between multiple Mastodon accounts Terminal User Interface ----------------------- toot includes a terminal user interface. Run it with `toot tui`. ![](images/tui_list.png) ![](images/tui_poll.png) ![](images/tui_compose.png) License ------- Copyright Ivan Habunek and contributors. Licensed under the [GPLv3](http://www.gnu.org/licenses/gpl-3.0.html) license. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/docs/license.md0000644000175000017500000010414414526070754015675 0ustar00ihabunekihabunek### GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. ### Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. ### TERMS AND CONDITIONS #### 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. #### 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. #### 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. #### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. #### 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. #### 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: - a) The work must carry prominent notices stating that you modified it, and giving a relevant date. - b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". - c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. - d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. #### 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: - a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. - b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. - c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. - d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. - e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. #### 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: - a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or - b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or - c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or - d) Limiting the use for publicity purposes of names of licensors or authors of the material; or - e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or - f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. #### 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. #### 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. #### 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. #### 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. #### 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. #### 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. #### 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. #### 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. #### 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. #### 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS ### How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands \`show w' and \`show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/release.md0000644000175000017500000000216614656344035015674 0ustar00ihabunekihabunekRelease procedure ================= This document is a checklist for creating a toot release. Currently the process is pretty manual and would benefit from automatization. Make docs and tag version ------------------------- * Update `changelog.yaml` with the release notes & date * Run `make docs` to generate changelog and update docs * Commit the changes * Run `./scripts/tag_version ` to tag a release in git * Run `git push --follow-tags` to upload changes and tag to GitHub Publishing to PyPI ------------------ * `make dist` to create source and wheel distributions * `make publish` to push them to PyPI GitHub release -------------- * [Create a release](https://github.com/ihabunek/toot/releases/) for the newly pushed tag, paste changelog since last tag in the description * Upload the assets generated in previous two steps to the release: * source dist (.zip and .tar.gz) * wheel distribution (.whl) TODO: this can be automated: https://developer.github.com/v3/repos/releases/ Update documentation -------------------- To regenerate HTML docs and deploy to toot.bezdomni.net: ``` make docs-deploy ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/settings.md0000644000175000017500000000563414656344035016117 0ustar00ihabunekihabunek# Settings Toot can be configured via a [TOML](https://toml.io/en/) settings file. > Introduced in toot 0.37.0 > **Warning:** Settings are experimental and things may change without warning. Toot will look for the settings file at: * `~/.config/toot/settings.toml` (Linux & co.) * `%APPDATA%\toot\settings.toml` (Windows) Toot will respect the `XDG_CONFIG_HOME` environment variable if it's set and look for the settings file in `$XDG_CONFIG_HOME/toot` instead of `~/.config/toot`. ## Common options The `[common]` section includes common options which are applied to all commands. ```toml [common] # Whether to use ANSI color in output color = true # Enable debug logging, shows HTTP requests debug = true # Redirect debug log to the given file debug_file = "/tmp/toot.log" # Log request and response bodies in the debug log verbose = false # Do not write to output quiet = false ``` ## Overriding command defaults Defaults for command arguments can be override by specifying a `[commands.]` section. For example, to override `toot post`. ```toml [commands.post] editor = "vim" sensitive = true visibility = "unlisted" scheduled_in = "30 minutes" ``` ## TUI view images > Introduced in toot 0.39.0 You can view images in a toot using an external program by setting the `tui.media_viewer` option to your desired image viewer. When a toot is focused, pressing `m` will launch the specified executable giving one or more URLs as arguments. This works well with image viewers like `feh` which accept URLs as arguments. ```toml [tui] media_viewer = "feh" ``` ## TUI color palette TUI uses Urwid which provides several color modes. See [Urwid documentation](https://urwid.org/manual/displayattributes.html) for more details. By default, TUI operates in 16-color mode which can be changed by setting the `color` setting in the `[tui]` section to one of the following values: * `1` (monochrome) * `16` (default) * `88` * `256` * `16777216` (24 bit) TUI defines a list of colors which can be customized, currently they can be seen [in the source code](https://github.com/ihabunek/toot/blob/master/toot/tui/constants.py). They can be overridden in the `[tui.palette]` section. Each color is defined as a list of upto 5 values: * foreground color (16 color mode) * background color (16 color mode) * monochrome color (monochrome mode) * foreground color (high-color mode) * background color (high-color mode) Any colors which are not used by your desired color mode can be skipped or set to an empty string. For example, to change the button colors in 16 color mode: ```toml [tui.palette] button = ["dark red,bold", ""] button_focused = ["light gray", "green"] ``` In monochrome mode: ```toml [tui] colors = 1 [tui.palette] button = ["", "", "bold"] button_focused = ["", "", "italics"] ``` In 256 color mode: ```toml [tui] colors = 256 [tui.palette] button = ["", "", "", "#aaa", "#bbb"] button_focused = ["", "", "", "#aaa", "#bbb"] ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/shell_completion.md0000644000175000017500000000114214656344035017605 0ustar00ihabunekihabunek# Shell completion > Introduced in toot 0.40.0 Toot uses [Click shell completion](https://click.palletsprojects.com/en/8.1.x/shell-completion/) which works on Bash, Fish and Zsh. To enable completion, toot must be [installed](./installation.html) as a command and available by ivoking `toot`. Then follow the instructions for your shell. **Bash** Add to `~/.bashrc`: ``` eval "$(_TOOT_COMPLETE=bash_source toot)" ``` **Fish** Add to `~/.config/fish/completions/toot.fish`: ``` _TOOT_COMPLETE=fish_source toot | source ``` **Zsh** Add to `~/.zshrc`: ``` eval "$(_TOOT_COMPLETE=zsh_source toot)" ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/docs/testing.md0000644000175000017500000000150714656344035015727 0ustar00ihabunekihabunek# Running toot tests ## Mastodon Clone mastodon repo and check out the tag you want to test: ``` git clone https://github.com/mastodon/mastodon cd mastodon git checkout v4.2.8 ``` Set up the required Ruby version using [ASDF](https://asdf-vm.com/). The required version is listed in `.ruby-version`. ``` asdf install ruby 3.2.3 asdf local ruby 3.2.3 ``` Install and set up database: ``` bundle install yarn install rails db:setup ``` Patch code so users are auto-approved: ``` curl https://paste.sr.ht/blob/7c6e08bbacf3da05366b3496b3f24dd03d60bd6d | git am ``` Open registrations: ``` bin/tootctl settings registration open ``` Install foreman to run the thing: ``` gem install foreman ``` Start the server: ``` foreman start ``` ## Pleroma https://docs-develop.pleroma.social/backend/development/setting_up_pleroma_dev/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/docs/trumpet.png0000644000175000017500000004014614526070754016140 0ustar00ihabunekihabunekPNG  IHDRaMsBIT|d IDATx]y\M/=MDQh 043 1#3_b%ƌk'„$l%JiӾ;-}^W<,<] (?-=P`tP ,@ݻ:BFW@w055Eee%ۧѣGcҥ=PF^^Fhm۶EBB.ڶm۴#VYqm|ᇼ}B111y&,X 35˗Ƴ… ׯ1-^˖-t zÑsssoUDJJJ fВܻwM^@UU555ӧ]HJJ7S6:u;w 99={7QSSGBOOfffĉQQQ@UU>KFFFBCC o:ZnІPVV[n֭[VbYmhԩ,(>44?lll0`]l… hР]\\7?yrZdd$Bh1&W0x`ڵkpvvf~߾}ܹ֭*<|EEE3g@ǏO>,G&LCCC| JJJ`T[dt)]ɵsk3ƎK}۷O#Ҍ3裏>۷oӒ%KM6H-XGGGe2z||SXĢnzzۖkGر#Ν;dzrªU`ccUVa믿->7m4ŋGii)b gΜaK!uuu,ZZZ:TVVB$L?/)TTT0m4 G!!!***۷/={{aٸuѹsg =zsxJJJŸ{.z@,Zhiiaʔ)066ƺuPRRUUU )) Æ ޽{yR@CC#TTT@YYRaeeDbBMM ***{Byy9{ܶ&;_qq1ZZZPUUetttPUUTWW׫}6BCC@NByy9ÇEEE0a݋.]`Ĉ OOOȑ#ڵ+V\CWW7BCCiHJJę[UTT ]]: iLHHȑ#̨fBHH"-ݧL"s)qq@l_yyL'ÇSUU۷֯_O)uN@ ,*..PZ~=%''~^ Dz ݼyFɮ۽{w200SNQbb"3O$&&gffF/^?^JHYYYl2rww'GGGK#SSS T***(#sN#hD,f̘Aiiil_rr2 OOOŋtyޱ322H~#}* `c~,GDo>0a]pm0***;w) bbb( f̘AnnnrO>+W4ۛ<$j.L2yCQO8)ڂ j*DDD0 N&}a߾}066f3MII @JJ QYY)3TÇx }}}Cii)z KKK\t :::PUUEDDB!k\vMnK@lVAnn.]B1}t|(**իQ^^K.>۶mCv񰲲ĉh׮zj_~R+Ǭv=-%%:t&SlXCmSҥKŶmpuVl޼MzwyyyPUU쐒Ç#::ׯ={66l}ȑ8y$e˖֭[ؼy3dHN4 %%%R2>L6 SNeAeeef'LII<.cǎz*Zj*,Xk֬iб***BMM jjjC@e=z -- HIIAnn.1j(TVV",, HNNL8JJJ077Gǎo ":u EEE077gG\878y$m{fc_0ftĬYXm0R`nn[ǣGx)zWWW3--- Z/Ʒ~[5)ƢErJaӦMRKvt)Fc555TWWCGGXt'CCC#kkj.k,׏)--sμ^}>|8 T#???255%]]]0`JQ]gAGӦM#???:~8]v ŋM2DtQbڵk+^w&g\II(7op̤VWWg}F={RCJatYKvSSSΦlrvv&^,:yd1??)8(ԩjՊ233uaٲeR!==K2z~~>u֍׷O>lLpF6sذa{Ak׮em ,I& mݺɯ?̾deeEZ۷oi۶m<"''iӦMR|N0zffԒ](Ǐ'4k,ڳgՀYǺuV7oeeeHKyVVƵk׎rssY$Z;88Paa!1ީS'zKwg@hΝez5%JKKNa۶mVN***OdaaAnh"2e %&& 4`6>|et###vԱcet&L|?&"¯JZl|999?t҅ƌCaaa믿/ݻwYN$}ۉZnbd1ɓ~ GMD|/҈}ƌV "OxkPMMMZ&MDt=K$9yݝxsZS ''wiӦѐ!CMQ>}ؘо}H Pxx8mۖ]F#Gd/IKKOw988ЬYk׮2qIE]$I-wED(+kkkޯSZZTk׮[>uג>iҤzctΎ&^ΝYY Dp"ѨQaΜ94f*))!]]]:}4YoNIĤ$Z>MvvvGjj*/ۼ ,%И$^Z_99s&x]y9p0yd/ /eۆ!@Qj*nݺaҥ-9Dp!?~^ahhkkkƍHLLDff&QQQ#;;/UUU.mڴ6mD_}988QYYGo۶mƆllľf̘A2S5VVmڴZEEEvI2s"4rss8>>LLL*7?$z-:'%%msYf+** 9{eL⧛ wf֛pY={of阳s=+QTT˗/`8JSAGG߳ fI022իf|||`hhCCC֙xVZIoQFڪU+<$fffO ???ykQoݺ5.\???̘1700y}򐓓.]666011۹`pvv Ny[YY kՉٳgۛm_t ٳgѡC%BfJԩlmmٶ@ н{w@rKSxƌ@@zzz֩S'8;;3X֭[m۶zb-|rmdd͛7>_d- ---!&&"aaa={6q8cС-uZj&(11455 g9uT3򧧧GgΜr)7ݏ?đC#Gy󉈚,3g8do}}}^ /׮MZ"@1coFNNNJ6664ydfߴiuܙ]Ɔ IMMm+++kXZZj׮듆<Ƥ}u҅rrrܸ54$y>71rhF/--}AֆG'" 5~@P߾}ݻT^^N^^^< gצ =zDAAAAR t1r {dd$>>>D$n۷/;v޽K}͘1RSS]>>Q9bcc[XXoFDD};.M4nݺՠqڪU+X+Wl媉TDG,NH,n̙3Xl6Ad``@EdkkK'NKRJJ ݻw/"Ξ=Kl?}9[pV/D\C\gϞ=˗ŋY8rCH2dLzVVYZZN$ %EFY}z"^]].Z>>>rqqRUU_TRRBĉ2u%%%Z|y 6DDwa(77|}}jjjԷo_9s&͜9O.ttthz/^$@@^^^`tI9ˬPF'2O?DM+Vx-Shܹ|rDT^^N CCC&VRAAD"JOOBϗ{SSS)))m;wWŋLyMf͒Zs3ӧ*8rqqɓ'P D$%Ν;Xؘx SNpţK^YWNC$^ 0A^[Ft6KeJ}Z9ZaǏ>|"())!!!w;wЭ[zգG?~xpUDDDSNEff&BBBwQ8wNSSSXZZwޘ0aB!<΂!bbbн{w8# @"BiiL=z >>^^^HOOÇyv킕ӧO1a>d>+Wʕ+l;88ڵCnn.k_yfhiiaذa ##]GG~)RSS~z\rڵk;_~,vj9*hݺ5-[KKKL4 ĉ'SV> j0TSuC[%v]oQQQhȶSNaɒ% ý{rJ ###|Xp!JJJ .`ܸqETMM ^EE???aHHH -,,vׯ3g=˃3@DXn pիjjj#33QQQPRR·~/q^p=zwLǞI&{u\f?SQQá'OJBUU1bW^طoKٱ7nl\\xyy9kccSWRXt)BCCw'N[i ͺv-碢"rttC6IH5jwel`` -ZNLzz:8p<==ISS%8MtAAٳdճG  =F>CڰaYYYX4hq&W}ժU)ښ}xQ;̙ۤ3ѣ^-xk3:̥obdtWWW^т;2il[__yyy1vtt4D"FD$-(?>yxx<K\ް&bt555h׮UVV6HF ҫW/*..&c͛71$KP(k]hjhypydggܹse?~쌅 ɓ'HHH#Gx}Æ ֭[@/%jՕ-]L=tPiժsssCfyƄ ၴos4TSSVc;v,f͚76p*^vHKK (++%ۛ0aDfWUUΝ;/8zx222h޼yĉ8GrӿoRWWWțО={ɉH(iӆidSN%A{S޽;YG2B/w GRF)/]DDݻw'wwwС9993KݩS'ee]^UUغu++\hqFJ*`?C iVSȖ-[x/ŋM4fڴiWMM mٲΟ?O= ۷/9UEDdffF{%#iLW">x`6ڴqF:CCCz*djjJ:tjj8Kʞ^^^ĄԦd?4&&&qB}rJvν{(""֯_OÆ . ښРA(""-ZӧEDDәSLcϏrrr¢E] >Q^^=xm߾\\\(//55W$vZ*..*HJDtfgպsŗ_~ݻw 'OĈ#󝹺"** gϞ&K9$ nHMMo+2! ###hkk믿fV_xݻw#33۶mP(lPqW^Mɓ'cΝ&n477GPPPݩS'L6ouD"`ooׯC]]8|0ر#lmmwΝ ~^^^駟"Byy9.\HR:K"&&.$$,XǏGPP@,6l> --JLL+T,hFݻ,A(˗/YW#OOf H?c?\-=cڱckoӦ -Y^xѠ&&&駟~b,Q|xDtR*nݺQAAҝÑ#G2WҜR^^ k ^cMjOҒV\ɼСCdnnN8yر$jftڴicUj$t]3}ڵ<+gh":t:tX .ΉcNE8=\E"=z˴!SSSݕ o57)55IWWW*RSbtx.kjjŋij… u*ttthռF||}߿O9]79F'"i-F!iiiʕ+)==rssVSSCM>>>u&faY(##nʮ]_6Ƃ^*9;ڶmKRcty=_zD"͝;Wn5UUUUf/,,l1fGJJJ51ӕ$M:Q䑖P(d?^\J_Pzz:ڵM0IYx'q~z,]G"##aaa۷㫯BNNY2,X ,@vЫW/ cǎ9 8::bȐ! ڼy3PSSe˖͍ij!//FFF޽;qYV޽;헞aϞ=22E LLL`ff~ Rae..];,VJwPVVƽ{BSL nݒRr⫯^G۶maff* : EQQ~Wڵ /^qAbݖ-[_XE&^zEG&@y,#Am۶/迳7[>nܸlΙ[pnnnnD>>>_Q3:g 7񄖖XwҧOzO4bא477,G\BN񨥥Evvv4}tZnbe߲eёvEcǎј1cёƎDN]dhhHNNNԳgOliر4}J xgHlC紣FFFqF""t7JCI__"""x2886{Qqq1@,^hԩv!5++-W\ctIq)1C]#3gZ8 4$XDTwII ӫW˗^/-Y|||>oС~z^fw9/>>F/_-߿Ovb㺺4uf 6ׯ{O]|ڷoO_Stt4"ZxqvvvS*,,ʫ. ,..&&ct"b*++رcGoX/R!t}]}{Dbf,@'={V1.99Y*9x>رwԉlج~I^_Uoonxz%СChr}:M4Iqvvvt);Vĉ~:>gϞ'pvvficcc| xpͨgpaXh>s^Bll,`bb{4Gرc<^EEǎCBB.]Zo"O>nnnlDz̙ NQPP %6;;pwwGbbzsEff&OZFFF(..Y\KK - UUU!++ {쁧'yyy:qh7͛7idii٠YNҒFݬ^IoM6M>]ȑ#>Ґ!CxIH6oL b2޽{I$Qee%u!22ؼy3*++q}5U{쁖LLL0`?Fb8}4>cX]vqH\~/^DBB"dff"77oPUUEϞ=1n8L6 ::: "ܻwgFDD}}}z gΜam۶w?k(((@LL bbb{rrr0w\;_w}MMM^gϞM6g}@Ücl|077w}x=TTTS_ryYea")))e",appp!CЫW/blؼy3ݻ}"--Mn~{@C{nL8AEK.'h8D"ݽ{e5k⋫6uT9ަDqq1=~|||̖W`& FDIt1qqq342zPqq1IHD" f^$uO8AzbhffF_|ϣWJ744ٳgK9?/t;v֭[k|ι2)Mcׯ^z*hiiA[[O>eKjHJJBǎKJedd`ٲePRRT!!!ppp`\PUU'|OOO|'L7aXjؽ{7W@ #Fd[BWW'O_Ç_~./^+B!QVVb,sex{шB^^tuu1d[[[\z :vk׮LetYHOOǞ={PTT_~ҽUBBaa![ңRvѢE -h` shows the documentation for the given command. Below is an overview of some common scenarios. Authentication -------------- Before tooting, you need to log into a Mastodon instance. toot login You will be redirected to your Mastodon instance to log in and authorize toot to access your account, and will be given an **authorization code** in return which you need to enter to log in. The application and user access tokens will be saved in the configuration file located at `~/.config/toot/config.json`. ### Using multiple accounts It's possible to be logged into multiple accounts at the same time. Just repeat the login process for another instance. You can see all logged in accounts by running `toot auth`. The currently active account will have an **ACTIVE** flag next to it. To switch accounts, use `toot activate`. Alternatively, most commands accept a `--using` option which can be used to specify the account you wish to use just that one time. Finally you can logout from an account by using `toot logout`. This will remove the stored access tokens for that account. Post a status ------------- The simplest action is posting a status. ```sh toot post "hello there" ``` You can also pipe in the status text: ```sh echo "Text to post" | toot post cat post.txt | toot post toot post < post.txt ``` If no status text is given, you will be prompted to enter some: ```sh $ toot post Write or paste your toot. Press Ctrl-D to post it. ``` Finally, you can launch your favourite editor: ```sh toot post --editor vim ``` Define your editor preference in the `EDITOR` environment variable, then you don't need to specify it explicitly: ```sh export EDITOR=vim toot post --editor ``` ### Attachments You can attach media to your status. Mastodon supports images, video and audio files. For details on supported formats see [Mastodon docs on attachments](https://docs.joinmastodon.org/user/posting/#attachments). It is encouraged to add a plain-text description to the attached media for accessibility purposes by adding a `--description` option. To attach an image: ```sh toot post "hello media" --media path/to/image.png --description "Cool image" ``` You can attach upto 4 attachments by giving multiple `--media` and `--description` options: ```sh toot post "hello media" \ --media path/to/image1.png --description "First image" \ --media path/to/image2.png --description "Second image" \ --media path/to/image3.png --description "Third image" \ --media path/to/image4.png --description "Fourth image" ``` The order of options is not relevant, except that the first given media will be matched to the first given description and so on. If the media is sensitive, mark it as such and people will need to click to show it. This affects all attachments. ```sh toot post "naughty pics ahoy" --media nsfw.png --sensitive ``` View timeline ------------- View what's on your home timeline: ```sh toot timeline ``` Timeline takes various options: ```sh toot timeline --public # public timeline toot timeline --public --local # public timeline, only this instance toot timeline --tag photo # posts tagged with #photo toot timeline --count 5 # fetch 5 toots (max 20) toot timeline --once # don't prompt to fetch more toots ``` Add `--help` to see all the options. Status actions -------------- The timeline lists the status ID at the bottom of each toot. Using that status you can do various actions to it, e.g.: ```sh toot favourite 123456 toot reblog 123456 ``` If it's your own status you can also delete pin or delete it: ```sh toot pin 123456 toot delete 123456 ``` Account actions --------------- Find a user by their name or account name: ```sh toot search "name surname" toot search @someone toot search someone@someplace.social ``` Once found, follow them: ```sh toot follow someone@someplace.social ``` If you get bored of them: ```sh toot mute someone@someplace.social toot block someone@someplace.social toot unfollow someone@someplace.social ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451832.0 toot-0.44.1/pyproject.toml0000644000175000017500000000306414656344670015721 0ustar00ihabunekihabunek[build-system] requires = ["setuptools>=64", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] name = "toot" authors = [{ name="Ivan Habunek", email="ivan@habunek.com" }] description = "Mastodon CLI client" readme = "README.rst" license = { file="LICENSE" } requires-python = ">=3.8" dynamic = ["version"] classifiers = [ "Environment :: Console :: Curses", "Environment :: Console", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", ] dependencies = [ "beautifulsoup4>=4.5.0,<5.0", "click~=8.1", "requests>=2.13,<3.0", "tomlkit>=0.10.0,<1.0", "urwid>=2.0.0,<3.0", "wcwidth>=0.1.7", ] [project.optional-dependencies] # Required to display images in the TUI images = [ "pillow>=9.5.0", "term-image>=0.7.2", ] # Required to display rich text in the TUI richtext = [ "urwidgets>=0.2,<0.3" ] test = [ "flake8", "pytest", "pytest-xdist[psutil]", "setuptools", "vermin", "typing-extensions", "pillow>=9.5.0", ] dev = [ "build", "flake8", "mypy", "pyright", "pyyaml", "textual-dev", "twine", "types-beautifulsoup4", "vermin", ] [project.urls] "Homepage" = "https://toot.bezdomni.net" "Source" = "https://github.com/ihabunek/toot/" [project.scripts] toot = "toot.cli:cli" [tool.setuptools] packages=[ "toot", "toot.cli", "toot.tui", "toot.tui.richtext", "toot.utils" ] [tool.setuptools_scm] [tool.pyright] pythonVersion = "3.8" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/pytest.ini0000644000175000017500000000003314656344035015022 0ustar00ihabunekihabunek[pytest] testpaths = tests ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.848274 toot-0.44.1/scripts/0000755000175000017500000000000014656360001014453 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/scripts/generate_changelog0000755000175000017500000000165614656344035020223 0ustar00ihabunekihabunek#!/usr/bin/env python3 """ Generates a more user-readable changelog from changelog.yaml. """ import textwrap import yaml with open("changelog.yaml", "r") as f: data = yaml.safe_load(f) print("Changelog") print("---------") print() print("") print() for version in data.keys(): date = data[version]["date"] changes = data[version]["changes"] print(f"**{version} ({date})**") print() if "description" in data[version]: description = data[version]["description"].strip() for line in textwrap.wrap(description, 80): print(line) print() for c in changes: lines = textwrap.wrap(c, 78) initial = True for line in lines: if initial: print("* " + line) initial = False else: print(" " + line) print() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/scripts/tag_version0000755000175000017500000000302314656344035016730 0ustar00ihabunekihabunek#!/usr/bin/env python3 """ Creates an annotated git tag for a given version number. The tag will include the version number and changes for given version. Usage: tag_version [version] """ import subprocess import sys import textwrap import yaml import toot from datetime import date from os import path path = path.join(path.dirname(path.dirname(path.abspath(__file__))), "changelog.yaml") with open(path, "r") as f: changelog = yaml.safe_load(f) if len(sys.argv) != 2: print("Wrong argument count", file=sys.stderr) sys.exit(1) version = sys.argv[1] changelog_item = changelog.get(version) if not changelog_item: print(f"Version `{version}` not found in changelog.", file=sys.stderr) sys.exit(1) release_date = changelog_item["date"] description = changelog_item.get("description") changes = changelog_item["changes"] if not isinstance(release_date, date): print(f"Release date not set for version `{version}` in the changelog.", file=sys.stderr) sys.exit(1) commit_message = f"toot {version}\n\n" if description: lines = textwrap.wrap(description.strip(), 72) commit_message += "\n".join(lines) + "\n\n" for c in changes: lines = textwrap.wrap(c, 70) initial = True for line in lines: lead = " *" if initial else " " initial = False commit_message += f"{lead} {line}\n" proc = subprocess.run(["git", "tag", "-a", version, "-m", commit_message]) if proc.returncode != 0: sys.exit(1) print() print(commit_message) print() print(f"Version {version} tagged \\o/") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.857274 toot-0.44.1/setup.cfg0000644000175000017500000000004614656360001014605 0ustar00ihabunekihabunek[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.849274 toot-0.44.1/tests/0000755000175000017500000000000014656360001014126 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/README.md0000644000175000017500000000126114656344035015416 0ustar00ihabunekihabunekTesting toot ============ This document is WIP. Mastodon -------- TODO Pleroma ------- TODO Akkoma ------ Install using the guide here: https://docs.akkoma.dev/stable/installation/docker_en/ Disable captcha and throttling by adding this to `config/prod.exs`: ```ex # Disable captcha for testing config :pleroma, Pleroma.Captcha, enabled: false # Disable rate limiting for testing config :pleroma, :rate_limit, authentication: nil, timeline: nil, search: nil, app_account_creation: nil, relations_actions: nil, relation_id_action: nil, statuses_actions: nil, status_id_action: nil, password_reset: nil, account_confirmation_resend: nil, ap_routes: nil ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1577956384.0 toot-0.44.1/tests/__init__.py0000644000175000017500000000000013603332040016216 0ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.849274 toot-0.44.1/tests/assets/0000755000175000017500000000000014656360001015430 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/tests/assets/small.webm0000644000175000017500000070011714526070754017433 0ustar00ihabunekihabunekEߣBBBBBwebmBBSgMt@-MSIfSMSTkS,MSSkSIfA*ױB@MLavf53.17.0WALavf53.17.0s-g0Xk_ykD@Tk4ׁsŁ"undV_VP8#ツU0@tׁsŁ"engA_VORBIS@pbdcP8Qvorbisqvorbis*Xiph.Org libVorbis I 20100325 (Everywhere)encoder=Lavc53.23.0vorbis+BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d( Ij9g'r9iN8 Q9 &cnkn)% Y@H!RH!b!b!r!r * 2 L2餓N:騣:(B -JL1Vc]|s9s9s BCV BdB!R)r 2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d ")Ifjihm˲,˲ iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r, Y@R,r4Gs4s@BDFHJLNPRT@CuA gqM*0@ WɨCuzάϳn{vv=w;;/SouGO}|??s?x+o_nO_u+k_~ 77? ?u?go?Y?'ÿw>~s˲_;$ovz'~~;o_o~BykW ?s_ߴ?=]6~|{?}Rק?ט p?a~}3o[_}k]0m;/KDxߌgѫB' CXkm 6?BI~&RgA%[K?ԥv]?gC]̞޺+Fttd8mW :@!P2ֵQ $XD2@]xtR;*90SWmm 3y8':`(,ܘL"#$@.F = F$ i df'f3pqe`Yޗ!] >m2~Yȸ8S߀XTcҵzYZ=5QCqVҘEw0OЎg^bgW!휳*܆E<碓=]fBr7bjq<8,IK6RzbǡZ`+0&HN @%Z#!ŹT=hY7<ƬAA۞){dOdP}j;R/˱\;Ô!?uzN%8(JBFp]qtYt`0E 7@P,&wBipurE PHM r_lhkqWvńHtpt&像j]> Ժ|jM-$< FMߜ =\S}WKn{paGk%OiYR%CtwF-Kn臅a<PԷQ i98&蟴q m~wbltYxxHGd,S+ i.怉r!IZՠ^ifzW^T]@uے3bcΰ%eDW~X6Sv%(?E-҉7%AsZs pBs3Q!K0jYÚh2 KHq!ʖ3#Kn_F-8t&^pb_vEd~`MNdU )rWG&b+ZaIVh|YXkG*NlP:x# 8?@/9f#)C3)eG]zSxVռt%S|;pQQ&+x(¥TccX*ENjg:"d~)GG7VICwj/0#6LRC*IE¶nL:8̕)΂:'㭥dN짏uvpܡ"fK?Q K{}G6%3һ +Ӧ4 asd[Z_MiEHyS QEf͂YlN*BP&nU4܋-/\`}H+Qdc~A\kJ\ݲ>P_vy7Zzc? :r\(~.c%;ҠAu8 ^)8Eܓ)?_ G/--.~S*tOh#d|ԏv婷ݻI#i¤iӵG@G6!xfKb`zr ' rY]f"v7n|zYIdk94c.HZv2W? dQ$ VSeJ5EŴ͓>Cp"+p[ȞzLn72d"@`F4R3Nf5`X-Pbܝ8oQh q -ޯ=R N'Ϩ[PAU;zF3] sܮC&" }mP6^W>_ɘL`)v4\̃)?b.yg=[G(/4`hFcxj_ =&Phw6lC8~7G N"W, Ge,=kBvѼYy/ Ud:../tьވT} i_2ɼe_~/'S z)9'#8*idbI9reM{)@guM2VKHw|1RP3;Zhx+/ʈ`u$OX8^ f@> d{&_plH`G떔i2jNxDvWWO|(=?ĮNUӗڅv˚?bKBh 7"'uc| |kc&(QA]Od0<BoF!! !JHuGC\5)ڌLViki+7!6F v 1z5 *O8vaX3$":}`$ӹV"œִÑEA 3n4kFI<ɦ=7]%$8_ oU9i<zv+p],s5'U3uQjuYv^!6[W _'`'~śihb_ cKffY⤗J !(amF& ] jaDwsiVi F,yepրM$$̭#]WB%(r]{zkt5j H+nc㡿%(rF擝ڵNl& GN,:P~H=J@ 2 rg2+R%%/|d9Xi3q4.1. )UedV5 R0*ñ69dnѮg :ƁbMA׽ UzO1P2m &5:XclP%.go26M>>-PdzOt sA|~Z^paX|*/r1BKE. x!:b-dK(W~wz<'^FCRϴ:@A$.3~8i"R_-["ʋ1OViQ/31D΃2{q^M=1ىcv[)6C(1]5wy8B9mczW qyݨvSyn*GT)ԍ޼܇9SZu{rY\l{j]]vTj5) =~z)Tơ^ޘ/qs[X_t3RDD1 GZr@Yc;P3]HJ'ql@g:lCq~d_cp̊ @iEUxu$}8ɺ)-DΆN8.M 2< IРIgլw\KuW!lHuFdh=p<+9z >kq_朧0U̕h/i[,t]8ؙh#L(K}#YX6Mʀ3@Yqqe_|{ ;5-z"aY|/=%hSod#:$ N&oI>dUy* X|¸~~R"A;~"9PĬ62Ee>8G"%uf u}{07<@S5= KY,CDm1~w椴BlKcwVғN$xju^ U>jxUY֊k} kof҈ 1$C]:z9'bw8-aFcEW|!N-VP6UaԥGr|]{^CSљoEvUKn^KWXW?{ظ:,c[X>ӊd0>#Z7F: >ԧ!滺[dȷC3ET/j̖F 7|.\&jY>ѴÓ:xe(BjPFa ˺&U [Vo$q кK^~]|IF:8uPuqm/u"6tԋJbp H$j+P _E/+vrk }"H9VV>:Xms͝ *]]m"h ,Mtͼ*goA=<„PtPˆqo3r=Wu,vO j0I?s\;8wop2Ȱ=h G=jʑ=H)(lsa/'n(2Ev }vQ}CmPA s/+QL%$(`3EGJq=G60t3@7fg), i{}*,%PV"un<֘;% U~wU -W6g“4s!;Elbd'w޸1kː읿$9ᛤh)5P?/&imY#2u; %g%6{@w~fN:*-@9`3Ws.% E̮ç1)b bNQ>HYO]::эwUC^SB҃3f;4Я>)wNJ$@Mc$/ f!bLMsRo쥌 O1ibޟ!$@|!YӬԀ#@+Eڟ85MI?x Gc$[[zb"Tj%h/-V S8*[s%@Hl,bW`]!8VVl~L݅I4DWSZT,9 a9_>F7W2HkNKv˕{C͟iU\eDˇ3Li] IN1Ҹ O_?IJ(]E _ε8xhEZhg|ǒYS*:q65y@lC4Xj&eːZ32KbT'Nt\-K2nQJ-Pg#"=Jq6mX9e +؟|AÃ74}X~hBTQi:Br`g,"XW֟;'$x!jSLz[fiw:Q;VCqEX"8`K1ě.s-Of&nn%Ph/Q?__y>ª^ؽJ E3>\2&p bZ'edA%bX Z(&J=< 9>%7J(t.PՋ{P`M2 ( ) |d83:@ pl3;jVt-"KmX%Ts25+KL* YOck6V [k,]Y _R@!ݪABRx&xrԋD ǧd'u&uRW_48b7]N2۶zGXʚv- «~iieuG_hbgpw*nSt|f(T\7f*qE &u=H>؂W,,!ǖ:٣rgqذ4Śk{Uں|QIO\#ǖ] 37w)erE4zʤLIO`_9Opq(QP/%mv /3cs flK^杝ѨGbؾP{|wonA;E'HP_.LB'-OߏZGztE`x=%g0= N;(|Zq2Ӳ=zI wKL$k}МhkZK6ei*ݥv+t51X;ԯ$Y3 5 hZ6( <`3T ss\@3 7mbv#`F`rJ+ivFS{.aq|'#r\+PZ ۫k}pۉn[KSq.޲CbAJ8^v~vD rT*tlA]42^"Ez X3g+N E\ȍl+T23]2eHv#W>G[D@Ƴ }=7(m0 M *ogNn nXs0,Ь{ %jۈRuT,ھ1MZV,I cVOFQŖT?"Nˮ@Exʃ%Ꝺ^q- 4 FChAP.B:C0*]㾃L`] gZ#/y/H$yc@F(.ÙV*d`2n]&ZrD|o ݡv|T)C#0bu^?PvG0HF$+T#Tsf 58M3 N4^fd4hXg/_B7,q1HY $.( W.ӌִUl?멭9>L~V0 2ի BYG4-"ټ5oV1}Pk:@صIIO܅;Go 2g\Lv$|iqj?$.0LAh+Y2w^Ƚw?CW[Dttzp'"A}i'~4 =`[*AD1, B-Ws+c+āϗC Ovv*9tim tIʷ/<:g8;Ё9#VJd[Y4-0k[Wo~~K}A~q)IܲUV͊Nlq\06B)j1"PdS[;1%@6ܩeW>{GkVjc*-!#+ˤQ&zot{-A9. ܆~G*iƐH+)"2?@ ٥cW4rN iE-n0h`~s*֑j-&Ϝ*sm;?ME ]HT^V7 a=)Ovrqu/Ki4 bvݚ:Nϑ UG(0UGqVjػ%)۟0D5E&ae68i k5>e[P צ\XTσ Pq?823}ϱNY9ӆ:7zwUk_D@U>폼\njƎA%8! ϲ7+;%C7,j\Ԁuob*9t5 P(T%%FPJ^ψ0ΛQÜj&Ӄjd60 M˨w 㼉|Tn:*N|{ ) +Cʫ ?&l&4P·.).F̫*rJl>\*Z&\g=~AXK9f#8 WAϲ#P6q2GuNCN x5*x -w l4"'o oy  ,V ÅAz7@Eco{!4jnbzT4KM+W5F}ۉ{ ¤\*$_'xFw/bm[xv4 :[w[{,JJͷ;izf4#VQBVVv,a%Lܮ=b-JGZ~+yMpK*azNEr'~Rm@+qDՔ6j)h^srL64wcWJ >~l{5Ēݜ$]mh;EQѬ~yź9x*SJCV)I=:P'퇱(5 AFT)p?TWL(C@c:7/SIC}`[Tde5t:DE3\&!ZqC{aur±'sOH&{_\92Eo4 R-Jĥ\D$ vĂ~p8^wuܲF8`tbX7<(I&0өu︰PW]pR? !WقזvjŅqt@6"9&;Ehl㌔BG^3Z+A8avB}K{|KrfF-l#-ГG"; \R&%aDb)ߦ?,E]{soa [n0->r"SBZRk4OKt ȴ;TUk%Z~7"I<1k8GNTf <m]~b.PHLYwk.bIrgLɌlDY5) Қd5{\vG݆OT1Y'Ǻn59D;)fY] Gqɷ/e>W19S Kf*.S&_$8Ӟ%YStSj,zUd+o$5iv>]]έ2彀a9 h9S'_y4Y'#2Eup[hU:GY ]@j'r[}i2$ӋDMd ?V (:P>H=J+f-7+2VE0[_ Sδ?ЇYuhuNz,e)'7Dlr Y*3 P =~yJĀk;׺{ =S.o?m' vK)JTK5߀w~2'eRs]wAKn[a8_k9k\ [LJ-J->vAekg6O7O˜m7Z[WՕ3T_~ ~9CW{.ktCaSx$) 9N~ [i^Ecx~QaG2'jr_7MCF=4R?JԨH ^$烍NYtaM_^Ohny\Xل|D`$ V|p%)<傘v(W(պY$4m6ĢK睼*ݥuzs !t#V,9iyNiX wje}xH c u̐eI!H1>@ /K? h.9!/'-u &$R-#zS3INN69cix*"VS/ b; -\ x?d]x){KÈ29r`T bvNjÕi6wړ@IC{}ϝ}z\x)㢓)VV E%dO_;(u!')3u)B  u; C]viVy.[U#&ky'+o .v @_WkR>-G kNGٴ0 %^ 2hi7ɻJeTVƨ84^H[i(x%6З_Yb8]Lclg3 p7!հk*4 w=h0]7@荥^, vz6&"[lhU -B$ٯn"y6GXm|7єe2B\6r+.ws6UZ. :re*_,O0ڼ̬d͕R -J ;摓Y%Xe\/hU("ND `G[li޸ĞFPd8~ jzP E)iH:8yo= kV{t%qcvHx%t}*U[{;NО>(-d'31Q`oǠsE #SW+df%,?VtIꜣ@Tg2&¶M7 M&,rŽɇfwFPhL}IR2~JX'X<B~< y2x.-&pRX=o`5Io@)zR(+0RLD%׆ѭGi 4K!R0*Q g'~SOf]WԗӤ_t+ F'H4 6=^sohap.}4,S7z2`Z\-^T"VDMl\ecb)PvnvjLF|6[WnRps8Ne =UU Wَs%ve&7޻;9|g~;ia%m{a?̾u>3im~JXMv{>ש;> xiD 5{ Hڙ_ :¡`s\=g;7=/F958ŘDWSQ e&d5wcb|^_w]Ox 2g62Mʵ q[T3]_α Ec5'oP xCȁzA;4wz!2w`[lsL6Y0Rb'O6Oyq8>C'oOHaؓ.'V=%'h?1 eb؀Gܜ E@#@%3:.*>AP%Jާ}ncsYA2?Y06C6=;Xi,]DvA5](ܧ='جդU+#g.''b?{FDů$&v&87Uu.FbڮACڵl\1ZTYZ.~MNJ@{v_D e<~JƳ<U_9eH/]RMq<|+1rn}TԒ$&LiE*^w4hv3t1;yuM\uG0W1]'ն(E=1?wr+`mb^u,j~zZfh{zFmEl{ 4!]' Eur65 UBRѦQtujVYJ se3IAU/}D$ڕ\V$eT.4|:(($3~G*'x?ËO}CxG3~A ~J&@yvgJ#) yzoFvNԋ:^)V}WlXweEmS$&zz〴whfUyȌ`R~`"m<%%6UJ|~sh}'h2*7s6gm эgJ{$UDv­ۉ{HuG,*pmC߯% ӣ>c' ʁ4.!;F&t7A7WI"6scŐ$塤qk4b (B#eH,m03Vr]<#ȒoTc h z=($`K~1.'f]ecɮyn3ϭkBlœW{_bҢhTł9a)*- PIpU\H`ᆶ[m:[uWqdQ 4,&O>{*_۶iW l{,^ΘK:ʘ]-J8X35u"in`%XXҺj+f=f|mH#\4#l';c 8ՊB243K荀_`i4LTCT.G1ǘ({Ehw,' !N#m>. vhf_{JET|\Q/3IdxvhΩ^2_?1y2ޡ}8:Uyd1^O„=)1saqM[a{ ~jB%zZQmTYkd&I\ BHV]{_4,v%WEk~7yBu6eahljj5F34 RsWF`|@+F>& =aXuRhiZ,y`*";G ZTӲ7aej?Hf㰿V%a^ycS/Ɍ!N%H[eY>. K کnN4}Ă{|vܤ6f&L`g R؂9e4D\G#pu W kݳg8.iǣ{q^8 ?k`lP#݄i` ۱˶?rץTծK6zuYIMs[\QW .zZuvDFl쵵jVH@.iτs۰o/Ӊak# * F'?BNzu˓F6 ! P'Y^IF>>N n%뉝5zRB U<٥օ?jbI"}2 +ciސ]tبkCidDkYJA8B0w _>JEF#Ƹ#ϋu7}D4%cQg^oOwUu>dwCID@:PH2>71="NlL5 `q=FspFA:-*> c{L5B&[Sy qȹMbLD0?tacM%D@D3Q'#U@vKWa d`jpObB/Q$2K8׭[7SA!S.Q>sFaZxPI"teJ<}Lܢafjr߸顟d* TR/eд]8Ω [(C"y"U{>u"+,4VGe9u雿!NmRRIۺ$IFbGp%zZEsCNiAR1kl0ZjlDE4UsfjIgE!M t?|D.O h&UU]D~ٿOFl & V!ԇ`orx|GV<.;ñVpz7q\H(f?QHUWk&Ŭ"r8q^U*o.:GVef(>ch뼎ej9%JnM*~w,,PQ9#4C 픫ːD)O0eWEݷyȶvo9~a ؔa1:积M@W&S e( B4)g xh+a y@;RNǦ]P;rvK:I=ΓFY*q(86i*敗:pyY2w)h+TRd+VNārq.~Me|-<vƮԀ4[-ª"<O=sG8#5j̚35Ӧ>^6a#_ mOՍ{Ŋ?L gaZ9gjfR'fBԚ`p|{C!ί}G'[˺fT/% - ۖ _(S\g4~)bͧKp)"I$X<Qԭt _$"67U@ppؖn/`BjÕUo`, PStŸ&QʓzCw;Ъ޶g韦|,6fvXfƚCyo΅ǿY:4|0ߙlP8)͸SKܜnJ+@$TOC7;o3'\#PG=T!ώhPЂB[ Y{<9oPe`ˡۆyM8 )4ѽWd dpܜżK y:2ob'^I%I|M_{n]= QS_鍡UK4cQZ;LF^s;ԔqX)TLSvTdsB zUh2]|'9AmyȀvF:qs":Rtog ٱ(F ˪l$ ހri !:X$Y |.Z ~glno}<w9B,AAEfF"WlJNiRa"/ǒJ!&yKJ7p9|w̋OgV/YRٌ#ZP4d@l51Q5˹Ko:8?FT"G~ì|/.Mst~,xHNՆPg`q.\DLؿT t;xVq0ZEqm`JHl\EfTCDBIl5h Gg_&e#~|p /S#ec)ﵮJ69f0(3F89M8r+rUBm\:8.w cJi.+P??r A :+{/z>noc몖;R7s$FSCZ(r':&#ʎ)'M@D1? fő; qO$6sbF\\,f`HtahJ5;gstÜ2,%Lw"7޾*&;Cua+]JkDS&ƚ2'$ZfGj>C|XuCR[q &  ,bYGΟ)v(V?Sve!%H}^23-t[mb{ʛt&XOx}H1S^j1i -jH yY!GtYjf%#@ 4aK ֻr#Bl=QOYۦ9;h>I6GL))A-5r-˱D{$r7D9."v?MI Q)wr) HP1<`%VnsQ0I1]6`D7-6A8acߑ:!a]qXY}GoySu)'"HGa;zrb2Lk2ֲw,^/ߚ=@_ECZ=Uei?%;I5f7@ L?Ny+z2g+g9ʡr{ bHQ4#:X`Z;u %ݩViwЂϭv:A߬ao ON`1. 9Ŝc+_aT>(\ Frڦ( ΁>$W}hǹ>dT<+?!pbӚ'z2etQ:,+ P:n]?/gH6n}GЈ]驛qt^!]1c#aM(pnm;Kbz& -s_ZeĢQ{ooTi!/UH"n/.aw\{ۂu@|U|Q:ݷ0=|a6sa r$hTJt"_"2 ,""O:n2jiУYKh}={W|l1Y:A,c+-7]>w'ygWl}hu%m)P@#EL B`e|/b?XHNZx:|Ө d.'NCaEŇ7YWM˲i\Ry~(qt脹LZ GYd68?iDhUTrx-_?h35Ĵq\]9 Y Ut]`tͶrMu9NbqL{k}ue3I !Ryc1(Cm=80&Ȝ@d)QU #"`[CQV[`Wp9R@pLcyُ #ս .~du'9}.ҡnS`TĤuC/by9?tz6瀶rTʚw)4g4nd5ÌF/_̵? DqԹI:JN7I*a$A}fFJ 0 4tnBA"E&.ٿyrJN{^NG\C-4} ˌaJ({21a0}%T;tJ_9ނE4&>w|}^aiz)K/`kvNM_8= hX/@Af_6 0 ނd[%g _|ҵyw!DBѫbS]{V!X/+o mUaQȤA$1=6s5Lﱋ'[n橰pէmRuߖK|ԑ%ޒ/ǔ:9ډS5Ţ-U>{G)nG_bA 6Fdf5pm^iO-_e[7-÷],I:XoRn[IV |oCy [is)H2YZ&`c-xɉWv?^*nK!B 6p%oF̊mr "Ëx-2Sء=nC|s=]iCd<AQ9T!n@S`?QJFK<ߴ~z1D0Vic#̐ Pm-ߑ"1p eASӮM)")>$//J vo'E ΀<#wSkԡLwnoQH\=KP^k f6F0ۅ螦 f47?2j9zt3I[s6Kyj3) 64MAk[ cAEN$q)Xg.%n2<)ʸH,m1^sC٢'hl S4A xXPE}%TyZ: LJ7Y~D0IIG2{F"~Th_乄)GT˗9$٭RQza8m'gng4?h݌1ğ(AJy [=u4#5dJ k Y '/בQЉDA7y? YۘF\]]*n}XҸ+'?N;tkSN*CZ Pc^9_(vk ^q2f[%r]Qq3Ho.|}J[=%ol \N5Ҡ iM!G\ل'ӨUP.1z6j> EM@X %` j?/ȈΨ o1HulN}L!r0FX/+o mUaQȤASc,ȵHghm95/7=G 5M}C6{ ^۱ 8n ~pGM棢;#QO_2uvvaVڎ8(-p?$[2^U&gv.ZFZψ^78!HcI;5c5}rY=dǪ,cP~^]?-;]Nǔ_lx6n, qEdلS GTʊ5sKm>. Ü/ٳF6+mx(_4zYzC߯<+H&_@MxUz}Pj<-g6V}Z2/ԟu ҂U1l}[ɪKwH&E)!IὛyuPeQm3BqFGy@PMΆX/+o mUaQȤAQ~ M>8t.*y&mHE ,AA}~tD"O$2PT'y_;52^kf3NY; r%iD|L}r0xl Xm(GCsQUat@F"TfI{?&u&gWv %ʇ UUǚcHqELѠK,~8CFL[Ol:,FeCњxɛ;;G1BVu$!W<`bK+,m]y*"-Ў]-N{ٴ۲ t `Ҧ%B8 q|y r1`ս^!֮B.1G@3TZ?Mn{;3Dh>,ǐc>&og% &C,_pd,/.!ehGH:_FvZ<+j6fhp,>EWS@+7WA k9'AutgɼRL6'.3)V4e5G _$Fښ>;]IF*ǻvɁA2 PH}d?Bj+y.L,4 53U/ ؒdK=SA_bg̩#]%ˇZVph$|tXu^4A} XҍAayl'0)'C#r˸`3nLEnAHH-/LZ= {At$a|7Y jB5 xuryF/NWƺj_B~5BAd%HZՊ}.'Z@S;Ҹk 3!؀]iZ m0?S `pq].'ktX oG ݑq~6 K t7}c{@_Zf_aG2[Xi3Y^B{(Ē@@AK |{ȮΖUF^)awx00 i?, Dusb!z-.JtTH}z#_{<&lzCeFy ,ZE+}4wuH[d'+ٍhxH;}W&|s',~S.ͫqX&5bSf1d'gDsojldKܑrW˖vC[pWqWWg- KcDx61o1FpU!DE.l]>2ۦ: X/+o mUaQȤAM mE/߽1&#:!k?#lF!|4iܘM(a@;toj>p\SAnzQ/>¼ /LTǾϛ1=<əUo* iWV)]@(eb&F̧v^y 1qx*}X])oOL43Pt[nVdy2Lh$R0@Ј Tk9/lfYsZ655tC=|RAݿP$Ew(Ih+ ddqVܿi e}4uCyrhhd_ĖЍfs2"߀";pϞq2;?^X7j2 3C@R>8.c:#R`:PuL[irF"NNy%I<uct>,axckneS3RG?%4MQaT~mPdHd0{13tѻ@)sη͈(aŷ}[)68@7i :c L8!K:LIvGԬ/LKǿs)?U3l*C8/pDf;Bf1e?rZdacc7ks$"/6( ܿs,egեxbiiqJxC AJd̒"i}` V_ (>7xB k4 C+ۺ/ *:keX腂S1[A/D 0[:cHa(}>oX/+o mUaQȤASy#ꍍ.tMm']֞|l7P{%w9 U /OmU#/A"S_CAH -=>*y7FDRg!N\OblKS_7* N? f&rssCfj[~kXG#. 5"Mm][Q/pcuSiܭJXoIߖalF{L '/~gkz/%9/\D0LFjl)Rʃ񡎨'.2i!m@ }:=^6|!G K!3# zBX Lk{:ط!پzI)suQ/ عr˗".K7,/H[P1wH85<mxTwP+ eוq4Qg$)}m%B}鞭)Db&FG~hbtսtuHwN7wb--t%2aPGe`&}T!j.Ӭ (.XQlS2Hq+9L[<}}@=Er$>&$rZ|.'ͯTCBs&hĈTa:.rk+KU y>Vtk\B u8a/sυ3ׅ.؀AV9"ჶH2ٕ]=V- cΡفۻ|śg/ 4xLO@)kB׍|`8h\>lUM/ MݫDzS_R==lzxEQT};i0t?⥔VR Y\N=RPhҀfB_3{vV|̼!`~)5 4H4of=<,Մ ]bWm*)/[3dG)_=6<.궦\D)..me VlкJNۏ!^#_:(R}UT&³`…s4#*1cO.̻_?}O#d;V)LVp~1s=q!ai0UJ0'r<ޝ?ďWX64\ӥ#Z3K󹆉y~y%{~Ͳ)qҫB&cFqi&BKjs,^  ̓d cW7;smmy0LU&!)W.W fHf 3t, @j!]ߙQ!E0Mn,\w ]&֤hC-zڛncBNaN(#~,yqpx,R˞.tUDzYYx#݃9`Ca:D.]#+.]r_?&7$NZM*ʆ21xC=_TB+} B4orc*t[GWgIfNb esa(8 Y"C,AxdfkiT3R 9%Gv<)Y_pc2aTD+7(ţ)K Hj?H7ؓ[$ty1EQ|5N dv1x s7[#zuS&#-OS*~#FikO$٧AvȢŖyK4N؂wŊ~z~gA$}ndBi6~Ni{^ϭ:YVX/x! (>4y{#Wox_]4.3)m Ͼ0ǹvi O*x>\D/ Ɍ8e /4_8/7ȇIaiwSIg0PG8a+sS؃)ӸcD\unїsM!pܹxg<Ј+!v5.ialmCҁN8%-ĵbBjk"5Y%}yf{)ujwG#yZH9"@5Q82uH{5npGiHb5n-`Q!fg^lrݫㇼKǥ%VCޜkuRLJ,5|.{/>>Рm>sqf.?GoI|gbWV5p0~5Ekg Qhxزs¥ USߓ(–>AEd ePdWzO+(KDx4# Z~TdșhӪֈE yT f_ִ a+J{#%GD F^ qGOA G̔(W:+辚ĪٝjP8\Wd3(ߴmGbmlH!iћk 0F'QP{X `G7iEG"L߇,| 0|6xFzƢvxh5Y5+c_;J@y|/6]vέbtM8mLϥ^ RҕiDK_q$`d:AyizBplqW}'= Wn+;@APytvUWxSL;4E,EDx` i9b+}[+\]֘ ,V ,'[9[ ꠻`컗ʑ7 pGtvbH[bғCnk9'%z;?<@OT4/gOhIhjn9Ō#]=-[So_ZLXH?7!^lѸ:#ʔ^xwT SuwӃB/n;H2?SEpg-qБoxOp2(٨Jno#EX9m9{B~>fq AFZ$}J~0tقISeJ h.5kŭրvF./XIfAigi;|U x9\.?aXNi"( Сѵ~A^[/W2n5ܫE=1(AY3*i[Plԑq֠ &vA<(o?oe~|!>hPea[piiV.YZ+u|&?Ј b$;C }GwKՃ$6fx@ l`.R@Ҵ/) ׯ. v+E 5hli _DjgK *O} 奟Cʔ9ʼKVfWRX[u ʼnއv6w-r88h+foGjςa"q|,@ w`<'8)=AŲ(2a1¢5.3S"V3m"{5Z8q 4y)k ujCoX6'c9u-H؞6}V.:꽁{#Bŝ։=s^dmvc2JUFH;鼎z)VN% c!޲kw~W94o5S\IAgUO*$iGG\+jϮ,;ڑv]]Wtj^C u)A8SOTIwO.{6CsH/Qp(d·B̀`%ATh8?C-Y.{̈ Q5*"3M0 6ثLkDǡ,*%?Nϟˌ@~A:\⫖4+vK/&&,+C.*QҷgiDpܱw=i_og).,WܽhZtv 6Eqրx C\)a^ 2 ܔ9Lo[;ñ3Ecuxܽ?ym󞇱s5A1+%TpDnqrZJm>:@؁Q l/XO Ë%S?>,_6CqdJA)Ycz0` uyg5mtc kAx aY]uWXhH*Be:0(<Hk9)QΛ@⶚dD kwWuAiaEa7Vw`w Y!VɉA_πLi5N-&*/x A78 hCN@[ jqa}UR%`U@j=j`g"=v+oK"%I  up\뮳j^ha?o2kf sJrҢ16nf5:茷 TJ ߻f&@idw%ՙ_03a hbNoi{ghh`2+ N kǃv@ lr-.QTo$ W[(uH)Ri l/6jU+ H\+vUXvRUXXWwᯟ`* 通*0EXuIYEx"s -ex@AZ~ Ŷn@҅%Cc#At0ϯViҰ3zށ|cŞ:Ь KY= |z, JcUUAZFIN8I=Z+C[V?Z gںt?%0hQ mKh lZ&K x#wv0njZ|NAH9;>(d P7Tt#fXE+ft2lRR4uZe5a#r>:@x=J =.TѲeC/%j·y3AP~xHZQ\ *bVdSf B3Fe1 b YZ/[݃Jb=|u{k`YOFͮ.Ye`SV%C-f L5Zjg#4wU6@s`ܹ[ 7;.͙X( @` gZcF3};EٜQ*y{x[) L <ҹ1q3}r}dAp|Y09.sw_ pb[FN&sXо7/eꈠV?_ @ l/8Z׽Y3e"7tSf&)$? rE祋 `@`Uȃ!.㾏a.Hp.[GM~Kæsf|J4lϲ9 `AQ^ } jl <l|em_q|eq<. &}!.4{LV oz%iQLƕNkdxSʻ %[>!@= Z: XP~CuݎH"bXڠ*“M{1{DX4{ǐ:_K`Q!A:f^,:"O|2'@[ZY Եcڽj: b7*bT?#D3~G'bJL0y3W[Y*p58L&gC <7e!cIёq":T1P:NūVkAx(.!k<_泥lPsT9)૱=**Sr[9¶7aYJ*MK길# $ZAA#H+6 Kge8]5xģ@46s!|!fozUd 9v9Of;1 g_ GM@uOPK?.waڏЭ1 &@A{M5 * pd,|<ܲ.;zPd܂ A]:5Uofw}][Zo~S̷t=7c}'m|;CM3 ";~&P6>/ނ % ;[!ܦߴ'|NcՇnvZ-7n,3$?É$/?QwO9?.ɗUނA6$a>fȍLsZz7d/Ӫ;dtKD/#u~lbWwK̽2/]a*xٷQArK5jE@ .xpXSxBij%ڙ* xJK]>J\tSmҲ/wTgy$4{ zV?q&S,x|`&PӟZ}VFSzy1a6HGHվqpWCcڳYE)6XRw݊B#+yZ?t7|'5 cp#F$\Uu͚h 8CI~~\7ֆ i8}uvNJ_4($Akm @ڬf,^ݍ~aZiT[(B{WxSLvWϟRMC`@+8΁ܾ7YBH>Qf l`/*=,Lu;p& 6@qO.XB7QhAU^)iH典l-xCK*,"{#g2޼Ujd ACex\z,HJ4wzF`ˇx#2ew['Ia>[ j pel#1{\> uT8 aΘ d P0ZQ_]yF7x0'gK6\:hr jxK?nUZ~:xc3a'HIu7~]9/}TP_S2P(_OV<`mDP T{:qwǤk.m9*GBm$3{wCXwKM})yقM(Eu: Yυp]۹!8|o(ȧ+l?퓒S7fF!'1mrpB:=NѼpV.%22Lx0ϷQysK9Ӿp h!@j#HcN*wBr jD<"Ťbg~ȁjrNbe90h4M̅IFxrФ"ɔ9c?N/dGW9C_v|ڈC@粪PJnw뭘$KjŽqdck1‘?M[`0d?'߾k&;cS2S_lSCjpȤE)V&}!< N~0Tyk?(׃Hp@b7orͥ]G, !)4_@WZ/BKsUը s܈.,pX4* |[c3 _d0[e]- ,iyɌ ʸJXD NQdYq)'0!i\m:j^4! AYTF$DWƆr0pGstBO=ay[vcETo<+p;!R x! ݍu>ZHTyx3:rch}i+49YP-څsmH2cʲeO3ey‚ w/Sˁ~k߭+Mb3mlHȄ@oE爐4 ]GcʣMr)]>i@ARπIe/@t=ZQ2%3z>#:_]idTɶҠ@#6а֧m!S.+ }wzmGCe-vFjY<iזN\_0P&b->>k#}ywc?kt9bR#[Z^OD&3ARXIL b q`7!/ ;P yu/%^c'<D@FqG|ca¾2V*0q ke 8؄)WPW,(J>?_AY䀞̪zK/ĵH61sNKq&HԡטSz5t'Gx$yQ@A~\qmK5ryi;PO1!ъڅ$%2 Yb: fKLdBYCؿU͸gh( eƇ-,c&,2{`PsUU#, p$~)e?kë0툐$!p[ q F܍zdgf5̾1WcX@d&ˢxEnF7q{  >Et}4WYÙ0_ ;[ @ l"<W"Ҵr,iG @rsۓ#:bIۃ%d8l HFZQv40[&Boԋh,p=M^*`@#C7T 1<(oA\^ T! A-wR#O:W84*YE)+VmSa$K -!!h9X¯x|SxbsLiO?>f1!`u' NÎ0h'n[7,lNT7pq!DŽ^)|'bulmW}zr>%5u*NlRMh@x`ֹžU,aE_M:Ϸ \@<&=_NՋ(qL?q3=XPp8JD$\,h[̇ l/6ק`2[#CwB&.+"3z@#b@ߚhuJЦB& PgMa06qTu6XPAU^Z&} hx 5/BEpؚl87m4L:"u1]Zgʛ 3;VmzY$s?Ss16?z~sww bK]TZ*K,k&>yA*J RY۪Ra0`(ZLb32zKZ o;;R.cY!0VP5 6K=ߏsG`~u]2A>ӂ(Z-O@W* YbmG$xoq<&7.hA_$^Y)h$p-xm8!82ewlĥiUqnU5xd!S *ў=.MK9 5]~ʶ]]q%ă^=zٍln[$y@Ԩ[aC]zOzCC:JDVeи@3#84ג⵻F0: VA"4[2ANs=7=UeLf\ɍvphu ;Z ->"wea@iuP_m X~r?:&9~B mJ Q],T7X}؁+1lx/*_2߷la8t9}b@m"+>y(۞&؜7oί[h?aj!E"sAf9̂\z3j 0~uu3ؚ.{y8G)ziH{F)uHYF*^ٓy3Tt@N 4j<fTϠ=&v9?[!S)Ɵ}VrmA @BY0@V# ݐ9iP"3?\=@g:c{p NHב2!@uڹR+f`bJӐb@9u0]M`3ƶu8fl@n!ԊM`)^'C#V Ҡw@+ށLl`/*. 9i_ߌa \iTs| k0Ll GɡS޾Z@AaOD5jxP͢q0YuZ#X#e{ y{y!ј=_+=*&R/mOҴ?M*a[Gd(k2IQ>d~w!`#Z+R[rqyf[4ԭO94U!F*Md>,y'l0W}Lrx!1(5;A$< K@UV)='. V-V. tS\o'`h ?Jd$% ) ]0]Atd^(>j:ڇ5H/cbTף;nEx,Hb۶ ӎ]*$c:o:j>Wz%5H:8%gq@Ɵ8pQHPօjV`Ѣ/Pm\ 𝶼O|5"cW:.³ YkD};~ hX;=oA,h'Nxi$yO='2̑esbj{Czx Ҁ:l =2V"p+_C3ńg(8pOz'u\1roKsk &O <d;S6 }-;bSC~yP2<7%(q2h ܁ml`/*. H!yx)^v_- OoJfKܚ~5]F[$E @A~y%8ecC?;4#ds'cw>l<>-ſGcfXLu`Zl-Z?p,.O@ޟ&\آ1NMo@Ξ:jDOlmuGv(rMxh{V(^d C]o\l(ն C5D@W^uq`J.KX@kS0xZemBN )5bz*p⋃@sz;\E^c4 z  p`8T)uթLPFRE}' l/6] nIԖj1(g ԼE3`љJX eLlAjp> +1aiLJd4 kArO3uޏDo1BiHx%(T˛ xut1pA) &'?^גS9doт]!yk[Rbn0V6^T` ,6fY a[W*8ݝ:HVԲ_| .\y+:ܮN>5ZKta 0xdDPİ* KAc-W `eui/V+Q8$$+ )a9u}t'vCLrFPwK|3K; Amnj~i( AuE܀mS헯DMT#釈} م^~9ц3m=SD&@;v@ |*SE8X/ŀF~1QLqzsײ;ӿǧmyD*A- ZͬSX'z}r;Br?x"T#jTw8|!w!nk 1u-Z@R0Ŗ*;'geΰf9xp>xh;`^) ׷0 WrJy5<s5djb LSH cZދ$bs֐@"J1 lx/*<%a^]qm-eQpH"ȏ9~Ⱥ90vPBy}* ħQ<=(A?TgQ^^ Al~Ժ~ =_N]K&ujM =5oĈB b|@[Jc89|z{]i[5 icԭ;AX>.kBY&0 l# mCRZ};;4/V+A=>tN6(*zYGcRTdWY lv0Q>)նtGks|ҋn =A wQas8?<܊$KsG|PKǐhB^8tHl% j6c^4} ӫAwπ~J.gQZ"DJ#%YLc $k? "_#%gCY\@KmcNόB{v`.mYM~BAePnxcժM_S@>Ag†T:lR!B} w5DQXH Z*_P t {.ΏVp'2<C1{Q!@;b/i5b\YXqP [€yH (^)2(/OLt 8,TDb@k *R7Y0bP[O9{l`/*< Y;wP4@Y<@-KM2ܣh3}R<MON_SEK}1ffAy䀾̪:ϳ(0"^"fF}%`*DT9Q^U7˔P[[Ođ#ba<2ȩ ̺6Vl60x椉ǠGO?'qw 2 BoӼn9 F!eBX`C2$#jəZHz_*E uT"4(Ў 79:<@3\tyۃH+-ZK7[#LMa8nr< 0rbn$Pi2XNױ .7jQP DЙFتTK @Rw5- hej1)قI O]=a0ӑe0ӯ^;SEl 2_KqeGYN^9ACe`$+oQ Yhihq[[ 0v7I &TsgnɘD~A}ENb-y."5a{: 9"<: @} JV؄xC`PwMu Tp|4vM Hrl8<zRxU;bsHW`^DآQB C)b'W~Qyņ&"◺ސ[a D3D@G+#S&2]ҡIp~)ҾO A+LQues!BKAb1,Asr#+jRsVM]dnR8 IV<]n'`N"E$@mK?Q+Z($_@I@I!TPca$}⠏d' Ar~%b3GL5A/)hSӲG?L0G 4lGblOe:"CRqk>0SNAnhk5 Ȫd:^Q*|?cF>V, YMw3 <˫;4Vʾpя`  3̈, )& ppQ("!{v6Q<%,Wk%2s j"d-vZ 9ݳ$8G؍truCjܛ`@|uX6 p[d@ L>hWS(X11@BB.5PϕPo: dDA*@lI@e%"93ز. +Y:԰bo) Ȳm"))n1Hݐ+*NU;N`\3|Vxg/fAl$%܀h}ӞK'+ SA*}QBKA)ML(^Ƌ4IwTI~b$;w AU Nڥ9 CfYy rF}~9솑CDps7} { ܴGwL[Y_4d8i+#("Nh13,SUt. 3Z/Y0 R)^^G~}~6JZ>vnvDY#0jP(S?v9b,!\)=:u #Lٛo ;DF5 L{hs̅y)q%h`8 /sŧQTiR80/iHR=>08. =!0˭]8/,J47|.Ъ E_[h:JWd:zkw?ωRZ`h@݀jI|An֨Di@EⓖzR *'%-=!"0љWg "kE!%-@$c+gCz ʮH? c? qLAg>,O8fKfZiٵD2|= ZJc̲7՟UCcGQɔDd4 ;W'F}B yzg1r) T}f@n0 ]1 DŽ+W+Hmh seiש@X6t7D8-BXZH($iv <&-a!^>C" cAAc9 YC<^[ %,o%5I50H| HMHX#N?72%;FNMf(m9>2fIoX ?#[oL&@4ݘ?*d&''|7'CWhЧl6}P+@H E\0%˷kR. A;;hBwv3-y6AWzOP 6(><AXO>xĊG?U,bgBUVlLL{1A&@?nu` ZmeTiH4=@ ԥ@2NŰ-{d&$%`o|IHRQE(XIȻ}j̸XK5wCÁWQ lS[~p hSrUI=T" iFm|D>ȎN%0wsڌ3xćV0Q_1zi|m^<]R)uGUgg}( D _bWG.2lL'-館[ 2VcBd~+47e0>N`TF>tRl!H$I n*%F39lQTjyUq+jxk1zXpO8+'GhyFZyNqayC-v'BO4Lt.g[*$;=O nq4kAr=rAo!s].!qTr6/zi7vc2VcuO~6WH.~dMD[e xqdR9;5L \[e(J~Z> חQXB^UT5X*kCݖ)"xV g/ Iji g8PCpoNB7 X6fni oiİȿ|ŹF7,dlnklW}[V\byea?uHF{| yh40".qc^U˧k9 ܂ foz4XONn׊Lp \o%ɰrgsɠ#s(% ?vƃ6jR^-G"XN*+֚yRuG9R"uSP P,e'л%oܱ24 80|A|S$~b'~dzGZ<%\kD[HEVrEez3 *0 9tadق2d *¤TS\=tyKPŦAО +ߦDsUjz?}jH Acd$jcJBPV}Z*+ aT:j8f# *Zf+ BaKUVj v5QS3<4u>8ײ]tnt6ub>+r8nio#CxOҊ"vfM68P`ZW.qxQ*%h-~/)!n&>BoLF3JJJ-΂D݁xQ* l̴׻P>=%/uE:5sv0bG4c1'-y|ejqwxB!OSq%mh~zT;dq2t%]' 'W%&qd)TW3pVl VHuA@5ߒڒ f־Koѹg X[ rUF6/tϯ3+ηi'vIs +Rm@"(pBc4)[N9=JDF{I+`L|4qiXF ~N{T;Cv]V#@w~64;9Lְ |?Evߝa4zMBM^'ЃY0477_0]D|!+u쉡piYevK|D9UNt&OtAl>#;N!h rL+y3 .pvYh˂#uA yjDnZ@e?BDkϬՇLE9_)Eph!ߛYʋ |w,1'qi9 RA?bu= UƜ31ڢsb>G-WaC Qps/,->imjp4M-"FDSB9X$jPI²+:#ڑBlk ~Vj9 Ig}wRc\f8N]?&ZHO鶑ۀ:6Y`!uG^ i똖\h&zӠ%&z>@&,`B 3:ܟJF_&JpYSS[?[*]Mz. Ӷ|rH% YLdߑ[X.7^?.WRkr. b*S wYhWS0ć..ĽqdWƺ[3wds)~Ѻ/ܯ%  tTKU% -|O S_&2vIrW읩>Lo1 Ѓ7d,y5)CEa"%*у/(䦨cL-ɞ͸-Rna 5 1XF^fi$o=T%Jnzb.*f4h0X.#1h3 -4Q<}䡮ϲcA]y1wW{|T$Z7dJp^#!eN )L;_`d'A0#Ae5P6$@1I,eD\ɳw]ixe׭.{d!M 6!GىaBZx1Hq J5ؿ;Gen:?XsqAl:@1a׹Y~ #wqǔF6XY _C.Yu])}:3 +Eؼ>% >L3)ͺ 2<  ϳpAE X*I@s҉ V~ ppgiet2آ@ҍhk A_nt$ɻs(Q2 +@C4E3 i'iguz ]a-yۜWݿגZA6(vdoJPӕ ǃqey{x~XRS>VKߌMi) }ZtCߴ`T~@,tuLWr `$&"m)2/WV4ϭ)cyEjvCς.^е9=(iвXk+iQ큫F;rRBR}|vka_B8֨Nf40Pן4}#ɣB"9`,%П~ #=eX"=,ILꚊiЗE o/\rܹxzq˩Rmtutܶ-Bdf$RR/V6`à˖>5yL!bL82@:t0a c*X+HǼުvǸ6ffIE"xc04Ju+ڧMf/8ri^^mѝBC -M4]%!J<$2cR\FBuf*OҾ;(jJ/mkx 4)<"{tŨ#G(>ݼsŎf(} d|A~s57zؖyqRGj/FI^&";9 ÖrI'$-ͧaP|nH*i5)St-5AfѪU.<|NHպk1- r9*s"LAQ^Ɨks-j0 O{ڵʠ*}ܘ.R°۸Idf㍕  ͪaz+ ;lZoz%1>՚zNidJgS ix*n2\!-i#s8+=4#ន@}t˦Z.lH5Xpr#}::&XC~[bՕvFM ٦9=y uJ`}H+ZBʜάTQPKIX5ppIrpb 7\{;Gf͓,@AP KMbt㥈:grъ>R/'jT@ )]Gp(p8yCX ;`9ɩjo<ehNYlA>JߥyLH& D3Bou:Vlf=nr֜PG/f p[UB?WG\wS!@fFp32&!*W\KVŐ=$`#Z]1ڶ7XB!@p- &0R4TR6TqsaT݅2+w`LWSeBQ$@-dNo>VߔF暿?lFԊ# T }Y g.i٫pN}-_۬*&zfݽЄ :W6m^@nUPpڹM6ţ@Ki)m)fU_Bȡ\j-_ #CF][ ]#G=CЊyFԯaw~F+MK7Bl k}؋ ~RpX&.iʽ^|;tQp/'AJoxQ T*-JAo˕{4a8[2LLooxiEĬׂ\$@f,11&6gDKIm*cA&+FWAB#wqdS%)QJ G+ Fۭ⿶m ƚJt; B)PcInuwlR^8ݳD ½Ɵl?Adk>N8y]41R=Z'1[dxdG,on-WS9XHwn w(gxKǿQ2'౱b / Mͨ?2A w 2:uCقͅMdK9F| 4 TV >j5w&aX]}pxTQ,ǟU3’ظR꯾ܯ@+5:2tP#0ݿ/qǤ!2h^y.ܤtlX nbǦNF3Yng68b 7B3 j'?2ݲ5ȩaTztR48nM%xH) o'>+W|Q`-ka.Gx39OԌQ"*&(t&۩aTl1\mIP8py I W-d.ܮXa&ЅUP q Os!߮LTx$O[ཪ?mJ}xۀAU^̩|MvC$B-owݰr[l00=ThFfܻ[nF` hez^k}XϴNlH=ΏpRo~ BԕdAVNҮqP:7nZHyx?瓳~P]: ɽo3N  QTsʸVC8` 91E)8qFyw* ĜD9?AVϻ_j,6~hdS(aUf+}qqA|\6<0qKOBQYJSi(zmp62՜.--7M`*krs1K֐ȎM|u.{eEf{mgpwl*B7{\#gŒ>E>h-k1Iϊ'c; `26 5m)=a.0l *"ml`|A3VG_p>q_cK~GoO=gB1>fݯЭTՈ'vc%ckuzZ,<Zޏ\-3u=JMusd)=_xɧFNYFRfAS$.fFuHRUFMz"[u<$8aWk#e_3Zb94{;%q/ ]PMiўp+dQ:U&QRTbخc)w 3yD1VI sp3d=ɀ'\c@:'%^ߢI>i^2H:>,Vwڂ6,0 E/-fx.8y)@bdq&_97 F9@:@:d} =%s^$-2'ai@Gl\X-A[9 TgB!78 :tg=pwAԀXc"!T&v@`lz, *MOUgʼ9$1oѶMԎoLU@VI,M]ٔ>%Wwrh@FVAZ= ]W9{QBN )Cc-3wl<fQ=L}Ax3@Dme9,`%.aZР-WŃ(prU%=t`G4\3m4Cd9T قf շ,b|/rBr;?,1RYy ?4#jͽ5THSG{7G}5߱jTcO5cP̓lY 8ݽڭ}H4ְ{_&cn-K iQ-ALOpOP (K)::ӊ[}mbՂܫz3v+[ZtL<$?taד S7&u)̀.&4ḣ~18ZWZuLKGO惴ݻO# 679 ȜsmpU76S i9Kq^jjf)xŋ@~j$˙b=o5DQ%i?\mm?=(թT7nΫw{oEb 'Bpx<}Eyzv:aܩslWvd9}n֎ #@Ad#'ɟ":_/5<1)6%Onlm4c`W N7i,'>ީ_glQk #c)K%PoUP%K~\ZI s~}W_Ey44X`I-1`PY}cge}rۭ(O?F(Q5~>*8Bh$a7|t_nWzqS sTIҳ &l_gj h`ڀ'g}[îmL!i%OLuTa2 r~jR-㿲1L!w|=W?O^ xLwZ eU ޗ+jo%}"O)%aoނ{唔@s=osO~^ox:ݽߍ;%4쯒<7ۛLfA/Wj܂})ۏܳ}_m1+>4?c߲: \HnS[o^61#SBLx B7!<!.]{%D(Q{htv0\b۵ĺF?Peۜ5pN 8CdrdET9DŽ@ԕ:?Y86QInGܬ)hމ7Lp?vyqĔ2HUmy4U|z_' o@ݻ8t&oJ/*"vz"Iϑ`w|G'4 U8τYz X,+-3KpCMTeBc~q}$luG;ϨyHj4=3?Pi顊^ʣ̶a%\'IrP˹.1#vY6*@o 4˩%}{oדK*r"/tG:̭`)S*3PI2nԍg*yt#/u'@YMA yF|˃?;\SJ ^">}a7>K`A:Q[Dg10v sDo4M}5g j_UJ6p wE'$F1|y4<1nxceŕCA&(U&;@v]>_V :K=jn}zD"8&o޼- 0$~v3ާ1H֎+;kv{:=FןcHȻ>䣸\i}xXQԺ6\)ͯǢyw<6Xby2MF檓I^/Qsy;bҢ,( ;πH2'bRaE`70xޖpN䠆HJkoGc14 )s bYCF*v9gҊ~g +L._a,T`"e:gs*y\BKܬ It6DXSCOd<|L80qpi-A#QU4emHS3F"7? p}S`YFX57z_iۦ-Pvӥ;/וi^hK㕓/r"=̨vHtfXZ ,p:P'>OM61SnрS%7V{@265٫<0r=[,bœgg=U6\q666!5We zZo,2e)&uOcӶpi LAe7j64?a 5Xjٝ϶gdcOΑc^0֘}uVG_Ed [ & "vG8Am x7hnFɮQYM{鬜=RXN8_ۿ4] 쮅+YOӋ7`pQ72#sd-Rf\ 7|:PZJ-g*|`zNe^X6޿Jq2]y*mG>u#-/P`&Sxbʂl1aƜFݰk,bÎޥ!R|W>q2Iظu%1d*Z%)H8se\43KZc@? (b? nP,"u؞ȱLV>) 17J'f|ªu=A ɥT 1m47lzoa@lK ZdKwCdݡ1&vAfuѻIA UZ.t }0(6R(o7G41IyΆh4*Ac^AmBldf\g0G:;69 d R٤K Ղ,FAEZ=1e&ͩ2Fm,8Iȷ^ߏ-􃖎2"" RuV%O-:47‘RslӷE [`G@סzEM S 7ՙd:&_,193"QV ddh JBԵBQ^GY*.H F0w Bcq% &=-tty[!c^nv7ʡS\-zDwWʶPm-/&ypἩ4Gu+/ZD(4LUʈ*N(G|ڰmdwpoL(N=歝ؿdmi Bb eɈߗkMWFRQ X hnoAQ^svDgUn@6TndМ A-IƐ-?`1AP4: xpp5g="鶕86ʘe'~4岈MsH]#òNu(QRY"{±ƈ(UBfwf:yT~y|9' dV: bV2@(|m}Q|;SRRTÊ] Iȍ F) #p2`BQixhg. ga%5#uPE47mO}({SˉJ(sfUiJ:i85W8Wꛧyׄnֽh5EXBr;Ɗ}(W8 nH\tdN9@6@Yeȿ1)40x|L|+LQi'T=hGƷ&ScD㤄uew=]05}3.^Ʒ,^ ̯UtI:,˂x(# f`^ is ;6@֋U/ng'w1tbDBHHrs%6kbC-PMfe#0W92^^y.FSt*El7))0}#Ǚ[/6p(&7cƈAYɀ^̓:f:d#(yn4D| ZJx8ڣ<0}!VRNBeLv,p*MADmrBܦ`ſ@D~ 4pݶb>M.ʜ_dh M"}@TpB+󮭫gYiUڭ5A{rْ> 2 ,]Y~;ޜr:/ o'ph,(3^Xz !P4;y2mOZMxH#ꝻύTÇeS|PH ]XAR߀>̓og:DrR,KV\~^&sO̳@=iJ:ݺ %c˄jߺzu(%˦AS"vMçdtssqV<\%T8@`Xџ~`193FbtڡOӲѾI̓s@0b|-bawF0EpuA@(ɶGa E(V\="̃9$*̂ ˅]NgXYw9P`Aف1+* D D P(XbUĬ$b-=- K<:=g6N\ђa.We-Iv>=~ZbpWPk5gVa;fیKLrօ51]av"~\;ՇJd:&4ߝcޞKSHlue&Sb.7WS'K "vxP̓>hhH.B׈ǣW}wѹq d#hWgIe<͎ؓ1r,Pjlx;sx2KlM-* (Bҵ#hTFILs~L+"|V'lmR ']'jQcc=#+3ùGIJxb45+(CLB[w|):Z'*RXſ}֊Ou=BiAGAR+2~O Hws 5ڢP|>s%xMd3E0PPܖ/gϼ m;D8Ph\?zo_[C[e=;m %(Ѵ@Cڥ;$D]_6- #ޯ3c<Mw$)XX9Mr!1猯l[cx; @q F?&\BU5! hoY"O҅F!Pf ʈ]hUcU=)qcW,aHM&ɜAq;v,!)W&d o%/L@J  dLΗ^>phV9AL콬pU"@@rɺVI L< b\WPdAO  \SB]dI9HӨ~Zlc5{f~{Gy"Ork-NtzB>"Fn:d#yki@籏q+n!wGD6gQɞ2d`4zA诅*O~п6u?:הvaW8N`@V|1 ӆgt&Y'g >0ڲ*o#?KjC Zw𻾆]h[-ĕe|4͞7c̸s&~GjdCx-O\cFMLMԝ4*AJI\M:$8[#Q\ZA9S<Ĝv;*`ah3JU4GC-6{GeC|#^} DE bӾ5 M" k܋EWŨ|Wkl6>8.K8R '3"["#M$F5 G1;C1Rʀem{0_{jZ> ZBQ T]6pxyzP&llRmĢ)kzfҒsbxA<) 1 }IAF$mUJHS4CP]6Hע7 3W\ܩ;}NaJ,Ċ mЄ/ Ҽ">đdik;|q3l72&*F9R񟣺ࢉUELƮڳ~{vui,:>!׉N2Dd/R" Mz)ʢn.עԯerm0L|(S5'cNCI|f{lѴMv`g+D՞!M~쪔vΞϧ7GG|}pL"*8Qʑ %)ފEA鴰,Z!J,VZRX/Aq~{Ck%Q|`;o+EdO40xi Woc-Um6eH@+@ۍ_FO}:5P>C)9%x$݋WTaċZ5T"S hw_3sZ9_(ұ9S[ZFh9g=~ׂp3!N' U.~ÎN(\c1@uү-BK3DpuR#wI͢&lAY_&S2%`emE'hxh'W]U 1yiUX:uP$S ␬[_+p}q0An`=^9&v ubP8L{%bM#v877$T>R,BAh64\鼇&K%_l zJy?qr@As(? j iѕÅ#iYXX(w+Gmr -B ^JOQʫ}H(Sg"/W (ќV("p" =k}9A!l4HG%*2]l- QN$TWtNW1 oa*ިumW[ ]ۘ+wLWuCA? ;``}. bHB.NO0^IW #$c@F"4vb}N5HlYt7_!pHihj+xC9zּRq=?/o6qfN@5$얾 V=ҕb^|S[s௷)M ZtfAKt30S{C]dЄ {m& L& ٠44p3Z͂>I#cn-(͖ݘ27' K Fa?3_Cbpb)3}PS'" 9B1"_Ά&刌r`GtJ^rne0dZ9E+{ T!}W9ԟl. "S-9?]=u %W0*]9\&[klT0"֚Xs+UF54p[Jm3H)5&W]VAP"X`?U1UH"Jny3+~ * / zM1c}Np7٥6DO#Z?z~bJJ]:9jݕ$=y‰[1b*D*f}_?5E꣥6W+%턱ϙEbE,H{zJx"ހ;O[:@ {0UQq\P2ѻ}J0BgfpQZsOJ5#o;V>_oY70^MRmX쟌t \ّѥliH ’ƴpEr];Jj8~ُg{W6 ffePF+6:Y ?Ճ[#CQ& ]=2Gޑ*.-UF{uYYK$C~``r :E У96k S-Զ4)a^ֶf[ӵ }ίdNVz)\v4^TMc|4^=M./\gU-G_BZ3_r㊊z B:lR3UISn PRQZc.VđIh=ߐSgQW`0#B̮0K3zh:[G<Ψ;:/zDV$֬id_(ˡ'z aKՊ`Zr^9p'fK@‚ՀLw0etñk_S0T5"utS;WN6s: ,B؀\wb W>/N>D\<xtZTC_3-Tk|>d*5qPEM܂ۀ pYKvk{,Ƿ/Bys;.y[̗7#:q$ #M}R[8g5R=;$eiYdق݀ K( mFyܦ|sW7{lRn.۵?fAz&C_ :JzO˞y{FA 鹷jG MF~(ӣ͖gk5ҝmrV(ܒ+Ϟ6|1ImV@0Y{|5o@KqM1&_y6?l_ &?ᾃW.Z;?ue]BB>$jgIGuD.` ;}8fAws&;Icj@iвty?+r'm%xW@ϲji/)[ؑ"+3I-z'+T#,&Q /V7I4dY5B:tL( <|I[)._9"%J*mёG42B+ (/XT&OĮbxix{CM -%61)<&jH68.40{5Q"lZFp h-h e{b)l?yY4>sӿ>ȗhY@0OثoxBr|'SjDr#=NBQMO-Ehx*͞}?,b1RN.dL9ﱾ,իre9>Kzf28 w"6\,t7 Z:ulAsIrT"XAğHϮZ,S {K57;]@CKt቞1l.!0ybaEYwY:>ˡu7vz؄qb}LNe0RiψƤ-e[hb^b-(-EU`˱Hks֗W ?7Ҹ@.RvLAaS ASgH]lhoٯ2]Dhfz_|)E\Lĺ%7SW3z0 A^2ԇ.JɆD7ld(Ielwݯ:Eg7;Q`3&gAqgI= Hۄ W1O yVLGId,FSTU:~h_T{{ UB&1% ^?_C1ߊG``WqhƫˊxhW9ϲM!>,ItM؈d_Z()Y#r)N[==ۢlDuJ'd-V1Kb>1wF@.?$KSQBUJE|+)dPfoV ݸdC%&Y5 X9sH5WA/ N%!ԗu";3&-*#3 \YW!Eų˸R퉦+Nդ +m| CʘUl/{FUY>󒀥l 2GRArm)OԈJ8hLAQ{*ID.9Pї,Sj<q- 0bWO;!OoEWbfѠdi Ɯم qFN*S14:O$H9Z` 3'b%۫/fgƎpW@kS tCr#R2ף |c`|D.m<5 eLvVPAM3[rxɡZ71~ |bC{OƫI1|k)VP-~ץɣp*m9i8=urh;Gsx=ej[M_TDeRm],GŬ]O7q uL85Wj%.$جHtVŀW-C>R/34G+<)z"[wӐI$AG>\M%H!kIZL>ۡZ3Cs,ܓ +awc}~1;n޸It $7熗<Z؟Li#߉%<)G &.Q"ΏyItHx܂(, +h׻sΗ<_t߬o7Fr'4O>~rzp+E LYl݂+l 4U$ؕ?>ν&|r{g߻Õ#wG!n((&`+;ۈq#cykWQ|'ڂ-4KTc?|s>NkFagGWٻ9!|[H;lґٙC|ݑB4!Fx[b(JF(išm5>3^% 傭m69-nBi9ϽOY~NM6r_\獆wcҿ!V8a(!y߰K\>@GQI rDAkYV&+L.sd6[eso%G>rba0qfcߢ&fe |e&w `]5 33pbq@!bb'o PqnEqԥ俇{E7畠Z%I&> >=.tS/{~vYN|`>1sucmR֛xV[2ם&=O Zlw<6Q.ؔ?(\jQ ';7# a-T,`/KԦq wIoCv^Um =-|.Xx.~aJ28ՙYڃ/bTЯ5iVYugX@dGˆ >?bJuKj.1@$F jRb$$?7.~}LbK9a|8<7:3 ~+\n捫΀_nm63kAv:߈YXmš⑥ᘜ7ԦGAs/6zgE5jeNd;o.AU -U\# @J f%0:k57e" C!f,VH v}ez2xhXmb=YT;,YYֳgrܴ/Vk!lwgrK#ݠsCˬ|E[cC8HL8k&R &W;lF7f2z0-Xl{8}km͇JG=βO(˔\ܪd^\$(V`GsSU^+]Ԫרѥڱ]%H(7\FEp}[K޼L8j˻W(!Aid&C*\q=e~lv4xH*X>cSUY$*4c l.KGr"Bᡵ@ePRZ`%[> ;c^^ U̓tJoCBo[JjX(':$PnOT?]S;jM,3kU-_ͥh{@a]ؘ-ӿQ;CXdhcw+ ݔNw2MԳ^iC2 eڅq\…j;}ݣ JN _d vI|嬼^^% Hw29˜LNKʯMؼ'瓆A[?B?C`,ӠoHG\ʪn͑YTߺ?7xW@4|璩2Y-C~h{rB߈ox&o\la iƊpoF*䑬SðoJnG9%&z@e㐕~S`[\_p1H~yI͎o^WdnObE; (9's%Ak8O?Z;^YNF,}DRnt L(FJmd%'V$lu٠槨9vNcQ qCv/Ǵmiͳ#&B?xr{.vlޫ)=٭x $޶\[CF[ t%B1yKǜNE 8VIS7/~oA%rXc򼆧が#]:3g:lT դD%j|8`@ ];p:Bx1/c3}4.U Cu㑖cIOymUI RnI$ _7B$7g|9PA1LtFĒh) R<ᨿá{gB fo2?w ᣊpr}Gbmn]j1[~6j] yPLV"g1 pǨP`'lM-kJNd%ڱwrQU0?t-\ͣRO=w8 |o`f>T(,\m=GHv%+NJ5#Ɋ‹WtRz$+r:},1q8G:S?? R ]}3RĎKQz#2B ʠ٢"XNK&I/7[$J &NߴujAfc%յdɵCERŨ57㔉m!9YU>c+L2%ۭbsZ46A~+N_2WVƮe:ċow۩GA:y4y#u5{tyw`^w%GX cNH] 3-˵#{ \2 exìrcNՋT,*^_PrqBn-4tߑpˮ`iPT# tWMl,^PK4js@wMX9Ruս No>(^˞ᨃx.J2 B28@xlf`>3ȿ_ ghu316iZ'栁X)uzٚ!gL</;;_vQCm'ncs6!㭼|wɯ̅xvS*g 2TuݚoBK)fy9V˿OGO.Oim9~hA]@xӪ$SENXSN9lLJsn.SBDZWt<Ď"ſZbEW5%m2yoH5»à%D~Lf/LMPJE\6*tTȟ+ UpXx%M;Oa.R ;  {t.oT/g%a kqe&%f'҄_kpbC7& %sDP=ފ =Ѵ5[\i|m|[̐VUQw ǤAfy~@4CJEI1V,boAMA?V*a&. zfX@~3m4;70x+V߆xo/[S [?v{Z:+ OsùKP AwL<Ѥ5I ۱92$UIY.zOOf(U}>A@r#P';㭆oj{ h,2Bѝ W*:T~2zJ5xew.R k GEvd<%wm=^Js脊٤Gn.ouƝvA`tc\HDuڵBw N G@K4]Q ; #^ }L:gFn0^cb Ztj{]̝G&F03RY1bv m3.i;8~z2{hx&',"Ŵxߴ47>ftOoXx.q7] / 幮׀!wh_y  PvtаŞG+b}Rc"ؼ{s39׶ѽYqGj{`#ݰXpIF[!Ty9L@Ձ(ހ Kjа:q (zeȕYMOHe|}9 q* 0<-h02'/<57>LT3ul 1C I|5%VV+5À'&>JZ7Lg/*|uABK,A4-xWPՒs`lA` ["j?=HpRd1hZo탦 d2:د0L*(RYhG)pYá̅G1X&>A6`V?Z~OAog82 oOs9ϑf7o롒q4/;FոjۭR1(?c[i͋+Sf0>\i 8x |n`jgw-\E?pd$%jq$㝇Q ͧg];#+B4":?T㷱D֦ؒ8y0,s/fjfvߜ۶:o _Aq*2E"o$G%` .JjW8:"jzՕWa<&QC(5@ۯB_4{_UXC؆̭k~b>W;h.Lt^ϭ)0)M)R Vp> C\vݕ_GfAf&?B |d~|Kn} bu,R+-9Jw'Y}Aπbx럄2k5I?v)"oZSl/}Dw hmF[,7 WI+騵d53#{eca.;]eAl.İrS/ S.c㨩ƘE'SD)HURXB0@#Ёd"0no_ H q轋!e#wYhAqȦ, !2:gY 'aI.>P.8i.:0Bĵ_A*DFVUe{3dyٖaVGFBŅrn>JU.ng'l&i[#2i}< >'zRq~ Vz7X 'HQ\ gz 4/ 5> !5qV#lX.'1)WaG iU9&AAa#a0[K5^Ɓo{VpF}GK:ɚЖ ˾pIpCdq'ӐƢ/kàMNK!%@AOy]'. - ÂtEbc" GcC#A6Fƣ#gKۂۀ<K9߉;϶uJi*NXkt[~5:^v7$ O?s?yjyTn.ʫKLea[^Zq7ݔz)J؂݀<w ӡ/˻c^}olɻ E=gV0\Ʋ_j_3'(GIn۫h܂Dw T.O6NhzM WpJ8jN&_?$:їu[|wjn]P&DA:P_PaGۻOG~2Rsڝ+"kV 4 6+%{9yiٓN Hk)|x?Ԑ"PÆwӔp ?5^@@KKu1mci]M @Y؞:tp©OS87O3B, w#cř+RnvwXX5?㾈uS  >F$%nƚ!/w{1~Zv r<ͲhRuʭ/mM2Bik-Ub,,AYP7YmlߣMBb &wDkbox8gGJ%u]ŴeH@f/3s&7rWߩos8JQ(l{!5(ia>f]'~14O6a{uK{Ošۺh壻)b\h w'ltGP qXY/򈜐'5ׄ*Q>C˿#oNε7"\ftS 4!L-\T2r(0#%?v1YǨASb6,J]H%f,FZ lą.`RPPeAL dƊ=-cvFhA74+ {ꢵsriPKVjtZdjmR(jʀu>LPY})036gՕ\4o7ܤ2ƚesY~mM0$.7;F(C d LON/iFא+J'IX? %"f"Ko-ꥹɨf!'|Cra+iQm\KEY7=ݴ3a<(A-yɢ{ѩ@>,TdqvY:,6x;PqEB{Tv @g$GZxݹ\{{^r7>kfgc 3p,UR4HaXz#'CT东)j !ӌ(+ڵY5t}4F(1pj5 a+)&S//Ia^OضfYSV@Z3 _R1T݊́V[[r(890D5K*H% aNyRb=>)" Մ |Vx`"qEgC7s[H"g]JʫDJLռ ݔ l| hſ$[lI6f/+G)PbRiHĞ4"VHO>a@k)ť=a"\tݘd 01!Bl_Rd7cM1`vaB-^/z]l*8SYw (Jխӟ %Y(ZF֋4=M 5D)B <,]?H?6+KT1L?K Vw臔Emj/9UkOY*/Gu=c~馐Y&&w5b% }YB Zvj߈ v|яoc64}$Yl'-%aTŇS.'<8l^y]3x5@7Qޓ ,K=c誚 }h(Vx-+L<)6@)iMf6\7GO(<fE2. g35]|-quWԫ ^P(CmE<^O@'PPt;Y|wiXt4Ċ˷[)8ETb9 pX5g7-Z]j Zh-wpTo+Z-Dx.17PAq0c.IsOc^@(N J[ )0@Mݓf(CCi7]}\CQN7nMz$ԥPu̙^WQ_W2egI]ޮ'nbb>?Y ˷߁';l&KyJyAc "DhqLO sohdFxƩ֪]Iiuubsd  uw 4;ZT/fC:.-`@.%{7Ҳ,b7*tCPh]Zxc8 GabJK, c7ړx3I3F-|=~Ѥ8'-u \~"P!㼑ƻrrvg |[YdY&b%;b $W AN 2?PRo\^2~twfpaw:woԅYRQ@A\jͦszUKOiٴUzT jACq>F)s :5Mh-IbVUS)а,{X>@pGlf?أH`E5>svqDkRIZbA{^)y3e ̥$;3"%#wňLΐ#YfWL@" F^X1 AcSO)&>йRr Եr X/+oOY` g-A0/1lVAU ,~2?t鼃b`ea0?Lذ3-@;O$t$$N8@g)LHZ'5pɵ;r- SYB2O|݊:9 L֐v#MNHZ?x\N?U4~ϻqy}#k}69(d:frd 9jIh̿v%cw8" Ŏ]tz݁R,f4^yH9yX$\Kf&~%z5Яb٨R]Q lTwO)*EW:@X3̖5[Y3+zB }'A ?30cdyMñۚݼ*ţXˁ K9C\өqOs4q]kpѹ@9LI0c}H3ԑeM /Q# x}O50C30dzM5x9$pIAW W",~B"wӾwsYcML#܂>otV4-q TT9yEh"ZwϺI:NH \cdlr׃E׸ĒKڑ3A+0I MfA%&*wRe䚠Nb0 ߎO?2y];?`u&{]ߜgM* 1qsJ0<7Ov;6> ^$V,!M~]b"!ģZ׋DgdrSgnyl A `ZIܕ<` iayH^¸[nblc ?ƺOg=Cp _1L~G7:0ӬMS='&Z,f~;( 0=ӃqW7rm׬׋ ÌР(]c$KT)srnp͌_cC $j2r ôlgfLZƘno=|roM6RKl [ Ót- &'mco4ij}$% (> ( vV"*HM4v;Xˮ7!i(-h[=`LCvk\1rrAlv.(/֜K:8: FDV'ڂΑ懃ϟ~' DJHF 1z]dii殉 AE\vE5)?#YgBsgĊ:>r;GMRk^C}K?p, %su:z:<ֲ/&77٠;(׏}>~ND+YO҅gfUO~b 4DMw+8c` @7kIs<:T#U\X {|<{>ԡs s| P(A& Jcoq8i]5=Ը,IM %lWZ"FlCiH3c5v,eU9)=8rǾdԱ>^Ԉ-4E2y~  .Bjnobn2"W<u(/u`w >.1K~4\?H]=v\,1 /" Jİ@l\pR"n-&g%;ђZNX1. JuǣB-9ʻ6JW.\r!Pu^,5zaADeǸNFֽto՜Basa -+=#yyB.O򅔓ɬ!9o?K5θEx/v)-=sӰn8nl4dӏ ?MLH;n߼K"df}׋M7͹tDeʬM/hmŽ (PA>(cHD1.6reGU"p\~Kؖ䏚цOGQxf M8M ҟ2LOlvX՚t@Ήq %(B \`l^׺ʘŨJ '7ޣӐ6*9C8G9eMq>[O~OPG@UȻVczXىxVi4S]ڤqQA=)E[^^kFZ=/6@tYBOFk\vT"l<⇴5j+em )Qz^J&Y78ƚ%t*pq͉{AmsC̶pIBfkyҞ񧍨9pT!ӯ j;"k i6-BD9*,Gjk"+ږ";J̯XIK+NB"bH[l~_/#&IѪhs֠ٹ^MI;MaJ3q.٤YaE~%^hyX- LTvQƲK>^n҃R[j,B9 f663oƔ0,mZfs"EEep?4墳JÔ] )c%_ҝ5R1k8JjAO~S-Xi{ŴhyF7L;SFP ?)9-Ξ x<&`))nJgtv)2Iɏ֎F .`aA<3. ~J,H4=k *>Ms^{kC]=5j8A)$Uc@Mh+̴.i-ī5O_~;臘%Zh'ӦtWVq?4Ydw+bء~$vSEwϒ|N uq+!摜tYggcީ?'E~]%N QNb]Q2NzPSV YN u-CgYYƃ=V> ``M> դ XDP"ԯh~DD&4 z߽309yL(:"Q 7!BD8n`OBrbJ{TvٔgeW9} ZRK#J/5؍rv,B Of0Ϯ,.nvDp'X(߭"e5^M[*pGaD#eM`DJ )2mp smd!8*jCItH qZ%nw/)Er)2'V(`/epv)yy= H;|PRPN66 UAbw/cD=fj2$Pi͢:%s"ז֫|UF ;?uKH t}H\0=ŤK\a(̫싛%CDim`888Ӂé'QNvw} ,QlWEХR=-ӿt;1Ww{d\|793>9l`?L,=}9;(z,WQU-]ۼ{DGsk72:2 ( !Açh/ѐ eSr  Qu-2PI \TJar^bin_2 <-kꎛ"HF oAQI@#$̴c]hk5l9mǞzug {nq jAƴiEim e!=2n8.%4NNJ)?k刟/u ƓdJ7S5 v<"+;=s Sc`m1_ 9͑y 0`fA&`܉,~YZ?npFwQ |\7y;`M2^4ngݦV+OwO`;#MwxD{Pc9ڠdcoqT[txR#B_oa̯4yC TOn "+P9x{F ϢAY Hw"I>]Hqi6,E׀֊2FC@1]Rnj %2{!(@" JjB^fW(7GokWYvb}C'Y-^h_OÈU9|Iv|O/1HiffM2gʭj{?D>a~8zfVJc:0!Dt~-}_"^@ap)ca_?Bt>*7!âeX Πec+8=U F}FDmU"h=pC q&`'04Q`BȨg] FtU]溋o tz ,uO:V3{d]%*O)3wv`7(-hɋvi.8ٻ>ca #+oG 91R0wo@N?h!%"6<~L cjmuP4}xrd:MP‰%8Ó>y仳2JE7 ʟ1m ~g:uɗ%>"֐`EiIbD9=~T|_m~Akz䴛Y[j+;ftv6 B)D{'y'yl˸חP{PkNWo D1.yQ/^ @R4R^B$- Ew|ܻflZ.C@l[ItJstUabQp8c4Cy_lӥRX,ι lsq oFN7cs%0{Pԙs;dbT%StJچ[_ +6kuX{#:BssC"‡ B#w[)rmRTh,,Skn h(83)02xl@'*H$(5*0E6C戋~HPG|º WN' k]USu|]۪WL>ViC\p&\9(T16#Bfg)F׆d"`AZ ~2?BgclW.76~R )rOn P7uKpdldՓBD67j}࿑D>Sb gnkZ.o(%JtE6R5~48|QVQbGh L ~zΡ6Ov|Zp]LޝhD@ h@4|4߃)+m9i% h?r FI@&Q+lToDlȄ$0)9hH /^ҮiqI (<{0y`ƾOA$*mwJlT{ P ! $ϺƝBBYd}zGɲ %H.z8SdXdEX|s9`C.Ai7<EiLlIRq?<{=ֽ-%ٗʺ: 9=nn R-[ၙZK.4:_gB}񜴓BeXzB6#I⟆ZxM%"rs GfTzԝ:$OQܪTMa_"uJsSSFĦ1(fBM+7>༸" @1H[1gw\2uŬ^Q|Y(q$^9kq&$_n(_ /B! ~7@eamEqVq"OGE3|'%z&Ư."q08CgBg?ʝqL/@J(y;Brs{IȡfMrK,[X^&7UqͤZ,K_|EL}fu7b?[ m*?nݭd(JNwhKN@a', L8e.>WQtHZ}D=-VEϸě /=)`tU2I(Ul`(Ĵ0QY 6Ely–`r>j3Qeyt?-UA)7! e+?##A]{G6j88hi\ox챘V%[$U%hͼgWQǬFV@pS6gd gR4 T.MfpQo3X#D\*0w[W7DCD &˧-Xi2˙Ëd!Tvtr?TxY`9ZakL7a;:<\Z~ySs.YDif63HXˉpH^ r:;Zr#qst FnMi|p^tբ`oTPzo|֨< ` Jm p#'ɡ3%υnw L q2ӕ r`)< C^璏-UB'=yblO ;ק'֞tu*.Z'e+i&v?Ocz-[bh$!煰q$Ui#>rC?*|!n;a4qp,*F ]kɘ9'pK' 1)^ 9 c^(RbJp/ym~//"TRg ld{3gESzܹ#_t|FbȃbOk{v6!a(0U#OUNK\^Fߐ8o:^ 4=q !G`>1Vt &=+o۫iSKm3.Y-l"EeM#֮ya s0;sTlWvؠD;EI/j^j>7-02p9هpNh gEv)Ԣ]id:NbR3ɲ>9 ښ1mV9=̓k%!ΨET=׊#䮜 `/v,ksN9ɶ=c-uC>}=%>S L6]9I]kM"{&؜5PKjkA59Xm#Q/=Lb# 7'IC kyH=)yuĀ;X"@xOl!B]n$_g$t{)M%`=h썭_ pR9Iu}Rȴ4K{9Wt`A] ,> DJF\PK4}+L-s:D9 6R5epw'=uO[N̊KX;J)j6&\ԗI*9Ta{풗ʵsHd!$9}VH[% !`Aa A^̊XM fښUe Ȳn./9- i7p 0; ȂeR X- ib/vRS#SQh$ qp{ 6Qyleh.jmy:Rȅ!I9}]K\9= y4 z>O1NYG4lDŁY(ތhDq`@P 6M -51ݸsN_= }L@U6>=o)rW~ Q!cLL=C@5N%n^]MAB I%   ق`)D$1fp2D }Fg"P!euYkƑF/#, \"H9?Ĉ@. BP 0tA|KgO7ܘ~;4oT[WdHK>wNER Xg#|GFuj>Dʳ8Vh?c+dݶ\m{.zmn;8xMűvʇԮרf3Wh98[D 08H+,D2=z)^_8Do֮*WLثSƖ[.Z nRdAq JV?Vu =tT0/UMA!:>+I1$b[I䨎fol$z8 1Goӆ{H=O$jt5jeqt+ O * Y*hh"h}’1M,V[ʸ]]מJ`4 4LqWewCB{7 >'9N.67ol_=ϻ]P@8J l1?ȉC`eL| HR/3ޛLHr ډvŮ`{mXcg*ڐGAi W̲Q<䴇Z V:ǰ2Aש&c, 4PJϐvKsӋ6huLh573 NjPmAU@s#+*[ņ@f4>3l0dy+cTƓ:q_bѰg9m"A@|$ 3} aЉ _|B kQ)~Gsc4f^$KPvoHQVQʠQr_F̬<|C8$2,s'syf./T %<7>-I rìT1xr5pfIšP*Ga-욶 O5sk?5 . qmM(+8!:9Eϱq3Ƌa}OAQ5F}!,A ~ȅ1\Qpϣ}~4y=bد=ld4!b2hS*(ѽR3Y˴q*yd{j]]8!]!̠: 0pBEX/sS=\n濧V8xEGxXQ7 ToO~Ek@ ԃ$?^.\"@ZQ͆u~31Oxzt`j(t@JH7~.K_ׂ{ bRn\2!2Mx$ R|@0t[L D?/ .b 0j3ZtVo%Wߘtzw&zA}RI:f%̑(^F3n1;M 3>^ oQe|6`Ar l̀e j-̓ BA8Šy"s_}rX BvEO<]Z~&tfl>R8Iٮ)MS͊!"[ٗvޫ֭9Xd}A!HtcKԳ`]{Fo6[2ڙCtݣpLNP}`C! 8g#êpMzlr-ΝA@qzy&N7,B *$‚#seM5IȕZQLػ& 9s,1cE"J3eN.! oab-83`ͤ؉Ӏban6uCjr=!3bzNp?Hc- "5/ 5Uݥ`f쇶'޳=rK$ZNP~58QVģۖ\*TH-̊|QO bR=>.d.1Ơȝ0` OqlqRO}ݳbZK4ft!ZQ$=lAXtG5Ghn 6M_#%_4-9  Ⓒу6*НK=!Z:?dMmDŌI(҃)&Z_ӱNy%p}%8yXAo ^6]WYgmRrQLSJljy:diD{# @k~ЉA RpiD0'@8 㕘`?L>d5Ot|QՒ}~hwMh9ͨl6(͵Qd;u}+摤Eť!`/z+ # P%1}trsկ޷w? V"@yy]4 ˧ž3&To)~%X!$fX!>{o68 4X;:"jޑэShδ*nD> բc#Eց 1@\eL-20< ="3|/lȓQy_i,I0)^PDfX .ab\$r*uMLˁ*d8?l?\Ejh ;ޓ~W`9Ar:of$:9xޅu^ksbq ?ZCTዙ<>n0Ha "Q纻o48$Po$$ J[=(coϊ^% >&?ӺlXyS;o|s*g/4\wTo٘~ b!ױ/L!ֈAK竗 69}(rCԝs~no ?s޳|}.]2÷U!xӮkLg@d8jDrY_5bQYp" ~U1O BX$n Zϗn%8e@d[Au͉Ӿ#3f$6r^t~EiʳjP-0G~]}S̶VUE}wh#L #0ac(Z$D'U$W&TyƄSQSyX4dUߙ׵i ̒CMPy`jE2oz ;X(,,߬DWIh/ghݮ.0s>t<-HO".E0o6T*!cBX> %y~o%6lߌ#}HR ;hãYp\e4;0J( YԴKkRv W"Vw9Qe1/,/@)޴ vLt8` 4߈DT'%w܁d,S;ٍT#0^6FǺ薡H3#Y g+Y|QasdvK鱹=0:49Yߒ6ɓdK7x_OnR! oHpC-EubABOYP܅)39uSQP8tvXWoeHrVB*פU=eFfXC2Hƪ1,Qz7ҽ4OrID|TDKEzWTHO~3"xg}2Na%fAJ|lEplhaNzѺZV[Wlg^6*<'ݡG.#'4}ˊi@eT._S)%]\9!lm7A;ciDAo ׀RX.|7CDsGL3grIe⻓q${@i?Oҏ/<:4Qt;,L@nOXz~ٹ,tHMy1KyѸJXˮrhi,V&r2ݎb vcw=e 4wͨo$ڼ>W|B4SfRݼ/cU{EtŖUE-.6~,/,h E{O3-kxtд?Ra MW*'/D>|'~ϒC;؁8;3hd23Mȸ+OX:?m{_p0!xxE~/m//RҩI3/xsAcVo5tpGS%Z d\ };*B7B@diC82hvBv]J$ 9Rǂڦ;%|' \MB[M_/\Ԙkl0\:=kعΤXRG+fb%qVͯ`l@PΙ 5sÑ&kw"@ʍSbxEz45!5vxgXtw(^s}An ~EPVS$H߯byZ0RDP*BQaԕwEn} CBAzK(thc3^O jF輅 н:򟬩rmgTo.qg栅1"#Pϼ-!pYʮ t#q U\eKmal[48]M\ݿC(|J16j?,t]M#؞6O񰂛6(O; H d <>.颔[cd5{ /C(Z߾0! v(eEC#C'B% (@.T 3XK}~؀3d+>[őB < HS wK_zG&=Rh2E ڃAh h 0T;dr $;/b7VvK~GYO<qSI*VmȌE7shu<(JCB&$D-0\ι"<80 ^I݅MLW|nV*i_F $ʁ+/P~vT1%or; fӡ'f-OOb!錖Kb$Uc Fdҳy-/A뮚X~:1ٷsÃ*VJfD0TF L´UGsi n2G[PEW!(N{y9{ЕߔWY; ` Y76[ظOa *-.n *dsH'WqLHOKNɅv"Pcr xfKE&㟍/sP.bm#DŽ@ P`~%ATW cYo 6 ;B9l魧tf0ё]@Qȁ*kbXQv a>)sǾAc< wGb]bR&!7LAy ,%T`.F'!Ej6\}Z_C*CO5QQ/0GڣwxkG|ʋC6B v' A/hƾgލ |`z6В `][N~F8~3E_=6\ !kk`vӷKctY|ZѯoafJwX@ BDΌ(/\ $/zYW'$!ok/X*} S4EZ [t~Lx%ғ{{űh4X؎f k]yԬ*WOfڿYH $2SF"#&nD7!@̚\B8 31*@F[Smn?+ 6XCvo}~]f :Ty_ g{'XW`~ʪVoţb/:Jb>^K7CfGriY}#wuyGq/v\fm O3H"sA۬k[y*L ,V[]_9>)M +B) 57䙄 }фSo|\⦒3879@fI*J#1[ 'U%a3 IџhGQǐl1Vub| -30c葻UMmޭ^a2mZxD`Ŀ*Ba1Oq^WX+s b[em1A8S=KU\:ڠkliqɒT (X"ò]]2 NHj&>wa)7g 3Vw<^#6]<͕aL?)Kӝ,V+>?Qjr,Az A~%|!)~\,B̈́%IGDCo Գ ksC,S6_n@^dy!%A7 δ.p{IZ' kMXjԹ?0NZDQ^g!cW|nP-KʓC˅!@M`˙F"A$ U2z|N2أ$ NQ/fiX;2+o흮n E%/B9!:pnXae7z'ӥ S;Pr[F>baΦ +|lrTFseup4joi6BIO T1@{#r-g &Ov"2pEtB#] =ۃyr,"[ "0z*d\-xCб$bɯ|2؈#HO^(8|ˆqIF&a`\Q#+WE[Q⤰] ūiTXITԿې!ɸu•sfe'܋. ɯki:\2S<n!qbW٥J%geHGIo4$g%6i S~znmT^b՟=~F-CZ ۋUFˎ;I1`C\dֱ:œ-%wE%A(JNCJh#lHo0/v(kAɨM48?Bj WKl Y=4 d< Onp.6krvXZn]IҶQ՝k:n!^: 뚻@yAhʂ'N ,mޯZ!6 _u /6?<\Uބ<`IS(  $ŃBvs2t0qh*Nw/k}hʀf"U9긊L=7gȕ}6gu/SwIƍ$ mEDyN܂O6|o|*xȫF-D'mjEq/iD;\ȞQt6k&Di2tWUKdo1CɅp#7 ˼`FǤLm X 1X2`1USui:ˑKbso{Tk2P0:!RRӕ(eʓINSpޠfsbA#1J w0KH}}k5GmU ; Uކ.:XsOt̊2Iׅ PͅB/yu޶x2yNX Wc>Qs-c9bE! Z %~[ALxu.ܻhKh{Ka.Z kQݲ>4S< Pގ vW8pC` "oAu l%D˦A$+r~QBK@j )0Ɣ9QccމTtM* 쥒v9 Cʇ`>j!08D@Pll0ָC7Nj*6[wZWY/_KlE|@^q{)] xOJ8:$9)?ܖD`(Bb3+rUm}0:YfBpĔb ܾvݗ`~&cl6?O 8h.ACF_F]=:G`Eyj1 Q“wxdapnW.ٷ6Z#\+^ Aف u<޿Vw""A pn7no'N?J$]h"y5(϶NW~Wj.m2 Fɖ;G[1qzJZ=*8&.)(\4xj[4 3] 2P;L?+]@g*ci\g!>=xdRfD/DX#Ye -4N4/|o~_NA,݉nyZ<﷥\5]q>c}d`ryyi"7r" H~+dT'\Q'Y 5I6Q|$PP4Jo |m[{n&\Ĩ97^5Ytc↻ipA ̒ S$%9ؘڠ7Q8B\MX{+٢'h1@Hꆵy@s3+v[x4ʄm SBv_$WL{x;@5<(ݲs~͎:ߡM[y+ ls]v@A0 6`X9\2i@"<@g~*!FN-| #Ra02 ӞȋZKDVǤ8Z?+Fr* |5-'yIf82 8y^ßA<Z G+0̀0iSV* bM0LV?eNމB ,#< @)b0!Bx͙(!Ƃ8uv͡ZX-x?ކ8;OJBnz`C^a|3 #(]Qጒ}٭i\!+dYzr%wlEO7hxgӺ׷{o5m۽nH[F lyZQ@$4Ǎ;c(=ԮP bΖNJ6SVzѨ@`NwR .7e ,Ǜrmm **Y0mRc @Ք)N?~0ASJm(;s8#/sjAi ׀zL{`̋ƺdƐLI0N4l_W5͞b/rtb{M7T[#PjڹIN%Vcj#EpFQ-t1#B KW"lZ;-:戬aO?佗Db)M}&UpLC]&ڹrƧ@fqr-SO#\:X qˆ4sooA5 !\.[c"8_ *X` Ho:,B&9ٍ#4S-j6(5hp  ƴԤ̔7*ԧܐSpֲ ,3$A "D.K(Q%!a#X7ӑ*dl vR&Bb]$60l7R 9O~ ^ ն"0_>3i{w9jybE>?LbH$Y 䐥w)A\`LPaֆW7%,[MͤpnL-%P J@&2 ;U$g5G*r_KEII*[gwAu`"J:+ԧi:W`klS_}HYti֓<$Aee4srv:2WeRC3FpAa&!U]*H&Csb$P#dtmwUpu>_7 ^j*'T|?\R5 5l>-W]-Ȯp(ag $We0h-}k\w(H!S|rl|P6΄E\Af 쀾J.cG3)Ԍ=3f9<9R9!@GRkRyaJ) %0lx쉓 A篟 ,Oz,| WjgV3Y*"@wwۊTMH#FџmBkU]ݙɝ/#(`ցTa@%n.nWk41$7VLq(hRDsp~+qi-/C!f~i-33-ՎRПEA4 NBNw=A`|o"2|}>zl ĵmh})[0jB9,KS:KS%Lrm݂7fRlW]41(5:L#!&w4ZJ\)ii぀~Ԑ1B?;d߬sх CgZb{ocDE-]-2KKXhd+B9|-v6/MfvoVg29:;̽N:P#]wpN~Ҳ+qOa z쀏-*v 8Ãs,nR3x5Z#j/ qwQzJ%AHEeL|1ʩ'/ڠl(\P:3hN 9[_Cߤx qZ2Hrd)L:[=9 uu}P ߱7>sΩ ip? 롓+d}ȘEՔq[gnB|]swnfC_+4B1IfU4ԥ Ƙ7c|3 J櫍U*ea()*U*>$:EO{/ps{' TIXUf(&iI|/eAۍG2(fL}m'$x JJ"z+n$ 0; =rAc|%~/#[뢂ECinݹ̞#ehc:?#gapZrhٌ3A;+ ?d4>>,o-̫AC_nL %$&V Q5mo¥6 {W|(xdܾ1j#@w l<r0ɑp| WzUvV^'q<'j/S}qdA\oەpWw#<gM(xVcf`3miesl!TD;O`0D6Mts/Gx*aHG <_05 p'lxnpkhf2>3 +DQmk8o)Hk{ + t:↦AZ v]@(_=1'ZFA q$ D Ա"vfD+~k X4yLyPimqz庎Vavpe'Ô;Pb!8ׇH D1͗j!8ZcLV"*j$X^S s\osopjH`l( e(S,DpgH>S|ĬJ (1P"ilpVd PlrJ9`aO| Zn&MW)T_#kEy{Jp%]1ne% YlisBo᭒< : ֓`)M*b9 Ͽ|(cAwH(qeÌNJ}k')[.CO:!&2%X@F <)yrf[4`Tvpqr󋷾(*'cr Ђ[@<^"M˻  Ve`,Lb>ur?階!^vXM93Pؚ 7KA^ƽ8j8z S'Y>0D~O-:CUyAc A>60O["bEi}$u3nO`Y/PGT=ׅ@ZcHpPfC+#+aB(qODSнFp(:UIl{C1];30uOG s~`»bm[_:Ž~c.%ީAmm}J$:57 X%BL%>) /A8juaݺR ǤFc辚NH8m9nD;Q!bā! lf$5:/O4 oEsa"nz#=|C0j:h8cIAk W~}x<5iJ&g/Z^9'( Da89@m`_K [{scO-04X* Չ=DehA[1g2QyF^R#~7IaG+ȸ>uǢ|KE୫vww؆@'[3GsZu8+?:Zc2Z.Oe1_ʯ< g_ىÝ$KT49(qʟ~{K82}>GէXXZP2tk'35޹p;o8tso0g`rTFc`aaA _!,8/C\w;ѝ&鴈ɮ]h 0~ӻjP(F]@C'bXDtL7 sP X%ߌ)yI[eO "L?]rpTq6'z4UQ biџ@<cn$֐(G+&2k;"8 71멚7\ۿMww+81=kb00r=\m-aରBNީܚ_"{UFgSMREx 5Ǔ֮͞ J ?|{2@.xhũ{Gl)YLh,CdCEozPITVpƄ/ө_!->*&\Y^eAi1b '5%o'7rL글H{ ⭸wVD(#< (."+ZB٠ c V#' ~[ "F=<:회Û"G[vƗ=E܆ *:sdd$Rl2iT tVfN_Ŷ̙cEӲ@'Uy84$E쇝f['CR8]^c{HFᇬ֎y )Wj!.%' lqC8U +-B<3 9c* sp["\Ӿ4 >XD%6ΠBo/lxlt tD"SӞ#X,]oI?,~KՉcz}Q6i ;$r$Ԥʪ{ H#IC-qoM.mʢI'f\C"%5.SjxghQ] O{CAeX6> S2YV`}''>vPDG%HIWV%•/*>'&}/s_5:n7$`E;Vm,eK@1ҁx3mtrUJb!G\"TU{F%`x, ,ַT{S<9LFH)g1D5~nǰz 8(isK{M`מ΄8=KRYV&'R&FUQ~frs[X*`B)GůOJNfQ>k"~Vqtdfp ˊsY& I($'Ԝuݬ3Q[ G&>/629,F7vaXZ "Z`\w نwt膽$ӟ*%]txYc3-tXhQT FCrj)q6G=EPRxZaĿ2nb╋z\)zs(@oXP7K56޹ % B+SonJ5/yf*[LUaU6FP)i;{^F[YXD8`A5/j]]ɵ ͊?-a,$2%IXs(ި1&oxU4Tj?.|~)P=@OX N66Ps ij JF3dw!qR> +S_t*Fh`uNhf??Ov^@EJBgxpB,Vɟ`t1ûs|RoKȆyQmcqF XZ΍Oh< x`S#]bf HAi [~]!sXpGٔUNĦH2{be1$(mGx*F!S(Z[h0\%>hx}2``>Ee߯V Z;p1ʃD#ՃK`z[IqD@Qx~&/adCRǂ]q:-g+adヺ p0+UsSė?;pk5VoŨoQhN,qn578w]0D)n$}Xm ,gMAH5w?`R19è;4KPEV^S'Pl`F/z)'yoocM7K.ͨg `V>U5#$@퓬`:0<{auԢ[qwQX#,ܽb6 Ʌ"zJOO4 |S>1NR4/M!O?r뚀A|񱥞N:ܮl\1sTQ";KɳSL @PoZXLtE(R XXED-!P T^]d G`A{qr1t>3hdűN6[?"kduy &?E4 ^/{Q1K?NyKf/PU|C`S!uu{1pD ̼t#:2|T`&1Ȼͻl:T".-\v֜&sƃk&|f5j ÔH3iI12 KvM:qc^Bm* yz0R.3dV{ !u3du\/SԪw -^̔0ܣUÜSj'\b|3ȁ/6wEg.({$aUukC:hh:VSh[v$%OAܦX3މdMtȠUEH7|2!uKGz}UMT4/xn]ێ-w#1pguJ~yffI|D /'-`^[Vrϣ6ҏiXRA] "+@}8+/tk7Yb^ ( 푬 <l#@KL f&"s-|n *3v@&TKKzo`O k=rՐiYZl]:wWl)AՋ߃Ρ3@[-[ɻ7fxH8wl̂3GOf`䎈|Kml$~,3CmK>,1htU5^ &c4=Lk"+rAϋ2skcyM" CXyH &D?qG %38F/`!+ WAUGӓ2/_ OX]BW˜6ؖ * w^D.i]k宜YhkF/DpDSlzvvd `AQ ^PaM3Tjrȱ5mlHyxV?5):Yoj W.!K$'8aAdՆ0Lxm%8GNZR6WA=LA͑Ɍ[ʑ֯umI82.obnks\ &f5CKkwb!&D,>ﵪ^(nHoT' LUE#hjUձxn@4qwzKjw`0c; ހ1D*BPNy!LJ9)1>n^ҪSg0le~^oS1AU Aõ&CMmJ:05ȂwCNR HA o7mV fL+Qo {h/Z96>+(ZrDMAv > 9^̇ak+zI\V2b0[OR3,6<b#p "tx4+C͢@-617Z80JGm1M;D 8쩈 TB轂"FyL3|Fn6n􌣁dizOycխS Q"9x8miw#R]q0|ƶmV`†~ p}t1/! × %љ_QX8",_M׊aO -p@&2jpË' $&N E.fA eT`&ӻ\3ѯaDD 0DL|$1@0?W#$`o܎4o[q8MET|~GqZdKS$캫5Ȣ<8 EAH-e{ET$K`Uq,5)< ;)9:%CHߗ=, 2ē6U25pޣCr{zО*֜M7CFiO2!G`66>"ɠ?$/c|0' ۳%\:N୎-Ȗ<ʤ]T?UNnA AWY WqpS+VoUsB3(IV 1.# 96.Qp7@1&O@K1T6f| d Q6C@Oįy[*ow1Y7*^Q `f(L0S!x}ҒNDi){[6 =r5;$آWC{hH{jp!iU'O Ɖg@!ҫI+hA]<4r{ 7> 1mU{@ALx5 pDŜDZi@t.} 6uÑ6JF '!l fsTXZW_i;xJc~㦫 ,=,,z-܀γUzQWuB問nsPA<eM':os 5RuD zC -nq˜93%Y'7Hغ;'o)\3{qO;nIފ4ɌN $e>*P, tRv8Q<% BapbAOGfJJòC(>#$9Ip tL .n4M򗥘4C :k(Zl[ZK ʼ]}_'Z}{4BʿWgM& ~*j1٫!cw22g\=OPshc3" _R"+vÚ94Phu ժE )Sj8z9oVv$5%DAXbd2#լYS>dg؇& ΰ2_SIm' J?hVsw/<   Go:J⃨~ [F*i)b@m[hq8̡5Ҿ^zhB<wY)7^+ ~_:K =˪sAObs1{XV^39m{e89], [m|UgÚUB*c| TmFUm6_匬"ө̼- 8*r< c窪x"FOq&I [slf}dFIbH[;uf<8AI}&B~1`tx}IrB.]g{pNLсν"@΂5h ӜDȲi$*µ^+򱪷$Yv^S1'3!YayOCL>1&hDII\So\$uΣ3Ŷզfwkge'[+w9$ ӀM-?1㼠l-fgKqw$ p~9\|f}:Y7LllYp:z'a! Nb{҉"/P}3E֞4UZ KSH? ._bGN <5Ajnj91ef*E(6dH۱:yJD_PޢsJjzM>Yrn)I.b#lQʖՁ <| xs~bB![-X,i^#<ӱ*EK z Ͼw>=aa%,!+8WТA@~'ry Qk9{'95Ʌ4c ]SZ ~'|3V~/),:S=\42#N۽g`ʂ^cn8X8wG:ՊTQב<]MעHB, xUsS?ߡ\@ʂs`~q36'4=vnF|sz|ɛkt@w![z/7zqٴ+A%Yc=RŸ=6 ]`6&qRTrX"S,jrOɩoVT*j!ȏFf;Ċ?⦈VG~(A7@ޯV?rk5e).7AFQL$;$:9@`qq(v<*Az , q[̱XLЍ61\0$_Jkԑ^Bm&VD^$iQ^#Ay AUc&w4 rtF<5&6 8 %UBK fV9]ruQ "ueE8ࣆ&4Ѥ]g 7JȬ"ufABv*c/NY5{]1]X =ҰUI>Qh>K?/{E2s|&hF9b   kΓ]j Ԍbˮt~dI(). FLCF;o>)Pz#^xZ.p0Ah;;q^Y!nz3c,`[|&a 4. 1u"u^X P~4X(ŀ3 HX/+o mUaQȤAv W6Tc.URZ #"4 TDLc&a~[~J;dR :70)'oR#xK-KU:{~>ܶ@_h إ$D9W&qk$#KêgDcuAiaw= /ʏZBKk+BbT B~BϠ=.fls{n$T9j 2WK.B#a拃حT!y'`;D CCc̘b\ib]@ w+ Lyܟd #Uwlb &៌vY Q0 i$@!UaQƃ'V]8YaOJ`A lu򞫶k⯴mHc|L;CX"@MEgJP@#/#.0 .QYxuX$^wF |@ʁjWP~6bbh|I8 ;A ^e"S6`1ƢUP>4T[B9?y (Sʨ Z50Rϼ۪r p7W,CPq-X`$Tf-)n\j'iFX_# T<_ {virzvf1'itPmA.0ڨbքHR~4kC 7e |p6}9Le oUl1@2#N%R^"keV7(EB xQW;Ai~:Q3 ]`,0[A Uc_/z:jmU3 u{pO- bhfԫiƟ=hPKBw~Y S#g ߽ZH@E3ЫCBQ!y. B5[4MXfL`J@!߈05t}Th1 p< _ T!Gҁ \{I"J5¹3un޸|2 ˖Mmjm@d-ةOY6KF3]kNsa̹=2tMMuډ?w1 B}[2￰8B1bzEЖ RA҈zHw< |F]QɥA`=A)l|'Pc§d,P>ąmύ(q_[b1>j; `"_#i&㿭=cL{ZhF`_5@}F?Sex6Cmlʑ_#""c؞Gdv7RPN {잽'?sM( +\n_c#Q`EIȆPA/:bE҈ɓ*@/>oy l-BnRVqQ>V  W:um%>gpgC$*"JnHM ^<%g~*Ӥi~e6n-a|ے%O (.xpF-諉nC;ɭN4x϶#GG_I WK|b+giN;i0o^a ܅ׇ}rEГe|枓;ۿ9Hk3ТjOD5ٗ;,z@SXH7>I2W4sƫ.5)9OVWz4TOf9@l{B qx9.jygڋ[zX13 wh/4C.QGM8k5֨(NPt* X-xhW1P<1 ]}s3t h9¦Fm05wMU`z)C"ΛW!O'Z ds3#Q,XWgaٜ0RA( "jU, 0PB ߝ&!3E[Jm( \jp_8(_lE]F"B`~c$Sը6JF(:e;B;i݌Z]OIb/PPd"RYK4U,@c5 "9|f\=G qJl#/ jO+3~1(wY!D1ec-MbA 5:[=xH}mf޵$Op5e{E,\Q݄y]RBa0A@ZJ_lMm9S @FY6\ T50'l{=-j@"NpSiKˍ?]*aQ6hG`Ch D@r V,ri2[De3O> srOE @$gg L9v{hj 108l1oEmOv+@+.mWa Dd@,pr, ep "l~F[E8e"l0lI'gA7DA ExZb>ULSDNϨ3ٰ@u³HFo kͰ.C`0yl% =N}וv.^ۢN3Ъ[dUU;hm9^piC6GZ XqџΕ++|$؀2h|_ fv*@0~R̊& X_@^^P+m nř cÇ~R_Zk@I~t"VLzj 5E֏`w.3s, p[ !`fjlqh;NAZ=H)HGDF Dw`xbV4lZ@ 1 |n?6>s YXjzҖVA輅h5&QkM~>9v`M:ރ[/Hp|XMn#ܡ)CNe2Ⱥ/顲$;@צVTVO藄ݚ /7 A ׀ETx >ԴEҺHoyX!\!`cN 25-I1pa9@x@ 0)o 857l摁o@/fz#0 E T1O-\oPe3O->Mx/d65+aN0djs;æ|(u* 7@8zEi|8H%VV>Z!+0!xFbrZ!N|VaKh%tO9(j=E.  ! G`4XBS8 ꡝ8A %(q^R"$ÏB}ƣ~LvP5eI &.TJ#0CPH)A9L5h Q,iJ*. @ v̩gy7){C_?&7&<\nsYGq);@Vx|%z+wvKIn(``ڻ=RSC(}UIDWw3?Zco0T4 4@-$xO8=U@o9Rmၕp h8sS=77ЧJG*1-iAN^=2Lʀ@1AMgA" q l-1UWR\*-qܔ7LsADRBtdz&1 2tc 3h,ڢcɏYXV 2ԅ$ۼ.Ԗ[j$ޮXXmHi7.TrOB1z 0h)iʘO ' ’QZ/zOP@]x0WSlc )k8lKÞ`K|>~e *9bϞ7މ0mqxZ`a֛o||#Jة 8:IgtAz ̆K]i] RG)Oa&(w#;CMLxfw&o` \тPP< ZTcU $xzm+Py2. o"( #֟hr9͗@Iww>(h'/ Kώ jYoL姇cW[Kh{_5\;hmle<7c/6t~2' gب IEcLP:影09!@@s:vnd` Ts_F ~B  R~tFC*`>l)y@P&}TdFdT!ݑA ĴGlao}|պgjQ[%slл'}+Dp./p_֧f+ A8n:Y +6Í vbV8)1B5 [H"cZ: S X8p#0oRGt7ћcAY,!. SCu>A4M?ݘQ/  M5p|B ;FJpyR2#w.dqOQvir<:_ΓEm?i:Ox gx>d]q<]#9_%G΄0^P}`A;$s3Q<4DW6-#,}}ѕ.2Њ߭J ^~n^TԨ0=L~@`^C"<_. eǾpfي(LF Zάq,\,F8a8%BXo 1! k'|e8A^11 l'`Y~ e-6ͤ>D1kUX+`࠵T.Irułv.INvޒT!JP3쀰|Kp4Yi>zO3IBLJ ֆ'AR֏L *WO[ly8_VTXmRd\nU ]9 Kش2  cÕS 6f4U"Qrqzax-&}mi6]tp?AkȂWޒWzɩspsH)A: bQ۲wpJ]ꞖzA^AUP?X@j1Rz^|:^TDN/^+ڡBШo*$ݵ* v[ 0y0ďve.1)]GSzKΛIߓ۷=Oeˤ\ daQBw݋ᡉ-]\%?ϯ RyOT;!40g48CHRY"m ż"&M)ϕۦ@K">|"8#tӪz~;`WL4JN3 e1pS:ͤ!D|\dX*Ƌ auK0 <6Wmd̗lUl;(|ےM3ˋMuGnJYWF imHʕhyڐyǼrX\CtKrX 4qHI3XY21dΟj+WN4ZkbAm#Ŕa\iX(:jV@ foA$Ufzq !&0ԍs(oFĴ<Lu0\}"/g%Asl(le, W%HuPW6 өeF[*ԕ( -mJ Б RǛ96jҸ sӘJ}\}iYMhws}\Б7U!ن?GsvK*~}R lb> *Ժwֻ7< l]2֊Uua$ mj3z)! ]2{ ܁#,̒~2F3Mę!nvhxȎ,i%^J~y_ReDDwRp(Rt&H;At l2\ȎfsH*xȓ 9^m @<0w6i\4|532eAZm!ə7##Qdc ʔn H}ۓ '<}RMMT=pOjYTtJ~WZ&e Ք+ܾ!; x>R6~G[^ц6[sX~+lu9ҏbWrZKb>7Sr(L: qȏ\"̙h3gR9RwUqC)sAfM?Hއ.V}Gw'>L*-g>)iRk琊e'x0kAן&2 Q)4%vVw^hkUw43FD1G.:b@j벯%Jswǻ;;d;6U깊y]Or8CE~5јcձm%wֱkvCmNX8;ndhz,g t9@pS6c5ZP# J;G0h{m~'^g*bXQ(eЁsΑl2s G1#h3 B 9뿋Je,B_./}HӘB zmE(W * AE vg7%lKIvUU‘GhE 6ߪ5idK5ލ=/| wMr7cehj{ߦ#<d6JZ0fB^l}[Nn80C0.C1wyⳎ){[+xA% k w+.fNYDZKs' q[ UXmR5;دu\l`X_ѿ3pwr9ӿq2 -ь\H&x#q؍I(lK(;5+-:#rQ㰂Zi18<ޓ ^an yH0V0d]3zK%9A8>U6} zʟߓ(|ņ0$߿Q%Ukȫ*tq$f>S4իXE|݂t +\ko9ORl lKDLPMlS)جdz c|+rb'?Mo_S2nU1"u^<}J_Mn,mSk;GPIg}{v.^ߚB l8/蝫b:^H:_cRx<%E*TE Qg<?lZe]>tM !AeqCfCXQpxt|l-]QR! vdy+BSثd݀뀸mFhsz LQ.rdxĚx,DՑm7H?r=JASR|FSM=K LGHhUx MupAu%)GSQqݭ &A<^>kt'l%}^d;&n:~E vq/LmM{h+j(׾fy[AzP1څ?[؜Kˌ}H9d3>F4y6} Y; :Ӻ6S[NYfB{~UEhO)u 8i>h Rz39% YQ`AUɀ^Ez@-eu ;h}N0.M*t4jp`x1 j"G ""ѴRv/\")([^F>kp=-־ۅ3RH5XZG1ܥ\9JӸ2eqoe>fx{벍̂NxW{Sa trl:#-Iv\t#H>\vwU0Ԁ66rM '!%E cV-\[Ih\p#, ]d<.BET[ ?{,t-YF. XafKu 圦 &:"|_^юMg+.d5+E!_㿚U-ل~ SB'K|4Gg_ 2Kcl+UaaATuç [zbw|90IpC]Y:_ai I40s8%3YtQK$4l(^R$Гw,`L,gUR?D RwQfj/CDh4f >ճ߫uӦ_FG'~7Sܠ%9MaaNfQֶCv[򦫤5s<IN3C8P|,ģݝSEd1I!̈ר .DHz湐ˡ4zA\̝&m 3 ק%jwGZj$iyszOxJCvU\#{yF8:7\^3+fkɓ.:wb^?7@^ oto᪔=nl&&ǔYV݃o", hbD21x능6n)`I)ظRS>=7\h K(02 (^W +02o僪xKH{T&i5<6Akb?TЪ]@M?Q'~2f[Et03//؂d~?;.0l<)ǎ˕eavBG3r0YB+ ZoWwZ҃ dd8h$vbR'٦iz Qg;{QUYިVsi*}؈9ahaUы58pȱHi,׎VLRf$* Q]sA4K}vвnc +#O+=ed f^єF) eYzñdb\H;ۇ?wgj=J~JDVS/z)+ʙ..[)W]֏SD׀0_39KPC//_;ifY:&fCڲ*Se~"b\hjI8XhyϕkB"ujex3[V!V6{Ig/Uteܾ>)IjXЍXQ?讣DcɢÎNQA2Q  z}dgf;Ezo0S(>엄t[뢗 rT5SIj7w7H(?[G9r̓Gkl豕[sΦkRkvI󠘈Cipp޴a>⢳m!HcI3X;n׀;|,Ee7ٙ%NNf=A1> 3AP߀xA|A-hMսbٗpkD7q3w1% Aij^ h,l~v{^]^ɇ\T%o\&]xlh_{Ctf;Ag"QgUHϰu [ ]G:}_yNpcxI[,ɳ#/>+c: yRiQq 7r4,;I8bk0_LĽF_Iѣl"(,jE;ԻFxnu<Z2p!mETC+AMTIW#]fŏ3 ʚ1;SP E7"ߍS0x 7ae~ĈۡW"ƎIx;(jU 9c:n| f^B8,lމAR3%0#H*6QѯkΪ)x}+ԜEwy !MY D1bQLY+^>H+\ieqL9,94di # b[N8F0kUhf1/_^f(p}[qCNS7Bn\NTX@mA l^eC7?fm|}2sT u"hc>cVq"RR@}2Z{\Z=p"{.܄%pT۔&Zm_/Rivu sj;3?Dt35~Y` yfGp%_S.1e_) {@")# -5i#HvF;ń/>ÜFm*:)- `A?syAK ~xPi*a `?: }nT4M/"`Д܅ y}k 櫷5lDK)X}}L d E~>֮N<εg9QsQ@#ri?7fMKf5:8.D./e77~5&t &O=v]Fo(BhP;"~F:7EcCp@WMK!vۧ{ orKh!Eu07`-tFvg=?*`#v GMzȨY@OG񥗥zN  ]~v?k=9ɷYڃ'Hj֑ |f\.SϬwRNASd>M f˨B`Ο{VϹ?GiɅkCXEq`lA}%RC@"`EA>; Al2M%n`ϊ3|^4 =x^GmAT$IHB_d/M0etHPAM?Z4{xahΒߚV#s%J=3/fZvOT>-HҠp +2$l~?.pt5Ed8*SIg`nrH{bq@-7 #%HC0Co`aOn͠NC$N{>ATֶ)2@HϾAO4D?as) R`e$c+lm(K@&Կ]e"yq,/' Γ+c<2_]X.F{lycn.LmyRh卹zdf O{y@E`0E[ǣ=G3Knǐ  ,*f<}C4B^z\'=.RyKUƔqPwq=7"WLzLt v?9J-hHR"HsRAM<Q l<V~ V,/#YۡL ̂.I]Ng$LET],P̱5Cjl_0R#9bl/~1)VMa.3&ϘQ ?/ؔ l+lTW=l%;aB?>DtXo1drD/*6jp2,>3^(-5?+8=0Xp=K9}LK8'p]9}/ Y kZM0d_bO+PIF 6LawpBt4(N"EUoN؂F$NjFL}AOI>TI?z $<2zqb3d›d`h4 H B7zdDړg⽵@5*xjO}>AYXhl256J@}iP-sۤ.!N[bbhֿ81SwlIa&}5jCm+/CJUj} Y5SDf)3G?+DnZm;]t}>*l^UD*Z\ÐDѸwqfHjoS1bP Hn #^h u") [ B]l$৤CAhbS5aQKt 7 [៳>ō!zi; z$42 YtBrW9} 9Td'Z}Gra*#E Y [PY!Hh0H1TOJ+a!mO ggY3} DMs>iՐ"C|ٜ+&qA]h|籵'lJJVѓY)OC]ݰ߄uZϝUmB\ijUibF[%F/RD%?!}?KWMeŲpeL*b>E\(E[ {^AQG^#03>)wݭWwjVV&$Bt2AiU,L!&ɾe OY+!A="MBY)_J݋OFo Ąe"'d 5_ NB*D_$g{ X iYXe\pT__ * \G*&qEeP 8:D_sХco|V>Ⱥ=vv{Iѹ{k ;U^!`D0}ǘʑ#ѕ`"%aQY84E` } {p ^e|")^WY+ ]Kae"6|>- n1.֕!Io1*Q[AO_Th3b8leȀz[9fWrO:~WTR'gHVdw\6OP7jmq["Msc ##sb[Uo۵a$8}B>%`֑N.ƶr<(srq8HJ_')_Z/ /|X -b zt5jn1FjN(̶۴o&(9%9>H̉+6nN걘B/Ԝ6-+yyQ`ړ~FRZTAc?VBkX1;g,TAQ,8$}?o΀ dmo!OS`v!Wt%qdzy5sg1D3sۆǐS6t Ivd`5Ze3Eg"koOM{Ԭ{>1&YF<U!6pp~]=et!h[@(*Nn|0`NpXh^nԠg(Z;R5ҖII,c{<0Vǻ;0P) Ȋyϫ:D$Z&ۭi# ,)߻qM.SsaefR oIβg|^B<~c+aepJpNMEQ=hF)bm89[2v8ˏ\DIdJ@E^@DyYw1ǼNoͽ+`AY3Jq3l a!?NK`V9 `%[!gqgP7+s; d+*W*4^"\j]YgUw$+b*wܻBNv֥IQvWئԹ`iݼf[]o@[a2c2h34E* 胶]A4v9Tmj+aIg~ܡz q$ G3*(/WX ,a `EgG{_N$; Kc}4FSS0taMU_A$8<#Po)?J[:`^}\:?bW5}1ϹEXF400i[fH~he<&Z~> ’c-D}x?:{|xZézwK u|L%r.;n{>o^NnT*r6>2ʢ<{4Lh_ipW=(+dJGf'v !-A Jz {z֩Н8pʻJfOinVo^h@'ϵ0WwX͑b# 7ͻaԞ|OZ |S'{)~кFFUyŋI?sgȠ jEEV3AQ l0/|n?lgnKY2Q=O9B8*2@2<ĔxHR?u؁&"A5]zglf៟eYy7Ba CɲߜԩcSXֺ` O| 2|!K(x|:/qku -WR~GbHdȉ:ҍR"h^R#'Z՝0j=I1?)>bf RbYfP[{GeviDq xbR| *%}[}B6Lϴca9?yֲR =NQ1R~zE1GM A2ɇ2@MLv<*8x8\‚%_7"DR0㗚ePV^ P7_}auF:Lνނ\-Y%noOZc;mou=ݔ?pYtRvTjOi3&_׵z&; |#-\݂l!{Âb4~)olS۳ao7+.QOLg[ڲЮ!@ ٘Dl ނ|wm*7r}n>WKzM^֕)xfYFb/\)q @ lScQ0 `M }!bmݲ_K=4K1 {[)[:kB$<`Q7@:/[P 67 "n$4 .|gb'K)&Rv`TںDQ췘7}C YЩ2h(Xg25 !±S3uUʽK+FAɀ$KvXw7FLaDZ=""F8 xMy0y5=LLO) vʞ=pdIdx|#dM>o? դYڮM^g {hky3DZqܼ352I R%v]"\%:y fQ{CGr3jh\r.۵֘Wr YkSQ{0nW vSwٻDD,b\n'2#`.<1 w?$)P@J}H|h ⑆cL /4l8{z[dk`أW1Wa:A1 lP J5}=(r"خ JgBlTTn3^$&7+!fko.W; r%B J#֪N'vnGB.i-#Nx3?Ԡ0gx%.0}7ٽg4u4qxw֌ ~ G[ɥ1k3PV|EQf%tR2EKRTAxC\GrEդ_}p7"ϝ]mG(5W[$HN,(l0hTuQ _ )iم#:K"M﬙\l% *l0 y7W&]]v+W;Xm N\B[<: 270 .8?@ϩoϝ>F֯U,{n3>9#'Ob険 W7E1$P.N.Ӗe+0A:<θA\ \6Z2^H7K/cz &#u${l]Ҋ[;eh9^ii%tA$x`g=h/)!HQ( ZC0q*fzQ 'p0e0k8/5˪.3whb0a$E7A]4` x:m+7f;A0TfJ @=#xӡ@àN΋09A&m;TL_JFƢ܎x)Oqe]gezu4W=sB)Tr-ȿ#PZ8qM7ouV?j @s5:T@>L, iIxV료r>Agrߍ浨͝kyusw5 ؘDd즴(E&ӷeW E"u~Pje*)^⭚1 (LBټTFʁG P{D?S]sT#ِ RO.a3\`=ARI  y Kc9$/֟$1=y_T^BuVk={B%E٦5~O}P B`XRB(n Dł.BeR,D*Ƒ?smCSyFuʁXn+y&|=V|0Bt .>ca"cV҄Q#2PDr3NYF:elvIvƨ;M3!;I[Ge# 6i=nIaT"w9N$jfŁAӪ]vlQrJ`F'DDP3Z9 <` ޕ$p f@j_*ӓAQ&oP'(}zyzddNj_+] lϻIwy7huEƘ <8qZg r+1N`e.̂4|E3cqP+2#q՟.Pҭ;NLl> afwulRhȖ_19%ƹ 4.TrO}h@ :PRJ)c|c1ids09]+Tx.A 鱙tKHO|,dY7c8:TE6K6Wq] ȵNtМ^OVA7fzM@&пz[\Ѫ< xs{~ΚIܠyß^Pv,dBQTO=C7Yi49^)5#řzx.[}m=HTd>LUrT|GZѭ_B䄋iu϶ceKh0PφKzeJΈP2vC[ vƛNA,=GU p{>Alc.0WJ 4ՎH$V"3^U15sRţdOڝNl-NPA&QM)ԛ,s#.عX.H \n:I"y౶\:o9T}Z mh78Yun=0N2jf.mǵvW[yO1iM<>TMn?(YB|,u:Fr@]Wh䍒E@=.ës _=v22R2i""fbq߄G:Q6bbE ֭bKQ q*lxlgϷ S+9LF$I/+N<fdI1BsequGC $o1Qi422 {qӷ#z^SAzq_{p߻ ]l..՚=RUκR[cRuJbsz.FzW#76M J61p1A,ǖrZyA{aOޘ:YZpIt*DhкrfJs^j=B8,ii'vaNQATI~@L/eGB_!(x8ץA "TH6.KzӔk)TԆl7%lF5ћW@MW׫T֑K*"E-c!Ӳ&!M8-.zPfu;0{}vVݺ8\MKj?y7$FR\K l(yC檃 z ):J~(#ݾWHZĝV4.1>Z4P)B2?2ƍSKcd m;MڡTd0o5|E? [MANp<]A. A`_Nn֧eꫫMCYO;(kE!OR5%>s؅L6E;R5gAl &$]8\Y {%+{({9S/',Nsr"3t'5iP$!`圂Yaf Bj:^ S<29UaҼb=Q1~wជ@()doh;F&-ũ耸c4l*I#ɑ7֒p,+l-:BiZ HS_ @m%W3Ŧl WU ibƨ{@@c>hA[t/cK*6}b Btdxz(;ݽ\tB ]M@ !c,5-PၔQRiIivֈb r?;rAlap/O8(Ķ$jpUXv u")N$솓ަp$( LXJ802*~FRZO"HDa~E *cW;|u ])TC옐{]& -{S腲A1Gk[z "K*GEԢeHW>@|_ 4D^oy9Y&^em%Njf2k ״sow)tcn[9,nΏI2ӄžlX&&ʊgGj8\JɕJ- Nl(7|B+luQv.Ƞϳ A ک\ b!,I-iA^΋xsE$MQ1ʋ-ШI5]f$y(x (b-*`\5:g8``ɿF},?&=I݄@bƜ) +O3R>`Ly59+MR+ nQ?Y8.*ծە\<M׉\p# wy&H{Lg";N<7͛5= Nc@93Wkh(%;*{SB,=TtMnۏ 󄧡r0|ȧS)l^IH45q{D7#} xAV>&j?Muqc7 W̵V VQq٦RRUlWe#Sދg6-ъ#yj*yoV~->Dg}wj~.pT"Vu-aZ|v^wtJ5fDr=Ҥ7gK.s`A*ZcdކF\"95 Zp9&I1uXBPl6ux7Wl'KsXZ3Lv-vmaGc( 7]"Ϙ{] NL~vȷW%6嗹1TO (Ay :;u@܁Q l/Xq8lP!;HURgBVb]ڋVYd-AN@ kPU1Rd@YsZwe,Vv8feހyB"ss& )0 1R`E״   -uWzCZrTDB~Džuuxw.r* "2fCMC XX 82AΧ_35#Eͧgy8 54{^[y]JAvO̍]/ g7~M[#ujכiySS[feX ov.?h2^YvVcp AYɀ ZSš3lf97Nh_ni> Axj o`Q<-ڎF_DQ")HJ3e"H|%Ή^*BnCuW,$DQ FWˡȩkYNoGlR7z:J6<2)fЂgw놶ZŃ;Ys( Ki@5vcʆXK^n첊~6bg5Ή,w:1I*n32Ԛtil>20^%9`6e#Q;=>ޙ&K]囡nU><[L]hkؔ}StS57\^k؍s3ET?n\\"B6o0%<W*NXMUK=lЭJ:ƅ(:D ƛV4f)*iZ__~X.ul6ǂ4V`H;B9_UdMC1a NON.2i^Iˌ+7C9usg{MdߓʓXe;]nQ8̰_HXR3м}WI'|a=}X\Qe*G1Cu^v5L!qf14HFCPyV͘Y w5 e ?f<͍!U˟.gvHU_4h(h<@qzL!鮱df)n.[݄:UѢ"YPdL/ ;OΫQ]>BۂiSj65ņL1mGŝ67׆ҋu}=wn8Q"H:ToA#5‰4Q /*2xN.8D23Y0 ID> (RnK-6~hu U]b+oV*eJ[V/nߠ¹. eITYI {L$~`j\u๩s%0A tl| 3\whEH6/=] ݏKxRiWTFǦTu{`n; 0aB(nrVbĪXz[eiqOHGfÃ+pq&1ogI_lere*!cˎclr,nd9SvRZ;4FAGzcD'=YX9C:wnzz h'v(;1gwGocvyo76ʽCQ3`xbҧ|J6l'`uktK D+^5"'KzARH as!a{ҩICŁq lDjM{o`eYvHI3yM4d+!̉BLk^ ɧҺ!mAsj$-DӜ*\$mhw1!õO A>y,#đ`IK_~2yP Ļ:@$DD 9*ek1-^'AE(֋*Q":Sh@1 Ԯ_eǾl5қV؟f&`i=|LݭQ%bűW*pJ;;Axh`W9ҥ:|g)| 8yJZ+nj/ *ΦO˶y>t D>'V@R_`,-'ms5ds6+d#"Sqz%>MGw o&~bm{ECb43wm̓lJП"ԯ>7p|*^J-@#M?u 6VXGpRPB21@-TI"5t,4[hY %IgI0~Ľ%hZjn/&[S4c0Fkw:Իߧ*ԓ฿mm; *LRɭ ڤrM~_z& )a[SB Ϥ+-,B{m޾X~WN=9!˝ Ŕ}l"nZwR=x)^>7 /oZ ]̢Dn0Tx rygfY$M̭L-M/6cm-a؋"+]/ݮrm颸$&rC]s׸ $) U WO/免]/hB >@\7a]3a[yAj~!Qgt#=RxIS"RqC+3vZ<ΊN`6X-dfR3}cmJl?pI}85|1P\d6wMpTx3z#mM\}ۼ8dFJpbck>k{km 4vMY$_@wi AAgo*R>9Q:;`kqsN TUV6 fIy|cW/` eXeׯy~3n,w7rG-6;*N:gJD^uos\2~orIbP ~JO5S.w~_C0!lfe90YC-!VIq- y8xQR0΁c;;QT ߪ3G+B("} MK<<˺eP}7S2|9ѣZ/eTWxH¢`/nAf*U_L <,kTѻnpVWlS0d>So[)e]*d0hdb]=p?(WuHEJ8RpWf\"ӶP ^iK 3gp픐+Sntk(A'&Omoɬv9DzĸYVbFWlsV^9 pȠ1:3AeIDуxΤD7K#V61ބ?7W,pͺHRi L?:B5C@G#mgE6!X` (. 0OJ͵{`ׅy%O~V&0I'pCq[2YBx'pܦol)o!DŽ➥Br=~dWx m~VZSK_j&bBt! OM~  Q6i#Aia7W q,8 DpNAOs1 l Uz4A*b9KOBb:PtWl$3k?TŎ Vf\x͕1;#frI$)akp'MԂ PPw8˱",=mSx%RA~A[6Ϳgs [i}{/3oq]a,:v4{nS:T$gjqUhc 㓱 Qɻ)$pUoЇ'i1])QPEd! 1 /hNNUFL{MR -dwv] w[:B2p0rΒcy_j@rc.Ճa3P|TG>Ov !@໧&x|IyIC*Ņc%tvVEۅy~wݺPo"8f%Φ{2$;Tr=}et/j ?yn+$Zi 9p1?HCV~& EJoxZ458#2oW(95gp/fH,.l` ?yq_ےSjaF$jS`)T9%.yQѰÿ q(5|i,ryBv+[M Qo'hQC9FlK.D r_WI;(f[6TQO!} pTKYBT0~h@09U-__DUof' (l-w{\"mw 7wAIubW~}@fWu2Wa^$t}xD.7(4܍WH9J;PŒuLx9֜M2GK8yէSNwNƢ#lpmo3QSt͉]̔D~,I:qWG SJt{Մ< :B8ɮYL[~]S2`_FW7+qآE跦#xm4~8PCƃU/C为#"Ox)YgDqֻ:k`=sʰ*?AzF J"eoHiyn+:0R jŪPzNhPXN4J,CW(Jo(w@  x:`~:U~< XIYXҮ#辥a|nI !%XAK}qzL'y2w}_1#itl% jS,Ǣel[}:'sM FF29CVYn1!ٿ3acC>gNH<dje+_2:` p*fZr)Q@42uMEVH#82/]* $㈰q=w͇*D"Q$ l5bΥܪ*OXR$"X~[TOS`.x܃ʕ$aI<a*fN-oalA[;ѮUu\Bxx6%\Pb.Oo -@ ۯlL祑QnPf/%6ٮ¢ə&r0.E0O.+(ټ=VJ1_,_>xAd%_D Ο9/ZCH|]KǾsHtfA~ɀ>EXJOk)ʂ|Oz VOW=HR]e@52^'+X.g!@m6Ey_8JȁpPeZE=[9ܡ~Ax*6!Ms'o#:2=ut`<TDw,KawYô, dD{ԅϪ.uFdK-nۍ:Ok!:i]f-UZE| #F5GItW*'%0/fUEpxToR BE`_qAXf0KU:G1}~`ߐAgU+4F-XJdDYisTKP*᎑f}U""A¼] OՅp Ƨ =@B0ʉ$ysv[jg0Uܧ2@Rq4єfoK4D ݻu+yk`.UNݢa"׶қ`@B35)*=D8J"dN|4Mh5(ު3G_xFU_"$p^%J;*49:yt82;m R`tfr:q׆)&X̎}=k.4k# pf % C? pp/=ع\#{V7rӸ,S|cJWvDhAebUUCWu]Yk毎ZS} m"j'ָցmEh-^gK `妇LA7 ߃ KkQ?9Pm}q7\mޗMYDqR[o-ͳ0/Wwj4ݾ/&M '7G#?˜留ض0qQrp?JϬX9uӅ\ݤjzS1g0çŧm?\W ʁB0Ӓu@GLc0>z(]Kfwњ8ZUj! dWS`QGEf7SOdU2%#hMm#*:{ RR\{n`8Br4 G`'hlO<+L|y3CфBP? ]7픠D[֗Sr;&/}=$\t6مC=[ђkj.M9ƣ6{ YÏcoC^Vzl8Tsڊ۰=F-J]1JsbBsB3S(4W0c{3eZf4ʃD3?QuzTfjtF%6Au UPP?Ϩ(6QZ&*HH=V>Ԭ4PcBQ mR;{;%G郂9iuXw vwk WÎ r 7]!y'c}0J *q$[҄$$SK/$9j^fD8Z-3@`)3_TP$uf>s:L'N!tiAAx5S:I wBco%3N~Cc ע4W(, 鰎D-Ֆe*;G82@>(%/Se=bq]Cā$ x%&(˼lWs$E;ŕ?Lq;V4/E\==1P힖:۶_p]nA\ ^"Vo]/&su $ZIkM 2efh8%86 I"CFeQO1Zjǜn]&?ͫfWq9@T#E8%}EnԵ0 +pf7(dDS\PT-Vg^Օtد*i'RZ3.Ac8d`\lUљf )$&a3v6KT6%OQoJHIS% x:FLU놤PWE2h",6D+mA_;~Iv-ޏn OmW p03y.@u]@k`"g/,D;( l1mmܹu.(݌O(fyEDŽ%Rձ09{&7J{c^b峛aFiuPEAI=M]Ta%`mFcɭw s UENPEU! =K^Wmu4wB㰕I=%+_yZtF:G5:U'VY; \ |aV죕٬i$^`kdkA~xQt7d A/ ]Eq |;\R8Hw2M߷y9roݶ& ߃*m,RQ13}Ny#߁70t]NiBׄkG1#)&nPpTFY\P _<\]0S5u{guP'2t[o5+C))0: {Թ+Dy ΧN*qX^Rﰏh]\B"X}67u ;xyΣW;Jr*2 5iәdSa{vБ)Mf/pyٮ?*9uˤ}K=~E01x) $$ (yTNNv~0ſ- `6|vB6/n\0Z_(PJ& k&u[ C(S99؉9I+/=hsaSCO$=mmUA]I 8.I*"f*dFO6q&=/D . o!ƛun}BMwF"N>.+kI=V͛ ܢkg1poHڱ`IZS^vb1K1UaFo'|@'΢"Bez9l_DBz_tӲDsjnX[8{]6~"(ӃЈW'RaYvs ''G<ڳNqZт߹dɎ,Fr&H.;ہXj%itIs}i>աE- 51t8b ψ )A {[sN~# A@Ah_~ =O+lƥG+C5bHs/`aRup7Ox,*ҋNGƿyR\}yMBE}% ,x1I! s%XAqfȜVI=T.o47{čGXl)Ith*_OX_b6]Yqޜ3&Zz_zd}^q=km^tB"ia̯8j2,{1ѷAq{>Ol!bl} wCo'Yd%#?z(v)zAv$Ĉ;r;dlsAʡoW^ĺKȋXqkUD.]QfW{|ŖhtH nK9i_𧻽D$$B I 7o|_Rl̇ v[JS}Rq~D(X0} ]ӽSAymWKm[|{㉁M1//B\5H&_/9o9Γp(bLS9}d`J8p` ޻e6A1'#52dnzzCb)M8CkI1"@sC p VsZBq^Tf ׃ 8Oc˰QU]E FHa,R7I LAj~% 2."s\〭\0T_g$_#(Y45d#4zofuv)A^m@*}(6ft#`*`}i}uފ4jhW%BraH%Apan-\Nɗl-uc?+\-rؖP 2o0W;d}v[|L뭂l0G|"L\L!VLLl3]QؗHQ;C#9$-!8BgƝsF5Ò (5_W\a|ΰJ3Lj hH;"QחL\^Bh؆q#B% oѝGI}DV3&q7rb;8M4?2kt2MLtFXR7#q͔#b{-?#DgNތ6ˌi_{QkuC@O`Uy*ֱ lZÔܵbgTIRsZΛ{@'-uD +o4#2'3I4dWv _sVZ,$`wjkcʎ#Ƿrޠ@nlR7Y0w+pf=X&'OJ (K>C^FQJ衩_C% ls#4ø27Į{U; gt.72W<:ccҹ\ჴ[GE+U~r'-lLI!yTU$o-"qfsv}qKw pH4%Ec/8CD@ON|-ytZ"ũոq5D/TV=p9׿,--}Μ|@oaPJ@r ^q?3aʓf)L>M#TO=cJ'tsRb/PKskʩ?oh ˬAF%Jk ١8]uE; })Ab^F0Hb[pCW=:?/AIR[;>dB=¼ Ze /oφƁz2+1EQL90 t/hs=_ *҇'f'E)8?[JڶՒ~ 'r11z@7`}z7'!ٴ.'\Y6n V(,(xh,؉@-AiA) +(T kb|#1ZSbNSqb&N:BCsdƧ1$oDo+# *F=0 >ސɳP8^@/Ag>&@ZR@-{g9a=n be Fjq u4hƳ{U 2 5A !$Q!R"j'a'vH0%ǘ2tOૡEޭ6';cy-by6V _4 KGZLÈUI b2| iJb.jTڟ6] ̺Xrsi51wY"n@@0$p73B?GL׀DPcH0kx. m^n-1<>[lzom Hq) Tpb'֗]( \6>=F}pe삩զZiQ>\xfsӽyo^Q+;\&ͯw L3w )a┴&Hd.588c ICd# 8K9ٺj j2p(`v|?[ŵ8,_B7+L2,\ZpH_e+>9mۇwTo] _G7UY{2RqQd76QX-5|2.4w%R08ѝC{paeuJ-RmEJWw'#~0KRB,smo`!u#P#radH^7q˘i[zd;Or5%jp͍Tq[:1n $9 TRJKO^Vuflvșګ{6D_'`mMbBKG,B͜&wJBqsyHB!e J۳z~ioރz-sԁư$n&E@Kqzv|"hI\>ӗ\RZv=$_HnPZCO3i^1a ;pF1(֠ng9Ŵ X?``_=,p<l ⑘5vJX~8=LK0=_-l~(#'ϣW%T G-U2򽅾 p09],1=flHn I+uW8IС[Ɨ{8gb+IH>2G$l}#l;yP_\Z#i$3H'= R v{F){^0=/eMrš,kヒ"?rn#;8= o)Z\.s5oRU'HihRB۵',lgXcASugUʚQDMQG+6ph{#{fioR2TyZ8G] :r°eqc$WeSx U%2%SՋλn] EwˢYjte (q["u~?@ڗnP5)RO&y Ĥ- OVv_w.`lej/gU?UkJS-bXK4gԘm0NiJBǗU_v*d}Q]NЈҍ*&$oڦg 5ѕ?l6jֺ2N%%, d}9mWMfUdP<]&!3ЇyH2Q뚺FWD|\}?o::zp<3]X]bBF2jSGT?ϓ՝c sڤ|u辨Ԟ.kXp^Om%>Emu//#Zcno9vU KajC yuk~'2OuIe7_yv Ѓ Zʁ'Wd$G=wHNRsVjÁ9]|D|+^oj;WG-|DS\̕&ZZ4vGFdYSr.mgvɜscEG%6@( jV0Xg/%DSŚY6i XPOE]![ϮTA\ɀ jK}Oz6nIXJBrUhĎӱ']:tE&nj$=8})k+#j"-kÂ2/Qp*kxa+tU1=k_>΍x'ʘg!9_"~A>9+m\A.qyO#l~7ߔjܚt#YlJ Ri)7GӋTQ]Ruj8u-R3A_߀I'eũp:> bɵ I٬)[bTG+6 \Ag̘/  6XGjZ],ޗ'uj<" x MƄ.{nk- |54FtWdUXp0IXlONUΦ *!aZ]+ѽؚ@ՅJCnNQpe*i:>ٛ?mFA߯O D G_b܌o0=1>|,/ADoQKGʢœO/vZÚ@5K\hEB% .m.+elΙtZ~aL{u =NaYb94.:0SD/}P.<] фJ\;ϣX'P(U;\3Om4~xލ\aѵwxK_ ]x[s}R2m$4v} nZ}pΒgFFt ?x+ q3X&dXdqD'0*L›FQ)Pybķ*BT_EY"4(I&C0Z~StV78}=!LFȒKx9i62Zl^XQqu#\hEoJÝ;SFa b?ܳR!}U<%+,uB)`TA zmIVm 0s+/=KPS(V*Asd3TbqUp*/>7+CX&[3+5c{<`A o.DZ cl;ĺoj–Kv$i7y5{)t|GzoRKl(G,*j(%h6g5C(I'X1]H2A 1y \#˯mG+9;`$jB-W( I.*?u/q)6fMpV7=qV\ vJK~qQAI=Ɲ= 퇠ZmL`Lɪw5\ 9ۆcm?Qɡ\v䦦i@a}/;->kƫ0f 1C+pZY4^C;8{.|U7 *W+,",_MdR8RTy 9aFMr+\'ÔKew'E9i9E 4rfwzJ#-jkZ)jݡ زEO%M`CVs3bDǕusBո< 0 BL95nsu0'Y|y(@Pzbh6A!7s*T'GǵJ/boՔ Ad > "$H6ȈQm 7aP6oe 9DpS!bM* @/W0a@@~,Mn#j$U .9f*Kk[岄e%@vbq̕Մ\zD&߮]e}ӊ\I@RVp_]&4AdkF)r AˁE.3=)&c aų SWXOARo aq$icK0ShU&@3(;avZŸ0sPLݍyKXs{3p6g\+*i'%!7[2z?x{-)i^:jpLNڙ?c͈apNjM$׌ 070:H)/ O_[m)!8rBu|:]oiFgG{4jމ#ۈ52-@'2ɠ8RLUVMf,Cԉifd9zFq%:m ?Y1^D75UEQZ`Ŵ"__mrYɮ|B㪹L.[{\J#PPOwt>ev4*~C6m=% <-8 *{oxAeI x;ܝ!Aƣ9~~T^MHEķ!!Pes|/c&Ak^ C8p/wgͪJjx>ܺS79;~q1֢ɨ" ~h,o#(t|DTH?觤7^lԢ?/d:KqK +.~>d-<$(heT`0"`?æX]vjIA% #,8b*/3 G![r5ʅC6=6ZpwF*]0Uak{#`)A`_>lRcBxWmUzC2;MuJ {v`.@"x/XtBY5= 0չ ?U߬> S1"<|WmIc{@uGou_]kYݦJ4pjb.]n%(TJ+aHɖFag) lP ]W9Z{ 3{qՆP5Sݏ5[ 6ʙZ{p@'=ߚ!U9Q$8rUJYzax@(co qmCՈe>iO^娄f,!(~!agݦlEZ`&AkV{ms~ vuK *mp Ykcˍ'8J&P'DPYzab?Xk~uޠ/>& j̓*Ka K4塚ɒ)Ke)ӻ "VC0D+: rNe1t2-|69a2 8 eN1'T0)`Rܲ5p8mfބT^xW 0q4j\f.c1l!ݹk}n@ e_l[dϱ!.}JPigc+w=Zk 6YhڿN!`u#2xly~ f;݊XxP"V#Ff $v5/̂Θ#^ʆQck o#rGfW4mDK鏉9.U+Fp!F!S*Tc1$7gyUITRAUΛHj]ШKjs8%3Zl $MˆKV{,x~:8"qŷA#u@Ћ TDF|bbz;}MnZdi-ޑ] G26Ssc#+}PfKc\^+Ȯ[dk0_zjFG`<).b<нAlH7ȇzIN yINq.O.GkԫuZ(&R]j\+L pvq[J ߀sfnz~" RKN<:lEA e% YS@8ZÂ̶WPm`9 r:(:[@  rCKgVh{/B沘’$ZTYއIҳ ginGrB,L,<.Z]9Dx 5H GfEyORy>CKj`#^ R|͊I# GMqAd~ H 3Pϙlқ]К* 5ќ)6ɂbUm񙦠G7- mTyuR`^*`J&6/owH|̭`ztTn9cN tQ%xh͂=!b78#'M @). G4g\xx9 vE>.5o#OB ҉OYrĦ krW螏*ty<THHk=e~֚䅄}띠6,ZTƾ%_aK ,$ƯZ:).\F̶D~,0Ag+̨0o`LZd/K^iT|]}=ZsޡI d 4PPۦc.JKG _b '_@XgĻWcګ%b}NS#xwq^&p-q'PxA[116⠟$DPfAf@T F5$5 ̓UV+y3ƒ^ S7qV?<ta×+l\@c'pH`laR6C{5tيh0;Ѩ";-ɼGC{sNnnj(qFI˶ۤ9f :"(-wbfJ=XF p- wWw%U٭6wN1JO̧ߺxҫw$S0$οK"T(LƟ&<)Vp{}m'ߠT.I*فKCRK7{AҁB yIІQ3c϶+Y_7~仈A9a䑥^Ȭ)Cr\ᐁm;`El&S #"qoڛ6vl9LP XlEDlյU)f.7{c ?a͢JN%o϶(r/ +dߎ y*3QA>=8 ,#i.XG09p$A\ԡj\[nx-IS?Mf[;1SR:{ҹ:=͞k6_dοLG!DPгVo5ƴܕiZpuujm s[W#hOϋ-re,| "Q届0]9|OJRS-4\x.mxÇ hrɘA[V^ 8-P#1.}˳VyAhN鍗h۔rSj0; }:|??g.. fnkRHjZ.nĜ0](w~/ /GPpH\$b|qp]6>SKwp p *8pZi:)tb{Űt{TJF/zu0@wd`c"Jfo֥'뫲۲lH ;o)#WPI\Ò8q=yڷ73vܾO_M!货}`Gցc% XpdTُrO@ 9 .G pC[&{8fTnb]Q@UgJ^^JiܰA#g/ȃ\L9^FMC>XH=o6Ta,kΠ=eL/i8 WeCY?HvCE;(z˽0ҖҒyYFJŴa /A,<Ypfps3!N檉7(D\s刯11,o6t6 ymsp)fKhrݸ~<Kbl8@Y.iS{.-e|G݈sw4I)<7qMj-([~f-~<#n5me .J5ʏoP\] ( ^?8Y[pm2l\!v4UX%\p6a/#%B*0QB}>I9|Qa= N*?C"CD_/ ێ@r]osދ=`[jh([жuZ1oJU(Vl2{==k8Zǂ+5_^k\|g+|f9 η3 SA*\/Ns8.tD ~`m>n:";ݿ{K]1Y6##L$㍔6WS_jPc FĂ~!MZ{Rs}CiÈ2n,f"X7R8My5#sqia*gS2.Hv/Nlt 4yi_Z"Xb 3V*\iA}J)xn2\i@:ꆺtE 7;TF_3|Nh4gT3A@[{g? r?v94O)GnOsNvHN$\l;ՏO|VrUg}sZuILzcpzϭg&.1: |n1K2ߦ:V%vYewɬw(۞GV8&.Oy\ad$ڪy_^8WRan9&թi!.ߠ Lv"y9*Ѯo<$ B\gnuOp3XѺYn*<;3)WXCK'rZ]fM\ k"g%oM܌ڮ/vХ(h]ǭAٹtC.p6ZX!F+RAfk|ߑW99|TyS^~()s=Qw ,kv 1i'UH/{[亂^0^z$-jo҄ 9 W*~FC_ ]<~ ~k2ٗyk_w<,\֝/<3-hWdrZ.6LQ%&[ m)J1-S9Ywc~Sz:5ֵuϥRCfRueYƱ&,dV4lz^3Aa CwxH)YT!6ArI m5K]dрːYr."5bYԛuy>:X>c_컚'f= vw-Uf"fH5$q0ooch}+4*3',,tdgbϴ$_;yЎ85;g6l(m9!Y煻ɝ&˿FWDn& Fg9L[fg}i0d06#YyyqB.Y/>tihq8YT)0T;Vnu(ês~(23j-!BI¦K|[SColA@Aq  J ѥj+7aI,iłL Ihϲ:MD0QIҖ2i WOJӈUE^&(?QJ=or3aq ZNL%3'$ [CI ipy@:OJXƠ9+CY;jDTaHhO:mr}1-L+q V~B+Bd {vP<}\ZhqP i2[^p]kWߡp /gqٶ@ʷ`bGQm؞k:*>ljrySYM (-:GXD,n2w`i''am& C7, [2^6 5fƭ ʜr{A]JSU*I|chc|62ʶ,fv򠷫7o 4\{Gz}K݋s 3l:EuQX\7JeKME( GRX. xf0Yot O)}{&V(P2`,"V :aaHLWdk;X;Y{E׻js/O9%\1a2M_+:r((Q狘r &xʷ $ě pYa3QyE%~1_εy/K6a,LwEfqk@%& ;mD?qAPQ 60|N##DZR9U!;<*ȕjC )Vu .?W|!0҄[ +S|6e:IhҪk P]}:SNLjTî:3Aҽ7 FŚbIPzxv&M҉wa/.B|epCZô2OGyڎdhxGrqӿ?rJ${qc}E+ ֘6zՠu#1Wz vf'F7 ii Ny+|֧7gTwZ9W$Jk>3bX`7 ~xgAg̑Vjouj| {Ң,}#ȭVgzF iѮfr{Mk)$\`22vl .̮5].(O7 *(^H<^(s2+P@q Dxw*{AJ@]rL9Xl%|nuAĒZAGAn|Xh[ ԩ}n9NNk!F :wHa_)A1Q*Ukg:+CA`cPEï)wXI$a 6vC2~jSc%-N^Cq$ x+Ţی}ݶrjBƻN$ۇNȪ_:C!*2IM-WpA}WqQ;B];VϓEs/(d*!dT!@b7|4EN(XuPw"K:D M;uׅIMtYG}t@i;P l] E,:or 6zwLu.qhRZdDAMsÁ6WG Fq91gJЫ9*y=g.B(.3! {ޟM`=u')Ȳ )Y۞XU Ay B d`B=| [adMAK 5.g;%141aے4xwXlV3][ܩA4#Mʭk[(U3 $&PGJB_$׹rT-E@ܚXW|Lm}͑f˲nd Ylv18T?8_jAE7h_@o FߝƮV+Z/I-8RfZ?D,(G n\K1~%,9ֺsҞ#ٗ]9TP'j:@X")rXoTsX4u#rjUj0SAjր"@`\)h ܻx +D]\$\UAIGG%rdGfF6+xPTUʕym6NᠫEp|*Nn>nߢ8(,lѓehgم440t;!{B6ֺcZU;S 6\ƎkiKo#V؟FPYNI͋! Wlm2IF9\ %djkDp\V%6FW6Th~+ųl_H̒i<-5OI#"Z(`֬w%BՁ#pMA#f m:qz'yu%z浱`\BBQ=m|3J O1-,ԕDy_| Ejno0iB#jgَb}JWaa"%ّsD\9t386pIa0!M퍫&,m|K#$~\$gmꂰwϴ'_uR$sK3dgzF9A+k~}c"2dҕUDOtڟ6Qja*2XUG'+Nb.  t\q剗;;P!V6KO&ɲq,fbO⫎r@ˊ|/J|*s.*bq9Hَx3>+bI(ˠ;tr4O`:"J^""8"4ò'}~SIt؇h:.Od;_z^8?:Bcs |D)0'$j>^AqZ*[t0TUt0H4$L0nIV=WT&3#603+15D+_![-锇ۈ -򾜵`&B JYs”åO&/'XYи[,׺4YŖ%"AYJvwm#14(Ag뀞܀`D*1)TQi,~4=cbYO5$nE#V%C7DEc'Nv;d@{{X+|6oNjvNP,fʰ[}r(er=f$ [gXX4ULG/43t6]W-t  gz8~6B%6N1H+N*]b24v~}-5u:6xrF'LxK-pg0)gd%_ spu ~^R:,೷HuI K/*`xۏ G*@yjAjnߑʢ 5IL'f>P(jyjp'䰤1 ke%RC $[P w7U*H_r2*Dit:4~ѪzU#j׻o2X_^-G}Gd%&j6EhGA\] AI (R4-*F`Qt)V`\ J޸yLO/(_@l@13̎Epدv>λ dԓq0 ӟ #dǁPjEB Q'l!3d 28JK(P^Wq_s}M/~nag&Qw'!8+ژ`?tz*m"ŏ { SKm> bN8;4K|AN06^ZYL"P`st_vB 0v*<(xЪgxſ U1o Zآ%xLv#z 6^O'Xѡvz_3(m F=nw--0Dj%p%aԞ; im@8fI@<ak(es[S\w4(fOʷedGѣ Ԩ.Jl=6o4=s$-$ tFN[(6 h#K2 ͫ}CLZY0Uf:5X@wH oA]3kG$\{W`ZQxXN~Iq-2ǀ'"T^:T:폅( zKH@M0 VS䭣aIHy&'L<+!ר] ݩl3[7=2't zMX8yOx9S8ȿuE2F;8RkԆ":~~ W8[jwڽmHuǐ;~)ه- Da1m>j(.-`=Qm.<\U6JTx_2Vzڨ >^6#D,T5 OqZ0\VO|k{PiK;J:Oť(V'8x<3 EKCjO"2=RTUsrsove  ßhxBn9GLrd{hF}ih2 QPv66 ‹Y\'L,=NQ{ufsgR'Rqm ny]SDQߖv]A5{lupG_ZfhPi~9 A_^乚>y!T#?3;"#1k՗>,2%'# 1ؼ)m]I&TxYLы54VϫaDq J^zh8>97k"δJ{,v܆|7eѻwD1 ߑ!Aw8{H&T1^nC;1Mx9|.?^4h |X̬|o<f9ΏPAXk 8єq59nq]DMpZ@[%,M.UXhgpnR;֐LXA܁+  IPwI)dYZnj7XZ$0afH2HjCӟ#/$djn(ʤcV>+A]#_PI`[xʼn@*贊 Hǽɘl(˛gŝIy~\[o-@B~yVK%X[MK ։A(z0(ݢ0@հW0qq:bdS'@=6U;BrR/S˩ÄuΕ ΄#([Wyi %ni}B@*sJ fR2e&[þK{&0 IT+"_BUie^{I_^h,^Q,JPijaQi@15ppYj(ѵ\S%棽WmݗԂAj+ y~i9/2L"]*U44O0M=zY xT 1 BKC^8&hfs (1%I;VJ姹Gۭ}e9v'_wRg 28nA{BFgKK/q*lf6M"qqwlS%ޮp[06\rj =,=>`we(CN/GL!Fy lr9ͭxlFj;aq8`X2>bA`@e=;$"0# UyHu ylC5'DTVZ-d^K3i`G6*ݖ ;i $l@d#ܝtF\w㎷G>-r!J Vd1yT\veFAA.]'G&':% !29jePi̓-WȒ/vg"jF%HoZmiԼK?/b2^ `@#x,=,! PgR:8f_d?qm"Ɠ/f0B-#L#']gu!Qm> [K\Z_o+bxҧM@L AWSV97\owOWUNώUX2uPZ_ڄ\HP^*IWݩБ'\5#˒{l[m(ڢuY,bSh{6 u2cpdk-o@p6P UOv^n?ӤJ| =\x'^Fcp,XB,ll>r>%͌3mIVA`Vsgퟆ=Wc _s6u(Z4.7hƱ׸" ;v252 o % N"R 2ҖYHml-1\FL:J~縏&.K}Nlh[X!$r3tɉ_jepTW@ tFpUΗkBnxP{(lf^ n-T_Ks0sl23y` /X~Mw@kA`k>QJ$HQ_B}EPDXk3W%xcESe'l\Mkvp3S[kh>+ =xQNOv\kY>z AubֺĐ-gbEeC@Q/Lua0B%.loRsFKU:S_I9yIX@C KDl"XŰC=[CTFD?lV3ZKQ;Cxb_KXDNOX;/=Cā9P0>O% Q H B݁n .cKxje0ㄌF|!R~}b~{ KPpP ʒ3\|.{I!h$m}%~ wS{boʊTNC(t;Fco)r7)HTW9Fq(9>HsΎ2 (fJx lm1K(3 C#us&wkhF^3__FI(`!!/&(;` O܎RChntU[yJLӒR 9":cw2/'NH.\Ԕ:ڽ70Rm=q$N|B'SxHgn. u| LTz'uԌBWRCwg9p57E?CW}EU_,悢+kwO,rZ [tH;\aBO^{vGr@)5GLwpT~<|.wu1:tv>d+a7uk-/J+pg5ܵHV`J}[XFy&Nox׸>Kޛ!Eܭ˰JōnZf2{Cu 7Rp+us.\S-%nx 2(f5z3g؞wx8#ڒ+kN\sS϶£Gwf[z$0V[#ӊޫ#`8~97A>qd n'k| |œq!Iz!pWط QwطR7ݏs)Q`Xp/u7+6yv}8TsI:^+G+|!Z/sR {\|F8Y.Vet^&[>>^nU|E\̚G BZ=CD(qAeO탸HV7@(ѐngXja3C4u*MU+K}nazbe֙(EM?j"ucMp[ MA(R䈌X9ss)_x5r#Ss`y\z^2.V'^v(*vQ1WJ9J:Egg@&i4K<-ccYgSU3R12/Qaqh]u|.8YU_Us;h+AI>a=|auX~nyO~g7RN;1 `WzY?,ODFuTx\=R:cOƤ |Z _"3o)]oI9%0UlH5mn&Fit!&*ݭ&(47Xs:A̕h}:[B #IR2QWboU ZJDq9' *t_r@>Wk7ň(l礂1z԰< X% itcUdCQYS @B Vqeпy($ l @. +t7my}Q5C1[^K_YY%= `} #?Z"Ԝ b5.<77VNXBe.^"3J.^Ko`q/.r;Ьm]DJ!B#Mc%Cb({FJ/a3P,0V,|ҋ 3gP p <LJyD4G<04*]{#>͡ @v&2w? L-*ɲH RL{S<CЯ݈i@Ad|LGs :Ae ctfPde#1z)nbGXA-xJH2{P&Y ZY1_[6߶yB)A<]):|v%|;?.ٸCNܭakytJ}5NdQٱ8TfI,DRh2T` }ɲ{v{=jeclqrPS_\!H9&pBP3VwM!b=3@;CވO;]aBB1GC=G [Sp'FPqp@x _%^B} | y1^i;Z@~M:"YGNw_!ӍI@pGtTTu['Y#iW|r:4oF{|// EG?|.q7wϷVưQ ~*Nɽ#^JZA:c˴m"E{v4M~LOёL! : w) ;4Xa!'$EVzZʋ<}B,3c&vA3ʜYp~]\R6iՓ^$0#/! + E2Qb@AjO~XSk,>g1nߟPB|lJU1G)kLK+y;F+^/2[> .SeM&wp/M V0L= ~|3`?O|$ez)drm Φm;ϟvo9:l@k7&FfH);._sY?lYn sf7;Q\hb7Vx$e0xh%-&t*J0A2޼)ҞoRάDwC sdQ O [ Cp`+a;5ȟ=bތF^0c#8! ߼0M SNk-Bfq' xlְh1ˈLX'qjKfH G_.)#fQ(}^$hFu#Rޱ"EU&C`<뷭vP콳NEt ߀ sP-&tvXٱ,'lPV=LJrA9Ş r0& w9?Ven>쾑:Em4{gwҷsnc_LJɊ‚;,?s3ET60Z4E/78Fm⢓9ƇشZ*"tE LEdz<(17nʈQ:_4vO0@ y#17g؛}'bMaA\~s0㣖jR: 0o$ 2:`Bp@z<;/^.L 2W7j|t&ʡCu26& Ԑo 9k 1g@t"--ơQ|+P&s7}ɒ|(RStD@uLEnpj>w1-cYr@Ɔ&^PcsfbNooykY ($2bL n/ -*؊}׻DTS 7WBz1 t fC]?*SHչ `v0[f < C3='HW?Z9P̛ $*vGz&NCVVbjj_ڝXr:)i:&(KXip) o9771D.z s;<LH.W,wXt,I VmLu6aH6VM7oz?O‚3q@s>1z쓚lB?NÐQ9Xі 95 \# 7PMoNZ6#p_؋E9rXS[y2}\LMi&4=PӃ Aڮ?M ϛgG ޿U1PԃsAbs˃XK%jr MY|{;{kz9¤}I h@!Q- )R96+=%nͱW= z3Uc-7ae0XKyP{MSc5P}Pg{[nL[ԔUK~:oPU|!yG?d`l~ 5!en=sK^~3ЇZ}AY't[6 ӟ*rSrz蒌ܐZ>_j3*wQ~ÈT8)`:UN;3Q(J? ڂ,5Xmн|m֗NN>sf]Φn+^ E\e+?RX Eoۂ/tHy{};~j4*v.ɯy,Cb57:adi>mț}A7'1ď\]~\pX!##[ x8 [R5_iK\98$DȨ`%XҪb"E1OK&9Q n/1vzնm~+/׀OҫM-rnMM.w.#i <~FߔoU>N4uey4r@5; pHYs.#.#x?vtIME MctEXtCommentCreated with GIMPW=IDATX102 s$ R=/'iiiiiiiiiiiiiiiiiiiiiiiiiiii,RubIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749800.0 toot-0.44.1/tests/assets/test2.png0000644000175000017500000000113214465406650017205 0ustar00ihabunekihabunekPNG  IHDR2<W~iCCPICC profile(}=H@_SA;qP,:jP :\!4iHR\ׂUg]\AIEJ_Rhq?{ܽZf(jLvE atS3d2u_<ܟGə 3L7,uMKObEI!>'1ď\]~\pX!##[ x8 [R5_iK\98$DȨ`%XҪb"E1OK&9Q n/1vzնm~+/׀OҫM-rnMM.w.#i <~FߔoU>N4uey4r@5; pHYs.#.#x?vtIME 2 @9tEXtCommentCreated with GIMPWCIDATh zl,UГc, s-IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749800.0 toot-0.44.1/tests/assets/test3.png0000644000175000017500000000114014465406650017205 0ustar00ihabunekihabunekPNG  IHDR2FP1iCCPICC profile(}=H@_SA;qP,:jP :\!4iHR\ׂUg]\AIEJ_Rhq?{ܽZf(jLvE atS3d2u_<ܟGə 3L7,uMKObEI!>'1ď\]~\pX!##[ x8 [R5_iK\98$DȨ`%XҪb"E1OK&9Q n/1vzնm~+/׀OҫM-rnMM.w.#i <~FߔoU>N4uey4r@5; pHYs.#.#x?vtIME :n tEXtCommentCreated with GIMPWIIDATh zl,UГW{9IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749800.0 toot-0.44.1/tests/assets/test4.png0000644000175000017500000000114614465406650017214 0ustar00ihabunekihabunekPNG  IHDR2PaiCCPICC profile(}=H@_SA;qP,:jP :\!4iHR\ׂUg]\AIEJ_Rhq?{ܽZf(jLvE atS3d2u_<ܟGə 3L7,uMKObEI!>'1ď\]~\pX!##[ x8 [R5_iK\98$DȨ`%XҪb"E1OK&9Q n/1vzնm~+/׀OҫM-rnMM.w.#i <~FߔoU>N4uey4r@5; pHYs.#.#x?vtIME WtEXtCommentCreated with GIMPWOIDATh zlUГKAa_IENDB`././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1723457536.8502738 toot-0.44.1/tests/integration/0000755000175000017500000000000014656360001016451 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700295148.0 toot-0.44.1/tests/integration/__init__.py0000644000175000017500000000000014526070754020561 0ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/conftest.py0000644000175000017500000001031014656344035020654 0ustar00ihabunekihabunek""" This module contains integration tests meant to run against a test Mastodon instance. You can set up a test instance locally by following this guide: https://docs.joinmastodon.org/dev/setup/ To enable integration tests, export the following environment variables to match your test server and database: ``` export TOOT_TEST_BASE_URL="localhost:3000" ``` """ import json import os import pytest import re import typing as t import uuid from click.testing import CliRunner, Result from pathlib import Path from toot import api, App, User from toot.cli import Context, TootObj def pytest_configure(config): import toot.settings toot.settings.DISABLE_SETTINGS = True # Type alias for run commands Run = t.Callable[..., Result] # Mastodon database name, used to confirm user registration without having to click the link TOOT_TEST_BASE_URL = os.getenv("TOOT_TEST_BASE_URL") # Toot logo used for testing image upload TRUMPET = str(Path(__file__).parent.parent.parent / "trumpet.png") ASSETS_DIR = str(Path(__file__).parent.parent / "assets") PASSWORD = "83dU29170rjKilKQQwuWhJv3PKnSW59bWx0perjP6i7Nu4rkeh4mRfYuvVLYM3fM" def create_app(base_url): instance = api.get_instance(base_url).json() response = api.create_app(base_url) return App(instance["uri"], base_url, response["client_id"], response["client_secret"]) def register_account(app: App): username = str(uuid.uuid4())[-10:] email = f"{username}@example.com" response = api.register_account(app, username, email, PASSWORD, "en") return User(app.instance, username, response["access_token"]) # ------------------------------------------------------------------------------ # Fixtures # ------------------------------------------------------------------------------ # Host name of a test instance to run integration tests against # DO NOT USE PUBLIC INSTANCES!!! @pytest.fixture(scope="session") def base_url(): if not TOOT_TEST_BASE_URL: pytest.skip("Skipping integration tests, TOOT_TEST_BASE_URL not set") return TOOT_TEST_BASE_URL @pytest.fixture(scope="session") def app(base_url): return create_app(base_url) @pytest.fixture(scope="session") def user(app): return register_account(app) @pytest.fixture(scope="session") def friend(app): return register_account(app) @pytest.fixture(scope="session") def user_id(app, user): return api.find_account(app, user, user.username)["id"] @pytest.fixture(scope="session") def friend_id(app, user, friend): return api.find_account(app, user, friend.username)["id"] @pytest.fixture(scope="session", autouse=True) def testing_env(): os.environ["TOOT_TESTING"] = "true" @pytest.fixture(scope="session") def runner(): return CliRunner(mix_stderr=False) @pytest.fixture def run(app, user, runner): def _run(command, *params, input=None) -> Result: obj = TootObj(test_ctx=Context(app, user)) return runner.invoke(command, params, obj=obj, input=input) return _run @pytest.fixture def run_as(app, runner): def _run_as(user, command, *params, input=None) -> Result: obj = TootObj(test_ctx=Context(app, user)) return runner.invoke(command, params, obj=obj, input=input) return _run_as @pytest.fixture def run_json(app, user, runner): def _run_json(command, *params): obj = TootObj(test_ctx=Context(app, user)) result = runner.invoke(command, params, obj=obj) assert result.exit_code == 0 return json.loads(result.stdout) return _run_json @pytest.fixture def run_anon(runner): def _run(command, *params) -> Result: obj = TootObj(test_ctx=Context(None, None)) return runner.invoke(command, params, obj=obj) return _run # ------------------------------------------------------------------------------ # Utils # ------------------------------------------------------------------------------ def posted_status_id(out): pattern = re.compile(r"Toot posted: http://([^/]+)/([^/]+)/(.+)") match = re.search(pattern, out) assert match _, _, status_id = match.groups() return status_id def assert_ok(result: Result): if result.exit_code != 0: raise AssertionError(f"Command failed with exit code {result.exit_code}\nStderr: {result.stderr}") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_accounts.py0000644000175000017500000002065414656344035021721 0ustar00ihabunekihabunekimport json from tests.integration.conftest import register_account from toot import App, User, api, cli from toot.entities import Account, Relationship, from_dict def test_whoami(user: User, run): result = run(cli.read.whoami) assert result.exit_code == 0 # TODO: test other fields once updating account is supported out = result.stdout.strip() assert f"@{user.username}" in out def test_whoami_json(user: User, run): result = run(cli.read.whoami, "--json") assert result.exit_code == 0 account = from_dict(Account, json.loads(result.stdout)) assert account.username == user.username def test_whois(app: App, friend: User, run): variants = [ friend.username, f"@{friend.username}", f"{friend.username}@{app.instance}", f"@{friend.username}@{app.instance}", ] for username in variants: result = run(cli.read.whois, username) assert result.exit_code == 0 assert f"@{friend.username}" in result.stdout def test_following(app: App, user: User, run): friend = register_account(app) result = run(cli.accounts.following, user.username) assert result.exit_code == 0 assert result.stdout.strip() == "" result = run(cli.accounts.follow, friend.username) assert result.exit_code == 0 assert result.stdout.strip() == f"✓ You are now following {friend.username}" result = run(cli.accounts.following, user.username) assert result.exit_code == 0 assert friend.username in result.stdout.strip() # If no account is given defaults to logged in user result = run(cli.accounts.following) assert result.exit_code == 0 assert friend.username in result.stdout.strip() result = run(cli.accounts.unfollow, friend.username) assert result.exit_code == 0 assert result.stdout.strip() == f"✓ You are no longer following {friend.username}" result = run(cli.accounts.following, user.username) assert result.exit_code == 0 assert result.stdout.strip() == "" def test_following_case_insensitive(user: User, friend: User, run): assert friend.username != friend.username.upper() result = run(cli.accounts.follow, friend.username.upper()) assert result.exit_code == 0 out = result.stdout.strip() assert out == f"✓ You are now following {friend.username.upper()}" def test_following_not_found(run): result = run(cli.accounts.follow, "bananaman") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Account not found" result = run(cli.accounts.unfollow, "bananaman") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Account not found" def test_following_json(app: App, user: User, user_id, run_json): friend = register_account(app) result = run_json(cli.accounts.following, user.username, "--json") assert result == [] result = run_json(cli.accounts.followers, friend.username, "--json") assert result == [] result = run_json(cli.accounts.follow, friend.username, "--json") relationship = from_dict(Relationship, result) assert relationship.following is True [result] = run_json(cli.accounts.following, user.username, "--json") account = from_dict(Account, result) assert account.acct == friend.username # If no account is given defaults to logged in user [result] = run_json(cli.accounts.following, "--json") account = from_dict(Account, result) assert account.acct == friend.username assert relationship.following is True [result] = run_json(cli.accounts.followers, friend.username, "--json") account = from_dict(Account, result) assert account.acct == user.username result = run_json(cli.accounts.unfollow, friend.username, "--json") relationship = from_dict(Relationship, result) assert relationship.following is False result = run_json(cli.accounts.following, user.username, "--json") assert result == [] result = run_json(cli.accounts.followers, friend.username, "--json") assert result == [] def test_mute(app, user, friend, friend_id, run): # Make sure we're not initially muting friend api.unmute(app, user, friend_id) result = run(cli.accounts.muted) assert result.exit_code == 0 out = result.stdout.strip() assert out == "No accounts muted" result = run(cli.accounts.mute, friend.username) assert result.exit_code == 0 out = result.stdout.strip() assert out == f"✓ You have muted {friend.username}" result = run(cli.accounts.muted) assert result.exit_code == 0 out = result.stdout.strip() assert friend.username in out result = run(cli.accounts.unmute, friend.username) assert result.exit_code == 0 out = result.stdout.strip() assert out == f"✓ {friend.username} is no longer muted" result = run(cli.accounts.muted) assert result.exit_code == 0 out = result.stdout.strip() assert out == "No accounts muted" def test_mute_case_insensitive(friend: User, run): result = run(cli.accounts.mute, friend.username.upper()) assert result.exit_code == 0 out = result.stdout.strip() assert out == f"✓ You have muted {friend.username.upper()}" def test_mute_not_found(run): result = run(cli.accounts.mute, "doesnotexistperson") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Account not found" result = run(cli.accounts.unmute, "doesnotexistperson") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Account not found" def test_mute_json(app: App, user: User, friend: User, run_json, friend_id): # Make sure we're not initially muting friend api.unmute(app, user, friend_id) result = run_json(cli.accounts.muted, "--json") assert result == [] result = run_json(cli.accounts.mute, friend.username, "--json") relationship = from_dict(Relationship, result) assert relationship.id == friend_id assert relationship.muting is True [result] = run_json(cli.accounts.muted, "--json") account = from_dict(Account, result) assert account.id == friend_id result = run_json(cli.accounts.unmute, friend.username, "--json") relationship = from_dict(Relationship, result) assert relationship.id == friend_id assert relationship.muting is False result = run_json(cli.accounts.muted, "--json") assert result == [] def test_block(app, user, run): friend = register_account(app) result = run(cli.accounts.blocked) assert result.exit_code == 0 out = result.stdout.strip() assert out == "No accounts blocked" result = run(cli.accounts.block, friend.username) assert result.exit_code == 0 out = result.stdout.strip() assert out == f"✓ You are now blocking {friend.username}" result = run(cli.accounts.blocked) assert result.exit_code == 0 out = result.stdout.strip() assert friend.username in out result = run(cli.accounts.unblock, friend.username) assert result.exit_code == 0 out = result.stdout.strip() assert out == f"✓ {friend.username} is no longer blocked" result = run(cli.accounts.blocked) assert result.exit_code == 0 out = result.stdout.strip() assert out == "No accounts blocked" def test_block_case_insensitive(friend: User, run): result = run(cli.accounts.block, friend.username.upper()) assert result.exit_code == 0 out = result.stdout.strip() assert out == f"✓ You are now blocking {friend.username.upper()}" def test_block_not_found(run): result = run(cli.accounts.block, "doesnotexistperson") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Account not found" def test_block_json(app: App, user: User, friend: User, run_json, friend_id): # Make sure we're not initially blocking friend api.unblock(app, user, friend_id) result = run_json(cli.accounts.blocked, "--json") assert result == [] result = run_json(cli.accounts.block, friend.username, "--json") relationship = from_dict(Relationship, result) assert relationship.id == friend_id assert relationship.blocking is True [result] = run_json(cli.accounts.blocked, "--json") account = from_dict(Account, result) assert account.id == friend_id result = run_json(cli.accounts.unblock, friend.username, "--json") relationship = from_dict(Relationship, result) assert relationship.id == friend_id assert relationship.blocking is False result = run_json(cli.accounts.blocked, "--json") assert result == [] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_auth.py0000644000175000017500000001505714656344035021044 0ustar00ihabunekihabunekfrom typing import Any, Dict from unittest import mock from unittest.mock import MagicMock from toot import User, cli from tests.integration.conftest import PASSWORD, Run # TODO: figure out how to test login EMPTY_CONFIG: Dict[Any, Any] = { "apps": {}, "users": {}, "active_user": None } SAMPLE_CONFIG = { "active_user": "frank@foo.social", "apps": { "foo.social": { "base_url": "http://foo.social", "client_id": "123", "client_secret": "123", "instance": "foo.social" }, "bar.social": { "base_url": "http://bar.social", "client_id": "123", "client_secret": "123", "instance": "bar.social" }, }, "users": { "frank@foo.social": { "access_token": "123", "instance": "foo.social", "username": "frank" }, "frank@bar.social": { "access_token": "123", "instance": "bar.social", "username": "frank" }, } } def test_env(run: Run): result = run(cli.auth.env) assert result.exit_code == 0 assert "toot" in result.stdout assert "Python" in result.stdout @mock.patch("toot.config.load_config") def test_auth_empty(load_config: MagicMock, run: Run): load_config.return_value = EMPTY_CONFIG result = run(cli.auth.auth) assert result.exit_code == 0 assert result.stdout.strip() == "You are not logged in to any accounts" @mock.patch("toot.config.load_config") def test_auth_full(load_config: MagicMock, run: Run): load_config.return_value = SAMPLE_CONFIG result = run(cli.auth.auth) assert result.exit_code == 0 assert result.stdout.strip().startswith("Authenticated accounts:") assert "frank@foo.social" in result.stdout assert "frank@bar.social" in result.stdout # Saving config is mocked so we don't mess up our local config # TODO: could this be implemented using an auto-use fixture so we have it always # mocked? @mock.patch("toot.config.load_app") @mock.patch("toot.config.save_app") @mock.patch("toot.config.save_user") def test_login_cli( save_user: MagicMock, save_app: MagicMock, load_app: MagicMock, user: User, run: Run, ): load_app.return_value = None result = run( cli.auth.login_cli, "--instance", "http://localhost:3000", "--email", f"{user.username}@example.com", "--password", PASSWORD, ) assert result.exit_code == 0 assert "✓ Successfully logged in." in result.stdout save_app.assert_called_once() (app,) = save_app.call_args.args assert app.instance == "localhost:3000" assert app.base_url == "http://localhost:3000" assert app.client_id assert app.client_secret save_user.assert_called_once() (new_user,) = save_user.call_args.args assert new_user.instance == "localhost:3000" assert new_user.username == user.username # access token will be different since this is a new login assert new_user.access_token and new_user.access_token != user.access_token assert save_user.call_args.kwargs == {"activate": True} @mock.patch("toot.config.load_app") @mock.patch("toot.config.save_app") @mock.patch("toot.config.save_user") def test_login_cli_wrong_password( save_user: MagicMock, save_app: MagicMock, load_app: MagicMock, user: User, run: Run, ): load_app.return_value = None result = run( cli.auth.login_cli, "--instance", "http://localhost:3000", "--email", f"{user.username}@example.com", "--password", "wrong password", ) assert result.exit_code == 1 assert result.stderr.strip() == "Error: Login failed" save_app.assert_called_once() (app,) = save_app.call_args.args assert app.instance == "localhost:3000" assert app.base_url == "http://localhost:3000" assert app.client_id assert app.client_secret save_user.assert_not_called() @mock.patch("toot.config.load_config") @mock.patch("toot.config.delete_user") def test_logout(delete_user: MagicMock, load_config: MagicMock, run: Run): load_config.return_value = SAMPLE_CONFIG result = run(cli.auth.logout, "frank@foo.social") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account frank@foo.social logged out" delete_user.assert_called_once_with(User("foo.social", "frank", "123")) @mock.patch("toot.config.load_config") def test_logout_not_logged_in(load_config: MagicMock, run: Run): load_config.return_value = EMPTY_CONFIG result = run(cli.auth.logout) assert result.exit_code == 1 assert result.stderr.strip() == "Error: You're not logged into any accounts" @mock.patch("toot.config.load_config") def test_logout_account_not_specified(load_config: MagicMock, run: Run): load_config.return_value = SAMPLE_CONFIG result = run(cli.auth.logout) assert result.exit_code == 1 assert result.stderr.startswith("Error: Specify account to log out") @mock.patch("toot.config.load_config") def test_logout_account_does_not_exist(load_config: MagicMock, run: Run): load_config.return_value = SAMPLE_CONFIG result = run(cli.auth.logout, "banana") assert result.exit_code == 1 assert result.stderr.startswith("Error: Account not found") @mock.patch("toot.config.load_config") @mock.patch("toot.config.activate_user") def test_activate(activate_user: MagicMock, load_config: MagicMock, run: Run): load_config.return_value = SAMPLE_CONFIG result = run(cli.auth.activate, "frank@foo.social") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account frank@foo.social activated" activate_user.assert_called_once_with(User("foo.social", "frank", "123")) @mock.patch("toot.config.load_config") def test_activate_not_logged_in(load_config: MagicMock, run: Run): load_config.return_value = EMPTY_CONFIG result = run(cli.auth.activate) assert result.exit_code == 1 assert result.stderr.strip() == "Error: You're not logged into any accounts" @mock.patch("toot.config.load_config") def test_activate_account_not_given(load_config: MagicMock, run: Run): load_config.return_value = SAMPLE_CONFIG result = run(cli.auth.activate) assert result.exit_code == 1 assert result.stderr.startswith("Error: Specify account to activate") @mock.patch("toot.config.load_config") def test_activate_invalid_Account(load_config: MagicMock, run: Run): load_config.return_value = SAMPLE_CONFIG result = run(cli.auth.activate, "banana") assert result.exit_code == 1 assert result.stderr.startswith("Error: Account not found") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_lists.py0000644000175000017500000001220614656344035021232 0ustar00ihabunekihabunekfrom uuid import uuid4 from toot import cli from tests.integration.conftest import register_account def test_lists_empty(run): result = run(cli.lists.list) assert result.exit_code == 0 assert result.stdout.strip() == "You have no lists defined." def test_lists_empty_json(run_json): lists = run_json(cli.lists.list, "--json") assert lists == [] def test_list_create_delete(run): result = run(cli.lists.create, "banana") assert result.exit_code == 0 assert result.stdout.strip() == '✓ List "banana" created.' result = run(cli.lists.list) assert result.exit_code == 0 assert "banana" in result.stdout result = run(cli.lists.create, "mango") assert result.exit_code == 0 assert result.stdout.strip() == '✓ List "mango" created.' result = run(cli.lists.list) assert result.exit_code == 0 assert "banana" in result.stdout assert "mango" in result.stdout result = run(cli.lists.delete, "banana") assert result.exit_code == 0 assert result.stdout.strip() == '✓ List "banana" deleted.' result = run(cli.lists.list) assert result.exit_code == 0 assert "banana" not in result.stdout assert "mango" in result.stdout result = run(cli.lists.delete, "mango") assert result.exit_code == 0 assert result.stdout.strip() == '✓ List "mango" deleted.' result = run(cli.lists.list) assert result.exit_code == 0 assert result.stdout.strip() == "You have no lists defined." result = run(cli.lists.delete, "mango") assert result.exit_code == 1 assert result.stderr.strip() == "Error: List not found" def test_list_create_delete_json(run, run_json): result = run_json(cli.lists.list, "--json") assert result == [] list = run_json(cli.lists.create, "banana", "--json") assert list["title"] == "banana" [list] = run_json(cli.lists.list, "--json") assert list["title"] == "banana" list = run_json(cli.lists.create, "mango", "--json") assert list["title"] == "mango" lists = run_json(cli.lists.list, "--json") [list1, list2] = sorted(lists, key=lambda l: l["title"]) assert list1["title"] == "banana" assert list2["title"] == "mango" result = run_json(cli.lists.delete, "banana", "--json") assert result == {} [list] = run_json(cli.lists.list, "--json") assert list["title"] == "mango" result = run_json(cli.lists.delete, "mango", "--json") assert result == {} result = run_json(cli.lists.list, "--json") assert result == [] result = run(cli.lists.delete, "mango", "--json") assert result.exit_code == 1 assert result.stderr.strip() == "Error: List not found" def test_list_add_remove(run, app): list_name = str(uuid4()) acc = register_account(app) run(cli.lists.create, list_name) result = run(cli.lists.add, list_name, acc.username) assert result.exit_code == 1 assert result.stderr.strip() == f"Error: You must follow @{acc.username} before adding this account to a list." run(cli.accounts.follow, acc.username) result = run(cli.lists.add, list_name, acc.username) assert result.exit_code == 0 assert result.stdout.strip() == f'✓ Added account "{acc.username}"' result = run(cli.lists.accounts, list_name) assert result.exit_code == 0 assert acc.username in result.stdout # Account doesn't exist result = run(cli.lists.add, list_name, "does_not_exist") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Account not found" # List doesn't exist result = run(cli.lists.add, "does_not_exist", acc.username) assert result.exit_code == 1 assert result.stderr.strip() == "Error: List not found" result = run(cli.lists.remove, list_name, acc.username) assert result.exit_code == 0 assert result.stdout.strip() == f'✓ Removed account "{acc.username}"' result = run(cli.lists.accounts, list_name) assert result.exit_code == 0 assert result.stdout.strip() == "This list has no accounts." def test_list_add_remove_json(run, run_json, app): list_name = str(uuid4()) acc = register_account(app) run(cli.lists.create, list_name) result = run(cli.lists.add, list_name, acc.username, "--json") assert result.exit_code == 1 assert result.stderr.strip() == f"Error: You must follow @{acc.username} before adding this account to a list." run(cli.accounts.follow, acc.username) result = run_json(cli.lists.add, list_name, acc.username, "--json") assert result == {} [account] = run_json(cli.lists.accounts, list_name, "--json") assert account["username"] == acc.username # Account doesn't exist result = run(cli.lists.add, list_name, "does_not_exist", "--json") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Account not found" # List doesn't exist result = run(cli.lists.add, "does_not_exist", acc.username, "--json") assert result.exit_code == 1 assert result.stderr.strip() == "Error: List not found" result = run_json(cli.lists.remove, list_name, acc.username, "--json") assert result == {} result = run_json(cli.lists.accounts, list_name, "--json") assert result == [] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_post.py0000644000175000017500000002662214656344035021070 0ustar00ihabunekihabunekimport json import re import uuid from datetime import datetime, timedelta, timezone from os import path from tests.integration.conftest import ASSETS_DIR, posted_status_id from toot import CLIENT_NAME, CLIENT_WEBSITE, api, cli from toot.utils import get_text from unittest import mock def test_post(app, user, run): text = "i wish i was a #lumberjack" result = run(cli.post.post, text) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() assert text == get_text(status["content"]) assert status["visibility"] == "public" assert status["sensitive"] is False assert status["spoiler_text"] == "" assert status["poll"] is None # Pleroma doesn't return the application if status["application"]: assert status["application"]["name"] == CLIENT_NAME assert status["application"]["website"] == CLIENT_WEBSITE def test_post_no_text(run): result = run(cli.post.post) assert result.exit_code == 1 assert result.stderr.strip() == "Error: You must specify either text or media to post." def test_post_json(run): content = "i wish i was a #lumberjack" result = run(cli.post.post, content, "--json") assert result.exit_code == 0 status = json.loads(result.stdout) assert get_text(status["content"]) == content assert status["visibility"] == "public" assert status["sensitive"] is False assert status["spoiler_text"] == "" assert status["poll"] is None def test_post_visibility(app, user, run): for visibility in ["public", "unlisted", "private", "direct"]: result = run(cli.post.post, "foo", "--visibility", visibility) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() assert status["visibility"] == visibility def test_post_scheduled_at(app, user, run): text = str(uuid.uuid4()) scheduled_at = datetime.now(timezone.utc).replace(microsecond=0) + timedelta(minutes=10) result = run(cli.post.post, text, "--scheduled-at", scheduled_at.isoformat()) assert result.exit_code == 0 assert "Toot scheduled for" in result.stdout statuses = api.scheduled_statuses(app, user) [status] = [s for s in statuses if s["params"]["text"] == text] assert datetime.strptime(status["scheduled_at"], "%Y-%m-%dT%H:%M:%S.%f%z") == scheduled_at def test_post_scheduled_at_error(run): result = run(cli.post.post, "foo", "--scheduled-at", "banana") assert result.exit_code == 1 # Stupid error returned by mastodon assert result.stderr.strip() == "Error: Record invalid" def test_post_scheduled_in(app, user, run): text = str(uuid.uuid4()) variants = [ ("1 day", timedelta(days=1)), ("1 day 6 hours", timedelta(days=1, hours=6)), ("1 day 6 hours 13 minutes", timedelta(days=1, hours=6, minutes=13)), ("1 day 6 hours 13 minutes 51 second", timedelta(days=1, hours=6, minutes=13, seconds=51)), ("2d", timedelta(days=2)), ("2d6h", timedelta(days=2, hours=6)), ("2d6h13m", timedelta(days=2, hours=6, minutes=13)), ("2d6h13m51s", timedelta(days=2, hours=6, minutes=13, seconds=51)), ] datetimes = [] for scheduled_in, delta in variants: result = run(cli.post.post, text, "--scheduled-in", scheduled_in) assert result.exit_code == 0 dttm = datetime.utcnow() + delta assert result.stdout.startswith(f"Toot scheduled for: {str(dttm)[:16]}") datetimes.append(dttm) scheduled = api.scheduled_statuses(app, user) scheduled = [s for s in scheduled if s["params"]["text"] == text] scheduled = sorted(scheduled, key=lambda s: s["scheduled_at"]) assert len(scheduled) == 8 for expected, status in zip(datetimes, scheduled): actual = datetime.strptime(status["scheduled_at"], "%Y-%m-%dT%H:%M:%S.%fZ") delta = expected - actual assert delta.total_seconds() < 5 def test_post_scheduled_in_invalid_duration(run): result = run(cli.post.post, "foo", "--scheduled-in", "banana") assert result.exit_code == 2 assert "Invalid duration: banana" in result.stderr def test_post_scheduled_in_empty_duration(run): result = run(cli.post.post, "foo", "--scheduled-in", "0m") assert result.exit_code == 2 assert "Empty duration" in result.stderr def test_post_poll(app, user, run): text = str(uuid.uuid4()) result = run( cli.post.post, text, "--poll-option", "foo", "--poll-option", "bar", "--poll-option", "baz", "--poll-option", "qux", ) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() assert status["poll"]["expired"] is False assert status["poll"]["multiple"] is False assert status["poll"]["options"] == [ {"title": "foo", "votes_count": 0}, {"title": "bar", "votes_count": 0}, {"title": "baz", "votes_count": 0}, {"title": "qux", "votes_count": 0} ] # Test expires_at is 24h by default actual = datetime.strptime(status["poll"]["expires_at"], "%Y-%m-%dT%H:%M:%S.%f%z") expected = datetime.now(timezone.utc) + timedelta(days=1) delta = actual - expected assert delta.total_seconds() < 5 def test_post_poll_multiple(app, user, run): text = str(uuid.uuid4()) result = run( cli.post.post, text, "--poll-option", "foo", "--poll-option", "bar", "--poll-multiple" ) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() assert status["poll"]["multiple"] is True def test_post_poll_expires_in(app, user, run): text = str(uuid.uuid4()) result = run( cli.post.post, text, "--poll-option", "foo", "--poll-option", "bar", "--poll-expires-in", "8h", ) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() actual = datetime.strptime(status["poll"]["expires_at"], "%Y-%m-%dT%H:%M:%S.%f%z") expected = datetime.now(timezone.utc) + timedelta(hours=8) delta = actual - expected assert delta.total_seconds() < 5 def test_post_poll_hide_totals(app, user, run): text = str(uuid.uuid4()) result = run( cli.post.post, text, "--poll-option", "foo", "--poll-option", "bar", "--poll-hide-totals" ) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() # votes_count is None when totals are hidden assert status["poll"]["options"] == [ {"title": "foo", "votes_count": None}, {"title": "bar", "votes_count": None}, ] def test_post_language(app, user, run): result = run(cli.post.post, "test", "--language", "hr") assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() assert status["language"] == "hr" result = run(cli.post.post, "test", "--language", "zh") assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() assert status["language"] == "zh" def test_post_language_error(run): result = run(cli.post.post, "test", "--language", "banana") assert result.exit_code == 2 assert "Language should be a two letter abbreviation." in result.stderr def test_media_thumbnail(app, user, run): video_path = path.join(ASSETS_DIR, "small.webm") thumbnail_path = path.join(ASSETS_DIR, "test1.png") result = run( cli.post.post, "--media", video_path, "--thumbnail", thumbnail_path, "--description", "foo", "some text" ) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() [media] = status["media_attachments"] assert media["description"] == "foo" assert media["type"] == "video" assert media["url"].endswith(".mp4") assert media["preview_url"].endswith(".png") # Video properties assert int(media["meta"]["original"]["duration"]) == 5 assert media["meta"]["original"]["height"] == 320 assert media["meta"]["original"]["width"] == 560 # Thumbnail properties assert media["meta"]["small"]["height"] == 50 assert media["meta"]["small"]["width"] == 50 def test_media_attachments(app, user, run): path1 = path.join(ASSETS_DIR, "test1.png") path2 = path.join(ASSETS_DIR, "test2.png") path3 = path.join(ASSETS_DIR, "test3.png") path4 = path.join(ASSETS_DIR, "test4.png") result = run( cli.post.post, "--media", path1, "--media", path2, "--media", path3, "--media", path4, "--description", "Test 1", "--description", "Test 2", "--description", "Test 3", "--description", "Test 4", "some text" ) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() [a1, a2, a3, a4] = status["media_attachments"] # Pleroma doesn't send metadata if "meta" in a1: assert a1["meta"]["original"]["size"] == "50x50" assert a2["meta"]["original"]["size"] == "50x60" assert a3["meta"]["original"]["size"] == "50x70" assert a4["meta"]["original"]["size"] == "50x80" assert a1["description"] == "Test 1" assert a2["description"] == "Test 2" assert a3["description"] == "Test 3" assert a4["description"] == "Test 4" def test_too_many_media(run): m = path.join(ASSETS_DIR, "test1.png") result = run(cli.post.post, "-m", m, "-m", m, "-m", m, "-m", m, "-m", m) assert result.exit_code == 1 assert result.stderr.strip() == "Error: Cannot attach more than 4 files." @mock.patch("toot.utils.multiline_input") @mock.patch("sys.stdin.read") def test_media_attachment_without_text(mock_read, mock_ml, app, user, run): # No status from stdin or readline mock_read.return_value = "" mock_ml.return_value = "" media_path = path.join(ASSETS_DIR, "test1.png") result = run(cli.post.post, "--media", media_path) assert result.exit_code == 0 status_id = posted_status_id(result.stdout) status = api.fetch_status(app, user, status_id).json() assert status["content"] == "" [attachment] = status["media_attachments"] assert not attachment["description"] # Pleroma doesn't send metadata if "meta" in attachment: assert attachment["meta"]["original"]["size"] == "50x50" def test_reply_thread(app, user, friend, run): status = api.post_status(app, friend, "This is the status").json() result = run(cli.post.post, "--reply-to", status["id"], "This is the reply") assert result.exit_code == 0 status_id = posted_status_id(result.stdout) reply = api.fetch_status(app, user, status_id).json() assert reply["in_reply_to_id"] == status["id"] result = run(cli.read.thread, status["id"]) assert result.exit_code == 0 [s1, s2] = [s.strip() for s in re.split(r"─+", result.stdout) if s.strip()] assert "This is the status" in s1 assert "This is the reply" in s2 assert friend.username in s1 assert user.username in s2 assert status["id"] in s1 assert reply["id"] in s2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_read.py0000644000175000017500000001353514656344035021015 0ustar00ihabunekihabunekimport json import re from tests.integration.conftest import TOOT_TEST_BASE_URL from toot import api, cli from toot.entities import Account, Status, from_dict, from_dict_list from uuid import uuid4 def test_instance_default(app, run): result = run(cli.read.instance) assert result.exit_code == 0 assert "Mastodon" in result.stdout assert app.instance in result.stdout assert "running Mastodon" in result.stdout def test_instance_with_url(app, run): result = run(cli.read.instance, TOOT_TEST_BASE_URL) assert result.exit_code == 0 assert "Mastodon" in result.stdout assert app.instance in result.stdout assert "running Mastodon" in result.stdout def test_instance_json(app, run): result = run(cli.read.instance, "--json") assert result.exit_code == 0 data = json.loads(result.stdout) assert data["title"] is not None assert data["description"] is not None assert data["version"] is not None def test_instance_anon(app, run_anon, base_url): result = run_anon(cli.read.instance, base_url) assert result.exit_code == 0 assert "Mastodon" in result.stdout assert app.instance in result.stdout assert "running Mastodon" in result.stdout # Need to specify the instance name when running anon result = run_anon(cli.read.instance) assert result.exit_code == 1 assert result.stderr.strip() == "Error: INSTANCE argument not given and not logged in" def test_whoami(user, run): result = run(cli.read.whoami) assert result.exit_code == 0 assert f"@{user.username}" in result.stdout def test_whoami_json(user, run): result = run(cli.read.whoami, "--json") assert result.exit_code == 0 data = json.loads(result.stdout) account = from_dict(Account, data) assert account.username == user.username assert account.acct == user.username def test_whois(app, friend, run): variants = [ friend.username, f"@{friend.username}", f"{friend.username}@{app.instance}", f"@{friend.username}@{app.instance}", ] for username in variants: result = run(cli.read.whois, username) assert result.exit_code == 0 assert f"@{friend.username}" in result.stdout def test_whois_json(app, friend, run): result = run(cli.read.whois, friend.username, "--json") assert result.exit_code == 0 data = json.loads(result.stdout) account = from_dict(Account, data) assert account.username == friend.username assert account.acct == friend.username def test_search_account(friend, run): result = run(cli.read.search, friend.username) assert result.exit_code == 0 assert result.stdout.strip() == f"Accounts:\n* @{friend.username}" def test_search_account_json(friend, run): result = run(cli.read.search, friend.username, "--json") assert result.exit_code == 0 data = json.loads(result.stdout) [account] = from_dict_list(Account, data["accounts"]) assert account.acct == friend.username def test_search_hashtag(app, user, run): api.post_status(app, user, "#hashtag_x") api.post_status(app, user, "#hashtag_y") api.post_status(app, user, "#hashtag_z") result = run(cli.read.search, "#hashtag") assert result.exit_code == 0 assert result.stdout.strip() == "Hashtags:\n#hashtag_x, #hashtag_y, #hashtag_z" def test_search_hashtag_json(app, user, run): api.post_status(app, user, "#hashtag_x") api.post_status(app, user, "#hashtag_y") api.post_status(app, user, "#hashtag_z") result = run(cli.read.search, "#hashtag", "--json") assert result.exit_code == 0 data = json.loads(result.stdout) [h1, h2, h3] = sorted(data["hashtags"], key=lambda h: h["name"]) assert h1["name"] == "hashtag_x" assert h2["name"] == "hashtag_y" assert h3["name"] == "hashtag_z" def test_status(app, user, run): uuid = str(uuid4()) status_id = api.post_status(app, user, uuid).json()["id"] result = run(cli.read.status, status_id) assert result.exit_code == 0 out = result.stdout.strip() assert uuid in out assert user.username in out assert status_id in out def test_status_json(app, user, run): uuid = str(uuid4()) status_id = api.post_status(app, user, uuid).json()["id"] result = run(cli.read.status, status_id, "--json") assert result.exit_code == 0 status = from_dict(Status, json.loads(result.stdout)) assert status.id == status_id assert status.account.acct == user.username assert uuid in status.content def test_thread(app, user, run): uuid1 = str(uuid4()) uuid2 = str(uuid4()) uuid3 = str(uuid4()) s1 = api.post_status(app, user, uuid1).json() s2 = api.post_status(app, user, uuid2, in_reply_to_id=s1["id"]).json() s3 = api.post_status(app, user, uuid3, in_reply_to_id=s2["id"]).json() for status in [s1, s2, s3]: result = run(cli.read.thread, status["id"]) assert result.exit_code == 0 bits = re.split(r"─+", result.stdout.strip()) bits = [b for b in bits if b] assert len(bits) == 3 assert s1["id"] in bits[0] assert s2["id"] in bits[1] assert s3["id"] in bits[2] assert uuid1 in bits[0] assert uuid2 in bits[1] assert uuid3 in bits[2] def test_thread_json(app, user, run): uuid1 = str(uuid4()) uuid2 = str(uuid4()) uuid3 = str(uuid4()) s1 = api.post_status(app, user, uuid1).json() s2 = api.post_status(app, user, uuid2, in_reply_to_id=s1["id"]).json() s3 = api.post_status(app, user, uuid3, in_reply_to_id=s2["id"]).json() result = run(cli.read.thread, s2["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) [ancestor] = [from_dict(Status, s) for s in result["ancestors"]] [descendent] = [from_dict(Status, s) for s in result["descendants"]] assert ancestor.id == s1["id"] assert descendent.id == s3["id"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_status.py0000644000175000017500000001374514656344035021430 0ustar00ihabunekihabunekimport json import pytest from tests.utils import run_with_retries from toot import api, cli from toot.exceptions import NotFoundError def test_delete(app, user, run): status = api.post_status(app, user, "foo").json() result = run(cli.statuses.delete, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status deleted" with pytest.raises(NotFoundError): api.fetch_status(app, user, status["id"]) def test_delete_json(app, user, run): status = api.post_status(app, user, "foo").json() result = run(cli.statuses.delete, status["id"], "--json") assert result.exit_code == 0 out = result.stdout result = json.loads(out) assert result["id"] == status["id"] with pytest.raises(NotFoundError): api.fetch_status(app, user, status["id"]) def test_favourite(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["favourited"] result = run(cli.statuses.favourite, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status favourited" status = api.fetch_status(app, user, status["id"]).json() assert status["favourited"] result = run(cli.statuses.unfavourite, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status unfavourited" def test_favourited(): nonlocal status status = api.fetch_status(app, user, status["id"]).json() assert not status["favourited"] run_with_retries(test_favourited) def test_favourite_json(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["favourited"] result = run(cli.statuses.favourite, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["id"] == status["id"] assert result["favourited"] is True result = run(cli.statuses.unfavourite, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["id"] == status["id"] assert result["favourited"] is False def test_reblog(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["reblogged"] result = run(cli.statuses.reblogged_by, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "This status is not reblogged by anyone" result = run(cli.statuses.reblog, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status reblogged" status = api.fetch_status(app, user, status["id"]).json() assert status["reblogged"] result = run(cli.statuses.reblogged_by, status["id"]) assert result.exit_code == 0 assert user.username in result.stdout result = run(cli.statuses.unreblog, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status unreblogged" status = api.fetch_status(app, user, status["id"]).json() assert not status["reblogged"] def test_reblog_json(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["reblogged"] result = run(cli.statuses.reblog, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["reblogged"] is True assert result["reblog"]["id"] == status["id"] result = run(cli.statuses.reblogged_by, status["id"], "--json") assert result.exit_code == 0 [reblog] = json.loads(result.stdout) assert reblog["acct"] == user.username result = run(cli.statuses.unreblog, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["reblogged"] is False assert result["reblog"] is None def test_pin(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["pinned"] result = run(cli.statuses.pin, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status pinned" status = api.fetch_status(app, user, status["id"]).json() assert status["pinned"] result = run(cli.statuses.unpin, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status unpinned" status = api.fetch_status(app, user, status["id"]).json() assert not status["pinned"] def test_pin_json(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["pinned"] result = run(cli.statuses.pin, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["pinned"] is True assert result["id"] == status["id"] result = run(cli.statuses.unpin, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["pinned"] is False assert result["id"] == status["id"] def test_bookmark(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["bookmarked"] result = run(cli.statuses.bookmark, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status bookmarked" status = api.fetch_status(app, user, status["id"]).json() assert status["bookmarked"] result = run(cli.statuses.unbookmark, status["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Status unbookmarked" status = api.fetch_status(app, user, status["id"]).json() assert not status["bookmarked"] def test_bookmark_json(app, user, run): status = api.post_status(app, user, "foo").json() assert not status["bookmarked"] result = run(cli.statuses.bookmark, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["id"] == status["id"] assert result["bookmarked"] is True result = run(cli.statuses.unbookmark, status["id"], "--json") assert result.exit_code == 0 result = json.loads(result.stdout) assert result["id"] == status["id"] assert result["bookmarked"] is False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_tags.py0000644000175000017500000001232614656344035021035 0ustar00ihabunekihabunekimport re from typing import List from toot import api, cli from toot.entities import FeaturedTag, Tag, from_dict, from_dict_list def test_tags(run): result = run(cli.tags.tags, "followed") assert result.exit_code == 0 assert result.stdout.strip() == "You're not following any hashtags" result = run(cli.tags.tags, "follow", "foo") assert result.exit_code == 0 assert result.stdout.strip() == "✓ You are now following #foo" result = run(cli.tags.tags, "followed") assert result.exit_code == 0 assert _find_tags(result.stdout) == ["#foo"] result = run(cli.tags.tags, "follow", "bar") assert result.exit_code == 0 assert result.stdout.strip() == "✓ You are now following #bar" result = run(cli.tags.tags, "followed") assert result.exit_code == 0 assert _find_tags(result.stdout) == ["#bar", "#foo"] result = run(cli.tags.tags, "unfollow", "foo") assert result.exit_code == 0 assert result.stdout.strip() == "✓ You are no longer following #foo" result = run(cli.tags.tags, "followed") assert result.exit_code == 0 assert _find_tags(result.stdout) == ["#bar"] result = run(cli.tags.tags, "unfollow", "bar") assert result.exit_code == 0 assert result.stdout.strip() == "✓ You are no longer following #bar" result = run(cli.tags.tags, "followed") assert result.exit_code == 0 assert result.stdout.strip() == "You're not following any hashtags" def test_tags_json(run_json): result = run_json(cli.tags.tags, "followed", "--json") assert result == [] result = run_json(cli.tags.tags, "follow", "foo", "--json") tag = from_dict(Tag, result) assert tag.name == "foo" assert tag.following is True result = run_json(cli.tags.tags, "followed", "--json") [tag] = from_dict_list(Tag, result) assert tag.name == "foo" assert tag.following is True result = run_json(cli.tags.tags, "follow", "bar", "--json") tag = from_dict(Tag, result) assert tag.name == "bar" assert tag.following is True result = run_json(cli.tags.tags, "followed", "--json") tags = from_dict_list(Tag, result) [bar, foo] = sorted(tags, key=lambda t: t.name) assert foo.name == "foo" assert foo.following is True assert bar.name == "bar" assert bar.following is True result = run_json(cli.tags.tags, "unfollow", "foo", "--json") tag = from_dict(Tag, result) assert tag.name == "foo" assert tag.following is False result = run_json(cli.tags.tags, "unfollow", "bar", "--json") tag = from_dict(Tag, result) assert tag.name == "bar" assert tag.following is False result = run_json(cli.tags.tags, "followed", "--json") assert result == [] def test_tags_featured(run, app, user): result = run(cli.tags.tags, "featured") assert result.exit_code == 0 assert result.stdout.strip() == "You don't have any featured hashtags" result = run(cli.tags.tags, "feature", "foo") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Tag #foo is now featured" result = run(cli.tags.tags, "featured") assert result.exit_code == 0 assert _find_tags(result.stdout) == ["#foo"] result = run(cli.tags.tags, "feature", "bar") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Tag #bar is now featured" result = run(cli.tags.tags, "featured") assert result.exit_code == 0 assert _find_tags(result.stdout) == ["#bar", "#foo"] # Unfeature by Name result = run(cli.tags.tags, "unfeature", "foo") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Tag #foo is no longer featured" result = run(cli.tags.tags, "featured") assert result.exit_code == 0 assert _find_tags(result.stdout) == ["#bar"] # Unfeature by ID tag = api.find_featured_tag(app, user, "bar") assert tag is not None result = run(cli.tags.tags, "unfeature", tag["id"]) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Tag #bar is no longer featured" result = run(cli.tags.tags, "featured") assert result.exit_code == 0 assert result.stdout.strip() == "You don't have any featured hashtags" def test_tags_featured_json(run_json): result = run_json(cli.tags.tags, "featured", "--json") assert result == [] result = run_json(cli.tags.tags, "feature", "foo", "--json") tag = from_dict(FeaturedTag, result) assert tag.name == "foo" result = run_json(cli.tags.tags, "featured", "--json") [tag] = from_dict_list(FeaturedTag, result) assert tag.name == "foo" result = run_json(cli.tags.tags, "feature", "bar", "--json") tag = from_dict(FeaturedTag, result) assert tag.name == "bar" result = run_json(cli.tags.tags, "featured", "--json") tags = from_dict_list(FeaturedTag, result) [bar, foo] = sorted(tags, key=lambda t: t.name) assert foo.name == "foo" assert bar.name == "bar" result = run_json(cli.tags.tags, "unfeature", "foo", "--json") assert result == {} result = run_json(cli.tags.tags, "unfeature", "bar", "--json") assert result == {} result = run_json(cli.tags.tags, "featured", "--json") assert result == [] def _find_tags(txt: str) -> List[str]: return sorted(re.findall(r"#\w+", txt)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_timelines.py0000644000175000017500000001525214656344035022071 0ustar00ihabunekihabunekimport pytest from uuid import uuid4 from tests.utils import run_with_retries from toot import api, cli from toot.entities import from_dict, Status from tests.integration.conftest import TOOT_TEST_BASE_URL, register_account # TODO: If fixture is not overridden here, tests fail, not sure why, figure it out @pytest.fixture(scope="module") def user(app): return register_account(app) @pytest.fixture(scope="module") def other_user(app): return register_account(app) @pytest.fixture(scope="module") def friend_user(app, user): friend = register_account(app) friend_account = api.find_account(app, user, friend.username) api.follow(app, user, friend_account["id"]) return friend @pytest.fixture(scope="module") def friend_list(app, user, friend_user): friend_account = api.find_account(app, user, friend_user.username) list = api.create_list(app, user, str(uuid4())).json() api.add_accounts_to_list(app, user, list["id"], account_ids=[friend_account["id"]]) return list def test_timelines(app, user, other_user, friend_user, friend_list, run): status1 = _post_status(app, user, "#foo") status2 = _post_status(app, other_user, "#bar") status3 = _post_status(app, friend_user, "#foo #bar") # Home timeline def test_home(): result = run(cli.timelines.timeline) assert result.exit_code == 0 assert status1.id in result.stdout assert status2.id not in result.stdout assert status3.id in result.stdout run_with_retries(test_home) # Public timeline result = run(cli.timelines.timeline, "--public") assert result.exit_code == 0 assert status1.id in result.stdout assert status2.id in result.stdout assert status3.id in result.stdout # Anon public timeline result = run(cli.timelines.timeline, "--instance", TOOT_TEST_BASE_URL, "--public") assert result.exit_code == 0 assert status1.id in result.stdout assert status2.id in result.stdout assert status3.id in result.stdout # Tag timeline result = run(cli.timelines.timeline, "--tag", "foo") assert result.exit_code == 0 assert status1.id in result.stdout assert status2.id not in result.stdout assert status3.id in result.stdout result = run(cli.timelines.timeline, "--tag", "bar") assert result.exit_code == 0 assert status1.id not in result.stdout assert status2.id in result.stdout assert status3.id in result.stdout # Anon tag timeline result = run(cli.timelines.timeline, "--instance", TOOT_TEST_BASE_URL, "--tag", "foo") assert result.exit_code == 0 assert status1.id in result.stdout assert status2.id not in result.stdout assert status3.id in result.stdout # List timeline (by list name) result = run(cli.timelines.timeline, "--list", friend_list["title"]) assert result.exit_code == 0 assert status1.id not in result.stdout assert status2.id not in result.stdout assert status3.id in result.stdout # List timeline (by list ID) result = run(cli.timelines.timeline, "--list", friend_list["id"]) assert result.exit_code == 0 assert status1.id not in result.stdout assert status2.id not in result.stdout assert status3.id in result.stdout # Account timeline result = run(cli.timelines.timeline, "--account", friend_user.username) assert result.exit_code == 0 assert status1.id not in result.stdout assert status2.id not in result.stdout assert status3.id in result.stdout result = run(cli.timelines.timeline, "--account", other_user.username) assert result.exit_code == 0 assert status1.id not in result.stdout assert status2.id in result.stdout assert status3.id not in result.stdout def test_empty_timeline(app, run_as): user = register_account(app) result = run_as(user, cli.timelines.timeline) assert result.exit_code == 0 assert result.stdout.strip() == "─" * 80 def test_timeline_cant_combine_timelines(run): result = run(cli.timelines.timeline, "--tag", "foo", "--account", "bar") assert result.exit_code == 1 assert result.stderr.strip() == "Error: Only one of --public, --tag, --account, or --list can be used at one time." def test_timeline_local_needs_public_or_tag(run): result = run(cli.timelines.timeline, "--local") assert result.exit_code == 1 assert result.stderr.strip() == "Error: The --local option is only valid alongside --public or --tag." def test_timeline_instance_needs_public_or_tag(run): result = run(cli.timelines.timeline, "--instance", TOOT_TEST_BASE_URL) assert result.exit_code == 1 assert result.stderr.strip() == "Error: The --instance option is only valid alongside --public or --tag." def test_bookmarks(app, user, run): status1 = _post_status(app, user) status2 = _post_status(app, user) api.bookmark(app, user, status1.id) api.bookmark(app, user, status2.id) result = run(cli.timelines.bookmarks) assert result.exit_code == 0 assert status1.id in result.stdout assert status2.id in result.stdout assert result.stdout.find(status1.id) > result.stdout.find(status2.id) result = run(cli.timelines.bookmarks, "--reverse") assert result.exit_code == 0 assert status1.id in result.stdout assert status2.id in result.stdout assert result.stdout.find(status1.id) < result.stdout.find(status2.id) def test_notifications(app, user, other_user, run): result = run(cli.timelines.notifications) assert result.exit_code == 0 assert result.stdout.strip() == "You have no notifications" text = f"Paging doctor @{user.username}" status = _post_status(app, other_user, text) def test_notifications(): result = run(cli.timelines.notifications) assert result.exit_code == 0 assert f"@{other_user.username} mentioned you" in result.stdout assert status.id in result.stdout assert text in result.stdout run_with_retries(test_notifications) result = run(cli.timelines.notifications, "--mentions") assert result.exit_code == 0 assert f"@{other_user.username} mentioned you" in result.stdout assert status.id in result.stdout assert text in result.stdout def test_notifications_follow(app, user, friend_user, run_as): result = run_as(friend_user, cli.timelines.notifications) assert result.exit_code == 0 assert f"@{user.username} now follows you" in result.stdout result = run_as(friend_user, cli.timelines.notifications, "--mentions") assert result.exit_code == 0 assert "now follows you" not in result.stdout def _post_status(app, user, text=None) -> Status: text = text or str(uuid4()) response = api.post_status(app, user, text) return from_dict(Status, response.json()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/integration/test_update_account.py0000644000175000017500000001177414656344035023103 0ustar00ihabunekihabunekfrom uuid import uuid4 from tests.integration.conftest import TRUMPET from toot import api, cli from toot.entities import Account, from_dict from toot.utils import get_text def test_update_account_no_options(run): result = run(cli.accounts.update_account) assert result.exit_code == 1 assert result.stderr.strip() == "Error: Please specify at least one option to update the account" def test_update_account_display_name(run, app, user): name = str(uuid4())[:10] result = run(cli.accounts.update_account, "--display-name", name) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["display_name"] == name def test_update_account_json(run_json, app, user): name = str(uuid4())[:10] out = run_json(cli.accounts.update_account, "--display-name", name, "--json") account = from_dict(Account, out) assert account.acct == user.username assert account.display_name == name def test_update_account_note(run, app, user): note = ("It's 106 miles to Chicago, we got a full tank of gas, half a pack " "of cigarettes, it's dark... and we're wearing sunglasses.") result = run(cli.accounts.update_account, "--note", note) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert get_text(account["note"]) == note def test_update_account_language(run, app, user): result = run(cli.accounts.update_account, "--language", "hr") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["source"]["language"] == "hr" def test_update_account_privacy(run, app, user): result = run(cli.accounts.update_account, "--privacy", "private") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["source"]["privacy"] == "private" def test_update_account_avatar(run, app, user): account = api.verify_credentials(app, user).json() old_value = account["avatar"] result = run(cli.accounts.update_account, "--avatar", TRUMPET) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["avatar"] != old_value def test_update_account_header(run, app, user): account = api.verify_credentials(app, user).json() old_value = account["header"] result = run(cli.accounts.update_account, "--header", TRUMPET) assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["header"] != old_value def test_update_account_locked(run, app, user): result = run(cli.accounts.update_account, "--locked") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["locked"] is True result = run(cli.accounts.update_account, "--no-locked") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["locked"] is False def test_update_account_bot(run, app, user): result = run(cli.accounts.update_account, "--bot") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["bot"] is True result = run(cli.accounts.update_account, "--no-bot") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["bot"] is False def test_update_account_discoverable(run, app, user): result = run(cli.accounts.update_account, "--discoverable") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["discoverable"] is True result = run(cli.accounts.update_account, "--no-discoverable") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["discoverable"] is False def test_update_account_sensitive(run, app, user): result = run(cli.accounts.update_account, "--sensitive") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["source"]["sensitive"] is True result = run(cli.accounts.update_account, "--no-sensitive") assert result.exit_code == 0 assert result.stdout.strip() == "✓ Account updated" account = api.verify_credentials(app, user).json() assert account["source"]["sensitive"] is False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/test_config.py0000644000175000017500000001160514656344035017020 0ustar00ihabunekihabunekimport os import pytest from toot import User, App, config @pytest.fixture def sample_config(): return { 'apps': { 'foo.social': { 'base_url': 'https://foo.social', 'client_id': 'abc', 'client_secret': 'def', 'instance': 'foo.social' }, 'bar.social': { 'base_url': 'https://bar.social', 'client_id': 'ghi', 'client_secret': 'jkl', 'instance': 'bar.social' }, }, 'users': { 'foo@bar.social': { 'access_token': 'mno', 'instance': 'bar.social', 'username': 'ihabunek' } }, 'active_user': 'foo@bar.social', } def test_extract_active_user_app(sample_config): user, app = config.extract_user_app(sample_config, sample_config['active_user']) assert isinstance(user, User) assert user.instance == 'bar.social' assert user.username == 'ihabunek' assert user.access_token == 'mno' assert isinstance(app, App) assert app.instance == 'bar.social' assert app.base_url == 'https://bar.social' assert app.client_id == 'ghi' assert app.client_secret == 'jkl' def test_extract_active_when_no_active_user(sample_config): # When there is no active user assert config.extract_user_app(sample_config, None) == (None, None) # When active user does not exist for whatever reason assert config.extract_user_app(sample_config, 'does-not-exist') == (None, None) # When active app does not exist for whatever reason sample_config['users']['foo@bar.social']['instance'] = 'does-not-exist' assert config.extract_user_app(sample_config, 'foo@bar.social') == (None, None) def test_save_app(sample_config): pytest.skip("TODO: fix mocking") app = App('xxx.yyy', 2, 3, 4) app2 = App('moo.foo', 5, 6, 7) app_count = len(sample_config['apps']) assert 'xxx.yyy' not in sample_config['apps'] assert 'moo.foo' not in sample_config['apps'] # Sets config.save_app.__wrapped__(sample_config, app) assert len(sample_config['apps']) == app_count + 1 assert 'xxx.yyy' in sample_config['apps'] assert sample_config['apps']['xxx.yyy']['instance'] == 'xxx.yyy' assert sample_config['apps']['xxx.yyy']['base_url'] == 2 assert sample_config['apps']['xxx.yyy']['client_id'] == 3 assert sample_config['apps']['xxx.yyy']['client_secret'] == 4 # Overwrites config.save_app.__wrapped__(sample_config, app2) assert len(sample_config['apps']) == app_count + 2 assert 'xxx.yyy' in sample_config['apps'] assert 'moo.foo' in sample_config['apps'] assert sample_config['apps']['xxx.yyy']['instance'] == 'xxx.yyy' assert sample_config['apps']['xxx.yyy']['base_url'] == 2 assert sample_config['apps']['xxx.yyy']['client_id'] == 3 assert sample_config['apps']['xxx.yyy']['client_secret'] == 4 assert sample_config['apps']['moo.foo']['instance'] == 'moo.foo' assert sample_config['apps']['moo.foo']['base_url'] == 5 assert sample_config['apps']['moo.foo']['client_id'] == 6 assert sample_config['apps']['moo.foo']['client_secret'] == 7 # Idempotent config.save_app.__wrapped__(sample_config, app2) assert len(sample_config['apps']) == app_count + 2 assert 'xxx.yyy' in sample_config['apps'] assert 'moo.foo' in sample_config['apps'] assert sample_config['apps']['xxx.yyy']['instance'] == 'xxx.yyy' assert sample_config['apps']['xxx.yyy']['base_url'] == 2 assert sample_config['apps']['xxx.yyy']['client_id'] == 3 assert sample_config['apps']['xxx.yyy']['client_secret'] == 4 assert sample_config['apps']['moo.foo']['instance'] == 'moo.foo' assert sample_config['apps']['moo.foo']['base_url'] == 5 assert sample_config['apps']['moo.foo']['client_id'] == 6 assert sample_config['apps']['moo.foo']['client_secret'] == 7 def test_delete_app(sample_config): pytest.skip("TODO: fix mocking") app = App('foo.social', 2, 3, 4) app_count = len(sample_config['apps']) assert 'foo.social' in sample_config['apps'] config.delete_app.__wrapped__(sample_config, app) assert 'foo.social' not in sample_config['apps'] assert len(sample_config['apps']) == app_count - 1 # Idempotent config.delete_app.__wrapped__(sample_config, app) assert 'foo.social' not in sample_config['apps'] assert len(sample_config['apps']) == app_count - 1 def test_get_config_file_path(): fn = config.get_config_file_path os.unsetenv('XDG_CONFIG_HOME') os.environ.pop('XDG_CONFIG_HOME', None) assert fn() == os.path.expanduser('~/.config/toot/config.json') os.environ['XDG_CONFIG_HOME'] = '/foo/bar/config' assert fn() == '/foo/bar/config/toot/config.json' os.environ['XDG_CONFIG_HOME'] = '~/foo/config' assert fn() == os.path.expanduser('~/foo/config/toot/config.json') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/test_utils.py0000644000175000017500000002526114656344035016716 0ustar00ihabunekihabunekimport click import pytest import sys from toot.cli.validators import validate_duration from toot.wcstring import wc_wrap, trunc, pad, fit_text from toot.tui.utils import LRUCache from PIL import Image from collections import namedtuple from toot.utils import urlencode_url def test_pad(): # guitar symbol will occupy two cells, so padded text should be 1 # character shorter text = 'Frank Zappa 🎸' # Negative values are basically ignored assert pad(text, -100) is text # Padding to length smaller than text length does nothing assert pad(text, 11) is text assert pad(text, 12) is text assert pad(text, 13) is text assert pad(text, 14) is text assert pad(text, 15) == 'Frank Zappa 🎸 ' assert pad(text, 16) == 'Frank Zappa 🎸 ' assert pad(text, 17) == 'Frank Zappa 🎸 ' assert pad(text, 18) == 'Frank Zappa 🎸 ' assert pad(text, 19) == 'Frank Zappa 🎸 ' assert pad(text, 20) == 'Frank Zappa 🎸 ' def test_trunc(): text = 'Frank Zappa 🎸' assert trunc(text, 1) == '…' assert trunc(text, 2) == 'F…' assert trunc(text, 3) == 'Fr…' assert trunc(text, 4) == 'Fra…' assert trunc(text, 5) == 'Fran…' assert trunc(text, 6) == 'Frank…' assert trunc(text, 7) == 'Frank…' assert trunc(text, 8) == 'Frank Z…' assert trunc(text, 9) == 'Frank Za…' assert trunc(text, 10) == 'Frank Zap…' assert trunc(text, 11) == 'Frank Zapp…' assert trunc(text, 12) == 'Frank Zappa…' assert trunc(text, 13) == 'Frank Zappa…' # Truncating to length larger than text length does nothing assert trunc(text, 14) is text assert trunc(text, 15) is text assert trunc(text, 16) is text assert trunc(text, 17) is text assert trunc(text, 18) is text assert trunc(text, 19) is text assert trunc(text, 20) is text def test_fit_text(): text = 'Frank Zappa 🎸' assert fit_text(text, 1) == '…' assert fit_text(text, 2) == 'F…' assert fit_text(text, 3) == 'Fr…' assert fit_text(text, 4) == 'Fra…' assert fit_text(text, 5) == 'Fran…' assert fit_text(text, 6) == 'Frank…' assert fit_text(text, 7) == 'Frank…' assert fit_text(text, 8) == 'Frank Z…' assert fit_text(text, 9) == 'Frank Za…' assert fit_text(text, 10) == 'Frank Zap…' assert fit_text(text, 11) == 'Frank Zapp…' assert fit_text(text, 12) == 'Frank Zappa…' assert fit_text(text, 13) == 'Frank Zappa…' assert fit_text(text, 14) == 'Frank Zappa 🎸' assert fit_text(text, 15) == 'Frank Zappa 🎸 ' assert fit_text(text, 16) == 'Frank Zappa 🎸 ' assert fit_text(text, 17) == 'Frank Zappa 🎸 ' assert fit_text(text, 18) == 'Frank Zappa 🎸 ' assert fit_text(text, 19) == 'Frank Zappa 🎸 ' assert fit_text(text, 20) == 'Frank Zappa 🎸 ' def test_wc_wrap_plain_text(): lorem = ( "Eius voluptas eos praesentium et tempore. Quaerat nihil voluptatem " "excepturi reiciendis sapiente voluptate natus. Tenetur occaecati " "velit dicta dolores. Illo reiciendis nulla ea. Facilis nostrum non " "qui inventore sit." ) assert list(wc_wrap(lorem, 50)) == [ #01234567890123456789012345678901234567890123456789 # noqa "Eius voluptas eos praesentium et tempore. Quaerat", "nihil voluptatem excepturi reiciendis sapiente", "voluptate natus. Tenetur occaecati velit dicta", "dolores. Illo reiciendis nulla ea. Facilis nostrum", "non qui inventore sit.", ] def test_wc_wrap_plain_text_wrap_on_any_whitespace(): lorem = ( "Eius\t\tvoluptas\teos\tpraesentium\tet\ttempore.\tQuaerat\tnihil\tvoluptatem\t" "excepturi\nreiciendis\n\nsapiente\nvoluptate\nnatus.\nTenetur\noccaecati\n" "velit\rdicta\rdolores.\rIllo\rreiciendis\rnulla\r\r\rea.\rFacilis\rnostrum\rnon\r" "qui\u2003inventore\u2003\u2003sit." # em space ) assert list(wc_wrap(lorem, 50)) == [ #01234567890123456789012345678901234567890123456789 # noqa "Eius voluptas eos praesentium et tempore. Quaerat", "nihil voluptatem excepturi reiciendis sapiente", "voluptate natus. Tenetur occaecati velit dicta", "dolores. Illo reiciendis nulla ea. Facilis nostrum", "non qui inventore sit.", ] def test_wc_wrap_text_with_wide_chars(): lorem = ( "☕☕☕☕☕ voluptas eos praesentium et 🎸🎸🎸🎸🎸. Quaerat nihil " "voluptatem excepturi reiciendis sapiente voluptate natus." ) assert list(wc_wrap(lorem, 50)) == [ #01234567890123456789012345678901234567890123456789 # noqa "☕☕☕☕☕ voluptas eos praesentium et 🎸🎸🎸🎸🎸.", "Quaerat nihil voluptatem excepturi reiciendis", "sapiente voluptate natus.", ] def test_wc_wrap_hard_wrap(): lorem = ( "☕☕☕☕☕voluptaseospraesentiumet🎸🎸🎸🎸🎸.Quaeratnihil" "voluptatemexcepturireiciendissapientevoluptatenatus." ) assert list(wc_wrap(lorem, 50)) == [ #01234567890123456789012345678901234567890123456789 # noqa "☕☕☕☕☕voluptaseospraesentiumet🎸🎸🎸🎸🎸.Quaer", "atnihilvoluptatemexcepturireiciendissapientevolupt", "atenatus.", ] def test_wc_wrap_indented(): lorem = ( " Eius voluptas eos praesentium et tempore. Quaerat nihil voluptatem " " excepturi reiciendis sapiente voluptate natus. Tenetur occaecati " " velit dicta dolores. Illo reiciendis nulla ea. Facilis nostrum non " " qui inventore sit." ) assert list(wc_wrap(lorem, 50)) == [ #01234567890123456789012345678901234567890123456789 # noqa "Eius voluptas eos praesentium et tempore. Quaerat", "nihil voluptatem excepturi reiciendis sapiente", "voluptate natus. Tenetur occaecati velit dicta", "dolores. Illo reiciendis nulla ea. Facilis nostrum", "non qui inventore sit.", ] def test_duration(): def duration(value): return validate_duration(None, None, value) # Long hand assert duration("1 second") == 1 assert duration("1 seconds") == 1 assert duration("100 second") == 100 assert duration("100 seconds") == 100 assert duration("5 minutes") == 5 * 60 assert duration("5 minutes 10 seconds") == 5 * 60 + 10 assert duration("1 hour 5 minutes") == 3600 + 5 * 60 assert duration("1 hour 5 minutes 1 second") == 3600 + 5 * 60 + 1 assert duration("5 days") == 5 * 86400 assert duration("5 days 3 minutes") == 5 * 86400 + 3 * 60 assert duration("5 days 10 hours 3 minutes 1 second") == 5 * 86400 + 10 * 3600 + 3 * 60 + 1 # Short hand assert duration("1s") == 1 assert duration("100s") == 100 assert duration("5m") == 5 * 60 assert duration("5m10s") == 5 * 60 + 10 assert duration("5m 10s") == 5 * 60 + 10 assert duration("1h5m1s") == 3600 + 5 * 60 + 1 assert duration("1h 5m 1s") == 3600 + 5 * 60 + 1 assert duration("5d") == 5 * 86400 assert duration("5d3m") == 5 * 86400 + 3 * 60 assert duration("5d 3m") == 5 * 86400 + 3 * 60 assert duration("5d 10h 3m 1s") == 5 * 86400 + 10 * 3600 + 3 * 60 + 1 assert duration("5d10h3m1s") == 5 * 86400 + 10 * 3600 + 3 * 60 + 1 with pytest.raises(click.BadParameter): duration("") with pytest.raises(click.BadParameter): duration("100") # Wrong order with pytest.raises(click.BadParameter): duration("1m1d") with pytest.raises(click.BadParameter): duration("banana") def test_cache_null(): """Null dict is null.""" cache = LRUCache(cache_max_bytes=1024) assert cache.__len__() == 0 Case = namedtuple("Case", ["cache_len", "len", "init"]) img = Image.new('RGB', (100, 100)) img_size = sys.getsizeof(img.tobytes()) @pytest.mark.parametrize( "case", [ Case(9, 0, []), Case(9, 1, [("one", img)]), Case(9, 2, [("one", img), ("two", img)]), Case(2, 2, [("one", img), ("two", img)]), Case(1, 1, [("one", img), ("two", img)]), ], ) @pytest.mark.parametrize("method", ["assign", "init"]) def test_cache_init(case, method): """Check that the # of elements is right, given # given and cache_len.""" if method == "init": cache = LRUCache(case.init, cache_max_bytes=img_size * case.cache_len) elif method == "assign": cache = LRUCache(cache_max_bytes=img_size * case.cache_len) for (key, val) in case.init: cache[key] = val else: assert False # length is max(#entries, cache_len) assert cache.__len__() == case.len # make sure the first entry is the one ejected if case.cache_len > 1 and case.init: assert "one" in cache.keys() else: assert "one" not in cache.keys() @pytest.mark.parametrize("method", ["init", "assign"]) def test_cache_overflow_default(method): """Test default overflow logic.""" if method == "init": cache = LRUCache([("one", img), ("two", img), ("three", img)], cache_max_bytes=img_size * 2) elif method == "assign": cache = LRUCache(cache_max_bytes=img_size * 2) cache["one"] = img cache["two"] = img cache["three"] = img else: assert False assert "one" not in cache.keys() assert "two" in cache.keys() assert "three" in cache.keys() @pytest.mark.parametrize("mode", ["get", "set"]) @pytest.mark.parametrize("add_third", [False, True]) def test_cache_lru_overflow(mode, add_third): img = Image.new('RGB', (100, 100)) img_size = sys.getsizeof(img.tobytes()) """Test that key access resets LRU logic.""" cache = LRUCache([("one", img), ("two", img)], cache_max_bytes=img_size * 2) if mode == "get": dummy = cache["one"] elif mode == "set": cache["one"] = img else: assert False if add_third: cache["three"] = img assert "one" in cache.keys() assert "two" not in cache.keys() assert "three" in cache.keys() else: assert "one" in cache.keys() assert "two" in cache.keys() assert "three" not in cache.keys() def test_cache_keyerror(): cache = LRUCache() with pytest.raises(KeyError): cache["foo"] def test_cache_miss_doesnt_eject(): cache = LRUCache([("one", img), ("two", img)], cache_max_bytes=img_size * 3) with pytest.raises(KeyError): cache["foo"] assert len(cache) == 2 assert "one" in cache.keys() assert "two" in cache.keys() def test_urlencode_url(): assert urlencode_url("https://www.example.com") == "https://www.example.com" assert urlencode_url("https://www.example.com/url%20with%20spaces") == "https://www.example.com/url%20with%20spaces" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749800.0 toot-0.44.1/tests/test_version.py0000644000175000017500000000036114465406650017235 0ustar00ihabunekihabunekimport toot from pkg_resources import get_distribution def test_version(): """Version specified in __version__ should be the same as the one specified in setup.py.""" assert toot.__version__ == get_distribution('toot').version ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1723457536.8502738 toot-0.44.1/tests/tui/0000755000175000017500000000000014656360001014727 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/tui/test_rich_text.py0000644000175000017500000000232214656344035020341 0ustar00ihabunekihabunekfrom urwid import Divider, Filler, Pile from toot.tui.richtext import url_to_widget from urwidgets import Hyperlink, TextEmbed from toot.tui.richtext.richtext import html_to_widgets def test_url_to_widget(): url = "http://foo.bar" embed_widget = url_to_widget(url) assert isinstance(embed_widget, TextEmbed) [(filler, length)] = embed_widget.embedded assert length == len(url) assert isinstance(filler, Filler) link_widget = filler.base_widget assert isinstance(link_widget, Hyperlink) assert link_widget.attrib == "link" assert link_widget.text == url assert link_widget.uri == url def test_html_to_widgets(): html = """

foo

foo bar baz

""".strip() [foo, divider, bar] = html_to_widgets(html) assert isinstance(foo, Pile) assert isinstance(divider, Divider) assert isinstance(bar, Pile) [(foo_embed, _)] = foo.contents assert foo_embed.embedded == [] assert foo_embed.attrib == [] assert foo_embed.text == "foo" [(bar_embed, _)] = bar.contents assert bar_embed.embedded == [] assert bar_embed.attrib == [(None, 4), ("b", 3), (None, 1), ("i", 3)] assert bar_embed.text == "foo bar baz" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/tests/utils.py0000644000175000017500000000132314656344035015650 0ustar00ihabunekihabunek""" Helpers for testing. """ import time from typing import Callable, TypeVar T = TypeVar("T") def run_with_retries(fn: Callable[..., T]) -> T: """ Run the the given function repeatedly until it finishes without raising an AssertionError. Sleep a bit between attempts. If the function doesn't succeed in the given number of tries raises the AssertionError. Used for tests which should eventually succeed. """ # Wait upto 6 seconds with incrementally longer sleeps delays = [0.1, 0.2, 0.3, 0.4, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5] for delay in delays: try: return fn() except AssertionError: time.sleep(delay) return fn() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.852274 toot-0.44.1/toot/0000755000175000017500000000000014656360001013751 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/__init__.py0000644000175000017500000000233514656344035016076 0ustar00ihabunekihabunekimport os import sys from os.path import join, expanduser from typing import NamedTuple from importlib import metadata try: __version__ = metadata.version("toot") except metadata.PackageNotFoundError: __version__ = "0.0.0" class App(NamedTuple): instance: str base_url: str client_id: str client_secret: str class User(NamedTuple): instance: str username: str access_token: str DEFAULT_INSTANCE = 'https://mastodon.social' CLIENT_NAME = 'toot - a Mastodon CLI client' CLIENT_WEBSITE = 'https://github.com/ihabunek/toot' TOOT_CONFIG_DIR_NAME = "toot" def get_config_dir(): """Returns the path to toot config directory""" # On Windows, store the config in roaming appdata if sys.platform == "win32" and "APPDATA" in os.environ: return join(os.getenv("APPDATA"), TOOT_CONFIG_DIR_NAME) # Respect XDG_CONFIG_HOME env variable if set # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if "XDG_CONFIG_HOME" in os.environ: config_home = expanduser(os.environ["XDG_CONFIG_HOME"]) return join(config_home, TOOT_CONFIG_DIR_NAME) # Default to ~/.config/toot/ return join(expanduser("~"), ".config", TOOT_CONFIG_DIR_NAME) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/__main__.py0000644000175000017500000000004014656344035016046 0ustar00ihabunekihabunekfrom toot.cli import cli cli() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723456714.0 toot-0.44.1/toot/api.py0000644000175000017500000005151314656356312015113 0ustar00ihabunekihabunekimport mimetypes import re import uuid from os import path from requests import Response from typing import BinaryIO, List, Optional from urllib.parse import urlparse, urlencode, quote from toot import App, User, http, CLIENT_NAME, CLIENT_WEBSITE from toot.exceptions import ConsoleError from toot.utils import drop_empty_values, str_bool, str_bool_nullable SCOPES = 'read write follow' def find_account(app, user, account_name): if not account_name: raise ConsoleError("Empty account name given") normalized_name = account_name.lstrip("@").lower() # Strip @ from accounts on the local instance. The `acct` # field in account object contains the qualified name for users of other # instances, but only the username for users of the local instance. This is # required in order to match the account name below. if "@" in normalized_name: [username, instance] = normalized_name.split("@", maxsplit=1) if instance == app.instance: normalized_name = username response = search(app, user, account_name, type="accounts", resolve=True) for account in response.json()["accounts"]: if account["acct"].lower() == normalized_name: return account raise ConsoleError("Account not found") def _account_action(app, user, account, action) -> Response: url = f"/api/v1/accounts/{account}/{action}" return http.post(app, user, url) def _status_action(app, user, status_id, action, data=None) -> Response: status_id = _resolve_status_id(app, user, status_id) url = f"/api/v1/statuses/{status_id}/{action}" return http.post(app, user, url, data=data) def _resolve_status_id(app, user, id_or_url) -> str: """ If given an URL instead of status ID, attempt to resolve the status ID. TODO: Not 100% sure this is the correct way of doing this, but it seems to work for all test cases I've thrown at it. So leaving it undocumented until we're happy it works. """ if re.match(r"^https?://", id_or_url): response = search(app, user, id_or_url, resolve=True, type="statuses") statuses = response.json().get("statuses") if not statuses: raise ConsoleError(f"Cannot find status matching URL {id_or_url}") if len(statuses) > 1: raise ConsoleError(f"Found multiple statuses mathcing URL {id_or_url}") return statuses[0]["id"] return id_or_url def _tag_action(app, user, tag_name, action) -> Response: url = f"/api/v1/tags/{tag_name}/{action}" return http.post(app, user, url) def create_app(base_url): url = f"{base_url}/api/v1/apps" json = { 'client_name': CLIENT_NAME, 'redirect_uris': 'urn:ietf:wg:oauth:2.0:oob', 'scopes': SCOPES, 'website': CLIENT_WEBSITE, } return http.anon_post(url, json=json).json() def get_muted_accounts(app, user): return http.get(app, user, "/api/v1/mutes").json() def get_blocked_accounts(app, user): return http.get(app, user, "/api/v1/blocks").json() def register_account(app, username, email, password, locale="en", agreement=True): """ Register an account https://docs.joinmastodon.org/methods/accounts/#create """ token = fetch_app_token(app)["access_token"] url = f"{app.base_url}/api/v1/accounts" headers = {"Authorization": f"Bearer {token}"} json = { "username": username, "email": email, "password": password, "agreement": agreement, "locale": locale } return http.anon_post(url, json=json, headers=headers).json() def update_account( app, user, display_name=None, note=None, avatar=None, header=None, bot=None, discoverable=None, locked=None, privacy=None, sensitive=None, language=None ): """ Update account credentials https://docs.joinmastodon.org/methods/accounts/#update_credentials """ files = drop_empty_values({"avatar": avatar, "header": header}) data = drop_empty_values({ "bot": str_bool_nullable(bot), "discoverable": str_bool_nullable(discoverable), "display_name": display_name, "locked": str_bool_nullable(locked), "note": note, "source[language]": language, "source[privacy]": privacy, "source[sensitive]": str_bool_nullable(sensitive), }) return http.patch(app, user, "/api/v1/accounts/update_credentials", files=files, data=data) def fetch_app_token(app): json = { "client_id": app.client_id, "client_secret": app.client_secret, "grant_type": "client_credentials", "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", "scope": "read write" } return http.anon_post(f"{app.base_url}/oauth/token", json=json).json() def login(app: App, username: str, password: str): url = app.base_url + '/oauth/token' data = { 'grant_type': 'password', 'client_id': app.client_id, 'client_secret': app.client_secret, 'username': username, 'password': password, 'scope': SCOPES, } return http.anon_post(url, data=data).json() def get_browser_login_url(app: App) -> str: """Returns the URL for manual log in via browser""" return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({ "response_type": "code", "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", "scope": SCOPES, "client_id": app.client_id, })) def request_access_token(app: App, authorization_code: str): url = app.base_url + '/oauth/token' data = { 'grant_type': 'authorization_code', 'client_id': app.client_id, 'client_secret': app.client_secret, 'code': authorization_code, 'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob', } return http.anon_post(url, data=data, allow_redirects=False).json() def post_status( app, user, status, visibility=None, media_ids=None, sensitive=False, spoiler_text=None, in_reply_to_id=None, language=None, scheduled_at=None, content_type=None, poll_options=None, poll_expires_in=None, poll_multiple=None, poll_hide_totals=None, ) -> Response: """ Publish a new status. https://docs.joinmastodon.org/methods/statuses/#create """ # Idempotency key assures the same status is not posted multiple times # if the request is retried. headers = {"Idempotency-Key": uuid.uuid4().hex} # Strip keys for which value is None # Sending null values doesn't bother Mastodon, but it breaks Pleroma data = drop_empty_values({ 'status': status, 'media_ids': media_ids, 'visibility': visibility, 'sensitive': sensitive, 'in_reply_to_id': in_reply_to_id, 'language': language, 'scheduled_at': scheduled_at, 'content_type': content_type, 'spoiler_text': spoiler_text, }) if poll_options: data["poll"] = { "options": poll_options, "expires_in": poll_expires_in, "multiple": poll_multiple, "hide_totals": poll_hide_totals, } return http.post(app, user, '/api/v1/statuses', json=data, headers=headers) def edit_status( app, user, id, status, visibility='public', media_ids=None, sensitive=False, spoiler_text=None, in_reply_to_id=None, language=None, content_type=None, poll_options=None, poll_expires_in=None, poll_multiple=None, poll_hide_totals=None, ) -> Response: """ Edit an existing status https://docs.joinmastodon.org/methods/statuses/#edit """ # Strip keys for which value is None # Sending null values doesn't bother Mastodon, but it breaks Pleroma data = drop_empty_values({ 'status': status, 'media_ids': media_ids, 'visibility': visibility, 'sensitive': sensitive, 'in_reply_to_id': in_reply_to_id, 'language': language, 'content_type': content_type, 'spoiler_text': spoiler_text, }) if poll_options: data["poll"] = { "options": poll_options, "expires_in": poll_expires_in, "multiple": poll_multiple, "hide_totals": poll_hide_totals, } return http.put(app, user, f"/api/v1/statuses/{id}", json=data) def fetch_status(app, user, id): """ Fetch a single status https://docs.joinmastodon.org/methods/statuses/#get """ return http.get(app, user, f"/api/v1/statuses/{id}") def fetch_status_source(app, user, id): """ Fetch the source (original text) for a single status. This only works on local toots. https://docs.joinmastodon.org/methods/statuses/#source """ return http.get(app, user, f"/api/v1/statuses/{id}/source") def scheduled_statuses(app, user): """ List scheduled statuses https://docs.joinmastodon.org/methods/scheduled_statuses/#get """ return http.get(app, user, "/api/v1/scheduled_statuses").json() def delete_status(app, user, status_id): """ Deletes a status with given ID. https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#deleting-a-status """ return http.delete(app, user, f"/api/v1/statuses/{status_id}") def favourite(app, user, status_id): return _status_action(app, user, status_id, 'favourite') def unfavourite(app, user, status_id): return _status_action(app, user, status_id, 'unfavourite') def reblog(app, user, status_id, visibility="public"): return _status_action(app, user, status_id, 'reblog', data={"visibility": visibility}) def unreblog(app, user, status_id): return _status_action(app, user, status_id, 'unreblog') def pin(app, user, status_id): return _status_action(app, user, status_id, 'pin') def unpin(app, user, status_id): return _status_action(app, user, status_id, 'unpin') def bookmark(app, user, status_id): return _status_action(app, user, status_id, 'bookmark') def unbookmark(app, user, status_id): return _status_action(app, user, status_id, 'unbookmark') def translate(app, user, status_id): return _status_action(app, user, status_id, 'translate') def context(app, user, status_id) -> Response: url = f"/api/v1/statuses/{status_id}/context" return http.get(app, user, url) def reblogged_by(app, user, status_id) -> Response: url = f"/api/v1/statuses/{status_id}/reblogged_by" return http.get(app, user, url) def get_timeline_generator( app: Optional[App], user: Optional[User], account: Optional[str] = None, list_id: Optional[str] = None, tag: Optional[str] = None, local: bool = False, public: bool = False, limit: int = 20, # TODO ): if public: return public_timeline_generator(app, user, local=local, limit=limit) elif tag: return tag_timeline_generator(app, user, tag, local=local, limit=limit) elif account: return account_timeline_generator(app, user, account, limit=limit) elif list_id: return timeline_list_generator(app, user, list_id, limit=limit) else: return home_timeline_generator(app, user, limit=limit) def _get_next_path(headers): """Given timeline response headers, returns the path to the next batch""" links = headers.get('Link', '') matches = re.match('<([^>]+)>; rel="next"', links) if matches: parsed = urlparse(matches.group(1)) return "?".join([parsed.path, parsed.query]) def _get_next_url(headers) -> Optional[str]: """Given timeline response headers, returns the url to the next batch""" links = headers.get('Link', '') match = re.match('<([^>]+)>; rel="next"', links) if match: return match.group(1) def _timeline_generator(app, user, path, params=None): while path: response = http.get(app, user, path, params) yield response.json() path = _get_next_path(response.headers) def _notification_timeline_generator(app, user, path, params=None): while path: response = http.get(app, user, path, params) notification = response.json() yield [n["status"] for n in notification if n["status"]] path = _get_next_path(response.headers) def _conversation_timeline_generator(app, user, path, params=None): while path: response = http.get(app, user, path, params) conversation = response.json() yield [c["last_status"] for c in conversation if c["last_status"]] path = _get_next_path(response.headers) def home_timeline_generator(app, user, limit=20): path = "/api/v1/timelines/home" params = {"limit": limit} return _timeline_generator(app, user, path, params) def public_timeline_generator(app, user, local=False, limit=20): path = '/api/v1/timelines/public' params = {'local': str_bool(local), 'limit': limit} return _timeline_generator(app, user, path, params) def tag_timeline_generator(app, user, hashtag, local=False, limit=20): path = f"/api/v1/timelines/tag/{quote(hashtag)}" params = {'local': str_bool(local), 'limit': limit} return _timeline_generator(app, user, path, params) def bookmark_timeline_generator(app, user, limit=20): path = '/api/v1/bookmarks' params = {'limit': limit} return _timeline_generator(app, user, path, params) def notification_timeline_generator(app, user, limit=20): # exclude all but mentions and statuses exclude_types = ["follow", "favourite", "reblog", "poll", "follow_request"] params = {"exclude_types[]": exclude_types, "limit": limit} return _notification_timeline_generator(app, user, "/api/v1/notifications", params) def conversation_timeline_generator(app, user, limit=20): path = "/api/v1/conversations" params = {"limit": limit} return _conversation_timeline_generator(app, user, path, params) def account_timeline_generator(app, user, account_name: str, replies=False, reblogs=False, limit=20): account = find_account(app, user, account_name) path = f"/api/v1/accounts/{account['id']}/statuses" params = {"limit": limit, "exclude_replies": not replies, "exclude_reblogs": not reblogs} return _timeline_generator(app, user, path, params) def timeline_list_generator(app, user, list_id, limit=20): path = f"/api/v1/timelines/list/{list_id}" return _timeline_generator(app, user, path, {'limit': limit}) def _anon_timeline_generator(url, params=None): while url: response = http.anon_get(url, params) yield response.json() url = _get_next_url(response.headers) def anon_public_timeline_generator(base_url, local=False, limit=20): query = urlencode({"local": str_bool(local), "limit": limit}) url = f"{base_url}/api/v1/timelines/public?{query}" return _anon_timeline_generator(url) def anon_tag_timeline_generator(base_url, hashtag, local=False, limit=20): query = urlencode({"local": str_bool(local), "limit": limit}) url = f"{base_url}/api/v1/timelines/tag/{quote(hashtag)}?{query}" return _anon_timeline_generator(url) def get_media(app: App, user: User, id: str): return http.get(app, user, f"/api/v1/media/{id}").json() def upload_media( app: App, user: User, media: BinaryIO, description: Optional[str] = None, thumbnail: Optional[BinaryIO] = None, ): data = drop_empty_values({"description": description}) # NB: Documentation says that "file" should provide a mime-type which we # don't do currently, but it works. files = drop_empty_values({ "file": media, "thumbnail": _add_mime_type(thumbnail) }) return http.post(app, user, "/api/v2/media", data=data, files=files) def _add_mime_type(file): if file is None: return None # TODO: mimetypes uses the file extension to guess the mime type which is # not always good enough (e.g. files without extension). python-magic could # be used instead but it requires adding it as a dependency. mime_type = mimetypes.guess_type(file.name) if not mime_type: raise ConsoleError(f"Unable guess mime type of '{file.name}'. " "Ensure the file has the desired extension.") filename = path.basename(file.name) return (filename, file, mime_type) def search(app, user, query, resolve=False, type=None): """ Perform a search. https://docs.joinmastodon.org/methods/search/#v2 """ params = drop_empty_values({ "q": query, "resolve": str_bool(resolve), "type": type }) return http.get(app, user, "/api/v2/search", params) def follow(app, user, account): return _account_action(app, user, account, 'follow') def unfollow(app, user, account): return _account_action(app, user, account, 'unfollow') def follow_tag(app, user, tag_name) -> Response: return _tag_action(app, user, tag_name, 'follow') def unfollow_tag(app, user, tag_name) -> Response: return _tag_action(app, user, tag_name, 'unfollow') def _get_response_list(app, user, path): items = [] while path: response = http.get(app, user, path) items += response.json() path = _get_next_path(response.headers) return items def following(app, user, account): path = f"/api/v1/accounts/{account}/following" return _get_response_list(app, user, path) def followers(app, user, account): path = f"/api/v1/accounts/{account}/followers" return _get_response_list(app, user, path) def followed_tags(app, user): path = '/api/v1/followed_tags' return _get_response_list(app, user, path) def featured_tags(app, user): return http.get(app, user, "/api/v1/featured_tags") def feature_tag(app, user, tag: str) -> Response: return http.post(app, user, "/api/v1/featured_tags", data={"name": tag}) def unfeature_tag(app, user, tag_id: str) -> Response: return http.delete(app, user, f"/api/v1/featured_tags/{tag_id}") def find_tag(app, user, tag) -> Optional[dict]: """Find a hashtag by tag name or ID""" tag = tag.lstrip("#") results = search(app, user, tag, type="hashtags").json() return next( ( t for t in results["hashtags"] if t["name"].lower() == tag.lstrip("#").lower() or t["id"] == tag ), None ) def find_featured_tag(app, user, tag) -> Optional[dict]: """Find a featured tag by tag name or ID""" return next( ( t for t in featured_tags(app, user).json() if t["name"].lower() == tag.lstrip("#").lower() or t["id"] == tag ), None ) def whois(app, user, account): return http.get(app, user, f'/api/v1/accounts/{account}').json() def vote(app, user, poll_id, choices: List[int]): url = f"/api/v1/polls/{poll_id}/votes" json = {'choices': choices} return http.post(app, user, url, json=json).json() def get_relationship(app, user, account): params = {"id[]": account} return http.get(app, user, '/api/v1/accounts/relationships', params).json()[0] def mute(app, user, account): return _account_action(app, user, account, 'mute') def unmute(app, user, account): return _account_action(app, user, account, 'unmute') def muted(app, user): return _get_response_list(app, user, "/api/v1/mutes") def block(app, user, account): return _account_action(app, user, account, 'block') def unblock(app, user, account): return _account_action(app, user, account, 'unblock') def blocked(app, user): return _get_response_list(app, user, "/api/v1/blocks") def verify_credentials(app, user) -> Response: return http.get(app, user, '/api/v1/accounts/verify_credentials') def get_notifications(app, user, types=[], exclude_types=[], limit=20): params = {"types[]": types, "exclude_types[]": exclude_types, "limit": limit} return http.get(app, user, '/api/v1/notifications', params).json() def clear_notifications(app, user): http.post(app, user, '/api/v1/notifications/clear') def get_instance(base_url: str) -> Response: url = f"{base_url}/api/v1/instance" return http.anon_get(url) def get_preferences(app, user) -> Response: return http.get(app, user, '/api/v1/preferences') def get_lists(app, user): return http.get(app, user, "/api/v1/lists").json() def get_list_accounts(app, user, list_id): path = f"/api/v1/lists/{list_id}/accounts" return _get_response_list(app, user, path) def create_list(app, user, title, replies_policy="none"): url = "/api/v1/lists" json = {'title': title} if replies_policy: json['replies_policy'] = replies_policy return http.post(app, user, url, json=json) def delete_list(app, user, id): return http.delete(app, user, f"/api/v1/lists/{id}") def add_accounts_to_list(app, user, list_id, account_ids): url = f"/api/v1/lists/{list_id}/accounts" json = {'account_ids': account_ids} return http.post(app, user, url, json=json) def remove_accounts_from_list(app, user, list_id, account_ids): url = f"/api/v1/lists/{list_id}/accounts" json = {'account_ids': account_ids} return http.delete(app, user, url, json=json) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/auth.py0000644000175000017500000000435314656344035015302 0ustar00ihabunekihabunekfrom toot import api, config, User, App from toot.entities import from_dict, Instance from toot.exceptions import ApiError, ConsoleError from urllib.parse import urlparse def find_instance(base_url: str) -> Instance: try: instance = api.get_instance(base_url).json() return from_dict(Instance, instance) except Exception: raise ConsoleError(f"Instance not found at {base_url}") def register_app(domain: str, base_url: str) -> App: try: response = api.create_app(base_url) except ApiError: raise ConsoleError("Registration failed.") app = App(domain, base_url, response['client_id'], response['client_secret']) config.save_app(app) return app def get_or_create_app(base_url: str) -> App: instance = find_instance(base_url) domain = _get_instance_domain(instance) return config.load_app(domain) or register_app(domain, base_url) def create_user(app: App, access_token: str) -> User: # Username is not yet known at this point, so fetch it from Mastodon user = User(app.instance, None, access_token) creds = api.verify_credentials(app, user).json() user = User(app.instance, creds["username"], access_token) config.save_user(user, activate=True) return user def login_username_password(app: App, email: str, password: str) -> User: try: response = api.login(app, email, password) except Exception: raise ConsoleError("Login failed") return create_user(app, response["access_token"]) def login_auth_code(app: App, authorization_code: str) -> User: try: response = api.request_access_token(app, authorization_code) except Exception: raise ConsoleError("Login failed") return create_user(app, response["access_token"]) def _get_instance_domain(instance: Instance) -> str: """Extracts the instance domain name. Pleroma and its forks return an actual URI here, rather than a domain name like Mastodon. This is contrary to the spec.¯ in that case, parse out the domain and return it. TODO: when updating to v2 instance endpoint, this field has been renamed to `domain` """ if instance.uri.startswith("http"): return urlparse(instance.uri).netloc return instance.uri ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.854274 toot-0.44.1/toot/cli/0000755000175000017500000000000014656360001014520 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/__init__.py0000644000175000017500000001257214656344035016651 0ustar00ihabunekihabunekimport click import logging import os import sys import typing as t from click.shell_completion import CompletionItem from click.types import StringParamType from functools import wraps from toot import App, User, config, __version__ from toot.output import print_warning from toot.settings import get_settings if t.TYPE_CHECKING: import typing_extensions as te P = te.ParamSpec("P") R = t.TypeVar("R") T = t.TypeVar("T") PRIVACY_CHOICES = ["public", "unlisted", "private"] VISIBILITY_CHOICES = ["public", "unlisted", "private", "direct"] IMAGE_FORMAT_CHOICES = ["block", "iterm", "kitty"] TUI_COLORS = { "1": 1, "16": 16, "88": 88, "256": 256, "16777216": 16777216, "24bit": 16777216, } TUI_COLORS_CHOICES = list(TUI_COLORS.keys()) TUI_COLORS_VALUES = list(TUI_COLORS.values()) DURATION_EXAMPLES = """e.g. "1 day", "2 hours 30 minutes", "5 minutes 30 seconds" or any combination of above. Shorthand: "1d", "2h30m", "5m30s\"""" def get_default_visibility() -> str: return os.getenv("TOOT_POST_VISIBILITY", "public") def get_default_map(): settings = get_settings() common = settings.get("common", {}) commands = settings.get("commands", {}) # TODO: remove in version 1.0 tui_old = settings.get("tui", {}).copy() if "palette" in tui_old: del tui_old["palette"] if tui_old: # TODO: don't show the warning for [toot.palette] print_warning("Settings section [tui] has been deprecated in favour of [commands.tui].") tui_new = commands.get("tui", {}) commands["tui"] = {**tui_old, **tui_new} return {**common, **commands} # Tweak the Click context # https://click.palletsprojects.com/en/8.1.x/api/#context CONTEXT = dict( # Enable using environment variables to set options auto_envvar_prefix="TOOT", # Add shorthand -h for invoking help help_option_names=["-h", "--help"], # Always show default values for options show_default=True, # Load command defaults from settings default_map=get_default_map(), ) class Context(t.NamedTuple): app: t.Optional[App] user: t.Optional[User] = None color: bool = False debug: bool = False class TootObj(t.NamedTuple): """Data to add to Click context""" color: bool = True debug: bool = False as_user: t.Optional[str] = None # Pass a context for testing purposes test_ctx: t.Optional[Context] = None class AccountParamType(StringParamType): """Custom type to add shell completion for account names""" name = "account" def shell_complete(self, ctx, param, incomplete: str): users = config.load_config()["users"].keys() return [ CompletionItem(u) for u in users if u.lower().startswith(incomplete.lower()) ] class InstanceParamType(StringParamType): """Custom type to add shell completion for instance domains""" name = "instance" def shell_complete(self, ctx, param, incomplete: str): apps = config.load_config()["apps"] return [ CompletionItem(i) for i in apps.keys() if i.lower().startswith(incomplete.lower()) ] def pass_context(f: "t.Callable[te.Concatenate[Context, P], R]") -> "t.Callable[P, R]": """Pass the toot Context as first argument.""" @wraps(f) def wrapped(*args: "P.args", **kwargs: "P.kwargs") -> R: return f(get_context(), *args, **kwargs) return wrapped def get_context() -> Context: click_context = click.get_current_context() obj: TootObj = click_context.obj # This is used to pass a context for testing, not used in normal usage if obj.test_ctx: return obj.test_ctx if obj.as_user: user, app = config.get_user_app(obj.as_user) if not user or not app: raise click.ClickException(f"Account '{obj.as_user}' not found. Run `toot auth` to see available accounts.") else: user, app = config.get_active_user_app() if not user or not app: raise click.ClickException("This command requires you to be logged in.") return Context(app, user, obj.color, obj.debug) json_option = click.option( "--json", is_flag=True, default=False, help="Print data as JSON rather than human readable text" ) @click.group(context_settings=CONTEXT) @click.option("-w", "--max-width", type=int, default=80, help="Maximum width for content rendered by toot") @click.option("--debug/--no-debug", default=False, help="Log debug info to stderr") @click.option("--color/--no-color", default=sys.stdout.isatty(), help="Use ANSI color in output") @click.option("--as", "as_user", type=AccountParamType(), help="The account to use, overrides the active account.") @click.version_option(__version__, message="%(prog)s v%(version)s") @click.pass_context def cli(ctx: click.Context, max_width: int, color: bool, debug: bool, as_user: str): """Toot is a Mastodon CLI""" ctx.obj = TootObj(color, debug, as_user) ctx.color = color ctx.max_content_width = max_width if debug: logging.basicConfig(level=logging.DEBUG) from toot.cli import accounts # noqa from toot.cli import auth # noqa from toot.cli import diag # noqa from toot.cli import lists # noqa from toot.cli import post # noqa from toot.cli import read # noqa from toot.cli import statuses # noqa from toot.cli import tags # noqa from toot.cli import timelines # noqa from toot.cli import tui # noqa ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/accounts.py0000644000175000017500000001547314656344035016734 0ustar00ihabunekihabunekimport click import json as pyjson from typing import BinaryIO, Optional from toot import api from toot.cli import PRIVACY_CHOICES, cli, json_option, Context, pass_context from toot.cli.validators import validate_language from toot.output import print_acct_list @cli.command(name="update_account") @click.option("--display-name", help="The display name to use for the profile.") @click.option("--note", help="The account bio.") @click.option( "--avatar", type=click.File(mode="rb"), help="Path to the avatar image to set.", ) @click.option( "--header", type=click.File(mode="rb"), help="Path to the header image to set.", ) @click.option( "--bot/--no-bot", default=None, help="Whether the account has a bot flag.", ) @click.option( "--discoverable/--no-discoverable", default=None, help="Whether the account should be shown in the profile directory.", ) @click.option( "--locked/--no-locked", default=None, help="Whether manual approval of follow requests is required.", ) @click.option( "--privacy", type=click.Choice(PRIVACY_CHOICES), help="Default post privacy for authored statuses.", ) @click.option( "--sensitive/--no-sensitive", default=None, help="Whether to mark authored statuses as sensitive by default.", ) @click.option( "--language", callback=validate_language, help="Default language to use for authored statuses (ISO 639-1).", ) @json_option @pass_context def update_account( ctx: Context, display_name: Optional[str], note: Optional[str], avatar: Optional[BinaryIO], header: Optional[BinaryIO], bot: Optional[bool], discoverable: Optional[bool], locked: Optional[bool], privacy: Optional[bool], sensitive: Optional[bool], language: Optional[bool], json: bool, ): """Update your account details""" options = [ avatar, bot, discoverable, display_name, header, language, locked, note, privacy, sensitive, ] if all(option is None for option in options): raise click.ClickException("Please specify at least one option to update the account") response = api.update_account( ctx.app, ctx.user, avatar=avatar, bot=bot, discoverable=discoverable, display_name=display_name, header=header, language=language, locked=locked, note=note, privacy=privacy, sensitive=sensitive, ) if json: click.echo(response.text) else: click.secho("✓ Account updated", fg="green") @cli.command() @click.argument("account") @json_option @pass_context def follow(ctx: Context, account: str, json: bool): """Follow an account""" found_account = api.find_account(ctx.app, ctx.user, account) response = api.follow(ctx.app, ctx.user, found_account["id"]) if json: click.echo(response.text) else: click.secho(f"✓ You are now following {account}", fg="green") @cli.command() @click.argument("account") @json_option @pass_context def unfollow(ctx: Context, account: str, json: bool): """Unfollow an account""" found_account = api.find_account(ctx.app, ctx.user, account) response = api.unfollow(ctx.app, ctx.user, found_account["id"]) if json: click.echo(response.text) else: click.secho(f"✓ You are no longer following {account}", fg="green") @cli.command() @click.argument("account", required=False) @json_option @pass_context def following(ctx: Context, account: Optional[str], json: bool): """List accounts followed by an account. If no account is given list accounts followed by you. """ account = account or ctx.user.username found_account = api.find_account(ctx.app, ctx.user, account) accounts = api.following(ctx.app, ctx.user, found_account["id"]) if json: click.echo(pyjson.dumps(accounts)) else: print_acct_list(accounts) @cli.command() @click.argument("account", required=False) @json_option @pass_context def followers(ctx: Context, account: Optional[str], json: bool): """List accounts following an account. If no account given list accounts following you.""" account = account or ctx.user.username found_account = api.find_account(ctx.app, ctx.user, account) accounts = api.followers(ctx.app, ctx.user, found_account["id"]) if json: click.echo(pyjson.dumps(accounts)) else: print_acct_list(accounts) @cli.command() @click.argument("account") @json_option @pass_context def mute(ctx: Context, account: str, json: bool): """Mute an account""" found_account = api.find_account(ctx.app, ctx.user, account) response = api.mute(ctx.app, ctx.user, found_account["id"]) if json: click.echo(response.text) else: click.secho(f"✓ You have muted {account}", fg="green") @cli.command() @click.argument("account") @json_option @pass_context def unmute(ctx: Context, account: str, json: bool): """Unmute an account""" found_account = api.find_account(ctx.app, ctx.user, account) response = api.unmute(ctx.app, ctx.user, found_account["id"]) if json: click.echo(response.text) else: click.secho(f"✓ {account} is no longer muted", fg="green") @cli.command() @json_option @pass_context def muted(ctx: Context, json: bool): """List muted accounts""" response = api.muted(ctx.app, ctx.user) if json: click.echo(pyjson.dumps(response)) else: if len(response) > 0: click.echo("Muted accounts:") print_acct_list(response) else: click.echo("No accounts muted") @cli.command() @click.argument("account") @json_option @pass_context def block(ctx: Context, account: str, json: bool): """Block an account""" found_account = api.find_account(ctx.app, ctx.user, account) response = api.block(ctx.app, ctx.user, found_account["id"]) if json: click.echo(response.text) else: click.secho(f"✓ You are now blocking {account}", fg="green") @cli.command() @click.argument("account") @json_option @pass_context def unblock(ctx: Context, account: str, json: bool): """Unblock an account""" found_account = api.find_account(ctx.app, ctx.user, account) response = api.unblock(ctx.app, ctx.user, found_account["id"]) if json: click.echo(response.text) else: click.secho(f"✓ {account} is no longer blocked", fg="green") @cli.command() @json_option @pass_context def blocked(ctx: Context, json: bool): """List blocked accounts""" response = api.blocked(ctx.app, ctx.user) if json: click.echo(pyjson.dumps(response)) else: if len(response) > 0: click.echo("Blocked accounts:") print_acct_list(response) else: click.echo("No accounts blocked") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/auth.py0000644000175000017500000001072714656344035016053 0ustar00ihabunekihabunekimport click import platform import sys import webbrowser from toot import api, config, __version__ from toot.auth import get_or_create_app, login_auth_code, login_username_password from toot.cli import AccountParamType, cli from toot.cli.validators import validate_instance instance_option = click.option( "--instance", "-i", "base_url", prompt="Enter instance URL", default="https://mastodon.social", callback=validate_instance, help="""Domain or base URL of the instance to log into, e.g. 'mastodon.social' or 'https://mastodon.social'""", ) @cli.command() def auth(): """Show logged in accounts and instances""" config_data = config.load_config() if not config_data["users"]: click.echo("You are not logged in to any accounts") return active_user = config_data["active_user"] click.echo("Authenticated accounts:") for uid, u in config_data["users"].items(): active_label = "ACTIVE" if active_user == uid else "" uid = click.style(uid, fg="green") active_label = click.style(active_label, fg="yellow") click.echo(f"* {uid} {active_label}") path = config.get_config_file_path() path = click.style(path, "blue") click.echo(f"\nAuth tokens are stored in: {path}") @cli.command() def env(): """Print environment information for inclusion in bug reports.""" click.echo(f"toot {__version__}") click.echo(f"Python {sys.version}") click.echo(platform.platform()) @cli.command(name="login_cli") @instance_option @click.option("--email", "-e", help="Email address to log in with", prompt=True) @click.option("--password", "-p", hidden=True, prompt=True, hide_input=True) def login_cli(base_url: str, email: str, password: str): """ Log into an instance from the console (not recommended) Does NOT support two factor authentication, may not work on instances other than Mastodon, mostly useful for scripting. """ app = get_or_create_app(base_url) login_username_password(app, email, password) click.secho("✓ Successfully logged in.", fg="green") click.echo("Access token saved to config at: ", nl=False) click.secho(config.get_config_file_path(), fg="green") LOGIN_EXPLANATION = """This authentication method requires you to log into your Mastodon instance in your browser, where you will be asked to authorize toot to access your account. When you do, you will be given an authorization code which you need to paste here.""".replace("\n", " ") @cli.command() @instance_option def login(base_url: str): """Log into an instance using your browser (recommended)""" app = get_or_create_app(base_url) url = api.get_browser_login_url(app) click.echo(click.wrap_text(LOGIN_EXPLANATION)) click.echo("\nLogin URL:") click.echo(url) yesno = click.prompt("Open link in default browser? [Y/n]", default="Y", show_default=False) if not yesno or yesno.lower() == 'y': webbrowser.open(url) authorization_code = "" while not authorization_code: authorization_code = click.prompt("Authorization code") login_auth_code(app, authorization_code) click.echo() click.secho("✓ Successfully logged in.", fg="green") @cli.command() @click.argument("account", type=AccountParamType(), required=False) def logout(account: str): """Log out of ACCOUNT, delete stored access keys""" accounts = _get_accounts_list() if not account: raise click.ClickException(f"Specify account to log out:\n{accounts}") user = config.load_user(account) if not user: raise click.ClickException(f"Account not found. Logged in accounts:\n{accounts}") config.delete_user(user) click.secho(f"✓ Account {account} logged out", fg="green") @cli.command() @click.argument("account", type=AccountParamType(), required=False) def activate(account: str): """Switch to logged in ACCOUNT.""" accounts = _get_accounts_list() if not account: raise click.ClickException(f"Specify account to activate:\n{accounts}") user = config.load_user(account) if not user: raise click.ClickException(f"Account not found. Logged in accounts:\n{accounts}") config.activate_user(user) click.secho(f"✓ Account {account} activated", fg="green") def _get_accounts_list() -> str: accounts = config.load_config()["users"].keys() if not accounts: raise click.ClickException("You're not logged into any accounts") return "\n".join([f"* {acct}" for acct in accounts]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/diag.py0000644000175000017500000000141114656344035016004 0ustar00ihabunekihabunekfrom typing import Optional import click from toot import api, config from toot.entities import Data from toot.output import print_diags from toot.cli import cli @cli.command() @click.option( "-f", "--files", is_flag=True, help="Print contents of the config and settings files in diagnostic output", ) @click.option( "-s", "--server", is_flag=True, help="Print information about the curren server in diagnostic output", ) def diag(files: bool, server: bool): """Display useful information for diagnosing problems""" instance_dict: Optional[Data] = None if server: _, app = config.get_active_user_app() if app: instance_dict = api.get_instance(app.base_url).json() print_diags(instance_dict, files) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/lists.py0000644000175000017500000002076014656344035016246 0ustar00ihabunekihabunekimport click import json as pyjson from toot import api, config from toot.cli import Context, cli, pass_context, json_option from toot.entities import from_dict_list, List from toot.output import print_list_accounts, print_lists, print_warning @cli.group(invoke_without_command=True) @click.pass_context def lists(ctx: click.Context): """Display and manage lists""" if ctx.invoked_subcommand is None: print_warning("`toot lists` is deprecated in favour of `toot lists list`.\n" + "Run `toot lists -h` to see other list-related commands.") user, app = config.get_active_user_app() if not user or not app: raise click.ClickException("This command requires you to be logged in.") data = api.get_lists(app, user) lists = from_dict_list(List, data) if lists: print_lists(lists) else: click.echo("You have no lists defined.") @lists.command() @json_option @pass_context def list(ctx: Context, json: bool): """List all your lists""" data = api.get_lists(ctx.app, ctx.user) if json: click.echo(pyjson.dumps(data)) else: if data: lists = from_dict_list(List, data) print_lists(lists) else: click.echo("You have no lists defined.") @lists.command() @click.argument("title", required=False) @click.option("--id", help="List ID if not title is given") @json_option @pass_context def accounts(ctx: Context, title: str, id: str, json: bool): """List the accounts in a list""" list_id = _get_list_id(ctx, title, id) response = api.get_list_accounts(ctx.app, ctx.user, list_id) if json: click.echo(pyjson.dumps(response)) else: print_list_accounts(response) @lists.command() @click.argument("title") @click.option( "--replies-policy", type=click.Choice(["followed", "list", "none"]), default="none", help="Replies policy" ) @json_option @pass_context def create(ctx: Context, title: str, replies_policy: str, json: bool): """Create a list""" response = api.create_list(ctx.app, ctx.user, title=title, replies_policy=replies_policy) if json: print(response.text) else: click.secho(f"✓ List \"{title}\" created.", fg="green") @lists.command() @click.argument("title", required=False) @click.option("--id", help="List ID if not title is given") @json_option @pass_context def delete(ctx: Context, title: str, id: str, json: bool): """Delete a list""" list_id = _get_list_id(ctx, title, id) response = api.delete_list(ctx.app, ctx.user, list_id) if json: click.echo(response.text) else: click.secho(f"✓ List \"{title if title else id}\" deleted.", fg="green") @lists.command() @click.argument("title", required=False) @click.argument("account") @click.option("--id", help="List ID if not title is given") @json_option @pass_context def add(ctx: Context, title: str, account: str, id: str, json: bool): """Add an account to a list""" list_id = _get_list_id(ctx, title, id) found_account = api.find_account(ctx.app, ctx.user, account) try: response = api.add_accounts_to_list(ctx.app, ctx.user, list_id, [found_account["id"]]) if json: click.echo(response.text) else: click.secho(f"✓ Added account \"{account}\"", fg="green") except Exception: # TODO: this is slow, improve # if we failed to add the account, try to give a # more specific error message than "record not found" my_accounts = api.followers(ctx.app, ctx.user, found_account["id"]) found = False if my_accounts: for my_account in my_accounts: if my_account["id"] == found_account["id"]: found = True break if found is False: raise click.ClickException(f"You must follow @{account} before adding this account to a list.") raise @lists.command() @click.argument("title", required=False) @click.argument("account") @click.option("--id", help="List ID if not title is given") @json_option @pass_context def remove(ctx: Context, title: str, account: str, id: str, json: bool): """Remove an account from a list""" list_id = _get_list_id(ctx, title, id) found_account = api.find_account(ctx.app, ctx.user, account) response = api.remove_accounts_from_list(ctx.app, ctx.user, list_id, [found_account["id"]]) if json: click.echo(response.text) else: click.secho(f"✓ Removed account \"{account}\"", fg="green") # -- Deprecated commands ------------------------------------------------------- @cli.command(name="list_accounts", hidden=True) @click.argument("title", required=False) @click.option("--id", help="List ID if not title is given") @pass_context def list_accounts(ctx: Context, title: str, id: str): """List the accounts in a list""" print_warning("`toot list_accounts` is deprecated in favour of `toot lists accounts`") list_id = _get_list_id(ctx, title, id) response = api.get_list_accounts(ctx.app, ctx.user, list_id) print_list_accounts(response) @cli.command(name="list_create", hidden=True) @click.argument("title") @click.option( "--replies-policy", type=click.Choice(["followed", "list", "none"]), default="none", help="Replies policy" ) @pass_context def list_create(ctx: Context, title: str, replies_policy: str): """Create a list""" print_warning("`toot list_create` is deprecated in favour of `toot lists create`") api.create_list(ctx.app, ctx.user, title=title, replies_policy=replies_policy) click.secho(f"✓ List \"{title}\" created.", fg="green") @cli.command(name="list_delete", hidden=True) @click.argument("title", required=False) @click.option("--id", help="List ID if not title is given") @pass_context def list_delete(ctx: Context, title: str, id: str): """Delete a list""" print_warning("`toot list_delete` is deprecated in favour of `toot lists delete`") list_id = _get_list_id(ctx, title, id) api.delete_list(ctx.app, ctx.user, list_id) click.secho(f"✓ List \"{title if title else id}\" deleted.", fg="green") @cli.command(name="list_add", hidden=True) @click.argument("title", required=False) @click.argument("account") @click.option("--id", help="List ID if not title is given") @pass_context def list_add(ctx: Context, title: str, account: str, id: str): """Add an account to a list""" print_warning("`toot list_add` is deprecated in favour of `toot lists add`") list_id = _get_list_id(ctx, title, id) found_account = api.find_account(ctx.app, ctx.user, account) try: api.add_accounts_to_list(ctx.app, ctx.user, list_id, [found_account["id"]]) except Exception: # if we failed to add the account, try to give a # more specific error message than "record not found" my_accounts = api.followers(ctx.app, ctx.user, found_account["id"]) found = False if my_accounts: for my_account in my_accounts: if my_account["id"] == found_account["id"]: found = True break if found is False: raise click.ClickException(f"You must follow @{account} before adding this account to a list.") raise click.secho(f"✓ Added account \"{account}\"", fg="green") @cli.command(name="list_remove", hidden=True) @click.argument("title", required=False) @click.argument("account") @click.option("--id", help="List ID if not title is given") @pass_context def list_remove(ctx: Context, title: str, account: str, id: str): """Remove an account from a list""" print_warning("`toot list_remove` is deprecated in favour of `toot lists remove`") list_id = _get_list_id(ctx, title, id) found_account = api.find_account(ctx.app, ctx.user, account) api.remove_accounts_from_list(ctx.app, ctx.user, list_id, [found_account["id"]]) click.secho(f"✓ Removed account \"{account}\"", fg="green") def _get_list_id(ctx: Context, title, list_id): if not list_id and not title: raise click.ClickException("Please specify list title or ID") lists = api.get_lists(ctx.app, ctx.user) matched_ids = [ list["id"] for list in lists if list["title"].lower() == title.lower() or list["id"] == list_id ] if not matched_ids: raise click.ClickException("List not found") if len(matched_ids) > 1: raise click.ClickException("Found multiple lists with the same title, please specify the ID instead") return matched_ids[0] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/post.py0000644000175000017500000002152514656344035016075 0ustar00ihabunekihabunekimport click import os import sys from datetime import datetime, timedelta, timezone from time import sleep, time from typing import BinaryIO, Optional, Tuple from toot import api, config from toot.cli import AccountParamType, cli, json_option, pass_context, Context from toot.cli import DURATION_EXAMPLES, VISIBILITY_CHOICES from toot.tui.constants import VISIBILITY_OPTIONS # move to top-level ? from toot.cli.validators import validate_duration, validate_language from toot.entities import MediaAttachment, from_dict from toot.utils import EOF_KEY, delete_tmp_status_file, editor_input, multiline_input from toot.utils.datetime import parse_datetime @cli.command() @click.argument("text", required=False) @click.option( "--media", "-m", help="""Path to media file to attach, can be used multiple times to attach multiple files.""", type=click.File(mode="rb"), multiple=True ) @click.option( "--description", "-d", "descriptions", help="""Plain-text description of the media for accessibility purposes, one per attached media""", multiple=True, ) @click.option( "--thumbnail", "thumbnails", help="Path to an image file to serve as media thumbnail, one per attached media", type=click.File(mode="rb"), multiple=True ) @click.option( "--visibility", "-v", help="Post visibility: " + "; " .join("{} = {}".format(visibility, description) for visibility, caption, description in VISIBILITY_OPTIONS), default=VISIBILITY_CHOICES[0], type=click.Choice(VISIBILITY_CHOICES), ) @click.option( "--sensitive", "-s", help="Mark status and attached media as sensitive", default=False, is_flag=True, ) @click.option( "--spoiler-text", "-p", help="Text to be shown as a warning or subject before the actual content.", ) @click.option( "--reply-to", "-r", help="ID of the status being replied to, if status is a reply.", ) @click.option( "--language", "-l", help="ISO 639-1 language code of the toot, to skip automatic detection.", callback=validate_language, ) @click.option( "--editor", "-e", is_flag=False, flag_value=os.getenv("EDITOR"), help="""Specify an editor to compose your toot. When used without a value it will use the editor defined in the $EDITOR environment variable.""", ) @click.option( "--scheduled-at", help="""ISO 8601 Datetime at which to schedule a status. Must be at least 5 minutes in the future.""", ) @click.option( "--scheduled-in", help=f"""Schedule the toot to be posted after a given amount of time, {DURATION_EXAMPLES}. Must be at least 5 minutes.""", callback=validate_duration, ) @click.option( "--content-type", "-t", help="MIME type for the status text (not supported on all instances)", ) @click.option( "--poll-option", help="Possible answer to the poll, can be given multiple times.", multiple=True, ) @click.option( "--poll-expires-in", help=f"Duration that the poll should be open, {DURATION_EXAMPLES}", callback=validate_duration, default="24h", ) @click.option( "--poll-multiple", help="Allow multiple answers to be selected.", is_flag=True, default=False, ) @click.option( "--poll-hide-totals", help="Hide vote counts until the poll ends.", is_flag=True, default=False, ) @click.option( "-u", "--using", type=AccountParamType(), help="The account to use, overrides the active account.", ) @json_option @pass_context def post( ctx: Context, text: Optional[str], media: Tuple[str], descriptions: Tuple[str], thumbnails: Tuple[str], visibility: Optional[str], sensitive: bool, spoiler_text: Optional[str], reply_to: Optional[str], language: Optional[str], editor: Optional[str], scheduled_at: Optional[str], scheduled_in: Optional[int], content_type: Optional[str], poll_option: Tuple[str], poll_expires_in: int, poll_multiple: bool, poll_hide_totals: bool, json: bool, using: str ): """Post a new status""" if len(media) > 4: raise click.ClickException("Cannot attach more than 4 files.") if using: user, app = config.get_user_app(using) if not user or not app: raise click.ClickException(f"Account '{using}' not found. Run `toot auth` to see available accounts.") else: user, app = ctx.user, ctx.app media_ids = _upload_media(app, user, media, descriptions, thumbnails) status_text = _get_status_text(text, editor, media) scheduled_at = _get_scheduled_at(scheduled_at, scheduled_in) if not status_text and not media_ids: raise click.ClickException("You must specify either text or media to post.") response = api.post_status( app, user, status_text, visibility=visibility, media_ids=media_ids, sensitive=sensitive, spoiler_text=spoiler_text, in_reply_to_id=reply_to, language=language, scheduled_at=scheduled_at, content_type=content_type, poll_options=poll_option, poll_expires_in=poll_expires_in, poll_multiple=poll_multiple, poll_hide_totals=poll_hide_totals, ) if json: click.echo(response.text) else: status = response.json() if "scheduled_at" in status: scheduled_at = parse_datetime(status["scheduled_at"]) scheduled_at = datetime.strftime(scheduled_at, "%Y-%m-%d %H:%M:%S%z") click.echo(f"Toot scheduled for: {scheduled_at}") else: click.echo(f"Toot posted: {status['url']}") delete_tmp_status_file() @cli.command() @click.argument("file", type=click.File(mode="rb")) @click.option( "--description", "-d", help="Plain-text description of the media for accessibility purposes" ) @json_option @pass_context def upload( ctx: Context, file: BinaryIO, description: Optional[str], json: bool, ): """Upload an image or video file This is probably not very useful, see `toot post --media` instead. """ response = _do_upload(ctx.app, ctx.user, file, description, None) if json: click.echo(response.text) else: media = from_dict(MediaAttachment, response.json()) click.echo() click.echo(f"Successfully uploaded media ID {media.id}, type '{media.type}'") click.echo(f"URL: {media.url}") click.echo(f"Preview URL: {media.preview_url}") def _get_status_text(text, editor, media): isatty = sys.stdin.isatty() if not text and not isatty: text = sys.stdin.read().rstrip() if isatty: if editor: text = editor_input(editor, text) elif not text and not media: click.echo(f"Write or paste your toot. Press {EOF_KEY} to post it.") text = multiline_input() return text def _get_scheduled_at(scheduled_at, scheduled_in): if scheduled_at: return scheduled_at if scheduled_in: scheduled_at = datetime.now(timezone.utc) + timedelta(seconds=scheduled_in) return scheduled_at.replace(microsecond=0).isoformat() return None def _upload_media(app, user, media, descriptions, thumbnails): # Match media to corresponding descriptions and thumbnail media = media or [] descriptions = descriptions or [] thumbnails = thumbnails or [] uploaded_media = [] for idx, file in enumerate(media): description = descriptions[idx].strip() if idx < len(descriptions) else None thumbnail = thumbnails[idx] if idx < len(thumbnails) else None result = _do_upload(app, user, file, description, thumbnail).json() uploaded_media.append(result) _wait_until_all_processed(app, user, uploaded_media) return [m["id"] for m in uploaded_media] def _do_upload(app, user, file, description, thumbnail): return api.upload_media(app, user, file, description=description, thumbnail=thumbnail) def _wait_until_all_processed(app, user, uploaded_media): """ Media is uploaded asynchronously, and cannot be attached until the server has finished processing it. This function waits for that to happen. Once media is processed, it will have the URL populated. """ if all(m["url"] for m in uploaded_media): return # Timeout after waiting 1 minute start_time = time() timeout = 60 click.echo("Waiting for media to finish processing...") for media in uploaded_media: _wait_until_processed(app, user, media, start_time, timeout) def _wait_until_processed(app, user, media, start_time, timeout): if media["url"]: return media = api.get_media(app, user, media["id"]) while not media["url"]: sleep(1) if time() > start_time + timeout: raise click.ClickException(f"Media not processed by server after {timeout} seconds. Aborting.") media = api.get_media(app, user, media["id"]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/read.py0000644000175000017500000000700214656344035016015 0ustar00ihabunekihabunekimport click import json as pyjson from itertools import chain from typing import Optional from toot import api from toot.cli.validators import validate_instance from toot.entities import Instance, Status, from_dict, Account from toot.exceptions import ApiError, ConsoleError from toot.output import print_account, print_instance, print_search_results, print_status, print_timeline from toot.cli import InstanceParamType, cli, get_context, json_option, pass_context, Context @cli.command() @json_option @pass_context def whoami(ctx: Context, json: bool): """Display logged in user details""" response = api.verify_credentials(ctx.app, ctx.user) if json: click.echo(response.text) else: account = from_dict(Account, response.json()) print_account(account) @cli.command() @click.argument("account") @json_option @pass_context def whois(ctx: Context, account: str, json: bool): """Display account details""" account_dict = api.find_account(ctx.app, ctx.user, account) # Here it's not possible to avoid parsing json since it's needed to find the account. if json: click.echo(pyjson.dumps(account_dict)) else: account_obj = from_dict(Account, account_dict) print_account(account_obj) @cli.command() @click.argument("instance", type=InstanceParamType(), callback=validate_instance, required=False) @json_option def instance(instance: Optional[str], json: bool): """Display instance details INSTANCE can be a domain or base URL of the instance to display. e.g. 'mastodon.social' or 'https://mastodon.social'. If not given will display details for the currently logged in instance. """ if not instance: context = get_context() if not context.app: raise click.ClickException("INSTANCE argument not given and not logged in") instance = context.app.base_url try: response = api.get_instance(instance) except ApiError: raise ConsoleError( f"Instance not found at {instance}.\n" + "The given domain probably does not host a Mastodon instance." ) if json: click.echo(response.text) else: print_instance(from_dict(Instance, response.json())) @cli.command() @click.argument("query") @click.option("-r", "--resolve", is_flag=True, help="Resolve non-local accounts") @json_option @pass_context def search(ctx: Context, query: str, resolve: bool, json: bool): """Search for users or hashtags""" response = api.search(ctx.app, ctx.user, query, resolve) if json: click.echo(response.text) else: print_search_results(response.json()) @cli.command() @click.argument("status_id") @json_option @pass_context def status(ctx: Context, status_id: str, json: bool): """Show a single status""" response = api.fetch_status(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: status = from_dict(Status, response.json()) print_status(status) @cli.command() @click.argument("status_id") @json_option @pass_context def thread(ctx: Context, status_id: str, json: bool): """Show thread for a toot.""" context_response = api.context(ctx.app, ctx.user, status_id) if json: click.echo(context_response.text) else: toot = api.fetch_status(ctx.app, ctx.user, status_id).json() context = context_response.json() statuses = chain(context["ancestors"], [toot], context["descendants"]) print_timeline(from_dict(Status, s) for s in statuses) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457097.0 toot-0.44.1/toot/cli/statuses.py0000644000175000017500000000746114656357111016765 0ustar00ihabunekihabunekimport click from toot import api from toot.cli import cli, json_option, Context, pass_context from toot.cli import VISIBILITY_CHOICES from toot.output import print_table @cli.command() @click.argument("status_id") @json_option @pass_context def delete(ctx: Context, status_id: str, json: bool): """Delete a status""" response = api.delete_status(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status deleted", fg="green") @cli.command() @click.argument("status_id") @json_option @pass_context def favourite(ctx: Context, status_id: str, json: bool): """Favourite a status""" response = api.favourite(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status favourited", fg="green") @cli.command() @click.argument("status_id") @json_option @pass_context def unfavourite(ctx: Context, status_id: str, json: bool): """Unfavourite a status""" response = api.unfavourite(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status unfavourited", fg="green") @cli.command() @click.argument("status_id") @click.option( "--visibility", "-v", help="Post visibility", type=click.Choice(VISIBILITY_CHOICES), default="public", ) @json_option @pass_context def reblog(ctx: Context, status_id: str, visibility: str, json: bool): """Reblog (boost) a status""" response = api.reblog(ctx.app, ctx.user, status_id, visibility=visibility) if json: click.echo(response.text) else: click.secho("✓ Status reblogged", fg="green") @cli.command() @click.argument("status_id") @json_option @pass_context def unreblog(ctx: Context, status_id: str, json: bool): """Unreblog (unboost) a status""" response = api.unreblog(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status unreblogged", fg="green") @cli.command() @click.argument("status_id") @json_option @pass_context def pin(ctx: Context, status_id: str, json: bool): """Pin a status""" response = api.pin(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status pinned", fg="green") @cli.command() @click.argument("status_id") @json_option @pass_context def unpin(ctx: Context, status_id: str, json: bool): """Unpin a status""" response = api.unpin(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status unpinned", fg="green") @cli.command() @click.argument("status_id") @json_option @pass_context def bookmark(ctx: Context, status_id: str, json: bool): """Bookmark a status""" response = api.bookmark(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status bookmarked", fg="green") @cli.command() @click.argument("status_id") @json_option @pass_context def unbookmark(ctx: Context, status_id: str, json: bool): """Unbookmark a status""" response = api.unbookmark(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: click.secho("✓ Status unbookmarked", fg="green") @cli.command(name="reblogged_by") @click.argument("status_id") @json_option @pass_context def reblogged_by(ctx: Context, status_id: str, json: bool): """Show accounts that reblogged a status""" response = api.reblogged_by(ctx.app, ctx.user, status_id) if json: click.echo(response.text) else: rows = [[a["acct"], a["display_name"]] for a in response.json()] if rows: headers = ["Account", "Display name"] print_table(headers, rows) else: click.echo("This status is not reblogged by anyone") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/tags.py0000644000175000017500000001057314656344035016047 0ustar00ihabunekihabunekimport click import json as pyjson from toot import api from toot.cli import cli, pass_context, json_option, Context from toot.entities import Tag, from_dict from toot.output import print_tag_list, print_warning @cli.group() def tags(): """List, follow, and unfollow tags""" @tags.command() @click.argument("tag") @json_option @pass_context def info(ctx: Context, tag, json: bool): """Show a hashtag and its associated information""" tag = api.find_tag(ctx.app, ctx.user, tag) if not tag: raise click.ClickException("Tag not found") if json: click.echo(pyjson.dumps(tag)) else: tag = from_dict(Tag, tag) click.secho(f"#{tag.name}", fg="yellow") click.secho(tag.url, italic=True) if tag.following: click.echo("Followed") else: click.echo("Not followed") @tags.command() @json_option @pass_context def followed(ctx: Context, json: bool): """List followed tags""" tags = api.followed_tags(ctx.app, ctx.user) if json: click.echo(pyjson.dumps(tags)) else: if tags: print_tag_list(tags) else: click.echo("You're not following any hashtags") @tags.command() @click.argument("tag") @json_option @pass_context def follow(ctx: Context, tag: str, json: bool): """Follow a hashtag""" tag = tag.lstrip("#") response = api.follow_tag(ctx.app, ctx.user, tag) if json: click.echo(response.text) else: click.secho(f"✓ You are now following #{tag}", fg="green") @tags.command() @click.argument("tag") @json_option @pass_context def unfollow(ctx: Context, tag: str, json: bool): """Unfollow a hashtag""" tag = tag.lstrip("#") response = api.unfollow_tag(ctx.app, ctx.user, tag) if json: click.echo(response.text) else: click.secho(f"✓ You are no longer following #{tag}", fg="green") @tags.command() @json_option @pass_context def featured(ctx: Context, json: bool): """List hashtags featured on your profile.""" response = api.featured_tags(ctx.app, ctx.user) if json: click.echo(response.text) else: tags = response.json() if tags: print_tag_list(tags) else: click.echo("You don't have any featured hashtags") @tags.command() @click.argument("tag") @json_option @pass_context def feature(ctx: Context, tag: str, json: bool): """Feature a hashtag on your profile""" tag = tag.lstrip("#") response = api.feature_tag(ctx.app, ctx.user, tag) if json: click.echo(response.text) else: click.secho(f"✓ Tag #{tag} is now featured", fg="green") @tags.command() @click.argument("tag") @json_option @pass_context def unfeature(ctx: Context, tag: str, json: bool): """Unfollow a hashtag TAG can either be a tag name like "#foo" or "foo" or a tag ID. """ featured_tag = api.find_featured_tag(ctx.app, ctx.user, tag) # TODO: should this be idempotent? if not featured_tag: raise click.ClickException(f"Tag {tag} is not featured") response = api.unfeature_tag(ctx.app, ctx.user, featured_tag["id"]) if json: click.echo(response.text) else: click.secho(f"✓ Tag #{featured_tag['name']} is no longer featured", fg="green") # -- Deprecated commands ------------------------------------------------------- @cli.command(name="tags_followed", hidden=True) @pass_context def tags_followed(ctx: Context): """List hashtags you follow""" print_warning("`toot tags_followed` is deprecated in favour of `toot tags followed`") response = api.followed_tags(ctx.app, ctx.user) print_tag_list(response) @cli.command(name="tags_follow", hidden=True) @click.argument("tag") @pass_context def tags_follow(ctx: Context, tag: str): """Follow a hashtag""" print_warning("`toot tags_follow` is deprecated in favour of `toot tags follow`") tag = tag.lstrip("#") api.follow_tag(ctx.app, ctx.user, tag) click.secho(f"✓ You are now following #{tag}", fg="green") @cli.command(name="tags_unfollow", hidden=True) @click.argument("tag") @pass_context def tags_unfollow(ctx: Context, tag: str): """Unfollow a hashtag""" print_warning("`toot tags_unfollow` is deprecated in favour of `toot tags unfollow`") tag = tag.lstrip("#") api.unfollow_tag(ctx.app, ctx.user, tag) click.secho(f"✓ You are no longer following #{tag}", fg="green") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/timelines.py0000644000175000017500000001233314656344035017076 0ustar00ihabunekihabunekimport sys import click from toot import api from toot.cli import InstanceParamType, cli, get_context, pass_context, Context from typing import Optional from toot.cli.validators import validate_instance from toot.entities import Notification, Status, from_dict from toot.output import print_notifications, print_timeline @cli.command() @click.option( "--instance", "-i", type=InstanceParamType(), callback=validate_instance, help="""Domain or base URL of the instance from which to read, e.g. 'mastodon.social' or 'https://mastodon.social'""", ) @click.option("--account", "-a", help="Show account timeline") @click.option("--list", help="Show list timeline") @click.option("--tag", "-t", help="Show hashtag timeline") @click.option("--public", "-p", is_flag=True, help="Show public timeline") @click.option( "--local", "-l", is_flag=True, help="Show only statuses from the local instance (public and tag timelines only)" ) @click.option( "--reverse", "-r", is_flag=True, help="Reverse the order of the shown timeline (new posts at the bottom)" ) @click.option( "--once", "-1", is_flag=True, help="Only show the first toots, do not prompt to continue" ) @click.option( "--count", "-c", type=int, default=10, help="Number of posts per page (max 20)" ) def timeline( instance: Optional[str], account: Optional[str], list: Optional[str], tag: Optional[str], public: bool, local: bool, reverse: bool, once: bool, count: int, ): """Show recent items in a timeline By default shows the home timeline. """ if len([arg for arg in [tag, list, public, account] if arg]) > 1: raise click.ClickException("Only one of --public, --tag, --account, or --list can be used at one time.") if local and not (public or tag): raise click.ClickException("The --local option is only valid alongside --public or --tag.") if instance and not (public or tag): raise click.ClickException("The --instance option is only valid alongside --public or --tag.") if public and instance: generator = api.anon_public_timeline_generator(instance, local, count) elif tag and instance: generator = api.anon_tag_timeline_generator(instance, tag, local, count) else: ctx = get_context() list_id = _get_list_id(ctx, list) """Show recent statuses in a timeline""" generator = api.get_timeline_generator( ctx.app, ctx.user, account=account, list_id=list_id, tag=tag, public=public, local=local, limit=count, ) _show_timeline(generator, reverse, once) @cli.command() @click.option( "--reverse", "-r", is_flag=True, help="Reverse the order of the shown timeline (new posts at the bottom)" ) @click.option( "--once", "-1", is_flag=True, help="Only show the first toots, do not prompt to continue" ) @click.option( "--count", "-c", type=int, default=10, help="Number of posts per page (max 20)" ) @pass_context def bookmarks( ctx: Context, reverse: bool, once: bool, count: int, ): """Show recent statuses in a timeline""" generator = api.bookmark_timeline_generator(ctx.app, ctx.user, limit=count) _show_timeline(generator, reverse, once) @cli.command() @click.option( "--clear", is_flag=True, help="Dismiss all notifications and exit" ) @click.option( "--reverse", "-r", is_flag=True, help="Reverse the order of the shown notifications (newest on top)" ) @click.option( "--mentions", "-m", is_flag=True, help="Show only mentions" ) @pass_context def notifications( ctx: Context, clear: bool, reverse: bool, mentions: int, ): """Show notifications""" if clear: api.clear_notifications(ctx.app, ctx.user) click.secho("✓ Notifications cleared", fg="green") return exclude = [] if mentions: # Filter everything except mentions # https://docs.joinmastodon.org/methods/notifications/ exclude = ["follow", "favourite", "reblog", "poll", "follow_request"] notifications = api.get_notifications(ctx.app, ctx.user, exclude_types=exclude) if not notifications: click.echo("You have no notifications") return if reverse: notifications = reversed(notifications) notifications = [from_dict(Notification, n) for n in notifications] print_notifications(notifications) def _show_timeline(generator, reverse, once): while True: try: items = next(generator) except StopIteration: click.echo("That's all folks.") return if reverse: items = reversed(items) statuses = [from_dict(Status, item) for item in items] print_timeline(statuses) if once or not sys.stdout.isatty(): break char = input("\nContinue? [Y/n] ") if char.lower() == "n": break def _get_list_id(ctx: Context, value: Optional[str]) -> Optional[str]: if not value: return None lists = api.get_lists(ctx.app, ctx.user) for list in lists: if list["id"] == value or list["title"] == value: return list["id"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/tui.py0000644000175000017500000000422714656344035015711 0ustar00ihabunekihabunekimport click from typing import Optional from toot.cli import TUI_COLORS, VISIBILITY_CHOICES, IMAGE_FORMAT_CHOICES, Context, cli, pass_context from toot.cli.validators import validate_tui_colors, validate_cache_size from toot.tui.app import TUI, TuiOptions COLOR_OPTIONS = ", ".join(TUI_COLORS.keys()) @cli.command() @click.option( "-r", "--relative-datetimes", is_flag=True, help="Show relative datetimes in status list" ) @click.option( "-m", "--media-viewer", help="Program to invoke with media URLs to display the media files, such as 'feh'" ) @click.option( "-c", "--colors", callback=validate_tui_colors, help=f"""Number of colors to use, one of {COLOR_OPTIONS}, defaults to 16 if using --color, and 1 if using --no-color.""" ) @click.option( "-s", "--cache-size", callback=validate_cache_size, help="""Specify the image cache maximum size in megabytes. Default: 10MB. Minimum: 1MB.""" ) @click.option( "-v", "--default-visibility", type=click.Choice(VISIBILITY_CHOICES), help="Default visibility when posting new toots; overrides the server-side preference" ) @click.option( "-s", "--always-show-sensitive", is_flag=True, help="Expand toots with content warnings automatically" ) @click.option( "-f", "--image-format", type=click.Choice(IMAGE_FORMAT_CHOICES), help="Image output format; support varies across terminals. Default: block" ) @pass_context def tui( ctx: Context, colors: Optional[int], media_viewer: Optional[str], always_show_sensitive: bool, relative_datetimes: bool, cache_size: Optional[int], default_visibility: Optional[str], image_format: Optional[str] ): """Launches the toot terminal user interface""" if colors is None: colors = 16 if ctx.color else 1 options = TuiOptions( colors=colors, media_viewer=media_viewer, relative_datetimes=relative_datetimes, cache_size=cache_size, default_visibility=default_visibility, always_show_sensitive=always_show_sensitive, image_format=image_format, ) tui = TUI.create(ctx.app, ctx.user, options) tui.run() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/cli/validators.py0000644000175000017500000000467514656344035017267 0ustar00ihabunekihabunekimport click import re from click import Context from typing import Optional from toot.cli import TUI_COLORS def validate_language(ctx: Context, param: str, value: Optional[str]): if value is None: return None value = value.strip().lower() if re.match(r"^[a-z]{2}$", value): return value raise click.BadParameter("Language should be a two letter abbreviation.") def validate_duration(ctx: Context, param: str, value: Optional[str]) -> Optional[int]: if value is None: return None match = re.match(r"""^ (([0-9]+)\s*(days|day|d))?\s* (([0-9]+)\s*(hours|hour|h))?\s* (([0-9]+)\s*(minutes|minute|m))?\s* (([0-9]+)\s*(seconds|second|s))?\s* $""", value, re.X) if not match: raise click.BadParameter(f"Invalid duration: {value}") days = match.group(2) hours = match.group(5) minutes = match.group(8) seconds = match.group(11) days = int(match.group(2) or 0) * 60 * 60 * 24 hours = int(match.group(5) or 0) * 60 * 60 minutes = int(match.group(8) or 0) * 60 seconds = int(match.group(11) or 0) duration = days + hours + minutes + seconds if duration == 0: raise click.BadParameter("Empty duration") return duration def validate_instance(ctx: click.Context, param: str, value: Optional[str]): """ Instance can be given either as a base URL or the domain name. Return the base URL. """ if not value: return None value = value.rstrip("/") return value if value.startswith("http") else f"https://{value}" def validate_tui_colors(ctx, param, value) -> Optional[int]: if value is None: return None if value in TUI_COLORS.values(): return value if value in TUI_COLORS.keys(): return TUI_COLORS[value] raise click.BadParameter(f"Invalid value: {value}. Expected one of: {', '.join(TUI_COLORS)}") def validate_cache_size(ctx: click.Context, param: str, value: Optional[str]) -> Optional[int]: """validates the cache size parameter""" if value is None: return 1024 * 1024 * 10 # default 10MB else: if value.isdigit(): size = int(value) else: raise click.BadParameter("Cache size must be numeric.") if size > 1024: raise click.BadParameter("Cache size too large: 1024MB maximum.") elif size < 1: raise click.BadParameter("Cache size too small: 1MB minimum.") return size ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/config.py0000644000175000017500000000667014656344035015612 0ustar00ihabunekihabunekimport json import os from contextlib import contextmanager from os.path import dirname, join from typing import Optional from toot import User, App, get_config_dir from toot.exceptions import ConsoleError TOOT_CONFIG_FILE_NAME = "config.json" def get_config_file_path(): """Returns the path to toot config file.""" return join(get_config_dir(), TOOT_CONFIG_FILE_NAME) def user_id(user: User): return "{}@{}".format(user.username, user.instance) def make_config(path: str): """Creates an empty toot configuration file.""" config = { "apps": {}, "users": {}, "active_user": None, } # Ensure dir exists os.makedirs(dirname(path), exist_ok=True) # Create file with 600 permissions since it contains secrets fd = os.open(path, os.O_CREAT | os.O_WRONLY, 0o600) with os.fdopen(fd, 'w') as f: json.dump(config, f, indent=True) def load_config(): # Just to prevent accidentally running tests on production if os.environ.get("TOOT_TESTING"): raise Exception("Tests should not access the config file!") path = get_config_file_path() if not os.path.exists(path): make_config(path) with open(path) as f: return json.load(f) def save_config(config): path = get_config_file_path() with open(path, "w") as f: return json.dump(config, f, indent=True, sort_keys=True) def extract_user_app(config, user_id: str): if user_id not in config['users']: return None, None user_data = config['users'][user_id] instance = user_data['instance'] if instance not in config['apps']: return None, None app_data = config['apps'][instance] return User(**user_data), App(**app_data) def get_active_user_app(): """Returns (User, App) of active user or (None, None) if no user is active.""" config = load_config() if config['active_user']: return extract_user_app(config, config['active_user']) return None, None def get_user_app(user_id: str): """Returns (User, App) for given user ID or (None, None) if user is not logged in.""" return extract_user_app(load_config(), user_id) def load_app(instance: str) -> Optional[App]: config = load_config() if instance in config['apps']: return App(**config['apps'][instance]) def load_user(user_id: str, throw=False): config = load_config() if user_id in config['users']: return User(**config['users'][user_id]) if throw: raise ConsoleError("User '{}' not found".format(user_id)) def get_user_list(): config = load_config() return config['users'] @contextmanager def edit_config(): config = load_config() yield config save_config(config) def save_app(app: App): with edit_config() as config: config['apps'][app.instance] = app._asdict() def delete_app(config, app: App): with edit_config() as config: config['apps'].pop(app.instance, None) def save_user(user: User, activate=True): with edit_config() as config: config['users'][user_id(user)] = user._asdict() if activate: config['active_user'] = user_id(user) def delete_user(user: User): with edit_config() as config: config['users'].pop(user_id(user), None) if config['active_user'] == user_id(user): config['active_user'] = None def activate_user(user: User): with edit_config() as config: config['active_user'] = user_id(user) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/entities.py0000644000175000017500000003126614656344035016170 0ustar00ihabunekihabunek""" Dataclasses which represent entities returned by the Mastodon API. Data classes my have an optional static method named `__toot_prepare__` which is used when constructing the data class using `from_dict`. The method will be called with the dict and may modify it and return a modified dict. This is used to implement any pre-processing which may be required, e.g. to support different versions of the Mastodon API. """ import dataclasses import typing as t from dataclasses import dataclass, is_dataclass from datetime import date, datetime from functools import lru_cache from typing import Any, Dict, NamedTuple, Optional, Type, TypeVar, Union from typing import get_args, get_origin, get_type_hints from toot.utils import get_text from toot.utils.datetime import parse_datetime # Generic data class instance T = TypeVar("T") # A dict decoded from JSON Data = Dict[str, Any] @dataclass class AccountField: """ https://docs.joinmastodon.org/entities/Account/#Field """ name: str value: str verified_at: Optional[datetime] @dataclass class CustomEmoji: """ https://docs.joinmastodon.org/entities/CustomEmoji/ """ shortcode: str url: str static_url: str visible_in_picker: bool category: str @dataclass class Account: """ https://docs.joinmastodon.org/entities/Account/ """ id: str username: str acct: str url: str display_name: str note: str avatar: str avatar_static: str header: str header_static: str locked: bool fields: t.List[AccountField] emojis: t.List[CustomEmoji] bot: bool group: bool discoverable: Optional[bool] noindex: Optional[bool] moved: Optional["Account"] suspended: Optional[bool] limited: Optional[bool] created_at: datetime last_status_at: Optional[date] statuses_count: int followers_count: int following_count: int source: Optional[dict] @staticmethod def __toot_prepare__(obj: Data) -> Data: # Pleroma has not yet converted last_status_at from datetime to date # so trim it here so it doesn't break when converting to date. # See: https://git.pleroma.social/pleroma/pleroma/-/issues/1470 last_status_at = obj.get("last_status_at") if last_status_at: obj.update(last_status_at=obj["last_status_at"][:10]) return obj @property def note_plaintext(self) -> str: return get_text(self.note) @dataclass class Application: """ https://docs.joinmastodon.org/entities/Status/#application """ name: str website: Optional[str] @dataclass class MediaAttachment: """ https://docs.joinmastodon.org/entities/MediaAttachment/ """ id: str type: str url: str preview_url: str remote_url: Optional[str] meta: dict description: str blurhash: str @dataclass class StatusMention: """ https://docs.joinmastodon.org/entities/Status/#Mention """ id: str username: str url: str acct: str @dataclass class StatusTag: """ https://docs.joinmastodon.org/entities/Status/#Tag """ name: str url: str @dataclass class PollOption: """ https://docs.joinmastodon.org/entities/Poll/#Option """ title: str votes_count: Optional[int] @dataclass class Poll: """ https://docs.joinmastodon.org/entities/Poll/ """ id: str expires_at: Optional[datetime] expired: bool multiple: bool votes_count: int voters_count: Optional[int] options: t.List[PollOption] emojis: t.List[CustomEmoji] voted: Optional[bool] own_votes: Optional[t.List[int]] @dataclass class PreviewCard: """ https://docs.joinmastodon.org/entities/PreviewCard/ """ url: str title: str description: str type: str author_name: str author_url: str provider_name: str provider_url: str html: str width: int height: int image: Optional[str] embed_url: str blurhash: Optional[str] @dataclass class FilterKeyword: """ https://docs.joinmastodon.org/entities/FilterKeyword/ """ id: str keyword: str whole_word: str @dataclass class FilterStatus: """ https://docs.joinmastodon.org/entities/FilterStatus/ """ id: str status_id: str @dataclass class Filter: """ https://docs.joinmastodon.org/entities/Filter/ """ id: str title: str context: t.List[str] expires_at: Optional[datetime] filter_action: str keywords: t.List[FilterKeyword] statuses: t.List[FilterStatus] @dataclass class FilterResult: """ https://docs.joinmastodon.org/entities/FilterResult/ """ filter: Filter keyword_matches: Optional[t.List[str]] status_matches: Optional[str] @dataclass class Status: """ https://docs.joinmastodon.org/entities/Status/ """ id: str uri: str created_at: datetime account: Account content: str visibility: str sensitive: bool spoiler_text: str media_attachments: t.List[MediaAttachment] application: Optional[Application] mentions: t.List[StatusMention] tags: t.List[StatusTag] emojis: t.List[CustomEmoji] reblogs_count: int favourites_count: int replies_count: int url: Optional[str] in_reply_to_id: Optional[str] in_reply_to_account_id: Optional[str] reblog: Optional["Status"] poll: Optional[Poll] card: Optional[PreviewCard] language: Optional[str] text: Optional[str] edited_at: Optional[datetime] favourited: Optional[bool] reblogged: Optional[bool] muted: Optional[bool] bookmarked: Optional[bool] pinned: Optional[bool] filtered: Optional[t.List[FilterResult]] @property def original(self) -> "Status": return self.reblog or self @staticmethod def __toot_prepare__(obj: Data) -> Data: # Pleroma has a bug where created_at is set to an empty string. # To avoid marking created_at as optional, which would require work # because we count on it always existing, set it to current datetime. # Possible underlying issue: # https://git.pleroma.social/pleroma/pleroma/-/issues/2851 if not obj["created_at"]: obj["created_at"] = datetime.now().astimezone().isoformat() return obj @dataclass class Report: """ https://docs.joinmastodon.org/entities/Report/ """ id: str action_taken: bool action_taken_at: Optional[datetime] category: str comment: str forwarded: bool created_at: datetime status_ids: Optional[t.List[str]] rule_ids: Optional[t.List[str]] target_account: Account @dataclass class Notification: """ https://docs.joinmastodon.org/entities/Notification/ """ id: str type: str created_at: datetime account: Account status: Optional[Status] report: Optional[Report] @dataclass class InstanceUrls: streaming_api: str @dataclass class InstanceStats: user_count: int status_count: int domain_count: int @dataclass class InstanceConfigurationStatuses: max_characters: int max_media_attachments: int characters_reserved_per_url: int @dataclass class InstanceConfigurationMediaAttachments: supported_mime_types: t.List[str] image_size_limit: int image_matrix_limit: int video_size_limit: int video_frame_rate_limit: int video_matrix_limit: int @dataclass class InstanceConfigurationPolls: max_options: int max_characters_per_option: int min_expiration: int max_expiration: int @dataclass class InstanceConfiguration: """ https://docs.joinmastodon.org/entities/V1_Instance/#configuration """ statuses: InstanceConfigurationStatuses media_attachments: InstanceConfigurationMediaAttachments polls: InstanceConfigurationPolls @dataclass class Rule: """ https://docs.joinmastodon.org/entities/Rule/ """ id: str text: str @dataclass class Instance: """ https://docs.joinmastodon.org/entities/V1_Instance/ """ uri: str title: str short_description: str description: str email: str version: str urls: InstanceUrls stats: InstanceStats thumbnail: Optional[str] languages: t.List[str] registrations: bool approval_required: bool invites_enabled: bool configuration: InstanceConfiguration contact_account: Optional[Account] rules: t.List[Rule] @dataclass class Relationship: """ Represents the relationship between accounts, such as following / blocking / muting / etc. https://docs.joinmastodon.org/entities/Relationship/ """ id: str following: bool showing_reblogs: bool notifying: bool languages: t.List[str] followed_by: bool blocking: bool blocked_by: bool muting: bool muting_notifications: bool requested: bool domain_blocking: bool endorsed: bool note: str @dataclass class TagHistory: """ Usage statistics for given days (typically the past week). https://docs.joinmastodon.org/entities/Tag/#history """ day: str uses: str accounts: str @dataclass class Tag: """ Represents a hashtag used within the content of a status. https://docs.joinmastodon.org/entities/Tag/ """ name: str url: str history: t.List[TagHistory] following: Optional[bool] @dataclass class FeaturedTag: """ Represents a hashtag that is featured on a profile. https://docs.joinmastodon.org/entities/FeaturedTag/ """ id: str name: str url: str statuses_count: int last_status_at: datetime @dataclass class List: """ Represents a list of some users that the authenticated user follows. https://docs.joinmastodon.org/entities/List/ """ id: str title: str # This is a required field on Mastodon, but not supported on Pleroma/Akkoma # see: https://git.pleroma.social/pleroma/pleroma/-/issues/2918 replies_policy: Optional[str] # ------------------------------------------------------------------------------ class Field(NamedTuple): name: str type: Any default: Any class ConversionError(Exception): """Raised when conversion fails from JSON value to data class field.""" def __init__(self, data_class: type, field: Field, field_value: Optional[str]): super().__init__( f"Failed converting field `{data_class.__name__}.{field.name}` " + f"of type `{field.type.__name__}` from value {field_value!r}" ) def from_dict(cls: Type[T], data: Data) -> T: """Convert a nested dict into an instance of `cls`.""" # Apply __toot_prepare__ if it exists prepare = getattr(cls, '__toot_prepare__', None) if prepare: data = prepare(data) def _fields(): for field in _get_fields(cls): value = data.get(field.name, field.default) converted = _convert_with_error_handling(cls, field, value) yield field.name, converted return cls(**dict(_fields())) @lru_cache def _get_fields(cls: type) -> t.List[Field]: hints = get_type_hints(cls) return [ Field( field.name, _prune_optional(hints[field.name]), _get_default_value(field) ) for field in dataclasses.fields(cls) ] def from_dict_list(cls: Type[T], data: t.List[Data]) -> t.List[T]: return [from_dict(cls, x) for x in data] def _get_default_value(field: dataclasses.Field): if field.default is not dataclasses.MISSING: return field.default if field.default_factory is not dataclasses.MISSING: return field.default_factory() return None def _convert_with_error_handling(data_class: type, field: Field, field_value: Any) -> Any: try: return _convert(field.type, field_value) except ConversionError: raise except Exception: raise ConversionError(data_class, field, field_value) def _convert(field_type: Any, value: Any) -> Any: if value is None: return None if field_type in [str, int, bool, dict]: return value if field_type == datetime: return parse_datetime(value) if field_type == date: return date.fromisoformat(value) if get_origin(field_type) == list: (inner_type,) = get_args(field_type) return [_convert(inner_type, x) for x in value] if is_dataclass(field_type): return from_dict(field_type, value) raise ValueError(f"Not implemented for type '{field_type}'") def _prune_optional(field_type: type) -> type: """For `Optional[]` returns the encapsulated ``.""" if get_origin(field_type) == Union: args = get_args(field_type) if len(args) == 2 and args[1] == type(None): # noqa return args[0] return field_type ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/exceptions.py0000644000175000017500000000062314656344035016516 0ustar00ihabunekihabunekfrom click import ClickException class ApiError(ClickException): """Raised when an API request fails for whatever reason.""" class NotFoundError(ApiError): """Raised when an API requests returns a 404.""" class AuthenticationError(ApiError): """Raised when login fails.""" class ConsoleError(ClickException): """Raised when an error occurs which needs to be show to the user.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/http.py0000644000175000017500000000762114656344035015321 0ustar00ihabunekihabunekfrom requests import Request, Session from requests.exceptions import RequestException from toot import __version__ from toot.exceptions import NotFoundError, ApiError from toot.logging import log_request, log_request_exception, log_response def send_request(request, allow_redirects=True): # Set a user agent string # Required for accessing instances using Cloudfront DDOS protection. request.headers["User-Agent"] = "toot/{}".format(__version__) log_request(request) try: with Session() as session: prepared = session.prepare_request(request) settings = session.merge_environment_settings(prepared.url, {}, None, None, None) response = session.send(prepared, allow_redirects=allow_redirects, **settings) except RequestException as ex: log_request_exception(request, ex) raise ApiError(f"Request failed: {str(ex)}") log_response(response) return response def _get_error_message(response): """Attempt to extract an error message from response body""" try: data = response.json() if "error_description" in data: return data['error_description'] if "error" in data: return data['error'] except Exception: pass return f"Unknown error: {response.status_code} {response.reason}" def process_response(response): if not response.ok: error = _get_error_message(response) if response.status_code == 404: raise NotFoundError(error) raise ApiError(error) return response def get(app, user, path, params=None, headers=None): url = app.base_url + path headers = headers or {} headers["Authorization"] = f"Bearer {user.access_token}" request = Request('GET', url, headers, params=params) response = send_request(request) return process_response(response) def anon_get(url, params=None): request = Request('GET', url, None, params=params) response = send_request(request) return process_response(response) def post(app, user, path, headers=None, files=None, data=None, json=None, allow_redirects=True): url = app.base_url + path headers = headers or {} headers["Authorization"] = f"Bearer {user.access_token}" return anon_post(url, headers=headers, files=files, data=data, json=json, allow_redirects=allow_redirects) def anon_put(url, headers=None, files=None, data=None, json=None, allow_redirects=True): request = Request(method="PUT", url=url, headers=headers, files=files, data=data, json=json) response = send_request(request, allow_redirects) return process_response(response) def put(app, user, path, headers=None, files=None, data=None, json=None, allow_redirects=True): url = app.base_url + path headers = headers or {} headers["Authorization"] = f"Bearer {user.access_token}" return anon_put(url, headers=headers, files=files, data=data, json=json, allow_redirects=allow_redirects) def patch(app, user, path, headers=None, files=None, data=None, json=None): url = app.base_url + path headers = headers or {} headers["Authorization"] = f"Bearer {user.access_token}" request = Request('PATCH', url, headers=headers, files=files, data=data, json=json) response = send_request(request) return process_response(response) def delete(app, user, path, data=None, json=None, headers=None): url = app.base_url + path headers = headers or {} headers["Authorization"] = f"Bearer {user.access_token}" request = Request('DELETE', url, headers=headers, data=data, json=json) response = send_request(request) return process_response(response) def anon_post(url, headers=None, files=None, data=None, json=None, allow_redirects=True): request = Request(method="POST", url=url, headers=headers, files=files, data=data, json=json) response = send_request(request, allow_redirects) return process_response(response) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/logging.py0000644000175000017500000000331514656344035015764 0ustar00ihabunekihabunekimport json import sys from logging import getLogger from requests import Request, RequestException, Response from urllib.parse import urlencode logger = getLogger("toot") VERBOSE = "--verbose" in sys.argv def censor_secrets(headers): def _censor(k, v): if k == "Authorization": return (k, "***CENSORED***") return k, v return {_censor(k, v) for k, v in headers.items()} def truncate(line): if not VERBOSE and len(line) > 100: return line[:100] + "…" return line def log_request(request: Request): logger.debug(f" --> {request.method} {_url(request)}") if VERBOSE and request.headers: headers = censor_secrets(request.headers) logger.debug(f" --> HEADERS: {headers}") if VERBOSE and request.data: data = truncate(request.data) logger.debug(f" --> DATA: {data}") if VERBOSE and request.json: data = truncate(json.dumps(request.json)) logger.debug(f" --> JSON: {data}") if VERBOSE and request.files: logger.debug(f" --> FILES: {request.files}") def log_response(response: Response): method = response.request.method url = response.request.url elapsed = response.elapsed.microseconds // 1000 logger.debug(f" <-- {method} {url} HTTP {response.status_code} {elapsed}ms") if VERBOSE and response.content: content = truncate(response.content.decode()) logger.debug(f" <-- {content}") def log_request_exception(request: Request, ex: RequestException): logger.debug(f" <-- {request.method} {_url(request)} Exception: {ex}") def _url(request): url = request.url if request.params: url += f"?{urlencode(request.params)}" return url ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457031.0 toot-0.44.1/toot/output.py0000644000175000017500000003033014656357007015676 0ustar00ihabunekihabunekimport click import platform import re import shutil import textwrap import typing as t from datetime import datetime, timezone from importlib.metadata import version from wcwidth import wcswidth from toot import __version__, config, settings from toot.entities import Account, Data, Instance, Notification, Poll, Status, List from toot.utils import get_text, html_to_paragraphs from toot.wcstring import wc_wrap DEFAULT_WIDTH = 80 def get_max_width() -> int: return click.get_current_context().max_content_width or DEFAULT_WIDTH def get_terminal_width() -> int: return shutil.get_terminal_size().columns def get_width() -> int: return min(get_terminal_width(), get_max_width()) def print_warning(text: str): click.secho(f"Warning: {text}", fg="yellow", err=True) def print_instance(instance: Instance): width = get_width() click.echo(instance_to_text(instance, width)) def instance_to_text(instance: Instance, width: int) -> str: return "\n".join(instance_lines(instance, width)) def instance_lines(instance: Instance, width: int) -> t.Generator[str, None, None]: yield f"{green(instance.title)}" yield f"{blue(instance.uri)}" yield f"running Mastodon {instance.version}" yield "" if instance.description: for paragraph in re.split(r"[\r\n]+", instance.description.strip()): paragraph = get_text(paragraph) yield textwrap.fill(paragraph, width=width) yield "" if instance.rules: yield "Rules:" for ordinal, rule in enumerate(instance.rules): ordinal = f"{ordinal + 1}." lines = textwrap.wrap(rule.text, width - len(ordinal)) first = True for line in lines: if first: yield f"{ordinal} {line}" first = False else: yield f"{' ' * len(ordinal)} {line}" yield "" contact = instance.contact_account if contact: yield f"Contact: {contact.display_name} @{contact.acct}" def print_account(account: Account) -> None: width = get_width() click.echo(account_to_text(account, width)) def account_to_text(account: Account, width: int) -> str: return "\n".join(account_lines(account, width)) def account_lines(account: Account, width: int) -> t.Generator[str, None, None]: acct = f"@{account.acct}" since = account.created_at.strftime("%Y-%m-%d") yield f"{green(acct)} {account.display_name}" if account.note: yield "" yield from html_lines(account.note, width) yield "" yield f"ID: {green(account.id)}" yield f"Since: {green(since)}" yield "" yield f"Followers: {yellow(account.followers_count)}" yield f"Following: {yellow(account.following_count)}" yield f"Statuses: {yellow(account.statuses_count)}" if account.fields: for field in account.fields: name = field.name.title() yield f'\n{yellow(name)}:' yield from html_lines(field.value, width) if field.verified_at: yield green("✓ Verified") yield "" yield account.url def print_acct_list(accounts): for account in accounts: acct = green(f"@{account['acct']}") click.echo(f"* {acct} {account['display_name']}") def print_tag_list(tags): for tag in tags: click.echo(f"* {format_tag_name(tag)}\t{tag['url']}") def print_lists(lists: t.List[List]): headers = ["ID", "Title", "Replies"] data = [[lst.id, lst.title, lst.replies_policy or ""] for lst in lists] print_table(headers, data) def print_table(headers: t.List[str], data: t.List[t.List[str]]): widths = [[len(cell) for cell in row] for row in data + [headers]] widths = [max(width) for width in zip(*widths)] def print_row(row): for idx, cell in enumerate(row): width = widths[idx] click.echo(cell.ljust(width), nl=False) click.echo(" ", nl=False) click.echo() underlines = ["-" * width for width in widths] print_row(headers) print_row(underlines) for row in data: print_row(row) def print_list_accounts(accounts): if accounts: click.echo("Accounts in list:\n") print_acct_list(accounts) else: click.echo("This list has no accounts.") def print_search_results(results): accounts = results.get("accounts") hashtags = results.get("hashtags") statuses = results.get("statuses") if accounts: click.echo("\nAccounts:") print_acct_list(accounts) if hashtags: click.echo("\nHashtags:") click.echo(", ".join([format_tag_name(tag) for tag in hashtags])) if statuses: click.echo("\nStatuses:") for status in statuses: click.echo(f" * {green(status['id'])} {status['url']}") if not accounts and not hashtags and not statuses: click.echo("Nothing found") def print_status(status: Status) -> None: width = get_width() click.echo(status_to_text(status, width)) def status_to_text(status: Status, width: int) -> str: return "\n".join(status_lines(status)) def status_lines(status: Status) -> t.Generator[str, None, None]: width = get_width() status_id = status.id in_reply_to_id = status.in_reply_to_id reblogged_by = status.account if status.reblog else None status = status.original time = status.created_at.strftime('%Y-%m-%d %H:%M %Z') username = "@" + status.account.acct spacing = width - wcswidth(username) - wcswidth(time) - 2 display_name = status.account.display_name if display_name: author = f"{green(display_name)} {blue(username)}" spacing -= wcswidth(display_name) + 1 else: author = blue(username) spaces = " " * spacing yield f"{author} {spaces} {yellow(time)}" yield "" yield from html_lines(status.content, width) if status.media_attachments: yield "" yield "Media:" for attachment in status.media_attachments: url = attachment.url for line in wc_wrap(url, width): yield line if status.poll: yield from poll_lines(status.poll) reblogged_by_acct = f"@{reblogged_by.acct}" if reblogged_by else None yield "" reply = f"↲ In reply to {yellow(in_reply_to_id)} " if in_reply_to_id else "" boost = f"↻ {blue(reblogged_by_acct)} boosted " if reblogged_by else "" yield f"ID {yellow(status_id)} Visibility: {status.visibility} {reply} {boost}" def html_lines(html: str, width: int) -> t.Generator[str, None, None]: first = True for paragraph in html_to_paragraphs(html): if not first: yield "" for line in paragraph: for subline in wc_wrap(line, width): yield subline first = False def poll_lines(poll: Poll) -> t.Generator[str, None, None]: for idx, option in enumerate(poll.options): perc = (round(100 * option.votes_count / poll.votes_count) if poll.votes_count and option.votes_count is not None else 0) if poll.voted and poll.own_votes and idx in poll.own_votes: voted_for = yellow(" ✓") else: voted_for = "" yield f"{option.title} - {perc}% {voted_for}" poll_footer = f'Poll · {poll.votes_count} votes' if poll.expired: poll_footer += " · Closed" if poll.expires_at: expires_at = poll.expires_at.strftime("%Y-%m-%d %H:%M") poll_footer += f" · Closes on {expires_at}" yield "" yield poll_footer def print_timeline(items: t.Iterable[Status]): print_divider() for item in items: print_status(item) print_divider() def print_notification(notification: Notification): print_notification_header(notification) if notification.status: print_divider(char="-") print_status(notification.status) def print_notifications(notifications: t.List[Notification]): for notification in notifications: if notification.type not in ['pleroma:emoji_reaction']: print_divider() print_notification(notification) print_divider() def print_notification_header(notification: Notification): account_name = format_account_name(notification.account) if (notification.type == "follow"): click.echo(f"{account_name} now follows you") elif (notification.type == "mention"): click.echo(f"{account_name} mentioned you") elif (notification.type == "reblog"): click.echo(f"{account_name} reblogged your status") elif (notification.type == "favourite"): click.echo(f"{account_name} favourited your status") elif (notification.type == "update"): click.echo(f"{account_name} edited a post") else: click.secho(f"Unknown notification type: '{notification.type}'", err=True, fg="yellow") click.secho("Please report an issue to toot.", err=True, fg="yellow") def print_divider(char: str = "─"): click.echo(char * get_width()) def format_tag_name(tag): return green(f"#{tag['name']}") def format_account_name(account: Account) -> str: acct = blue(f"@{account.acct}") if account.display_name: return f"{green(account.display_name)} {acct}" else: return acct def print_diags(instance_dict: t.Optional[Data], include_files: bool): click.echo(f'{green("## Toot Diagnostics")}') click.echo() now = datetime.now(timezone.utc) click.echo(f'{green("Current Date/Time:")} {now.strftime("%Y-%m-%d %H:%M:%S %Z")}') click.echo(f'{green("Toot version:")} {__version__}') click.echo(f'{green("Platform:")} {platform.platform()}') # print distro - only call if available (python 3.10+) fd_os_release = getattr(platform, "freedesktop_os_release", None) # novermin if callable(fd_os_release): # novermin try: name = platform.freedesktop_os_release()['PRETTY_NAME'] click.echo(f'{green("Distro:")} {name}') except: # noqa pass click.echo(f'{green("Python version:")} {platform.python_version()}') click.echo() click.echo(green("Dependency versions:")) deps = sorted(['beautifulsoup4', 'click', 'requests', 'tomlkit', 'urwid', 'wcwidth', 'pillow', 'term-image', 'urwidgets', 'flake8', 'pytest', 'setuptools', 'vermin', 'typing-extensions']) for dep in deps: try: ver = version(dep) except: # noqa ver = yellow("not installed") click.echo(f" * {dep}: {ver}") click.echo() click.echo(f'{green("Settings file path:")} {settings.get_settings_path()}') click.echo(f'{green("Config file path:")} {config.get_config_file_path()}') if instance_dict: click.echo(f'{green("Server URI:")} {instance_dict.get("uri")}') click.echo(f'{green("Server version:")} {instance_dict.get("version")}') if include_files: click.echo(f'{green("Settings file contents:")}') try: with open(settings.get_settings_path(), 'r') as f: print("```toml") print(f.read()) print("```") except: # noqa click.echo(f'{yellow("Could not open settings file")}') click.echo() click.echo(f'{green("Config file contents:")}') click.echo("```json") try: with open(config.get_config_file_path(), 'r') as f: for line in f: # Do not output client secret or access token lines if "client_" in line or "token" in line: click.echo(f'{yellow("***CONTENTS REDACTED***")}') else: click.echo(line, nl=False) click.echo() except: # noqa click.echo(f'{yellow("Could not open config file")}') click.echo("```") # Shorthand functions for coloring output def blue(text: t.Any) -> str: return click.style(text, fg="blue") def bold(text: t.Any) -> str: return click.style(text, bold=True) def cyan(text: t.Any) -> str: return click.style(text, fg="cyan") def dim(text: t.Any) -> str: return click.style(text, dim=True) def green(text: t.Any) -> str: return click.style(text, fg="green") def yellow(text: t.Any) -> str: return click.style(text, fg="yellow") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/settings.py0000644000175000017500000000261114656344035016174 0ustar00ihabunekihabunekfrom functools import lru_cache from os.path import exists, join from tomlkit import parse from toot import get_config_dir from typing import Optional, Type, TypeVar DISABLE_SETTINGS = False TOOT_SETTINGS_FILE_NAME = "settings.toml" def get_settings_path(): return join(get_config_dir(), TOOT_SETTINGS_FILE_NAME) def _load_settings() -> dict: # Used for testing without config file if DISABLE_SETTINGS: return {} path = get_settings_path() if not exists(path): return {} with open(path) as f: return parse(f.read()) @lru_cache(maxsize=None) def get_settings(): return _load_settings() T = TypeVar("T") def get_setting(key: str, type: Type[T], default: Optional[T] = None) -> Optional[T]: """ Get a setting value. The key should be a dot-separated string, e.g. "commands.post.editor" which will correspond to the "editor" setting inside the `[commands.post]` section. """ settings = get_settings() return _get_setting(settings, key.split("."), type, default) def _get_setting(dct, keys, type: Type, default=None): if len(keys) == 0: if isinstance(dct, type): return dct else: # TODO: warn? cast? both? return default key = keys[0] if isinstance(dct, dict) and key in dct: return _get_setting(dct[key], keys[1:], type, default) return default ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.855274 toot-0.44.1/toot/tui/0000755000175000017500000000000014656360001014552 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749800.0 toot-0.44.1/toot/tui/NOTES.md0000644000175000017500000000307214465406650015777 0ustar00ihabunekihabunekInteresting urwid implementations: * https://github.com/CanonicalLtd/subiquity/blob/master/subiquitycore/core.py#L280 * https://github.com/TomasTomecek/sen/blob/master/sen/tui/ui.py * https://github.com/rndusr/stig/tree/master/stig/tui Check out: * https://github.com/rr-/urwid_readline - better edit box? * https://github.com/prompt-toolkit/python-prompt-toolkit TODO/Ideas: * pack left column in timeline view * allow scrolling of toot contents if they don't fit the screen, perhaps using pageup/pagedown * consider adding semi-automated error reporting when viewing an exception, something along the lines of "press T to submit a ticket", which would link to a pre-filled issue submit page. * show new toots, some ideas: * R to reload/refresh timeline * streaming new toots? not sold on the idea * go up on first toot to fetch any newer ones, and prepend them? * Switch timeline to top/bottom layout for narrow views. * Think about how to show media * download media and use local image viewer? * convert to ascii art? * interaction with clipboard - how to copy a status to clipboard? * Show **notifications** * Status source * shortcut to copy source * syntax highlighting? * reblog * show author in status list, not person who reblogged * "v" should open the reblogged status, status.url is empty for the reblog * overlays * stack overlays instead of having one? * current bug: press U G Q Q - second Q closes the app instead of closing the overlay Questions: * is it possible to make a span a urwid.Text selectable? e.g. for urls and hashtags ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749800.0 toot-0.44.1/toot/tui/__init__.py0000644000175000017500000000044714465406650016701 0ustar00ihabunekihabunekfrom urwid.command_map import command_map from urwid.command_map import CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT # Add movement using h/j/k/l to default command map command_map._command.update({ 'k': CURSOR_UP, 'j': CURSOR_DOWN, 'h': CURSOR_LEFT, 'l': CURSOR_RIGHT, }) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/app.py0000644000175000017500000010210614656344035015715 0ustar00ihabunekihabunekimport logging import subprocess import urwid from concurrent.futures import ThreadPoolExecutor from typing import NamedTuple, Optional from datetime import datetime, timezone from toot import api, config, __version__, settings from toot import App, User from toot.cli import get_default_visibility from toot.exceptions import ApiError from toot.utils.datetime import parse_datetime from .compose import StatusComposer from .constants import PALETTE from .entities import Status from .images import TuiScreen, load_image from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource, StatusLinks, StatusZoom from .overlays import StatusDeleteConfirmation, Account from .poll import Poll from .timeline import Timeline from .utils import get_max_toot_chars, parse_content_links, copy_to_clipboard, LRUCache from .widgets import ModalBox, RoundedLineBox logger = logging.getLogger(__name__) urwid.set_encoding('UTF-8') DEFAULT_MAX_TOOT_CHARS = 500 class TuiOptions(NamedTuple): colors: int media_viewer: Optional[str] always_show_sensitive: bool relative_datetimes: bool cache_size: int default_visibility: Optional[str] image_format: Optional[str] class Header(urwid.WidgetWrap): def __init__(self, app, user): self.app = app self.user = user self.text = urwid.Text("") self.cols = urwid.Columns([ ("pack", urwid.Text(('header_bold', 'toot'))), ("pack", urwid.Text(('header', ' | {}@{}'.format(user.username, app.instance)))), ("pack", self.text), ]) widget = urwid.AttrMap(self.cols, 'header') widget = urwid.Padding(widget) self._wrapped_widget = widget def clear_text(self, text): self.text.set_text("") def set_text(self, text): self.text.set_text(" | " + text) class Footer(urwid.Pile): def __init__(self): self.status = urwid.Text("") self.message = urwid.Text("") return super().__init__([ urwid.AttrMap(self.status, "footer_status"), urwid.AttrMap(self.message, "footer_message"), ]) def set_status(self, text): self.status.set_text(text) def clear_status(self, text): self.status.set_text("") def set_message(self, text): self.message.set_text(text) def set_error_message(self, text): self.message.set_text(("footer_message_error", text)) def clear_message(self): self.message.set_text("") class TUI(urwid.Frame): """Main TUI frame.""" loop: urwid.MainLoop screen: urwid.BaseScreen @staticmethod def create(app: App, user: User, args: TuiOptions): """Factory method, sets up TUI and an event loop.""" screen = TuiScreen() screen.set_terminal_properties(args.colors) tui = TUI(app, user, screen, args) palette = PALETTE.copy() overrides = settings.get_setting("tui.palette", dict, {}) for name, styles in overrides.items(): palette.append(tuple([name] + styles)) loop = urwid.MainLoop( tui, palette=palette, event_loop=urwid.AsyncioEventLoop(), unhandled_input=tui.unhandled_input, screen=screen, ) tui.loop = loop return tui def __init__(self, app, user, screen, options: TuiOptions): self.app = app self.user = user self.config = config.load_config() self.options = options self.loop = None # late init, set in `create` self.screen = screen self.executor = ThreadPoolExecutor(max_workers=1) self.timeline_generator = api.home_timeline_generator(app, user, limit=40) # Show intro screen while toots are being loaded self.body = self.build_intro() self.header = Header(app, user) self.footer = Footer() self.footer.set_status("Loading...") # Default max status length, updated on startup self.max_toot_chars = DEFAULT_MAX_TOOT_CHARS self.timeline = None self.overlay = None self.exception = None self.can_translate = False self.account = None self.followed_accounts = [] self.preferences = {} if self.options.cache_size: self.cache_max = 1024 * 1024 * self.options.cache_size else: self.cache_max = 1024 * 1024 * 10 # default 10MB super().__init__(self.body, header=self.header, footer=self.footer) def run(self): self.loop.set_alarm_in(0, lambda *args: self.async_load_instance()) self.loop.set_alarm_in(0, lambda *args: self.async_load_preferences()) self.loop.set_alarm_in(0, lambda *args: self.async_load_timeline( is_initial=True, timeline_name="home")) self.loop.set_alarm_in(0, lambda *args: self.async_load_followed_accounts()) self.loop.run() self.executor.shutdown(wait=False) def build_intro(self): font = urwid.font.Thin6x6Font() # NB: Padding with width="clip" will convert the fixed BigText widget # to a flow widget so it can be used in a Pile. big_text = "Toot {}".format(__version__) big_text = urwid.BigText(("intro_bigtext", big_text), font) big_text = urwid.Padding(big_text, align="center", width="clip") intro = urwid.Pile([ big_text, urwid.Divider(), urwid.Text([ "Maintained by ", ("intro_smalltext", "@ihabunek"), " and contributors" ], align="center"), urwid.Divider(), urwid.Text(("intro_smalltext", "Loading toots..."), align="center"), ]) return urwid.Filler(intro) def run_in_thread(self, fn, done_callback=None, error_callback=None): """Runs `fn` asynchronously in a separate thread. On completion calls `done_callback` if `fn` exited cleanly, or `error_callback` if an exception was caught. Callback methods are invoked in the main thread, not the thread in which `fn` is executed. """ def _default_error_callback(ex): self.exception = ex self.footer.set_error_message("An exception occurred, press X to view") _error_callback = error_callback or _default_error_callback def _done(future): try: result = future.result() if done_callback: # Use alarm to invoke callback in main thread self.loop.set_alarm_in(0, lambda *args: done_callback(result)) except Exception as ex: exception = ex logger.exception(exception) self.loop.set_alarm_in(0, lambda *args: _error_callback(exception)) # TODO: replace by `self.loop.event_loop.run_in_executor` at some point # Added in https://github.com/urwid/urwid/issues/575 # Not yet released at the time of this comment future = self.loop.event_loop._loop.run_in_executor(self.executor, fn) future.add_done_callback(_done) return future def connect_default_timeline_signals(self, timeline): urwid.connect_signal(timeline, "focus", self.refresh_footer) def build_timeline(self, name, statuses, local): def _close(*args): raise urwid.ExitMainLoop() def _next(*args): self.async_load_timeline(is_initial=False) def _toggle_save(timeline, status): if not timeline.name.startswith("#"): return hashtag = timeline.name[1:] assert isinstance(local, bool), local timelines = self.config.setdefault("timelines", {}) if hashtag in timelines: del timelines[hashtag] self.footer.set_message("#{} unpinned".format(hashtag)) else: timelines[hashtag] = {"local": local} self.footer.set_message("#{} pinned".format(hashtag)) self.loop.set_alarm_in(5, lambda *args: self.footer.clear_message()) config.save_config(self.config) timeline = Timeline(self, name, statuses) self.connect_default_timeline_signals(timeline) urwid.connect_signal(timeline, "next", _next) urwid.connect_signal(timeline, "close", _close) urwid.connect_signal(timeline, "save", _toggle_save) return timeline def make_status(self, status_data): is_mine = self.user.username == status_data["account"]["acct"] return Status(status_data, is_mine, self.app.instance) def show_thread(self, status): def _close(*args): """When thread is closed, go back to the main timeline.""" self.body = self.timeline self.body.refresh_status_details() self.refresh_footer(self.timeline) # This is pretty fast, so it's probably ok to block while context is # loaded, can be made async later if needed context = api.context(self.app, self.user, status.original.id).json() ancestors = [self.make_status(s) for s in context["ancestors"]] descendants = [self.make_status(s) for s in context["descendants"]] statuses = ancestors + [status] + descendants focus = len(ancestors) timeline = Timeline(self, "thread", statuses, focus=focus, is_thread=True) self.connect_default_timeline_signals(timeline) urwid.connect_signal(timeline, "close", _close) self.body = timeline timeline.refresh_status_details() self.refresh_footer(timeline) def async_load_timeline(self, is_initial, timeline_name=None, local=None): """Asynchronously load a list of statuses.""" def _load_statuses(): self.footer.set_message("Loading statuses...") try: data = next(self.timeline_generator) except StopIteration: return [] finally: self.footer.clear_message() return [self.make_status(s) for s in data] def _done_initial(statuses): """Process initial batch of statuses, construct a Timeline.""" self.timeline = self.build_timeline(timeline_name, statuses, local) self.timeline.refresh_status_details() # Draw first status self.refresh_footer(self.timeline) self.body = self.timeline def _done_next(statuses): """Process sequential batch of statuses, adds statuses to the existing timeline.""" self.timeline.append_statuses(statuses) return self.run_in_thread(_load_statuses, done_callback=_done_initial if is_initial else _done_next) def async_load_instance(self): """ Attempt to update max_toot_chars from instance data. Does not work on vanilla Mastodon, works on Pleroma. See: https://github.com/tootsuite/mastodon/issues/4915 Also attempt to update translation flag from instance data. Translation is only present on Mastodon 4+ servers where the administrator has enabled this feature. See: https://github.com/mastodon/mastodon/issues/19328 """ def _load_instance(): return api.get_instance(self.app.base_url).json() def _done(instance): self.max_toot_chars = get_max_toot_chars(instance, DEFAULT_MAX_TOOT_CHARS) logger.info(f"Max toot chars set to: {self.max_toot_chars}") if "translation" in instance: # instance is advertising translation service self.can_translate = instance["translation"]["enabled"] elif "version" in instance: # fallback check: # get the major version number of the server # this works for Mastodon and Pleroma version strings # Mastodon versions < 4 do not have translation service # If the version is missing, assume 0 as a fallback # Revisit this logic if Pleroma implements translation version = instance["version"] ch = "0" if not version else version[0] self.can_translate = int(ch) > 3 if ch.isnumeric() else False return self.run_in_thread(_load_instance, done_callback=_done) def async_load_preferences(self): """ Attempt to update user preferences from instance. https://docs.joinmastodon.org/methods/preferences/ """ def _load_preferences(): return api.get_preferences(self.app, self.user).json() def _done(preferences): self.preferences = preferences return self.run_in_thread(_load_preferences, done_callback=_done) def async_load_followed_accounts(self): def _load_accounts(): try: acct = f'@{self.user.username}@{self.user.instance}' self.account = api.find_account(self.app, self.user, acct) return api.following(self.app, self.user, self.account["id"]) except ApiError: # not supported by all Mastodon servers so fail silently if necessary return [] def _done_accounts(accounts): self.followed_accounts = {a["acct"] for a in accounts} self.run_in_thread(_load_accounts, done_callback=_done_accounts) def refresh_footer(self, timeline): """Show status details in footer.""" status, index, count = timeline.get_focused_status_with_counts() self.footer.set_status([ ("footer_status_bold", "[{}] ".format(timeline.name)), ] + ([status.id, " - status ", str(index + 1), " of ", str(count)] if status else ["no focused status"])) def show_status_source(self, status): self.open_overlay( widget=StatusSource(status), title="Status source", ) def clear_screen(self): self.screen.clear() def show_links(self, status): links = parse_content_links(status.original.data["content"]) if status else [] post_attachments = status.original.data["media_attachments"] or [] reblog_attachments = (status.data["reblog"]["media_attachments"] if status.data["reblog"] else None) or [] for a in post_attachments + reblog_attachments: url = a["remote_url"] or a["url"] links.append((url, a["description"] if a["description"] else url)) def _clear(*args): self.clear_screen() if links: links = list(set(links)) # deduplicate links links = sorted(links, key=lambda link: link[0]) # sort alphabetically by URL sl_widget = StatusLinks(links) urwid.connect_signal(sl_widget, "clear-screen", _clear) self.open_overlay( widget=sl_widget, title="Status links", options={"height": len(links) + 2}, ) def show_status_zoom(self, status_details): self.open_overlay( widget=StatusZoom(status_details), title="Status zoom", ) def show_exception(self, exception): self.open_overlay( widget=ExceptionStackTrace(exception), title="Unhandled Exception", ) def show_compose(self, in_reply_to=None): def _close(*args): self.close_overlay() def _post(timeline, *args): self.post_status(*args) # If the user specified --default-visibility, use that; otherwise, # try to use the server-side default visibility. If that fails, fall # back to get_default_visibility(). visibility = (self.options.default_visibility or self.preferences.get('posting:default:visibility', get_default_visibility())) composer = StatusComposer(self.max_toot_chars, self.user.username, visibility, in_reply_to) urwid.connect_signal(composer, "close", _close) urwid.connect_signal(composer, "post", _post) self.open_overlay(composer, title="Compose status") def async_edit(self, status): def _fetch_source(): return api.fetch_status_source(self.app, self.user, status.id).json() def _done(source): self.close_overlay() self.show_edit(status, source) please_wait = ModalBox("Loading status...") self.open_overlay(please_wait) self.run_in_thread(_fetch_source, done_callback=_done) def show_edit(self, status, source): def _close(*args): self.close_overlay() def _edit(timeline, *args): self.edit_status(status, *args) composer = StatusComposer(self.max_toot_chars, self.user.username, visibility=None, edit=status, source=source) urwid.connect_signal(composer, "close", _close) urwid.connect_signal(composer, "post", _edit) self.open_overlay(composer, title="Edit status") def show_goto_menu(self): user_timelines = self.config.get("timelines", {}) user_lists = api.get_lists(self.app, self.user) or [] menu = GotoMenu(user_timelines, user_lists) urwid.connect_signal(menu, "home_timeline", lambda x: self.goto_home_timeline()) urwid.connect_signal(menu, "public_timeline", lambda x, local: self.goto_public_timeline(local)) urwid.connect_signal(menu, "bookmark_timeline", lambda x, local: self.goto_bookmarks()) urwid.connect_signal(menu, "notification_timeline", lambda x, local: self.goto_notifications()) urwid.connect_signal(menu, "conversation_timeline", lambda x, local: self.goto_conversations()) urwid.connect_signal(menu, "personal_timeline", lambda x, local: self.goto_personal_timeline()) urwid.connect_signal(menu, "hashtag_timeline", lambda x, tag, local: self.goto_tag_timeline(tag, local=local)) urwid.connect_signal(menu, "list_timeline", lambda x, list_item: self.goto_list_timeline(list_item)) self.open_overlay(menu, title="Go to", options=dict( align="center", width=("relative", 60), valign="middle", height=18 + len(user_timelines) + len(user_lists), )) def show_help(self): self.open_overlay(Help(), title="Help") def show_poll(self, status): self.open_overlay( widget=Poll(self.app, self.user, status), title="Poll", ) def goto_home_timeline(self): self.timeline_generator = api.home_timeline_generator( self.app, self.user, limit=40) promise = self.async_load_timeline(is_initial=True, timeline_name="home") promise.add_done_callback(lambda *args: self.close_overlay()) def goto_public_timeline(self, local): self.timeline_generator = api.public_timeline_generator( self.app, self.user, local=local, limit=40) timeline_name = "local public" if local else "global public" promise = self.async_load_timeline(is_initial=True, timeline_name=timeline_name) promise.add_done_callback(lambda *args: self.close_overlay()) def goto_bookmarks(self): self.timeline_generator = api.bookmark_timeline_generator( self.app, self.user, limit=40) promise = self.async_load_timeline(is_initial=True, timeline_name="bookmarks") promise.add_done_callback(lambda *args: self.close_overlay()) def goto_notifications(self): self.timeline_generator = api.notification_timeline_generator( self.app, self.user, limit=40) promise = self.async_load_timeline(is_initial=True, timeline_name="notifications") promise.add_done_callback(lambda *args: self.close_overlay()) def goto_conversations(self): self.timeline_generator = api.conversation_timeline_generator( self.app, self.user, limit=40 ) promise = self.async_load_timeline( is_initial=True, timeline_name="conversations" ) promise.add_done_callback(lambda *args: self.close_overlay()) def goto_tag_timeline(self, tag, local): self.timeline_generator = api.tag_timeline_generator( self.app, self.user, tag, local=local, limit=40) promise = self.async_load_timeline( is_initial=True, timeline_name="#{}".format(tag), local=local, ) promise.add_done_callback(lambda *args: self.close_overlay()) def goto_personal_timeline(self): account_name = f"{self.user.username}@{self.user.instance}" self.timeline_generator = api.account_timeline_generator( self.app, self.user, account_name, reblogs=True, limit=40) promise = self.async_load_timeline(is_initial=True, timeline_name=f"personal {account_name}") promise.add_done_callback(lambda *args: self.close_overlay()) def goto_list_timeline(self, list_item): self.timeline_generator = api.timeline_list_generator( self.app, self.user, list_item['id'], limit=40) promise = self.async_load_timeline( is_initial=True, timeline_name=f"\N{clipboard}{list_item['title']}") promise.add_done_callback(lambda *args: self.close_overlay()) def show_media(self, status): urls = [m["url"] for m in status.original.data["media_attachments"]] if not urls: return media_viewer = self.options.media_viewer if media_viewer: try: subprocess.run([media_viewer] + urls) except FileNotFoundError: self.footer.set_error_message(f"Media viewer not found: '{media_viewer}'") except Exception as ex: self.exception = ex self.footer.set_error_message("Failed invoking media viewer. Press X to see exception.") else: self.footer.set_error_message("Media viewer not configured") def show_context_menu(self, status): # TODO: show context menu pass def show_delete_confirmation(self, status): def _delete(widget): promise = self.async_delete_status(self.timeline, status) promise.add_done_callback(lambda *args: self.close_overlay()) def _close(widget): self.close_overlay() widget = StatusDeleteConfirmation(status) urwid.connect_signal(widget, "close", _close) urwid.connect_signal(widget, "delete", _delete) self.open_overlay(widget, title="Delete status?", options=dict( align="center", width=30, valign="middle", height=4, )) def post_status(self, content, warning, visibility, in_reply_to_id): data = api.post_status( self.app, self.user, content, spoiler_text=warning, visibility=visibility, in_reply_to_id=in_reply_to_id ).json() status = self.make_status(data) # TODO: fetch new items from the timeline? self.footer.set_message("Status posted {} \\o/".format(status.id)) self.close_overlay() def edit_status(self, status, content, warning, visibility, in_reply_to_id): # We don't support editing polls (yet), so to avoid losing the poll # data from the original toot, copy it to the edit request. poll_args = {} poll = status.original.data.get('poll', None) if poll is not None: poll_args['poll_options'] = [o['title'] for o in poll['options']] poll_args['poll_multiple'] = poll['multiple'] # Convert absolute expiry time into seconds from now. expires_at = parse_datetime(poll['expires_at']) expires_in = int((expires_at - datetime.now(timezone.utc)).total_seconds()) poll_args['poll_expires_in'] = expires_in if 'hide_totals' in poll: poll_args['poll_hide_totals'] = poll['hide_totals'] data = api.edit_status( self.app, self.user, status.id, content, spoiler_text=warning, visibility=visibility, **poll_args ).json() new_status = self.make_status(data) self.footer.set_message("Status edited {} \\o/".format(status.id)) self.close_overlay() if self.timeline is not None: self.timeline.update_status(new_status) def show_account(self, account_id): account = api.whois(self.app, self.user, account_id) relationship = api.get_relationship(self.app, self.user, account_id) self.open_overlay( widget=Account(self.app, self.user, account, relationship, self.options), title="Account", ) def async_toggle_favourite(self, timeline, status): def _favourite(): api.favourite(self.app, self.user, status.id) def _unfavourite(): api.unfavourite(self.app, self.user, status.id) def _done(loop): # Create a new Status with flipped favourited flag new_data = status.data new_data["favourited"] = not status.favourited new_status = self.make_status(new_data) timeline.update_status(new_status) self.run_in_thread( _unfavourite if status.favourited else _favourite, done_callback=_done ) def async_toggle_reblog(self, timeline, status): def _reblog(): api.reblog(self.app, self.user, status.original.id, visibility=get_default_visibility()) def _unreblog(): api.unreblog(self.app, self.user, status.original.id) def _done(loop): # Create a new Status with flipped reblogged flag new_data = status.data new_status = self.make_status(new_data) new_status.original.reblogged = not status.original.reblogged timeline.update_status(new_status) # Check if status is rebloggable no_reblog_because_private = status.visibility == "private" and not status.is_mine no_reblog_because_direct = status.visibility == "direct" if no_reblog_because_private or no_reblog_because_direct: self.footer.set_error_message("You may not reblog this {} status".format(status.visibility)) return self.run_in_thread( _unreblog if status.original.reblogged else _reblog, done_callback=_done ) def async_translate(self, timeline, status): def _translate(): self.footer.set_message("Translating status {}".format(status.original.id)) try: response = api.translate(self.app, self.user, status.original.id) if response["content"]: self.footer.set_message("Status translated") else: self.footer.set_error_message("Server returned empty translation") response = None except Exception: response = None self.footer.set_error_message("Translate server error") self.loop.set_alarm_in(3, lambda *args: self.footer.clear_message()) return response def _done(response): if response is not None: status.original.translation = response["content"] status.original.translated_from = response["detected_source_language"] status.original.show_translation = True timeline.update_status(status) # If already translated, toggle showing translation if status.original.translation: status.original.show_translation = not status.original.show_translation timeline.update_status(status) else: self.run_in_thread(_translate, done_callback=_done) def async_toggle_bookmark(self, timeline, status): def _bookmark(): api.bookmark(self.app, self.user, status.id) def _unbookmark(): api.unbookmark(self.app, self.user, status.id) def _done(loop): # Create a new Status with flipped bookmarked flag new_data = status.data new_data["bookmarked"] = not status.bookmarked new_status = self.make_status(new_data) timeline.update_status(new_status) self.run_in_thread( _unbookmark if status.bookmarked else _bookmark, done_callback=_done ) def async_delete_status(self, timeline, status): def _delete(): api.delete_status(self.app, self.user, status.id) def _done(loop): timeline.remove_status(status) return self.run_in_thread(_delete, done_callback=_done) def async_load_image(self, timeline, status, path, placeholder_index): def _load(): # don't bother loading images for statuses we are not viewing now if timeline.get_focused_status().id != status.id: return if not hasattr(timeline, "images"): timeline.images = LRUCache(cache_max_bytes=self.cache_max) img = load_image(path) if img: timeline.images[str(hash(path))] = img def _done(loop): # don't bother loading images for statuses we are not viewing now if timeline.get_focused_status().id != status.id: return timeline.update_status_image(status, path, placeholder_index) return self.run_in_thread(_load, done_callback=_done) def copy_status(self, status): # TODO: copy a better version of status content # including URLs copy_to_clipboard(self.screen, status.original.data["content"]) self.footer.set_message(f"Status {status.original.id} copied") # --- Overlay handling ----------------------------------------------------- default_overlay_options = dict( align="center", width=("relative", 80), valign="middle", height=("relative", 80), ) def open_overlay(self, widget, options={}, title=""): top_widget = RoundedLineBox(widget, title=title) bottom_widget = self.body _options = self.default_overlay_options.copy() _options.update(options) self.overlay = urwid.Overlay( top_widget, bottom_widget, **_options ) self.body = self.overlay def close_overlay(self): self.body = self.overlay.bottom_w self.overlay = None if self.timeline: self.timeline.refresh_status_details() def refresh_timeline(self): # No point in refreshing the bookmarks timeline # and we don't have a good way to refresh a # list timeline yet (no reference to list ID kept) if (not self.timeline or self.timeline.name == 'bookmarks' or self.timeline.name.startswith("\N{clipboard}")): return if self.timeline.name.startswith("#"): self.timeline_generator = api.tag_timeline_generator( self.app, self.user, self.timeline.name[1:], limit=40) elif self.timeline.name.startswith("\N{clipboard}"): self.timeline_generator = api.tag_timeline_generator( self.app, self.user, self.timeline.name[1:], limit=40) else: if self.timeline.name.endswith("public"): self.timeline_generator = api.public_timeline_generator( self.app, self.user, local=self.timeline.name.startswith("local"), limit=40) elif self.timeline.name == "notifications": self.timeline_generator = api.notification_timeline_generator( self.app, self.user, limit=40) elif self.timeline.name == "conversations": self.timeline_generator = api.conversation_timeline_generator( self.app, self.user, limit=40) else: # default to home timeline self.timeline_generator = api.home_timeline_generator( self.app, self.user, limit=40) self.async_load_timeline(is_initial=True, timeline_name=self.timeline.name) # --- Keys ----------------------------------------------------------------- def unhandled_input(self, key): # TODO: this should not be in unhandled input if key in ('x', 'X'): if self.exception: self.show_exception(self.exception) elif key in ('g', 'G'): if not self.overlay: self.show_goto_menu() elif key == '?': if not self.overlay: self.show_help() elif key == ',': if not self.overlay: self.refresh_timeline() elif key == 'esc': if self.overlay: self.close_overlay() elif self.timeline.name != "home": # similar to goto_home_timeline() but without handling overlay (absent here) self.timeline_generator = api.home_timeline_generator( self.app, self.user, limit=40) self.async_load_timeline(is_initial=True, timeline_name="home") elif key in ('q', 'Q'): if self.overlay: self.close_overlay() else: raise urwid.ExitMainLoop() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/compose.py0000644000175000017500000001375314656344035016613 0ustar00ihabunekihabunekimport urwid import logging from .constants import VISIBILITY_OPTIONS from .widgets import Button, EditBox logger = logging.getLogger(__name__) class StatusComposer(urwid.Frame): """ UI for composing or editing a status message. To edit a status, provide the original status in 'edit', and optionally provide the status source (from the /status/:id/source API endpoint) in 'source'; this should have at least a 'text' member, and optionally 'spoiler_text'. If source is not provided, the formatted HTML will be presented to the user for editing. """ signals = ["close", "post"] def __init__(self, max_chars, username, visibility, in_reply_to=None, edit=None, source=None): self.in_reply_to = in_reply_to self.max_chars = max_chars self.username = username self.edit = edit self.cw_edit = None self.cw_add_button = Button("Add content warning", on_press=self.add_content_warning) self.cw_remove_button = Button("Remove content warning", on_press=self.remove_content_warning) if edit: if source is None: text = edit.data["content"] else: text = source.get("text", edit.data["content"]) if 'spoiler_text' in source: self.cw_edit = EditBox(multiline=True, allow_tab=True, edit_text=source['spoiler_text']) self.visibility = edit.data["visibility"] else: # not edit text = self.get_initial_text(in_reply_to) self.visibility = ( in_reply_to.visibility if in_reply_to else visibility ) self.content_edit = EditBox( edit_text=text, edit_pos=len(text), multiline=True, allow_tab=True) urwid.connect_signal(self.content_edit.edit, "change", self.text_changed) self.char_count = urwid.Text(["0/{}".format(max_chars)]) self.visibility_button = Button("Visibility: {}".format(self.visibility), on_press=self.choose_visibility) self.post_button = Button("Edit" if edit else "Post", on_press=self.post) self.cancel_button = Button("Cancel", on_press=self.close) contents = list(self.generate_list_items()) self.walker = urwid.SimpleListWalker(contents) self.listbox = urwid.ListBox(self.walker) return super().__init__(self.listbox) def get_initial_text(self, in_reply_to): if not in_reply_to: return "" text = '' if in_reply_to.is_mine else '@{} '.format(in_reply_to.original.account) mentions = ['@{}'.format(m["acct"]) for m in in_reply_to.mentions if m["acct"] != self.username] if mentions: text += '\n\n{}'.format(' '.join(mentions)) return text def text_changed(self, edit, text): count = self.max_chars - len(text) text = "{}/{}".format(count, self.max_chars) color = "warning" if count < 0 else "" self.char_count.set_text((color, text)) def generate_list_items(self): if self.in_reply_to: yield urwid.Text(("dim", "Replying to {}".format(self.in_reply_to.original.account))) yield urwid.AttrWrap(urwid.Divider("-"), "dim") yield urwid.Text("Status message") yield self.content_edit yield self.char_count yield urwid.Divider() if self.cw_edit: yield urwid.Text("Content warning") yield self.cw_edit yield urwid.Divider() yield self.cw_remove_button else: yield self.cw_add_button yield self.visibility_button yield self.post_button yield self.cancel_button def refresh(self): self.walker = urwid.SimpleListWalker(list(self.generate_list_items())) self.listbox.body = self.walker def choose_visibility(self, *args): list_items = [urwid.Text("Choose status visibility:")] for visibility, caption, description in VISIBILITY_OPTIONS: text = "{} - {}".format(caption, description) button = Button(text, on_press=self.set_visibility, user_data=visibility) list_items.append(button) self.walker = urwid.SimpleListWalker(list_items) self.listbox.body = self.walker # Initially focus currently chosen visibility focus_map = {v[0]: n + 1 for n, v in enumerate(VISIBILITY_OPTIONS)} focus = focus_map.get(self.visibility, 1) self.walker.set_focus(focus) def set_visibility(self, widget, visibility): self.visibility = visibility self.visibility_button.set_label("Visibility: {}".format(self.visibility)) self.refresh() self.walker.set_focus(7 if self.cw_edit else 4) def add_content_warning(self, button): self.cw_edit = EditBox(multiline=True, allow_tab=True) self.refresh() self.walker.set_focus(4) def remove_content_warning(self, button): self.cw_edit = None self.refresh() self.walker.set_focus(3) def set_error_message(self, msg): self.footer = urwid.Text(("footer_message_error", msg)) def clear_error_message(self): self.footer = None def post(self, button): self.clear_error_message() # Don't lstrip content to avoid removing intentional leading whitespace # However, do strip both sides to check if there is any content there content = self.content_edit.edit_text.rstrip() content = None if not content.strip() else content warning = self.cw_edit.edit_text.rstrip() if self.cw_edit else "" warning = None if not warning.strip() else warning if not content: self.set_error_message("Cannot post an empty message") return in_reply_to_id = self.in_reply_to.id if self.in_reply_to else None self._emit("post", content, warning, self.visibility, in_reply_to_id) def close(self, button): self._emit("close") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/constants.py0000644000175000017500000000633414656344035017157 0ustar00ihabunekihabunek# Color definitions are tuples of: # - name # - foreground (normal mode) # - background (normal mode) # - foreground (monochrome mode) # - foreground (high color mode) # - background (high color mode) # # See: # http://urwid.org/tutorial/index.html#display-attributes # http://urwid.org/manual/displayattributes.html#using-display-attributes PALETTE = [ # Components ('button', 'white', 'black'), ('button_focused', 'light gray', 'dark magenta', 'bold,underline'), ('card_author', 'yellow', ''), ('card_title', 'dark green', ''), ('columns_divider', 'white', 'dark blue'), ('content_warning', 'white', 'dark magenta'), ('editbox', 'white', 'black'), ('editbox_focused', 'white', 'dark magenta'), ('footer_message', 'dark green', ''), ('footer_message_error', 'light red', ''), ('footer_status', 'white', 'dark blue'), ('footer_status_bold', 'white, bold', 'dark blue'), ('header', 'white', 'dark blue'), ('header_bold', 'white,bold', 'dark blue', 'bold'), ('intro_bigtext', 'yellow', ''), ('intro_smalltext', 'light blue', ''), ('poll_bar', 'white', 'dark blue'), ('status_detail_account', 'dark green', ''), ('status_detail_bookmarked', 'light red', ''), ('status_detail_timestamp', 'light blue', ''), ('status_list_account', 'dark green', ''), ('status_list_selected', 'white,bold', 'dark green', 'bold,underline'), ('status_list_timestamp', 'light blue', ''), # Functional ('account', 'dark green', ''), ('hashtag', 'light cyan,bold', '', 'bold'), ('hashtag_followed', 'yellow,bold', '', 'bold'), ('link', ',italics', '', ',italics'), ('link_focused', ',italics', 'dark magenta', "underline,italics"), ('shortcut', 'light blue', ''), ('shortcut_highlight', 'white,bold', '', 'bold'), ('warning', 'light red', ''), # Visibility ('visibility_public', 'dark gray', ''), ('visibility_unlisted', 'white', ''), ('visibility_private', 'dark cyan', ''), ('visibility_direct', 'yellow', ''), # Styles ('bold', ',bold', ''), ('dim', 'dark gray', ''), ('highlight', 'yellow', ''), ('success', 'dark green', ''), # HTML tag styling ('a', ',italics', '', 'italics'), # em tag is mapped to i ('i', ',italics', '', 'italics'), # strong tag is mapped to b ('b', ',bold', '', 'bold'), # special case for bold + italic nested tags ('bi', ',bold,italics', '', ',bold,italics'), ('u', ',underline', '', ',underline'), ('del', ',strikethrough', '', ',strikethrough'), ('code', 'light gray, standout', '', ',standout'), ('pre', 'light gray, standout', '', ',standout'), ('blockquote', 'light gray', '', ''), ('h1', ',bold', '', ',bold'), ('h2', ',bold', '', ',bold'), ('h3', ',bold', '', ',bold'), ('h4', ',bold', '', ',bold'), ('h5', ',bold', '', ',bold'), ('h6', ',bold', '', ',bold'), ('class_mention_hashtag', 'light cyan', '', ''), ('class_hashtag', 'light cyan', '', ''), ] VISIBILITY_OPTIONS = [ ("public", "Public", "Post to public timelines"), ("unlisted", "Unlisted", "Do not post to public timelines"), ("private", "Private", "Post to followers only"), ("direct", "Direct", "Post to mentioned users only"), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/entities.py0000644000175000017500000000602514656344035016764 0ustar00ihabunekihabunekfrom collections import namedtuple from toot.utils.datetime import parse_datetime Author = namedtuple("Author", ["account", "display_name", "username"]) class Status: """ A wrapper around the Status entity data fetched from Mastodon. https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#status Attributes ---------- reblog : Status or None The reblogged status if it exists. original : Status If a reblog, the reblogged status, otherwise self. """ def __init__(self, data, is_mine, default_instance): """ Parameters ---------- data : dict Status data as received from Mastodon. https://docs.joinmastodon.org/api/entities/#status is_mine : bool Whether the status was created by the logged in user. default_instance : str The domain of the instance into which the user is logged in. Used to create fully qualified account names for users on the same instance. Mastodon only populates the name, not the domain. """ self.data = data self.is_mine = is_mine self.default_instance = default_instance # This can be toggled by the user self.show_sensitive = False # Set when status is translated self.show_translation = False self.translation = None self.translated_from = None # TODO: clean up self.id = self.data["id"] self.account = self._get_account() self.created_at = parse_datetime(data["created_at"]) if data.get("edited_at"): self.edited_at = parse_datetime(data["edited_at"]) else: self.edited_at = None self.author = self._get_author() self.favourited = data.get("favourited", False) self.reblogged = data.get("reblogged", False) self.bookmarked = data.get("bookmarked", False) self.in_reply_to = data.get("in_reply_to_id") self.url = data.get("url") self.mentions = data.get("mentions") self.reblog = self._get_reblog() self.visibility = data.get("visibility") @property def original(self): return self.reblog or self def _get_reblog(self): reblog = self.data.get("reblog") if not reblog: return None reblog_is_mine = self.is_mine and ( self.data["account"]["acct"] == reblog["account"]["acct"] ) return Status(reblog, reblog_is_mine, self.default_instance) def _get_author(self): acct = self.data['account']['acct'] acct = acct if "@" in acct else "{}@{}".format(acct, self.default_instance) return Author(acct, self.data['account']['display_name'], self.data['account']['username']) def _get_account(self): acct = self.data['account']['acct'] return acct if "@" in acct else "{}@{}".format(acct, self.default_instance) def __repr__(self): return "".format(self.id, self.account) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/images.py0000644000175000017500000000767214656344035016416 0ustar00ihabunekihabunekimport urwid import math import requests import warnings # If term_image is loaded use their screen implementation which handles images try: from term_image.widget import UrwidImageScreen, UrwidImage from term_image.image import BaseImage, KittyImage, ITerm2Image, BlockImage from term_image import disable_queries # prevent phantom keystrokes from PIL import Image, ImageDraw _IMAGE_PIXEL_FORMATS = frozenset({'kitty', 'iterm'}) _ImageCls = None TuiScreen = UrwidImageScreen disable_queries() def image_support_enabled(): return True def can_render_pixels(image_format): return image_format in _IMAGE_PIXEL_FORMATS def get_base_image(image, image_format, colors) -> BaseImage: # we don't autodetect kitty, iterm; we choose based on option switches global _ImageCls if not _ImageCls: _ImageCls = ( KittyImage if image_format == 'kitty' else ITerm2Image if image_format == 'iterm' else BlockImage ) _ImageCls.forced_support = True if colors == 256 and not can_render_pixels(image_format): _ImageCls.set_render_method("INDEXED") return _ImageCls(image) def resize_image(basewidth: int, baseheight: int, img: Image.Image) -> Image.Image: if baseheight and not basewidth: hpercent = baseheight / float(img.size[1]) width = math.ceil(img.size[0] * hpercent) img = img.resize((width, baseheight), Image.Resampling.LANCZOS) elif basewidth and not baseheight: wpercent = (basewidth / float(img.size[0])) hsize = int((float(img.size[1]) * float(wpercent))) img = img.resize((basewidth, hsize), Image.Resampling.LANCZOS) else: img = img.resize((basewidth, baseheight), Image.Resampling.LANCZOS) if img.mode != 'P': img = img.convert('RGB') return img def add_corners(img, rad): circle = Image.new('L', (rad * 2, rad * 2), 0) draw = ImageDraw.Draw(circle) draw.ellipse((0, 0, rad * 2, rad * 2), fill=255) alpha = Image.new('L', img.size, "white") w, h = img.size alpha.paste(circle.crop((0, 0, rad, rad)), (0, 0)) alpha.paste(circle.crop((0, rad, rad, rad * 2)), (0, h - rad)) alpha.paste(circle.crop((rad, 0, rad * 2, rad)), (w - rad, 0)) alpha.paste(circle.crop((rad, rad, rad * 2, rad * 2)), (w - rad, h - rad)) img.putalpha(alpha) return img def load_image(url): with warnings.catch_warnings(): warnings.simplefilter("ignore") # suppress "corrupt exif" output from PIL try: img = Image.open(requests.get(url, stream=True).raw) if img.format == 'PNG' and img.mode != 'RGBA': img = img.convert("RGBA") return img except Exception: return None def graphics_widget(img, image_format="block", corner_radius=0, colors=16777216) -> urwid.Widget: if not img: return urwid.SolidFill(fill_char=" ") if can_render_pixels(image_format) and corner_radius > 0: render_img = add_corners(img, 10) else: render_img = img return UrwidImage(get_base_image(render_img, image_format, colors), '<', upscale=True) # "<" means left-justify the image except ImportError: from urwid.raw_display import Screen TuiScreen = Screen def image_support_enabled(): return False def can_render_pixels(image_format: str): return False def get_base_image(image, image_format: str): return None def add_corners(img, rad): return None def load_image(url): return None def graphics_widget(img, image_format="block", corner_radius=0) -> urwid.Widget: return urwid.SolidFill(fill_char=" ") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/overlays.py0000644000175000017500000003562614656344035017015 0ustar00ihabunekihabunekimport json import traceback import urwid import webbrowser from toot import __version__ from toot import api from toot.tui.utils import highlight_keys from toot.tui.images import image_support_enabled, load_image, graphics_widget from toot.tui.widgets import Button, EditBox, SelectableText from toot.tui.richtext import html_to_widgets class StatusSource(urwid.Padding): """Shows status data, as returned by the server, as formatted JSON.""" def __init__(self, status): self.source = json.dumps(status.data, indent=4) self.filename_edit = EditBox(caption="Filename: ", edit_text=f"status-{status.id}.json") self.status_text = urwid.Text("") walker = urwid.SimpleFocusListWalker([ self.filename_edit, Button("Save", on_press=self.save_json), urwid.Divider("─"), urwid.Divider(" "), urwid.Text(self.source) ]) frame = urwid.Frame( body=urwid.ListBox(walker), footer=self.status_text ) super().__init__(frame) def save_json(self, button): filename = self.filename_edit.get_edit_text() if filename: with open(filename, "w") as f: f.write(self.source) self.status_text.set_text(("footer_message", f"Saved to {filename}")) class StatusZoom(urwid.ListBox): """Opens status in scrollable popup window""" def __init__(self, status_details): ll = list(filter(lambda x: getattr(x, "rows", None), status_details.widget_list)) walker = urwid.SimpleFocusListWalker(ll) super().__init__(walker) class StatusLinks(urwid.ListBox): """Shows status links.""" signals = ["clear-screen"] def __init__(self, links): def widget(url, title): return Button(title or url, on_press=lambda btn: self.browse(url)) walker = urwid.SimpleFocusListWalker( [widget(url, title) for url, title in links] ) super().__init__(walker) def browse(self, url): webbrowser.open(url) # force a screen refresh; necessary with console browsers self._emit("clear-screen") class ExceptionStackTrace(urwid.ListBox): """Shows an exception stack trace.""" def __init__(self, ex): lines = traceback.format_exception(type(ex), value=ex, tb=ex.__traceback__) walker = urwid.SimpleFocusListWalker([ urwid.Text(line) for line in lines ]) super().__init__(walker) class StatusDeleteConfirmation(urwid.ListBox): signals = ["delete", "close"] def __init__(self, status): def _delete(_): self._emit("delete") def _close(_): self._emit("close") walker = urwid.SimpleFocusListWalker([ Button("Yes, delete", on_press=_delete), Button("No, cancel", on_press=_close), ]) super().__init__(walker) class GotoMenu(urwid.ListBox): signals = [ "home_timeline", "public_timeline", "hashtag_timeline", "bookmark_timeline", "notification_timeline", "conversation_timeline", "personal_timeline", "list_timeline", ] def __init__(self, user_timelines, user_lists): self.hash_edit = EditBox(caption="Hashtag: ") self.message_widget = urwid.Text("") actions = list(self.generate_actions(user_timelines, user_lists)) walker = urwid.SimpleFocusListWalker(actions) super().__init__(walker) def get_hashtag(self): return self.hash_edit.edit_text.strip().lstrip("#") def generate_actions(self, user_timelines, user_lists): def _home(button): self._emit("home_timeline") def _local_public(button): self._emit("public_timeline", True) def _global_public(button): self._emit("public_timeline", False) def _personal(button): self._emit("personal_timeline", False) def _bookmarks(button): self._emit("bookmark_timeline", False) def _notifications(button): self._emit("notification_timeline", False) def _conversations(button): self._emit("conversation_timeline", False) def _hashtag(local): self.message_widget.set_text("") hashtag = self.get_hashtag() if hashtag: self._emit("hashtag_timeline", hashtag, local) else: self.message_widget.set_text(("warning", "Hashtag name required")) def mk_on_press_user_hashtag(tag, local): def on_press(btn): self._emit("hashtag_timeline", tag, local) return on_press def mk_on_press_user_list(list_item): def on_press(btn): self._emit("list_timeline", list_item) return on_press yield Button("Home timeline", on_press=_home) yield Button("Local public timeline", on_press=_local_public) yield Button("Global public timeline", on_press=_global_public) yield Button("Personal timeline", on_press=_personal) yield Button("Bookmarks", on_press=_bookmarks) yield Button("Notifications", on_press=_notifications) yield Button("Conversations", on_press=_conversations) if len(user_timelines): yield urwid.Divider() yield urwid.Text(("bold", "Shortcuts:")) # show all hashtag shortcuts for tag, cfg in sorted(user_timelines.items()): is_local = cfg["local"] yield Button(f"#{tag}" + (" (local)" if is_local else ""), on_press=mk_on_press_user_hashtag(tag, is_local)) for list_item in user_lists: yield Button(f"\N{clipboard}{list_item['title']}", on_press=mk_on_press_user_list(list_item)) yield urwid.Divider() yield self.hash_edit yield Button("Local hashtag timeline", on_press=lambda x: _hashtag(True)) yield Button("Public hashtag timeline", on_press=lambda x: _hashtag(False)) yield urwid.Divider() yield self.message_widget class Help(urwid.Padding): def __init__(self): actions = list(self.generate_contents()) walker = urwid.SimpleListWalker(actions) listbox = urwid.ListBox(walker) super().__init__(listbox, left=1, right=1) def generate_contents(self): def h(text): return highlight_keys(text, "shortcut") yield urwid.Text(("bold", "toot {}".format(__version__))) yield urwid.Divider() yield urwid.Text(("bold", "General usage")) yield urwid.Divider() yield urwid.Text(h(" [Arrow keys] or [H/J/K/L] to move around and scroll content")) yield urwid.Text(h(" [PageUp] and [PageDown] to scroll content")) yield urwid.Text(h(" [Enter] or [Space] to activate buttons and menu options")) yield urwid.Text(h(" [Esc] or [Q] to go back, close overlays, such as menus and this help text")) yield urwid.Divider() yield urwid.Text(("bold", "General keys")) yield urwid.Divider() yield urwid.Text(h(" [Q] - quit toot")) yield urwid.Text(h(" [G] - go to - switch timelines")) yield urwid.Text(h(" [E] - save/unsave (pin) current timeline")) yield urwid.Text(h(" [,] - refresh current timeline")) yield urwid.Text(h(" [?] - show this help")) yield urwid.Divider() yield urwid.Text(("bold", "Status keys")) yield urwid.Divider() yield urwid.Text("These commands are applied to the currently focused status.") yield urwid.Divider() yield urwid.Text(h(" [B] - Boost/unboost status")) yield urwid.Text(h(" [C] - Compose new status")) yield urwid.Text(h(" [F] - Favourite/unfavourite status")) yield urwid.Text(h(" [K] - Bookmark/unbookmark status")) yield urwid.Text(h(" [N] - Translate status if possible (toggle)")) yield urwid.Text(h(" [R] - Reply to current status")) yield urwid.Text(h(" [S] - Show text marked as sensitive")) yield urwid.Text(h(" [M] - Show status media")) yield urwid.Text(h(" [T] - Show status thread (replies)")) yield urwid.Text(h(" [L] - Show the status links")) yield urwid.Text(h(" [U] - Show the status data in JSON as received from the server")) yield urwid.Text(h(" [V] - Open status in default browser")) yield urwid.Text(h(" [Y] - Copy status to clipboard")) yield urwid.Text(h(" [Z] - Open status in scrollable popup window")) yield urwid.Divider() yield urwid.Text(("bold", "Links")) yield urwid.Divider() yield link("Documentation: ", "https://toot.bezdomni.net/") yield link("Project home: ", "https://github.com/ihabunek/toot/") class Account(urwid.ListBox): """Shows account data and provides various actions""" def __init__(self, app, user, account, relationship, options): self.app = app self.user = user self.account = account self.relationship = relationship self.options = options self.last_action = None self.setup_listbox() def setup_listbox(self): actions = list(self.generate_contents(self.account, self.relationship, self.last_action)) walker = urwid.SimpleListWalker(actions) super().__init__(walker) def account_header(self, account): if image_support_enabled() and account['avatar'] and not account["avatar"].endswith("missing.png"): img = load_image(account['avatar']) aimg = urwid.BoxAdapter( graphics_widget(img, image_format=self.options.image_format, corner_radius=10, colors=self.options.colors), 10) else: aimg = urwid.BoxAdapter(urwid.SolidFill(" "), 10) if image_support_enabled() and account['header'] and not account["header"].endswith("missing.png"): img = load_image(account['header']) himg = (urwid.BoxAdapter( graphics_widget(img, image_format=self.options.image_format, corner_radius=10, colors=self.options.colors), 10)) else: himg = urwid.BoxAdapter(urwid.SolidFill(" "), 10) atxt = urwid.Pile([urwid.Divider(), (urwid.Text(("account", account["display_name"]))), (urwid.Text(("highlight", "@" + self.account['acct'])))]) columns = urwid.Columns([aimg, ("weight", 9999, himg)], dividechars=2, min_width=20) header = urwid.Pile([columns, urwid.Divider(), atxt]) return header def generate_contents(self, account, relationship=None, last_action=None): if self.last_action and not self.last_action.startswith("Confirm"): yield Button(f"Confirm {self.last_action}", on_press=take_action, user_data=self) yield Button("Cancel", on_press=cancel_action, user_data=self) else: if self.user.username == account["acct"]: yield urwid.Text(("dim", "This is your account")) else: if relationship['requested']: yield urwid.Text(("dim", "< Follow request is pending >")) else: yield Button("Unfollow" if relationship['following'] else "Follow", on_press=confirm_action, user_data=self) yield Button("Unmute" if relationship['muting'] else "Mute", on_press=confirm_action, user_data=self) yield Button("Unblock" if relationship['blocking'] else "Block", on_press=confirm_action, user_data=self) yield urwid.Divider("─") yield urwid.Divider() yield self.account_header(account) if account["note"]: yield urwid.Divider() widgetlist = html_to_widgets(account["note"]) for line in widgetlist: yield (line) yield urwid.Divider() yield urwid.Text(["ID: ", ("highlight", f"{account['id']}")]) yield urwid.Text(["Since: ", ("highlight", f"{account['created_at'][:10]}")]) yield urwid.Divider() if account["bot"]: yield urwid.Text([("highlight", "Bot \N{robot face}")]) yield urwid.Divider() if account["locked"]: yield urwid.Text([("warning", "Locked \N{lock}")]) yield urwid.Divider() if "suspended" in account and account["suspended"]: yield urwid.Text([("warning", "Suspended \N{cross mark}")]) yield urwid.Divider() if relationship["followed_by"]: yield urwid.Text(("highlight", "Follows you \N{busts in silhouette}")) yield urwid.Divider() if relationship["blocked_by"]: yield urwid.Text(("warning", "Blocks you \N{no entry}")) yield urwid.Divider() yield urwid.Text(["Followers: ", ("highlight", f"{account['followers_count']}")]) yield urwid.Text(["Following: ", ("highlight", f"{account['following_count']}")]) yield urwid.Text(["Statuses: ", ("highlight", f"{account['statuses_count']}")]) if account["fields"]: for field in account["fields"]: name = field["name"].title() yield urwid.Divider() yield urwid.Text([("bold", f"{name.rstrip(':')}"), ":"]) widgetlist = html_to_widgets(field["value"]) for line in widgetlist: yield (line) if field["verified_at"]: yield urwid.Text(("success", "✓ Verified")) yield urwid.Divider() yield link("", account["url"]) def take_action(button: Button, self: Account): action = button.get_label() if action == "Confirm Follow": self.relationship = api.follow(self.app, self.user, self.account["id"]).json() elif action == "Confirm Unfollow": self.relationship = api.unfollow(self.app, self.user, self.account["id"]).json() elif action == "Confirm Mute": self.relationship = api.mute(self.app, self.user, self.account["id"]).json() elif action == "Confirm Unmute": self.relationship = api.unmute(self.app, self.user, self.account["id"]).json() elif action == "Confirm Block": self.relationship = api.block(self.app, self.user, self.account["id"]).json() elif action == "Confirm Unblock": self.relationship = api.unblock(self.app, self.user, self.account["id"]).json() self.last_action = None self.setup_listbox() def confirm_action(button: Button, self: Account): self.last_action = button.get_label() self.setup_listbox() def cancel_action(button: Button, self: Account): self.last_action = None self.setup_listbox() def link(text, url): attr_map = {"link": "link_focused"} text = SelectableText([text, ("link", url)]) urwid.connect_signal(text, "click", lambda t: webbrowser.open(url)) return urwid.AttrMap(text, "", attr_map) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/poll.py0000644000175000017500000000660314656344035016110 0ustar00ihabunekihabunekimport urwid from toot import api from toot.exceptions import ApiError from toot.utils.datetime import parse_datetime from .widgets import Button, CheckBox, RadioButton, RoundedLineBox from .richtext import html_to_widgets class Poll(urwid.ListBox): """View and vote on a poll""" def __init__(self, app, user, status): self.status = status self.app = app self.user = user self.poll = status.original.data.get("poll") self.button_group = [] self.api_exception = None self.setup_listbox() def setup_listbox(self): actions = list(self.generate_contents(self.status)) walker = urwid.SimpleListWalker(actions) super().__init__(walker) def build_linebox(self, contents): contents = urwid.Pile(list(contents)) contents = urwid.Padding(contents, left=1, right=1) return RoundedLineBox(contents) def vote(self, button_widget): poll = self.status.original.data.get("poll") choices = [] for idx, button in enumerate(self.button_group): if button.get_state(): choices.append(idx) if len(choices): try: response = api.vote(self.app, self.user, poll["id"], choices=choices) self.status.original.data["poll"] = response self.api_exception = None self.poll["voted"] = True self.poll["own_votes"] = choices except ApiError as exception: self.api_exception = exception finally: self.setup_listbox() def generate_poll_detail(self): poll = self.poll self.button_group = [] # button group for idx, option in enumerate(poll["options"]): voted_for = ( poll["voted"] and poll["own_votes"] and idx in poll["own_votes"] ) if poll["voted"] or poll["expired"]: prefix = " ✓ " if voted_for else " " yield urwid.Text(("dim", prefix + f'{option["title"]}')) else: if poll["multiple"]: checkbox = CheckBox(f'{option["title"]}') self.button_group.append(checkbox) yield checkbox else: yield RadioButton(self.button_group, f'{option["title"]}') yield urwid.Divider() poll_detail = "Poll · {} votes".format(poll["votes_count"]) if poll["expired"]: poll_detail += " · Closed" if poll["expires_at"]: expires_at = parse_datetime(poll["expires_at"]).strftime( "%Y-%m-%d %H:%M" ) poll_detail += " · Closes on {}".format(expires_at) yield urwid.Text(("dim", poll_detail)) def generate_contents(self, status): yield urwid.Divider() widgetlist = html_to_widgets(status.data["content"]) for line in widgetlist: yield (line) yield urwid.Divider() yield self.build_linebox(self.generate_poll_detail()) yield urwid.Divider() if self.poll["voted"]: yield urwid.Text(("grey", "< Already Voted >")) elif not self.poll["expired"]: yield Button("Vote", on_press=self.vote) if self.api_exception: yield urwid.Divider() yield urwid.Text("warning", str(self.api_exception)) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.855274 toot-0.44.1/toot/tui/richtext/0000755000175000017500000000000014656360001016404 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/richtext/__init__.py0000644000175000017500000000077114656344035020533 0ustar00ihabunekihabunekimport urwid from toot.tui.utils import highlight_hashtags from toot.utils import format_content from typing import List try: from .richtext import html_to_widgets, url_to_widget except ImportError: # Fallback if urwidgets are not available def html_to_widgets(html: str) -> List[urwid.Widget]: return [ urwid.Text(highlight_hashtags(line)) for line in format_content(html) ] def url_to_widget(url: str): return urwid.Text(("link", url)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/tui/richtext/richtext.py0000644000175000017500000003263014656344035020625 0ustar00ihabunekihabunekimport re import urwid import unicodedata from bs4.element import NavigableString, Tag from toot.tui.constants import PALETTE from toot.utils import parse_html, urlencode_url from typing import List, Tuple from urwid.util import decompose_tagmarkup from urwidgets import Hyperlink, TextEmbed STYLE_NAMES = [p[0] for p in PALETTE] # NOTE: update this list if Mastodon starts supporting more block tags BLOCK_TAGS = ["p", "pre", "li", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6"] def html_to_widgets(html, recovery_attempt=False) -> List[urwid.Widget]: """Convert html to urwid widgets""" widgets: List[urwid.Widget] = [] html = unicodedata.normalize("NFKC", html) soup = parse_html(html) first_tag = True for e in soup.body or soup: if isinstance(e, NavigableString): if first_tag and not recovery_attempt: # if our first "tag" is a navigable string # the HTML is out of spec, doesn't start with a tag, # we see this in content from Pixelfed servers. # attempt a fix by wrapping the HTML with

return html_to_widgets(f"

{html}

", recovery_attempt=True) else: continue else: name = e.name # if our HTML starts with a tag, but not a block tag # the HTML is out of spec. Attempt a fix by wrapping the # HTML with

if (first_tag and not recovery_attempt and name not in BLOCK_TAGS): return html_to_widgets(f"

{html}

", recovery_attempt=True) markup = render(name, e) first_tag = False if not isinstance(markup, urwid.Widget): # plaintext, so create a padded text widget txt = text_to_widget("", markup) markup = urwid.Padding( txt, align="left", width=("relative", 100), min_width=None, ) widgets.append(markup) # separate top level widgets with a blank line widgets.append(urwid.Divider(" ")) return widgets[:-1] # but suppress the last blank line def url_to_widget(url: str): try: widget = len(url), urwid.Filler(Hyperlink(url, "link", url)) except ValueError: widget = len(url), urwid.Filler(urwid.Text(url)) # don't style as link return TextEmbed(widget) def inline_tag_to_text(tag) -> Tuple: """Convert html tag to plain text with tag as attributes recursively""" markups = process_inline_tag_children(tag) if not markups: return (tag.name, "") return (tag.name, markups) def process_inline_tag_children(tag) -> List: """Recursively retrieve all children and convert to a list of markup text""" markups = [] for child in tag.children: if isinstance(child, Tag): markup = render(child.name, child) markups.append(markup) else: markups.append(child) return markups URL_PATTERN = re.compile(r"(^.+)\x03(.+$)") def text_to_widget(attr, markup) -> urwid.Widget: markup_list = [] for run in markup: if isinstance(run, tuple): txt, attr_list = decompose_tagmarkup(run) # find anchor titles with an ETX separator followed by href match = URL_PATTERN.match(txt) if match: label, url = match.groups() anchor_attr = get_best_anchor_attr(attr_list) try: markup_list.append(( len(label), urwid.Filler(Hyperlink(url, anchor_attr, label)), )) except ValueError: markup_list.append(( len(label), urwid.Filler(urwid.Text(url)), # don't style as link )) else: markup_list.append(run) else: markup_list.append(run) return TextEmbed(markup_list) def process_block_tag_children(tag) -> List[urwid.Widget]: """Recursively retrieve all children and convert to a list of widgets any inline tags containing text will be converted to Text widgets""" pre_widget_markups = [] post_widget_markups = [] child_widgets = [] found_nested_widget = False for child in tag.children: if isinstance(child, Tag): # child is a nested tag; process using custom method # or default to inline_tag_to_text result = render(child.name, child) if isinstance(result, urwid.Widget): found_nested_widget = True child_widgets.append(result) else: if not found_nested_widget: pre_widget_markups.append(result) else: post_widget_markups.append(result) else: # child is text; append to the appropriate markup list if not found_nested_widget: pre_widget_markups.append(child) else: post_widget_markups.append(child) widget_list = [] if len(pre_widget_markups): widget_list.append(text_to_widget(tag.name, pre_widget_markups)) if len(child_widgets): widget_list += child_widgets if len(post_widget_markups): widget_list.append(text_to_widget(tag.name, post_widget_markups)) return widget_list def get_urwid_attr_name(tag) -> str: """Get the class name and translate to a name suitable for use as an urwid text attribute name""" if "class" in tag.attrs: clss = tag.attrs["class"] if len(clss) > 0: style_name = "class_" + "_".join(clss) # return the class name, only if we # find it as a defined palette name if style_name in STYLE_NAMES: return style_name # fallback to returning the tag name return tag.name def basic_block_tag_handler(tag) -> urwid.Widget: """default for block tags that need no special treatment""" return urwid.Pile(process_block_tag_children(tag)) def get_best_anchor_attr(attrib_list) -> str: if not attrib_list: return "" flat_al = list(flatten(attrib_list)) for a in flat_al[0]: # ref: https://docs.joinmastodon.org/spec/activitypub/ # these are the class names (translated to attrib names) # that we can support for display try: if a[0] in ["class_hashtag", "class_mention_hashtag", "class_mention"]: return a[0] except KeyError: continue return "a" def render(attr: str, content: str): if attr in ["a"]: return render_anchor(content) if attr in ["blockquote"]: return render_blockquote(content) if attr in ["br"]: return render_br(content) if attr in ["em"]: return render_em(content) if attr in ["ol"]: return render_ol(content) if attr in ["pre"]: return render_pre(content) if attr in ["span"]: return render_span(content) if attr in ["b", "strong"]: return render_strong(content) if attr in ["ul"]: return render_ul(content) # Glitch-soc and Pleroma allow

...

in content # Mastodon (PR #23913) does not; header tags are converted to

if attr in ["p", "div", "li", "h1", "h2", "h3", "h4", "h5", "h6"]: return basic_block_tag_handler(content) # Fall back to inline_tag_to_text handler return inline_tag_to_text(content) def render_anchor(tag) -> Tuple: """anchor tag handler""" markups = process_inline_tag_children(tag) if not markups: return (tag.name, "") href = tag.attrs["href"] title, attrib_list = decompose_tagmarkup(markups) if not attrib_list: attrib_list = [tag] if href: # urlencode the path and query portions of the URL href = urlencode_url(href) # use ASCII ETX (end of record) as a # delimiter between the title and the HREF title += f"\x03{href}" attr = get_best_anchor_attr(attrib_list) if attr == "a": # didn't find an attribute to use # in the child markup, so let's # try the anchor tag's own attributes attr = get_urwid_attr_name(tag) # hashtag anchors have a class of "mention hashtag" # or "hashtag" # we'll return style "class_mention_hashtag" # or "class_hashtag" # in that case; see corresponding palette entry # in constants.py controlling hashtag highlighting return (attr, title) def render_blockquote(tag) -> urwid.Widget: widget_list = process_block_tag_children(tag) blockquote_widget = urwid.LineBox( urwid.Padding( urwid.Pile(widget_list), align="left", width=("relative", 100), min_width=None, left=1, right=1, ), tlcorner="", tline="", lline="│", trcorner="", blcorner="", rline="", bline="", brcorner="", ) return urwid.Pile([urwid.AttrMap(blockquote_widget, "blockquote")]) def render_br(tag) -> Tuple: return ("br", "\n") def render_em(tag) -> Tuple: # to simplify the number of palette entries # translate EM to I (italic) markups = process_inline_tag_children(tag) if not markups: return ("i", "") # special case processing for bold and italic for parent in tag.parents: if parent.name == "b" or parent.name == "strong": return ("bi", markups) return ("i", markups) def render_ol(tag) -> urwid.Widget: """ordered list tag handler""" widgets = [] list_item_num = 1 increment = -1 if tag.has_attr("reversed") else 1 # get ol start= attribute if present if tag.has_attr("start") and len(tag.attrs["start"]) > 0: try: list_item_num = int(tag.attrs["start"]) except ValueError: pass for li in tag.find_all("li", recursive=False): markup = render("li", li) # li value= attribute will change the item number # it also overrides any ol start= attribute if li.has_attr("value") and len(li.attrs["value"]) > 0: try: list_item_num = int(li.attrs["value"]) except ValueError: pass if not isinstance(markup, urwid.Widget): txt = text_to_widget("li", [str(list_item_num), ". ", markup]) # 1. foo, 2. bar, etc. widgets.append(txt) else: txt = text_to_widget("li", [str(list_item_num), ". "]) columns = urwid.Columns( [txt, ("weight", 9999, markup)], dividechars=1, min_width=3 ) widgets.append(columns) list_item_num += increment return urwid.Pile(widgets) def render_pre(tag) -> urwid.Widget: #
 tag spec says that text should not wrap,
    # but horizontal screen space is at a premium
    # and we have no horizontal scroll bar, so allow
    # wrapping.

    widget_list = [urwid.Divider(" ")]
    widget_list += process_block_tag_children(tag)

    pre_widget = urwid.Padding(
        urwid.Pile(widget_list),
        align="left",
        width=("relative", 100),
        min_width=None,
        left=1,
        right=1,
    )
    return urwid.Pile([urwid.AttrMap(pre_widget, "pre")])


def render_span(tag) -> Tuple:
    markups = process_inline_tag_children(tag)

    if not markups:
        return (tag.name, "")

    # span inherits its parent's class definition
    # unless it has a specific class definition
    # of its own

    if "class" in tag.attrs:
        # uncomment the following code to hide all HTML marked
        # invisible (generally, the http:// prefix of URLs)
        # could be a user preference, it's only advisable if
        # the terminal supports OCS 8 hyperlinks (and that's not
        # automatically detectable)

        # if "invisible" in tag.attrs["class"]:
        #     return (tag.name, "")

        style_name = get_urwid_attr_name(tag)

        if style_name != "span":
            # unique class name matches an entry in our palette
            return (style_name, markups)

    if tag.parent:
        return (get_urwid_attr_name(tag.parent), markups)
    else:
        # fallback
        return ("span", markups)


def render_strong(tag) -> Tuple:
    # to simplify the number of palette entries
    # translate STRONG to B (bold)
    markups = process_inline_tag_children(tag)
    if not markups:
        return ("b", "")

    # special case processing for bold and italic
    for parent in tag.parents:
        if parent.name == "i" or parent.name == "em":
            return ("bi", markups)

    return ("b", markups)


def render_ul(tag) -> urwid.Widget:
    """unordered list tag handler"""

    widgets = []

    for li in tag.find_all("li", recursive=False):
        markup = render("li", li)

        if not isinstance(markup, urwid.Widget):
            txt = text_to_widget("li", ["\N{bullet} ", markup])
            # * foo, * bar, etc.
            widgets.append(txt)
        else:
            txt = text_to_widget("li", ["\N{bullet} "])
            columns = urwid.Columns(
                [txt, ("weight", 9999, markup)], dividechars=1, min_width=3
            )
            widgets.append(columns)

    return urwid.Pile(widgets)


def flatten(data):
    if isinstance(data, tuple):
        for x in data:
            yield from flatten(x)
    else:
        yield data
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0
toot-0.44.1/toot/tui/scroll.py0000644000175000017500000004355514656344035016447 0ustar00ihabunekihabunek# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details
# http://www.gnu.org/licenses/gpl-3.0.txt

import urwid
from urwid.widget import BOX, FIXED, FLOW

# Scroll actions
SCROLL_LINE_UP        = 'line up'
SCROLL_LINE_DOWN      = 'line down'
SCROLL_PAGE_UP        = 'page up'
SCROLL_PAGE_DOWN      = 'page down'
SCROLL_TO_TOP         = 'to top'
SCROLL_TO_END         = 'to end'

# Scrollbar positions
SCROLLBAR_LEFT  = 'left'
SCROLLBAR_RIGHT = 'right'

class Scrollable(urwid.WidgetDecoration):
    def sizing(self):
        return frozenset([BOX,])

    def selectable(self):
        return True

    def __init__(self, widget, force_forward_keypress = False):
        """Box widget that makes a fixed or flow widget vertically scrollable

        TODO: Focusable widgets are handled, including switching focus, but
        possibly not intuitively, depending on the arrangement of widgets.  When
        switching focus to a widget that is ouside of the visible part of the
        original widget, the canvas scrolls up/down to the focused widget.  It
        would be better to scroll until the next focusable widget is in sight
        first.  But for that to work we must somehow obtain a list of focusable
        rows in the original canvas.
        """
        if not any(s in widget.sizing() for s in (FIXED, FLOW)):
            raise ValueError('Not a fixed or flow widget: %r' % widget)
        self._trim_top = 0
        self._scroll_action = None
        self._forward_keypress = None
        self._old_cursor_coords = None
        self._rows_max_cached = 0
        self.force_forward_keypress = force_forward_keypress
        self.__super.__init__(widget)

    def render(self, size, focus=False):
        maxcol, maxrow = size

        # Render complete original widget
        ow = self._original_widget
        ow_size = self._get_original_widget_size(size)
        canv_full = ow.render(ow_size, focus)

        # Make full canvas editable
        canv = urwid.CompositeCanvas(canv_full)
        canv_cols, canv_rows = canv.cols(), canv.rows()

        if canv_cols <= maxcol:
            pad_width = maxcol - canv_cols
            if pad_width > 0:
                # Canvas is narrower than available horizontal space
                canv.pad_trim_left_right(0, pad_width)

        if canv_rows <= maxrow:
            fill_height = maxrow - canv_rows
            if fill_height > 0:
                # Canvas is lower than available vertical space
                canv.pad_trim_top_bottom(0, fill_height)

        if canv_cols <= maxcol and canv_rows <= maxrow:
            # Canvas is small enough to fit without trimming
            return canv

        self._adjust_trim_top(canv, size)

        # Trim canvas if necessary
        trim_top = self._trim_top
        trim_end = canv_rows - maxrow - trim_top
        trim_right = canv_cols - maxcol
        if trim_top > 0:
            canv.trim(trim_top)
        if trim_end > 0:
            canv.trim_end(trim_end)
        if trim_right > 0:
            canv.pad_trim_left_right(0, -trim_right)

        # Disable cursor display if cursor is outside of visible canvas parts
        if canv.cursor is not None:
            curscol, cursrow = canv.cursor
            if cursrow >= maxrow or cursrow < 0:
                canv.cursor = None

        # Figure out whether we should forward keypresses to original widget
        if canv.cursor is not None:
            # Trimmed canvas contains the cursor, e.g. in an Edit widget
            self._forward_keypress = True
        else:
            if canv_full.cursor is not None:
                # Full canvas contains the cursor, but scrolled out of view
                self._forward_keypress = False

                # Reset cursor position on page/up down scrolling
                try:
                    if hasattr(ow, "automove_cursor_on_scroll") and ow.automove_cursor_on_scroll:
                        pwi = 0
                        ch = 0
                        last_hidden = False
                        first_visible = False
                        for w,o in ow.contents:
                            wcanv = w.render((maxcol,))
                            wh = wcanv.rows()
                            if wh:
                                ch += wh

                            if not last_hidden and ch >= self._trim_top:
                                last_hidden = True

                            elif last_hidden:
                                if not first_visible:
                                    first_visible = True

                                if w.selectable():
                                    ow.focus_item = pwi

                                    st = None
                                    nf = ow.get_focus()
                                    if hasattr(nf, "key_timeout"):
                                        st = nf
                                    elif hasattr(nf, "original_widget"):
                                        no = nf.original_widget
                                        if hasattr(no, "original_widget"):
                                            st = no.original_widget
                                        else:
                                            if hasattr(no, "key_timeout"):
                                                st = no

                                    if st and hasattr(st, "key_timeout") and hasattr(st, "keypress") and callable(st.keypress):
                                        st.keypress(None, None)

                                    break

                            pwi += 1
                except Exception as e:
                    pass

            else:
                # Original widget does not have a cursor, but may be selectable

                # FIXME: Using ow.selectable() is bad because the original
                # widget may be selectable because it's a container widget with
                # a key-grabbing widget that is scrolled out of view.
                # ow.selectable() returns True anyway because it doesn't know
                # how we trimmed our canvas.
                #
                # To fix this, we need to resolve ow.focus and somehow
                # ask canv whether it contains bits of the focused widget.  I
                # can't see a way to do that.
                if ow.selectable():
                    self._forward_keypress = True
                else:
                    self._forward_keypress = False

        return canv

    def keypress(self, size, key):
        # Maybe offer key to original widget
        if self._forward_keypress or self.force_forward_keypress:
            ow = self._original_widget
            ow_size = self._get_original_widget_size(size)

            # Remember previous cursor position if possible
            if hasattr(ow, 'get_cursor_coords'):
                self._old_cursor_coords = ow.get_cursor_coords(ow_size)

            key = ow.keypress(ow_size, key)
            if key is None:
                return None

        # Handle up/down, page up/down, etc
        command_map = self._command_map
        if command_map[key] == urwid.CURSOR_UP:
            self._scroll_action = SCROLL_LINE_UP
        elif command_map[key] == urwid.CURSOR_DOWN:
            self._scroll_action = SCROLL_LINE_DOWN

        elif command_map[key] == urwid.CURSOR_PAGE_UP:
            self._scroll_action = SCROLL_PAGE_UP
        elif command_map[key] == urwid.CURSOR_PAGE_DOWN:
            self._scroll_action = SCROLL_PAGE_DOWN

        elif command_map[key] == urwid.CURSOR_MAX_LEFT:   # 'home'
            self._scroll_action = SCROLL_TO_TOP
        elif command_map[key] == urwid.CURSOR_MAX_RIGHT:  # 'end'
            self._scroll_action = SCROLL_TO_END

        else:
            return key

        self._invalidate()

    def mouse_event(self, size, event, button, col, row, focus):
        ow = self._original_widget
        if hasattr(ow, 'mouse_event'):
            ow_size = self._get_original_widget_size(size)
            row += self._trim_top
            return ow.mouse_event(ow_size, event, button, col, row, focus)
        else:
            return False

    def _adjust_trim_top(self, canv, size):
        """Adjust self._trim_top according to self._scroll_action"""
        action = self._scroll_action
        self._scroll_action = None

        maxcol, maxrow = size
        trim_top = self._trim_top
        canv_rows = canv.rows()

        if trim_top < 0:
            # Negative trim_top values use bottom of canvas as reference
            trim_top = canv_rows - maxrow + trim_top + 1

        if canv_rows <= maxrow:
            self._trim_top = 0  # Reset scroll position
            return

        def ensure_bounds(new_trim_top):
            return max(0, min(canv_rows - maxrow, new_trim_top))

        if action == SCROLL_LINE_UP:
            self._trim_top = ensure_bounds(trim_top - 1)
        elif action == SCROLL_LINE_DOWN:
            self._trim_top = ensure_bounds(trim_top + 1)

        elif action == SCROLL_PAGE_UP:
            self._trim_top = ensure_bounds(trim_top - maxrow + 1)
        elif action == SCROLL_PAGE_DOWN:
            self._trim_top = ensure_bounds(trim_top + maxrow - 1)

        elif action == SCROLL_TO_TOP:
            self._trim_top = 0
        elif action == SCROLL_TO_END:
            self._trim_top = canv_rows - maxrow

        else:
            self._trim_top = ensure_bounds(trim_top)

        # If the cursor was moved by the most recent keypress, adjust trim_top
        # so that the new cursor position is within the displayed canvas part.
        # But don't do this if the cursor is at the top/bottom edge so we can still scroll out
        if self._old_cursor_coords is not None and self._old_cursor_coords != canv.cursor and canv.cursor != None:
            self._old_cursor_coords = None
            curscol, cursrow = canv.cursor
            if cursrow < self._trim_top:
                self._trim_top = cursrow
            elif cursrow >= self._trim_top + maxrow:
                self._trim_top = max(0, cursrow - maxrow + 1)

    def _get_original_widget_size(self, size):
        ow = self._original_widget
        sizing = ow.sizing()
        if FLOW in sizing:
            return (size[0],)
        elif FIXED in sizing:
            return ()

    def get_scrollpos(self, size=None, focus=False):
        """Current scrolling position

        Lower limit is 0, upper limit is the maximum number of rows with the
        given maxcol minus maxrow.

        NOTE: The returned value may be too low or too high if the position has
        changed but the widget wasn't rendered yet.
        """
        return self._trim_top

    def set_scrollpos(self, position):
        """Set scrolling position

        If `position` is positive it is interpreted as lines from the top.
        If `position` is negative it is interpreted as lines from the bottom.

        Values that are too high or too low values are automatically adjusted
        during rendering.
        """
        self._trim_top = int(position)
        self._invalidate()

    def rows_max(self, size=None, focus=False):
        """Return the number of rows for `size`

        If `size` is not given, the currently rendered number of rows is returned.
        """
        if size is not None:
            ow = self._original_widget
            ow_size = self._get_original_widget_size(size)
            sizing = ow.sizing()
            if FIXED in sizing:
                self._rows_max_cached = ow.pack(ow_size, focus)[1]
            elif FLOW in sizing:
                self._rows_max_cached = ow.rows(ow_size, focus)
            else:
                raise RuntimeError('Not a flow/box widget: %r' % self._original_widget)
        return self._rows_max_cached


class ScrollBar(urwid.WidgetDecoration):
    def sizing(self):
        return frozenset((BOX,))

    def selectable(self):
        return True

    def __init__(self, widget, thumb_char=u'\u2588', trough_char=' ',
                 side=SCROLLBAR_RIGHT, width=1):
        """Box widget that adds a scrollbar to `widget`

        `widget` must be a box widget with the following methods:
          - `get_scrollpos` takes the arguments `size` and `focus` and returns
            the index of the first visible row.
          - `set_scrollpos` (optional; needed for mouse click support) takes the
            index of the first visible row.
          - `rows_max` takes `size` and `focus` and returns the total number of
            rows `widget` can render.

        `thumb_char` is the character used for the scrollbar handle.
        `trough_char` is used for the space above and below the handle.
        `side` must be 'left' or 'right'.
        `width` specifies the number of columns the scrollbar uses.
        """
        if BOX not in widget.sizing():
            raise ValueError('Not a box widget: %r' % widget)
        self.__super.__init__(widget)
        self._thumb_char = thumb_char
        self._trough_char = trough_char
        self.scrollbar_side = side
        self.scrollbar_width = max(1, width)
        self._original_widget_size = (0, 0)

    def render(self, size, focus=False):
        maxcol, maxrow = size

        sb_width = self._scrollbar_width
        ow_size = (max(0, maxcol - sb_width), maxrow)
        sb_width = maxcol - ow_size[0]

        ow = self._original_widget
        ow_base = self.scrolling_base_widget
        ow_rows_max = ow_base.rows_max(size, focus)
        if ow_rows_max <= maxrow:
            # Canvas fits without scrolling - no scrollbar needed
            self._original_widget_size = size
            return ow.render(size, focus)
        ow_rows_max = ow_base.rows_max(ow_size, focus)

        ow_canv = ow.render(ow_size, focus)
        self._original_widget_size = ow_size

        pos = ow_base.get_scrollpos(ow_size, focus)
        posmax = ow_rows_max - maxrow

        # Thumb shrinks/grows according to the ratio of
        #  / 
        thumb_weight = min(1, maxrow / max(1, ow_rows_max))
        thumb_height = max(1, round(thumb_weight * maxrow))

        # Thumb may only touch top/bottom if the first/last row is visible
        top_weight = float(pos) / max(1, posmax)
        top_height = int((maxrow - thumb_height) * top_weight)
        if top_height == 0 and top_weight > 0:
            top_height = 1

        # Bottom part is remaining space
        bottom_height = maxrow - thumb_height - top_height
        assert thumb_height + top_height + bottom_height == maxrow

        # Create scrollbar canvas
        # Creating SolidCanvases of correct height may result in "cviews do not
        # fill gaps in shard_tail!" or "cviews overflow gaps in shard_tail!"
        # exceptions. Stacking the same SolidCanvas is a workaround.
        # https://github.com/urwid/urwid/issues/226#issuecomment-437176837
        top = urwid.SolidCanvas(self._trough_char, sb_width, 1)
        thumb = urwid.SolidCanvas(self._thumb_char, sb_width, 1)
        bottom = urwid.SolidCanvas(self._trough_char, sb_width, 1)
        sb_canv = urwid.CanvasCombine(
            [(top, None, False)] * top_height +
            [(thumb, None, False)] * thumb_height +
            [(bottom, None, False)] * bottom_height,
        )

        combinelist = [(ow_canv, None, True, ow_size[0]),
                       (sb_canv, None, False, sb_width)]
        if self._scrollbar_side != SCROLLBAR_LEFT:
            return urwid.CanvasJoin(combinelist)
        else:
            return urwid.CanvasJoin(reversed(combinelist))

    @property
    def scrollbar_width(self):
        """Columns the scrollbar uses"""
        return max(1, self._scrollbar_width)

    @scrollbar_width.setter
    def scrollbar_width(self, width):
        self._scrollbar_width = max(1, int(width))
        self._invalidate()

    @property
    def scrollbar_side(self):
        """Where to display the scrollbar; must be 'left' or 'right'"""
        return self._scrollbar_side

    @scrollbar_side.setter
    def scrollbar_side(self, side):
        if side not in (SCROLLBAR_LEFT, SCROLLBAR_RIGHT):
            raise ValueError('scrollbar_side must be "left" or "right", not %r' % side)
        self._scrollbar_side = side
        self._invalidate()

    @property
    def scrolling_base_widget(self):
        """Nearest `original_widget` that is compatible with the scrolling API"""
        def orig_iter(w):
            while hasattr(w, 'original_widget'):
                w = w.original_widget
                yield w
            yield w

        def is_scrolling_widget(w):
            return hasattr(w, 'get_scrollpos') and hasattr(w, 'rows_max')

        for w in orig_iter(self):
            if is_scrolling_widget(w):
                return w
        raise ValueError('Not compatible to be wrapped by ScrollBar: %r' % w)

    def keypress(self, size, key):
        return self._original_widget.keypress(self._original_widget_size, key)

    def mouse_event(self, size, event, button, col, row, focus):
        ow = self._original_widget
        ow_size = self._original_widget_size
        handled = False
        if hasattr(ow, 'mouse_event'):
            handled = ow.mouse_event(ow_size, event, button, col, row, focus)

        if not handled and hasattr(ow, 'set_scrollpos'):
            if button == 4:    # scroll wheel up
                pos = ow.get_scrollpos(ow_size)
                newpos = pos - 1
                if newpos < 0:
                    newpos = 0
                ow.set_scrollpos(newpos)
                return True
            elif button == 5:  # scroll wheel down
                pos = ow.get_scrollpos(ow_size)
                ow.set_scrollpos(pos + 1)
                return True

        return False
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0
toot-0.44.1/toot/tui/timeline.py0000644000175000017500000005610114656344035016746 0ustar00ihabunekihabunekimport logging
import math
import urwid
import webbrowser

from typing import List, Optional

from toot.tui import app

from toot.tui.richtext import html_to_widgets, url_to_widget
from toot.utils.datetime import parse_datetime, time_ago
from toot.utils.language import language_name

from toot.entities import Status
from toot.tui.scroll import Scrollable, ScrollBar

from toot.tui.utils import highlight_keys
from toot.tui.images import image_support_enabled, graphics_widget, can_render_pixels
from toot.tui.widgets import SelectableText, SelectableColumns, RoundedLineBox


logger = logging.getLogger("toot")
screen = urwid.raw_display.Screen()


class Timeline(urwid.Columns):
    """
    Displays a list of statuses to the left, and status details on the right.
    """

    signals = [
        "close",  # Close thread
        "focus",  # Focus changed
        "next",   # Fetch more statuses
        "save",   # Save current timeline
    ]

    def __init__(
        self,
        tui: "app.TUI",
        name: str,
        statuses: List[Status],
        focus: int = 0,
        is_thread: bool = False
    ):
        self.tui = tui
        self.name = name
        self.is_thread = is_thread
        self.statuses = statuses
        self.status_list = self.build_status_list(statuses, focus=focus)
        self.can_render_pixels = can_render_pixels(self.tui.options.image_format)

        try:
            focused_status = statuses[focus]
        except IndexError:
            focused_status = None

        self.status_details = StatusDetails(self, focused_status)
        status_widget = self.wrap_status_details(self.status_details)

        super().__init__([
            ("weight", 40, self.status_list),
            ("weight", 0, urwid.AttrWrap(urwid.SolidFill("│"), "columns_divider")),
            ("weight", 60, status_widget),
        ])

    def wrap_status_details(self, status_details: "StatusDetails") -> urwid.Widget:
        """Wrap StatusDetails widget with a scrollbar and footer."""
        self.status_detail_scrollable = Scrollable(urwid.Padding(status_details, right=1))
        return urwid.Padding(
            urwid.Frame(
                body=ScrollBar(
                    self.status_detail_scrollable,
                    thumb_char="\u2588",
                    trough_char="\u2591",
                ),
                footer=self.get_option_text(status_details.status),
            ),
            left=1
        )

    def build_status_list(self, statuses, focus):
        items = [self.build_list_item(status) for status in statuses]
        walker = urwid.SimpleFocusListWalker(items)
        walker.set_focus(focus)
        urwid.connect_signal(walker, "modified", self.modified)
        return urwid.ListBox(walker)

    def build_list_item(self, status):
        item = StatusListItem(status, self.tui.options.relative_datetimes)
        urwid.connect_signal(item, "click", lambda *args:
            self.tui.show_context_menu(status))
        return urwid.AttrMap(item, None, focus_map={
            "status_list_account": "status_list_selected",
            "status_list_timestamp": "status_list_selected",
            "highlight": "status_list_selected",
            "dim": "status_list_selected",
            None: "status_list_selected",
        })

    def get_option_text(self, status: Optional[Status]) -> Optional[urwid.Text]:
        if not status:
            return None

        poll = status.original.data.get("poll")
        show_media = status.original.data["media_attachments"] and self.tui.options.media_viewer

        options = [
            "[A]ccount" if not status.is_mine else "",
            "[B]oost",
            "[D]elete" if status.is_mine else "",
            "[E]dit" if status.is_mine else "",
            "B[o]okmark",
            "[F]avourite",
            "[V]iew",
            "[T]hread" if not self.is_thread else "",
            "L[i]nks",
            "[M]edia" if show_media else "",
            "[R]eply",
            "[P]oll" if poll and not poll["expired"] else "",
            "So[u]rce",
            "[Z]oom",
            "Tra[n]slate" if self.tui.can_translate else "",
            "Cop[y]",
            "Help([?])",
        ]
        options = "\n" + " ".join(o for o in options if o)
        options = highlight_keys(options, "shortcut_highlight", "shortcut")
        return urwid.Text(options)

    def get_focused_status(self):
        try:
            return self.statuses[self.status_list.body.focus]
        except TypeError:
            return None

    def get_focused_status_with_counts(self):
        """Returns a tuple of:
            * focused status
            * focused status' index in the status list
            * length of the status list
        """
        return (
            self.get_focused_status(),
            self.status_list.body.focus,
            len(self.statuses),
        )

    def modified(self):
        """Called when the list focus switches to a new status"""
        status, index, count = self.get_focused_status_with_counts()

        if image_support_enabled:
            clear_op = getattr(self.tui.screen, "clear_images", None)
            # term-image's screen implementation has clear_images(),
            # urwid's implementation does not.
            # TODO: it would be nice not to check this each time thru

            if callable(clear_op):
                self.tui.screen.clear_images()

        self.draw_status_details(status)
        self._emit("focus")

    def refresh_status_details(self):
        """Redraws the details of the focused status."""
        status = self.get_focused_status()
        pos = self.status_detail_scrollable.get_scrollpos()
        self.draw_status_details(status)
        self.status_detail_scrollable.set_scrollpos(pos)

    def draw_status_details(self, status):
        self.status_details = StatusDetails(self, status)
        widget = self.wrap_status_details(self.status_details)
        self.contents[2] = widget, ("weight", 60, False)

    def keypress(self, size, key):
        status = self.get_focused_status()
        command = self._command_map[key]

        if not status:
            return super().keypress(size, key)

        # If down is pressed on last status in list emit a signal to load more.
        # TODO: Consider pre-loading statuses earlier
        if command in [urwid.CURSOR_DOWN, urwid.CURSOR_PAGE_DOWN] \
                and self.status_list.body.focus:
            index = self.status_list.body.focus + 1
            count = len(self.statuses)
            if index >= count:
                self._emit("next")

        if key in ("a", "A"):
            account_id = status.original.data["account"]["id"]
            self.tui.show_account(account_id)
            return

        if key in ("b", "B"):
            self.tui.async_toggle_reblog(self, status)
            return

        if key in ("c", "C"):
            self.tui.show_compose()
            return

        if key in ("d", "D"):
            if status.is_mine:
                self.tui.show_delete_confirmation(status)
            return

        if key in ("e", "E"):
            if status.is_mine:
                self.tui.async_edit(status)
            return

        if key in ("f", "F"):
            self.tui.async_toggle_favourite(self, status)
            return

        if key in ("m", "M"):
            self.tui.show_media(status)
            return

        if key in ("q", "Q"):
            self._emit("close")
            return

        if key == "esc" and self.is_thread:
            self._emit("close")
            return

        if key in ("r", "R"):
            self.tui.show_compose(status)
            return

        if key in ("s", "S"):
            status.original.show_sensitive = True
            self.refresh_status_details()
            return

        if key in ("o", "O"):
            self.tui.async_toggle_bookmark(self, status)
            return

        if key in ("i", "I"):
            self.tui.show_links(status)
            return

        if key in ("n", "N"):
            if self.tui.can_translate:
                self.tui.async_translate(self, status)
            return

        if key in ("t", "T"):
            self.tui.show_thread(status)
            return

        if key in ("u", "U"):
            self.tui.show_status_source(status)
            return

        if key in ("v", "V"):
            if status.original.url:
                webbrowser.open(status.original.url)
                # force a screen refresh; necessary with console browsers
                self.tui.clear_screen()
            return

        if key in ("e", "E"):
            self._emit("save", status)
            return

        if key in ("z", "Z"):
            self.tui.show_status_zoom(self.status_details)
            return

        if key in ("p", "P"):
            poll = status.original.data.get("poll")
            if poll and not poll["expired"]:
                self.tui.show_poll(status)
            return

        if key in ("y", "Y"):
            self.tui.copy_status(status)
            return

        return super().keypress(size, key)

    def append_status(self, status):
        self.statuses.append(status)
        self.status_list.body.append(self.build_list_item(status))

    def prepend_status(self, status):
        self.statuses.insert(0, status)
        self.status_list.body.insert(0, self.build_list_item(status))

    def append_statuses(self, statuses):
        for status in statuses:
            self.append_status(status)

    def get_status_index(self, id):
        # TODO: This is suboptimal, consider a better way
        for n, status in enumerate(self.statuses.copy()):
            if status.id == id:
                return n
        raise ValueError("Status with ID {} not found".format(id))

    def focus_status(self, status):
        index = self.get_status_index(status.id)
        self.status_list.body.set_focus(index)

    def update_status(self, status):
        """Overwrite status in list with the new instance and redraw."""
        index = self.get_status_index(status.id)
        assert self.statuses[index].id == status.id  # Sanity check

        # Update internal status list
        self.statuses[index] = status

        # Redraw list item
        self.status_list.body[index] = self.build_list_item(status)

        # Redraw status details if status is focused
        if index == self.status_list.body.focus:
            self.draw_status_details(status)

    def update_status_image(self, status, path, placeholder_index):
        """Replace image placeholder with image widget and redraw"""
        index = self.get_status_index(status.id)
        assert self.statuses[index].id == status.id  # Sanity check

        # get the image and replace the placeholder with a graphics widget
        img = None
        if hasattr(self, "images"):
            try:
                img = self.images[(str(hash(path)))]
            except KeyError:
                pass
        if img:
            try:
                status.placeholders[placeholder_index]._set_original_widget(
                    graphics_widget(img, image_format=self.tui.options.image_format, corner_radius=10,
                                    colors=self.tui.options.colors))

            except IndexError:
                # ignore IndexErrors.
                pass

    def remove_status(self, status):
        index = self.get_status_index(status.id)
        assert self.statuses[index].id == status.id  # Sanity check

        del self.statuses[index]
        del self.status_list.body[index]
        self.refresh_status_details()


class StatusDetails(urwid.Pile):
    def __init__(self, timeline: Timeline, status: Optional[Status]):
        self.status = status
        self.timeline = timeline
        if self.status:
            self.status.placeholders = []
        self.followed_accounts = timeline.tui.followed_accounts
        self.options = timeline.tui.options

        reblogged_by = status.author if status and status.reblog else None
        widget_list = list(self.content_generator(status.original, reblogged_by)
            if status else ())
        return super().__init__(widget_list)

    def image_widget(self, path, rows=None, aspect=None) -> urwid.Widget:
        """Returns a widget capable of displaying the image

        path is required; URL to image
        rows, if specfied, sets a fixed number of rows. Or:
        aspect, if specified, calculates rows based on pane width
        and the aspect ratio provided"""

        if not rows:
            if not aspect:
                aspect = 3 / 2  # reasonable default

            screen_rows = screen.get_cols_rows()[1]
            if self.timeline.can_render_pixels:
                # for pixel-rendered images,
                # image rows should be 33% of the available screen
                # but in no case fewer than 10
                rows = max(10, math.floor(screen_rows * .33))
            else:
                # for cell-rendered images,
                # use the max available columns
                # and calculate rows based on the image
                # aspect ratio
                cols = math.floor(0.55 * screen.get_cols_rows()[0])
                rows = math.ceil((cols / 2) / aspect)
                # if the calculated rows are more than will
                # fit on one screen, reduce to one screen of rows
                rows = min(screen_rows - 6, rows)

                # but in no case fewer than 10 rows
                rows = max(rows, 10)

        img = None
        if hasattr(self.timeline, "images"):
            try:
                img = self.timeline.images[(str(hash(path)))]
            except KeyError:
                pass
        if img:
            return (urwid.BoxAdapter(
                graphics_widget(img, image_format=self.timeline.tui.options.image_format, corner_radius=10,
                                colors=self.timeline.tui.options.colors), rows))
        else:
            placeholder = urwid.BoxAdapter(urwid.SolidFill(fill_char=" "), rows)
            self.status.placeholders.append(placeholder)
            if image_support_enabled():
                self.timeline.tui.async_load_image(self.timeline, self.status, path, len(self.status.placeholders) - 1)
            return placeholder

    def author_header(self, reblogged_by):
        avatar_url = self.status.original.data["account"]["avatar"]

        if avatar_url and image_support_enabled():
            aimg = self.image_widget(avatar_url, 2)

        account_color = ("highlight" if self.status.original.author.account in
                        self.timeline.tui.followed_accounts else "account")

        atxt = urwid.Pile([("pack", urwid.Text(("bold", self.status.original.author.display_name))),
                           ("pack", urwid.Text((account_color, self.status.original.author.account)))])

        if image_support_enabled():
            columns = urwid.Columns([aimg, ("weight", 9999, atxt)], dividechars=1, min_width=5)
        else:
            columns = urwid.Columns([("weight", 9999, atxt)], dividechars=1, min_width=5)

        return columns

    def content_generator(self, status, reblogged_by):
        if reblogged_by:
            reblogger_name = (reblogged_by.display_name
                              if reblogged_by.display_name
                              else reblogged_by.username)
            text = f"♺ {reblogger_name} boosted"
            yield urwid.Text(("dim", text))
            yield ("pack", urwid.AttrMap(urwid.Divider("-"), "dim"))

        yield self.author_header(reblogged_by)
        yield ("pack", urwid.Divider())

        if status.data["spoiler_text"]:
            yield ("pack", urwid.Text(status.data["spoiler_text"]))
            yield ("pack", urwid.Divider())

        # Show content warning
        if status.data["spoiler_text"] and not status.show_sensitive and not self.options.always_show_sensitive:
            yield ("pack", urwid.Text(("content_warning", "Marked as sensitive. Press S to view.")))
        else:
            if status.data["spoiler_text"]:
                yield ("pack", urwid.Text(("content_warning", "Marked as sensitive.")))

            content = status.original.translation if status.original.show_translation else status.data["content"]
            widgetlist = html_to_widgets(content)

            for line in widgetlist:
                yield (line)

            media = status.data["media_attachments"]
            if media:
                for m in media:
                    yield ("pack", urwid.AttrMap(urwid.Divider("-"), "dim"))
                    yield ("pack", urwid.Text([("bold", "Media attachment"), " (", m["type"], ")"]))
                    if m["description"]:
                        yield ("pack", urwid.Text(m["description"]))
                    if m["url"]:
                        if m["url"].lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp')):
                            yield urwid.Text("")
                            try:
                                aspect = float(m["meta"]["original"]["aspect"])
                            except Exception:
                                aspect = None
                            if image_support_enabled():
                                yield self.image_widget(m["url"], aspect=aspect)
                            yield urwid.Divider()
                        # video media may include a preview URL, show that as a fallback
                        elif m["preview_url"]:
                            if m["preview_url"].lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp')):
                                yield urwid.Text("")
                                try:
                                    aspect = float(m["meta"]["small"]["aspect"])
                                except Exception:
                                    aspect = None
                                if image_support_enabled():
                                    yield self.image_widget(m["preview_url"], aspect=aspect)
                                yield urwid.Divider()
                        yield ("pack", url_to_widget(m["url"]))

            poll = status.original.data.get("poll")
            if poll:
                yield ("pack", urwid.Divider())
                yield ("pack", self.build_linebox(self.poll_generator(poll)))

            card = status.data.get("card")
            if card:
                yield ("pack", urwid.Divider())
                yield ("pack", self.build_linebox(self.card_generator(card)))

        application = status.data.get("application") or {}
        application = application.get("name")

        yield ("pack", urwid.AttrWrap(urwid.Divider("-"), "dim"))

        translated_from = (
            language_name(status.original.translated_from)
            if status.original.show_translation and status.original.translated_from
            else None
        )

        visibility_colors = {
            "public": "visibility_public",
            "unlisted": "visibility_unlisted",
            "private": "visibility_private",
            "direct": "visibility_direct"
        }

        visibility = status.visibility.title()
        visibility_color = visibility_colors.get(status.visibility, "dim")

        yield ("pack", urwid.Text([
            ("status_detail_timestamp", f"{status.created_at.strftime('%Y-%m-%d %H:%M')} "),
            ("status_detail_timestamp",
             f"(edited {status.edited_at.strftime('%Y-%m-%d %H:%M')}) " if status.edited_at else ""),
            ("status_detail_bookmarked" if status.bookmarked else "dim", "b "),
            ("dim", f"⤶ {status.data['replies_count']} "),
            ("highlight" if status.reblogged else "dim", f"♺ {status.data['reblogs_count']} "),
            ("highlight" if status.favourited else "dim", f"★ {status.data['favourites_count']}"),
            (visibility_color, f" · {visibility}"),
            ("highlight", f" · Translated from {translated_from} " if translated_from else ""),
            ("dim", f" · {application}" if application else ""),
        ]))

        # Push things to bottom
        yield ("weight", 1, urwid.BoxAdapter(urwid.SolidFill(" "), 1))

    def build_linebox(self, contents):
        contents = urwid.Pile(list(contents))
        contents = urwid.Padding(contents, left=1, right=1)
        return RoundedLineBox(contents)

    def card_generator(self, card):
        yield urwid.Text(("card_title", card["title"].strip()))
        if card.get("author_name"):
            yield urwid.Text(["by ", ("card_author", card["author_name"].strip())])
        yield urwid.Text("")
        if card["description"]:
            yield urwid.Text(card["description"].strip())
            yield urwid.Text("")
        yield url_to_widget(card["url"])

        if card["image"] and image_support_enabled():
            if card["image"].lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp')):
                yield urwid.Text("")
                try:
                    aspect = int(card["width"]) / int(card["height"])
                except Exception:
                    aspect = None
                yield self.image_widget(card["image"], aspect=aspect)

    def poll_generator(self, poll):
        for idx, option in enumerate(poll["options"]):
            perc = (round(100 * option["votes_count"] / poll["votes_count"])
                if poll["votes_count"] else 0)

            if poll["voted"] and poll["own_votes"] and idx in poll["own_votes"]:
                voted_for = " ✓"
            else:
                voted_for = ""

            yield urwid.Text(option["title"] + voted_for)
            yield urwid.ProgressBar("", "poll_bar", perc)

        status = "Poll · {} votes".format(poll["votes_count"])

        if poll["expired"]:
            status += " · Closed"

        if poll["expires_at"]:
            expires_at = parse_datetime(poll["expires_at"]).strftime("%Y-%m-%d %H:%M")
            status += " · Closes on {}".format(expires_at)

        yield urwid.Text(("dim", status))


class StatusListItem(SelectableColumns):
    def __init__(self, status, relative_datetimes):
        edited_at = status.original.edited_at

        # TODO: hacky implementation to avoid creating conflicts for existing
        # pull requests, refactor when merged.
        created_at = (
            time_ago(status.created_at).ljust(3, " ")
            if relative_datetimes
            else status.created_at.strftime("%Y-%m-%d %H:%M")
        )

        edited_flag = "*" if edited_at else " "
        favourited = ("highlight", "★") if status.original.favourited else " "
        reblogged = ("highlight", "♺") if status.original.reblogged else " "
        is_reblog = ("dim", "♺") if status.reblog else " "
        is_reply = ("dim", "⤶ ") if status.original.in_reply_to else "  "

        return super().__init__([
            ("pack", SelectableText(("status_list_timestamp", created_at), wrap="clip")),
            ("pack", urwid.Text(("status_list_timestamp", edited_flag))),
            ("pack", urwid.Text(" ")),
            ("pack", urwid.Text(favourited)),
            ("pack", urwid.Text(" ")),
            ("pack", urwid.Text(reblogged)),
            ("pack", urwid.Text(" ")),
            urwid.Text(("status_list_account", status.original.account), wrap="clip"),
            ("pack", urwid.Text(is_reply)),
            ("pack", urwid.Text(is_reblog)),
            ("pack", urwid.Text(" ")),
        ])
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0
toot-0.44.1/toot/tui/utils.py0000644000175000017500000001010614656344035016273 0ustar00ihabunekihabunekimport base64
import re
import sys
import urwid
from collections import OrderedDict
from functools import reduce
from html.parser import HTMLParser
from typing import List

HASHTAG_PATTERN = re.compile(r'(?>> highlight_keys("[P]rint [V]iew", "blue")
    >>> [('blue', 'P'), 'rint ', ('blue', 'V'), 'iew']
    """
    def _gen():
        highlighted = False
        for part in re.split("\\[|\\]", text):
            if part:
                if highlighted:
                    yield (high_attr, part) if high_attr else part
                else:
                    yield (low_attr, part) if low_attr else part
            highlighted = not highlighted
    return list(_gen())


def highlight_hashtags(line):
    hline = []

    for p in re.split(HASHTAG_PATTERN, line):
        if p.startswith("#"):
            hline.append(("hashtag", p))
        else:
            hline.append(p)

    return hline


class LinkParser(HTMLParser):
    def reset(self):
        super().reset()
        self.links = []

    def handle_starttag(self, tag, attrs):
        if tag == "a":
            href, title = None, None
            for name, value in attrs:
                if name == "href":
                    href = value
                if name == "title":
                    title = value
            if href:
                self.links.append((href, title))


def parse_content_links(content):
    """Parse  tags from status's `content` and return them as a list of
    (href, title), where `title` may be None.
    """
    parser = LinkParser()
    parser.feed(content)
    return parser.links[:]


def copy_to_clipboard(screen: urwid.raw_display.Screen, text: str):
    """ copy text to clipboard using OSC 52
    This escape sequence is documented
    here https://iterm2.com/documentation-escape-codes.html
    It has wide support - XTerm, Windows Terminal,
    Kitty, iTerm2, others. Some terminals may require a setting to be
    enabled in order to use OSC 52 clipboard functions.
    """

    text_bytes = text.encode("utf-8")
    b64_bytes = base64.b64encode(text_bytes)
    b64_text = b64_bytes.decode("utf-8")

    screen.write(f"\033]52;c;{b64_text}\a")
    screen.flush()


def get_max_toot_chars(instance, default=500):
    # Mastodon
    # https://docs.joinmastodon.org/entities/Instance/#max_characters
    max_toot_chars = deep_get(instance, ["configuration", "statuses", "max_characters"])
    if isinstance(max_toot_chars, int):
        return max_toot_chars

    # Pleroma
    max_toot_chars = instance.get("max_toot_chars")
    if isinstance(max_toot_chars, int):
        return max_toot_chars

    return default


def deep_get(adict: dict, path: List[str], default=None):
    return reduce(
        lambda d, key: d.get(key, default) if isinstance(d, dict) else default,
        path,
        adict
    )


class LRUCache(OrderedDict):
    """Dict with a limited size, ejecting LRUs as needed.
        Default max size = 10Mb"""

    def __init__(self, *args, cache_max_bytes: int = 1024 * 1024 * 10, **kwargs):
        assert cache_max_bytes > 0
        self.total_value_size = 0
        self.cache_max_bytes = cache_max_bytes

        super().__init__(*args, **kwargs)

    def __setitem__(self, key: str, value):
        if key in self:
            self.total_value_size -= sys.getsizeof(super().__getitem__(key).tobytes())
        self.total_value_size += sys.getsizeof(value.tobytes())
        super().__setitem__(key, value)
        super().move_to_end(key)

        while self.total_value_size > self.cache_max_bytes:
            old_key, value = next(iter(self.items()))
            sz = sys.getsizeof(value.tobytes())
            super().__delitem__(old_key)
            self.total_value_size -= sz

    def __getitem__(self, key: str):
        val = super().__getitem__(key)
        super().move_to_end(key)
        return val
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0
toot-0.44.1/toot/tui/widgets.py0000644000175000017500000000634314656344035016611 0ustar00ihabunekihabunekimport urwid
from wcwidth import wcswidth


class Clickable:
    """
    Add a `click` signal which is sent when the item is activated or clicked.

    TODO: make it work on widgets which have other signals.
    """
    signals = ["click"]

    def keypress(self, size, key):
        if self._command_map[key] == urwid.ACTIVATE:
            self._emit('click')
            return

        return key

    def mouse_event(self, size, event, button, x, y, focus):
        if button == 1:
            self._emit('click')


class SelectableText(Clickable, urwid.Text):
    _selectable = True


class SelectableColumns(Clickable, urwid.Columns):
    _selectable = True


class EditBox(urwid.AttrWrap):
    """Styled edit box."""
    def __init__(self, *args, **kwargs):
        self.edit = urwid.Edit(*args, **kwargs)
        return super().__init__(self.edit, "editbox", "editbox_focused")


class Button(urwid.AttrWrap):
    """Styled button."""
    def __init__(self, *args, **kwargs):
        button = urwid.Button(*args, **kwargs)
        padding = urwid.Padding(button, width=wcswidth(args[0]) + 4)
        return super().__init__(padding, "button", "button_focused")

    def set_label(self, *args, **kwargs):
        self.original_widget.original_widget.set_label(*args, **kwargs)
        self.original_widget.width = wcswidth(args[0]) + 4


class CheckBox(urwid.AttrWrap):
    """Styled checkbox."""
    def __init__(self, *args, **kwargs):
        self.button = urwid.CheckBox(*args, **kwargs)
        padding = urwid.Padding(self.button, width=len(args[0]) + 4)
        return super().__init__(padding, "button", "button_focused")

    def get_state(self):
        """Return the state of the checkbox."""
        return self.button._state


class RadioButton(urwid.AttrWrap):
    """Styled radiobutton."""
    def __init__(self, *args, **kwargs):
        button = urwid.RadioButton(*args, **kwargs)
        padding = urwid.Padding(button, width=len(args[1]) + 4)
        return super().__init__(padding, "button", "button_focused")


class ModalBox(urwid.Frame):
    def __init__(self, message):
        text = urwid.Text(message)
        filler = urwid.Filler(text, valign='top', top=1, bottom=1)
        padding = urwid.Padding(filler, left=1, right=1)
        return super().__init__(padding)


class RoundedLineBox(urwid.LineBox):
    """LineBox that defaults to rounded corners."""
    def __init__(self,
                 original_widget,
                 title="",
                 title_align="center",
                 title_attr=None,
                 tlcorner="\u256d",
                 tline="─",
                 lline="│",
                 trcorner="\u256e",
                 blcorner="\u2570",
                 rline="│",
                 bline="─",
                 brcorner="\u256f",
                 ) -> None:
        return super().__init__(original_widget,
                            title,
                            title_align,
                            title_attr,
                            tlcorner,
                            tline,
                            lline,
                            trcorner,
                            blcorner,
                            rline,
                            bline,
                            brcorner)
././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.855274
toot-0.44.1/toot/utils/0000755000175000017500000000000014656360001015111 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0
toot-0.44.1/toot/utils/__init__.py0000644000175000017500000001022414656344035017232 0ustar00ihabunekihabunekimport click
import os
import re
import subprocess
import tempfile
import unicodedata
import warnings

from bs4 import BeautifulSoup
from typing import Any, Dict, Generator, List, Optional
from urllib.parse import urlparse, urlencode, quote, unquote


def str_bool(b: bool) -> str:
    """Convert boolean to string, in the way expected by the API."""
    return "true" if b else "false"


def str_bool_nullable(b: Optional[bool]) -> Optional[str]:
    """Similar to str_bool, but leave None as None"""
    return None if b is None else str_bool(b)


def parse_html(html: str) -> BeautifulSoup:
    # Ignore warnings made by BeautifulSoup, if passed something that looks like
    # a file (e.g. a dot which matches current dict), it will warn that the file
    # should be opened instead of passing a filename.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        return BeautifulSoup(html.replace("'", "'"), "html.parser")


def get_text(html: str) -> str:
    """Converts html to text, strips all tags."""
    text = parse_html(html).get_text()
    return unicodedata.normalize("NFKC", text)


def html_to_paragraphs(html: str) -> List[List[str]]:
    """Attempt to convert html to plain text while keeping line breaks.
    Returns a list of paragraphs, each being a list of lines.
    """
    paragraphs = re.split("]*>", html)

    # Convert 
s to line breaks and remove empty paragraphs paragraphs = [re.split("
", p) for p in paragraphs if p] # Convert each line in each paragraph to plain text: return [[get_text(line) for line in p] for p in paragraphs] def format_content(content: str) -> Generator[str, None, None]: """Given a Status contents in HTML, converts it into lines of plain text. Returns a generator yielding lines of content. """ paragraphs = html_to_paragraphs(content) first = True for paragraph in paragraphs: if not first: yield "" for line in paragraph: yield line first = False EOF_KEY = "Ctrl-Z" if os.name == 'nt' else "Ctrl-D" def multiline_input() -> str: """Lets user input multiple lines of text, terminated by EOF.""" lines: List[str] = [] while True: try: lines.append(input()) except EOFError: break return "\n".join(lines).strip() EDITOR_DIVIDER = "------------------------ >8 ------------------------" EDITOR_INPUT_INSTRUCTIONS = f""" {EDITOR_DIVIDER} Do not modify or remove the line above. Enter your toot above it. Everything below it will be ignored. """ def editor_input(editor: str, initial_text: str) -> str: """Lets user input text using an editor.""" tmp_path = _tmp_status_path() initial_text = (initial_text or "") + EDITOR_INPUT_INSTRUCTIONS if not _use_existing_tmp_file(tmp_path): with open(tmp_path, "w") as f: f.write(initial_text) f.flush() subprocess.run([editor, tmp_path]) with open(tmp_path) as f: return f.read().split(EDITOR_DIVIDER)[0].strip() def delete_tmp_status_file() -> None: try: os.unlink(_tmp_status_path()) except FileNotFoundError: pass def _tmp_status_path() -> str: tmp_dir = tempfile.gettempdir() return f"{tmp_dir}/.status.toot" def _use_existing_tmp_file(tmp_path: str) -> bool: if os.path.exists(tmp_path): click.echo(f"Found draft status at: {tmp_path}") choice = click.Choice(["O", "D"], case_sensitive=False) char = click.prompt("Open or Delete?", type=choice, default="O") return char == "O" return False def drop_empty_values(data: Dict[Any, Any]) -> Dict[Any, Any]: """Remove keys whose values are null""" return {k: v for k, v in data.items() if v is not None} def urlencode_url(url: str) -> str: parsed_url = urlparse(url) # unencode before encoding, to prevent double-urlencoding encoded_path = quote(unquote(parsed_url.path), safe="-._~()'!*:@,;+&=/") encoded_query = urlencode({k: quote(unquote(v), safe="-._~()'!*:@,;?/") for k, v in parsed_url.params}) encoded_url = parsed_url._replace(path=encoded_path, params=encoded_query).geturl() return encoded_url ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/utils/datetime.py0000644000175000017500000000232514656344035017272 0ustar00ihabunekihabunekimport math import os from datetime import datetime, timezone def parse_datetime(value: str) -> datetime: """Returns an aware datetime in local timezone""" dttm = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f%z") # When running tests return datetime in UTC so that tests don't depend on # the local timezone if "PYTEST_CURRENT_TEST" in os.environ: return dttm.astimezone(timezone.utc) return dttm.astimezone() SECOND = 1 MINUTE = SECOND * 60 HOUR = MINUTE * 60 DAY = HOUR * 24 WEEK = DAY * 7 def time_ago(value: datetime) -> str: now = datetime.now().astimezone() delta = now.timestamp() - value.timestamp() if delta < 1: return "now" if delta < 8 * DAY: if delta < MINUTE: return f"{math.floor(delta / SECOND)}".rjust(2, " ") + "s" if delta < HOUR: return f"{math.floor(delta / MINUTE)}".rjust(2, " ") + "m" if delta < DAY: return f"{math.floor(delta / HOUR)}".rjust(2, " ") + "h" return f"{math.floor(delta / DAY)}".rjust(2, " ") + "d" if delta < 53 * WEEK: # not exactly correct but good enough as a boundary return f"{math.floor(delta / WEEK)}".rjust(2, " ") + "w" return ">1y" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/utils/language.py0000644000175000017500000000727114656344035017266 0ustar00ihabunekihabunek# Languages mapped by their ISO 639-1 code # https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes LANGUAGES = { "ab": "Abkhazian", "aa": "Afar", "af": "Afrikaans", "ak": "Akan", "sq": "Albanian", "am": "Amharic", "ar": "Arabic", "an": "Aragonese", "hy": "Armenian", "as": "Assamese", "av": "Avaric", "ae": "Avestan", "ay": "Aymara", "az": "Azerbaijani", "bm": "Bambara", "ba": "Bashkir", "eu": "Basque", "be": "Belarusian", "bn": "Bengali", "bi": "Bislama", "bs": "Bosnian", "br": "Breton", "bg": "Bulgarian", "my": "Burmese", "ca": "Catalan", "ch": "Chamorro", "ce": "Chechen", "ny": "Chichewa", "zh": "Chinese", "cu": "Old Slavonic", "cv": "Chuvash", "kw": "Cornish", "co": "Corsican", "cr": "Cree", "hr": "Croatian", "cs": "Czech", "da": "Danish", "dv": "Divehi", "nl": "Dutch", "en": "English", "eo": "Estonian", "ee": "Ewe", "fo": "Faroese", "fj": "Fijian", "fi": "Finnish", "fr": "French", "fy": "Western Frisian", "ff": "Fulah", "gd": "Gaelic", "gl": "Galician", "lg": "Ganda", "ka": "Georgian", "de": "German", "el": "Greek", "kl": "Kalaallisut", "gn": "Guarani", "gu": "Gujarati", "ht": "Haitian", "ha": "Hausa", "he": "Hebrew", "hz": "Herero", "hi": "Hiri Motu", "hu": "Hungarian", "is": "Icelandic", "io": "Ido", "ig": "Igbo", "id": "Indonesian", "ia": "Inupiaq", "ga": "Irish", "it": "Italian", "ja": "Japanese", "jv": "Javanese", "kn": "Kannada", "kr": "Kanuri", "ks": "Kashmiri", "kk": "Kazakh", "km": "Central Khmer", "ki": "Kikuyu", "rw": "Kirghiz", "kv": "Komi", "kg": "Kongo", "ko": "Korean", "kj": "Kuanyama", "ku": "Kurdish", "lo": "Lao", "la": "Latvian", "li": "Limburgan", "ln": "Lingala", "lt": "Lithuanian", "lu": "Luba-Katanga", "lb": "Luxembourgish", "mk": "Macedonian", "mg": "Malagasy", "ms": "Malay", "ml": "Malayalam", "mt": "Maltese", "gv": "Manx", "mi": "Maori", "mr": "Marathi", "mh": "Marshallese", "mn": "Mongolian", "na": "Nauru", "nv": "Navajo", "nd": "North Ndebele", "nr": "South Ndebele", "ng": "Nepali", "no": "Norwegian", "nb": "Norwegian Bokmål", "nn": "Norwegian Nynorsk", "ii": "Sichuan Yi", "oc": "Occitan", "oj": "Ojibwa", "or": "Oriya", "om": "Oromo", "os": "Ossetian", "pi": "Pali", "ps": "Pashto", "fa": "Persian", "pl": "Polish", "pt": "Portuguese", "pa": "Punjabi", "qu": "Quechua", "ro": "Romanian", "rm": "Romansh", "rn": "Rundi", "ru": "Russian", "se": "Samoan", "sg": "Sango", "sa": "Sardinian", "sr": "Serbian", "sn": "Shona", "sd": "Sindhi", "si": "Sinhala", "sk": "Slovak", "sl": "Slovenian", "so": "Somali", "st": "Southern Sotho", "es": "Spanish", "su": "Sundanese", "sw": "Swahili", "ss": "Swati", "sv": "Swedish", "tl": "Tagalog", "ty": "Tahitian", "tg": "Tajik", "ta": "Tamil", "tt": "Tatar", "te": "Telugu", "th": "Thai", "bo": "Tibetan", "ti": "Tigrinya", "to": "Tonga", "ts": "Tsonga", "tn": "Tswana", "tr": "Turkish", "tk": "Turkmen", "tw": "Uighur", "uk": "Ukrainian", "ur": "Uzbek", "ve": "Venda", "vi": "Vietnamese", "vo": "Walloon", "cy": "Welsh", "wo": "Wolof", "xh": "Xhosa", "yi": "Yiddish", "yo": "Yoruba", "za": "Zhuang", "zu": "Zulu", } def language_name(code: str) -> str: return LANGUAGES.get(code, code) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723451421.0 toot-0.44.1/toot/wcstring.py0000644000175000017500000000637114656344035016203 0ustar00ihabunekihabunek""" Utilities for dealing with string containing wide characters. """ import re from typing import Generator, List from wcwidth import wcwidth, wcswidth def _wc_hard_wrap(line: str, length: int) -> Generator[str, None, None]: """ Wrap text to length characters, breaking when target length is reached, taking into account character width. Used to wrap lines which cannot be wrapped on whitespace. """ chars = [] chars_len = 0 for char in line: char_len = wcwidth(char) if chars_len + char_len > length: yield "".join(chars) chars: List[str] = [] chars_len = 0 chars.append(char) chars_len += char_len if chars: yield "".join(chars) def wc_wrap(text: str, length: int) -> Generator[str, None, None]: """ Wrap text to given length, breaking on whitespace and taking into account character width. Meant for use on a single line or paragraph. Will destroy spacing between words and paragraphs and any indentation. """ line_words: List[str] = [] line_len = 0 words = re.split(r"\s+", text.strip()) for word in words: word_len = wcswidth(word) if line_words and line_len + word_len > length: line = " ".join(line_words) if line_len <= length: yield line else: yield from _wc_hard_wrap(line, length) line_words = [] line_len = 0 line_words.append(word) line_len += word_len + 1 # add 1 to account for space between words if line_words: line = " ".join(line_words) if line_len <= length: yield line else: yield from _wc_hard_wrap(line, length) def trunc(text: str, length: int) -> str: """ Truncates text to given length, taking into account wide characters. If truncated, the last char is replaced by an ellipsis. """ if length < 1: raise ValueError("length should be 1 or larger") # Remove whitespace first so no unnecessary truncation is done. text = text.strip() text_length = wcswidth(text) if text_length <= length: return text # We cannot just remove n characters from the end since we don't know how # wide these characters are and how it will affect text length. # Use wcwidth to determine how many characters need to be truncated. chars_to_truncate = 0 trunc_length = 0 for char in reversed(text): chars_to_truncate += 1 trunc_length += wcwidth(char) if text_length - trunc_length <= length: break # Additional char to make room for ellipsis n = chars_to_truncate + 1 return text[:-n].strip() + '…' def pad(text: str, length: int) -> str: """Pads text to given length, taking into account wide characters.""" text_length = wcswidth(text) if text_length < length: return text + ' ' * (length - text_length) return text def fit_text(text: str, length: int) -> str: """Makes text fit the given length by padding or truncating it.""" text_length = wcswidth(text) if text_length > length: return trunc(text, length) if text_length < length: return pad(text, length) return text ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1723457536.855274 toot-0.44.1/toot.egg-info/0000755000175000017500000000000014656360001015443 5ustar00ihabunekihabunek././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457536.0 toot-0.44.1/toot.egg-info/PKG-INFO0000644000175000017500000012667714656360000016562 0ustar00ihabunekihabunekMetadata-Version: 2.1 Name: toot Version: 0.44.1 Summary: Mastodon CLI client Author-email: Ivan Habunek License: GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . Project-URL: Homepage, https://toot.bezdomni.net Project-URL: Source, https://github.com/ihabunek/toot/ Classifier: Environment :: Console :: Curses Classifier: Environment :: Console Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: beautifulsoup4<5.0,>=4.5.0 Requires-Dist: click~=8.1 Requires-Dist: requests<3.0,>=2.13 Requires-Dist: tomlkit<1.0,>=0.10.0 Requires-Dist: urwid<3.0,>=2.0.0 Requires-Dist: wcwidth>=0.1.7 Provides-Extra: images Requires-Dist: pillow>=9.5.0; extra == "images" Requires-Dist: term-image>=0.7.2; extra == "images" Provides-Extra: richtext Requires-Dist: urwidgets<0.3,>=0.2; extra == "richtext" Provides-Extra: test Requires-Dist: flake8; extra == "test" Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-xdist[psutil]; extra == "test" Requires-Dist: setuptools; extra == "test" Requires-Dist: vermin; extra == "test" Requires-Dist: typing-extensions; extra == "test" Requires-Dist: pillow>=9.5.0; extra == "test" Provides-Extra: dev Requires-Dist: build; extra == "dev" Requires-Dist: flake8; extra == "dev" Requires-Dist: mypy; extra == "dev" Requires-Dist: pyright; extra == "dev" Requires-Dist: pyyaml; extra == "dev" Requires-Dist: textual-dev; extra == "dev" Requires-Dist: twine; extra == "dev" Requires-Dist: types-beautifulsoup4; extra == "dev" Requires-Dist: vermin; extra == "dev" ============================ Toot - a Mastodon CLI client ============================ .. image:: https://raw.githubusercontent.com/ihabunek/toot/master/trumpet.png Toot is a CLI and TUI tool for interacting with Mastodon instances from the command line. .. image:: https://img.shields.io/badge/author-%40ihabunek-blue.svg?maxAge=3600&style=flat-square :target: https://mastodon.social/@ihabunek .. image:: https://img.shields.io/github/license/ihabunek/toot.svg?maxAge=3600&style=flat-square :target: https://opensource.org/licenses/GPL-3.0 .. image:: https://img.shields.io/pypi/v/toot.svg?maxAge=3600&style=flat-square :target: https://pypi.python.org/pypi/toot Resources --------- * Homepage: https://github.com/ihabunek/toot * Issues: https://github.com/ihabunek/toot/issues * Documentation: https://toot.bezdomni.net/ * Mailing list for discussion, support and patches: https://lists.sr.ht/~ihabunek/toot-discuss * Informal discussion: #toot IRC channel on `libera.chat `_ Features -------- * Posting, replying, deleting statuses * Support for media uploads, spoiler text, sensitive content * Search by account or hash tag * Following, muting and blocking accounts * Simple switching between authenticated in Mastodon accounts Terminal User Interface ----------------------- toot includes a terminal user interface (TUI). Run it with ``toot tui``. TUI Features: ------------- * Block graphic image display (requires optional libraries `pillow `, `term-image `, and `urwidgets `) * Bitmapped image display in `kitty ` terminal ``toot tui -f kitty`` * Bitmapped image display in `iTerm2 `, or `WezTerm ` terminal ``toot tui -f iterm`` .. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_list.png .. image :: https://raw.githubusercontent.com/ihabunek/toot/master/docs/images/tui_compose.png License ------- Copyright Ivan Habunek and contributors. Licensed under `GPLv3 `_, see `LICENSE `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457536.0 toot-0.44.1/toot.egg-info/SOURCES.txt0000644000175000017500000000427214656360000017333 0ustar00ihabunekihabunek.coveragerc .flake8 .gitignore .vermin CHANGELOG.md CONTRIBUTING.md LICENSE MANIFEST.in Makefile README.rst book.css book.toml changelog.yaml pyproject.toml pytest.ini trumpet.png .github/workflows/test.yml docs/SUMMARY.md docs/advanced.md docs/changelog.md docs/contributing.md docs/documentation.md docs/environment_variables.md docs/installation.md docs/introduction.md docs/license.md docs/release.md docs/settings.md docs/shell_completion.md docs/testing.md docs/trumpet.png docs/tui.md docs/usage.md docs/images/auth.png docs/images/trumpet.png docs/images/tui_compose.png docs/images/tui_list.png scripts/generate_changelog scripts/tag_version tests/README.md tests/__init__.py tests/test_config.py tests/test_utils.py tests/test_version.py tests/utils.py tests/assets/small.webm tests/assets/test1.png tests/assets/test2.png tests/assets/test3.png tests/assets/test4.png tests/integration/__init__.py tests/integration/conftest.py tests/integration/test_accounts.py tests/integration/test_auth.py tests/integration/test_lists.py tests/integration/test_post.py tests/integration/test_read.py tests/integration/test_status.py tests/integration/test_tags.py tests/integration/test_timelines.py tests/integration/test_update_account.py tests/tui/test_rich_text.py toot/__init__.py toot/__main__.py toot/api.py toot/auth.py toot/config.py toot/entities.py toot/exceptions.py toot/http.py toot/logging.py toot/output.py toot/settings.py toot/wcstring.py toot.egg-info/PKG-INFO toot.egg-info/SOURCES.txt toot.egg-info/dependency_links.txt toot.egg-info/entry_points.txt toot.egg-info/requires.txt toot.egg-info/top_level.txt toot/cli/__init__.py toot/cli/accounts.py toot/cli/auth.py toot/cli/diag.py toot/cli/lists.py toot/cli/post.py toot/cli/read.py toot/cli/statuses.py toot/cli/tags.py toot/cli/timelines.py toot/cli/tui.py toot/cli/validators.py toot/tui/NOTES.md toot/tui/__init__.py toot/tui/app.py toot/tui/compose.py toot/tui/constants.py toot/tui/entities.py toot/tui/images.py toot/tui/overlays.py toot/tui/poll.py toot/tui/scroll.py toot/tui/timeline.py toot/tui/utils.py toot/tui/widgets.py toot/tui/richtext/__init__.py toot/tui/richtext/richtext.py toot/utils/__init__.py toot/utils/datetime.py toot/utils/language.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457536.0 toot-0.44.1/toot.egg-info/dependency_links.txt0000644000175000017500000000000114656360000021510 0ustar00ihabunekihabunek ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457536.0 toot-0.44.1/toot.egg-info/entry_points.txt0000644000175000017500000000004614656360000020740 0ustar00ihabunekihabunek[console_scripts] toot = toot.cli:cli ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457536.0 toot-0.44.1/toot.egg-info/requires.txt0000644000175000017500000000055514656360000020047 0ustar00ihabunekihabunekbeautifulsoup4<5.0,>=4.5.0 click~=8.1 requests<3.0,>=2.13 tomlkit<1.0,>=0.10.0 urwid<3.0,>=2.0.0 wcwidth>=0.1.7 [dev] build flake8 mypy pyright pyyaml textual-dev twine types-beautifulsoup4 vermin [images] pillow>=9.5.0 term-image>=0.7.2 [richtext] urwidgets<0.3,>=0.2 [test] flake8 pytest pytest-xdist[psutil] setuptools vermin typing-extensions pillow>=9.5.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1723457536.0 toot-0.44.1/toot.egg-info/top_level.txt0000644000175000017500000000000514656360000020167 0ustar00ihabunekihabunektoot ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1691749799.0 toot-0.44.1/trumpet.png0000644000175000017500000004014614465406647015216 0ustar00ihabunekihabunekPNG  IHDRaMsBIT|d IDATx]y\M/=MDQh 043 1#3_b%ƌk'„$l%JiӾ;-}^W<,<] (?-=P`tP ,@ݻ:BFW@w055Eee%ۧѣGcҥ=PF^^Fhm۶EBB.ڶm۴#VYqm|ᇼ}B111y&,X 35˗Ƴ… ׯ1-^˖-t zÑsssoUDJJJ fВܻwM^@UU555ӧ]HJJ7S6:u;w 99={7QSSGBOOfffĉQQQ@UU>KFFFBCC o:ZnІPVV[n֭[VbYmhԩ,(>44?lll0`]l… hР]\\7?yrZdd$Bh1&W0x`ڵkpvvf~߾}ܹ֭*<|EEE3g@ǏO>,G&LCCC| JJJ`T[dt)]ɵsk3ƎK}۷O#Ҍ3裏>۷oӒ%KM6H-XGGGe2z||SXĢnzzۖkGر#Ν;dzrªU`ccUVa믿->7m4ŋGii)b gΜaK!uuu,ZZZ:TVVB$L?/)TTT0m4 G!!!***۷/={{aٸuѹsg =zsxJJJŸ{.z@,Zhiiaʔ)066ƺuPRRUUU )) Æ ޽{yR@CC#TTT@YYRaeeDbBMM ***{Byy9{ܶ&;_qq1ZZZPUUetttPUUTWW׫}6BCC@NByy9ÇEEE0a݋.]`Ĉ OOOȑ#ڵ+V\CWW7BCCiHJJę[UTT ]]: iLHHȑ#̨fBHH"-ݧL"s)qq@l_yyL'ÇSUU۷֯_O)uN@ ,*..PZ~=%''~^ Dz ݼyFɮ۽{w200SNQbb"3O$&&gffF/^?^JHYYYl2rww'GGGK#SSS T***(#sN#hD,f̘Aiiil_rr2 OOOŋtyޱ322H~#}* `c~,GDo>0a]pm0***;w) bbb( f̘AnnnrO>+W4ۛ<$j.L2yCQO8)ڂ j*DDD0 N&}a߾}066f3MII @JJ QYY)3TÇx }}}Cii)z KKK\t :::PUUEDDB!k\vMnK@lVAnn.]B1}t|(**իQ^^K.>۶mCv񰲲ĉh׮zj_~R+Ǭv=-%%:t&SlXCmSҥKŶmpuVl޼MzwyyyPUU쐒Ç#::ׯ={66l}ȑ8y$e˖֭[ؼy3dHN4 %%%R2>L6 SNeAeeef'LII<.cǎz*Zj*,Xk֬iб***BMM jjjC@e=z -- HIIAnn.1j(TVV",, HNNL8JJJ077Gǎo ":u EEE077gG\878y$m{fc_0ftĬYXm0R`nn[ǣGx)zWWW3--- Z/Ʒ~[5)ƢErJaӦMRKvt)Fc555TWWCGGXt'CCC#kkj.k,׏)--sμ^}>|8 T#???255%]]]0`JQ]gAGӦM#???:~8]v ŋM2DtQbڵk+^w&g\II(7op̤VWWg}F={RCJatYKvSSSΦlrvv&^,:yd1??)8(ԩjՊ233uaٲeR!==K2z~~>u֍׷O>lLpF6sذa{Ak׮em ,I& mݺɯ?̾deeEZ۷oi۶m<"''iӦMR|N0zffԒ](Ǐ'4k,ڳgՀYǺuV7oeeeHKyVVƵk׎rssY$Z;88Paa!1ީS'zKwg@hΝez5%JKKNa۶mVN***OdaaAnh"2e %&& 4`6>|et###vԱcet&L|?&"¯JZl|999?t҅ƌCaaa믿/ݻwYN$}ۉZnbd1ɓ~ GMD|/҈}ƌV "OxkPMMMZ&MDt=K$9yݝxsZS ''wiӦѐ!CMQ>}ؘо}H Pxx8mۖ]F#Gd/IKKOw988ЬYk׮2qIE]$I-wED(+kkkޯSZZTk׮[>uג>iҤzctΎ&^ΝYY Dp"ѨQaΜ94f*))!]]]:}4YoNIĤ$Z>MvvvGjj*/ۼ ,%И$^Z_99s&x]y9p0yd/ /eۆ!@Qj*nݺaҥ-9Dp!?~^ahhkkkƍHLLDff&QQQ#;;/UUU.mڴ6mD_}988QYYGo۶mƆllľf̘A2S5VVmڴZEEEvI2s"4rss8>>LLL*7?$z-:'%%msYf+** 9{eL⧛ wf֛pY={of阳s=+QTT˗/`8JSAGG߳ fI022իf|||`hhCCC֙xVZIoQFڪU+<$fffO ???ykQoݺ5.\???̘1700y}򐓓.]666011۹`pvv Ny[YY kՉٳgۛm_t ٳgѡC%BfJԩlmmٶ@ н{w@rKSxƌ@@zzz֩S'8;;3X֭[m۶zb-|rmdd͛7>_d- ---!&&"aaa={6q8cС-uZj&(11455 g9uT3򧧧GgΜr)7ݏ?đC#Gy󉈚,3g8do}}}^ /׮MZ"@1coFNNNJ6664ydfߴiuܙ]Ɔ IMMm+++kXZZj׮듆<Ƥ}u҅rrrܸ54$y>71rhF/--}AֆG'" 5~@P߾}ݻT^^N^^^< gצ =zDAAAAR t1r {dd$>>>D$n۷/;v޽K}͘1RSS]>>Q9bcc[XXoFDD};.M4nݺՠqڪU+X+Wl媉TDG,NH,n̙3Xl6Ad``@EdkkK'NKRJJ ݻw/"Ξ=Kl?}9[pV/D\C\gϞ=˗ŋY8rCH2dLzVVYZZN$ %EFY}z"^]].Z>>>rqqRUU_TRRBĉ2u%%%Z|y 6DDwa(77|}}jjjԷo_9s&͜9O.ttthz/^$@@^^^`tI9ˬPF'2O?DM+Vx-Shܹ|rDT^^N CCC&VRAAD"JOOBϗ{SSS)))m;wWŋLyMf͒Zs3ӧ*8rqqɓ'P D$%Ν;Xؘx SNpţK^YWNC$^ 0A^[Ft6KeJ}Z9ZaǏ>|"())!!!w;wЭ[zգG?~xpUDDDSNEff&BBBwQ8wNSSSXZZwޘ0aB!<΂!bbbн{w8# @"BiiL=z >>^^^HOOÇyv킕ӧO1a>d>+Wʕ+l;88ڵCnn.k_yfhiiaذa ##]GG~)RSS~z\rڵk;_~,vj9*hݺ5-[KKKL4 ĉ'SV> j0TSuC[%v]oQQQhȶSNaɒ% ý{rJ ###|Xp!JJJ .`ܸqETMM ^EE???aHHH -,,vׯ3g=˃3@DXn pիjjj#33QQQPRR·~/q^p=zwLǞI&{u\f?SQQá'OJBUU1bW^طoKٱ7nl\\xyy9kccSWRXt)BCCw'N[i ͺv-碢"rttC6IH5jwel`` -ZNLzz:8p<==ISS%8MtAAٳdճG  =F>CڰaYYYX4hq&W}ժU)ښ}xQ;̙ۤ3ѣ^-xk3:̥obdtWWW^т;2il[__yyy1vtt4D"FD$-(?>yxx<K\ް&bt555h׮UVV6HF ҫW/*..&c͛71$KP(k]hjhypydggܹse?~쌅 ɓ'HHH#Gx}Æ ֭[@/%jՕ-]L=tPiժsssCfyƄ ၴos4TSSVc;v,f͚76p*^vHKK (++%ۛ0aDfWUUΝ;/8zx222h޼yĉ8GrӿoRWWWțО={ɉH(iӆidSN%A{S޽;YG2B/w GRF)/]DDݻw'wwwС9993KݩS'ee]^UUغu++\hqFJ*`?C iVSȖ-[x/ŋM4fڴiWMM mٲΟ?O= ۷/9UEDdffF{%#iLW">x`6ڴqF:CCCz*djjJ:tjj8Kʞ^^^ĄԦd?4&&&qB}rJvν{(""֯_OÆ . ښРA(""-ZӧEDDәSLcϏrrr¢E] >Q^^=xm߾\\\(//55W$vZ*..*HJDtfgպsŗ_~ݻw 'OĈ#󝹺"** gϞ&K9$ nHMMo+2! ###hkk믿fV_xݻw#33۶mP(lPqW^Mɓ'cΝ&n477GPPPݩS'L6ouD"`ooׯC]]8|0ر#lmmwΝ ~^^^駟"Byy9.\HR:K"&&.$$,XǏGPP@,6l> --JLL+T,hFݻ,A(˗/YW#OOf H?c?\-=cڱckoӦ -Y^xѠ&&&駟~b,Q|xDtR*nݺQAAҝÑ#G2WҜR^^ k ^cMjOҒV\ɼСCdnnN8yر$jftڴicUj$t]3}ڵ<+gh":t:tX .ΉcNE8=\E"=z˴!SSSݕ o57)55IWWW*RSbtx.kjjŋij… u*ttthռF||}߿O9]79F'"i-F!iiiʕ+)==rssVSSCM>>>u&faY(##nʮ]_6Ƃ^*9;ڶmKRcty=_zD"͝;Wn5UUUUf/,,l1fGJJJ51ӕ$M:Q䑖P(d?^\J_Pzz:ڵM0IYx'q~z,]G"##aaa۷㫯BNNY2,X ,@vЫW/ cǎ9 8::bȐ! ڼy3PSSe˖͍ij!//FFF޽;qYV޽;헞aϞ=22E LLL`ff~ Rae..];,VJwPVVƽ{BSL nݒRr⫯^G۶maff* : EQQ~Wڵ /^qAbݖ-[_XE&^zEG&@y,#Am۶/迳7[>nܸlΙ[pnnnnD>>>_Q3:g 7񄖖XwҧOzO4bא477,G\BN񨥥Evvv4}tZnbe߲eёvEcǎј1cёƎDN]dhhHNNNԳgOliر4}J xgHlC紣FFFqF""t7JCI__"""x2886{Qqq1@,^hԩv!5++-W\ctIq)1C]#3gZ8 4$XDTwII ӫW˗^/-Y|||>oС~z^fw9/>>F/_-߿Ovb㺺4uf 6ׯ{O]|ڷoO_Stt4"ZxqvvvS*,,ʫ. ,..&&ct"b*++رcGoX/R!t}]}{Dbf,@'={V1.99Y*9x>رwԉlج~I^_Uoonxz%СChr}:M4Iqvvvt);Vĉ~:>gϞ'pvvficcc| xpͨgpaXh>s^Bll,`bb{4Gرc<^EEǎCBB.]Zo"O>nnnlDz̙ NQPP %6;;pwwGbbzsEff&OZFFF(..Y\KK - UUU!++ {쁧'yyy:qh7͛7idii٠YNҒFݬ^IoM6M>]ȑ#>Ґ!CxIH6oL b2޽{I$Qee%u!22ؼy3*++q}5U{쁖LLL0`?Fb8}4>cX]vqH\~/^DBB"dff"77oPUUEϞ=1n8L6 ::: "ܻwgFDD}}}z gΜam۶w?k(((@LL bbb{rrr0w\;_w}MMM^gϞM6g}@Ücl|077w}x=TTTS_ryYea")))e",appp!CЫW/blؼy3ݻ}"--Mn~{@C{nL8AEK.'h8D"ݽ{e5k⋫6uT9ަDqq1=~|||̖W`& FDIt1qqq342zPqq1IHD" f^$uO8AzbhffF_|ϣWJ744ٳgK9?/t;v֭[k|ι2)Mcׯ^z*hiiA[[O>eKjHJJBǎKJedd`ٲePRRT!!!ppp`\PUU'|OOO|'L7aXjؽ{7W@ #Fd[BWW'O_Ç_~./^+B!QVVb,sex{шB^^tuu1d[[[\z :vk׮LetYHOOǞ={PTT_~ҽUBBaa![ңRvѢE