Repository: pTinosq/Electro Branch: main Commit: 3b0fd93c2fdb Files: 50 Total size: 278.9 KB Directory structure: gitextract_v6s7gl6j/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── feature_request.md │ │ └── report-a-bug.md │ ├── README_content/ │ │ └── README_Banner.ai │ └── workflows/ │ ├── manual-publish-to-beta.yml │ └── manual-publish-to-full-release.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── index.html ├── package.json ├── src/ │ ├── App.tsx │ ├── commands/ │ │ ├── CLICommand.ts │ │ ├── CLICommandCategory.ts │ │ └── CommandRegistry.ts │ ├── components/ │ │ ├── canvas/ │ │ │ ├── Canvas.tsx │ │ │ ├── ImageTransform.ts │ │ │ └── canvasUtils.ts │ │ └── terminal/ │ │ ├── Terminal.tsx │ │ ├── commands/ │ │ │ ├── electroCommands.tsx │ │ │ ├── imageCommands.tsx │ │ │ └── terminalCommands.tsx │ │ └── styles.css │ ├── keybinds/ │ │ ├── Keybind.ts │ │ ├── KeybindRegistry.ts │ │ └── keybinds/ │ │ ├── imageKeybinds.ts │ │ └── terminalKeybinds.ts │ ├── main.tsx │ ├── stores/ │ │ ├── useImageStore.ts │ │ └── useTerminalStore.ts │ ├── styles/ │ │ ├── global.css │ │ └── normalize.css │ └── utils/ │ ├── CircularFileList.ts │ ├── normalizeFilePaths.ts │ └── parseCommandInput.ts ├── src-tauri/ │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── capabilities/ │ │ ├── default.json │ │ └── desktop.json │ ├── icons/ │ │ └── icon.icns │ ├── src/ │ │ ├── lib.rs │ │ └── main.rs │ ├── tauri.conf.json │ └── windows/ │ └── hooks.nsi ├── tsconfig.json ├── utils/ │ └── vb.js ├── vite-env.d.ts └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: 'FEAT: Your title here' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/report-a-bug.md ================================================ --- name: Report a bug about: Noticed something wrong? Raise an issue to get it fixed! title: 'BUG: Title goes here' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. Windows] - OS Version: [e.g. 11] - Electro Version: [e.g. 0.5.2; This can be obtained by running the `version` command in the Electro terminal] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/README_content/README_Banner.ai ================================================ %PDF-1.6 % 1 0 obj <>/OCGs[25 0 R 26 0 R 27 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf README_Banner Adobe Illustrator 29.1 (Windows) 2024-12-02T01:01:37+01:00 2024-12-02T01:01:38Z 2024-12-02T01:01:38Z 256 124 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+IMWElDQ19QUk9GSUxFAAEBAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJ AAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAgAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAAAVAAAAAz ZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwA AAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAA A9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RS QwAABDwAAAgMYlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1Q YWNrYXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAS c1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAA AAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9kZXNj AAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5p ZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA AAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAA AAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAA AAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDP FAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAAAAAA AAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMA KAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCy ALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIB WQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4 AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oD ZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATT BOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowG nQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiq CL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsL Igs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3e DfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPUR ExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSL FK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUY ihivGNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzM HPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUh oSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3 JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDks biyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJj Mpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5 BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/i QCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVH e0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9J T5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX 4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2Cq YPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFq SGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11zuHQU dHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+ wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZ if6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSV X5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFH obaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1Erbiu La6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsu u6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJ Osm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc 1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3m lucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe 9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf///+4ADkFkb2JlAGTAAAAAAf/bAIQA BgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8f Hx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f Hx8fHx8fHx8fHx8fHx8f/8AAEQgAfAEAAwERAAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQF AwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMB AgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPBUtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdU ZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eX p7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUE BQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEyobHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PS NeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG 1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/a AAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FUk1m8SQmFShSJv3gkSU0cDYgp4cs53tXVCfoHDUTvxRn z79u63MwQrfv9yB9GF2kAWIuimgKzsCeQqSa7jiAM1nhQkZCoWB3ZD1/VQbuIiuf2NRyWihmQxCo HH91NTluK06bVO+DHkxCyOD/AEk+e4+zff7lIl5/MK9nHay3kSqIaFqgcJQTTeg5Hj/usZk6SGOe WIHBz/mz9/Xb+EfYOXOGQkRPP7Px1ZHnWuA7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYqp3N1bWsLT3MqQQJTnLIwRBU0FWag6nFUD/AIm8t/8AV2s/+kiL/mrFXf4m8t/9 Xaz/AOkiL/mrFXf4m8t/9Xaz/wCkiL/mrFXf4m8t/wDV2s/+kiL/AJqxVTPm3yoG4nWrANWlDcw1 rSv82QOWI5kJ4SiP09of/Vxtf+R0f9cr/M4v50fmE8Eu536e0P8A6uNr/wAjo/64/mcX86PzC8Eu 5Vt9U0y5k9O2u4ZpKV4RyI7UHegJycM0JGgQfigxIROWIdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVeb/85FoX/JvzAgIBY2QBYgDe/g6k7DFXzL5lbyrpvly0stMbTLq8 mK2t1eARzSrGF/eTLwSQjk37bHkB9lT1VVHz3/5Kie3lTQ7JkYmKSBNV1ngvqBSJZC+nh/3XEgcG P2jVW2IVQd7qn5U2tJINB06+SOJA8EWoa0JJJI3UOUaW3gVfVV+jbDid60DKpBrer+TtWtoLLQfK 36GvnuAzXZv5rotEeQEPCUIg3IPL298jOXCCUgWzr8r/AC0up67DI68rDTVSZ6g0ZlY+ilG3/vAW od0Idehzke2tZ4eIgfVPb9f2bedxPMOfpsdy8g9l1fRtM1eza01G3S4hO6hhUq1CAyH9lhXYjONw aieKXFA0XYygJCi848y/k5o0FlLdWupvYwwlWb1kWSNI0IKj92queJA79K9986PR+0GQyEZQ4ie4 0ft2cPJpBVg0yP8A5x/07QrTzC8uma6urfWYRIYkiaDinF6OyGm7GvUda5vdFmyz1IE8fBUZdb7t vx5ONkjEQ2NvoPOicN2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KoK91NL aeKBVDySVd6niEiX7TsaHNdq+0BinGAFylud64YjnI8/2t2PFxAn8X3IVNXW8MMBtBILpiyxuwI9 BT/euCppU9B+OYuDtg5ZQjGG8ya3/gH8Z22voOvezlp+EEk8vv7kd+jNN/5ZIf8AkWn9M3bjO/Rm m/8ALJD/AMi0/pirv0Zpv/LJD/yLT+mKrX0jSXXi9lAy+DRIR+IwEWqz9F6Jbry+qW0KsyqT6cag s7cVHTqWNB75Hw49wTZVf0Xpn/LJD/yLT+mPhR7gvEXfovTP+WSH/kWn9MfCj3BeIr4bKzhfnDBH G9KckRVNPmBhEIjkFsq2SQ7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUl fRLqdnaeVS1xJW6K1/ul+xEm3Txznp9kZchJnIXkl66v6RyhHy73LGoiOQ5cvf3ozTdOa2aWeYq1 zMdyv2VRdkRemwGZ+g0JxGU50ck+7kAOUR5Bqy5eKgOQR2bJpdiqD1nSNP1nSbvSdRjaWwv4nt7u JZJIi8Ug4uvOJkcBlNDQ4qwuf8hPymuIbqO50L6yb2SWa5mnu7yWdpZ3jklkE7zNKru0K8mVgSKj ozAqqsf5G/lXHZahZpoSrDqqomosLi6EswjkilUvL6vqcvUt0ctyqWqTuzVVUT+QX5T0twuivGbS 2eytmjvb6Nkt5Z5Lh0DJOrfFLM5JrXenTbFU+8n/AJc+SvJ31j/DWlx6abqOGK5KPI5kW3LmPl6j PVgZnq3Vq7k7YqyPFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqo215 a3IJt5VkCmjcTWh98x8GqxZgTjkJV3M5QlHmFbMhg7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUr8x6j9S01yppNN+7j8d+p+gZpu3dd+X05o+uWw /SfgHJ0uLjn5BgsM00Mgkido3HRlJB/DPNMWWeOXFAmJ8ncyiCKKf6d5xuI6Jep6ydPUTZ/pHQ/h nUaH2pyR2zDiHeOf6j9jg5dCDvHZk1lqdlepytpQ57p0YfNTvnYaTtDDqBeOV+XX5OvyYpQ5hE5m NbsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVYb5inS8 vGYuRDb1RKd6H4j9JBH3eOef9u5hqMpJPohsP0n4mx/pe922ljwR8ykGcu5rsVbR3Rw6MVdd1ZTQ g+xGShMxNxNEKRfNPdP83XsFEu1+sR/zdHH09DnS6H2nzY9so44/KX7fxu4WXRRP07MnsNY0++A9 CUc+8TbOPo/pnY6LtTBqR6Jb9x2Py/U6/JglDmEZmwaXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FUBrmoCx095OXGR/gjI68m7/QN81fa+uGnwGV1I7D3n9XNv0+L jlTBbi5R0CoKb7/IAUH8PoGebZ84lGoj8fj7g7mMKO6HzEZuxV2KuxVHaJYve6jFEKhVPORhtRV6 /wBM2XZGjOo1EYjkNz7h+KadRk4IEvQ89WdE7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYqwnzXqP1nUPQQ/uraq/Nz9o/wzzn2l13jZ+AfTj2+PX9TuNHi4Y31KSZz rluxV2KuxV2Ksz8pad6FkbpxSW4+zXsg6ff1z0L2Y0Ph4fFP1ZP9z0+fP5Op1uXilw9An2dM4TsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVQesX62Onyz1+OnGIeLnp/ XNf2prRpsEp9eQ955frbcGPjkA86JLEsTUnck+OeUEkmy752BXYq7FXYqitLsWvr6K2XYMau3go3 Y5m9naM6nNHGOvP3dWvNk4IkvRkRURUUUVQAoHQAZ6zGIiAByDoSbbySHYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqpXNpbXURiuIxJGd6N4+I8Mo1Gmx5o8OQCUWUJm JsFjeoeTer2En/PKT+Df1zktd7KdcEv82X6D+v5uwxa7+cGOXVndWsnp3ETRv4MOvyPQ5yWo0uTD LhyRMS58JiQsFSyhk7FXYqy/yfp3pWz3rj45vhj/ANQH+JzvfZbQ8GM5pDeew937T9zq9dls8I6M izq3AdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVU57eC4 jMc0ayRnqrCoyrNghljwzAlHzZRkYmwx7UfJ0T1exk9M/wC+nqV+huozldd7KwlvgPD5Hl8+f3ud i1xH1MbvNPvLN+FzE0fYMfsn5EbHOQ1Whzac1kiY/d8DydhDLGfItWFm95eRWydZGoT4DqT9Ax0W lOfLHGP4j/b9i5JiESXpEUSRRJFGKJGoVR7AUGet4sYhERjyiKdBIkmyuyaHYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FVO5+reg31nh6FPj9SnGnvXb KdR4fAfErg68XL7WULvbmkuj/wCH/wBKTfo+vq8P9hSvxcK75z/ZX5D8zLwL4uH/ADfPhvf8bOXn 8XgHFyT7OmcJ2Kv/2Q== proof:pdf uuid:65E6390686CF11DBA6E2D887CEACB407 xmp.did:e2589742-a48d-4741-9715-c87fa59f7e50 uuid:13afde10-0584-4fd0-8991-dbb0cf6b8811 uuid:d12d0cae-8e1f-654f-a69a-77158b6e30dd xmp.did:142446db-8e24-4829-9ba0-84db788810a6 uuid:65E6390686CF11DBA6E2D887CEACB407 proof:pdf saved xmp.iid:e2589742-a48d-4741-9715-c87fa59f7e50 2024-12-01T19:31:07Z Adobe Illustrator 29.1 (Windows) / Web Document AIRobin 1 False False 2428.000000 332.000000 Pixels Obviously-ExtdMediItalic Obviously Extended Medium Italic Open Type Version 1.800;PS 0.0;hotconv 16.6.54;makeotf.lib2.5.65590 False 44120 Obviously-ExtdSemiItalic Obviously Extended Semibold Italic Open Type Version 1.800;PS 0.0;hotconv 16.6.54;makeotf.lib2.5.65590 False 44122 Cyan Magenta Yellow Black Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 0 0 0 RGB Red RGB PROCESS 255 0 0 RGB Yellow RGB PROCESS 255 255 0 RGB Green RGB PROCESS 0 255 0 RGB Cyan RGB PROCESS 0 255 255 RGB Blue RGB PROCESS 0 0 255 RGB Magenta RGB PROCESS 255 0 255 R=193 G=39 B=45 RGB PROCESS 193 39 45 R=237 G=28 B=36 RGB PROCESS 237 28 36 R=241 G=90 B=36 RGB PROCESS 241 90 36 R=247 G=147 B=30 RGB PROCESS 247 147 30 R=251 G=176 B=59 RGB PROCESS 251 176 59 R=252 G=238 B=33 RGB PROCESS 252 238 33 R=217 G=224 B=33 RGB PROCESS 217 224 33 R=140 G=198 B=63 RGB PROCESS 140 198 63 R=57 G=181 B=74 RGB PROCESS 57 181 74 R=0 G=146 B=69 RGB PROCESS 0 146 69 R=0 G=104 B=55 RGB PROCESS 0 104 55 R=34 G=181 B=115 RGB PROCESS 34 181 115 R=0 G=169 B=157 RGB PROCESS 0 169 157 R=41 G=171 B=226 RGB PROCESS 41 171 226 R=0 G=113 B=188 RGB PROCESS 0 113 188 R=46 G=49 B=146 RGB PROCESS 46 49 146 R=27 G=20 B=100 RGB PROCESS 27 20 100 R=102 G=45 B=145 RGB PROCESS 102 45 145 R=147 G=39 B=143 RGB PROCESS 147 39 143 R=158 G=0 B=93 RGB PROCESS 158 0 93 R=212 G=20 B=90 RGB PROCESS 212 20 90 R=237 G=30 B=121 RGB PROCESS 237 30 121 R=199 G=178 B=153 RGB PROCESS 199 178 153 R=153 G=134 B=117 RGB PROCESS 153 134 117 R=115 G=99 B=87 RGB PROCESS 115 99 87 R=83 G=71 B=65 RGB PROCESS 83 71 65 R=198 G=156 B=109 RGB PROCESS 198 156 109 R=166 G=124 B=82 RGB PROCESS 166 124 82 R=140 G=98 B=57 RGB PROCESS 140 98 57 R=117 G=76 B=36 RGB PROCESS 117 76 36 R=96 G=56 B=19 RGB PROCESS 96 56 19 R=66 G=33 B=11 RGB PROCESS 66 33 11 Grays 1 R=0 G=0 B=0 RGB PROCESS 0 0 0 R=26 G=26 B=26 RGB PROCESS 26 26 26 R=51 G=51 B=51 RGB PROCESS 51 51 51 R=77 G=77 B=77 RGB PROCESS 77 77 77 R=102 G=102 B=102 RGB PROCESS 102 102 102 R=128 G=128 B=128 RGB PROCESS 128 128 128 R=153 G=153 B=153 RGB PROCESS 153 153 153 R=179 G=179 B=179 RGB PROCESS 179 179 179 R=204 G=204 B=204 RGB PROCESS 204 204 204 R=230 G=230 B=230 RGB PROCESS 230 230 230 R=242 G=242 B=242 RGB PROCESS 242 242 242 Web Color Group 1 R=63 G=169 B=245 RGB PROCESS 63 169 245 R=122 G=201 B=67 RGB PROCESS 122 201 67 R=255 G=147 B=30 RGB PROCESS 255 147 30 R=255 G=29 B=37 RGB PROCESS 255 29 37 R=255 G=123 B=172 RGB PROCESS 255 123 172 R=189 G=204 B=212 RGB PROCESS 189 204 212 Adobe PDF library 17.00 21.0.0 endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/Thumb 32 0 R/TrimBox[0.0 0.0 2428.0 332.0]/Type/Page/PieceInfo<>>> endobj 29 0 obj <>stream HWˎ\Wp)-H="h I# B>i;c\=,ԩWݩWwUǏrW}<~8`U9+XE lu;TH 0)d}=AESa LJBX{2G< #vdl;&ǛPLAC41$3.8&EcqO\od)x\DǸK [qM]> & ;q a7xʳ)$v::؜!"獳`8 dr!q-}}0~B4Ⱦ|/֏z3ּ{[9.+ KyiDSeԠ¯&{1Cm/>gdH[UBKPc|#!"xRlZ[֙ O/I5i@2l'2(d,t4N53IZUv-9hi_gёRhlh]~-pU%As8`5v,%2 ~(qL_TˎkvZZ\t )[ M+k jtͬ}8+ĩ\ 'i\;T8a{զ=( RvRh)N;zH+D[  iN4Ζz[VQ $|6VEzYVaCKcUDZSNqt''\UY!Hi>c3L>Hh 'Y$ qkҷlYΠ%' L5YIeo'SЖ|L.戶3!}>dpv6*f PZ&ZY1\L$3sD᧮Vke̪p$YJ vKO[%rMjl4䗾%OCl〦\[f jajn*$()6fT|", JX49;*%m"LߜvC->ĥ6F7ǫH+ TN`$ "Q7&5EZXVgD69h/D\&lk]}t?6flL$7zc!:,^%2\;. D6; { :)}Rπ5G(x$vsJkaѫlPGrpO@[2HBKFsgDdj谌:Zٕ)Au qm~Ɣ6TNizzLF,ց5f24Q}<~al-6MYMɼRacX5):Ŀ~6y fz&pzA Y"|-ˡ?OX9B>NЦ.pODשm˥tE6OhdiK$#Y(MSS.&!"+! +Wp$;^RU\jeQ IzBDu]-Ɉ$%$ݞp9Cӗо+_icm}YCf9RFB*a*UCx"3նq^ޕϴG*etlN_|afҙ.x9aNP5I/NDIb  LTF[*'8k3;*dBˊVYY"Zn(g&E7ԾhiYDgcnef40{8@2D$?`(gtzV `>cKzq|w t^Yd`"mF'ڀa ջ$lVWee#22FYѲgV/[@zNf(rS: mixlh[wj:P4HSrthY_s9}p6F;5"A"OxrthF r6wj:d9p~Hwrm7h}z5r5} 1Fr_s-ޖNfLMzzC&ΏTjoͺOWx)jc0GGW^ϯ|Ļ\Mt~9jĤYJ@֒xFb+~>rh@KϹ#;=.䘈s*o?{gE6kzj^o~P9#}i`V#Ue ֜|.yS`䮞32N|Q`#jܞkmiA0'[U'ŵg!VCҶӉQ6~~> WF:EWjb{((*0dio_?U;]IijUBeCtp}|} o2 &ߘfTKRyʘ]C3gnW:,c>Vb3$rat1tb[y"։X+*Zg̲d+0U|*''@3K0VvMMLspQ bM?wA@l2b&+:<lN#D 4mFzv>2%#X(0I %%KgC$l_a3 jߒY'd2FsiⳉBL92T ն]A6;ЋR)r$;HϩDJ[c>RJmrGJMRo_RTlp40m@QA*5LO|ޅ>ZPXqNSGC*b$g#H`ljq+beV`b#I"`NYGq'\'IqH"4|TJs' T=ZOm%%q@an#i,X#z+Y99, rd_huC lp?YQ(klPޟZuHܒ / ">[x;ц?_@9 vw$PA`# Aɹ3ua)ImIdgu`pT^~?nLCYcnTFn*݌Mb~Z/tr4%pFDhXuRx~~ǧo>\r=^14K.ow?F#ay|yz~7O XIGTÏ?\ή61(풇0OϏ.2q"JoλY6  l\O@Dʾh #CF2OpP X95 S-:=t-'cAh$2;fĩM,gd;$TJ vU H5] 6TyfMlQj&ꭎMqKmǑDMbHf+ 6=0pt$RI'P<!\G瘊Qi:琗aa0yZD/<6shhLD2l/=x#4 mcL x);1e"SɡĚf_CN~}|t);sgC3}B@~٢~6suE@m.tQIyOx\Ş#mĶ|z3~*< endstream endobj 32 0 obj <>stream 8;X]Q9+Jo`#in2)\uRHr$>Zfno$PmO=8VN!1.D@C`^(1Kn2"c*l4@n;Au`A`P*u^s hJ)9@+g7Q1;r/stC#,gE7&F62L3%/@Ue5Y+.r#(6r7iI]P/=e[EsmS05&RqWIc\r0/NP;;j_qpJth9\*&+RJsnO1uZlP+)7W4`=Nd!WW6#rr<$!s8N0$[&Y,V~> endstream endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <>stream %!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 24.0 %%AI8_CreatorVersion: 29.1.0 %%For: (Constantinos Ps) () %%Title: (README_Banner.ai) %%CreationDate: 12/2/2024 1:01 AM %%Canvassize: 16383 %%BoundingBox: -541 -4141 9323 600 %%HiResBoundingBox: -540.193633309911 -4140.07124958665 9322.97697303648 599.831547443167 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 14.0 %AI12_BuildNumber: 142 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -332 2428 0 %AI3_TemplateBox: 1214.5 -166.5 1214.5 -166.5 %AI3_TileBox: 793.130009762943 -463.600006103516 1634.98999023438 131.419952392578 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI24_LargeCanvasScale: 1 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 3 %AI17_Begin_Content_if_version_gt:24 4 %AI10_OpenToVie: -630.261005516395 1084.12264004164 0.646159619784013 0 8423.62519862088 8069.85536734892 2184 1266 18 1 0 78 121 0 0 0 1 1 0 1 1 0 0 %AI17_Alternate_Content %AI9_OpenToView: -630.261005516395 1084.12264004164 0.646159619784013 2184 1266 18 1 0 78 121 0 0 0 1 1 0 1 1 0 0 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 777 %AI17_Begin_Content_if_version_gt:24 4 %AI17_Alternate_Content %AI17_End_Versioned_Content %%PageOrigin:814 -466 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 11 0 obj <>stream %AI24_ZStandard_Data(/X D)LU$ "9L]EX4"/K/T "al"i0(z8_<dŚ#X4>-oGEI)HW$"a,·*DƩ"I<+c$g ň qR42K$:o*CeE*J\4n'so( [CŨ[k͏CFfJMfvrx43R"V P$vCP.TExؐ\v{E`4+EHpӡH,P$ hxi)>Bxfz<Ƀ@Y,׳hPt"Iī<D8z0 u(IE޲x[8ԑh4IAbCX$d_-Z[PQ$l J,&r!,CwK[ͩPS̩BE@! << E"ϯc[nu"*;,5ۭRiگR{hLJ]ku>TU4T{{)MTVnCeIO B]!hWwS4=y-% ErぃRӢ7% *&EBCh)cUx({0 F`,P$d$Ǹb=\mQ /!7Xy7"C ]uUQOO푎l\hfb^^ڃq\+4);jW$Hp8W9dUj@C8IH_EV(X|HU$ j8HΗJYZna c8 bx M&&.蠄7l@O(ect+e|.aP$ r|8 QzC )$$%M]C8e3 EX4eEĊ0~P$ j<օ"q8P$g<q<Zdr{Q]"(G5J+`8tX<H{= GpR([]3ܰT*+,ע(G= Rd2345Ev[氇AWgw[AXhxx\ BPP:+-/1357dd#HHZKMOQSUW ehCPzkmoqsuwmc Y_y_rZxQ [.X,±x, EB 3ac #X `8A 7ak6h8A8:ҡE;p8~G>?RI. H|ܣ~@Gpܱu8;i8hlC5LA p0e(0Ā, Ǣ`,]yݮHt>ֱ]W7K Q6UR+)$HF.rNJEA6 <*&!85nqû=a cmM- Q,sYhԢdQگYE`,N<") D~8 "YЂůt%_Ň 8aÁEbk"A  xX`$pR(r 2xhaa"" 8 (D&"DEaaaB@ y  $""x,8P$4W X0A)D@L,D"(BcD,aAuRkՎ4d;[FYjiΟftZNBɛ,VWti+U8y.TJvCcQڊmh_eI3m>ΩδC sݮ-\ļ.㏈n-2~w߷fw7, /Qj{Uz+nU}Ϊ7;$\FWwڲU矓TDդ1*5Nx4C2ė'JV'7&3DCgfR"*e7坻Sg5r}]1<ڥWݎWIsn>O[mx2^䚲uS'~[Us.Mj^u0mS"VlUթ2ʧz8.dFzջQ;ӛIUϹece5m^w?W~`9ye<=injv.ߚsg,M ɦWt巬gyj8z mI1qaFėꂚ9{Yix ^/YWS[cJodmKUk$.wMq3 yKnfb(JT:OW kU.8iet.?o8fcW6~Wx?oꦝzAtu.ӹ-sCv.g75ܼ )RU4ܬߋj.+~r.hu)7]aׇGץ_v#!Iud}qΥ6Oݢ|.iJ$3Z.PFkTKc鲙v.?R0Vyy".xez\"^zx\*fdefS%:C*~  ̺)~&ܽcMQ+ՄҤzR3ܺLHr2c#$}%w=k-AL>O6<V--QsF^G]ȇ}ʖpVw'$;Z,>=bh!]kUXXuU*M]ur\3ߗ\SVrG.Z/Kzd zwg]j?ާ\%~.iL-vGw_ U9}ͳooeU\ҡ~cUC4&z5Iv][KtezlU˟Min-wj Z޺6vf~wKY%mbR}my{娹Ii:=ԼYllN>j9oRd˾*EGt0Kf'Uߠ[ ^yϱgʵ.4kDzJ>z?Sf])BSٽ<,Vo-<4v:yV&ڜFqh gKՓVOWe])L̓[%wu~E趮Y7S^6 ebgf9?E3ǯrzαZ[fhbFcۂ jPAڟꟗUHxe[fB @D@<4< 7(DA  Vw#OWZל<^[}pF5D#(֐-4g4coQkm*w:xw ڐ:Iخ&wǻdR-VZ Z7[ 6xWgnQ{O.)=L,ƣצM=%V0j6UvVR!I_kfbنw3h1Ԅt[ժuUgJ\?},-\..Nl'4ɘ^<;_ϬUDSTקg.%tq˪>*  $L``X0 ("xTZUFVk)Dy#IUߡսhkj ۦ> \ǒHk*|~m攺YI{HuK7Hj. T{He:U*<뿇/P>I63WLTbh1UiTu7Ӕ嗚~ݪA,UkC)--w.߽EZ3ݴ*ڍ((J8D221)'EJ<\vx;iŧˡESs/4ór2D;+6&XFۤ+td&ά ov7-~semd&n2 HJ{l uj*IrL|Mԫ1m␫,_UH%_v/Tkgj]ռ;aͭRϪzWK[tg8KJwhsVTF%*LqIIw7& ͐K) 3љL/;Zᑕy!#"C:^ݸJgUk\zJkW YQ!BJآ=[nԉ֫9✫V9!ҺDUhڜorx6{^6Z3TcE ^Ӧ:V2 f8g{T!r1هWVTMϑҐ5WIpR/OwwֳiVΗ4YYisZwi3e[SVUODnPrufΩQh Wm[ .<|)OUǘwsxڥ߬Zpz O5t9j;+TY-)}Ltť)pV Kx^CJګZb)aZb£}Ew6CxzڔOFRl[b-a5yegrvZϳŲ?헛E=*2Umڡ^cZVGܻƱWc--S*_ՖluLuYm^U^+Gg;]]UW}K=V[;Siφ9SU/]j&jꐱN֔N7O_vG2"$ݵwqm)ξ/xߛkvytSLޤѧEu YNJOӗ0m㻻Ҩo%^7lzd=MʳY;QY͸zWT%Eu:hz)鹃##R[[IL53*қ7}RO5g*ݴ5"ݲ˶43S}c3-flfS]R7mlg٦K}CiN^4VT{VuIWYwy& WڵkM5L^6zjLWQY_ln[J4gku:[tetVө EU7;6QgDW]_Z5/rOψOt֊ yEY݌9xLRU1ڸnZK[j7L['=v6t,5Xv2cMfq.U%O.#-=;e]~-o|rYWv]nl8j-o*;27f,:W)YowRߘyw+ܡúʧk8FU\)Rvx%9Y5*/hu0Ӝ/SMz!jUeU7z*jkEȰ$O4LOc )T @<Aٌ>D^Bq0CDFb"6@,s٢1 D@ntJ)P͚)5;M,ý= ^MYƫ,ܪTxnӲH/xH!Y yJ|_~hk'ws[Y4DÖGV!NgйH׉ `o PjwJZ&HF!CG DhlO3N) =-fduΣOJ,z)N-Nd``6jc;e%ߌoJy^eC7R>ADTPFJZtd_`F{nF1Q3zX6}4'j"Sb~!gWVx*nu|Ο Zm.">p;Sj_);W) BCcut~w\ɜtJ#!ն$]s،;~kz3d}W,z2E1BE*f!,o| 60s0\I3AT كJrtMƌE虣G֦yo?f*)D5п;ADAPZF YU/QJ04Llfm6Gau0$!hJlxĕeG TT49nj>8<Q)QQǬ, Fݶ@42?j,;+)tfiM>kjmzayV{OC=&Gݛ뢾!*X$&¹tr|gP\a0јS|8(u_MH8QxКD pꛜW6<l`">={~Wu.՟E UZ<Ѣ$tB2 b4)+FP$8|vXvCpÚɦ4vZ "ȵ62DA72; h0} ɢC`a.# Bud &K.!f.AbAF:&燔c%q(p< 6F#mϠ%pG;/iUsd0zׂ> L˝xQ 6`$@bvj^]E+JodIb sإ`xK;^)6qds'H{-귙l bZn}CtqWrz:O҇ۛ)|f2t>YnOr),#aAU ^{jJp#ĹO&IRߞd’ɹ/Q借y]mUOGX nIAW S3sݱc~@, ;W*=l`:eYJta9>60-Q4%penPB #ѕvfX#M"T8>toPS0.g'Mte& jvL'ȳ;6 %1sZr%N-HMރx CF:s7bJr@+UA"YGoڙ(Zq(j!t_N0UX0jpe5"0AMf2io *"a<><^uU iA2gw{+zu!&^ >Q>Uh=o!JѾu&/l86ya4*(5G[RKd&WHVɫċ.RU{! C!LW|fY}KCj,g͕K. --̵[,46PI"e.jh%Q6Ip-6Kl"%4"Co*P~90zsZ[?!W062 Ex6j >H\wEf5%Y*;:ɐm9t$;a&6cbQ%U p~%@I> 39Oy.t(yB(>ϖb%>Q Kؓ#GETMr5!#vE΋"&?2P4GS!%D y&HXo<ɱJ([Y|<jp-TS?& 4E={>&SW)g Ø?CO 4toH0`^jвeR#{i ߺKTc("0FmgprA;ay|S+ K]75^607J0h*./z0Rވדl.{tle<[jotqE y7= uT_3jqwx+"kQlmq7CsѣHÙ1PZ(H/sMd߬3YK[GnzZ%q+PS) __SaD  aUpNG Y+<&''G0`}1 t-+Ph#0a"y{LT [t)S\4IHu}֝i|+ͭu?ᴄn~}b;/=lQ$^Ƙ\Zڪ I`0o;н:yZ63A]nG4`P7CT*·z ?i 1JA@?wDb귧'9vq7cnDC(xdA-f_b=ӌbaa'c䮆p19IȢ:P` FeRbhHRbS cw'l$7ӡ?j"pịبv٩zlZN]7۠3 ѧb ]يYïΎ:uĤpw9J0(q=2O*&j]M-UL TǔD ̋DwF3}̈*#u~6*s@'xid+9S^fGJ*yQ!0"ɹ\SU1XMֆ!j4$PbP֪XZᐓ2ŽU rcw6{ *ivLiVșB  }o>}dˈ}MK`0Hoavc4KAY8ZV!]"QpGS+r"d/< 41L+],97"&j6[K+":yuJ:;́\`Ѫ}ٱ&yD]4<Kkrdo1\cc6Ռ%˳Ig,yYq,dF: /Àt u8pQ ¼3¹0P=4 XđS\ Nl_UIfvex>GM-e!3D]MָaFj\BwEHUGa, 1#D~8gxB?{9~%דߟŹ(eqZ4S)pG9!Yfn0wt{^ O( P8Mx25AqV<ք,XBCm]lAv n?+!UICSo&D QDkrx-=r~ ~*\kU+ao҈O >''|edЇB:3fx)OЧ~e'Y!`!(MNˡ 2X#4܉Cۉho+]B+䑮cCA2J wm4o1'EAڍ,.@P>!ڵ//4qp/=̟I->(KI  ކx+ecbצC qǔS@QN]'lFHX}L դ)bDO#[ewh6&2H<̃`i߉!>T׫_Dg `DS9:~}+ )MG[-rqH!/=㞣TRr6pF)Sw]b3чM|GHPcÀf'̚2 "dUFUВ8NX[&6̧W#v۲,Y28*AM3[өf%HVn)X gZaCǹ (7] $(cבqጀ`*R,f!)qsb1]d6xb:vD;"5] x5o5ksGS~@+dnw`YqHD$}5 l*O2Q4Cv.2F!ܻࣝl?Q k)(ڤUdi+Y0p$tm }ϕ#V5NL 2aίbS& *SE*mu'Xu%00IgzY"r4YpUIzH"6Ot-Z*74S0,1Z45 jD]ΑXAQAumZoK>D{8-`.f~%5ǒY Z*a!zH *-wNniH6a7-|n`t/ot / ȶQ緧/+<: &8 nP]7 _0Uc ^6UŪF,ԜKCU7 8cZdBb^ Z][:ՐmYf5(0Rîj1r"J3BuW쌅RHIzl\fG*7Uc_2G=ēK7ճyvlͰ'6  2lbRqbJOڡ*>I2 Q&yS YK_Wc jFvfשme[[C!x~b9}]-Ēz rŒs~!R57` BRYhE薍 sa!KW FˑˡM5h0`>`>xgJ e1# a޸T\x@);Mdb06 FicIc^ h7|1iop'kʀ2Cf22#6)g3g&~3 ZPEFՌ >(^ۤi")¾4GiLfSFTFٳfxRmwY|%0Da?H}*Cbtl˝1"8|0s ¬F]y^3Tl]>{Wt3%Άߋ}9E^.F#m\6vf{J{&mn@>A#D![A9*8,GMoq8WhB8<%⠟{kAԎ8,.I9Q B~9F;@sPV& OTQT%bNғٚ% t/3?PQAS֘oq+ 7fV my(V~5JYEid!TXSݡW4="o[W Y^W} b;q &>й aK.I6Ͼ!{D߉<;ߙRA (B& ','Pq4<${{¿ SiˆG%.̿5~\;? uz)ِٯb!JE ZB7θNcz2_ "_Z'P,xNyh=cI*qCRtb9kB B@*|KnTуQ}vY}5%5u<ƒS[ ,"bPMc+Z]gd*+}>.^;µc&s@ 0_""tXI8]0<Ǡ3NՁ1lbH* vAӻ4T O|/ajIͯM(ƮQaF ¨l]f轠2Ht =(imXJU\ j].sV"V*,MR75/sD~D8E;&'*B|J&&*8u0hF8я¬X)!0r3n]4%*S^ Vjd9|uܿO:X Ly, M25L!{n(!8[2asnAAE7Hk l0@~-#"sW%{;;4ԪAݰ\D N)\A7|gS lSF2$3QS4 (5r\f5<5C]u5P5lz@XFIl7Rpz$4rjbdm_dHr^ fݐr'7+[Y/wHTaNtoCU u7t,;ς\ΟS ٗiHHxTj2X!1HLd)MV 2v.nYAkEr: GIBE;Xb[\aX y2@zf.&O!*l2j{UdSoL! t_GW-dP#xIP 7Bg;TH|;W. \ y)ye;1 tև[QσA6I]n(n>Z.@LejW&B0*ZxWt,]jtvq!2F3!]n~ ΦT)FpǛ$]0*%؆1e:.3[w ]ʤ)]j]_gD1Xwݫ$LmD/!OaiOݱb~M$Y%0M/('* C5'L^ڒVX2ƫKuc%d ݨCK/j#ɼW*߅6Uc| jUV - y{J}zP/>z&vZ&oiYY'8jM^lIJ?3Jm$3*_f1˦]z߸阂Es/g+KpNi .ʞJ_X=e1{Ďqtyn$H̶H3ewXw~r`n}qYRfQU$xLʲq%L}(}[\׆S'pHN +~s ?k%Jzmni!B/EBZn2FZP :n\0Q-ǡFzT쑈3{eQ6"DWHEWe_ƌb,W""cE9[J srDyf*Sqd؛C$[rTh`jz0X%BFtkz\* a| )9NhK,: !ȶb Jx9 ̢J4V`- qSE/86T:l$ˆ@IB:U/5t/Hs?UA0-pU:47p# -W@p O{!DײX?/uZzHR4<:X=^e{UU: C[U(Dny%S)nHW&0TpO _|R3vLc[K#ΉRGV5nhIuEikBjoXeσjY:-J!(+IQ@ -Of kҘdr05&OȄvʬPNZ+i1鬛JcRv1w[4SBE&_Kbs$_)I[qKFS.f+JEa&1)(v%Q77 ױj>LUF$X`(=+ Z׺voqR:b+7RT1!剗qqfH<0]G)mIHk*w922YF'M`ϋ!`fdyz]IprCF8F^G ~Z8Q?'8OB^o&+24#CD $[&mu YƬ 3G=>˄op_/;EsMw‚X"ϟoL? SWHyP|9 oS8dxd9rvU A ո7}杣MkNc笷ѣ\bh9@?k [/|}[ ]Q(/0G,jnMBsU}ڰņS4~T- tt% ϼ^ XˇaC[p rXp=RqYz+ˣ13wsNx&hcm̝݃7ۇo#n0[[22HR!Xv]a1اyzl%Թ?2j_}&E "l^ܤ6Oԏ|tXZuto" Uձڽd S V0,B!g/I .WxK/|ķcl는ߥsK=&9ZSLhǝPݷ\MҠ"O>:z#TH[FrǤRC2e j?Wzu)6Na8XTea #*_|pvh,bAP&v B'&0ZšXi­>|䂽;Q~s醑Z( D֌aU瘓rd2߂m?V 1ɸJ HS=Pu)ei V*NSu0֎6; ),'cTM :DcD-! CO}(;PSI?==|VV|"W±r<;.YLiSAZߕlťal-BH+dzׁUΕ w)S<<$>z*&!Tď}8 GncSG?[vAyT!O>b1wpe Dbw͑׫y@`]Y%1SWOy f.7EIn9 ":+{Qaw䆼8 3A[tFD]Ua#T22Z.>0/GYcV2NjK XjuÎŃē<yrNLI?bJ=U=%W'%3ܹkbS_Xپjũ ;`ڿsUek E#5* VLM/U7ozE[ɗ+M#6U JX[KlզUn (JViRuJLC"x5/ {R:s4`S!-h~ɭ*9^4q9Lm#Fl2 j/}ipG[)rx P{[jJ}ޑKR֗/;\NWp^,gŃ%%}3""1O0]6ݯEYRwWRy7&rK ԜلQʞP{윖 BKȌ%2 SaDw}9)dܱɭa^7=m&'PPQVN Gn<Jv#H pKla-~Va7DNNrMJk7 ˼ytm#wSlASʿNv1^M=PA}ɱb!#u!gY怉0z5Ğ{hdҹStdn,TPl6,K+ʼnJѹ-qe];7^+Q9r5]`w&:txߎkQaÒ0}cؔ=kn'lщfظvU؅3#:?r@Ub 8pnĊM|H?0Tkkٯ"-6|{\-4ڮVTL0pTK-S:v U+~IGMIA)B(Ӌb1$a|6e4局~"r$H]0qE yAU@i.]U gU:#*;jp"ZUzGI#!֑#/`+Ȝ>Bua@?\`] BX?İg TN5Ѳ;M-c?@oqVzeM\^xHr[%LhA[S{~ 9qzX}Bc= ׻l. }k" u ѻ }'T`.{>xdyж]6{|++!aR=r7_HY+S *s'DPN-K:"[s\@ b8$FZ4r :Gҁv܈ Bޡ־MfD$!!OCGKC7Cp|v]nˆDHn+B7[jx8&:+WP?_Qd`5><TC+V7u2%mnּ>_{7 ^][,'fB0ЋEtR hդq:"RpQah}yMd./:e ꭹV:gܳr LkS}T6mOm\\k}9DXVĮl) S _ΫO\*ߗϖs 9nԹPD&!=wo[_Ff;_by/8aپ^+8HH>yoҩ7+JFhAh"{s!lwf[m  l,P,|a yF_`oQ=. msR01Uq"1SNеK_ 1C9]!t1Lrr]cwD0:Z `h;+\Z"әˑ"sh)9F= *O4\1{%i—mb牐X1-N>ט[},BT=_İH`R  hbUw;JR=3U8rU<0<8r⯀Jxɸ@B_Ml6E3x_~1*G}Q~&/EI․2e} qD-yQHZHuGkiIMGh;$qp#1QY$L2o#d.2K"=tݾKRlGn9ZF4[gQ7AHyf(oQ׍b(bZ?*+kQ)GVs΅,gD4,Gn|^A{/;#Zl'*4'B4Xm T{4?~B3\+!mvb2=x ]lq;ph :Z lj`T~=*e_,lHqOR 6`FrL"K^Ev"mgx)K~5G3wI^߇#?W3l $\rXz'?ڽ~ 7g 멄 QF~,v|`=d;Y$ KU+L[C7)Uwm m6!ۺn@B^%"Œ 9eVItcKaC9s'z&=3ڄ=7zLش 0ؐ7½Ŋ4.IhXƯ8Ay3l Q[2ωUj,;˗ns-7r5ߝ(3z\^ P7eFT~M{J-(W0qOHi_0vj5AOSշ,Jn_g{@ ?+/&cо ,tvGMo Gh5#PQ+*H>u׿ 'bp4q$C^@ntĂŁ[Ybf dFox'vyF2\Ϡ(V0aQ͆4ϽPL.LT_.ޔY#Ѥ*<NIVNY^™U+Љ|WїAARY͕F;uUuvK=Ȉh&pr]0EkZB D !h}{ܺ]%H4z8:G1^Ƌ87QBWA75ޮ7 T jn k4jkZ>C̆'{N#=0Q0h Sk'|trT1rFD.{4`WS- CtD $em^Q5+ XMP`[i"+n| Ią >ljnBdE"y)BHD\HܭԩeB]8#&(L&/&~!x @Itc˛6p"-5Z4G'Q4SarߞiI#BiekN}Zp)$ 3H +V@BN,5k`f*-o繌dE #kccqBNe0 !]}@ v27A X.ʨMW3d% _vi l̹2nM=]Y~0  dF% zN:Ji$MR Ʌ`"pL`Gr1}晧Fxl: ` &CwbGU#+c@1Z 䑠QT*w ϕR nkXl@Se8j޻Zmn)2v#NXmFbΈS: 'T2aC܈aa7Ǜp'ѳ8>|?gf/;[n~eI .7):݁.-卄860G<쐲H$@n 94?֪ F~jBقZW(R6 zS1u' 9,{M9{9jB>qXD٧-Q"N|[ȮY)`Oy]?ȝBC6sxqC|i}[mYD,IySW&g [봧/OlNI-k.Ʉҳ)n,=mi}@ۚҖ(U)ڂ,n}jޒ^k4ڙKYk)'>Ûg|5qiH-齔o2WD6*r"("|"_,EIdY!H3t}D\h\HRAQjp65큧tIK4l?64O)ACNH`jwc}Qt뉽 •$_EW*jSRZo#aˀPk(S_d{b,k,7!+$ˉ %liہ{2n;x ;ƙf0xTU>`޲`s"?`XOzTdYEgk"..ň-)Y7&Q"fEo#vq[;nds= -K>}v|@%OגjS:Q*Y Q8v$wPZJ'zUY̳Z֒hV¡]i7vc#RU>~d=RtQ?S Lߌ>jgBgo1|7'K@=0]T~)wYb"W啐&Zqqi|3ԭˇg헝YQ_ "X ނդ+S'@)U~<&CX4MBXdL]d"HGcbIT)]y~ jk R*p67P+ 5qB :a8_KDoC"ܤASK1\fLW$\\g_63 Ji_<Y eq>t>ɂ8,X?z-^v":p]!W oTS .V vt6:}\<(ZXu"C1Am'bk.,pS 2Ny^kgU ~RJB5$UY^?>"z_'jQ?P΄Q ݫqF[}yse멕n `>\|3=.\%JQ]9Hl7bhr؄Py*xJ[K:6Z6ja 7RVf$Z5{>7z/c"_u&B*S$[#P «&Z+Lj1#,V2-户\UK(bL7Ä31Z32S.#&&ք[V+VX왰Pxe9"q#DqH ^`2Tid^LXV "tVTkDJ3<4 a4e3C,Φ”J(+%&}=@<>=V{HꏘcF,Ll%8 x$)ud)sBa|dȨ7TCĦ&df-uơ- .ķsSFdN$ͩ5@R4&LdfF1"S7UU"CD <̠.2Sa^UL{3kNEcaLa|# ߍ/6$FM keQ;Bь6!'ujJbm&6X;'N(CȐ.I8{z*. Q|)EceYlڣC32#zm 6R@MjĪī&zD'Z Y2YyU}@mI\bQrAӊO$_6@s(FqNq(BY8}7h7(Dфd,ZFOLN$gBΜ:© l,dD9PXcsHYQK2#z"^Av*ꦛ?)6@&zN #M]ڑIL^j:ej?p EVb .-Ś/!fTvJߦ/!()x#wJq֖tGw7U?D6A:$BB9"T0J^T  CBM D}$Ȏ0UšYD>&MYP8(fRZ Y&fMCMc$[D2E R bn&f"}ȼyXd,G< "#TR){ 5 >T="C4!/˰C̅&\xC~2ٸ"F*IqdZ1%“b&:ec$kYb5NC)8Ф*H`1Q"+FCN㒰wU|؅ӴD8=<}xio=C~ͫ"lƳL%BƆ:24(gCB~WWQА12VBVd3F ^F7q^ĭ Ea(.'b(7Ԉ8|g:3lFIȬ!oCLMSYij%o4pxrJh#wiLa9AOIZ>VէrHV "bRCia)<TR \vۺ:XEMgQm,P%]/GMjtU .&CUx^Ġrt!V V|2w"?PO\dJ* :5j[eV(BV\NTh+Qu!cLA4bQR(2 P"#Ġ"CErbS-Pw" SC[ꊖ/8+oۚޚ$uIX3% 堄ϠwQ^U%% bSUT&;WWM {lMzVTHTvWT UC&WUuQ*k!64_eOF. T^&/p w/V$XzqLSx/zx[,4J-DGGLݗ O%kR/1@/*s@sF4WG̗B(bbO~G!"3=x wgl_|H)ٚfRbg_fzlӕxp&'&J7iMc%ݤlHնoa2jv5޾EURTst6Y8lzd"QS$VdTx"Aqh6>Bc-Wg~)%_1L%5کbR[h |#)!ۍBS *YTbny|:߇=%U6D Aњ*y4 <-cl OmKܻ$;`Bt&̠╖oƌf2VsrB3qٴB#ӂR4 )WmrHHL $HT|cI֣Ԙ8煔~4~uE /v2&!רIH3V*3ȍ jьΌ07]V kYs3bH KY֘EcbJpXAI!i,@%ᐰɡd٩IC&9C̄&dC00[߬ȨN4T̢.[/z'ߎx|ߎ IݸBn/_;E1 }>EA{)kUD Y^t,VnL$+,U GA!^: % M'Ze-fKkk)jTHg!ER$ Y2[se +hJTV+> Ħt YVDt/2)Unr:i) -$&ij9k,فƏwya"KH9f.B]18&Dy- Q QEm؆V [T.4 VX4 Vdۤ)*2LPoaXdm/G&ouWT"b0$^D@^vb-Ya y)?pܤ@bb&F.* N=K$%*&eY>K1Pv% vLdTdar!Dny C| ( `bL2D"OQͽ yidd,Ղ T N?^XT(O p!dɼ^Mln,.UR#it0e !s Y=IdنF5QmWQzV5O[B%*;:8@ 9=>V*76uf;P<Ę H` `X@l@   D M 0,^}CSՀ*M#9uKy42eJܖ*[֛{fPdJNR1ڢj )'`U#K/ ł&42&}1&^kT:7uiF( Q,̓j^5.:~ͤEZi MRC2$Rӑkr>s+[).^DΠa:<i2E™!3&4-f#%Ͽx"+y#H2*0װ6U#݇ػǿ}SsCOšIJ'^]Xb c /bgC[b[Yxyfe(ب߯7ݤnEB `@ Cbx|4|@ AqXPZ  1ƮԲv 婢^ f\?!WE m #3Oy&6 .~7 fR>@!5zS~^:6pJql" ]%JwCboBgVfH)b >sSPL& tW]8LL{Pc#wɍA(V@ǼjOAVHT9~C 5:,ܴv&ߐ͕'ኍYP&j:ȿF=;ڷ#ǞUK-mmR7|+1`σA  nQƵTCE('P/.gj8o.hrM0c3b+j-4/w#"ǖYg"Fݫ\pObJ-^gT > pf7&s8?m 䧦-H[0:W3jaK),A4kaYvf}zRƓ%. )g XR*$~Ъ_t`@I, G=l~Iq3f8"q$ifsb;W}:g4Ul87*h)0Hs܅kį^IUScߖ8քXHu|Yn>"7kMlR09~j;2` #>(mJW$3ڋc>|})}F ƌNzþ]sH`=8=:O=)Ahnfǧ[XB6 A3|D)&fBF }yO„%A{4v~[+RT.2mɝ7TԎk\C"^5 Rm/!bzGibPmy?&u{,ww74LH,G/PL]X/%`h@q|aAԲEpm{jBEZκM`dYؘ?/'T {)qdT2|ϡ8X,;jOPW.̻.C ̋j!\P]۠{t0z]D T5DiEն:[Zd;_oܡb!Op{/IN :H¼ nqӀ>M;/ CsvEe e`p.d2.-CN, .Ca3!c#0=N4_H Ŕ.KJBʯyBИ^S|#OEzBE vK%Qg~,N%Lv!C"FS =[;Zq ! ek{؜ hZm{`K@K/ִ9 ٸ|B[) .Y9J[sȎ>dYJik\W ;nB:mA22=_5nD#t۠ 9ON^}%ٌRPRȟ(B0.nuϽVzs rnĉHTki4BcmU+V ~*Z7ha+1Ua.SȾ;k<,X /RmK- l.W^9[F[%ؾ=Ql!|c)2bb^Pq>(+wmj.oW~.8m0 X2#kpöpkkhKnӫU,C.m]*srPϴh@ ƵGcQucVSG *J&~ &zK0on&WW4M{<"{W[Z&i3~ z D#0`Q5(n.s@dVQqAOq7zZ{#n{A0^RZ;nC̾sv`B6_VpAp,[`̕P3(ap>Y0d!OyNE" \"ц>;ymxҟ7jS[ukL=M0vptTH[HU`MDnvZz'\ 6EИУ y+h mAۣ ,Az"puWJȢfY@97!y980y V${֒UKHTcBUV ϸ`Zr^r;qx#!|v]uj 1D;e?4o)Go\{vF/=*BL>Yiޝ~Qp1zNݖrı踎7J3գ _- U7b>>j9&ɗkYjՔk;]ǂ u0|UR_#㯽^|-Z, b]b$Lo,dg1 wif\6Ԗ+,=tQUm(\tyzJj g$qZmݦ0;u~~ Dمbk$=&Cm'!@f^XEɳc]_;R6v$dK##0t-"`@r jrPVRMS{-q;Ҝk3矺\l, B%LBj[ͤ".3PU@oEe+&yM<8iCo=q F=G*$ӻiPюB:mǯf!:H`#yW5CWT73ፄj!TVd /4_EhoChK ?T] QG믽l1s`A |."?'<̮e,[2mvPyJ3WdÜ\r{NZs)KUK`E hWd XCU$=J(50^?v%fL"-&$GbS)JWO fs"8ϣHИDTrc$O_IkKLVܬo˜UADiH9V}1Ġt@ vΠ:j/-BDŖKQxi".Ѳ1ibS>읻ݔ/2w~!Q"95v>kV tY[bfmq 26eàl\H{KW]"#``Gu\eX1n,˸b cspDD-p[]VguG :bR <[6%u15+z-?5$ĺR [녊[ITĢiؙ B`tQ/70޻|z !@o4SKM0x0t.R'z2TEcE"I!~ a;:Jtba% Q^Q 'ursD7GbMP H3BNQ}ΑqZ#JyK[$FiKs|xΡ5P&0|j Ѭy7纰X6~P.ec Vd)?T#}7NfQr%)^]H=w*SR[NA1 LeA .-PI&̀{:2~7_G2ݐ38ꀪe!jbn?o*Z`I1!oPyMGQ"F\PZ{e_:Ӓ^z!x$@#M(D 2붷{v3#Zb4+Si@I}tVc4e 7OTS ׅuL"Rnc1n^qBPժƫo@ѯ"eЄ$(:S=& Iy&8}swpl&Ęၭy1 rb&BZ=*8:P&Dڃtdb4d=dA> Gܥ~Ec ɚ<",G&?2<Է!KXԘA,&*B1R$ ʖݴ 1 R5d- ,D5¤AmZj _gdt}F ؈pqx[Gt&U`/ |IN^>$%W%.0ȜK@< ,J8%UB["Kei KSK$`B_Sɲieo,x&d%~W5+QZbmbc$.J ($NP%p7]A_ G.V}#˜n*zS/,QºaDƜkhV1Stx8!]ЕgC$P5!\W YԆ lH5C<lq6Oi: ]_ۆ^`.{^$7 ݨώ8)&d 2Hp@'B$<I!B8|w!i >8s)5, Rs C $CD31::A{tx,A0FFCDkT>zq!i 0v@+O a#bcrxߑK! cG˜adGAL1Іcc#h9$g?=!c<0?1>1b}>$EQ]߇Cȶ]}H>ccCa-/N' T|Cm9CMXXa#s~hf h{@ A㓂d`(up)݉}`tS>6(%o'A$(3)ȑ%{AAMd42:D Bm5Pmd=/dA:S3*  .goE?A(3)Au_.Ԩ5V.5V/5FgYkDEaWC*jW~.54 ( "@4=!Faib(mxY%D4AixBF hB5Of!R tf,Cn?C6wÞgA;CEgDLDĮ @7 ی/]klI3BAm3Ñr2Ôp3]a,eq[^D;(2t*CMa2\2Έ1\1< J~d4 t2# ד?w$.>8k~$Ml141 !R8;2Bm@C"\1'k)#ubϑ !l/I $@ GDaXI$aY_7 SՄJt" ߢRO)`t1R0H{`VV(ha=`h gF Yo_4(ю_Rb'j&$PJDaľJBzEz1Km^PH^\M%/_IWTPnD$D_Hx$.""]P%қ >BɅ Dž>qES0.PwXIm+Rw|{cX(x-9 ⍗顕6WiaQiJޭdb&uЂ#iςd+EYfbqZ}+,?d=G9@^,DP$,ti_!mK q %[&r1+B4&J؊$D0LorEi&9)+膰/VԌM~^E5!Uަ&ڄBDǪo` 'zs"K>\$ר:"*N zwT&)4O$) Na{P'}2SS\O)~%LAPX\ +(J)% EBxPE SBOy*n%zFiQ4^eUQu ʏQR$:^LL )B*J1A(W)U@H (8B~bߘ>u>)!ɦƁ8:a1dPW"%p%M#KC*N~9!l#İ- .w`ey 2VFp2`&%ZVx%.d(!Ub=M7X.+r\ҧ$<$&+Ajrq$F.[$`eC"c(FX-HI \~nl9!P4ڒO"W݄0xJHBsQ[GnB$D0a @+3,%!iE%- 7BxB,YRE,'7LBI׆18/ .!00\ýSw%ORHXDx@μ@c M ,'˄l,f` `:,|XhQ1?ᇡ0`ia5샕jةe=}XZ|@lыe/L[yj-:Cq@9t- \Ճ  Ӄzp|31.15@.$R ǃl`}x@F<$.~Eޡ+S].m/%;$pd/u04!n^*gu^(* /`/K5`+{9 za #/&Ρ/`fS(0XLȡ 㠼`\[ qȼ:xã€8h0Ȱ㯝0O8Foì l1sFBㆆXb{<b idb,6R c,Mb6`ưC6hja`_C(X@eBYL(QXY DFyj0qdn"< iD,is2 X# BRFh@He ~+gPOܶ`ˬ h26T3ޙ32 3 hft hFef ) !6D3$I̧mg@1yF!=#cp3@CYK%4[01:!m|L\h: a0!͛?<BPmk iњ_NG_/0dABz1/xE5F-\j0yt!ĒsA4԰T.bk|5SkH~ͯ-įqT aC/pQDgfp5d!x,6b;_[GU C^qFW6x0o#>WfV+ qVDGD\ tU FYqUr7K# QD{IMPА)rr NF%M羁)7ܕKcݚ) GF68 g(e.S%pP(8FG~BY=A`8D1G6'3K #r |X(&x)m& &(Srg Of/d0(~ r>]RȈ%JYN8A(!'! BP$ $H4]8"Az $9c#՛3`59}(sTP&t$:/B"$p+[:Ӧe:QHQ"X@бx`묮ZG \'Dphꪐɍf(vFva'vq(BwF7"r\NɾwgӉa Ou4!&DD<+[!3#ykks!\tY <~BuDpqBG AEʭxNϲfы H} #z S `-'=3݃|y{^iʓ) k 5#iXc>3R`-P[UFA'褲TY vgc8 A1`yhmGO˃= \BڃPd)|g,f{P r=ȩek{0`i23,6TS%\\%T0zۃ*[PA?Yֶ= )zPLHMH'A4ms=`IQ'{^~4uG =gQBY ,5ͦzdF =άf=(vUY qуo+^T9X: #be%opoЋ:fd&N}24;ZS D*6N{#*@䊔H.#xsqCE] CL/u`A&QxC170qE=Z&'mAVElD܀ZǸ'q-u>۠PY 6``V^gcklpSkp+Q X Wݍz4 [g# E޾®Pf׵ (06^d d^?% oy%O->? @ixJt)v SGNo'$}.h% K*v8@Ɖ1cz "#.?S9#8ݻ[JzcE ֌$uo| oURc AF7cs \cAǁh6Ezs]QۜKgGW& $C:bsݯilhhL-Dusx t! 8e%wSz}e@eYU+׏ _Kp |bo+ l7V1$̀U08B/PŴN7| ]MQ L2@F[SX1BBz> y8F0#P&S6xD'gD` QM*D!q I^^X*>S⣾U ФMxA|;[#zNq5@HYOwp?᜞xtZ 23-7`9%B5;J8^ @QUa)"ȝAy^b1@؄<Eτ(.HrJig P8gg:ɞKVsAŰ+M oO%20A߻XZ?4Hy/IPd`̄MN @pqK|H*MxnD( }Vp:RL$pYBq ɗaQ=?Oq&4fգK]ş4/%VzQdٿZ綱" JV~̠/ Y]U:[p1Sag؏HO9z6הh{IG PF<(;l4LTL-"s[ZCufz2ڻ}hnawZBD¨ӄϻ(9Pdc9V)Ńڏ_,ǿqߏ7]0Q+I[Ytng=ۼ~FŦQus&V]2~KBR%r`osEV^xLs"#83dF'eLSO||ŦWԲσ㚥F?~ HJ&ԏsyhSꓔ-! q!T)(~۶ 0K?UFҗ<Ƃ. n' hsG; }}1;C_R I7e>m-x$]|Z86xC67#@%y5xĔs\"c\n; V`.|ɕ⪊3u`cU]$w^9ؠw̌ su[{Q H`SJ4W#Rz.<]jL*7 \Q j=6sfOW3`jXCO"?J~5^ /V_,D̺KO= ~,FP3F}:7n5ß%*X_ȧ?Bu){@۳Oo.+ɘ~FB@җ-44ҟ3Rq0Gz%{, G;<V:xY̒;h]-.ГzyĚ31'd֘.BhP<zExyE"j{~ n{Jh4-N3"0?!|ӧBL@tgMS$λ|OvL3I$HKkhwsŽ@½N(Q7Qu0lYK+Oyׯɼ<ԀmFنH^'r0|)GR!A;K~xȔ"$/kjݱa7M!uLC3;* SPа;~"ȕ^<:6mCy+hS9lV\b$>j[+8 ՞>b)o#7 Q͉..Z x7HTUT|7(in_ZwD JWz+9ºȶSNe+"=!nxsބB9$r S&{u0x $Y~߳ӇDF]ޱA|1rV (3"4_!FKUe ׿ۺI61"~&@nM4lUXξRhbw9}fR7d1"U|jl-25hj'=6_1]!3-yc{PsؗrNyoL.N]q/etBCK1c3R \9^c=AYiol;Ջ_M%el͖#=91M&p)5]ו)dGO~^|ƨUz$pov5K ACvsF4pń6Mr.#Qk#k9[# V; Z7j?S(._5: '-RMQ jCV@CseW<\UDg(p:!5E+<ɣ=D[~SPVбe۬b[^+Qd-B,/ ʵxTpL첗eIi-qZ2Ψ4KE+S˫ nCA}ͳ,Ο,(C HŠzP) h_ٰe\A,J~~*;prN:O̺uءxkyGA_SJIe^-s,?d$Iu92瀯N%\`ID]Z=#mo߾ V<B@wS 7gA}]ng-YUU\|nGԑ/EzeIqp,#Zӫ+t^Zsz-6L{[AE!ۚ8d3Q(h@RGaoKdUǚ1^ `Kڟ'w K?DF4dIz*HQY Hpn$E {BF_B_ZeJFqohU9DNwK '=,9?ToO-a^y;T7jw^`1t%:3{5L\(ab9OQkr#!)RL,(8nC-v ..9ʼn,T~6g}%b5UTs5j4BgO=} !gNir dMSun.a*R.,%sv B sj`5o q֨f(%?XVpƀ1Ì@_|y:c&j82.' -ICo{LEQU|-d85VrªJ;z [q [YUds LP&`D j OuRN9+?+(?He!}(dK@>Q[ jNfHxSrB[ ϛ I,dBrMv bm;> r::6 OVxJf.@.;VWl$"T'w]țqKτV9n휟nJ. <37ew)KtjDe ~0R r&i%rag-Gݚr eKX:\4+Ȼ8 UMeW'r7䙊' w|8$Oun GFP 9N³^| 1RM]7dR֧NLq~bywgK ʟ `qq%8¨@7~8wԕЯq"uTaoOIf}gDUpU**,T0S1=_B '=[]wVn}HA+3mVv`*z3J/)m|ŭb\N%.8@0o=m<}`SȶȲWī Y}1:C\Ai s:+*~*XoI |y'Z~{fP0T.\Jʩ֮[1/3i!ґ7"}n!oEOp;\!a5kܨ_h.b ܇ܿb@6 _j VQy{:* -iqv!ݥtN`JW ,g$o&0)w )@@eO DI-}[ ~q" "-jϾΥC7>\@xxZ%{>py*T`^_4`쁒ҿ='5keDso6 mMtjoihuػ=4zk@M<Y~zgŔFf6# dg=砙?9Ѽɕ!1+뻇NW N-ñ $M], SW& D[cw3\ TnAvGn{KDR[VdhAn evB@ը,",N:q8AmM|Žc 5Fm('0og}V" v4FضJY7^r`;pY'jVNVASF6 /(v}.g~U{X[QK!ZA;ڡ\AԒTIqh"=坡=rbz ]7FAv+g/Sqvj, mY8P9I*`e,[٣H-S6DRƺSuqއj0D%P5P}V<ٜːIZɶjh?1K;XX 3zvy+UqAv+fƒǖ% +ϏMQjhˋ諎UgiRcbz~5=>E@= `Wo3l޽Szc`a+na{J*.КTЁ(ɾMA^oSfTZ: UAT#a}w!am D=+>kxRWfea <;RE,9l/ȽKO&yJ\W9#1;䨲UMqe`co5>u.gN7gp *grsTLnMFzpAhkw`C.Y5SM2c68"iM,i܏=7*h((l ~i/ȥ@FhnE`Ac1oGpSW:"MKú óuvq[ ZLAګVgnz+t"}G{*9Vc391yc<-N_5!Ajm٬AZDrz~ڰ] vʑje VonDu's?qN]+0mdʧГU+GKiXxLf#$uYZXIMG킌 W 5ynQӦӨyH-HqlE-" Ұj7m j6#_paʯю ~(pQAm%e 4 5s鵕v))8 qj8.y}&VQꭌ`bPXd.'s&K\= t$"eģ,6j:~tV+X]tzfZx E j; Q5-?\gԖ:*.ƤcA-Q1Ba^CWwu)1H61P׮1jM OLf׫ A \\cAK/?=pi5_ ©i_{jueT)MN֤ר'bOת7\: RIi{ȼ0mTnLW31ie&V2-WL'm2퍄?8.M0uMQ%e6|Wihh^w b8FX{6+)"wjX\{%\.;5u OR|cFh[Q7K鹑i AFi,*Tta2*Z2NyJ UT@x%TZ UU4泪vn^kgX B|+>m[OWKƫ+Ӿ^&ճ}ub=]ykLL u l'YQ>Wewx'*;H֨3sV_ތ5Im=tnU6֗M3<,֢UXVO9fu i7']yXϣ*HJxyi7>&Len.dwd_Y^,/`_GbWufP! TWA#sL[g'NL@& T ΘX3=TZ=֏P[SƌdRɂdOYh*βpvxS/X8xBnVsʂ,暽YO1/N`dgFYIU|`hQ;uII;d"UX$ikvcBd_jmb-1U 2kwN,[EamI]lMPNKeK^8=[j7lgug]OwJw9]9ᵛ1 fT@|Huy1ͿjU(LKzԻ}Ptogzd/D& T< <ot8>Bv]488F4_ :~O7ӰXEWL=̥5zR'4GQg>c=t 8<Rai` ErF)Lê/L$ہԯ+*sT;``F(;ab|0ZwrcaR"}fX5h- CJU`*(]2}q!2!-x0A0i62%*O:^kHNp39)δhϒaxkwsz1,'x926c?5EqW0=R`Y58 E%dH帟4NO)[{F>AMJg+Qs v{rYzG9 NN8i@EGQ%1Ǟ7.tHsl&?E<_hܮY9,+(鷢O95q q*OS2[eCAD嘃`T j(x75fk({;rh L<1nd_ 7_=<8)\ҢvӗyU3w!Lhe 1yqv~f endstream endobj 12 0 obj <>stream Pby !"d K3wTWp ڷwKKGS.^a  O*ԄqP@x[|Ts''1pg`p9|1*zk%>G1ȼ0c$=Kb)Hr3L͐qϭ2%%@Ƅ9!cd dOol *q)|LrTwYy%&-ʤa~?d8SYեc,yen:Y`(1Dk0PƝҍYp&4e. "M;h 'ˌ4\x¸6!aL: ~O@i4/x`Śs}ţ"wvLޞa])՛Q.vk+37ƀEOJs|g4{)X[4ٲ/>aKGv6"bSۻ?+]>}1و̲XX%q8Qb_QO8pQ ʪh8U]0v-.}CPѼ5H- E6=]7zQ#pB9.RzxNS%H 3_+h\80Dى_uG Eʀ-++ոgm89|oiE y8o)~}o?uu͌7n@h!Юol 4>%uN|8(~26 I!-YY'qe5Iz+<8R xV"k]O^ӓ#ۨjO9shr=zyR(t:*<﷟tyfLkQj._}Ts ϺgiwugPC|{y,A3tcC*&a)LY s?sXj2:c$p6n#6kfd3_[HȚ- ?[KWOC؁sr 3Ňk:[,YR&l xdMAr^%S.Da=c $-]_?!.l/ypܰsHۚy_?Ʌt_jY ڲ2vQ RbAc!+l9gmFH(`;chؙ`}:k)]u4&=A$ >Mz/'aqje!Fh`Ԣ@`E6E2 ZqI]S+ ew }b;SS!9>n$Fd(YN?hFJf;;|BР#aZ 4[ U Xǻqӑ0wRuNqcUZ(c9g##y4/8_`XKcuQsFVCxS6v]keZ'k$M` @Cq-^SKCu 1i0=@\9U8ɰ[\/.ElW2+v^ki힙ƞENU4xh>v6G呝޸6Y&IcKwߓpPf=vxl l$LC{hߏvt3L7ת6H1k#`1'UF4Jٲkinj{mqr{VݮpXٵۧ6Y, On6[/\ylpuE6Vx1ea7CN EFd#m}|vۢ:8P}vt$GtelU">gG?h +]h=mS7ۘW5MWr-_7Jo{Vw*wf3: 2D΀(Zq YV@}[koKMIpַaX "D4̷ItDI}R /m"hexNf}aL O (H o7a]:Vx o7!;_˹Ia7YUo`9>),ߞ oëٵŞx{/>tm#c<7vW /-`{6v^  c*o`Js 6Hvs z*n"a:/pSEɯ1K44k>|Zs'}fv狵Ip4ze$`m5&m)IsE$WmX,ֿmm]kt *6킄{I:%BUm%"Bƚmb‹ٶKH- \StpvڅGنvV2P;>4۪j9$/Gul\Elyk3(-71|`$mͅFC]örΓ 6Ėmvmۮ"O w{j;ç3KZ6GuZm̻]lWʁw{Y2M16޳2p[9HqϟgǭjHyrI`nS_9;mR,t#$.t'[7a~ toq,ݵ*", {Be[v;mn2B!H -!>@P`vc=ggE펲;jŦNd^}V9^bò.G7&.6:I펰w ;TTsw3yb7TZFtGw#:hk0wk!`Lswv%6ŏVn.*[or,Q xn:Gq5VB;W?`cpc wҪJ8 niLθJJ:򭖿[/ȁp ^/PC%r]ݯ~BD&x7- ޗ:oqx/)*g41EEH˳@`˙2!~ o[u [ΩSM{gr:&q9zⅼCIyiwx [OJx8:rqm|e$ޥ7)-_.K8a+NPH7 ?Nw‘uDn#CݨoMM5{'G_bfƥgJj9].2'z!))оɆFgܙ;M;:)LHH|/BF+l `{V04P:'Yv^XCB&ut-.&/FAQ? Ss>I2:Y_ԫY"ɺ-{, CEzf俞 GWz+8i'|2=rͨz-Y(:i!4Rk*(۫@ȋz7M%'.}(;tNbtgy¬;{TE<VfDgwh[+TG=I^i xv4)" IADAU>'=w>5ej% E|TI_+ C~9|LDW70/}5w7m%_/c [n7Ea9IB`?_K,ѾT)kЍf4`' hg>%>}}~A!V+W8;/S%ImDU~Ho'_/2s#gl5`jB3~vvcVrn_"w! w@NTC优 cgqS>Nv-3Tϋ> KIŹE/dsw@"&~n"fГ Z6֗ } .@τK;IBgAz%)y\U!0LY&_6z%h3OA_L,5jE猼iqJ < w*;ju tuJ-W7(jհj@l;߽b* T+k!9R])bC"_{'֖ͅJ6\]}ܔA7/ɅZ0J@TDZ'6p9Hl3Q,C0#{4w BD".lcl䉋p‹x w+df,\4W`=F!e&6j*U?񴥃M9+CROBlQr(A49 }zAd@qT,sxJ`cOgTPh ߣ+߯%Է6=r+F!ҋ1@PٶZͿkx A S{kԆZu}ݕg^_!k.4o컇 }(TQkHSж-  PRogɤkippTnLkIYMPmi* A T- t*YAs(E H}Pj`JEL!神3;g&6842b!ǰ9]9Cli`+hmNj{Bɉ6 @*v*|߽!Bp3)kY&FI[6ܼ)^6ɯzy&V@3)E XvUj\^o @!t?H9͉T[sXO2wQ*"M=SIҎ\ Skk^Oݤpw4 :erƍ d rAKLIWi a4H|?jq*TYZ<%۰ͬOIufRrY#X~t/5*D9ƣ$+ُwD9i'wKDCWȱv(A_Jj-{LmwROp7")JpDjNxZ@! PF ͉#Ǝ]6TË) !@r\zUYBݎ>lW]QSSzw᪦HH9Mp --42-O=я.RTǓlg%ٝO G-i~KY3hnd3#Uisg#9ǧo0%$Nl<9jgPO<%?%Iʇ8 í L1*5n%J:{s]@ʺҲ>4v hpD∠EeaTek^Pg>΃KL:K`U96Qoij!B>ɢ3LPhڭ ꒹r./+& UrN8u r]o.y>9Ucl+5gٯ=l͍_0jh/U;)G^**Z JfɣgVqזm<$E[ic+Պ5yᏰuBWqmXZnA L}hX.ΤLoNIjgk~5C^8'{ohHC]54%(, _ k5vG:}R\zmw~ )5e $5H ƟW!1wB~2wD3RO^Req}r  bȎ/zN۞,VQZTfsb>T/s)]RҎx瑱3`\Fgu[Ȃ S}=4Qд~{IVIyHЫ)}O=g1b{:tY7P u))Og`g%VV5?9; &%?+- _>E!#Zx#862zpk_E"۪7ùWccpz g:>FvߎN̘Btʶh}g6xŸD\<7,K.~ pH PhhO?,M{12XgכPD W >ɑ|j}BE8NqkiX eB5K*H&W~]UXem8LL'#\6jL9R]\vZTb, ~R[9 &JIɬ;Y:l) @h0euAuQoiPjn~LSLm]ȵ1aВ}^IQ/z"}T}[-&-+ͳ#]( w% I fԨ+LX9O&gTYT^*CynA)"~=(5oL5TCuN}J=+:9w!ٷB2ϴ91ڨX:wte7w76恚N: c"us(ܶu8jNphupyVWpth D2 (M`Lwߓ'g2r)mr}ӜPG;[xD}1a7 AC i)@ŎQح$G*$#F-ͩ3!4.ÛW a=43}l͓ZBI#?b9^pO;@2%)ȎT̼䧋",$$yLY}mr_伴^Z SWƅ÷3mtN}}^suqbҺ;v_N-dwH5pwL۝#Ë0<D[ފ2JoPb888WV#u~^ƛjw:- >#N$9x@'+kmNZ`$Ł$fob~óЬxhQii$>[@W4j^/"mqBo 5|p_ͮoof0aa=Jj'UzC9' Ai;4 mN4`jx'q)~|` J8zί͟!}!Gg%+@x;06n X9̻^#f6r~]n.9I G>oE@EW&P;#cx}\LA~|Ϣ<>yU,$L_2c1MV?}w)g"7c6slNͤb8P o#o!=X5W-$p$7s&/bQy+y6N{BP[KM"I5}o_gL3}z@wwxV. XRgD>g(Hϣp|p LO"!Σh"cx;~sLÇֽ!P!k):‡Q\BD*Q;pzV NjL qF>Ϳ:d:)( ewy $J3FO3Ã"|? A0wg]vτպ5p5g`%*A񽠀^XDgq|!f0M7!NT|R\[1uhU 5_6#/r.̜Y7h e>yd?"q?Q柷3|jWBO8?zX6p7Ye>Q4}Iɾ6V5o2~f6 X3gV }Ah;"Ҩ/(THS$$=Q7bRayG0JC0Yo X:,k&{1>zvS᝵;!LŢ.@|)riSZ/Px80ufs 6L ;*DN`bV Sg)={F0S("Q"O2޿yE<|IT~"=T*"}:O$=9;?s棘k ^m'yxѥ nO!1O!1? ZdΉ&Bxg`^ Pĉ&w p.#ah9/M3y|`}=/q9DZ0uc3LYGkqH)n>Ot wnl;kXyDF$MB8?KxR'uş@W>{`ܟ[w p ~NS2ulW$K`/R`JCLۃc92!̀d-k۽\:ogW10QNKF5V%TJᜧyF<< 3g6;f 掬 S761vh ܟ_|e˷׶[,lLkl6yΦda̋1nXǻb/tL#V3}nf_"%G_xwe6,v(6vwf6scsbscȺW9`٧Ƒ:A}ާQۄB_ڽ\:&l#]f02Ze泘w 0]xwIy=F5o5Nlk$#ߍuc39.seA7[f~ܡus j"KA:s>&+INH~ [elp`#hW{s6`jhBǿI] A>'L߀nF=Od)&~Oͮt6oy>7G',7?D#m?|&P wx)0o%Q0$or^xDIE$절X׶vsz7_S؇,}1}?x!3j˽n~{ne:Gr!Wfus"PAky~m >;h4&񽀅@4@{~'1AJ z>:k:s?~|{Ůno]puQ>yY >}#ր0ܝ#8bۧ1H`/3|nZHwP%Q#}$8w6p& :v6g^%5owL,6@{@m`cXsu^lw8Hbi]t}naD?'LSjd݀e}y 8y;2zfo&Md4JS)^/o۽[0ǸC򎀄V"&u>dWʁY7ik}hS-_w >C)7xTNݯ{6~qsrޞEQ);v~q+_}~QG{E<;.;iB<Q(}F=Shw(=z>GLsv6"N=Tx욯cX~$NT >V1*k$%Q1s ;4VTE4JDaEwEA I[ ;jB7QH8 DY z#)ƛF )?{F>gq$y9Sh$=(!J:σx.L1nt)0}E;Jxe`>\Hzĉ(s0]v@|(/qt7NXÅ(ѥ n9(P%~9#i Q(78r˟H#|&O̕/i9SGf߈M_lU E}̼468hxt:}wVQ(A{4q9&lk{l"KA_ q 뚷ͮ|g7_v|7چ׶k4~j^ahA7Of[͝c$L!A~qc >Е8bۧ1$@8oήseNݽsxw!`"~ i5nv jwv&=z )m5w_l :s =Å 8:/×VۄO!8:{!B|hr$yqGF {3wo[-V_lGօK_ʺ2yh6[aZ޽3ZO~i nvf[9d;ۨSĭ3zkEGfyiQ8_LwqC?36xζquuN!$*;~ ;s$N}?)@{8s/4JR@3}ɴ?O4;"z%O1CXqr?l{F<)=yh2q L B/7q>ͦ{y:o#qs^ǰ::\Nԭ8`O$CD sy\I7ăcb˕4 *x2 UAzFϭk}p F|9f0#ezN?^2 eh[ȗ7q]ɇ߈>X}]ù/txi9bs1ㆽs͠m}4j&ek-vu[#^(A7e.i^esA`]W3h6Yږsj۽s͕Kk9oץ+m_u- wvoB0z9Vo; z 4vCw1pj?}q}>J2EJ`\S y9f0 ]S&_c}\Sfȭ.aƸ6?} B6u0 +vb9C{ G9/Wfm;t Rw&xg_pn~u]߼7oup~gֵ9lm jpb ^ue2~gtLY%slwXj{up9îprsapa\ʶa.jb`rIkyU]nm 8`SXodŋ2ߍ_ʶ/r x$~#cWqM?s0Q=v9NX@6OT?~LY҆K<E|13zob.d]UmCk4:O[eh;iwW4,cvy5;K0of.^Fsej:#HW$y<|.!ye< ɇE~?e ·& ݯs6q:: 4Sxċ"8ME~ԙ8b8h_Lbѥ Γ2xw0kg 9yr.ӧFbd^^/wyw&pb}AC0s1hXט>8 wƽ}n֡m9umq ]X]ʖlCàm]v[ZryaCaBa1zh[@ZwfPq]غ9t sL˥l׵:Vq;ÆW7h`dL.0aLY*&*6*)UŔKI1Q1Q1}/BY^څ69l*Q69vixwe[^WZmMNVT֕6 , M&gbaaQUMeYl JWueeҚVUaiYSZ+**lvJJKj *UEfaeaUU]i]UUaUWXZWUqک*,- ͲºҚ^iMUiU*+++ՔW6ʲSZieYelVK5+²jY,l6[^MUieWXTUUXZ**l6faY*+-VKjUUY K*k*jZTJKu%Uª^e]eZlKjꊪJ jeSX+,*,UU Jk*Kº^iYUQ]IUQUieXZVK+k*KŚjZ픕JEebMYiUZY+)*JUfY*Ք6f,ZilVNil6ZaeY,U6KfVZ,,l*ղfTWRZYY-6+;feYXSYV6be*,VVfYl^eWSZU*š^eXSVY+)j*R]IeSZ+6jeZZZYZWVVk KKҪjZYSX,VkJ*KK Kujl*K+kJ+ ªNeeai,l6 ^]eaiYZUVv*+keZUYʲʲfeyQ]IeaUUMUa]QeeiXY,-V K՚ºZNeaQ]IUeQeiX),,,,*--/),V EEҺ^UiUeUYaiQMQQa]iVY-VJ՚RaYX[+k+[5ZeX*,+++-lv bY*, KuZa^ZJj Kͪ^eYMUaUX*,ՕUKfiTX+) V) {U5jVZ*fZf]aaZZS+,TV e5ueuujUYYYWTWRYW,+)+lUVʢbZXUWSXU;EbWTY,U* 5eueueuEuUu5EueEE5EEuR]eQiZ+*핕v*<8- wi3rgMCL <^Lޠ졃rPMUKR:<L214 I° :@aP4H ]yKN/?u^CpBp 7_B2jЍhtQH]/u! iA,XaCܔ}PHH9!1ٷa!6 {Ccfa>J7 uz*Jv~&,R1E+Wf9M 7$-8H1mlb.⠼$8r Crr't,N!KGR,^A%8 َs#\.бe؁P.p-sl)˔qq?԰PI K@%.$Zwh~bW^@l[nlHȒ}Gv<׺%oȏK1`_/6,ZrFإ)id`/,;BXuHQ)ce8"PھRk[piа$Zؕ UU6Za9QjI؃TQ|l=6 y@)rWyYP)t TE&xOF$I477q>TQE2Ȗ1%U)!ؚO>)G02LAE-G3 YPQLW s#搿J"`PS%vJ!H> $wxI8J>o'bK"&))BǤVt20^xIo ̅D (p fWYyK˗pY8e+^c)AE xꔷ)WO bU_@`>~_K.HPvhje )kEk[Yr9B#>a+̉P 0]ƸbY"ʚ*VKֳ9 ~ qI]19-w8),eXxH~ / \oI< ۪[b:[Тڭ[WRC ٸ2;&93*'r,**r eV0x5b )HQH~HlH)!ٷd~GvX 1I.Y/S5T4 JYT&xIC!ZBI$K gƟ(+8m9 ,E(xB+Zj G(Sn&Sc\O 8aSVq.az3),*@&-l-?F1MR%tbVAV?~L 2G5Ȑ>#?&#ro dGHXVyL"xXpr `)ik''n.qxҶXC ]kk5ۘ41eTAaL 0,*`2:u!`V#?oHCE0]eBzĮ8)[K bL@5w^99y,?j+w +fI04^.h\ x9YrЈ%j&$JĖ<=AR#h:1 t @ۤ Ӑi l?Ͱ*UVӢƜ1IrH\SēT Y䙢L0>J/?L\jDhYd[.MTI$e2cЇcCM'pKqJdɈ<}'w2=5cJR|%f2^C+H2I[i<O0iH9;Co Wc]dѶ (nQ|- JgҐ CJ##e6EBƶehRᮁRaYwe9K#yOp;`.Fd+[HZ0Um.<$ 'o2UB.s&B3 4h3qIg(n;%iBjNfVˊ%3(-~_ɌcFqqOXeDJASR"OKDW/Ȍ15%|+ l|\ hDT:&g+ lo]!c *jDlDU ?en\ROd'.#`F5LaA_l@bkeds뀊Xwxݏy[ٔ8Ȍ<x ߐ?I Ȅфo¦׬nMZx@p-SB:WX eƭMGŇ'H %wlbQlE5V,xxZhLR4Pr:1$e2g U NƢF{Q^J(x[pG`g etg7d)_MP[GpFb?ls0`ۑFD$ջaKw'7fwKZF`F# 2`ic8ҙh$GKI- V6w:5XMz:ү&T0& xӮ԰$PBlLJ>NyMh7jba1w4.mSQaZbvRxQsT|wRtUxiQ{Ƞi*,f.DsBeO`%( AʇZʈnB¿y!lM /ToA }+9qq(+Z ~pIajݵR.RdW%Od}Qb )PN# 6v]Zu6l'73( {Fņ{{ PDFvD'kMo3H1\PHk RNkociCFuzZUWTU􆰤٤+"n6#*)']Rgox]e}6fQQFf7oR)KwbÛF.$2$RQG$ZpO,OXA%+K =9qJx$ g+ݷrv.PmJ,ݭT.(䝠⯚lPxY=,k&o!?{AŻ U%w1!u՜v("p~Oo_c\vuf38'7Yfty>7@M"gٸdLX7QzTU;D.dCX<' L5l5./v ,")&jɊی ܛD}lfƝͮYZ`; Bb@%Dj}an+(H<PP:Jc0bj{!!UTEojw91Y>0kv$3*.(6ِ 24#*rɉTL5!/)(l{ MaiAOT=אfP`ܾI~¾Ĵv`Ьd\Y5(xR07IHf^0~ O@?L)N E$d@}Uր4"ҙozϊ; $gQ-.j+1!8!k] Ίz&6b*>0'Y9>j ܨcAw U>V3̀yiNJ D}6ԉCGe CBЬ\(L?kT:ek5xS,*GduZchCNM`ZH9u4n&6T!>$;iwO\^G$ j=KF"*7#0{z͙۬$]Cɖo%$1#%rx] '] WHsWoZ<6BOABP:BйK kUZQ t(s̺ ᝠDzP Bि FFo L@ UTk.(VҹA [b՜%vBYG0JG0JWxjsa!9XQ\SHpTU /H ']CAG?a߲AH~2ɪћ ƣG@# X+d!7q'ñ<4 ULm\ [nI  |Υ2҂J_8 upj}gQQ`SU].|~a Qk8|j9/yDtd,wsN6b|3 xP X8z>J36c63A}^,= Iwh5>D6%_}cҎ2h7'ǤM(]a )m0F΍#qrAO:Lɇۂ+&'qgJ /XB$Y<4A>, HJ>i UP&^Ks1IWሬvlv L8M%] RPXO?sJMऩTPT)*m-uMjfc1iCi70:^!ɾ\p+f-(h`DbOIEe'uDֺi38F O 96+!^%G(}xQfᨼT,QD@EHBcGUB8i ԋƢLA9.dD T#q?܇}(Ϳy3k|v7TQ8.$>{ 3X8G<rZykyxW10U/"}śɔ\5kx ԊFT:NɶBB:Ff4JN}77LyG`4!%4LjwB=ą*"@ I?oXb#}9QC8']QT FH%N) ǺLFɹA*"N"+(swi_A23:qÏ8qq>~`z(T _Wa' >zN7*W&S_DƼ)3AA~ \C8D*X= =Sj׵틽ȑmsrxtL]w揍F ძopo͎Cu#ϯ_jP&= h Y<]ͨnTZ8,2*o&_b~REGw/T)F A}?I3a*?i3@ | \8w;Vfh J,z!}ŦRB|HR+uq;C~ qxU9stХ_"9K B:OJţ_JE=B^P#48e ^F(8aR3ہ1 y(]zES¸7.o$Kqȗ MGEU,$sGP D(a28hg A{O͎3op20[*EwB=z_!DID)&;w3xoLCwVȕ0qe7\YMvu.e]1Ò/ad_Ⱥ1yiܠ˿*Fe-Q @}tR6l#}~=w&vևføqr(TOp  GdStIb IݔZhXLbAi-S":[ݠR Џa'> )OyE#Z@)^)'yo$o&Bl68iU~pv88)'K$ʱIG`!A}~8F '(nT\EJm' Z G)7ݽ!m61aAcBkd>$~*7jbwudC|cMboe`΃Xy!$ެ݃8AƢKA0Q PEw@UA6_#wu#OaB=E;w~0*&p@_#Hh7[yopo{^:XW߷d>{b>(B*1G68 Zz(@|5TDJ@@~vδ8&7͹ ZHn:oorwe Q&_2uVTQ:kt*r꿌~P Ƿa+(7V^4"1|#C;/aܻ A{ާR\ D/!Ep[B8?vƤA)=yKbSQI%F@h̟H&MWIW{3(xUJlҎxu} ]و|~B<#߳xtZBu6߷Bfu ;w7EU6H1yj s!`θ9v#Wy ^8֞IU[П~ L[ÇjPxX5o'qøˡ>,U1$'xRT8&I{"@ G0u*?`ݬ{n{a܌t:xc /m7wr>5pFT*&oO3}nXB}8.s5~nFVy5{gL7΋]IP*iw*5ę:DV-0k~)4ox LM4ShjAFy\^i:ŭCiؼIÅ&4lw9xˋFPC}8JFwuk1`^p&3IۀK GZEH;"W6ϭt 𗷱̕?|7S}ûkPspp4\۾؍ai:oFm!t]ln`a`W6z;7a8)!bSg0uf4ޛMy !JO9{A Ga)cwUU4&-)5*sՋHӘuB6ހCR5̗B2vi6$0rd438GAN1$RwyQe`w^]57!E8N!qt C64V+fICy0>y7cx*u r8>|ɓ+(Hɇ;Pyr×:8{P:B+)W sbb:?Y+~E A"#u~@ =.wV܍9oT)e$)E`'&i"iD $Wm SP\v<,)vt&<=t%*Ai$y;4zfF-=z应ێs.eb_+_[[?|](E_b㒞+!S7Pqdn PWia3Q%>hSPfk5tBlr$jk )MbҎx_hbZw5Q)%(! *Ct)7`AY?` 5}k5~kc4}kA{AF TG]߼[ #cҺ3iWzm\Z:`ČH ]11vk]'vTvzXv+dYRP TJra{F]B`d3l_Y)A!JRx!*1"#`PTGhWVgShG68ZGiJ(֔n֑9*}:hӈh48q41|k.v6ml5؋w8G08[HtRRb 4Bn`XOb\ R0V娜x6}l=WqR0$i x9l A?h&}!ZIt|CP 5Gh(6>U.q @4?yD܇yС{M+`=PJ VV&mI7𙅹uu>,F-l !Q1+a XQ3 28e$"-Is%:gH@u2 u"ƛ9{@$t6R0% ~T0T:[LH wVk$n ZApf(]oX002Hܞ+a{Ĩ|nLóNa^׭.Ɏ*w\ |nX1ɤܘ-d=q^*E:kHt:õ y՚D97P@7A9GP?{ imtϹ^=*Bz, .i>Gr r)FgQAk0 = /;+PNWS0*VN f%°HDP"i#O9TlyWJxLl pidU=, (*iVxmH2'wG$̟@˜F t^μ`ŵWտ2`~!Be@o8t Zs}˿zMCxUyĆ$]ee)_fe(_syH8ļH]E Fl:qm!!@FxYweA͐%*9#ѼBYAoIģ>4 ])"mDt{ۀԞJs^ofRh-Ôx_O>5t@[OZ\Zz \}Yyȶщlb*{lTz\bP~!Z leaQ4v}(j k-Q.7 /##eAhM\N( iH@]|J 03 2r"p] N MBGDA}6=8C*<$&SEHp>wh<ޅgh-F/U/) ^^ξds^@zq(=8g|q(*'Pޒ/뫜% ,ׅ0 eaYUHԘb ba~dT XCṱ@Zpvhn1([?!x`Hn3 aBQ1%iZSrD^pO竇r _J)snW 9 b/ۆ .duo\{R*KlJ:;|沔 r ]TmC b1{ˋ{$JB@1F "V܂ݩ4#u)4ud f9Գ.& DA:Jź9#uZX:QP%|}յ0t. ^r.N\ c7M% ^Lvs 1zlb@߰21\c @ЀhlBxQEDәrᙄ"R]7z`@SĚkn^9ǏABX܊3hYBvSgR}?Aa[:c^Ф7 D~M4 8'u`e {iDeXhXjЩ/Ï1(+\We$mdPA\Ð-I3l/2',04{)P]x!.*,_Y[HZ4n|`nm-1=$6ꂸ c֍; uJh\.;f3  *90 vP4vmXp۞訹0!qi  ə Ȫ15 @PU12 laTT4&QtsȘP˖Z ~\vVaF-R8a,Zy ޕ$utLphL:۵yTwkJƤJ zf F! aM܅K*0t `2yw`0ls`C``e_WtKؐ7x;ADy jc(B0PC `e$]C,(!@ Z0"ഺ_%dԛt|z OGz>݆~PeYTg٭*9m-!-wF@r/fD%sfCrɽ5 Jc2P 2m_n :B*`I%aRO,4c|e `p)be-ݳre^s\>-+V>af\*Øt =S;f`QP* Iĸ΀@*=E@ ÑL ʚ9C[;ں}Q(%xtTeV0 e 30r)̄3"aKB!` ,+ Ё\ dA^^+\/2!ʢt,fchiumΕZCJ gH+/l:*Ƥfa_$.$!eēp)Xy8,k_he ܮ2/G 5qn8j +S@rVW^^ö@k5֯wH-msLA{im'Z=.UzYGG7uo5@P BȑB>#Nn>}ZԍP 3e¸&.<Ӣ[V?ʬeP b T w  S@!d(jW:tY(`lvigZe]t QQNcS=O^~:ցNoPoF/@/8vMnmEZx+)|/V|xP2M>^>&aQ^I?5r z[ %+r.r Hi(A5!Cҋ팿Kj[V#$hC*`lEeLH*B1Kpf ^WA\KBP7C=?h@ ڷȉ*waC4'/:T-:Z`,WV_~-'ᐡ|$ BL)(|TE!d36hx b$Qaˆe+F`| htj|,ZPQ!ȠO!~tT 9,0ЧzP?05D0??>[9>hJrGa-,:ZT~s` iٹ2 avĶG.$( HXC%2sAB! (Р|c+ cRC ,`b] :.ԙb|POH0A=)ȧ u:*E%G,@V _R*Xt꒸JS2SM`VVVU„r-2,IlؾYOuǯn?US4u0M=kԯ~Q9S>I=’Mϴz]_@: vKO7y1r~v RP!,E l2 r`f r1 kQZh# HJ+2A VQH!([8a"YEꪂANu1WTo2kIR0%uEH=gu-%No`j[JL=QtgLM6Xq"E< h B\42sL448Z`dܡD * ٲ* PT^u$ěUwK,u2io .TJ=R_NFSQ4`g+]:P7]kߗҫRO"@Bdsݰ)BI!_VI~C]NQ9E2Bs//]ftүbXr.;J=?| Ѳ h$-Q,w$AÓFq$~\2t BwhbYZWr#_=s@Q `KD}"괁75z.%=E=ʦ] M^yhW7YY//9.9\a  K:@-=41 utZqL̑yx#17FAY&$n#@:Ps*E=@Rz::VcԍQ73{:O M3f >07?`~yqab3#& A68lmG!A"hXnwM25|BU^p' La42h %ޑIfOBF.G%,b'yIZ_JWC7M[ռ~s:N;ݦtn#s/.z}!*JY8Xr)>)7g$ a6%-=_zLヲts%YS j[@# 40$VYCX1q1SRSRRGL927ddypIhfƋLr 3mM5tӯݥ^c:L0=ty:_KhpõGI1LPU.~xd Xx%~ORvc„P%&!QA8(8jXs`㒰)-0 q^wZs|$ 4HPSDF(Up*egrG2WV@Xr+-[8Qx$ŴtԁP;ozN!NBLހɩm)@!rP^хÑoS.AXYI!̝%{MDќ\( LDLbM.KKKbºfIea]iʬYWRUihfRjfUX3ԕUV,KFu*BJ;C23BJiXRYeTTYUWWWZZTWSVUu69i[LTڬ)lV͚ʺ²2iia1i]aˮV3,*),2**M*-KͬJW.m60a6l͝ hhiWY,Z6+eJUeIiQYUUE]0mQUm؊bz`,kj,Ӆ W& S  ǒqi?8Cej!iMRQV('n5"@ CHk$0P%`E#$}U@hM49zJ\?*q~ Ⱦ8wH= Q&8DGh0%CyHNߑ~pD鿊Y'{4 q Ǿٷ ģg`gYgklT-**5[%y (]XHV??WQ볘ing e} >HAODE_0 9t(wJ=CkP#ߏ @xxô}-:S_ H3߈<'/ƽsxg}%g <ʴ^ #y!>FmݼU1VFdkjlk#s±9;:GJ ?DT-*k-3).n7YsxwkѰ%TXY'UśII!? h7Kdxe旻w6;zuVLV]~8qRg'j%ٍ@d27B D }:6/$KDZ2JKM%? "VP彀I}p[GDk=>(hqi0-ā>s) #AG(YUR(=V uQAă6qDw'0q' V Tğ#X5:u:vF1G??> ]`jʻQyam1wd6x!?zv\Z-$ ~VP7aHA]Uīvd Ƥ]' wJQaiCY_Ȥ6"~# ?Ѧߏ+af&F`݀?p:[cꚿ8pLrh(5"* ];f/~5ʛitgr;뫏u6{yyB~M@Eaikw GoO>$o^o% +)ńzt8sBmNJ2n~7ळjdXAgQD4Q & ;w H,zCJw2ER#" D(joxZ36m)7 H>H+814mwSo%K9g/'-֕S*o-. t I)Qi4(3yjmFn0d )IJ L@9JOT'f-DF_ƏEckȼF :(8IU0%đ2 ˻ŤT0i:y;i#(a">M`CsrH *0G\qufpY;JcuLV2&mhsN'CGmQ7Ȓ~#vWab״O峐_X1 ?BoŽܦ 9uuP$ .Wvy^"y;% P,ބntZKFN?7].5_ĩE~ԊEiTm:%{ ) O:(5DoQJ_o!Gq:MY7J)_M*ǾȬ96 L;ŤoCHCشX\P^sH'QFF1jFi{P-H ! m?Dvk%r LFtU$k1sxBW5`l Z)AI魁 'MH J+&mhr|$KxZ*Sel<'<$rwBEP> ĢV-&=QI3@|8 P4✆cJy7aD~yDEe9kȬZDRoʽQ o&B2`#ilvq^93~gF& p_@. ̡V=Q$`Sf2gV89m{M5o6Ө-<'/8!\LF=ӧ+É,"ɰRUЬ)^lH Z fV}9$shO{$?S!6h8taj63H$?9ʿD)I Gb\J#$)CPI1|kۙA5'Lw;z JN ZVi1&S/HdCFya½1,)~ŢfBDt~㓶Ay,yqՋI ED,5P!<P'KMd˯DxcN1gl1e޺uOF ~'hrR]ċ&6ۥ z?#q~'LAc6_pL\y;4:lg9簯8<#V7x4yq4lKy5y'?X@Ń}u. 4}nL^ 8 2pAF Ec}e]4z~q#gּL&?{}r"HA)XI`%l6VC&i0? Z7uQTHI:uA?63ԇq5%Ŕkn<*u? +#H 6W 5,H*ġO Bc>n`X/0 @J0"8Q_3B|)0У F!qzN>GA3fС3n#!>5 ^'isue̮c;t}u 4\j ytz1y/@x?8(jOb_ּu~yګC/ 9Xv I(3+;Cۺas;\ې Ih"_D<˺XAǠJC>, ;iT0H<zdHKfs n$\'YsE=h 6b7E:C+ ziӈlp@+g쎃uޭjDy8ı87__]dV4G_ (d@g!Du&\+[.Y>j57 !ۂQ"dii ]/sd {F5-P߉4):_9F1'qN4iHPZmǥ@TTwևf])Qy/o Ǿ:^HDsV<QO=U6B6V:CTBP-ӧ6_r.Pޭ:!(餥\8A|wVpm6m[j-pej?8] x?}άsxWgJoZ4A \JƏ&97tȴ(<,w7`+!P5n>&l᣻q<Fyng6|-vanp7ގa`4n 5_9Ԍʮ&fXJsujТ.JPx@@8vT*&o DD FF(7Qf-O0vdt_`1ths#'}5w; y=zSgr~+ s1u-%XPZփ~mC2}jUՄ·a_Å HVi-P-5TgϮ7e7',!~(!vֆf1iKR' o6"?*AofG8ē@HP;w5-yܳ ~MhGD9x#mo?J+~=Hǐ!t_ȶ:w5(Ymv1`̸:uvQԻc r9É(q[FD!6~A!ܻ&?g8C9[:ktP e~wzC.ѦG>{ a}C8G|AKB3pr9 7)񽮀>4DTE+QqɰQ6G1au2h,BďAqqMAkZs 13)GV"'iWBXBʽ]ʃ_" } q+/;9;IM/rg[1. H仅0 JoE{^3tьt']ܟȿ%l{eù/q83H "3, vh0?{sl㲻s,$]$p̈́Jr&,I ۤIAI' jPX5S* 5D˥%uPuΐEׂR0$#}C(7y}6)>F gy2l\$@W3ksO^ś]8GUi~qWϩI ed0tR[ C` 8 Ģp p"NgI텠' F03` feM9[Yb?;S3^W kaH)ܺu|̢W)#V˨@Rp~!q_H|Z|6-^E@a]?NVrq\^%UH`JOx/CKUa(bاX:36\zneݻBÈA_ |#ݭtͣOX\iK'$0wiDQ^g'qp+i@ڢno??O8[u)X[$/u˻;Gl{E_s`&Cur'%XS*v \L{3OYK Vt=Otx>%EU ,nڽ⟌ n>78!}+s}rcGGݏϊ#G=0>@`͂';1yB%gYsg:-mV6ws^բ>-gOH?p_ʏ۾`Mw)uz%}0Q@^=\j51 Y2_BmQyFMt'l bpoXܤ=w x ?DX 1e?k| 6~]#Y1pMqsUt89uTiV٧?ƒI/st Ogyߒ-͖SOv>Gۯ6綟P ~aV3bp;|Xzo!ҏxb[ȑ7JdͧkI%4֝3"|)6OGQ?BJ{~M'ci+&>ք=:wxBWyІT- ]-0xr[P*yNtCnjO<}zuFy.At܉i7!1KQ6$9TY{3b<&>{Qʺ~ Xy0FͯQr  t1>[-L#w-2Qioռ+{ߩ/ā%NægZ$==~ {|hR/1]>#)6 Oڨ 1N @4xJ .XW&6lp8)=y#"5WlA 1,D3L(;Ża` ~^N18ք܊c"m_l`Ӛ`~E @A.yY} Hʷ<ޑ*l*u}OYm*jKן)EmWha}cYmt#z>gKӠy;6dmr,-iCjA F?*.YAfQ{Q{6^My49:q)I/Mƾӆg;'aAo@~zh'("kDWy] u/*Uהm `K 2^s?h#}s5-j,&AЏ RTMTeS(X 4f5 3\-e FXĵm!P?Tb.y8T[6Ae⺾> 85!E?Zm.ڷݤmo] 4vǛ<\}30N" JCkT&8($'>oHh40AY@nfR] gEh}GBVG۴ϹnlJ".[1Z=&p(}͹ !dd38z:Io6[MG'WwAȰ/qNmˏL6BY&YTxT9` XFaf"K!F,maĂ.3F|]aehb&)fO>Ϧ8*X}@{Η5UW՘Yw^KI$<޹}L_-!P!FW|YΤNZ=V =#]! >},2C1 LnLh˥!%7 ʴ i*Ha44Pf5/2`%"Ƃ'IePpMtSuR-ѧT'=Fi_4#8~"I9 s,75ǟxԄFW5 O փI|+AÞ i?TپF 79x%%J} ]ҾuA3#EГ&F_@dl9aɱdՌ )`:"{h$RJO|x}XHDjS5u0צ^ R;ya{)#֐y9Mx焗b49)DKHQG $1Y+ ˝[vxjvQFԚaFb"~=m^WwVDK滪7!t18Jo;#_@DqɒV,CU!^m [Z3-,$)zl%MTb@mPpJj'،HH3~gۿz\| >w%"% X(+sɜkXOOvp2ĥ[i_rmU2R%ߦE-q]8tH6XܦDA҆r1kJ[=ۘ1qTLR_~-&<^4@3ֵcUO2/Bqqg=}t2Cy4m5՘Xv!<^GBÅ 4"cuX~ \՝߭Ќrx h늙mj? ,# c\t&H+lM>9jդg[cpklgO*E5vs1Mx|?/"!doe FFs4=?x];[:wEP&{+$4ꍆw5Fx?/? *xK\*>ue/5`Kf$Φ`j }Etl"QBdV|eO E^0!-O.[MH,i`0f>jj"\Y&kmrǓ"$(#ѽ'(pT"1Fq 3V :B*0-}/C.}oc xaҚR P$=!A֜7[/cš&kGpH.Sm^Qߠ9.6Yw 2fλ3OB_ hҴ}4UofD;H },َMc09NrJV}5˜-^T?7 {莧/I.ih4̂,m&:J9w}ؠc08r6샫LegqIo@.rT $ 202230bf0-acd5-40be-833f9364b67152585ca4-61e5-4651-867a-fe8ec8c67fc18024 667afb942da-0174-456b-bac4-46354d7e476401efca59-9f29-471e-8eb4-1267ea49.4612.252125237732 D!; 0 hMX͹mkh26& q'$x5fC^^/wx=8L<@8wPc:M ~@ X"`|y8{%18Mi%99P.'y661938b9eb2-a314-483e-ab45-1d2e895fe054e101ed4-fa04-4ca1-8a4b-ded04d9cb43837.ml10SVGFilter / : /XMLNodexmlnode-nodevalu1 /Inttyp/(AI__2;attribute/Arrachildre(id)nam; ,0%yyxx10wwhof,1(turb2resulturbulencenoStitchsTiles2numOctav0.05baseFrequencfeTin(SourceGraphicinoperatorComposit/Def ;4fractalNois4BevelShadow-2xx144blurstdDeviGaussianBddoffsetyyeOlighting-color:whstyl(specOu(specular10Exponesurface-2000zz-1yy5xxPointLSingkk411kk2litPai33arithmetinMergeNodCoolBreez-xx11aid1.radiudilaorphologb(-db25bddsRxChannelSelecAyyDisplacementMap1matriColorM50sbegfromffilltotoalwayrestar5dNlineacalcMreadditnonaccumuanim-8ccccdd(cccc1cccccD_66Eroder66__7PixelPlay(50 5R1 1;20 15;200 200; 15 20;1 1 2remo1spliindefinrepeatDddckk32;20 20;diffuyellow;green;blue;indigo;violet;red;oran518azimu6elevDistaD15rel10334401(redyy2-15xx3688tac0.0.xx08544Woodgra-xx155floodblack; opacity:FloosC1303 !IdH $. U<Ba(  CA PȀ$Y['rނ=%Y{[duPof_]!&qF"[pExMm(I\?2=h"Wއ4SPdf=Q?L>HG(N˜eG8{?9_/&^o`4X˫;NI7. T^dZzro7Uxb,!YL-ڃ͍Y-S/TQըȐ ]Qd'p_ nXb3W繟^|Yl~ē* k;)b `ߘ,}`CVA3Ogsbq@B}E|B:MRpBDEuRxl/VV O4'=T\Dž?TfX+_.*Ib&dD$Σ}ñ !}A]]("ѭ&6 %bk?pgEqXÒ!'%M(ɿZV,,]tI%D nr\ ` i6}RY=Au)2*yE|Kmb4y|yuEcRޜ7 Eʓ`[KF,[};jl@'F$<+2e J[*@y{e{`s\ߴpLZ2r5 8WUWI7!/ɭ8PQ,,'S]6/&!0*S/r)VgV/4S!Nj>]/N!ӨmcB*~[yDkQ =ܶmb :Bi22e@)*[9AȖMJL^  y1 -%tY( #D%s,R*L屸) Y,)5q+֨<^oU$Yezp6v-2bdw橊cVKw;4ۑiy6okmEG̜Hm'Z4`Z8 ɪ;cJS% ξ$ iڃB>@%B#J~ p$M6b 6A#N})oG5@k( aA]mjF(clףʣ ͱ `)@7ۆ0iI3Å] ?@%Zw#2ԌV 'UaF$nNjTƫ{EFfog\e<۾YAP[G?3WuPR <39"9CM*g0Q{EyoP+'xt~7M \Ȳ'WYqEy8Ъ#CFa_2b 7Ѩ@.$DϡD90s5ϰ@2C4Csv3nՌ>Ka>h`7_&=s#43' ?f@ɴaB3zVҶ|tك%",>4 !)=A oSBWa2 zxvuЫ>R)9%3@D[OE6^etkM-(szR4RHQ۳A;ls+ɸAt`F#%Fh:vh8u1c&6O[W JͺLi}4``7Aٰ2: ĂZr[C`Gt!  .ZDKvmژ0P E|*VRdJjdB 1ew9tvy^˜s Imh4Ǭok,suՍucb4 :55 k┽՜C[ lu5K4w߽nurK\< P H@8UhP8#8"5#8(ō XH@YG/" 8:z̶MuZm[\[z?Mv G$8$ P@ H`S#.1DqD0@8D8 _d[۝y?oi3mpm?dm ݭ?{녞ޘ{ϛٺt[S]o:-Vv}ٟιoO];{^}[Y]lx1ǜE,cAMѰh}ڟ켝k~k,ݮk5i:u.i q2bF袚ȋh,`PiPM ,`θ{1t|!L5ĩ[ש- k k1M_Ŀ[1_f a\&ʁYׅMnMKekAf2wsl܌;wys7r[ȚٙUӥ3/tfUm 5Ӆ c[ln{_ ¶͖ܽЗ% ua{/ _Зw,{_:U֟ϳ;㖮6%zrT]C׬4F.۵V-w]\ftka] @(PM 4SLǦ/̝`S!ڙK|x`2{:Cn.TF⁊h4SZ P巆р`&7nX겇˸s˭.f*!XLĩQE/`4-kow?(DGʈAEM X#"õ:cFX[Ϲ2^FÁZ~c}(MR*!EGqDGqDGqDGqDGqDa8"g4(01!fh4ًMswe4 MR^[&{m1][3]͘=1ie΍)1yodF~a{L߻~2{{-to{77kZh@Y[^NkhLR43t9ֻ z,u1n^}=uz6¦,u12~{M}F#T5Y8H&iD")p4\,A%=C!M̃+q* z e*=. LpP y RAgKvΩ9R'[o 0utC=5A> 0EEUH`TRQԠA 0Ei 2gFX"=у@z,<t ۾3m DQ=DPA4A*ʑX@Fc,P }"LT510#ĩ&" X@e4 (ANd᱀Ia|,N `"r$L KFƧQ`4(&&.,$&.<.*0 V+0aWNЉhD*'zxbO@"#'0D4 cDf SHh$``<H=4OB fPJMGF4 HbTN􀬺`*0 ,PYuAC P8+0@(2ӇNh4a4]Dzs7k{LP Մc]o y?]mYh4̷vƻĩ<$~mme4,dݙ.E5S K]Ѱ0ٰF{ }69oƄ[=滫)SmZ[|oYFy=]2}L9Seα)ה%7[M|J9*&z_{̾5[] u]^c4 씹Tlndc/T^VWYClr'k{Lw[~RFgn]ذeL}cNY{Tg m͒dfOuok1W~7_~F#C6-oGqDG&Zٿeꖽ)ߒQ_MWm1g[-uLUU䷴1V|TL(pӆcM>{zc\Xn c!?l2^,M}5|]~[V/ O4 kzk6}3){lݬML4xBшz ?oQm,zagg@to+.]kǔs=j[j./\u묺mrs<<_k5˵[ב׹f/=ㅪA}iͲ-T]Z:/.DkfٞZ{h:e9еqK|͟/ 1dEBGGѹo1 >|[  [茆Rf  Ͼ3{]z_/sLB^;6.+7M2]~ʬreJk8 X3!Mq4G4 $8#T+|ִ-$O|RUR?m5GߢP$- (]n2E`n4M@4M6i^a&рh@ၾ ssN:wy>cx`RHbԊGrsٱ\5v>|omLuYvcfm۽n}svO-γrcnܹ_:e@wߥfs/ս֩YO~M+?oe@:dꎛ?;f^3}N?]{@ ͫv1S}r7mf7>f@~@qUo~SI@umunwsn8Wr?N[z bu:< @AhDU"OU=#AD^Dq@eЀ.(a z }ot޿ldZ\%+kN 1 <qj<Het}4&XDJG@M(ȹP M0)L ^&ʡw_cֹRIXRa*o3UQ9goy>^k=9x,ʉ<8 1P8POX!i.x< 0^|,UyoNlxYb]]ך{ٺtv,mM7f휽rnNe>^o꺵{7osܦ['kݒyW󽶪o/7~6jwͧOdGà!T2ŀ R PYxj@L(HNh&`L8 . !X<@DDP c*,@(],(0 501*g QP8jX*Hq*LPd@BM *dX2M2]X&LL5a @˄. L؊ T&]&L \.vADf"e*-sP4a O%A2i2шa.#1̃.F].OD ]&HS%MP r R < Pi20 5P.L`q.URz(Na.T4LH2VIv IPQE&F&Bq00đ&.Pe<DE\"d1-T \8,,x8 A\.PrK9ȑʡPʡHN5DrL%<+<TA'CAE`/5<L0I bʄ&#Iq"`$ ].V‰A B*<邚JC4DLك '*(9H!ABb($ R|0O"E<NY@=D4(@B!ADQZ94(g*P%=4A*19 MNҴ$M D& ` $AB5a@ F"Pu*LATC9<Tő&"qHTx(ɉ$(fz0cPQhM& XX U 0@QBԀ *(,@D@,A&CD/(G X=8(ς(@sL F9UDM j"2 %k@!AȁFy$ﮒ B=e"9#AEA@`HLxx,)$HzZ&τ{UU4닩2SMPEĕj*(_Q@i( '<2ME F4<]&=. OcQ#9T/\ < E4(V)d"lSO$r Q$TEVPI 'C!9 ^23Ѡ ) tF4(*$=D4H.j}ni~N7ǯ?nm vS5Ϭgx??ǹۍɫw,dSS9LBƔ0,IBAK̈p\0N> a8 Ab b cB*L_ :Dȴ&BAE~.)ҐIvϮ H8ba+'- 7E4x:ޣ'!38cjtPk 1|I^BR񽌝;a >Mg Ī=S5OP5M\ \ø#(g%_8(VL?LB4bWgh8ԷC\2߬# v %B:U_"{RT:nAS.)y$Dc2THr;cv;V<2"&Q@V^Bѯ7RKG&%=<,SjD"yz`[u9 ) B{G%H_?~;EuFZA0~"oݹHlBX))Xt gMTq%&0 )RJe,GqF=VJ6ݬ㥖V6D f(/SIfK %ZRŌ?!O.! ,;^$4l P[Txwg !T/B8` (vG#P`3/ tv$Ħϐ ,!NagFUY-inإʈ*j4vk8"4*lq&w6ˠ@4t[|+~!9H) c˥ Jϐ;@4=ia&q<;'CfemyF.H2  0܍2b#WH }ɗ`csJw>QX6suh(2q]FTm䭄d:N BYG@h!/Tv>3Lv3 iZ=6H_Ąz65x>deI- #7Y!y |!"5#Q>n*D;1RK5o]0`% ~pX(-HfF&ZRVk519]|s#M)iNl2f?yC|Je" FC BjK!*|*Af#xg2@w]A~d9LS)8?Йذ %Z4g i|Ȁi/.{] TLJ6E]@,mOq|T%JNrqJ길ٯW/8)98 ns![T\BZ)U'01Ns3M)s94`?Bq0z84{4Fa!';c|Pzg Q[FZt&zQ$Ѧٞvz54y OĀ fbMޱR20-ބl;4eT]nk+<9CW#T)8& Vx%/qКY" oe҃mœ6a+D 0*< tD(.5dQ2g_Б?7V *ׯgd3 ?/_i͎,=H=It'' :&%Z,YPDL&;R+8D-+X:mV=DoepRlToR7$hi3Z`.Ե ͣ9S"x`t=K-iח#(J2k&9'+q9R_Wyl׀J9֤1yH wmIJ1cU`[e9}"f"}E*Dukg#)<*R z`ޟ%9(#߇odA,A7iv]c*)[MmpS}~8v\5u xozxOym7utbt>@th.yǓ4\D\-{ g'9"xxTvUE g'V=QcoJ:?R].gˣjpa z6@^1A^`s 񵰎 u> 4a%bM nE RJ/s< NN*Z)kqjhj\Zg'g6HEwUdVˤ}xjZњroz1BJ kxkQ9>}el~ Na9x(Jԙjڿ.%HI@1;qXyDcwf F_ <5+i_YIBuEp!f+C]lJ)A?ReRghDt^\5Ƚ8L P@~Ҥe@lE 7jGk$]_ LQdb'΂B%ڱJ"L26ԝIk7*:Nr Po3 %"8@oLkC/P}OĬX\#k<]-aaZ GȬIOԴ_-ݪUg^͟#+ڜ xt'djeЇXDV P*Z.S ~cvΪ@ @~&%>D8Xul[28q.%: endstream endobj 13 0 obj <>stream i(HTnOfuonSHs {>ML0t*!r ~U}xBLcAEʹSPd՘!lKZtwBz0N[Ө.0JmY#JyAo%Սl͎4GVPJdU&7nΣW$1lc1Ki"Xl@BUZR~3=4.sE؆a,Abm5u ѿaz Ezִdks`aD :2h¼Yb`gcYB! IdNWF4qG4SQ_H)C8{d3B3,Y:>Gk\x"vV~)!y6\PQ3gvq/!EO<^&_2iᨉNЄe'0 vPivR":fL* 0Vw3gX(k* akp /٭爴]{N0ux!B+!._v& Wq!W0^ѝ1L-õ# $l]~аHlf ˲N\-&ѸG#cy%=tp+y,@T'#3FV{n-x>|pr(ؿjNi8T*F5UJ9Bp[%%!qBO(wA烙lu)(K!3ik4 $???b=$|Z纄ۅR&Rk3?Z|W=} qߵc2l XY/S1ds%*VNL N zWq$臜r̒1ѧ0:o:KdEWt4 j %0R%oX+iX)x%ZɒT3BMT1Y[1f3 [^o ղpd#)(>( ڂa@f/:{9U*[xT,oLnJ-zpn  &| lhc[L++Ԯۭ4zI :e`hJ68JlPТxr8꽃f"b!LB 0-)贵MT![R"B> `1cD&r/eYk`l%&XE9"7>޸Vu= 4BA}3dڰZP橄xl5ܾCJ Q]y˔ rj feC[)}a ~'̖yΙx R?YU-=?e>=wrg):;S)~~.J`*.s +h Mһ;}Jo]ӮcTSb}zJe:7É'! &ty'jN0LeScN ,(< T6//>|B/ݩGQ=l ٕ4 o1ބXnLقɇA}$hmiM-XhnM6ںo5 aW&ޟJLpdfQ nVk4uzWjQ#t 6^^>2O#byt\7p/kkRDQ9 v) g1݅[Poii܌%Sӝz)&@rv[WC7\3(QN+uO15m Oa@֋h1aFX4Cu|'=&PZ /t]8/f P[PsC4褨<҈ep]K:!N}t~}ƣd5@[W(3%{9m۸@qӉOdi}ZP7탰\2M䣛iI<@` `?I*TBSc+"N馽 x/kI?bVhJ m[|F=v!@[#cIXq;~GpM4s&yD&Gˣ?gnrF TJ_J";b `/ A75 :u}/7oN<'.p&@x7@ԡ{#s5CBwd,{eRA)+FpZ0a?LZ5zZu 6Cdƍ }n,?Dz֛B0pC#71|">*=`"o%Gjqhb*LfʰTLAпDtM8|,𹔪Q J-soE %d t1Qy*a9'[:FGKgMiJ(ThH2&bE|0_d+ c:I}09 Ոd3ΕFpo|kC<˓+s(2 F_/22nk&|0wS84thlrlaa0J)imiB`j8=dPAт w.X&kZ)x(+鸅U2&~}iDA &/1g2y2Ƒħoy8tN[G?! S|F\j\Rb_<ѱ_ˈ(خHJ8"i G UDK]94nUe՘tpEy`1& pUT^*U %׵n.cz1a:pz7snYëuHMk0Iϛn S`uX1cr51.pz/Nd}DVXy*AѢ⹌ߦxhz*O+|2V}z5z'+dz0[e#" 2Y, c..g¿|ʂ=ɵA%xbguh׉gGg=kvcMIlao 6J$g>jlPf $S/!Y3_ϊLaZԃI7~213{{3nOgxf8{ %Tܻ sc ("z6Г+6 "z7lØvJRoTB<*'K1]Wn3pm4л3nsb:T `x\+{|FPAvaBCka[ FⰢ@pd`|˔8]5r8FB _E&[XįhM {&yCep°vDJW+&*7B$x*GyVad}3ȴisE~0ЌϬTSj=ք{+5.W TsEQsv c [4N U)8dc.%i]ѣ5=g E±Q4$qf"G̓`h0T޵xkpF.Ҩ6c>J_?!(Cƒ6hUDbDUXMEwkm ]: U8e a''3ׄ"Pnܦk9"1k9$x+mo:,WVa({a9'⃃x=ڛ&L6M1E$ E>"Z;jUC5f A>+N?.j޶ⲿ^" D4 }pۜ]AͧJEɈ(=. 4#1凤m Sw=06Mp cB Z^~>vtϮ+̇/ _ˣ"zrCy؏AdVNQ1Rgu⶛Z^ ̲ eZlܿR=Xuoow Imƽ9y" sr8z.B&&ʮ:ܿ6.[n9\+*Q]|'fD1>iNmUs?) ۮXwy WK):R*ofq 6R:$|QZIZ .rڟ9%R,d1diffUtc[ ~O FgmFߒH_'8ɰѲYL/Z^l -*I k0VÖn$buWGREHXi>MPl0z&}^z38%Dd:z$ҸaBE.VW)1=$^tL, 'fDcxD/Il+2^#5%4"L ozV DŤ񊱁hM~}mEfz&ѯQ?ֹ?¤L]3%Π|Å$*D.O*Y:Ͳ4iq\WV|#.Vͫ&]d[-TvQlC ǮF"|! v \mt@[6!n/fp8 pD[QaB54_mׂlds ebet<#!3 Dk8I4q.{r>{3[ țf1mEfON(L!p#| R\ 1K n@dH_ "'3H[ؓܛL(Ҫl7\2BG.)%HPtp56GN/#sbz ׂj(_3u4M Gr.,7X!pv!T@73)]H܌c Q;:A.R!d߀`2M,Ҥ(Y̏0Js.m4,-<YI_s"2$opȖCY%J֜ԄIW'{h$ "P [ pV,s'fbPR@AyϞ(fiRHew`|$)@Uv'C3lAg/~'@_pI{"2EZ+Q%%GW%j$ 1P}?lg[s&|D ؿ^ ʫלoq5| X8isg46r˙Xg|T-=h7gxfaQ5:ؓa ;9DmB4v8,ĉ4iFY݊1˳oT藑b@ۉO `U`o2tb1ТDfx?`[H ۼjBbjm|{c@DP.S p-^dc4]~ߢgݻR^rj|wFq*% ޠbRkd..ncL޷V9e4K%15L;1'3y~PG|eQ&y0 SG.)S4y 9kp'<= # ]OwFYP?ԯrRPLEIDX%<0. ׬7UIs&;}¦I}=TE) f{^l),k3}t#d-C" p ,_L 3]VEb\X;C:b"6$ϤnWl}Dp|<`rͻL,"&P5U69y^({za!j8 mkuG|#>Ԥct683_76DX VT%,˩ *o'X_bsS ¡HУHA[CIքyLJ+vPMeI/UkPl=P-‰}+&c9T4O|Rsw>4/#_jL3Df5CHiI1+lꗚ<^ha6׮* Oc)hؒCP]==up9T( &Uo(* =<֌Xi('cR@1L 5 XI9uc8}p@]9EG)"r;Evs˖o'<~w(JV-HB;Zf"I{Z} ALAvE!_։)`|Ȗe WѐB"!-γAq;*Q /v¤Qj-uh`U`XJ+@qm.UyWU4t hB/m)ҡR޲_hKMhpՍ@9E)'jNi54 $ohӚb3 U6ll+,[w`C!OK}&C".[CKiYӤPd?9P\ ,"<, 7( דSU/CjiVPG7ܠ l|QB{,W$7Yx~.\8R]+~{[OaPX8P{TƩH A 3܉1c2E-$?4agbDxqQOD+m;mhlpl퐦9L^']y˯4Km4$[XHq*0tJ_N aHe Fv~9ƅ Y#u5HF1H@!j#PCKH:;'ı lsw\'6@C ZREcC04}1K[.W1oTfpA ${~\M"ޖKuhj/()FɓK\t?8:%P|a2Z7 56 '4DHB .VVm`Ò>u TbT_ٚH+?HU-\>OҹmY=Q*!A^S3/~^wۉ'0 PP ߠK $@揟As5=2="O`ghβotSMzt ލCq*]AiohbTMe-gM'$:O!c}[p1ړ$ 1`fv#!Or_= t|79.nJt|stp;hGI< O1jXuH斡u{>j >m63/Dje ibSN$:iY Z%VyJh3C"/RSfGRi(rmYNl+i*>E_$FԔcXަ}4dr`IO>2(=by3¶jv 0pNlxƹνKpf ddyD~ 1he315s'pH]MhuyȔPAVGʀpg_OA*kB.eO6$d c)@Ǫeͅ; ;8|lBNI+@.2ڵKn!7 fP|=ÝNh6" rj[LIE"0R``h3 ,kD*'Dy70E wv(*k\, .$-?0 C8u?۹DGYĞԈܝyb} ])x{EuL. 4,0s(^H凐͟. /֨XZg49YHR4TI. HȍQmHfXNLx|g54wlIhUظ$,]h:Wk)֘ʥlzNϕ՗MH-t9ƌ֧Iàz'op^paц)4nD(C!w ?X=sSPq‰cE]'Ş!Ζy. 6>a. ߄{ N:9eizDuxI%t7m,u: '9!SNl+xٺ 5xL/vHIj'o\1` آ?bܻ> 1^,Daf9PkHǝ|Ӎ=}.p_0`Ml 1tEVM("FŔf.+,'`ɰdP,VKY셱NN[e]@@4:Rjy+!@B?0I%# f0 >.œHW)^/CO24m@B1 =¤hȠd ~`JBN2d حΌ(` Bu. H Pua- +2SCS=hBP1Rkcf3ϓ ?X%O(0XpTy` ̀hu` cEsF8U . "YXZTh k!|֞կPVꯪPTU KJ B*lb(,(׊4J5qDT٠6J0Y*[ SYJͪ@&t}DWFV4:t15s-*`SDC :2PcфL"ڡfZ ڧFJM"GX'±"@*<&MsV8R:lp"QXN0mqj#IYMok5Q@>f9ʶP@E%P3vRD1/ubI좞WNF :0ApaBbFHuT%|/F!o܇,bIyAd2@9l`'aE&#qV*J\ +"&5"Fl- (.(?6Gl6F<~qb]@0hZ;[QKs,b|-XgaW̶>ϾB^1Ժ;~L9DF/JFQC$U_nYEK 2ICL?5 ͨqb&% ף RAqbU-2^-̢wna1A. xJy= %*HR[&*O2HVH 1YBByf 8I`M h͡ɇ. Qa`(ăI 0f20sa&uAmEpW )\WF'׈ք_j'UN=w`4 k; ]@)XE\%$B*_CBO /ü?`R(/b{D.Yǯ=۪u~Se}vSKxY!p{D@G_Do1qλAq Uk 7*Sߦ8MKy2;a̜5I`D6&@. 8X 3ySiGn%bJ D*?ݔ3 RrjdKšӮ,_Wr^ ҉' يkJ9qĞl#Å&B+tGa/9U&n^U&~B*L(5󈥙gS?¬7dۉ(V @ͤg>:!eVɡY0Daetj6[=PFwE 6* ILp7u}پ#FAIX7G[OMA8 ~eM뜩+9q晜W}+x3ɖAzOH< {]@[pGygA>PBm/vRѺ i -`ch:(`䬳9m5[ɲQQWlVBFqlZě@{ >~EE.[Q7呦 lbFB_"6=-`x6hnlSoK%w:wXAވ6n9˸D3 Lfm=aO^r!{w{*Vr p {bB!Zgn.${. ]{fcsI~Pz9D]ǒp; "$}X xqٯԡVXx?DَZU <FrLkBء0 5Qm jz;t jİ@qCsN77L<Njm <*Fݜ 3jck/myj+RZbgW` n=ua/V*.oePp[Ѹp?Mx*M 0]@01ٖ2cXJh\%zc|ѿAnTGA0!XqW2l h_,"ONqHʵQ]vs@n:R 0Jͪ=gCVGXLwv`.'>v+-sf4 aisRpR•H W(Uqh mdP\**J0adFNЃ<1pp|\nSq,_{)u y'w潭qUd#P[ r U Ԁ@&܌8 w:BDXQ*Med^4D*#$ ќ @th#6`ʸ^F8kѣ٣(e)|Z̅<]6Xx 9PXȔu l1&FWo@ϣbP"{v@hYTdSG;||! iʕ)rxҩ}XEN1[4sSfjqTJqL'$uoB F[*(TMNEN~[E?#uqk ~rM9fsi;;NoMi}-mR.!t(45/2j;ӈWZ+ĥ_tĥS~ֳ6Š*v7DY<+ g3d( p1Z|a^3*-K7'BzИGeHJ. T!ӓzaݗ!gq^P:60,"NaFcU]@i'ge!2呧eŜb)ˁ00Lx"梘mYy9OtH#l]dnmj2sP31\I3h%$QiY-0RЛD$:+$OU\߰2QmL4F=NC $&6 iitU&Pm7AɝN =Gr KJJj Nݒ몑*NhMGNހֳFݬ}*iS eO%S?UzSFөWj12uAC T]-4AձaPz֚:-_Sx(~LW6׳vSD ] i9նY_U6@i#,0r"'$^C\ڙQm4S:ʶO5exjl)&9 %.ɉ4 0kNv ou:\).m3^J}N#=o4s/$uj6r:žYI/]@AdIf~X.ouzX> U $j>ytH4R쯃SH{ yW?OJn#G╎SuvwW;4FVrlY;zZLBvp).}(~D adW,ݑh\0@ bOPKgڌ,6Cf|`4͠ f9@"] klBF`uF!2Px #Ct9zf"/#PftL.IZnF((+4Gs N ]+d n``]@ѣ7.<یN 50JCta{hjNPE 4}4N4zt/H!$kH+w5"-;#ڐsyq0;( ]i ۬mڢTXrYVצ@poCwP:Z \cl>]gsGJlEF&B=CT˿&}vzpHǘbIIPhvzT0+/4"*W@b@'] W\ pxD-WllaֶdpS=`8)^. ljagK`"-W k7 S0xZS:)4MGZXiZ w&ͻg,Mwh3E(<{\g S!U0.W_F('RL`~zQu3BLHL1 , )Fwb3dWgDMrBuM]@F`V:"9. hi&#Pp.Q ]@҈3­Zr6J. B22 I30ԧz-Gfq}ȭCj&dN('qN(f R]b  rBZf_C5nƇQ. dJѹBiБ"6!:XC)Ag?}> Dk@R[nW|tGo/ag\,rcJDm[g Vrb!qT0M j=<$n)8v) ĢA.Ħj5R/H]{]J@1F{9$^P3HZhu[p!H4L)P+]V(*Jq)i4MWɐB<;D; (7t L޳@ڌ3oJ Sy]Wꠖs;\r>**hZ9Bt_YBMuȏK |uNW")m,(F1ǟcvbu$A'i"Hk;PG5SҤ4 @cS';V/D+SyޫYdw?Ŷ$uŦR)^$g 2aW}њ<즭3$/Q#8TR{! bE5bz8릭3oʸ~..OӲaNXLYqYHƊ;h~MKb ?YIί`/22Ҭ?%B^!MP]#;׹ P:+hb^-nsi˥tr~0+)QƐbz uy99_T+h:P _#iƔr Cs*NiE||5NNR`uYmVYaɖR|FVOC#:Bv"~ :BZgD"rX;`[[o&̳3a. j`-f0h[9G̽FfW y%MG m\e8X {%?'3=+41BBtm 2x>bVШ8@ot (DT\Pq(, ,%2jHYq; r$ )\xbsf9|,NlD@o4£5b" 9 MG~Ydzb Wn4 -l']^\ܹv2;6k)BQja!p}+7u:y _2|ԋLNK #X}w<'G9L[c +J=tD* 4l-Hc  S9- |q@yTNNOJp05ZWN>o[RJG s4 \A<"y,qOXB qmHOC!=yg4gtѓӟNzr+o@x@ODm#4Mvrڃ4*Hp mYPgSY$`!+x(wb\I *[D7 +*$'^,bJQFҬKⓔ $Ij4= 7ģ Hqfp=1O6q5y}Y4zVdӿ`rB& */Nu 8:,#?[ܬ]@dΜt9E` "L?FdwS. ؤ. @khIl T#=ѵe$/֑יlDJ$lh S.O9˸/'Tpw~{9]V?m$FLco?4VOĞ03 R껀gļծ. }7)VtbT"gn\"LMB  %^"cf͝I8$vCƂjKG 1le. PtX~ RxU{bDYq"&Âċ*騈2N X-u[r -~vgNQn xF q`'YnyIJ}ͫxd$bX${JYTVOXttD&S#1B-»W< ^S,Dkr`L!Dpr'"{zH^ȓsxm@#h9rWk( @U]@$y|. HWip Q-wْ6VL_1.M "O48#$6ɏvgXY_r޸s?%,;FJ:4(/GJܡxt"`~ŀ^K. B LʥI@U|舘:OHDqs4nEV DlpkHnLGBn<Jrc`P}+ؓ5O^aaعKD#夹E>{ɞ]5]Aӑ+vg{ M:41. d߫u][w4+ =<[ko^rMrv4B!JYQ}ĮFOˁ=7[wVʟ JHyQq:1U[Y~|0H_xu?=Sv3:_T]%Y'urO8Ҥ0ٌUʁ7 0|/ZdX۬&[_0d f^L) j z K%, j[)X^"A8;Mz,Ut ֻڷ޶޸n/{*3|y=kF1aПnhֱ8Tl~\;DjAs6z`HG)f(d$g"};b,Aѭ 4:Ю/E o0Vv0E1bGYSs!4(wة]@ʢ ]0[2]rQB"NA" zF^R-vxgb2ږa`لRV*MwADcv7Գ99+NϩVog0AcG('60x 4G1\K#pi&. ] '=E %ꢙzVRaHΐI~׈jDnU&7Rq~ "O17@[Ȼ5C8df"^ui?:q+p).SMKP`ρ ;0dkv{6tG!]~I~Rb8k9 r/a%xVdɧOc: 7O:uȿԿk8:G'ݝz2zNO#{#DbQ@8x}Npدd2jc/8^:yL('OhDM//Xj%r宺]Ԩx%~0|Fލ@[SM'{ {to7;:ѓG4/9wvr?tSٚ SF<uE\x(nȌYIèQ?rU|q`FQ2~=81M_2[v4>(O[}መH1긺wO೫|=87MI3'_uBg8.r"5; _ƫy$Zޠ؟3 ޟlHBA'At̉^Iy_^l/ӸMLDNc -*5|fg5>6RŐ*4*"}+RϠ̻w1I'n-J^Gxڷg ȨuZ1Ba|8UV‹9!]g,Hem]4:"[Ve{IQ8doT\ (Nt,3p~.aYhѕK; K+,B7: csmOӉ+E wF[!anX1.w.#tRQq1uCA~LJyH&`K^WkjpyZg t|c4S_Q7PNoS:>iG$~(+F 1ϒǛPj5O׃V㋧u2HX:V&ޏT'[ʬN';/^i:ˊ=։c侄p?M$;m_pbdO4^c?t)&'RG_1X{aX,Q]=L\SQ?X}R]@F'{o)b툹N@u\/R*Tَu MGe>!J)ݣ1u6>p@"`!0fD̷KQl)9X&$hdKǕu3`9`4n>mV*~P@~a$E4_Pc˹T]ՎF9!BDD$wT3Yz_$$Zrq]@0H8X"a]NNnl4X͊-> }\] 9t#fKtx_uWB(*%'RC$0 ݌u.Xgy0WT׬vNNhuȭ`joH3,r:rN$trq] &zTʍݼF4$Fe _a]ECc0)/Og1#Ӵ{-X-^ 2CMbF>(g.-'VOTxQV"veg S74S<@u0`Su"tdįW)"S?`#h\vs_P\{HKV=Ш?.R|GL`w0D˅6hw?Q -m~=Siȓ)+YbK,Y 2NvcW|Wb>>76z($ }~%,WRy7xǗ` pUQv1WkO;DPNp`@cKaA[#n4 H*U^D],/`x]6lEM+ɆyD;k`c#I%)c/w}425"wí.!aAF?̀3tvDa!$oX(-)NҽB$T86<(Іg0J{.LvH yKBbc(TJre)uw@r({vU=?XTZ妶CC )ګ)fɸ[6!9G5U$fP'kFO7б5}xGsO+| p`qLqh!V5i0H0:vΣ[ZBT㸁6>prإgprz| ^1v{\ONLY+$C \ ]8!q&ya fu勴ü$JIǛ3y}YBSդ\X{Tj Xp?*9.UnA懀w!L߹1jmxx JڂygRTٝ8ZkHsx1j_ZE8鋕! aEKB B5 #WDV<+MD-/(n$?BG@i ʲW5q%?c5gq!,w`gqsBRUBk!d\"6\#Q*LSoGŁ^ci:UL)`Xq)B=w+ʻ ^WzCnA;5ʧo,fؓUo,No/6;2ٍ9Kh!@3Q!F`eu)n1JX 6c>hMT,l%͝0WK!cDwf^(Q}i{Q70Uicټ@b$5E@L /Ep(}ɘsaU6=7Xe,`ЋU._O,=F حZJ¼&^Qn8Vm=ȼ2ӹf!XT6z$=HsX+dP ؤWm'P]܉꩐,[yp8~.|1[+H=RD7,2*jaiୌSLt5 xƴ)5 m+m(_Q$Nܢx%Yc&1-eC?¥Rτ ;V!PHP`Bztɮ,KZ*_eBS~ 5Yp[DBuU!pPpuyF:Nу<}-fYL:dLAq`g*1z LT.eSl3, ۅh>#W^&ۿVfeC{&R5/t!,M&" ;7To '+`)2&^˥%R mu3n2^_3LJڣ h^@rv)1(JJiKl|!txW_#IV&DhsvZh&q쟏S7kQNIbCRs܎yĥҊ$4@(Q̹HΘöb<^;9^GPzp)/,>aq}d22J–$9N#ޠτIβW݄ceR(ʐdf0'n`suw䤦S-;1wa,hO<7/UnwlC$ȥӼ *9V8WJ}Jj +\Grf{Ec$<IR#sy *{L0"4q%U{5$_Gd=X%.I=jsa&~w:'2,ܢ_E~%"GV js$qFq_Vn~Puc_N[#XOb9?Z%llu? ,TH͈唆撚%k7##b! vT)#5}rܭj J=S?-"iI6ܦr"ϗ$]:e}/4qө8tA?\"v[\YtC!~%x+M| $EP&,Xv9D#Vvv"7H$9F̈!#GIȅ셚{|YY` dJM>=9J)؍'t֨ݛyF\Kv[qw6Bjhd<%[ BH7%wPsμ,Tݵ$E;.Dxw gI{uaK֒smI%QgzA-$Pc>aI[}` :k̽Dm9e`O,ALf_B 2`:Cm/xp<(#FX)"0WZN$$[Np3Qp;^v>|q#9"G;4T_ZaH7fIY6i1~@bHT<`(j.WjP5M8]/?GW/1MnBD# ~*s qv>zBĨBXXԉw*o6> ʈӍD@nћ!ov w РUFoHR \oE[[6u黥|@^TD:FF`\iC||ޗ4= GQ\x]7^.I!o4)7,0Th׭ZZ X .֏4\>>яKQ(U5堖GRRxpIg{ 6 ɽX=XN+ m,Ncy+Ѷu!Mpw?Z^ӅiN!|,_~+V`\%oB\Hqoe1o{1 E;߻flYZj]f|6#%qwuO%;!;j24)?GrMH%o2)H8%}&ņGI% j`g71cvs!J8"6HǞWd;%AUB5"֍(L4%L5Fq_8\i0n4Cr\ٽ]%Y)QFCҟk"A]31t DS%#` OisF7VK)Jqh~a:/`2G [bIsF ɽ1o&\[qloeV,CCeA ed!`+qýb؎RJ!.h?u$ YྲྀD|/A{ߋYOQsfuMCv[Փ+u7-IkmG;n.;hjDogӀJJ.hȞձԸz˧4o/MYn9y4?ԩm>LDuz k^dK'AoWYi@ M %eeB=D fߐޗEǽ8i@io0k p! $@A6{+pvqRGC; R_Qs::5Ko@@J`IcQ7 wu8b Dk:MV b[ľk+䭽l11@~vgoE"9G9vg[ٗOpâl̀J!!m)WvA=ޤ}vSPWUvmG="'f*3Eͷ224sT1df5Fd *~ yX-Sӂa6F{)7^?8 f$H]> M8?igH2N~iF̝T"l]M4!UsZDu0rfJZb$p~E $&P9"`az!/1ݽ^[["o]C]]]\cdQndfmY9d.69LrqBi+j0K;q qBpQܚ;֒ D[u-rH0lnpvͺl $Tڞ S%NFp?JHt`N-42+jX9 )Zl* n-ypi1tQUSޓ =bkfDrē,ߜm  %Qt??t lOZ 9(tTp ?k@ ʢUf)/+@4j| -9LMP o˓ dF4A=<[ `XG1Ǚv 9x|V.$'&}*@V&?y5 e #qyf2o@q,9xՖA) '#PhmݩA=J ᙍK=$ÎnmnH y/\K})=b<@b;+Xld:y.dP9 Eg:Vj#-wLNۊnE[KS@jTq̆턊+{@D>DTxf Ȑkx_꼦[tC {+CơK_JB 5=~kfM/T0/ HEXNyehf2^~C7S=hiw@]vfWx6I<zȵSrHᚗY&8mŬX3ZߖH b/)Yc F`x nTdCzzH a(zKl3ihT7V'ŲzɋNr@ f!Ҽ:s(;3[x-y`~8_… e3W"1-Dxg)"E001.g٘9p+)4Dmyʾ{N{9kl!9xt XESȆ'k3/sxvBͣKGu' {bm$WmB(ULmc NeVxJO_"tiZ^>i&5MJ lef$|RMPD|fPqsvh^+N_<'0&WEPsv!,,NuJ"$Np!LRYxFݍ@%И=m`rd<) jDo:SB,p4G1f~S{e7?ʘJv~r3R K[zWmւ^J,TV dn̚V7>H6/|yRfP &462lt IUEWVThlVK$Q С+,XkVr}˕z7b̸cS H//-7:[rY&1Ѷ;z&4y7J4`$ET&bNDu26B& L9@ #櫢]n-Lqy\ҥ}C0M-Λ~Vd1o|YK,lc?_\t) (I*/rjw, R|_6|undn%ޘӡN,xwIಙ9s@?ŞL ;H.<ܨJ+$7}R OU{" qзs5ӭI dp#]_ n6@QO4Kɶm/k5-!2SC7\Ε^0cn`z-g' GmnڂwB# , -yp5b`hzK/ݿG- i֪ͽ,z`-//G] ث"amQ#Ȋn .B䍉c0(FRɚXɰX\EUF뤸s!kʿ;H/Q}b9O$`&"/ /#[ĺ}w>Oh '/Uن&UbV%E$6^\wو0xVU $,Qmew`^W״)A_␸={ŝy,@ ^@. FFۼE"8M]AJ ` VT~*@i6tw-jPiE}>xdБhhP:S}ď _8~;!|+Rf" FRT 1MBR;bb>} cFUF$"epn+As%g-K7YHR3'2<"җ>pYjl%1y!ƩR{yw!Itfq.Yj77M@q4#(*-X7n}t 7  ( fjRw)'".fjDz`XǷp@g&$7TjYe6Re_HJ 360-WIEHǿIZyD|P[nGze?B-1NTS;ƋoxsU&tM}ΏkFIL Z@&֣8 auՄa4B ALBʒ&sY兰؀ ?`&bլtMEd0/dtTs&PMlӟaj0Gkn\17fmͦtuk}⥫ C:3fط-};{*›R4' VHص%H7-0W՜'c`L*L1=uNORli e3oR7stP~V(ev5I^t^TNCjfzO$28f Aď$l3 S`W1? ի`MO{|c:e:9EĎ k$ üJ@Fu5u|y\R dD9<贸ݔSY?gg֯&( _t"ohX(if 0EC2ډ y'e٤SuK~`8_F| SiL[V(u%g1imШG DF+ocS3MGc}'jg:hȸsסp2S{$j!@#jO|`FxHG1@dnBZd8O񿀊IFߦMltbc5M=%лX!9/Qa"xam(4IC,&} Jz3Mp< x,9n-p7[Cia%QS>d dpy؇ ڽ;KQfOl}m \Se)C> qa Ju+> mB6~ªsQ(ա,9ىgIMRgyXY(qE+Z-];ܾ^i(ZYfNNH!{rE̳K[N?iJ@`s՜N׸|e d@uǁ [†R?ڧÔr60-t̆]fIp~ h5,$EˤU玱:sp-Y΋Lggl8|MC9BphN] K}t_3w\Ui*\p]GndqL⣅fňZw]33=-H[C#M D h1HNJ/12=xa~Gw[^IYk\=MO?%zj 9v5x$ێ:'i(B<AZ8_͠t׮MMK4KPW*]ڄOʏ?:G@3AO-O-Օd^(4 }XLK}# Ǜ5:6}3c>w}ʾ;3hDd~9 [؆RYɽ9,h@;NmbS6n !KVE pIKESb{r"f:\pMb{Nն@^7bqy}WF<fA]lN+ (xW*r= *S0ӻxP{0L$"A`X- #p͘N,_ظnW$6zF = "EN07FQ'\2*,s3NAvh" v<y4HˋB:8Κ 2"jIFeQ|=$^JplQp kpj/[+Vi7`9 :9&]Ba ᐑřJ<jþXZ?USElb[Ap~${}-DPVY_{irYrFϝ#+uy~R/GE&@m/$o&֖+ &IDZٲYڞѮ9 Rx[w@>O<C~@rÍALP `W#6%hr}Fȑ3ݓzh\Nzn)z:)#{s[ ̤pX^jOSBӰhPK6Lّ rYuOF1eH{͒; iSܨtc ʠzz6p79A|oXoF̝kty#!I0 dt-Bq@_[eov:@8s9QY 89mEq|j1TlCfwi `Zɞe7 0,{h>zPR'aQؗ0ot/y#iƶ*SS4wI  e[򮀉u|\v醏g9NU :fQ~[߈L1[%E8 5n2v@D1sO6$蟫j8ؕY`* ^3 i+Xx*_^k [?ai+$"Ì*|2'P`Pgj)c&1~H +ethXV X9nYkW6 9![FAfB0e/BXbƶ @t=?࡟<:|{ADUܜe$"U ]@6naThNnRVLmD>3 Ff8({}hTڗD<6\J@[GʩC9|6._3.5z= uM!Vvˈ\vZ'~ЀhdV^xw4k}~~wJ(Ւsu Z4S1B|yb( ɪT-yOjf~T  YV kFAN;g[Pnl]i;ѯp ZtTICph- J6 wNP6"GL|Ӣs,ђ1&`֢a9`ӈ dM S=y !@u)yDm2:?yj@_G+OUFoC}7D㢫y=95]$筺ޡVQЦUxQRxYJ:u~[\2oz8ʀm*_8U 9o5ݷ)Z{&~];EgW1# my{Mk`$JHqDoTmAWP|n& J7`O Z#[EqMoY+7Iu>)Nq!GHvC &XXs>R r%>ՙ zӍ)k"Ǻq簒Ԩg(ꗧXx>utzh-Mt EP3'QndBp7:&ȒιtBE,F5%YSpq8t/h01mc1 9f:釞эڀR("lsL J3<ыXԝC6Ypww]pRl a9GwN*$*1<$&\+`ݽSJI΂ΈD@By#֬0b&0>V3VG\(Ȫ#fK5s A5@"dlY5xjF,^0cehTH1)>n\0 @T6ܭ -CJ7JnI0" G=P2h+rimE0us]hZ%ʀ#$M@20 -chH ,ɦ@"d,$d Eb(& 6!@p 9LzVAq4pCb@5ɥRSC*2L4AK1daF|0; 5aBRФ#,DhֱZhh$X}<Ew20hZ1+LA"&^=7ЫT;M[nT*< Ũ(`r OBg*tku@ ?#eJT88 NsrV,AA5q⃚Pyg;H}'|B 9@R҇& d 8jLDž_b:4թ2BCu $YFNԌ-#pW`umQ"q$$o=t@Lv+u\p M$@듅j0&惪)EL ӣPѣnm*(ަ3(oÓh. 3M|u%r ƍC\"E9,zk= xrJFWG_]e60m@I eJW@@ڧ! 8&V-bT|M$֛7&U!5| . ;pB6Ԥy0nuUxXx4> e; R8n ص <Y}EH2 Fr#B@%QNBDB>Bn&`Q4n6d3|'oY6F'W5HRjhEPkZx|E)"ȢZ?UC"Q zH420Pgg$&z[s}  ¢;2MOJug q@" [_D.10J[_|B)Qe[: [E!I Y;!ǡ@"X|R:v0@j-4HXʑx%b!ٴ*a~bh! ,ZWVCp\TEl8MkHf@q4 't P*PY> Mk|:zށ @5?_P(p(GB0Yi^h4H>Aj π,fQ|}pЁбsZT1oө/1*q ,  Ѫ6=T(b`b4h]Do{ #D2>͆@7TA&'FwP zqЪcj%PZ Dkx@ VOV*o}r. ,QP Xf9iQ wh=07|(00 qH @"KDa0dZ1C[&sӁЉ&z'd!Cb Rk<ߗ{H88h,y3U;m`NXXO})]\&2+ymp Nڈ %4ŚTB$(PwbT:: j:hQD%b"I(P|.ҙ]:3tf<`Q(NRU4KiꄌR(8X+ia,85D;)>a䥳ʸ0JrS| X2-''ӹ '?4NkV-*J/SF9>/YI.S )WYeù3Vu#OJ{\;.[֞tC– ן2rp'˖',뤐g]fHu^^~BS.20 xcVteң9θee)]vMJ_ˆD6`eW tXsB7NWRK'pk<}BYOJ%/#uЂy#i^q#UFtƮ4vZc9#ޞORSFϔ+FX#\aSJH}J#k/}c?NWFcG'pd)g:wzӟF w csK;v#re+{Mkp9Zu9R)irVnחe \ۻ>sp/XKoH$pdP;kLǥ{Zwʏm*Ny!VQؔ17uJ˸]cW%56$b0\G*ck.2DJ/r:88R&Ӹ:-F[ #EEcep8{gfѡw:2082.8>:X1pxzr2ӑi`pr2V H`<Hp3΀=&@,uAP 3\0b)AB;q#At> vux殣@"nt'iLQe:Mp7ٍl4^L> C[DFrK)pmpMb\2B*J x&S4>)cq^ZdsbSy8fHdtkK# c(:j ˢSY 43V`5rBh4*TSب( urK\|7a)xO/>uZ3)z4eƦPdwR<-t((J^р$LV f#h@`8PTr>@ަ[# Ir W$t 7Ftj7e.0nFY!e?tC!tBsB'{!X@_r}|RV,)C=NҡN_\+K=Nw~lt#S=n;)\s|^B.zKno.c]uؾ܇퐈c;gr2mo}F9O.FN+˭vnI%;*{O8}:{5Ɲ3Fc;= ӺgG.+|,a|_p.lu#/KYkSJYÕ5ҍt]qcZ˅J9X%frk7]s笱eZ+.J+ݧֆD,Bfg kC9:rus\ }RgD,=7>6d.kúo/RƕNz3^Xy҅go:)9)pRY'm4J鿑Y2=e.yJ;ʹ? +t:S_<}iJ5*{)wv]U:{#֎VkC"GH6*%vRB* PDT.4&("0aREI&,O9I{p Q 8,&#ʰwI`UY\= %e,w1 c?r½.@V!%`eʿDS쉰5: ;6G9Rp)7xRfT7!Gs~KOWQ:t6ٶp-Xuuz[H/A ИuAylųeOX:xH.T @7&4_hwMIJNkI-oe\;סÔU"aw| .ĈH|]6gwW3/M\2aRa0Zp]BLYȟO8$m#DE]uđ?5TJmg A @FtAӀHM${A?D[K p1#IӶ9Ip}s=&9[ ,T,UыN~~tX=+BHlBJKAϟj (ɛeu;3 ZIhsSX\M 85<<wq15ZO3ޒHF4c̫ibƔ@Ȑku!l/oA./ܦӰ{~tz]6E(_]=.2ѐZil}eABvo3eFxXLȖ1R_g qv k1y @q!Ҿ ⃔)4ἁ]l_`S5U ?v1diK^ՍhZ?WuLvF,."~Ԭ| ȁ8!rՍV_9yfFkޫB$ד#ER#`{ǼPL`o&r6rGDt#"n⁅Q ~ܲT]SHSq9/'N[>8QS1ytU35Bc[~#Kt-OJ*E}rP-!EWbb\W #Rȗ)LS9mBf\>$eOS2ܰ#!@9^,M0o CAyk҅%߮.P7)j:`kYg=1b?foR*3^ qVazBQ3arBG z, 9Pݣ'N^/ 70 -i rݎ8%y?XBF9vyh7z-(v#EGן~͛<:3ݺB:eϽ%V3:p 1̵]h(;% u20y{Xv%Ĩ,cJetdӅſsU*{GnM wDű]c^̘a2:7.~( n cFbjC"㥋zzfjZF%OY2m Q\ G&F*t b]l8>t5vEw?KfNDhs|p`7Ahp6S9*WdoIQVL!SEn圁w޸xܔ9[zﰹPq7p͖;XnIGzVwX4 Iu J<4 7Bu6> 5AA.6K93+Hގ/!)DZ ;ٙ 8nX9"7Uae@(JaC揕r8o,it/5d![~,9)>N"<5AxVC:4Gd3tV$5Idzx,!׸ " '#gr iM,^(|<}Iă/TI=`J%c(VbPqWbL{)X,A\eO5H?:j^y4A3<9eҐ\^.~I#wfǃ?8"kezkm9'ee'D6O"BdR,[.!T_AԭŷȒDR x.0Y=$ J"TCu9/w=w~%EEYQ^JY2 wwpBbvicb?΍  sqEep(纭V~e27nݪ.s]=+*syL|!bن\!0y[4!b vs~//F\I~ v16c9I@4ΘB,7җ.=wK6F !)G -J2 $P?q{XZFrO, _F /"ˮwC3^i :񦢗r\,J#QNoy(5P'3`Wà7f|=0R17Ar`5d `D{t^(36:lkb]QmQ"㠁0#|jő}Kg;22 v6>;gvb.]@ 9 8MP>}=P•X:OHefG8Hwr[kPşH], ed=nw)Ill\35,*t7JelvbF'zх .c{*J+ ^cY 7pfɞuT+~e}ͫ|yx+EWYp"#mp1<: zbJztmE\Wؤ+SUX4`/ ğT00k _Uo.c +ύ U[.#$_w)l=|**p[n*vH- LJߋY}╭0-̎JȔj )yPo@e<R,9GMfdbjez.JT7XFueδ못lMTthޢ;@M1/5q93^_Nٶ @+B7Ky!{"g '#y=;e=:Ocdgc>{t~覃Kx}u\u;oCwT[{m>rvu2[.O'.ኹ ͮ`P wVBDWڭf<,{q{=ԛ17|\r !rBdo3{59,.hI~g6g9nZv4nxԪ\46&On,p,p9_jM[# _S&=%EפMPR$2R~ڐ3"ZǪ)r%w(N9SEOMKH3@:)i1mL=oHK˅X%zi0墊~jRkΙLRL1 MZ13yLx3̖Ֆ&,=ٵ3!٪G֕@gŘ&YYk($UI^ HQjc),@XqGG/Bо5#ى|AQ,[kJJz$8`AoP *-mn}dXP mڣիuv!@pHY2"$UluUe^[ p Q+1uEu\iT$EjWi ºN{ף ̈́y KX?4w&6+B g/6X[7DCG-gߤSoY4-%)u8Z/er t:#ԑ7(݆ mƶ!sMix'\ HsdD(Bo!-> >KÈ`9CA}g}$Mq|x7?{RImmHBLdlL|J$xL?zۊEa:5ͨ$goG6 ]gk&g%|Nq:Ml:i(M\ҭp`!u3R81?#,(\1%tgN9zWw8̿S Iԥk҉w6_yc-ao\^# !ˢ115ew')A[a|o]A]&@-b;&pytO;6hA;D8NPb̿`ѳv>@Hd0,c~}!wvn[:<( ":J,.dth!> 5"OID$h1m13@$ 9^H'W)3 F޳ﮰCQ<ၲf]%vW_5 W T }Cܞ(#teGW {mmgGCJ3a#;,Y/qDMt ;'̓#Da{3C*oXSmgx {aa(sۍQ+#퀆]kg7x\k% bOx51a5l㵭"1p5\f2Ri gѫunkG|GÎkEа-Q**TΦv19&/t>w|u@m{KE./kJ*J*"y%v{/c6۰ Y7/b,^-@dۤM:w060p5Lᘓtid#Bo;]b @('a O.PhlFy+T5}l9)8%Q*h&E]E!oޕ=~!8BR RnP8`\S9DTO+76D#p@]:Z栨!a.L2-s`$qJW+ٷ}"QbfG'N͕|wt=^c.k8Ho܎#7] 3 E[@y(Hnn*FJ7\JCl| wV}sߢ6)tp)\5(% bI( %)|ف-E_0Y;ϐMJa, Q͔[q[Qa("v[ sɛq{<"Y 0kYŭ}!&G]Ε] lȗdbt$!geYT_pɥ VQ旧t|`DC[޾ G<0o,E(P~vs_U њV-B<<jPw#9ҮLtWz-IK_<5L0U H0VWLLP2QV_LۛVXZie,Wdξf.2-o]>Z9tCtܕS^Hj5ӒXvWM8AL)3jkhu=% lآrEhZdD2s1$n|;Izj$7Ba }'[O}FSC-P8] jۙ)"qp 8é=Ig%D\)wXKR@̶JicܮO}AѺ*=)^#CvAZAM۔~ NMIMD2_䉺b6Y #V'DX܌OpUe8U*tZkMpl5jЦ M@~ fm8`܇6?1hSf#P6J".6o9 ` ?wp8:48vI,Og~DpQ?XE< aw p`4φTRm![ {o5"&dn6{| m tb0'6OzJnQsH., ?83{֣r54x"kJXءl$r 0rGڵT7 aCS*&9յ՝iꥱFUJMV}jÑD)mJR͡6B}-_%2K Y)QN)0=Ry_˜ <8K4H=ES!}]GF989ۘ'xpJtB!=V:]+;egI\A!yI* B fM߳){,g@v5%ɕ @!K25$x&+ͽ5228 :xY;M!W,kvK @2n(&GlqA 8dBm]^LR2d8⿣~E2?{|!U[m4Zy}~rLw[NπtcG-N Fm ̺pkJ6!],ꡐ  iZ.;C%2N\TE$5K /q5o6$@Ͻ࢖2xvME_t,ߌEӶw; RCsvJM_H9UvM2@}TD ?l[݀P۾cWv:>-tX49y-$ Bx\pQ6#1M ESɈ2=kU[B G涷)Q$x1ȎҘ=#pb#ݪR kݠӟѧ}Nѥds#9zqZ٘Æg66GU.ŶĠ^62ҙާ?0|GjTC\g 80NㄹZ`Ra1®#Ujj]JJ匟 Np# l8i~G4TpS0C+ty5g3J\ <`ɣI$.*+Men%>ia*sbB$w6KIDp84~F= f8W+׮VV:M$C,Rd@&lT?Ùa\%oVgjg* I (1mh{ߜmC$B#omFĴ@0?3(ܝVgXvY0gN鎩΄pW3A(髕ɵZOb^֯T0 | 4?CaܓBe3tД2P|9 ˂ dc!r9]`>-K;p |Z1]N~N>§2K Ԩa!@ EksAuAHo;ovY,hlȭ&-!DK;.>.- ӱҥ $MkmYj9_[3 .mv ֪xTϢK`j)t=M oj%ڑU[%h(IUp,հ3]iE[n]п.^Sݪ_ƝWK۸4BVk)@uhϔk79#?[q%;GaU5A@X,T+4Uy36 ytChO68n) (`6;xzJ#W.eUJx= u`4RěGJ_:km&ESOkKYDh.ٶ zXp^̸,ȿ}t'`^`D;W!RM\6N]3GW!T]ozmՄ}ދhkhzk ՆrPI_J9NϔC WPRM"h8DL 85¥ IsqQ:  KY2 $ O..辰R<.I/%\ɜ<{A,^zEZZ&s?55FWŏtqM~(J~L: 4AK&p//߫Y/!%)gwSNQ'Kw6Ι{}2-~i{ d}^)!_Qٱ3˗{9BrcRGQ !{;&EzGĎIRr_!6K-%^,Y,q޶x_JSC[uA(bL 606{獩5Ӹ4D4uUŞ1ZU=G|ȸ;wԡ6R'W㨏}B>x++gAצ{w6E^mx0o=T xGݟ'HL{HȏT{)a~YOlC(|;:7Cw՛9W_Df'HoTyʛQn;+X'țU hONJ bPԧ)0q *ur3&Z L] r sɧˏ: ~\k<f1=d7%y6eуCPf,|5u,x,:L ?c垤$R 4wlEO;NUwgyijtݥCFo4Ȧ(&gSȺf %8) +'w7lh\iL] nJfI$~1rxlV_ }I"JB\;*jŊ@=z_{OpX)gPBx)ӚE]~m  L9\tTI9qVWTL"ʂuEi @'D2S8˄d@c*601 gL{ݭgM#tOC|UPBt2z Ht cVRȾ4RQz쒂;L9g6QMYUjmmaئ;>yo[;z~L cOq-R蕸?nҔsoi:m?Qrݶmh.>myv9D4W;iAEĩ:4a֫ ؙ3/|ѮѲx )q?^WX*3=Lkؾ{OSVHLRxKF*$ӜgR.ƒN n˾T)N^XZog5^?[:EiE D@kiqzẌAxѨ*+>W{̷R%@EK'u{28De4PDS>ju AjJ(p[4R[]iD;kw5F2aV}>׈t{WS:|=x{asJ|V5]Wk3XY~fIQ" /9-{bfYTuvEt<RE>|+xgd BH>÷>|RRtQE?X' ږ%]SܨksRϲElFMT\tOmֹ72wwyqʤI̜Qq0%܋W ;}{NP=ZWF /U-ǛQ _4 <soD9578V̉@&~Q-a2A,АB$0p Bbň]9D)s>; izIfv@CI̻7DX0!$>HI2nQAL[V- ֗u7Bn ,4mTGwp=J'>`?¹,%|%2 '?@UiG,>wZ>[O"/DՅ4s _|"8G!{E=blݨ,t nҺ=G6LK+UD6MhJ!Bo{f"fOɠЪ ~4i~ۂk |t]F45steM?]g=PȠ<h5x)ßm@eܻhZ [.sA*,ǖP4 ohY?I).b «7~ʺAE]nL |?M̈́6܈! %7(*@+9ۗu)ix29CI  (F4(PӽXPwX4Cۄ*dlpL17Ђ-(}5(eH:m5LGPۡvQ AmH@> $ZP-ޣQb  dDF rl#uc RN4A]e(P 3(_F@\1'rw&}P DFJ@k!pwǭ/*ٙ󋮹EsaUPuhV.$XxnA_\;|]t8)aR[p4q=xϔ@nū;8j0\+nA~.F~Vx vGBu&HzTМՠ9fN/~AJh_* ;d& A(ȇ|ܻlJkx~F۶5WU[ 1#%jE[8 #SpDg<~C\R#T!o—5|||*ig{A?Fnp}UgF+0Y*TOy/iiL(%)y%+F a4"|<RN66zwCL|V"늢9|4iaU(a rgR([п@'atc!h-(֨a8Oڑ#R٠mMI؂G'cE  F׮ق)lATԢqn s҂T b5_B `w氀vti6S͚00!G7=/((T(ȥN<^^8p RjV#ϱ1 $!1`ؚc< IITf kEi9Ʉ1!ìh\0E,MW5F/{n_H.`DO4Qo4gڤӿ7C LEMpvS,R+vŖ[^Ϙ[ >{# `hy m/y!,U*x:x['!yRw-jdbV4vguY]PS7w͓~sx7<2" _4ec`ua[ Hqp엲x9CUA@ّ/rPɧTPT|Y;ޑvtpg Ȃ:c Fc,+":hP4d"XZXUQBHmoݚ/S7dQe6:qiB?_л Xίw+&vWDhRmf m~/ߛKUZwcg=A*$D'H U8f>>?NI?Ȧp=m߽[ٕC#!4g7@W:?ڌ', _iF8-}P6n|Ϻ%z<^uaR8BAӈ93Ld0PQ_ą/5>^"95B˗9tB@ᣚ؃oW\R!ȁøyWcJh@̅PG8 uq:֥Q-?KS݄Y+sF/QˡG5 N-tY5֯h2j[~èu}y2dz%V|wVW 4^*6hSaozo`HI5(r~|oցhD#B<[- ߑ\)ʠ6ſq[ۋu&C*ًRHؗ.t.8n0d&34՚4/Tk,\݃օ˿Zm KB{J1l_”w.sV4iu&~j2(.PnS m[+rCn3jSX g͡;tt M]&RI .2dv}@TMޝfޢKY,Tgv YN}8)Ln2rssɨQ%@jh_wmC#|)a K{q{Qժrϑ|mfpCd`%4vn#ۄ˄˗gPyFM ZY7wm LNzx0J` ͤpLQU.Ƥ;L$v{gAmqy"ݟ/p/7rg֞mNdv<1NڅL?5,R;^.07cxMny3$Ƃ+Tz0 Dǿ3(H8 ?ʜFI-W12v @ e͉yt9#ӠKH#@vnjZ/̆?gHY"t(Π.j0L+yF)IWk2:p :wٖH\y  nuuVif#5yERs,d*,X$м?dC_ ( ?GN\AG̝_۞Щ{F4# i`!{5i*DIRp!r[C !N7pw7V+$$2POAk#f ˗ =k0򱀳DZd"aPxp?6}bX땻=Xe<6esa:X.DqpD;)fH^kG?qeu*e^WVM'7EAD?8P!q|RNwOԞ(D..bƳ]NCM4Nߓ~Y\c//ͽ~sz@?:ZHEI_"LW})atAdpUҷ"f7Z~5eʤ'r&Ujk! zYcٽSJ?\~!R.Otlv߷ 8y >lS58Qd-j.{5,J=r:y´ЉfY؞bэci/>ԉ "wZJu12JdĤ`n7/.tA[n~A&GQ4C1EkM=Kj1uf!!D6'N:4ԫ!7_jn&Ht>$0?|1N̍hI >̾ݴ%Bn#S}3WW1.SgLj1 roOſқ*|Šۼt'dvac3lL$8l%j<^OOkCp-u>?!'z&+>WNaє16m6d!EѬ#IS|p&)ٿJhfʍ:r.BiXv0{~ɡsȞ:↘cvwW~oF{ދ eJ_L endstream endobj 14 0 obj <>stream PP'z$T^c Z_KuCAŴl痢5l>E5jtQC2DɈ+4_=NjCQQDdE\q4 o0ͅ5t?ع(D^F15U]]t>kmj(DIMں:Ykua8jCCZYzDM5ɸkډ i]ĕvl%/w%ng*aYAR5iwdDX!RuI)qǞn oHqc*b,[z"g(aKoW+e*~_?V:m߾5^w 2AKjguZ Ȉ-̇'VʋbN:Kr5XM)[%F;X Yh7"5V"%L(9K VLteG.Qt3:uSBP/^G-{ςB(G>#ǹ9j..7'9Vv 7Ը7eWV&K ) ial&Bg6F]qps?irW4vCC)182XLxGtASV$䍒ojn6ovX_v99UtY,:pA%#q=WG,2B1=fT ˊ4E(8 &vS$_5e MMB>Jhzՙغ_Wy8nevK.b#v.AGRgD4ؐGʵ(P Ad!D $>D4ܥTvF'^'HbIЧT}?˭[Y~FՇ8G똟8晃>Q?gO.?C E 6zd {u0{_8S qpGR~A2P0h:ACAs.EJ4T4r*{QŹ$/gAk\٘ĝ /%/yELv{'ˮ-9HlDˢ  1r)ǓҪA֣d%1^n \`$. @p@6GLZ6Su&kO]7Zd]-y`iم #0Х߼r IDD˒Eia=*0-Ƞ@se$ :RH@ <d`@@ f?5=RkX-WȃNh`ɑB j6( ڟ)u ]-ʊ2Tf'e k]BX +3MMsxdvѡ+X `NN@aal)]Elt0uW#!B`B8L`~ ( T0QH@C: ءN,(50H A0  8  x P H  `! %U!'%:j &9E]3Y*g07! '>{uƸ@bֲ csÞz ՠ;H<6U^٨j[]NRjUXKA>UԙQ9;QR Vx0DKL4;"RL F "pJ~t4P< yxf_6 D1!Ch -1.J06dxGl/'ٽ9$EvR<~Y SLkJ%CV-`z1>=(5Ls]V8V:6SF^kW|&q@=xZT#e<!vƹU+8=1w (80)JZr=l8ϟP@PeY˫WUWt*[>Ge&#$-^fL5bʦKL4 QPHo  ox0+"IW ^Gx0cT:D3iȣt0 Pu0Iau0P74p"i22PXRP2<f0#  5,6 5 \ w dA^ V1 RݥK`(QSIlΈ@%4׷^+2hptRUfqD哊IBt-]];$C>ּ5 3q,?ݢx?r-ĮhgbŎ@. X01:s_83*V[& z1>8Nbd(qsF'qV%d˔,p$E9O]fj, B&px-L 0&B,> ,鬹Q*Vuօ]hʪ{&s'%![J.I'ߘ&w1Umʙg2Sgӝpvh'MrX'Yn\'PФr(~4O'qPe¹eBxuyBP{cFXT25 +n+ףYEtF2' U䂦H*EQOtb+VKrJ}! s5DR7Aln.Zq'TrQhhV=o?y=\"$sǡ|+oJD.5U\5V{ ߉*X@+*bL᤹4N_Y^.qZmCT 2̈́jFnfV%2Tz/m为=Q2 I^*D>LkUe>l]75]DaWsŨ5Ԝg#cĦs޻/lnL5+>%\Xs:PkIoU䲵, :Y/S|Eb6[}DdՖTD]9ͥm{%Z}Ùf,sUL^v/x(]8AӃkdj뫰ȄJ>zEbF7XJeS3jUKK4?wUx5dTMUlUF#N/W;" @.`:m8sK"g2 0PahX|J~D"PUd>2t;jGջ41zlGuXh.c?żJ3b1.Mn8&䙔ClnVc2)UYzsgWN滢ԩ(R# I/V eҷDϸ1m%9oֱ1ziJ^?ͰJ'JU׳I\oC&/&4zd*C1`8Y^ mQ1zX4ԄuReT95c;TFߪHio*v^iMɎ-<lmٌ)Cpy-+wbc+~D'&g(UdV$tHj/i rU5'q*/:rN|J"\bFzO2UJ%;9z]G6w5eҕ3%(X㓞8EI{mMDһ9".Ҋ31eF(O>쵱R\O]hf&WT!ETĬvWMѸh-=~%1N\vHJm+]Ӄ䫃Vry]JɆdX)fK̞e d!7ҧ=^V$my垸5x-9!I !SrIT`V S݈јh#2P#$KΨY9c,)椌ⳚjU Ě?`QgUYpz&̨C˩ɚNj=M9OBPu78M.ޜO"%= 4hР @.  $X@ A>`H@ x@,9v \3H/ MUug%O< ;9AkR-t͔|Z%eb㡗V!a~矔XCUFeE!Tv}ErSD܅R 2SRO#4񍘵d3cÌfG(bRW4=CDJ;Sd GUx6Pe_-l&cWBǤ}A 48]X'CάG6Gerd:1XɋU#S ͕=ΤE+k) UxRGșO|'qC"Q}3m2b/m 4D8LP!")8H`Q95@@dB $4BIAʤ`I88E@5MP ^ (`!T`"0AE+LP5I $Ё`7?|p0&8Xp`|QWl; zCk6;Hb 8Kg*Qܸ1{8"1͜eȬw3 QB%9݋_mR *׽uuIPl\]ב ;|D)6fJGOi9WQ3̐ח w#ٌbg1Ga9*lCϜVCǏ*DmhL~KYNXʕgg^ ɂwGu2aejoxaHSqw9jiL+g9kŸՉdX|%#3#o'u!T6JfMqTknLc2NcNIy:+Qn NvJbs)>[AC6#)]n!> 闵q\K5~]^(#º(GJL.0ڄf1Ǚ͝p,uT4Ը\yjCsbvK5C>r=?)3[/9y%U]C"&!unHe2U:R+J~eՍ، Il"EUǻ9vTFx)tL>m^}wZVx5mJFՒI#;H'9`ȉ鈭ԉb#Xeq7,K}KBr$ў #g\T6f&Eհub蛈y4ciHC!E>1‰ZvcB*Kd^z]C盝 jR40v1\ϞMCE}Uc,mEotj7ʟὯnSJL3uA2S$>!ɶb@j!!\άh 2jA6;|a1)ӳ!^KIVH]OV3 bwLl 5d7pռzd Py/[rEfUjΟaܔE]qLtԔtVN?Չ Y[Cr&4"zdfΤ /"{؆ҘqY]/^:bRvtaH>eb"6m(NL+ljj2-) sNb(Dn'L/4r4!++r]LH.=k*MƩ#zv61Q o5J^(۩SnMM/>;jή9KS횙 Ipfz:v)$E~Y|- ٨<) #8 C"l<V(6*&d%A D@BQPs`R("m=#\gpfb h5x@?)> fz_=b0㐎,S ,頭+ibӪ"bL' z$|4x׬(@>,X2"C9&o ގXIag!J( yt-VYGYw<fc.F[M?}VD>[ cFoPр3ލ|;I$^I'5!Lz@9x [M CGhUYYL.BB 18C3IR kqA9Ϗ - t'|q(C#̈́Qޕvװ'6ۊ&W9y~R$_%)l$c)55`]Dr1etbZ4#!2uu$w\:JlFΌ'(uAi"?8h0> f݅ҙ-@e,B6Xʀud o0kWD"Bk/} ` ˖yxs+U#t߈%SϫF rPvup[䞯˿3ܱP~۳ ehyzDLgQ#0=t5߱ZtG *=jg 5扂2馭׸)(U6:),n-U3P;*Di3!:k⥚EKRmx}y ,3q']zW,Yw>EևkY [um1A,10{hDf0;ۅ؅~Đ~h栯,3h _(M:+taUzDD/Hs7Ev+B5z]w! ;D]6 v% 6օ6U.!]:?+XA\\$QEyZ 1.bvC\FjԬ-LqDkBL@If yE-Oynzδ(1G5Nh"7C.Bűw\{&@-5piZP)Hnjʟߓkٙ-6V#6wõU.sH6gh}BWcn Lg dub09ML/8aσ14>cI w/V ,~43LoM" ˠ S|"sI͐)>MͷBOk͖!)ZWRS>Z<2 JW>`emI4!6 1~1IA h#OŤ0~0`J29 .*EH,ag)kŌ]UVL )PׄvbzT5҄F2h߭P[rz"f{]c ,z@@Naõ=}x$a Ib'.kX[`i`ƫ  G% ƔjEE%f)"A-\mU}wb /ZN![z|4:͡.7aqǝIlW?fU:"5mbLB`ő2o٧B(; GDWSUE.8;#u!dIFn "Akt3 ǽeI%)}O18tDfds =(]w[aN: Ti鞚_Nd 5-<4*||OɳӟN82DLi(=c)?hT#h(v|)ԏywVpꕔp =ƺ%隬wL,*W[tA[U{@؝HPyJ-'uz5PtU!˞gykyMdgטjdYY;*˸sN:=+X9"%2op /Kl6_t[(j`92'X)P75 $gWj8 gHEWeMrCx.NĽ,#iҩ&n_X@Q;]ZR e,X@i 1n n+P2]CgjwwCl0.pXTuE:n GG Y~4=Pc.D{4c>6n1X4@ٯ=K Cd˝k|i">wcR}qZPq΋1Mٞ>Li4i&S<ϣW[A\GݍvgkgBM1m5~ޛ>4;P9,2ܭy=j6"vz]:gѐT"A*=4c(KA8y^.JTH58Nct4K#=$WiK/= B>am')3}$l) ֌LY-t4324M?GeXvlRΟ1擄zw/$dh{ 뤝Ju=[ȭ9.婮ȿ8$pLC˨>0ܠ:#C 5BkjԺ6Oh_d ,> RL;]i_} Xz]"~ Sq F=z$nqRh2Ug*Q2=&tu1v3>ľwF:kۆz5<h} &_Xœe#.i F+A굘6cUf P/q >ם,:&Mgf*bi5wܭ"^[Kn8Ĥ?0 >R„CuB}պ5\225%{xC^ Dǯm)Ae/VgVNRoOA=u \ۈh_5gE,׉oZ EJ#TiV-:E#5 1$!2ȃEgF!ⷊ:8H~B;[7eH mGj9u5+25e&]If萶VU"+$( }Y?*,Dr!H_Ԭ.<$ V5D5o׋?1̽e\.j=|MIvW9dԜ&"' hf`*m9AEkT@f9s49CӅYu&0 ?DΔ&9rr " ݘP)F(4.t;7 f#Dw3 8yzKPuq3o]"'PTV=_"]L(BO(w$W?3Ap+GS}h}I݃64!IǜZIpxgb'A Rb4&Яd$&PxT{YlP;%Zw@OV"5{%\ekU 9ѡWa$fv1|0i%ħ1:G2ސjrG}-fO,qQ|XHNdTٔ&?lGOp II2&l92R`XCjM@e0y.B-K{Ը_ ).%!pgB6mhBx(cN(6H\/&=J-j-wRFSb03*рcϷ%_mZfӏ?~N m洂6?|PV4iO0XʆLJ:3F޳eŁjf ;*ȟH\tqOFeO1uCSz0g+sPY9!!Fibc=Xd-qDy<;]3!S){&hS2HUhh=x\^< C~bqAgH镥ͫ@(_Umyş-SUv7ЭxkLE bi[4˗8ЃT|p~;b&dr HG\L!QkRU5d` o̠?wT'8-!50=[?e7+XԐ>kh F鉭n/j)Jps?1ͻMwĤ(Ҕ+Tbȏ C8 ҫbU7]K2nQSЕ&-A3%Ӵs l",uI<هU#u @TVJ.jWd0V%!ɱpP.U*`J~01aUop/x&)뻇;PA/济Lπ!$ڨhIe 7#HFۆVuC)0P=%P$uPP*s ~ [䩑@~8gQy%^E+HUh&qB4&*5+zj$>(PI SࠚU5R< ILDPڎG+1J7?zoS PO qgIRۤvEF+H6oPg6( |{*qxfL d7lL%4mغ7TRפ î 9Rʃ1~R&9 /OH"Ċxf 'mf%nV[q0tlCڹ]T>dރ˸5J_ܳkTj&Iq#-}e7`FU.#@\ż>A^r}Oӣt@CSCUu "~Nc'2NNT*h&&Pva _b/ֲ<@տ{t;t`=LB), Q]XbxKe&BR\ ! }nO[]#krTLJ`{RK 0_Kj? M'ktn>#N*^.#%p 4: /-n ߔ$:F0G_Q}jUS'AOG23 N)˲mime Sr{PdQFMM t`/wt{vQg;= }B)^ohG-C 5o5Xo `ܠ%>!# 솉ЇLĶSkßHHËq٥̣\*yӓК$! :39*p,mՃmLCN(`@jKUR xOt6Psf#-S5\jn/ dM_Dہ|7Cp '4ZXlծa)N<4>*RW@|4xqxY(w$37Ŝnĝm<5Q|HcmԀg ^uLR.W\#Et]z/A^%^(Z7 {K#(br<Gz'=SFDz 033iz<࠙yZxWH\L, 8~S:Ǭ>B2i_\Hm'Ly`4xIQI%t^9BP9lI2GHg~s-u`J.ZCt\(y oT"u97N=e|)M*#dߐ#fQzmK+KIbYR9FLѦo`8 tL 0TZ3hd^6)8yۅie2Ҭ`,kk67Dk0k=k)fX;0t4_W Ћ/SʭjBwLWF>yةb̈́N(;^hfĜ_Yty7->FQcxF u~4oe%ܬУyRX swSjRNe Z?A 5 Dk <1=G9B16düwͽk4JDC- ʈ| 'a1jv?\hQ΁3HRHÑ-'fi(i œ㑧Y=Guo2%Ktm\ѐTlKҽQx`$fP^ *% ssa1M4ʺҼ8klA5[eƦτZ/lH)v\{&'BMf}*FIcdG ;?pث ҄҇t/HZqjD 0PY L>Jgx#¾8(NsEkib]SE+c]`bF~χ^8pɍtiX+4NovH@ s *&te< RK$hVn#1^]Բo"&6789,y}]-N0n1Y}Y365Ijc=b%w9αt״{P̬dR2-?M(+"sEM$XZn&D *Eig{W-- K2E6#niK3ZXI7]4!eAhzDz \# Vd5;.ݩ-Ң'U-tQ ui,W(w>dPmÐpk@ÈH*ܠ>4(L@MLE{3Wo'`lp3czU$FN,yCg. H'.z&aQҿ#>Pnb^IIŊJb HVz u sq74sLjksЍVW-vy,ycxAM/vñn u>g*p %P]|b6(u}Sv+(:gMp-F d+WtMy "kBh MM{s8U{.(kpD9j~Im LxbHV9)h4G/F`wsW hޭjܮ/Юbߑ!im%fqrS9S(?>m!PZT,-%>֪1reaL[^T4eo);ƁM8lA,9y>$^bӽ<38P?h7S.g PWP BU'&hkIAla2 *6-dเH<*lH0\B:&̉#5 *+va2QK7}8Tc!J҈C{\^Yz1($jAUWꞜF&::T íTG7I+*Oѷb;KxPy4.x(]a B@zb/Xx_ 4"yM8PT[rhOsYoh*`O`ĽoqP3F{T* (A^ncb5\8h$5`wJk$e}2 k~=*Af'hjT:r%bOFd`晔iDcJ}YM^Fi%WXejBlJ6, gk9FO (/$gp?/,M%þ=֢',,3"c{78&!9M{.8}HyrXdb 5tצg6xg WHvREO aWӟj`-4жN#ZСoq!9Rc-J Wr Y8CY.2gMJ>ԑ+uR' 2qzDPhx0HTM^eP_ eP~~ JFaO*@\ R.CNB߅j9Vp'TdfACEGmK_pc"sxwBt@ @#%l3Xo?iɏ/X2g!s/Yb$\ݴo2N푆L'sU:<:MHsA"+dKD+Ra2 0K6J?D8PhBد`(c2V7dUL* '$W*Ś( Ӻ-`VèuJJMa4i "zaYRk^flҨ<|+m$bD ֑(K;, [++^-72DXLbUxòrQ~/zaUB:<7.T끀yўXMש/D3@$c9Qsf np {N.42C!I"/e(bƑ4<&r$T S˖-L+IJ-'p5Cmc8PYXL+~|~JڗTwWq7r,\ <-Qv=咨lnoJYUբF|!og}I\\hq$Q$r*x} ݭנ#w6( Ҿ̴: CYr,Usbr3uXBZ)}AfkdڍbXer‫wjxC]w .& T~ ~]%&|<&oU׫L(g'b ؙ2 jAPuBP%m1;LrsAJb*v;߱SK@n۵!B+ug~Q}P,ަi^ߡ mM+a#z4&Jyz5%2$&QIȂK1H|IK_"n u);f ` ӿFBoc }8e@p{}Mlcl[;__eeyY B[n_!Y)3ȧ-p5?{A:H>ЅosړiQ '0Al*™, v~ylx=#a_ȭlF-f2 J\\Ce6m݈x rZ%Bw~&&>gqSI |BFf7&d,CVk W;ȣ\T~FM #>vIm! j3hv%3]W~FRyw4I}AߘN O^eDNdgԟ;{$Xj`08\HʍK˝YPN y} ?OEĎph% 'X bɘ^Z|@亮 :6B!l0OBXHE*QO+Fe,O\.exo76~FKFR>:j_x \7-V%@4%TXmԑ1{*`I MƟKy&k' #_`M~&V4ˠ|"\M q(2ʰ|_<ȨRmZ-VJTwz$US%do)R (CJWCN6qE1 ^+: J K!ЋmG9[gcEoM1 FE삷tqVbdh8SSTxMq v|9j F?\#lW޵.Q]=O3~P}%'C&/g-4჻B9n"R9hrVgRw`]dZNM9\Pvł/0 $T sf )<2r祗#"00rITǝdvdQڰtCe_zڙrIG>?8-9ߔvo 8LQմ**PD&?tJB/mwÄ҇逸>J(8k #3TӀcIkaD ?`lH)|Oo)8L*>ͅ3LLU0jS_@ "/o(.aEe$_X9\Yj* -z(]_Ж|瞑^O)P "@=ѫN$>PEDc"Q}8`4ՋZRmTJ\ֆ #ՎʩsV1Ⅷni-wk-h9b̿GvJ1,;Qų( h1'P`S+XZ`3iˌ{n0}ycd!11ZKUVPH:5 96K z"*1 f_K2v-+c&4Qd9 Ψ9J\ I]!S,[]pȑJ(CavcD]g>0:8LMGZ pu) lx@Ce+W//U);7c..,4rߩ~_aOsx(wS8񌋫V*m|ISy8Oꈚ7*2GpYjd10NFR:.g:TPU) 9S t\\djqO"m9+Tt(lhyğV}B߈nU\6՚IsRAe;=w#`FhMVDd$|GRyCaC*$zh-#UXA a-Y}.EOj|5w$ْ).Y>_W$z#yC$K%{n&|w&(6/ҥ,pXUג#ݸ"y _tӯl-(n },WK3- e v2فTݨ$H4}WC&|vJP:Z$82iu}7`VCcR`{[k=o3_`],Pimée2I=pBM ] Cbdz0mG+J g-uk(f9| uڵ~8_`}pFdB>E92&եpapEJp.StlL0Z)Yܔ}c0O:B ENu%ڼ)|pgD LۙY!Dc} a@9µT3⪳@&hv),΍kQ]BR5ePk&Fv| dA{;+]=Yc+/\&)O\mv(eVn `V3 >C0Ʉ֣*5my,HytG R^UYaFG /4RYX6Q:10#loY2u:|W C4NFZF$ĈaWHNxS)Y鐃{wTh+fKdJ[b"'Z=;:فNaXju  QS( reƞMhN/W uo0L_h Z|)6~/ lPeW؈) wHURHN(𑪸؋}>0SÛH/"^\KQU.+$ۅХ<`MO-H<kp,6Dj9de ؞<| ՛3 Nkh:g,1SA#8O^m%t=a'_LBD<-61UG} 5 [f0{nAd`_rzN-VZZDt7N'Q?Zd-gZH#T7P EA fɭvk9$4+NǘLqŔyi=Sz͆(ՋWpJPalX lF;J繖ò5w)'$h;KVY|Os. l !AV=g ˡ'恸U%hs`^YlyH)/J$^ V' ';wRZ1)%Aꕚ2`oks'S<!Ue 0/Gt6]aPɄh)?/oq 2VUP%ڈZ@-Dd?$^! @R (}TpJ [m\[=(Scԫ*ppX♫?Ն{HBLAt[k>)-dIլ68 s0e:U)֜/~wx2 pMh*0caM-H?[;r"͹w+XŜȓd#|T˃ s"{ݑx\͛V PpJ,A% (_ 9y|Rr|tz_.AgPu Nγj$V[Ѻ?;̲3jw)|4rӉP//\sZ% ҷ9R҃*(sj;CzaDlV0jQ|Z~͞;#A];^~!3){2(S W*0Bl/d(m؂~Of"w,wmC#3A55FB9;$*`q$1BR 4d܂J 0,a5VPX=p>WpjZ:ܗHe'6?*/grɰ}lXCe Hv)kHNlvr\QuA"SL)8Id$N1j(xmgY2H@Sf 9$ _G,e+GAa~<^y_yרe((.vh>/,z*`{9ޫ+ٿPk Ą[.`:e4J/*P^G,%gZ6 ɖ<ǏJ Æp"bپ$xJ_A%[$PO9FM"ڊc]pVܧG~Pѿ.^%>.&)UJy\Ц4*!B-ap.nP[B ;}WMXF6UQhТ f?K7bĘ t#fPF~")mzd̴YkZV#,m[V9˵62G5#R5خeJV.5A5hAWIʨ9ޕwҩηhC't<:ҧek\(pj[R,r8.'xk !KDAM͡H~0Y閝S[o)fj*՟P\~E!D'aJbѮ\tc֘e i@YgO\ /~}K8ug[x_bW^XHz.WT0 = LL=)ܢI|tDv 0g-3lPR(IY"r \~QO] *~ko*DixqbܭR&nfa^- A]oawJLPUX=5)lo2Um̿`|7VH/ SqZ vO4I gU%{5SE%Wp,.o"+rY9M0&p>ѝ>S8B:![o"IK$Skl-W85,i<ǥjˣQoBO{T<ЂBYAotX%HDůQ.ڪ4-<8b( Tj}ӄ:uDAC b&--F/Qx!)E`g71n+zXSCuBVɄ8 xâ5dd5#[#WTE|[Fy^ݯu[:9@gjXD_Wk;:<#v)Yz?쾀M9 >9ru }:XcVKSE9&P,Ȭ> wljZj"<&M0[+xL٤ʡ$"|ʡ8W*ۄPp<>,~9}C␴vI J '<'LA~ged% H#]UBK!e(-n{՛1glSrX9Dcyzky(U ]rbS%'=/ˡt|pbKFuo( lr+$DV)~MO.PEWS8)'xiT dJ R(-ćCc®3<, endstream endobj 33 0 obj [/Indexed/DeviceRGB 255 34 0 R] endobj 34 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> endstream endobj 25 0 obj <> endobj 26 0 obj <> endobj 27 0 obj <> endobj 39 0 obj [/View/Design] endobj 40 0 obj <>>> endobj 37 0 obj [/View/Design] endobj 38 0 obj <>>> endobj 35 0 obj [/View/Design] endobj 36 0 obj <>>> endobj 23 0 obj <> endobj 24 0 obj <> endobj 42 0 obj <> endobj 43 0 obj <>stream H,TiTT}Mw? ViA^{^+ ***7 0(ز4t#AdFŸ nQAg93'IFL'߃М:Uw1"H7;YL=tӁ ɛԩqw<N$(%G턹 [#JɀC 5sX9s'F,yl MP3UjUV:>!MQU!*N}0=AKKWk.Q.{u^F1 $R -e~51"&I$J0isThnݰ8R|RTR JWQ% Ɏ7e2Py+pYd矎>{q =){Zº:rS' 7؍Qv xl^ Ա-C3Ha;n 0o jffp`i"")B"Xe.~uFv#@|Q0/WGMUW@(l2>H 9Ŀ Mwq:#Xv䝼|OojϨLK,Ve5O[{sPG(d&p pEtE)R  u-k=y융H Xls2b"WsvvIj0.~Fh H VzzTTTX)fr)tƴ(ѹHcOn[=CA ʋ /.",N3~ʕuT^i8k|.k.k3u;iAjjM^hZ8`{,P^F\V|0P_{2(J8 X*A.,x C9'YEB"s||ѭBp϶e1%rC!VJZ)!G6jqF Rtl8Pb_`Fslql0OZws!zTtԯ'a 72){<!jv(μ{ϽD0բ\Jz$Ed 9~b8j$Td)lY6Q:6[0i|^˰\ n@aM骒(!>ȫOb?%~(7#f/ڏ:nHIJ-hq/x=zRs\o3+ܥׇn]^np>u GP^Hg&7\ iϘROeeŴǣO;gj4- sqo` x3 gx͞s!fϭԾiA7"=VZ^m- /q+c$ۢ9'F"x)s*nS}x@#䅛w蒂.  DZ<)lS胵#1²;yM}ǀ^ơ{n+槣}˺yi("c+W߯NhMA%x=ZG]#p#$<-,t 7 5bɲ{E3 =LBϿ}GBǡnv:W}D5  l7TX8c[=#l ^g:Ctݏ7[C h],NOܜhK!P|l|*(ݻ&~rVb3sxKI:_o/#V >B0'NJ`W[ZP_աč9!8ΘIiUIEI%w]¹/m9,b=OH5pNMdDZ-Yɰt};fPfh Nh NdҜ[ 炱;ˠ늩ѭ]]'xOzmth^Fa,GNHf20@T9bC'@:jP}Pv2f|XcD*(F6)˘ƛk&vw?pG9-;]Wgdol|t@={_U$' $+@XV 559#N5`?#g*$ZEkZsdyk#o\p\gaEj $Z1L%*p[FfWqc}o!`O@ܦ $+β˪YMI'+3‹ $.3#I[vw)[\B):BOafllwoaS'o rr)s0o $$V#4C>C?qqonrnَCE $;DkE}- #  _  _ Gjs endstream endobj 41 0 obj <> endobj 44 0 obj <>stream HmL[uz-.ͽ9 Yg]L>Xh2^*[1kˠo cJ)EQ20&rt;9_Γ+ |CSpV@ z\`<#u:NIpzyه`J 9 <%%89pP.Tн{Pۀ6,rڵd5CG3h*|l^-G /y2G: A  D$ OGg]6_.:.̼i38ci\DފGeF4xMMd>?X `5^y_u pҝa~u]jPaCx.d|;Y7.N _}zL>@1 5H8F%T ^w9wR{0Lz> endobj 30 0 obj [/ICCBased 45 0 R] endobj 45 0 obj <>stream HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 N')].uJr  wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 28 0 obj [27 0 R 26 0 R 25 0 R] endobj 46 0 obj <> endobj xref 0 47 0000000004 65535 f 0000000016 00000 n 0000000175 00000 n 0000044064 00000 n 0000000000 00000 f 0000044115 00000 n 0000000000 00000 f 0000000000 00000 f 0000050420 00000 n 0000050492 00000 n 0000050709 00000 n 0000052342 00000 n 0000117931 00000 n 0000183520 00000 n 0000249109 00000 n 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000277546 00000 n 0000277765 00000 n 0000276985 00000 n 0000277056 00000 n 0000277127 00000 n 0000285295 00000 n 0000044573 00000 n 0000282602 00000 n 0000282489 00000 n 0000050012 00000 n 0000276410 00000 n 0000276458 00000 n 0000277430 00000 n 0000277461 00000 n 0000277314 00000 n 0000277345 00000 n 0000277198 00000 n 0000277229 00000 n 0000281182 00000 n 0000278107 00000 n 0000278449 00000 n 0000281485 00000 n 0000282637 00000 n 0000285334 00000 n trailer <<2DBD02D4FB1C614F9E8204F1A04AE2F7>]>> startxref 285542 %%EOF ================================================ FILE: .github/workflows/manual-publish-to-beta.yml ================================================ name: "publish" on: push: branches: - beta jobs: create-release: permissions: contents: write runs-on: ubuntu-latest outputs: release_id: ${{ steps.create-release.outputs.result }} steps: - uses: actions/checkout@v4 - name: setup node uses: actions/setup-node@v4 with: node-version: lts/* - name: get version run: echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV - name: create release id: create-release uses: actions/github-script@v6 with: script: | const { data } = await github.rest.repos.createRelease({ owner: context.repo.owner, repo: context.repo.repo, tag_name: `v${process.env.PACKAGE_VERSION}-beta`, name: `🧪 Electro v${process.env.PACKAGE_VERSION} Beta`, body: '', draft: true, prerelease: true }) return data.id build-tauri: needs: create-release permissions: contents: write strategy: fail-fast: false matrix: include: - platform: "windows-latest" args: "" runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 - name: setup node uses: actions/setup-node@v4 with: node-version: lts/* - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - name: install dependencies (ubuntu only) if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - name: install frontend dependencies run: npm install # change this to npm, pnpm or bun depending on which one you use. - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: releaseId: ${{ needs.create-release.outputs.release_id }} args: ${{ matrix.args }} publish-release: permissions: contents: write runs-on: ubuntu-latest needs: [create-release, build-tauri] steps: - name: publish release id: publish-release uses: actions/github-script@v6 env: release_id: ${{ needs.create-release.outputs.release_id }} with: script: | github.rest.repos.updateRelease({ owner: context.repo.owner, repo: context.repo.repo, release_id: process.env.release_id, }) ================================================ FILE: .github/workflows/manual-publish-to-full-release.yml ================================================ name: "publish" on: push: branches: - main jobs: create-release: permissions: contents: write runs-on: ubuntu-latest outputs: release_id: ${{ steps.create-release.outputs.result }} steps: - uses: actions/checkout@v4 - name: setup node uses: actions/setup-node@v4 with: node-version: lts/* - name: get version run: echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV - name: create release id: create-release uses: actions/github-script@v6 with: script: | const { data } = await github.rest.repos.createRelease({ owner: context.repo.owner, repo: context.repo.repo, tag_name: `v${process.env.PACKAGE_VERSION}`, name: `⚡Electro v${process.env.PACKAGE_VERSION}`, body: '', draft: true, prerelease: false }) return data.id build-tauri: needs: create-release permissions: contents: write strategy: fail-fast: false matrix: include: - platform: "windows-latest" args: "" runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 - name: setup node uses: actions/setup-node@v4 with: node-version: lts/* - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - name: install dependencies (ubuntu only) if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - name: install frontend dependencies run: npm install # change this to npm, pnpm or bun depending on which one you use. - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: releaseId: ${{ needs.create-release.outputs.release_id }} args: ${{ matrix.args }} publish-release: permissions: contents: write runs-on: ubuntu-latest needs: [create-release, build-tauri] steps: - name: publish release id: publish-release uses: actions/github-script@v6 env: release_id: ${{ needs.create-release.outputs.release_id }} with: script: | github.rest.repos.updateRelease({ owner: context.repo.owner, repo: context.repo.repo, release_id: process.env.release_id, }) ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Electro Thank you for your interest in contributing to Electro! This project is designed to be a great place for developers of all skill levels to get involved, whether you are just starting out or an experienced contributor. ## Getting Started 1. **Fork the Repository** – Click the 'Fork' button on the Electro repository to create your own copy. 2. **Clone Your Fork** – Clone the forked repository to your local machine: ```sh git clone https://github.com/your-username/Electro.git ``` 3. **Set Upstream Remote** – Add the original Electro repository as an upstream remote: ```sh git remote add upstream https://github.com/pTinosq/Electro.git ``` 4. **Create a Branch** – Work on your changes in a feature branch: ```sh git checkout -b feature/your-feature-name ``` ## Branching Strategy Electro follows a structured branching model to ensure stability across different versions: - **`main`** – The production branch containing stable, tested releases. - **`beta`** – The pre-release branch for features that have been sufficiently tested in `dev`. - **`dev`** – The active development branch where all changes are initially merged. Once changes are tested and stable, they move to `beta`. When `beta` is confirmed to be issue-free, it gets merged into `main`. ### Where to Target Your PR - All changes should be based on `dev`. - Once tested and confirmed stable, changes from `dev` will be merged into `beta`. - `main` only receives changes after they have been verified in `beta`. ## Submitting a Pull Request 1. **Ensure Your Code is Up-to-Date** ```sh git fetch upstream git checkout dev git merge upstream/dev ``` 2. **Commit Your Changes** ```sh git commit -m "Add feature: description here" ``` 3. **Push Your Changes** ```sh git push origin feature/your-feature-name ``` 4. **Open a Pull Request** – Navigate to the Electro repository, and open a pull request against `dev`. ## Code Guidelines - Keep your code clean and well-documented. - Follow the existing code style and structure. - Ensure your changes do not break existing functionality. ## Feedback & Discussion If you are unsure about anything, feel free to open a discussion or issue before making changes. We encourage collaboration and want to make contributing as smooth as possible! Thank you for your interest in Electro! We look forward to your contributions. 🚀 ================================================ FILE: 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 . ================================================ FILE: README.md ================================================
# Electro - A lightweight & blazingly-fast image viewer Electro is no longer maintained and has been archived as of 29/06/2025. ## Why is Electro being archived? The primary goal of Electro was to build an image viewer which aligns with the following two core principles: 1. Be as fast as you can be 2. Be free and open source. Forever. I spent months experimenting with different languages - Go, C++, C#, Rust, TypeScript - chasing performance gains and refining things down to the millisecond. After three months of building in private, I realised I should probably find out whether anyone actually wanted what I was making. So I pushed myself to strip things back, build a super minimal version of Electro, and just release it. To my surprise, the response was immediate. Within days, hundreds of people across the world were using Electro, sharing kind words, mentioning issues, and contributing feedback. That support meant the world to me, and it gave me the motivation to dive deeper into GPU rendering, Rust internals, and low-level image processing. Then finals arrived. I had to put everything on hold. Every waking hour went into my final university project or prepping for exams. I told myself I'd return to Electro once things settled down. Exams ended, and I found myself with one month of freedom before starting a new job. I knew it was going to be intense, so instead of burning that time on a side project, I chose to travel, spend time with friends, and enjoy the last chapter of uni life. No regrets :) Fast forward to now - I've started work, my days are completely packed, and I've had to accept that keeping Electro alive just isn't feasible right now. I told myself that I would pick it back up on weekends, but today, as I sat down to revisit the codebase, I came across [this Reddit post](https://www.reddit.com/r/rust/comments/1jhecge/i_built_a_gpuaccelerated_image_viewer_with_iced/) by [ggand0](https://github.com/ggand0). Three months ago, right around the time I paused development, he released ViewSkater - a GPU-accelerated image viewer that is blazingly fast, cross-platform, free, and open source. In short, it's everything I wanted Electro to be. Rather than trying to compete or one-up an already incredible project, I'd rather acknowledge great work when I see it and point Electro's users in the right direction. [ViewSkater](https://viewskater.com/) is the real deal, and I'm genuinely happy to see someone else push this vision further than I had the chance to. Thanks to everyone who supported Electro. It was a blast while it lasted. ViewSkater to the moon! 🚀🚀🚀 ## So what's next? Well, ideally the company I work for exits, I become a trillionaire, and I donate all my money to [Battersea Dogs and Cats Home](https://www.battersea.org.uk/). I'm kidding (or am I?). There's nothing planned for Electro, but if you're looking for a blazing-fast image viewer to use or contribute to, head over to [ViewSkater's repo](https://github.com/ggand0/viewskater/). It deserves your attention. ================================================ FILE: biome.json ================================================ { "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, "files": { "ignoreUnknown": false, "ignore": [] }, "formatter": { "enabled": true, "indentStyle": "tab" }, "organizeImports": { "enabled": true }, "linter": { "enabled": true, "rules": { "recommended": true, "a11y": { "all": false } } }, "javascript": { "formatter": { "quoteStyle": "double" } } } ================================================ FILE: index.html ================================================ Electro
================================================ FILE: package.json ================================================ { "name": "Electro", "private": true, "version": "0.6.3", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "tauri": "tauri", "format": "biome format --write ./src", "lint": "biome lint ./src", "vb": "node utils/vb.js" }, "dependencies": { "@reduxjs/toolkit": "^2.5.0", "@tauri-apps/api": "^2", "@tauri-apps/plugin-cli": "^2.0.0", "@tauri-apps/plugin-fs": "^2.2.0", "@tauri-apps/plugin-shell": "^2", "preact": "^10.26.2", "redux": "^5.0.1", "zustand": "^5.0.3" }, "devDependencies": { "@babel/plugin-transform-react-jsx": "^7.25.9", "@biomejs/biome": "1.9.4", "@preact/preset-vite": "^2.10.1", "@tauri-apps/cli": "^2", "typescript": "^5.2.2", "vite": "^5.3.1" } } ================================================ FILE: src/App.tsx ================================================ import { useEffect } from "preact/hooks"; import Canvas from "./components/canvas/Canvas"; import { listen } from "@tauri-apps/api/event"; import { useImageStore } from "./stores/useImageStore"; import { convertFileSrc, invoke } from "@tauri-apps/api/core"; import "./styles/normalize.css"; import "./styles/global.css"; import Terminal from "./components/terminal/Terminal"; import { useTerminalStore } from "./stores/useTerminalStore"; import { normalizeFilePath } from "./utils/normalizeFilePaths"; import { resolveResource } from '@tauri-apps/api/path'; const DEV_DEFAULT_IMAGE_PATH = "/src/assets/electro-default.jpg"; const DEFAULT_IMAGE_PATH = "assets/electro-default.jpg"; const IS_DEV_MODE = import.meta.env.DEV; interface DragDropEvent { payload: { paths: string[]; position: { x: number; y: number; }; }; } interface ImageSourceEvent { payload: { source: { value: string; }; }; } export default function App() { const { loadedImage, setLoadedImage, setDefaultSrc, loadSiblingImagePaths } = useImageStore(); const { setCwd } = useTerminalStore(); useEffect(() => { // Load the default Electro image on mount const loadImage = async (path: string) => { const img = new Image(); img.src = path; img.onload = () => { setLoadedImage(img) }; }; // Load the default image if (IS_DEV_MODE) { loadImage(DEV_DEFAULT_IMAGE_PATH); } else { resolveResource(DEFAULT_IMAGE_PATH).then((path) => { loadImage(convertFileSrc(path)); }); } // Start up the drag-drop listener listen("tauri://drag-drop", (event) => { const dragDropEvent = event as DragDropEvent; const filePath = normalizeFilePath(dragDropEvent.payload.paths[0]); const fileDirectory = filePath.split("/").slice(0, -1).join("/"); loadSiblingImagePaths(fileDirectory); setCwd(fileDirectory); setDefaultSrc(filePath); loadImage(convertFileSrc(filePath)); }); // This listener is for when Electro is opened from the command line w/ an image path as the argument listen("image-source", (event: ImageSourceEvent) => { const filePath = normalizeFilePath(event.payload.source.value); if (!filePath) return; const fileDirectory = filePath.split("/").slice(0, -1).join("/"); loadSiblingImagePaths(fileDirectory); setDefaultSrc(filePath); loadImage(convertFileSrc(filePath)); }).then(() => { invoke("on_image_source_listener_ready"); }); }, [setDefaultSrc, setLoadedImage, setCwd, loadSiblingImagePaths]); return ( <> ); } ================================================ FILE: src/commands/CLICommand.ts ================================================ export default class CLICommand { name: string; description: string; commandString: string; callback: (canExecute: boolean, ...args: string[]) => void; when: () => boolean; constructor( name: string, description: string, commandString: string, callback: (isAllowed: boolean, ...args: string[]) => void, when: () => boolean, ) { this.name = name; this.description = description; this.commandString = commandString; this.callback = callback; this.when = when; } execute(...args: string[]) { const isAllowed = this.when(); this.callback(isAllowed, ...args); } } ================================================ FILE: src/commands/CLICommandCategory.ts ================================================ import type CLICommand from "./CLICommand"; export default class CLICommandCategory { private category: string; private commands: CLICommand[]; constructor(category: string, commands: CLICommand[]) { this.category = category; this.commands = commands; } public getCommands(): CLICommand[] { return this.commands; } public getCategory(): string { return this.category; } } ================================================ FILE: src/commands/CommandRegistry.ts ================================================ import { electroCommandsCategory } from "../components/terminal/commands/electroCommands"; import { imageCommandsCategory } from "../components/terminal/commands/imageCommands"; import { terminalCommandsCategory } from "../components/terminal/commands/terminalCommands"; import type CLICommand from "./CLICommand"; import type CLICommandCategory from "./CLICommandCategory"; export default class CommandRegistry { private static instance: CommandRegistry; private commands: Map; public static allCommands: CLICommandCategory[] = [ terminalCommandsCategory, electroCommandsCategory, imageCommandsCategory, ]; private constructor() { this.commands = new Map(); } static getInstance(): CommandRegistry { if (!CommandRegistry.instance) { CommandRegistry.instance = new CommandRegistry(); } return CommandRegistry.instance; } addCommand(command: CLICommand): void { if (this.commands.has(command.commandString)) { console.warn( `Command "${command.commandString}" already exists and will be overwritten.`, ); } this.commands.set(command.commandString, command); } removeCommand(commandString: string): void { if (!this.commands.has(commandString)) { throw new Error(`Command "${commandString}" not found.`); } this.commands.delete(commandString); } getCommand(commandString: string): CLICommand | undefined { return this.commands.get(commandString); } listCommands(): CLICommand[] { return Array.from(this.commands.values()); } autocompleteCommand(commandString: string): string[] { return Array.from(this.commands.keys()).filter((key) => key.startsWith(commandString), ); } public loadCommands() { for (const commandCategory of CommandRegistry.allCommands) { for (const command of commandCategory.getCommands()) { this.addCommand(command); } } } } ================================================ FILE: src/components/canvas/Canvas.tsx ================================================ import { useEffect, useRef, useState, useCallback } from "preact/hooks"; import { drawImageToCanvas, fitImageToCanvas } from "./canvasUtils"; import { DEFAULT_IMAGE_TRANSFORM, type ImageTransform } from "./ImageTransform"; interface CanvasProps { image: HTMLImageElement | null; } export default function Canvas({ image }: CanvasProps) { const canvasRef = useRef(null); const ZOOM_SENSITIVITY = 0.001; const [_transform, setTransform] = useState( DEFAULT_IMAGE_TRANSFORM, ); const [isDragging, setIsDragging] = useState(false); const lastMousePos = useRef({ x: 0, y: 0 }); // Runs once when image changes (initial setup) useEffect(() => { const canvas = canvasRef.current; if (!image || !canvas) return; const context = canvas.getContext("2d"); if (!context) throw new Error("Failed to get canvas rendering context"); // Set canvas size to current window dimensions canvas.width = window.innerWidth; canvas.height = window.innerHeight; // Fit the image and set initial transform state const initialTransform = fitImageToCanvas(canvas, image); // Dynamically adjust zoom limits based on image size const minSize = Math.min(image.width, image.height, 10); const maxSize = Math.max(image.width * 50, image.height * 50); setTransform({ ...initialTransform, minWidth: minSize, minHeight: minSize, maxWidth: maxSize, maxHeight: maxSize, }); // Draw the image with the initial transform drawImageToCanvas(canvas, image, initialTransform); }, [image]); // Redraw on window resize while preserving current transform useEffect(() => { function handleResize() { if (!canvasRef.current || !image) return; const canvas = canvasRef.current; canvas.width = window.innerWidth; canvas.height = window.innerHeight; // Redraw using the existing transform so the image isn’t stretched setTransform((prev) => { const newTransform = { ...prev }; drawImageToCanvas(canvas, image, newTransform); return newTransform; }); } window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, [image]); // Handle mouse down const onMouseDown = useCallback( (event: MouseEvent) => { switch (event.button) { case 0: setIsDragging(true); lastMousePos.current = { x: event.clientX, y: event.clientY }; break; case 1: { // Reset transform on middle-click event.preventDefault(); const canvas = canvasRef.current; if (!canvas || !image) return; const context = canvas.getContext("2d"); if (!context) throw new Error("Failed to get canvas rendering context"); // Set canvas size canvas.width = window.innerWidth; canvas.height = window.innerHeight; // Fit the image again const initialTransform = fitImageToCanvas(canvas, image); // Update transform limits const minSize = Math.min(image.width, image.height, 10); const maxSize = Math.max(image.width * 50, image.height * 50); setTransform({ ...initialTransform, minWidth: minSize, minHeight: minSize, maxWidth: maxSize, maxHeight: maxSize, }); drawImageToCanvas(canvas, image, initialTransform); break; } } }, [image], ); // Handle mouse move (panning) const onMouseMove = useCallback( (event: MouseEvent) => { if (!isDragging || !canvasRef.current || !image) return; const dx = event.clientX - lastMousePos.current.x; const dy = event.clientY - lastMousePos.current.y; lastMousePos.current = { x: event.clientX, y: event.clientY }; setTransform((prev) => { const newTransform = { ...prev, x: prev.x + dx, y: prev.y + dy }; if (!canvasRef.current) return newTransform; drawImageToCanvas(canvasRef.current, image, newTransform); return newTransform; }); }, [isDragging, image], ); const onMouseUp = useCallback(() => setIsDragging(false), []); // Handle zoom on scroll const onWheel = useCallback( (event: WheelEvent) => { if (!canvasRef.current || !image) return; event.preventDefault(); const delta = -event.deltaY * ZOOM_SENSITIVITY; const zoomFactor = Math.exp(delta); const rect = canvasRef.current.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top; setTransform((prev) => { const newWidth = prev.width * zoomFactor; const newHeight = prev.height * zoomFactor; if ( newWidth < (prev.minWidth ?? 0) || newWidth > (prev.maxWidth ?? Number.POSITIVE_INFINITY) ) { // Don’t zoom beyond min/max return prev; } const newX = mouseX - ((mouseX - prev.x) * newWidth) / prev.width; const newY = mouseY - ((mouseY - prev.y) * newHeight) / prev.height; const newTransform = { ...prev, x: newX, y: newY, width: newWidth, height: newHeight, }; if (!canvasRef.current) return newTransform; drawImageToCanvas(canvasRef.current, image, newTransform); return newTransform; }); }, [image], ); // Attach event listeners for mouse and wheel useEffect(() => { window.addEventListener("mousemove", onMouseMove); window.addEventListener("mouseup", onMouseUp); window.addEventListener("wheel", onWheel, { passive: false }); return () => { window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); window.removeEventListener("wheel", onWheel); }; }, [onMouseMove, onMouseUp, onWheel]); return ; } ================================================ FILE: src/components/canvas/ImageTransform.ts ================================================ export interface ImageTransform { x: number; y: number; width: number; height: number; offsetX: number; offsetY: number; minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number; } export const DEFAULT_IMAGE_TRANSFORM: ImageTransform = { x: 0, y: 0, width: 0, height: 0, offsetX: 0, offsetY: 0, minWidth: 0, minHeight: 0, maxWidth: 0, maxHeight: 0, }; ================================================ FILE: src/components/canvas/canvasUtils.ts ================================================ import type { ImageTransform } from "./ImageTransform"; export function drawImageToCanvas( canvas: HTMLCanvasElement, image: HTMLImageElement, transform: ImageTransform, ) { const context = canvas.getContext("2d"); if (!context) throw new Error("Failed to get canvas rendering context"); context.imageSmoothingEnabled = false; context.clearRect(0, 0, canvas.width, canvas.height); context.resetTransform(); context.translate(transform.offsetX, transform.offsetY); // Draw the image using the transformed position and size context.drawImage( image, transform.x, transform.y, transform.width, transform.height, ); } export function fitImageToCanvas( canvas: HTMLCanvasElement, image: HTMLImageElement, ): ImageTransform { // Calculate scaling factors const widthScale = canvas.width / image.width; const heightScale = canvas.height / image.height; // Choose the smaller scale to maintain aspect ratio const scale = Math.min(widthScale, heightScale, 1); const width = image.width * scale; const height = image.height * scale; // Center the image on the canvas const x = (canvas.width - width) / 2; const y = (canvas.height - height) / 2; return { x, y, width, height, offsetX: 0, offsetY: 0, }; } ================================================ FILE: src/components/terminal/Terminal.tsx ================================================ import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import "./styles.css"; import { useTerminalStore } from "../../stores/useTerminalStore"; import CommandRegistry from "../../commands/CommandRegistry"; import { parseCommandInput } from "../../utils/parseCommandInput"; export default function Terminal() { const { addHistory, history, isTerminalOpen, cwd, setIsTerminalInputFocused, } = useTerminalStore(); const [_historyIndex, setHistoryIndex] = useState(-1); const inputRef = useRef(null); const terminalHistoryRef = useRef(null); useEffect(() => { if (isTerminalOpen) { inputRef.current?.focus(); } }, [isTerminalOpen]); // biome-ignore lint/correctness/useExhaustiveDependencies: we want this to run when history updates (biome is not smart enough to know this) useEffect(() => { // Ensure scroll only happens when history updates if (terminalHistoryRef.current) { terminalHistoryRef.current.scrollTo({ top: terminalHistoryRef.current.scrollHeight, }); } }, [history]); const handleHistoryNavigation = useCallback( (direction: "up" | "down") => { const inputHistory = history.filter((entry) => entry.type === "input"); if (inputHistory.length === 0) return; setHistoryIndex((prev) => { const newIndex = direction === "up" ? Math.min(prev + 1, inputHistory.length - 1) : Math.max(prev - 1, -1); const historyEntry = inputHistory[inputHistory.length - (newIndex + 1)]; if (historyEntry && inputRef.current) { inputRef.current.value = historyEntry.value.split("> ")[1]; } else if (newIndex === -1 && inputRef.current) { inputRef.current.value = ""; } return newIndex; }); }, [history], ); const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case "Enter": { const inputValue = inputRef.current?.value.trim(); const inputTokens = inputValue ? parseCommandInput(inputValue) : []; const formattedInputValue = `${cwd}> ${inputValue}`; addHistory({ type: "input", value: formattedInputValue }); if (inputRef.current) { inputRef.current.value = ""; } const commandToken = inputTokens[0]; if (commandToken) { const command = CommandRegistry.getInstance().getCommand(commandToken); if (command) { command.execute(...inputTokens.slice(1)); } else { addHistory({ type: "output", value: `Command not found: ${commandToken}`, }); } } break; } case "ArrowUp": { e.preventDefault(); handleHistoryNavigation("up"); break; } case "ArrowDown": { e.preventDefault(); handleHistoryNavigation("down"); break; } case "Tab": { e.preventDefault(); // For now we'll only provide autocomplete for the first token const inputValue = inputRef.current?.value.trim(); const inputToken = inputValue?.split(" ")[0]; if (inputToken) { const commands = CommandRegistry.getInstance().autocompleteCommand(inputToken); if (commands.length === 1 && inputRef.current) { inputRef.current.value = commands[0]; } else if (commands.length > 1) { addHistory({ type: "output", value: `${commands.join(", ")}` }); } } } } }; const handleTerminalClick = (_: MouseEvent) => { // Prevent focus if text is being selected if (window.getSelection()?.toString().length) { return; } inputRef.current?.focus(); }; return isTerminalOpen ? (
{history.map((line) => (
{line.value}
))}
{cwd}>  setIsTerminalInputFocused(false)} onFocus={() => setIsTerminalInputFocused(true)} />
) : null; } ================================================ FILE: src/components/terminal/commands/electroCommands.tsx ================================================ import { invoke } from "@tauri-apps/api/core"; import CLICommand from "../../../commands/CLICommand"; import CommandRegistry from "../../../commands/CommandRegistry"; import { useTerminalStore } from "../../../stores/useTerminalStore"; import CLICommandCategory from "../../../commands/CLICommandCategory"; // These commands control the Electro app export const electroCommands = [ new CLICommand( "Quit", "Quits Electro", "quit", async () => { await invoke("exit_app"); }, () => true, ), new CLICommand( "Help", "Displays help information", "help", async (_, token) => { if (token) { // Return help for a specific command const command = CommandRegistry.getInstance().getCommand(token); if (command) { useTerminalStore.getState().addHistory({ type: "output", value: `${command.name} (${command.commandString})`, }); useTerminalStore.getState().addHistory({ type: "output", value: `${command.description}`, }); } else { useTerminalStore.getState().addHistory({ type: "output", value: `Command '${token}' not found`, variant: "error", }); } } else { // Return a list of all commands const allCommands = CommandRegistry.allCommands; for (const category of allCommands) { useTerminalStore.getState().addHistory({ type: "output", value: `${category.getCategory()} commands`, variant: "success", }); for (const command of category.getCommands()) { useTerminalStore.getState().addHistory({ type: "output", value: `${command.commandString} - ${command.description}`, }); } } } }, () => true, ), new CLICommand( "Version", "Displays the version of Electro", "version", async () => { const version = await invoke("get_version"); useTerminalStore.getState().addHistory({ type: "output", value: `Electro version: ${version}`, }); }, () => true, ), ]; export const electroCommandsCategory = new CLICommandCategory( "Electro", electroCommands, ); ================================================ FILE: src/components/terminal/commands/imageCommands.tsx ================================================ import CLICommand from "../../../commands/CLICommand"; import { useTerminalStore } from "../../../stores/useTerminalStore"; import { convertFileSrc } from "@tauri-apps/api/core"; import { useImageStore } from "../../../stores/useImageStore"; import CLICommandCategory from "../../../commands/CLICommandCategory"; import { normalizeFilePath } from "../../../utils/normalizeFilePaths"; export const imageCommands = [ new CLICommand( "Load Image", "Loads an image from the specified file path. Usage: load ", "load", async (_isAllowed, filePath) => { if (!filePath || filePath.trim() === "") { useTerminalStore.getState().addHistory({ type: "output", value: "Usage: load ", variant: "warn", }); return; } try { // Determine if the path is a URL or local file const isRemote = filePath.startsWith("http://") || filePath.startsWith("https://"); // Normalize the file path let normalizedFilePath: string; if (isRemote) { normalizedFilePath = filePath; } else { normalizedFilePath = normalizeFilePath(filePath); } const image = new Image(); if (isRemote) { image.src = normalizedFilePath; } else { image.src = convertFileSrc(normalizedFilePath); } image.onload = async () => { useImageStore.getState().setDefaultSrc(normalizedFilePath); useTerminalStore.getState().addHistory({ type: "output", value: `Image loaded from '${normalizedFilePath}'`, variant: "success", }); useImageStore.getState().setLoadedImage(image); if (!isRemote) { // Set the current working directory to the directory of the loaded file const cwd = normalizedFilePath.split("/").slice(0, -1).join("/"); useImageStore.getState().loadSiblingImagePaths(cwd); useTerminalStore .getState() .setCwd(normalizedFilePath.split("/").slice(0, -1).join("/")); } }; image.onerror = () => { useTerminalStore.getState().addHistory({ type: "output", value: `Error loading image from '${normalizedFilePath}'`, variant: "error", }); }; } catch (error) { if (error instanceof Error) { useTerminalStore.getState().addHistory({ type: "output", value: `Error loading image: ${error.message}`, variant: "error", }); console.error("Error loading image:", error); } else { useTerminalStore.getState().addHistory({ type: "output", value: "Error loading image: Unknown error", variant: "error", }); console.error("Error loading image: Unknown error", error); } } }, () => true, ), ]; export const imageCommandsCategory = new CLICommandCategory( "Image", imageCommands, ); ================================================ FILE: src/components/terminal/commands/terminalCommands.tsx ================================================ import CLICommand from "../../../commands/CLICommand"; import { invoke } from "@tauri-apps/api/core"; import { useTerminalStore } from "../../../stores/useTerminalStore"; import CLICommandCategory from "../../../commands/CLICommandCategory"; import { normalizeFilePath } from "../../../utils/normalizeFilePaths"; export const terminalCommands = [ new CLICommand( "Close terminal", "Closes the terminal", "close", () => { useTerminalStore.getState().setIsTerminalOpen(false); }, () => { return useTerminalStore.getState().isTerminalOpen; }, ), new CLICommand( "Clear terminal", "Clears all terminal history", "clear", () => { useTerminalStore.getState().clearHistory(); }, () => true, ), new CLICommand( "Get current working directory", "Get the current working directory", "cwd", async () => { const state = useTerminalStore.getState(); state.addHistory({ type: "output", value: `Current working directory: ${state.cwd}`, }); }, () => true, ), new CLICommand( "Launch file explorer", "Launches the file explorer in the specified directory. Usage: explorer ", "explorer", (_, path) => { invoke("open_file_explorer", { path }); }, () => true, ), new CLICommand( "Change directory", "Changes the current working directory. Usage: cd ", "cd", async (_, path) => { const store = useTerminalStore.getState(); if (!path) { store.addHistory({ type: "output", value: "Error: No path provided", }); return; } try { const newPath = (await invoke("change_cwd", { path })) as string; const normalizedNewPath = normalizeFilePath(newPath); store.setCwd(normalizedNewPath); } catch (error) { store.addHistory({ type: "output", value: `Error: ${error}`, variant: "error", }); } }, () => true, ), new CLICommand( "List directory contents", "List the contents of the specified directory. Usage: ls ", "ls", async (_, path = ".") => { const dirs = (await invoke("list_directory", { path })) as string[]; useTerminalStore.getState().addHistory({ type: "output", value: dirs.join(", "), }); }, () => true, ), ]; export const terminalCommandsCategory = new CLICommandCategory( "Terminal", terminalCommands, ); ================================================ FILE: src/components/terminal/styles.css ================================================ #terminal { display: flex; flex-direction: column; position: fixed; height: 200px; min-height: 75px; width: 100%; overflow: auto; background: black; color: white; font-family: monospace; padding: 10px; padding-bottom: 0; box-sizing: border-box; } .terminal-line { display: flex; width: 100%; border-top: 1px dotted white; } #terminal-input { flex: 1; border: none; background: black; color: white; outline: none; margin-top: auto; font-size: 12px; } #terminal-path { font-size: 12px; } #terminal-history { display: flex; flex-direction: column; width: 100%; height: 100%; overflow: auto; font-size: 12px; color: #51ff51; } .terminal-history-default { color: #e0e0e0; } .terminal-history-success { color: #51ff51; } .terminal-history-warn { color: #ffcc51; } .terminal-history-error { color: #ff5151; } /* If the screen gets too small place the terminal input area beneath the path */ @media (max-width: 500px) { .terminal-line { flex-direction: column; align-items: flex-start; } #terminal-input { width: 100%; margin-top: 5px; } } ================================================ FILE: src/keybinds/Keybind.ts ================================================ export default class Keybind { id: string; name: string; shortcut: Uppercase; callback: ( canExecute: boolean, event: KeyboardEvent, ...args: string[] ) => void; when: () => boolean; constructor( id: string, name: string, shortcut: Uppercase, callback: ( isAllowed: boolean, event: KeyboardEvent, ...args: string[] ) => void, when: () => boolean, ) { this.id = id; this.name = name; this.shortcut = shortcut; this.callback = callback; this.when = when; } execute(event: KeyboardEvent, ...args: string[]) { const isAllowed = this.when(); this.callback(isAllowed, event, ...args); } } ================================================ FILE: src/keybinds/KeybindRegistry.ts ================================================ import type Keybind from "./Keybind"; import { imageKeybinds } from "./keybinds/imageKeybinds"; import { terminalKeybinds } from "./keybinds/terminalKeybinds"; export default class KeybindRegistry { private static instance: KeybindRegistry; private keybinds: Map; public static allKeybinds: Keybind[] = [ ...terminalKeybinds, ...imageKeybinds, ]; private constructor() { this.keybinds = new Map(); } static getInstance(): KeybindRegistry { if (!KeybindRegistry.instance) { KeybindRegistry.instance = new KeybindRegistry(); } return KeybindRegistry.instance; } addKeybind(keybind: Keybind): void { if (this.keybinds.has(keybind.shortcut)) { throw new Error(`Keybind "${keybind.shortcut}" already exists.`); } this.keybinds.set(keybind.shortcut, keybind); } removeKeybind(shortcut: string): void { if (!this.keybinds.has(shortcut)) { throw new Error(`Keybind "${shortcut}" not found.`); } this.keybinds.delete(shortcut); } getKeybind(shortcut: string): Keybind | undefined { return this.keybinds.get(shortcut); } listKeybinds(): Keybind[] { return Array.from(this.keybinds.values()); } private handleKeyPress(event: KeyboardEvent) { const shortcut = this.normalizeShortcut(event); const keybind = this.keybinds.get(shortcut); if (keybind) { keybind.execute(event); } } public registerListener(): void { window.addEventListener("keydown", (event) => this.handleKeyPress(event)); } public loadKeybinds(): void { for (const keybind of KeybindRegistry.allKeybinds) { this.addKeybind(keybind); } } private normalizeShortcut(event: KeyboardEvent): string { const keys = []; if (event.ctrlKey) keys.push("CTRL"); if (event.shiftKey) keys.push("SHIFT"); if (event.altKey) keys.push("ALT"); if (event.metaKey) keys.push("META"); // all keys are in uppercase const key = event.key.toUpperCase(); keys.push(key); return keys.join("+"); } } ================================================ FILE: src/keybinds/keybinds/imageKeybinds.ts ================================================ import { convertFileSrc } from "@tauri-apps/api/core"; import { useImageStore } from "../../stores/useImageStore"; import Keybind from "../Keybind"; import { useTerminalStore } from "../../stores/useTerminalStore"; export const imageKeybinds = [ new Keybind( "image.previous", "Previous image", "ARROWLEFT", (isAllowed) => { if (!isAllowed) return; const previousImage = useImageStore .getState() .siblingImagePaths.previous(); if (previousImage) { const image = new Image(); image.src = convertFileSrc(previousImage); image.onload = () => { useImageStore.getState().setDefaultSrc(previousImage); useImageStore.getState().setLoadedImage(image); }; } }, () => !useTerminalStore.getState().isTerminalInputFocused, ), new Keybind( "image.next", "Next image", "ARROWRIGHT", (isAllowed) => { if (!isAllowed) return; const nextImage = useImageStore.getState().siblingImagePaths.next(); if (nextImage) { const image = new Image(); image.src = convertFileSrc(nextImage); image.onload = () => { useImageStore.getState().setDefaultSrc(nextImage); useImageStore.getState().setLoadedImage(image); }; } }, () => !useTerminalStore.getState().isTerminalInputFocused, ), ]; ================================================ FILE: src/keybinds/keybinds/terminalKeybinds.ts ================================================ import { useTerminalStore } from "../../stores/useTerminalStore"; import Keybind from "../Keybind"; export const terminalKeybinds = [ new Keybind( "terminal.open", "Open terminal", "T", () => { useTerminalStore.setState({ isTerminalOpen: true }); }, () => { return !useTerminalStore.getState().isTerminalOpen; }, ), new Keybind( "terminal.close", "Close terminal", "ESCAPE", () => { useTerminalStore.setState({ isTerminalOpen: false }); }, () => { return useTerminalStore.getState().isTerminalOpen; }, ), ]; ================================================ FILE: src/main.tsx ================================================ import { render } from "preact"; import App from "./App"; import CommandRegistry from "./commands/CommandRegistry"; import { useTerminalStore } from "./stores/useTerminalStore"; import { homeDir } from "@tauri-apps/api/path"; import KeybindRegistry from "./keybinds/KeybindRegistry"; import { normalizeFilePath } from "./utils/normalizeFilePaths"; export const SUPPORTED_FILE_EXTENSIONS = [ "png", "apng", "avif", "gif", "jpg", "jpeg", "jfif", "pjpeg", "pjp", "svg", "webp", "bmp", "ico", "cur", ]; // Load all CLI commands before rendering const commandRegistry = CommandRegistry.getInstance(); commandRegistry.loadCommands(); // Load all keybinds before rendering const keybindRegistry = KeybindRegistry.getInstance(); keybindRegistry.loadKeybinds(); keybindRegistry.registerListener(); // Set CWD homeDir().then((homeDir) => { const normalziedHomeDir = normalizeFilePath(homeDir); useTerminalStore.getState().setCwd(normalziedHomeDir); }); render(, document.getElementById("app") as HTMLElement); ================================================ FILE: src/stores/useImageStore.ts ================================================ import { create } from "zustand"; import { readDir } from "@tauri-apps/plugin-fs"; import { CircularFileList } from "../utils/CircularFileList"; import { SUPPORTED_FILE_EXTENSIONS } from "../main"; interface ImageState { loadedImage: HTMLImageElement | null; defaultSrc: string | null; siblingImagePaths: CircularFileList; setLoadedImage: (image: HTMLImageElement) => void; setDefaultSrc: (src: string) => void; loadSiblingImagePaths: (path: string) => void; } export const useImageStore = create((set) => ({ loadedImage: null, defaultSrc: null, siblingImagePaths: new CircularFileList(), setLoadedImage: (image: HTMLImageElement) => set({ loadedImage: image }), setDefaultSrc: (src: string) => set({ defaultSrc: src }), loadSiblingImagePaths: (imageDirectory: string) => { const directoryContents = readDir(imageDirectory); // Construct circular doubly linked list (CDLL) of image paths const CDLL = new CircularFileList(); directoryContents.then((paths) => { paths // Only show files .filter((path) => path.isFile) // Only show files with supported file extensions .filter((path) => SUPPORTED_FILE_EXTENSIONS.includes(path.name.split(".").pop() || ""), ) // Push the file paths to the CDLL .map((path) => CDLL.push(`${imageDirectory}/${path.name}`)); set({ siblingImagePaths: CDLL }); }); }, })); ================================================ FILE: src/stores/useTerminalStore.ts ================================================ import { invoke } from "@tauri-apps/api/core"; import { create } from "zustand"; export interface TerminalHistoryEntry { type: "input" | "output"; value: string; variant?: "default" | "error" | "warn" | "success"; } interface TerminalState { // Variables history: TerminalHistoryEntry[]; isTerminalOpen: boolean; isTerminalInputFocused: boolean; cwd: string; // Methods addHistory: (entry: TerminalHistoryEntry) => void; setIsTerminalOpen: (isOpen: boolean) => void; setIsTerminalInputFocused: (isFocused: boolean) => void; clearHistory: () => void; setCwd: (cwd: string) => void; } export const useTerminalStore = create((set) => ({ history: [], isTerminalOpen: false, isTerminalInputFocused: false, cwd: "/", addHistory: (entry) => set((state) => ({ history: [...state.history, entry] })), setIsTerminalOpen: (isOpen) => set({ isTerminalOpen: isOpen }), setIsTerminalInputFocused: (isFocused: boolean) => set({ isTerminalInputFocused: isFocused }), clearHistory: () => set(() => ({ history: [] })), setCwd: async (cwd) => { await invoke("change_cwd", { path: cwd }); set({ cwd }); }, })); ================================================ FILE: src/styles/global.css ================================================ html, body { margin: 0; padding: 0; font-family: monospace; font-size: 16px; width: 100%; height: 100%; } #app { width: 100%; height: 100%; } canvas { display: block; margin: 0 auto; width: 100%; height: 100%; background-color: gray; } ================================================ FILE: src/styles/normalize.css ================================================ /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /* Document ========================================================================== */ /** * 1. Correct the line height in all browsers. * 2. Prevent adjustments of font size after orientation changes in iOS. */ html { line-height: 1.15; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ } /* Sections ========================================================================== */ /** * Remove the margin in all browsers. */ body { margin: 0; } /** * Render the `main` element consistently in IE. */ main { display: block; } /** * Correct the font size and margin on `h1` elements within `section` and * `article` contexts in Chrome, Firefox, and Safari. */ h1 { font-size: 2em; margin: 0.67em 0; } /* Grouping content ========================================================================== */ /** * 1. Add the correct box sizing in Firefox. * 2. Show the overflow in Edge and IE. */ hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ pre { font-family: monospace; /* 1 */ font-size: 1em; /* 2 */ } /* Text-level semantics ========================================================================== */ /** * Remove the gray background on active links in IE 10. */ a { background-color: transparent; } /** * 1. Remove the bottom border in Chrome 57- * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ abbr[title] { border-bottom: none; /* 1 */ text-decoration: underline; /* 2 */ text-decoration: underline dotted; /* 2 */ } /** * Add the correct font weight in Chrome, Edge, and Safari. */ b, strong { font-weight: bolder; } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ code, kbd, samp { font-family: monospace; /* 1 */ font-size: 1em; /* 2 */ } /** * Add the correct font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` elements from affecting the line height in * all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } /* Embedded content ========================================================================== */ /** * Remove the border on images inside links in IE 10. */ img { border-style: none; } /* Forms ========================================================================== */ /** * 1. Change the font styles in all browsers. * 2. Remove the margin in Firefox and Safari. */ button, input, optgroup, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 1 */ line-height: 1.15; /* 1 */ margin: 0; /* 2 */ } /** * Show the overflow in IE. * 1. Show the overflow in Edge. */ button, input { /* 1 */ overflow: visible; } /** * Remove the inheritance of text transform in Edge, Firefox, and IE. * 1. Remove the inheritance of text transform in Firefox. */ button, select { /* 1 */ text-transform: none; } /** * Correct the inability to style clickable types in iOS and Safari. */ button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } /** * Remove the inner border and padding in Firefox. */ button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } /** * Restore the focus styles unset by the previous rule. */ button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; } /** * Correct the padding in Firefox. */ fieldset { padding: 0.35em 0.75em 0.625em; } /** * 1. Correct the text wrapping in Edge and IE. * 2. Correct the color inheritance from `fieldset` elements in IE. * 3. Remove the padding so developers are not caught out when they zero out * `fieldset` elements in all browsers. */ legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } /** * Add the correct vertical alignment in Chrome, Firefox, and Opera. */ progress { vertical-align: baseline; } /** * Remove the default vertical scrollbar in IE 10+. */ textarea { overflow: auto; } /** * 1. Add the correct box sizing in IE 10. * 2. Remove the padding in IE 10. */ [type="checkbox"], [type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Correct the cursor style of increment and decrement buttons in Chrome. */ [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Correct the odd appearance in Chrome and Safari. * 2. Correct the outline style in Safari. */ [type="search"] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } /** * Remove the inner padding in Chrome and Safari on macOS. */ [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * 1. Correct the inability to style clickable types in iOS and Safari. * 2. Change font properties to `inherit` in Safari. */ ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } /* Interactive ========================================================================== */ /* * Add the correct display in Edge, IE 10+, and Firefox. */ details { display: block; } /* * Add the correct display in all browsers. */ summary { display: list-item; } /* Misc ========================================================================== */ /** * Add the correct display in IE 10+. */ template { display: none; } /** * Add the correct display in IE 10. */ [hidden] { display: none; } ================================================ FILE: src/utils/CircularFileList.ts ================================================ /** * Implementation of a circular doubly linked list (CDLL) to store file paths. */ export class CircularFileList { private head: FileNode | null = null; private current: FileNode | null = null; push(filePath: string) { const newNode = new FileNode(filePath); if (!this.head) { // First node in the list this.head = newNode; this.head.next = this.head; this.head.prev = this.head; this.current = this.head; } else { // Insert at the end const tail = this.head.prev; if (tail) { tail.next = newNode; newNode.prev = tail; newNode.next = this.head; this.head.prev = newNode; } } } pop() { if (!this.current) return null; const removedFilePath = this.current.filePath; if (this.current === this.current.next) { // Only one element in the list this.head = null; this.current = null; } else { if (this.current.prev && this.current.next) { this.current.prev.next = this.current.next; this.current.next.prev = this.current.prev; } if (this.current === this.head) { this.head = this.current.next; } this.current = this.current.next; } return removedFilePath; } next() { if (this.current) this.current = this.current.next; return this.current?.filePath ?? null; } previous() { if (this.current) this.current = this.current.prev; return this.current?.filePath ?? null; } getCurrent() { return this.current?.filePath ?? null; } clear() { this.head = null; this.current = null; } } export class FileNode { filePath: string; next: FileNode | null = null; prev: FileNode | null = null; constructor(filePath: string) { this.filePath = filePath; } } ================================================ FILE: src/utils/normalizeFilePaths.ts ================================================ /** * Normalizes a given file path to use forward slashes (`/`). * Handles Windows-style (`C:\\path\\to\\file`) and Unix-style (`/path/to/file`). */ export function normalizeFilePath(filePath: string): string { return filePath.replace(/\\/g, "/"); } ================================================ FILE: src/utils/parseCommandInput.ts ================================================ /** * Parses a command-line style input string, correctly handling quoted arguments. * * This function splits an input string into arguments, preserving spaces inside * quoted substrings (both single `'` and double `"` quotes are supported). * * Example: * parseCommandInput('load "C:/Users/Public users/example"') * -> ['load', 'C:/Users/Public users/example'] * * @param input - The raw input string from the terminal. * @returns An array of parsed arguments, with quotes removed. */ export function parseCommandInput(input: string): string[] { const args: string[] = []; let currentArg = ""; let insideQuotes = false; let quoteChar: string | null = null; for (let i = 0; i < input.length; i++) { const char = input[i]; if (char === '"' || char === "'") { // Toggle quote state if it's the start or end of a quoted section if (insideQuotes && char === quoteChar) { insideQuotes = false; quoteChar = null; } else if (!insideQuotes) { insideQuotes = true; quoteChar = char; } } else if (char === " " && !insideQuotes) { // If we encounter a space outside quotes, push the currentArg and reset if (currentArg !== "") { args.push(currentArg); currentArg = ""; } } else { // Append character to current argument currentArg += char; } } // Push the last argument if any if (currentArg !== "") { args.push(currentArg); } return args; } ================================================ FILE: src-tauri/.gitignore ================================================ # Generated by Cargo # will have compiled files and executables /target/ # Generated by Tauri # will have schema files for capabilities auto-completion /gen/schemas ================================================ FILE: src-tauri/Cargo.toml ================================================ [package] name = "Electro" version = "0.6.3" description = "A lightweight & blazingly-fast image viewer with a built-in terminal" authors = ["pTinosq"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] # The `_lib` suffix may seem redundant but it is necessary # to make the lib name unique and wouldn't conflict with the bin name. # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 name = "electro_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = ["protocol-asset"] } tauri-plugin-shell = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" chrono = "0.4.38" tauri-plugin-fs = "2" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" # Enables dev tools in release mode # https://github.com/tauri-apps/tauri/discussions/3059#discussioncomment-1793205 [profile.release.package.wry] debug = true debug-assertions = true ================================================ FILE: src-tauri/build.rs ================================================ fn main() { tauri_build::build() } ================================================ FILE: src-tauri/capabilities/default.json ================================================ { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", "windows": [ "main" ], "permissions": [ "core:default", "shell:allow-open", "core:resources:default", "fs:read-all", "fs:write-all", { "identifier": "fs:scope", "allow": [ { "path": "*/**" } ] } ] } ================================================ FILE: src-tauri/capabilities/desktop.json ================================================ { "identifier": "desktop-capability", "platforms": [ "macOS", "windows", "linux" ], "windows": [ "main" ], "permissions": [ "cli:default", "cli:default" ] } ================================================ FILE: src-tauri/src/lib.rs ================================================ use std::env; use std::fs; use std::path::{Path, PathBuf}; use tauri::{AppHandle, Emitter}; use tauri_plugin_cli::CliExt; /* This file should definitely be abstracted into separate modules but it works for now so I'm leaving it as is. */ #[tauri::command] fn get_version() -> String { env!("CARGO_PKG_VERSION").to_string() } // This function will be called by the `tauri` runtime when the application is ready // Here we will parse the CLI arguments and emit them to the frontend #[tauri::command] fn on_image_source_listener_ready(app: AppHandle) { match app.cli().matches() { Ok(matches) => { app.emit("image-source", &matches.args) .unwrap_or_else(|err| eprintln!("Emit error: {:?}", err)); } Err(_) => { eprintln!("Error while parsing CLI arguments"); } } } #[tauri::command] fn exit_app() { std::process::exit(0x0); } #[tauri::command] fn get_cwd() -> String { match std::env::current_dir() { Ok(path) => { let path_str = path.to_string_lossy().to_string(); let clean_path = if path_str.starts_with(r"\\?\") { path_str[4..].to_string() // Remove `\\?\` prefix } else { path_str }; clean_path } Err(_) => "Failed to get current working directory".to_string(), } } #[tauri::command] fn open_file_explorer(path: String) { if cfg!(target_os = "windows") { std::process::Command::new("explorer") .arg(path) .spawn() .expect("Failed to open explorer"); } else { std::process::Command::new("xdg-open") .arg(path) .spawn() .expect("Failed to open file manager"); } } #[tauri::command] fn change_cwd(path: String) -> Result { let new_path = if Path::new(&path).is_absolute() { PathBuf::from(path) } else { match env::current_dir() { Ok(cwd) => cwd.join(path), Err(_) => return Err("Failed to get current working directory".to_string()), } }; // Normalize the path (convert to absolute and resolve "..", ".") let canonical_path = match new_path.canonicalize() { Ok(p) => p, Err(_) => return Err("Invalid directory".to_string()), }; if canonical_path.is_dir() { if let Err(err) = env::set_current_dir(&canonical_path) { return Err(format!("Failed to change directory: {:?}", err)); } // Convert to a normal string without Windows `\\?\` prefix let path_str = canonical_path.to_string_lossy().to_string(); let clean_path = if path_str.starts_with(r"\\?\") { // Strip the "\\?\" prefix path_str[4..].to_string() } else { path_str }; Ok(clean_path) } else { Err("Invalid directory".to_string()) } } #[tauri::command] fn list_directory(path: String) -> Result, String> { let dir_path = Path::new(&path); // Ensure the path exists and is a directory if !dir_path.exists() { return Err("Path does not exist".to_string()); } if !dir_path.is_dir() { return Err("Provided path is not a directory".to_string()); } let entries = fs::read_dir(dir_path).map_err(|_| "Failed to read directory".to_string())?; let mut files_and_dirs = Vec::new(); for entry in entries { if let Ok(entry) = entry { let file_name = entry.file_name(); files_and_dirs.push(file_name.to_string_lossy().to_string()); } } Ok(files_and_dirs) } // Main entry point #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_cli::init()) .invoke_handler(tauri::generate_handler![ on_image_source_listener_ready, exit_app, get_cwd, open_file_explorer, change_cwd, list_directory, get_version ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ================================================ FILE: src-tauri/src/main.rs ================================================ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { electro_lib::run() } ================================================ FILE: src-tauri/tauri.conf.json ================================================ { "$schema": "https://schema.tauri.app/config/2", "productName": "Electro", "version": "0.6.3", "identifier": "com.pTinosq.electro", "build": { "beforeDevCommand": "npm run dev", "devUrl": "http://localhost:1420", "beforeBuildCommand": "npm run build", "frontendDist": "../dist" }, "app": { "withGlobalTauri": true, "windows": [ { "title": "Electro", "width": 800, "height": 600 } ], "security": { "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost", "assetProtocol": { "enable": true, "scope": [ "**/*" ] } } }, "bundle": { "active": true, "targets": "nsis", "icon": [ "icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico" ], "windows": { "nsis": { "installerHooks": "./windows/hooks.nsi" } }, "resources": { "../src/assets/electro-default.jpg": "assets/electro-default.jpg" } }, "plugins": { "cli": { "args": [ { "name": "source", "index": 1, "takesValue": true } ] } } } ================================================ FILE: src-tauri/windows/hooks.nsi ================================================ Function PostInstallHook MessageBox MB_YESNO "Set Electro as the default image viewer?" IDYES continue_installation IDNO skip_registration continue_installation: DetailPrint "Registering file associations." ; The APP_ASSOCIATE macro is comes bundled with Tauri v2.0's `installer.nsi` script. ; See: https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-bundler/src/bundle/windows/nsis !insertmacro APP_ASSOCIATE "png" "Electro.PNGFile" "PNG File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "apng" "Electro.APNGFile" "APNG File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "avif" "Electro.AVIFFile" "AVIF File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "gif" "Electro.GIFFile" "GIF File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "jpg" "Electro.JPGFile" "JPG File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "jpeg" "Electro.JPEGFile" "JPEG File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "jfif" "Electro.JFIFFile" "JFIF File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "pjpeg" "Electro.PJPEGFile" "PJPEG File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "pjp" "Electro.PJPFile" "PJP File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "svg" "Electro.SVGFile" "SVG File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "webp" "Electro.WEBPFile" "WEBP File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "bmp" "Electro.BMPFile" "BMP File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "ico" "Electro.ICOFile" "ICO File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' !insertmacro APP_ASSOCIATE "cur" "Electro.CURFile" "CUR File" "$INSTDIR\Electro.exe,0" "Open with Electro" '"$INSTDIR\Electro.exe" "%1"' Goto end_messagebox skip_registration: DetailPrint "Skipping file association registration." end_messagebox: FunctionEnd !macro NSIS_HOOK_POSTINSTALL Call PostInstallHook !macroend ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "jsx": "react-jsx", "jsxImportSource": "preact", "types": [ "vite/client" ] }, "include": ["src"] } ================================================ FILE: utils/vb.js ================================================ /* VB = Version Bump USAGE: `npm run vb` Script that propagates version changes to the package.json and tauri.conf.json files. */ import { readFileSync, writeFileSync } from "node:fs"; import { createInterface } from "node:readline"; const rl = createInterface({ input: process.stdin, output: process.stdout }); const askVersion = () => { return new Promise((resolve) => { rl.question("Enter new version (e.g. 0.4.2): ", (version) => { resolve(version.trim()); }); }); }; const updateJsonFile = (filePath, updater) => { try { const json = JSON.parse(readFileSync(filePath, "utf8")); updater(json); writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`, "utf8"); console.log(`Updated ${filePath}`); } catch (error) { console.error(`Error updating ${filePath}:`, error); } }; const updateTomlFile = (filePath, updater) => { try { const toml = readFileSync(filePath, "utf8"); const updatedToml = updater(toml); writeFileSync(filePath, updatedToml, "utf8"); console.log(`Updated ${filePath}`); } catch (error) { console.error(`Error updating ${filePath}:`, error); } } (async () => { const newVersion = await askVersion(); rl.close(); if (!/^\d+\.\d+\.\d+$/.test(newVersion)) { console.error("Invalid version format."); process.exit(1); } updateJsonFile("./package.json", (json) => { json.version = newVersion }); updateJsonFile("./src-tauri/tauri.conf.json", (json) => { json.version = newVersion }); updateTomlFile("./src-tauri/Cargo.toml", (toml) => { return toml.replace(/version = "\d+\.\d+\.\d+"/, `version = "${newVersion }"`); }); console.log(`Version updated to ${newVersion}`); })(); ================================================ FILE: vite-env.d.ts ================================================ /// // Extend Vite's existing ImportMetaEnv instead of redeclaring built-in variables interface ImportMetaEnv extends Readonly> { readonly VITE_API_URL?: string; readonly VITE_SOME_KEY?: string; } // Extend ImportMeta to include env interface ImportMeta { readonly env: ImportMetaEnv; } ================================================ FILE: vite.config.ts ================================================ import { defineConfig } from "vite"; import preact from "@preact/preset-vite"; // @ts-expect-error process is a Node.js global const host = process.env.TAURI_DEV_HOST; export default defineConfig(async () => ({ plugins: [preact()], clearScreen: false, server: { port: 1420, strictPort: true, host: host || false, hmr: host ? { protocol: "ws", host, port: 1421, } : undefined, watch: { ignored: ["**/src-tauri/**"], }, }, }));