@arghajit/playwright-pulse-report 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,36 @@ import { readFileSync, existsSync as fsExistsSync } from "fs";
5
5
  import path from "path";
6
6
  import { fork } from "child_process";
7
7
  import { fileURLToPath } from "url";
8
- import { getOutputDir } from "./config-reader.mjs";
8
+ import { getReporterConfig } from "./config-reader.mjs";
9
+ import { animate } from "./terminal-logo.mjs";
10
+ import { mergeSequentialReportsIfNeeded } from "./merge-sequential-reports.mjs";
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ let logo =
15
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAANDCAYAAADy+LIPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAgAElEQVR4nOzdeZxfVX34/wlhR1ZBMJk550wyNta4B5cqrqV8XYoV26hVi1pstCpCzMx5n0lUPtXa4tJWrBu1dUHbKoioYF2wpSqoaBQnyXmfzySEIJEdkbBDlvk+zmTUsiRkJjOfc+/9vJ6Px/nj9/j1W+3J65577p37ubenBwAAAJiM1tgeJqw6dK4f6TVh7bx+WfMEE9IiG+LzTdBjTYiLXdBXmJCW5OG8nmqCyv2HDfpu5/X0/ztMiH/3gP9ZH9/2m/9dv/3fOf6/Py7uD+m4ftFn5//83iF91Pb/TqsO7Vk8Npt/VAAAAAAAAHS13qXxsH7fXmAkHuO8vjTfWLNeV1iv73c+nukknW0kftOK/tCKRiv6Syt6mxUdq8m414rebEV/YUVHjeil1qdv2JA+byV92Ph0mpF4spX0KhfiC/p9fGrf0Or582T9waX/bQAAAAAAAIAHtWjJyr3mr1jdZyU9w/j4Z0b0FCvpAxM3vf7bia6yotdM3BwrfYOuymOLFb3OSvq5Fb3Aev2kC7E1/gTiUPpjG+KT3FA8qqdnbBYpAgAAAAAAYNoMtNYdlH9ya4OeYCW93Yb4z07i16zoSit6rRXdVoGbZ9007rUSN1qvP7ASv2R9/KD1+ubtTxS2FwycvG4f8gcAAAAAAMB99Pn2nPw+PSfxjU70ffmnuFb0J1b0pgrc8GJMbg62Gq9XGdHvGomftl7faX18dX5Cs3/56iNJHwAAAAAAoKEWtuLe+QMaRtLx+UMX+Z171seLregmbrJ11U3GW8af3gx61vaPnsTFRuLC/KGV0o0CAAAAAABgVywemz1vOP1evrGTv3prRc+zEtfnJ8MqcPOJUd05uH3iZ92fcz4N5598568cc2MQAAAAAACgIBNWHWp8/EPn9VQr+m8TP9u9swI3kxjNmYP8FeZLrOhHrNeT7JA+OT9NyoEPAAAAAAAwzRb49oFG4jHjX9kNepYVjXyAo/jNsW4dm8f7yz8jFj3FBD22d2k8jIMeAAAAAABgF81prdy/X/TZVuKgFf1PI7qWm33Fb3oxdj4H+SfmbRP0M/ljMv2D7cfnn6Nz0AMAAAAAAEx8hTd/nMN5PX3iwxx3c8OJG24NaOC23LML6Yz8TsqB4XVHcMADAAAAAIDma120Z19Y85T800kn+kUrcWMFbtQwmINONLDNSkxG4qdNSEvGvz7cMzar9CEJAAAAAACwexaPzTYhLdp+wy+dbUVv5mYTNxxp4LcN3GCDnm+CSj5OuCEIAAAAAACqjxt+3NziBuduNJCuzzfKt39chBuCAAAAAACgInpDHLBe3+xC+qoV3cQNIG4C0sC0NXC1Denz1utJ81es7it9rAMAAAAAgC5x5ODIASboseMf7RCN3Ozhhh8NdKqBuN75eGb+aM7Ayev2Kb0WAAAAAACAxhiblX+OaL2ucEG/Z0U3c8OHm340ULyB2/JTty7EN7mQXOlVAgAAAAAA1Ezv0o375aeM8tNGVvSX3OwpfrOHwRw81NOByYr+Y39Ix/F0IAAAAAAAeFADw+uOMKInTnyt91ZuuHDTjQZq28Ad418XFj1xnqw/mCUPAAAAAIAuZiQuNJLeZUVXWtFtFbhxwWAOaGB6G7jHSPymk/hGNxSPKr3mAAAAAACADt30cyG2+IAHN5q42dh1DWzNN/vHj3+/+vdZcAEAAAAAaIyxWX1hzVMmvtp7eQVuQjCYAxqoQgNeV9ug7zbD8TGlVykAAAAAADAF27/cGz9og15Z/EYDgzmggWo3EHTE+TTMF4UBAAAAAKi4/mVqTVCZ+CJo+ZsKDOaABurYQBxfR5brI0uvaQAAAAAAoKenZ+5wergJaYn18WI+5FH8xgmDOWhSA1vzumJET8lfCWfBBQAAAACgg45oxYe5kF5nRb9jRbdU4EYBgzmggWY3cI8L6avWp5ctbMW9WfABAAAAAJjB9/o5H8+0ordW4IYAgzmgge5s4Oa8DuX1iMUeAAAAAIBp0LtidO729/rpugpc+DOYAxqggQe8L3D+4MgjWPABAAAAAJiE3qUb97M+vtqKXjj+Hi5uOHDTiQZooNoN3GMlfsmKvrhn8dhsFnwAAAAAAHbA+tW/70Q/lH9iV4ELegZzQAM0MJUGrnFeT+8bWj2fxR4AAAAAgJ6enoGT1+1jQlw88bTfNm44cNOJBmigIQ3kp5cvzOtbT+uiPVnwAQAAAABdp3dIH5WfkrGiN1TgQp3BHNAADcxkA1fn9W6uH+ktvfYCAAAAADCzFo/Ntj69zEr6b57244YTN5xooAsbuMeK/me/6LM53QAAAAAAGmWerD/YiJ5iRTdU4AKcwRzQAA1UoYF2/oJw79J4WOk1GgAAAACAKZs3nH7PhXSGFb29AhfbDOaABmigig3c5nw8s9+3F3C6AQAAAADUQ2tsDxP0WBv0fH7mW/zGAoM5oIH6NLA1r5t5/Sy9jAMAAAAA8KDmtFbub0XfYkUvr8CFNIM5oAEaqHMDPxz/evDisdmccgAAAAAAxc1ZNnp4fo+VFb2mAhfNDOaABmigSQ1syOurCasOLb3WAwAAAAC6UG+IA1b0Y1b0zgpcJDOYAxqggSY3sMn6+MF5Ek3ptR8AAAAA0AXskD7ZBj3Lim6uwEUxgzmgARropgbuzeuvGx59dOlzAQAAAACggYzEYyY+7FH6ApjBHNAADXR7A+MfDOnz7aNLnxsAAAAAAE258SfxfypwwctgDmiABmjggQ1c6Lw+vfS5AgAAAABQO2OzjKTjjeil3HDghgMN0AAN1KABHy/O63bpswcAAAAAoOpaY3s40Vdar6uLX8wymAMaoAEamMKNQP2BFX1x6dMJAAAAAKCKN/5C+nMr2uaCm5suNEADNFD/BvIT3P0hHVf69AIAAAAAqMhPfa3oz0pfrDKYAxqgARqYoScCQ3x+6bMNAAAAAKAAE/RYK/oTLri56UIDNEADXdCAjxf3iz6bEy4AAAAAdMlXfY3od4tfjDKYAxqgARoo0cCFJqRFpc9FAAAAAIAZYCU9wwX9Hhfc3HShARqgga5vYJuVdI4Zjo/hhAsAAAAADeCGRx9tRb/MBX/XX/Bz04cGaIAG7t/AVif62fkrVveVPlcBAAAAAKZgYHjdES6kM6zoZi56ufFBAzRAAzSwkwbuyecLd+plh3DCBQAAAIAaOHJw5AATVKzorVzwc8FPAzRAAzQwiQZ+lc8fAyev26f0uQwAAAAA8CAWLVm5l/XprVb0Bi74ueCnARqgARqYagNGdK0N6U852QIAAABAhZigx1rRNVzwc8FPAzRAAzQwXQ0Y0Uv7huKzSp/jAAAAAKCrGYkLjei3uODngp8GaIAGaGAmvxjcG+JA6XMeAAAAAHSV3qXxMD7wwcU+N3xogAZooIMN3JvPOwOtdQeVPgcCAAAAQOPf82dCWmJFb+TCnwt/GqABGqCBAg1cbURP7OkZm1X6nAgAAAAAjWMlvtCKtrng54KfBmiABmigdAMu6PfcUHxi6XMjAAAAADRC74rRuTboWaUv9hjMAQ3QAA3QwP0a2JrPTwPD644ofa4EAAAAgPr+3Ff0FCt6GxfdXHTTAA3QAA1UuIGb8/mqZ/HY7NLnTgAAAACojf6QjrOioxW4qGMwBzRAAzRAA7vawGVmuP3M0udQAAAAAKi0Pt+ew899udnAzQYaoAEaqHED2/J5bM6y0cNLn1MBAAAAoFpaF+1pJQ5a0dsrcPHGYA5ogAZogAZ2t4HrrKRXlT69AgAAAEAl5K8oOkk/5mKTGw40QAM0QAMNbOCift9eUPpcCwAAAABFzGmt3N95Pd2KbqnABRqDOaABGqABGpipBu50IbYWtuLebDkAAAAAdA07rC+yQa/kYpMbDjRAAzRAA13TgNfVVtIzSp+DAQAAAGBGuaF4lPH6heIXYQzmgAZogAZooEwDW43Xj7tTLzuELQcAAACAxnE+vt6K3sxFNxfdNEADNEADNKC/tKIvLn1uBgAAAIBpYZfrI11IX+Vijwt+GqABGqABGrhvA07S2XOH08PZcgAAAACoLRPiYiv6Ky74uOinARqgARqggR02cK3z+tLS52wAAAAAmBSe+uNCnwt9GqABGqCByTXA04AAAAAA6vbUH+/64+Kfi38aoAEaoIHJN8DTgAAAAACqa64f6TWi3+KClwteGqABGqABGti9BpzoZ01YdWjpczsAAAAA/JYTfSVP/XHBzwU/DdAADdDAtDZwtQvxBWw3AAAAABQ1T9Yf7Hw8kws+LvppgAZogAZoYEYa2JbPs3NaK/dnywMAAACg42yIzzder+KCj4t+GqABGqABGpjxBtSG+CS2OwAAAAA6wrU27Ou8nm5Ft3LBx0U/DdAADdAADXSsgXtdiK2e1tgebHkAAAAAzBg3FJ9oRddwsccFPw3QAA3QAA0Ua+DC3hWjc9nuAAAAAJhmY7NMSEus6J1c8HHRTwM0QAM0QAPFG7jFhfTnbHcAAAAATIs5y0YPdxK/xsVe8Ys9BnNAAzRAAzRw3waCnrXAtw9kywMAAABgytxQeo6VuJELLi66aYAGaIAGaKCaDRjRtfkVHWx3AAAAAEzO4rHZ+UXjVnRL6QsbBnNAAzRAAzRAAw/ZwN1G9BS2OwAAAAB2yVw/0mtEv8vFFhfcNEADNEADNFC7Br7sTr3sELY8AAAAAHbIir7Yit5UgQsYBnNAAzRAAzRAA1P8SbAN8UlsdwAAAADs6Ce/W7ng5IKTBmiABmiABmrfAD8JBgAAAPA7A8PrjnCi367AxQqDOaABGqABGqCB6W2AnwQDAAAA3a5f9NlW9BouOLngpAEaoAEaoIGGNhD0Sjccn1Z6zwEAAACg48Zm5a8FWtF7i1+YMJgDGqABGqABGpjpBu62Qd/AhgsAAADoEiasOtQGPZ+LLS64aYAGaIAGaKDLGgj60YWtuHfpvQgAAACAGdQ/2H68lbi++AUIgzmgARqgARqggVINXNLn23PYcAEAAAANZCS+3IrezgUXF900QAM0QAM00PUN3OBCem7pvQkAAACAaTM2ywQVK7qNC76uv+Djop8GaIAGaIAGftPA5rw/YMMFAAAA1NxAa91BLqSvcrHDBS8N0AAN0AAN0MAOGvhc79KN+5XeswAAAACYgn7fXmAlJi74uOCjARqgARqgARp4iAZ+5kJybLgAAACAGrE+vcyK3sYFHxd8NEADNEADNEADu9ZAut4Mt59Zeg8DAAAAYBcY0VOs6FYu+LjgowEaoAEaoAEamGQDdzufXsuGCwAAAKiogZPX7eNEP8vFHhd7NEADNEADNEADu9OAC+mMntbYHqX3NgAAAAD+jznLRg83ot/lgo8LPhqgARqgARqggWlq4NwjB0cOYMMFAAAAVEDfcn2sFd3ABR8XfDRAAzRAAzRAA9PaQNCReRJN6b0OAAAA0NVciC+wordwwccFHw3QAA3QAA3QwAw1cE1fWPOU0nseAAAAoCtZSW+3olu44OOCjwZogAZogAZoYIYbuMOEuLj03gcAAADoHovHZlvRj3Cxx8UeDdAADdAADdBABxvYZkXfUXobBAAAADSea23Y10k6mws+LvhogAZogAZogAZKNGAkfnrRkpV7ld4TAQAAAI00dzg93Pp4MRd8XPDRAA3QAA3QAA2UbMCJfnuBbx9Yem8EAAAANIoJa+dZ0VEu+LjgowEaoAEaoAEaqEIDTtKP5w+OPKL0HgkAAABoBDccn2YlXV96o89gDmiABmiABmiABu7XwBXzhtPvld4rAQAAALXmvL7Uit7JBRcXXDRAAzRAAzRAA9VsIF3f7+NTS++ZAAAAgFpyIb3Oim4uv7FnMAc0QAM0QAM0QAM7beAOM5T+uPTeCQAAAKgVE1Ss6DYuNrjgpAEaoAEaoAEaqEkDW1yIbyq9hwIAAACqrzW2hxP9UAU28QzmgAZogAZogAZoYNINGEnvKr2dAgAAAKqrddGeVtKnuNjggpMGaIAGaIAGaKDmDXwk/1Gz9NYKAAAAqJQ5rZX7W69fr8CGncEc0AAN0AAN0AANTEcDn8t/3Cy9xwIAAAAqwYRVh1ofL+ZigwtOGqABGqABGqCBJjVgRL/iWhv2Lb3XAgAAAIpyQ/Eo63V16Q06gzmgARqgARqgARqYkQZ8+kb+pQNbTgAAAHSl+StW9xnRtVxwccFFAzRAAzRAAzTQ5AaM6KW9S+NhpfdeAAAAQEe5kJwVvbz0hpzBHNAADdAADdAADXSogZ/NHxx5BFtOAAAAdIV+315gJW7kgosLLhqgARqgARqggS5roJ1/AVF6LwYAAADMqH5Z8wQr6foKbMAZzAEN0AAN0AAN0ECJBjbYwXY/W04AAAA0kglpkRW9iYsNLjhpgAZogAZogAa6uQHj9aq+odXzS+/NAAAAgGllJB5jRTeV3nAzmAMaoAEaoAEaoIEqNMBNQAAAADSKlfQMK3pr6Y02gzmgARqgARqgARqoVgNxY2+IA6X3agAAAMBu4eZf6QsLBnNAAzRAAzRAAxVv4FrrV/8+W04AAADUEjf/il9QMJgDGqABGqABGqhHA9ea4fiY0ns3AAAAYFK4+Vf8QoLBHNAADdAADdBAvRq4jpuAAAAAqA1u/hW/gGAwBzRAAzRAAzRQzwauMxIXlt7LAQAAADvFzb/iFw4M5oAGaIAGaIAG6t3Atf2+vYAtJwAAACqp38en8rXf4hcNDOaABmiABmiABmrfQNzoQnKl93YAAADAffRKepwV/VX5DTODOaABGqABGqABGmhEA7+YJ9Gw5QQAAEAl9A7po/LPVSqwUWYwBzRAAzRAAzRAA01qYNQNxaNK7/UAAADQ5fJfpm3QKyuwQWYwBzRAAzRAAzRAA81rIOhI79J4WOk9HwAAALpU74rRuVbi+uIbYwZzQAM0QAM0QAM00OwGfrTAtw8svfcDAABAlxkYXneEFY0V2BAzmAMaoAEaoAEaoIHmN+DjxUcOjhxQeg8IAACALmHCqkOtpJ8X3wgzmAMaoAEaoAEaoIHuauC/Frbi3qX3ggAAAGi4Oa2V+1vRSyqwAWYwBzRAAzRAAzRAA93YwLk9i8dml94TAgAAoKEWLVm5lxW9oAIbXwZzQAM0QAM0QAM00L0NBD2rp2dsVum9IQAAABpnbJaV9KniG14Gc0ADNEADNEADNEAD+Sbgu0vvDgEAANAw1scPstnmgosGaIAGaIAGaIAGqtOA8fFtpfeIAAAAaAgnKZTe4DKYAxqgARqgARqgARp4QANbTYiLS+8VAQAAUHPG619Y0W1suLnoogEaoAEaoAEaoIFKNnCPCXps6T0jAADT8uEBE1YdOteP9Jqwdp4JaZEdTn+QT3TO60uNxJebkJbsaFjRt5ig8mDDhrRs/H8m6BvyX8/Gh9c/yv+7+318av7P6vftBfk/1w3Fo/J/j57WRXvyz4pu0OfjS6zo5gpsbBnMAQ3QAA3QAA3QAA3suIFb+gfbjy+9dwQAYNwRrfgwF5Jzw/FpdlhfZERPtJLe7ryebkX/zYX0VSt6iRUdtaLXWtGbK/zk0W3G61U26IgR/a4R/YoJ+hkj+k/Gp9Py+zjy/31G0vFG4jFuePTR+f9+UkBdjB+nondU4FhjMAc0QAM0QAM0QAM08NANXJ2vtUrvIQEADTdP1h+c/+o0fsPLx7dZif9gJX7JSfqxlbjRit7FxmX8xJxvqIy6oN8zXr/gRD9kgw7ln1nmpw6NxIW9S+Nhpf890d3yE69W0vUcs1xs0AAN0AAN0AAN0ECtGmjPWTZ6eOm9JACgxlxrw779suYJNugJLujSfOMqP/VmRS+zor+uwMmuaeMuKzFZr1+3oh/JT0rmuc//BgOtdQeV7gHNNXc4PTxvHitwDDCYAxqgARqgARqgARqYfAM/7F26cb/Se0oAQMUt8O0DraRn5HfiWR8/uP0GVFyfvzDFBqRSG5Ab89OV+SlCI/HvjaS/sr79vIHhdUeUbgj1vtFvRb9fgb4ZzAEN0AAN0AAN0AANTLmBdE5Pa2yP0ntLAEBF2MF2//hHNHw6zYqea0Uvr/A79xiTuDloRS+yoh/LH0CxIT5//uDII0r3hoprje2RbyhzrLHW0AAN0AAN0AAN0ED9G8gPCZTeXgIACsjvlXMhvsBIepcVvcCK3lD6pMTo+BzcNP7BEq8ftz691Q2l5+QnPjkgkTnR93FMsi7RAA3QAA3QAA3QQIMaCPEv2ekCQKONzeqV9Dgj8WQb0ueN6NriJx9GVedg6/i7BkP6vPN6at9QfBZfK+4+LsQ3VaBFBnNAAzRAAzRAAzRAA9PbwL3510Cl95oAgGn+aqcJaYmTdPbET0A5eTIHu9PANTbo+S7EVv468ZGDIwdwwDaT8fpHVnQzxwtrJg3QAA3QAA3QAA00soFNfcv1saX3nACAKZon0TiJb5x4dx83/MqfWJs+tljRlfnrz9anl/FOwWbo9+0FfMW7+LHFYA5ogAZogAZogAZmuoHL+VggANTF4rHZJqRF+YmsfCOGj3WwUSq/UYrrbdCz8pOnRuLC0ocIJv9eUF4PUPoYYjAHNEADNEADNEADHWrAx4tda8O+7JkBoILcqZcdYkRPtBK/lB/d5uTIBqnaDcSNVuK/O9G/7h3SR5U+frBji5as3MtK+u/yzTCYAxqgARqgARqgARroWANe/yO/L559MgBUgAmrDnUhvW7iS733sCFgQ1DjBi63QT9qJB3Ph0WqxUj6RAX6YDAHNEADNEADNEADNNDpBry+s/ReFAC6+qZf/kS7Ff0vbvqxCWjoRvCe/MSZEfX9g+3Hlz7mupkRPaUCPTCYAxqgARqgARpodgO/tqI3T4xfjr865rdD10280ui3w4h+14pe+BDjkvv/v9s+/u//7t/+Z9zAe453+G+ztc/Hl5TekwJA91g8NttKfKHx+gUrelcFTtIM5qCTDVxtJX3K+Phnc1or9y99OHYLF+ILJj7owvHOHNAADdAADdAADfymgfyro+usqI7fZAt6fn7Pc/4lh/N6en5iLP8Rd/t7n/VEE+Li/pCOc0PpOfkd5fmjYi4kN/5Lpgq+Y25hK+6d/7vN9SO9JqydZ4f0yW44Ps0EPTb/UiX/32O9npRfY2OCivHpNCvxH/Je1YqeZ0UvspJ+bkV/YUVvbUg3m6xf/ful/20AoNHyQutE37f9BkjxhZ/BHFShgTvyey6d6CsX+PaBpY/RpsrvZeQv4cVbZzAHNEADNEADM93A7Va07YJ+z0o6J7/2w4T4dzboUL7JZX16mQvpuf2y5gnzV6zu4zUtU7B4bPacZaOH571Vv49PHb8ZGvQVLsQ3OUnBSvqAE/2s9ekbNuiIFb02P3VXwfZH8zvnp3/XCQBdLP8lzPn4eiN6aQUWegZzUOUG7nIhfdV4/Qs2JNMnb+6t6JoK/PsymAMaoAEaoAEamFoD1znRVfmmkgn6GSPx742PbzMSX943FJ+Vb0YdOThywDRuHzDdv/5aro/MN17zr8CcT6/NNwud6Ifyhzls0P+deALz1x09Rrx+vac1tgf/2ACwm+ZJNPnkbEVvZLPHZo8GJt3APXlTkt+Pyc3A3TE2Kz8BQH+sQTRAAzRAAzRQ6af21kx8BPAj+Wm9fGMv/0y1d8Xo3EVLVu7FhVn36F26cb/tvxpL/89I+isr+p78k+zxpzqDXmlFN09nf/kJ0dL/NwNAbVnffp4VPZd3bRXfTDGaMwd3WYn/nt/Vwl8pJ2f7T1GK//sxmAMaoAEaoIFubmBTfnrPSfyalfRhK+ntNqQ/7fPto/NPSWfokgRNtXhsdv75tpF4jJH4Gitxef6p98QHJeMU3i+/Ld9wLv1/FgDUx+Kx2fkdZhMviC29yWAwB81tIOiVLsSWHWz3lz7sqy7/5Zg/RFSgWQZzQAM0QAPd0MBd29/3ls6xQf82v84kvx+ud2k8rPR+AF2mNbZH/zK1xusfWa9v3v4z4/QNK3r5Tp4evD3/PLn0f3UAqLT8SH7+GpaVmCqw8WAwB93UwFbr48X5i3S88+aB8tf4eP1A8UYZzAEN0AANNK+Bm/P+w/l45vgXY0NcbCQuzA8DFLgUASZ/7RrWzhv/AnJIS1xIZ1jRC63E9Vb0ioHhdUcwpQBwP3NaK/cff/Gu16sqsBFhMAfd3sAt43/d5KnAcfmG6MRX50r/uzCYAxqgARqggTo2sG3ihsiXncS/Gf+VT4hP4g+OaPpH40xYdWjp/x4AUKm/muRHqSc+5156c8JgDrZYSdfn933kFwPnL+jakD5vvH7ceT19+ztB4skupNfld83kv/a5kJ5rQlqUR2+IA/mvgPmrZPmEP0/WHzyZ42GBbx+Y/9/l0efbc7b/RXHtvHnD6ffG/zPyu0nyXxh9/DMr6VX5L435vTfOp+Ht//3yi67T5ydeen3JxHtLrrGid07x33aLk3S28/r0ni42MaesD8wBDdAADdAADTx0A5vyE31W9GNO4hvtcPqDvL8pfS4HAADFjM3a/oi/rmUzyWayAw3cOPEE1wXjPzHx6TTr9aQ+H19ihtvP7PftBU1/afTAyev2cUPxqPyzGjeUnuOCviLfzLRB3z3xouPzJjbsoxNfz7v/HF6Sb3p2209yXIhvYo1ijaIBGqABGqCBBzSwdfs+Pp1jvb7TSfyT7b8cGJtV+twNAAAqYvyJKdFL2UixmZ7GBn7lJP3Yev0PK/qe/IRev+iz8xN5rrVh39LN19FAa91BZjg+pj+k4/J8GknvyjcK87tN8tx2wwY/P3U5hS+/MZgDGqABGqCBpjWw1YqqCfoZK/oWNxyfxs93AQDADo3/NDLo+RXYxDDqOQdbrGjbSvxS/mptfoKtz7eP5t0amAm5q/zy5gp0z2AOaIAGaIAGOtxA3GhFz3WSgg3x+fmPguw2AADAQ8pPYI3/5JInadjA7/rmM78T8oLx99r5+ONIatwAACAASURBVOr8ouj8E1YON3TG2Cwj+hUuOFmzaIAGaIAGuqCBXxvRb9mgf5tfjZLfY8xuAwAATJod1hdZ0csrsLlhVHcObnai37aS3muDnjDXj/RyqKEkG3SoAscFgzmgARqgARqY7ga2Wq+rbdCP5j+w5o+MdcMrPQAAwAzKN3Gs6JfZuLF5v38D4y+M9vpJI/E1+R19HIiokr6h+Cwrupm1i7WLBmiABmigAQ3k89kPraQPGEnH9y6Nh5U+zwIAgAbJN3byzwkqsOlhVGMOdOLjEX/e59tzSvcJ7MjA8LojrOjVFThmGMwBDdAADdDAVBq4w0r8n/yuZOPjH/KhDgAAMJMXz+eyYev6Tfum/KEOG+Jf8h4Z1MfYLOv166xfXb9+cdOFBmiABurUwM35A3tG1Nvh9AeLlqzcq/TZFAAANJzz+lIr6foKbIQYReYgJuvjB/OX4th8oo6c11NZP1g/aYAGaIAGKt7AXVb0QhNU7JA+uac1tkfp8ycAAOgSvUs37mdF/7UCGyJGh+fAia6yXlfwHj/UXb6IsqJ3s46wjtIADdAADVSsgW1W0s/zO/z6Qzou77tLnzMBAEAX6l+m1kn6cQU2R4zOzcEGF9IZ4391Bhogvx9p/AlW1hHWURqgARqggUo0kK53ks42IS3JH9UrfZ4EAABdbvtPfvWW8pskRgfm4CYr+o9uKD6xdHfAdDMSP806wjpKAzRAAzRQsIHb8zto86sojMSFnOkBAEA1tC7aM/8MYftPEtgsNngO8r/vRVbSqwZOXrdP6eyAmZC/TF2BY43BHNAADdBA1zUQ1zvRD5mgxy5sxb05ywMAgEqZJ+sPzi8eLr9pYszgHNyYb/DOG06/V7o3YCaZsHaelfzFatYT5oAGaIAGaGDGG9hifbzYSQo85QcAACpt/orVfdbrajaIjb1IuMKKvmVOa+X+pVsDZtzisdnW6w8qcNwxmAMaoAEaaG4Dt1pJ5zifXjswvO4Izu4AAKDyeiU9zni9qgIbKcZ0z0HQESN6Yv5pd+nOgE6xXt/JesJ6SgM0QAM0MAP7qiudj2caScfzChUAAFArVuILrehtbJIbt0n+fn9Ix5XuC+g0E9IiK3pvBY5BBnNAAzRAA81oYKWVuLx/sP14zuoAAKCWnOgrrejmCmysGNM3B7HPx5eUbgsooXfpxv3yMcCawppKAzRAAzSwuzf9TFDJ75PljA4AAGrNBX0FN/8atTn+pQlpCT/1RTdzIZ1RgWORwRzQAA3QQD0biC7EFh9KAwAAjZHfCTf+tbLyGy3G7s/BHc6n4fzkU+mugJJM0GOt6DbWFdZVGqABGqCBSTTAk34AAKCZbIh/aUW3sjluwOY46P/yV2qgp8edetkhVvQXxY9JBnNAAzRAA3VogCf9AABAsxmJr+HmXyPGjeNf9gUwzkr89woclwzmgAZogAaq28AaJynwTj8AANAtP4+7pwIbMMZuzUH894HhdUeU7gmoChv0BNYV1lUaoAEaoIEHNpCud6IfskP65NLnKgAAgI7oW66PtaK/ZnNc683xnTboGzpTDFAPc4fTw63otRU4PhnMAQ3QAA1Uo4G7bdDzTYiLFy1ZuVfp8xQAAEDH9K4YnWu8XlWBDRljynMQU76J27lqgHqwIX2etYW1lQZogAZoYPxjHqKnzFk2enjpcxMAAEDHzZP1B+d3nrAxrvXG+N/mtFbu3/l6gGozQ+mPK3B8MpgDGqABGijXwOXGp9N4rx8AAOhyY7Os6HlszGu7Md9sJP1V6YqACn/19+oKHKcM5oAGaIAGOtvAr42kT5jh9jPzXrf0+QgAAKA4I+rZlNd2U35HfrqpdENAVRmJn67AccpgDmiABmigcw2sNCEtOXJw5IDS5yAAAIDKsJKeYUXvZWNey435zdv/qg1gJ18031aBY5XBHNAADdDAzDawyfl4pg3xSZwRAQAA7scNxaOs6DVsymu4KQ96pRsefTRRAw9ugW8fyEeNKrBWMZgDGqCBGWzASfqx9XoST/sBAADs/L1/32FjXsuN+S/7l6klbmDHrKQPV+BYZTAHNEADNDD9Ddya3+3H034AAAC7wIX4JjbltdyU/7pf1jyByIEd6wtrnmJFt1TgeGUwBzRAAzQwze/2O6IVH8Y5EAAAYBfMX7G6L78rhU157TbltzuvTydyYCdaF+1pJf28AscrgzmgARqggd1v4E7r9ZN9vn005z4AAIBJsqL/xaa8dpvye63EFxI7sHMmqFTgeGUwBzRAAzSwew1c60JszVk2ejjnPQAAgClwIb2OTXn9NuVG4slT+fcGukl+N2Z+Urb08cpgDmiABmhgyg38NP/M17U27Fv6nAIAAFBbJqw61IrexKa0XhcmRvQr+aMtpfsBqs5I/Gbp45XBHNAADdDApBvY7CSdbYbbzyx9HgEAAGgEK/Ef2JTW7cIkbpw7nB5euh2g6qyPry5/vDKYAxqgARqYRAObXEhn5Ke3S59DAAAAGsOEtfOs6N1szGu1Md/MX8OBXX26OV1fgWOWwRzQAA3QwEM30LZe33zk4MgBnOMAAACmmRP9Ipvymm3Kg757ujsAmsiKfqT48cpgDmiABmhgpw040W/bYX0RrzUBAACYIW44Ps2KbmNjWp+LEyO6lhdgAw+tV9Lj8tOypY9ZBnNAAzRAAw/awFYb9Px+H5/KOQ0AAGCGWUn/zaa0Xhcm/SEdN9NdAPU3NsuKXlL6eGUwBzRAAzTwgAbuNRI/7YZHH136TAEAANAV+nz7aDal9bowMaLfKt0NUAcupNeVPl4ZzAEN0AAN3KeBu23Qs3pDHCh9jgAAAOgqxusX2JjW6uJkmx3SJ5fuBqg6d+plh1jR6ypwzDKYAxqgARoQvcWE+HfzB0ceUfr8AAAA0HXsYLufd2PVblN+QelugDqwkj5cgeOVwRzQAA10ewM3uBBb+Wvspc8LAAAAXcuG+M8V2BgyJjEHfUPxWaW7Aaqub7k+lj9usLZyfqEBGijawC+M6ClzWiv3L31OAAAA6GoDrXUHWdHb2RzX6gLpZ6W7AapvbJYL+r0KHK8M5oAGaKAbG7jC+fj6RUtW7lX6bAAAAID89J/XkyqwSWRMZg68vpl4gZ1zQV/B2sLaSgM0QAOdbiBuzE/8DZy8bh/OUwAAABViRS9ic1yrC6S780cNSncDVJlrbdjXim6owPHKYA5ogAa6pYFrjY9v48YfAABABfUvU2tFt1Zg08jYxTlwEr9Wuhug6qzE5awrrKs0QAM00JEGbsof91jg2weWXvsBAACwA1b0HWyO63WB5EJ63Y7+PQH09PQvX32kFd1U+lhlMAc0QAMNb+BW5/X0ebL+YM49AAAAFWclpgpsIBm7Pgfb3FA8qnQ3QJVZ0X9lXWFdpQEaoIGZu/FnRd9jwqpDS6/3AAAA2AX9vr2AzXHtLpDirvzbAt3KDcUnWtEtFThWGcwBDdBA0xq4w4V0Bn+IBAAAqBkj8eQKbCYZk5uDj5XuBqgyPmrEmsp5hQZoYNob2Jz3H9z4AwAAqCkb9Hw2yTW7UPJ6UulugKpyXl9a/BhlMAc0QAMN+/CYGx59dOn1HQAAAFO0sBX3tqK3ld5YMiY5ByE+aar/5kCjLR6bbUWVNYV1lQZogAampYGfWt9+XumlHQAAALspb+rYINfuImmba23Yd3f/7YEmMpL+qgLHKIM5oAEaqHUDxutVxutf9LTG9ii9rgMAAGAa2KB/W3qTyZj0HFwzHf/2QNPMaa3c34pezZrCukoDNEADU25gk/NpuHfpxv1Kr+kAAACYRtanb7BJrt2F0g+nswGgKfJFawWOTwZzQAM0UMcGNtugH50/OPKI0ms5AAAAZoAVva4Cm07G5ObgvJloAagzE1YdakVvZj1hPaUBGqCBSTdwYa+kx5VexwEAADBDeleMzmWTXL8LJSPx0zPVBFBX1scPlj42GcwBDdBAzRr4qQvpuaXXbwAAAMwwI+n4Cmw+GZOfg3+c6TaAOpkn0VjRu1hPWE9pgAZoYJcauMmEtIQPfAAAAHQJ49NpbJRreLHk9f2l2wGqxAT9TPHjksEc0AANVL+BrUbSJ+YOp4eXXrcBAADQQU7S2RXYjDImOQdO9H2d7ASoMjc8+mgruoW1hLWUBmiABnbcgBG9tM+3jy69ZgMAAKCA/DVZNsv1u2DiBiDwO8brF0ofkwzmgAZooMIN3GiDvoGf+wIAAHQxK3p1BTamjMnPwUdKtwNUQd9yfWz+SRvrCOsoDdAADTygga026Flzlo0eXnqtBgAAQEGLlqzci5/N1fOCyYl+tmQ7QFVY0fNKH48M5oAGaKCCDfzEDcenlV6jAQAAUAH9y9RWYIPKmMoNwJC+WrofoDQT0iIruo11hHWUBmiABn7TQLre+fj6np6xWaXXaAAAAFSEkXgMG+baXjT9pHQ/QGlW9IIKHIsM5oAGaKAKDWy1If6zO/WyQ0qvzQAAAKgY69PLKrBhZUxtDq4t3Q9QkvP6dNYP1k8aoAEa0DHrdXVeEzkrAQAA4EEZia9h41zbi6etrrVh3wf/lwWaz4l+uwLHIYM5oAEaKNnAPcan0xa24t6l12QAAABUmAlpCRv3+l689Q+2H1+6IaAEM9x+Zunjj8Ec0AANFG3A6w/McHwMZyEAAAA8JOf1VC5g6nsBYyS+/KH/lYHmsT59o/Txx2AOaIAGCjVwhwkqPYvHZpdeiwEAAFATzqdhLmBqfAET9N2lGwIKffm3/PHHYA5ogAY63YBP3+hfppYzDwAAACbFir6HC5gaX8D49I3J/YsD9WdFv1z82GMwBzRAA51t4Ob82pbS6y8AAABqynk9nYuYWl/E/KqnZ2xW6Y6ATjESF+YP4FTg2GMwBzRAA51pwOt/DAyvO4IzDQAAAKYsfzmODXy9L+L6fXvB1AsA6sWG9PnSxxyDOaABGuhEA8brVVb0xaXXXQAAADRAfok0FzL1vpBxIb6pdEdAJ5iwdp4V3Vz6mGMwBzRAAzN+bpd0tgmrDuXsAgAAgGlhJJ7MhUzdL2TSOdNTA1Bt1usnyx9vDOaABmhgRhu4wQY9ofR6CwAAgIaxXk9iI1/7i7lf9Swem126JWAmzfUjvVb0ngocbwzmgAZoYEYacBK/1r989ZGcTQAAADDtrKRXsZGv/8Vcv+izp78OoDqsjx8sfZwxmAMaoIEZauAOI3oKH/UCAADAjHES/4QLmkZc0PzjzFUClDVP1h9sRTdV4DhjMAc0QAPT3cD38/tNOc8AAABgRjmvT2cz34gLuitmthSgHBt0qALHGIM5oAEamM4G7skfYuMVHgAAAOjke7W4qGnAHLjh+LTOVAN0zqIlK/cyXq8qfXwxmAMaoIFpbGCNHdIncy4BAABA5ywem21Ft3Bh04ALm6Af7WA5QEc4n15b/NhiMAc0QAPT08A2F9IZAyev24dTCAAAADrOiv6Si5tGXNz8iosKNMvYLBt0pALHFoM5oAEa2L0Ggl7phtJzSq+qAAAA6GJG9FI29g25uAvpT0v3BEyX/pCOK35MMZgDGqCB3W/gPBNWHcrZAQAAAEVZ0XO5wGnMBc6FZWsCpk/uuQLHFIM5oAEamGoD945/6KNnbBbnBgAAABRnJf4Dm/vGXOBt61uujy3dFLC7eiU9rgLHE4M5oAEamGoDl/f59tGcDQAAAFAZLqTXscFv1EXev5RuCthd1usnK3AsMZgDGqCBqTTwZX7yCwAAgMoxIS1ig9+oi7w75ywbPbx0V8BU5QtnK3p7BY4lBnNAAzQwmQbuNqKnsPoDAACgklxrw75WdAub/OZc6LkQW6W7AqbKiPrSxxCDOaABGphkA6NuKD6RlR8AAACVZiUmLnYadbFzCz8/Qi0tHpttRa+owDHEYA5ogAZ2sYH4pXmy/uDSyycAAADwkJyks9noN+tiz0h610P/ywPVYoOeUPrYYTAHNEADu9jAXfzkFwAAALVivb6TC57GXfDczBMJqBsr+p0KHDsM5oAGaOChGohG4sLSayYAAAAwKf0hHcdmv4EXfEHfPbkSgHLyxbQV3Vb8uGEwBzRAAzttIJ1zRCs+jPMFAAAAaidvZK3oZi56GnfRc0fvitG5pfsCdoXx+vEKHDMM5oAGaGBHDWxxkkJPz9gsVnUAAADUlhG9lE1/Ey/80qdKtwXs4h8hbi1/vDCYAxqggQdtYFOfjy9hNQcAAEDtWUkfYNPfyAu/rXZIn1y6L2BnXIhvqsCxwmAOaIAGHthA0JG+odXzWcUBAADQCGYo/TEb/8Ze/H2ndF/AzljRn1bgOGEwBzRAA/dpwIl+8cjBkQNYwQEAANAY+Yux+f02bP6beQHoRF9ZujHgwfSFNU8pfXwwmAMaoIH7NbDFBBXe9wcAAIBGsqI/4yKosRdBVw+01h1UujHg/qzXT1bg+GAwBzRAA79p4Ff9IR3Hag0AAIDGspLeywVAcy8Cjeg/lW4M+L8W+PaBfPyj/NrAYA5oYKKBoCMmrJ3HSg0AAIBG6/fxqVwENPpCcLMN8UmlOwN+g49/FF8TGMwBDfyugf/kfX8AAADoEmOz8k9FuSBq9AXRZYuWrNyrdGlAlnuswDHBYA5ooLsb2GpEPe/7AwAAQFcxXj9egc04YwbnwPh0WunOAD7+wTrHWk8DFWjgDhv0BFZkAAAAdB0X4gsqsCFnzOwcbLZD+uTSraG7WdGPcayz1tEADRRs4Nr8h4jSayEAAABQxMDJ6/axopu4KGv8RdlP+SkwSnGtDfta0ZsrcBwwmAMa6MYGvK7uX6aWswAAAAC6mhP9YvHNOaMDc5DeW7o1dCcj8eUc46xzNEADJRowot+aJ+sPLr0OAgAAAMXl9+FwYdYVF2ZbbYjPL90buo8V/a8K9M9gDmig+xr4V55+BwAAACYsbMW9reiNFdioM2Z8DuLG3qXxMOJHp7iheNT4eyg5vlnfaIAGOtfANhdii5UeAAAAuB8r6cNcnHXNxdm5HADoFCPqK9A8gzmgge5p4C4n+kpWeQAAAOBBmJAWVWDTzujQHDiJb+RAQCdY0cixzdpGAzTQoQau7ffxqazuAAAAwE7YoCNcpHXNRdrdfb59NAcEZlK+EK9A6wzmgAa6o4E1LiTHqg4AAAA8BCtxsAIbeEbn5uAKE1YdyoGBmWJFP8IxzZpGAzTQgQa+w5d+AQAAgF3Uv3z1kbysv+su1C7o6RmbtauNALusddGeVtL1FWicwRzQQIMbMKJfca0N+7I6AwAAAJNgRc8rvZlndHoO4vLJNALsChfiCziWWc9ogAZmuIF/6Vk8NptVGQAAAJgkF9JzuWDrugu2rUbS8ZNtBdgZG/SsCrTNYA5ooKENuJDO4Al2AAAAYDdY0ctKb+wZHZ+DW43EhbvTDfAb+ed4VvQWjmPWMhqggRloYJsNOsSKCwAAAOwm5+PruWjryou2K+YsGz18d/sBjI9/VoGeGcwBDTSvgS3W60mssgAAAMC0Pb3Dy/u7cRjRb+WPN0xHR+heVuKXSrfMYA5ooHEN3NXn40tKr28AAABAoziJf1OBzT6jxBx4/WTp/lBfA611B1nROzl+Wb9ogAamsYHbTNBjS69vAAAAQOPY5fpIK3oPF3DdegEXB0s3iHpyPr22fL8M5oAGGtTAdTbEJ5Ve2wCU17t04375GsUNjz7aeX26C/EFTvSVJqQlVtLbTVAxIf6d83q68/FMK/qvTtLZRvQrVvRC6+PFVnSlFW1biet/N/Q6K3rzg4zbdrI23f9/9qb7/u+M643o2vyfZ0QvHf/PD3p+/u9jJX0q//fL/z2t6HvG/3v7+Lb8f0f+v8dJ/JP8YUY3FJ/oQnLu1MsOKT33AICGc6KfrcDGn1FmDrbm97iVbhD1Y336Bsct6xYN0MA0NbChd0gfVXpdAzD9TFh1aL6R1zcUn2VCXGx9emv+BZLx+nEn+sX8WpqJG2ejEzfo7ubcMn6j8Qor+jMrepEVPc9I/LQR/Sfj02lO9K9t0BOMxGP6fXvBPFl/MO0CAHZJPimPv3CbC5luvZi9M/91lcMFuyr/hZonh4sftwzmoBkNeF3d59tzWIGBenFD8ai+sOYp229E6Sn5Kbd8k8p6/fr2p+/iRvYKHV1P756Y85X538AE/Yz1+v7xJya9/kV+0jD/oSU/XVm6HQBAYdbrfxS/CGCUnIMb8l8QS3eIesgbSY5X1iwaoIFpaOAyvkoPVM8C3z7QSFxoJb5w/Ge3Qd+9/RdD8X+s6Dqe0qv9+n+jlfTz/FPl/CSmFX2HET3Rhvj8fD0wp7Vy/9INAgBmkBmOj8k/B63ACYlRag6CXtm7YnTuTHaGZsg/ReFYZa2iARrYzQZ+Nnc4Pbz0egZ0q/yzURPSovwuunwDaPwGn9cf5D8Ks76zvm9/16H+xHj9Qn7fog36Buvbz+tfprZn8djs0v0CAHZTXuA54Xf5Cd/r6t6l8bDdbQnNdUQrPoyv/1bgWGUwB/VuYCXnGmDmudaGffPHdVzQV1ivK8Z/Fip6CTf5iq+BdR/5A5Kj4++DDvrR/DNj5/WlvZIeN3Dyun04tgGgBngKsPjJtCrjR0cOjhxQukdUk5H48go0ymAOaKC+DfyUm3/ANFs8NtuEtfOMpOPzl2Zt0LMmvoLLBzXKr3ndOK7JX0N2IZ2Rf0Jugh6b3xnJcQ8AFbP9k/XFTxqM0nPg9esLW3Hv0j2ieqzofxbvk8Ec0EAtG8hf+swfESq9jgF1ln+CaUVfPH6jT/RzE1+K5UZfBdY4xkPNQbo+f9XYSPpE/nBMf0jHzZNoSh9TANC1tr/wl3cBcgIfP1F/uad10Z6lm0R15J91WNFNHB9s8mmABqbQwEoTVh1aeh0D6iR/ITs/1edCbOUPNkzcQGENZg6a1sAm6+PFzscz841BI/EYPkQCAB3CF4GLnwSrND7X0xrbo1PtodryRUgFmmQwBzRQtwa8/mCgte6g0msYUGX5J5JmKP3x72726bXFj10Gc1CugS1WVPMvT5ykkL9IzccKAWAGuJCcFb2Lkz4n/YkLt0/29IzNmonWUC9W0qdYF1gXaIAGJnfzL168wLcPLL1+AVV7ot4Mt59pRL0R/YoV/SVrK2srDezy14m/47yebkP6U35CDADTwHp9PychNiK/ayB9mJuAXa41tgc/PWJN4LxAA5NpwAX9Hjf/gJ6e/PP3/HSfkfj3VvT7/KGdtZTz6XQ2MP7T+AuMT6fZYX3R/MGRR7DuAMAk5Jd0W9EbOTmxQfltA0E/yk3A7uW8Pp31gPWABmrVQP4owKgN+r9W9Nz8biUr6b1W0tuNpL8yIS7Oo8/Hl+QvNP7f4UJ8wW/+/9ugb7BBh8b/3wb96MSHgPINjCt29uEBI/rdI1rxYaXXLqDUr2mMxNcYrx+3Xlfzfu3i6yGj2+Yg6JVW0jn5KVvr28/jNRQA8BCMxJOLL96Mqs3Bv/JOwO5kg/5tBfpjMAc08MB3JLWdpLONpHeN33AYbj8zfzigU3+wyU9auOH4NCN64sQNxnOc6BePHBw5oBP/+UB1PtahJ9qgZ/FzXtZpztWVbGCLlfRzK/oRK+lV81es7iu9bgBApSxasnIvI7q2Ags2o0pzENLn+Tpw95nYNJXvj8EcdG8D26xotKL/Yr2e1OfbR/cu3bhf6bUB6Eb56dbxD3aIfsiKrqnA+sBgDmhgkg0Yr1eNf/zSp7e6ofjEnsVjs0uvLQBQlA16AidUTqgPbCCdk28Ql60TnZJfsMw6wDpAAx1vYKsRvXT7U3X64vwOMVY9oJDWRXtaSc/IT9rmd1ta0XtZEzkv0kDjGthkJH4zH+c2xOfzJDuArjSx0Sm9IDMqNgcupK/mr9iV7hMzz3p9c+neGMxBF73I/HP550lzlo0ezvoGlGOX6yPz+zKt6Hn5xkD59YHBHNBAhxu4J18HuxBbbig9h+seAF0hPxJtRTdz0uWke/8G8l/J+Ala81nR/+L45/ingRlr4Jr8pfX87j7esQpUYs/7DifpxxM/u2ftYw5ogAZ+08AdTvTbzqfh/O5bXokEoLGM6D+x+LMBePAG4v/wiHxz5X9bK3oXxz/HPw1MawM35S/q5icKuOkHlJOf6HGS/t/EF65/wTrHuY4GaGASDWyyQc93QZf2y5oncD4H0BgLfPtAvmrGCXGHJ0AfLx5orTuodKeYfn0+voTNMMc+DUxLA9vyH0xcSH/Oz4iAcvLP652Pr7ei51rR21jfOMfRAA1MUwM3WtH/dD69tn/56iNZ5wHUmglxMSdITpA7aiD/XGb+4MgjSneK6TXxVASbY+aABnbjaT/n9fTeIX0U6xNQRu/SeFj+erYR/RavtWE955xOAx1oIL9C4Kf5Q159Q/FZ/FwYQC1Zr1/npMlJcycNXNHv2wtKd4rpY0XXccxzzNPAVBqI643oKbwiAShjnqw/2IiemH+il1/mzzrGuYwGaKBgA7fntciEtGT+itV9nBcA1EJviAO8D4yT50Oc4G5wXp9eulXsPjvY7mezzPFOA5Nu4BIb9ATeBQQUemWNj692IX3Vit7N+sU5jAZooJINBB1xou9zIT23Z/HYbM4XACrL+HRa8UWTUfU5uMNJ/JPSrWL3OIlvrEBLDOagLg38qD+k41h3gM5yrQ37Gokvn3inHx+tKr8WMpgDGphcAzeZoJ/Jfzyc01q5P+cQAJWSX15uRduc3Di5PUQDW6zoW0r3iqmbuJhiE8cc0MBOGnCiq/I7cnt6xmax3gCdY0Ja5EI6Y+LF+6xTzAEN0EATGsh/xLgwv0LELtdHck4BUAnG6x9VYIFk1GAOjMS/58K4hhaPzbaivy7dD4M5qGwDQa/MTx2xvgGd0+fbc4yot6JafA1gMAc0QAMz28AWI/pdK+ntJqydx7kGQFHG6xc48XHi28UGPrewFfcuWywmI7/HkeOb45sGHrSB263oO3qXbtyPeW3g7gAAIABJREFUVQXozC9P8lO2Ex+iy78uYG1iDmiABrqwgfRz59Nwfkc35x4ARf4Ka0U3lV8MGTWZg+/kL/J1vlRMhZH0rgo0w2AOqtTANhv0rHzuY1UBZl6fbx9tRT9iRX9VgeOfwRzQAA1UqYEfuaBLe1eMzuV8BKBjjI9vq8ACyKjLHAQdmetHejlEq8/6eHHxXhjMQXUauNz4+Ielj0ug6fKTtc7H11vRlRU47hnMAQ3QQNUb2Jp/JuxE/3pgeN0RpddwAE23eGy2Eb20Aosfoz5zcI0dTn9QOl3s2JGDIwdY0Xsr0AqDOSjdwGYr6QN8lQ+YWfn9VvlY42m/4msegzmggfo2sNmIfiv/EcWdetkhnLcAzAjrV//+xBeLSi96jPrMwd3W60kcktXER36KHx+MKsyB19X5K6Olj0egsVpje9hhfdHEu/22Fj/mGcwBDdBAcxq4y4l+Ma+x+YGd0ss9gIaZ+CJb6YWOUbM5cD6euWjJyr1K94v7spLeW7oNBnNQsIFteW3iqT9gZuT3AZuQlvAlX9Z5zvU0QAMdaeAaF9IZNsQncV4DMD0Wj822oj9kEedEPoUGLuKdFdXC+/84jrt4Lb/WhfiC0scg0ER9y/WxVvTfrOidFTjWGcwBDdBANzbwE+vTW+cOp4eXPicAqDk3PPpofgpcfFGv69jghuITSzeMnp781JMVvacCTTCYg8424PXr/DECmH79os+2oheMf0mbdY1zGw3QAA1UoYG81z/XSDqeX2MBmDIrcbACCxqjnnNwh5H4cg6/skzQYyvQAoM56GQDW12Irfw+MtYfYBrf7+fTy/h1COczzmc0QANVbyBd70Tf1xviAOdAAJPf8Il+v/xCxqjpHGxzXk/nZbXlWNH3VKADBnPQqQY22aAnFDzkgEZZ2Ip7G9ETeb8f5zHOYzRAAzVswMeLTYiLeSoQwC7r9+0FvN+lAgt4vccF+SXhHHadxw384u0zOjQHTnRV39Dq+awzwO5zp152iPNpOL9Hk3WMdZwGaIAGat/AteMfBRxs93OOBPCQrKS3V2DhYtR7Dkb7Zc0TONw6p3fpxv14/1/x7hkdmAMj8ZsLfPtA1hdg9+T3Zlqv77eit7J+sX7TAA3QQOMa2Gp9+obz+tKe1kV7cs4EsJN3v8SLK7BoMeo9B3e5EN/EYdYZLqTnVuDfnMEczGgDzscz2cQCu6d3aTzMhPh3VvQ21izOWzRAAzTQFQ1cbYO+e/6K1X2cQwE8gAlr57ExLL5QN2V8Of+8iMNsZlmJyyvwb81gDmaqgW3jH/sAMGX5yVkTVKzor1mrOF/RAA3QQFc2sNUGPT9/OJDTKYD7cF5PrcAixWjGHKyzQ/pkDrGZ4yR+rQL/zgzmYCYa2Gwkvob1A5j6jT8r+g4rejNrFOcpGqABGqCBiQZ+5nx8vWtt2JfzK4DtPwUO+r+cJDhJTFMDd1uf3sqhNRPGZlnRGzhWOVYb2MA91qeXsW4Ak3fk4MgBE0/83VSBY5nR7DnYYiVdP/EF6e8b0a9Y0X/Lr22wkj7svJ6eR74RbSS967f/30E/mv9nTNDP5KeSrNcf5PdI02zxf09GV83B+LH7nj7fnsO5Fuhy+T0BVvRX5RcmRnPmIH6JrwRPr94hfVT5f1cGczDtDdxpJb5wmg8XoPEGTl63jwu6dOKijrWJOZiuBq51ot82kj7hJAUn+krn9eluKB41IyG3xvaY60d6jcRjjNe/sF7fmW8qGtHv8jQrxzXH9Yw0cK/1+h9uOD5tRo5pAPWQn75gkeVEO803Adf3hTVPKd12UxjREzlGOUYb1sDt1refV/rYAuplbJaR+HIrekUFjmFGfedgm5WYxp/Kk/T2/K6w+YMjj+ip5kMKL843I43XL1jRX1Rg7hjMQVMa+JGV9KqFrbh36WMdQAH5r30VWIgYzZqDe4zoKfmChYN6N49Prx+vwL8ngzmYrgbu4uXUwOTY4fQHEz+fZC1mDia9H8s/280/yTWSjp+zbPTwuh5/vStG57qgr3AhnWFFfzr+wQN6YE2ggd1p4JdW4uBAa91BpY9vAB3Uu3TjflY0soByEp3uBvLHK/qXrz6SA3rqrKSfc2xybDakgXvzBSjrAbCL6/9gu3/i6adtFTh+GXWZg6BXjv+U1+tLj2jFhzX1eMs3M/NTTFb0c7wruQLdMeo8B7eMv7dzuT6y9HENoEP6B9uPz09mVGABYjRvDm7gRf+79XXHLRX4N2QwB7vbwJb888VpPnUBjeROvewQ6/X94x/YYu3h/LNrDVzmfBo2w/ExPd2oNbZHfv2Mkfj3+VU0HDccNzQwpQbutl4/2e/bC0of0gA6IH/FlcWSE+aMNRD0rHxRw8E8iWMyxOdzTHJMNqKBEP+SYx/YuUVLVu5lRd9iRW8sfswyqj8HXlfnj2dwsf5Afb59tJX0gfw0ZPF/JwZzUL8G8s/rz8uvn+C8DTTa2Kz8k80KLDqMhs6B8XoV7//adUbUl/43YzAHu91A0HfP4IkLaIR8bswfZ2DNZc19iAZusaIfsyE+qXSz9TA2K390Kn/9lCdqObZYXyffgAv6ve2vb+G97kCD36ehV7NAcpKcwQa22RD/eU5r5f6le686J/pFjkWOxZo38Dk2jcBO1vmheNTEzYnSxyqj2nNwiQvpdeydpm7ucHq483oq7z0v3jKjnnOwxoie2NO6aE/O6UDDuKH0HN47VnyR7YIR1/cNxWeV7r3KeI9N6UYZuzMHRvS7Ayev26f0cQRUUmtsj3wxZUVvYq1hrd1BA1tt0POd16eXzrVpjMRj8tzygR2OPdbfSTewwYiewv4OaJjtL9HlpMAczHgDm62k9y5sxb1LN1/Jl8Dz5UeOwfquw5f3Lo2HlT6OgCoyIS2yoj+pwHHKqOYc3GpF/7F/mdrSrXbDRxCd6Get6D0V+HdnMAf1aSDolU7iG7mGA5r1IuofFV9cGN0yB5f1Snpc6e6rhA+AFG+SMfU5uCNfVJU+hoCqmSfrD7aSPsyvLFhfd7B23prfmcoH0zpv/orVfUbSJ6zovZz/OT5pYHLvd88fEnWtDfsWOHQBTCcT1s6beNkwCyFz0IkG7jY+ncYj5dvZoEN0x9pTywZ8fDVnY+C+nOgrreg1xY9PRhXn4HYn+r78jjqOm7LsYLvfSvrU9l+oFO+CwRzUqYGr8zs2eU8pUHPO60v5GWLxBbXbhvJuwJ4e4/ULFfi3YDAHk2rAhXRG6fMWUCXzJBoj8ZusJZxPHqSBzfmjaP3LVx9ZulPcV++QPspJOpvjluOWBibdwHVW4uCRg/+fvTOPsuOo7n9L8optDF5BmunqkQQGFCCOgQBhC2ENe7AIS2LAgCEQg4Vm+tZIAR4EEhOWQLAxkPzYwmrHYQeDwWILZpFt5FHdGnmQBciAdxvbeJX0fqekITG2JI9m3ntV3fX5nHP/yTlJrNuf+61+Pd1V6w4gVwAaCvsBsvhFWPy2mVr/vbQX3rPIFCM6xU0Hs9cwB84N20fEnh2AVDDWHc+XFNFzKckqRb9ejrsHxHYUZnQw4vmxfaHoQQMduLy0KjwIBGjuSXVfTyBIqPx6cOn2I+ez3CdKtyXQf4oezNSB68MbE7FnByAFwhtdpejnyQ/WkDs6UIpeVFq3PLajMKsTu3/DTDPTOLDHDlwRHgSyRyBAwwj7koRjvwk9Fr5IDny1sr4qMmFE9DHMGrPWJAeq2r849twApEB4uGNEr4w9k1RyPbi1qvVk9jlu9h9nwzYXRnRrAj5R9KBxh4WU1p9QdNbsFXuWAWCGjNTuYeGghtgBQmXbgxsr6zo5HDdvRF+TQL8pejBDB9x/xZ4ZgNgsGV13hBE9k9xg7biTA7X+oBS3LLaj0BtKcY8y4jyzzqzjwGwccH77112d7nwyCaABmFpfTdix4EV24PzhevIhRYspaz2NOWPOGuLAJTnv1QkQKGt3bNjvKIF5pNLqwfWluBP5ods+wueMpXX/xGnB0WeMam4Pzjfj+pexZxkAZoCx+vEEQoPKuwdbKtH3hM8x2ji0pnbfT6DHFD24SweGa/fM2PMCEIuhFe4QI/ppsoL1YicO/HDIuqVMZ7upan24EbeRDCADcGCWDtTu+2Hro9izDAC7YWFn7d2M1XUEHYtdfAf8ZaX4V7Trr+vdeZwaGdsraiY9qEQ/G3taAGJhxv0jjOgvyAvy8g4ObDGi/8g+V/lwVD15UGn1o2QBWYADs3egFHdWaf0xsecZAHZB+KumEb2GoGOxS8SB89vy16ORlWoS6CdFD+7KgavCSaex5wVg8HTnhRMN+fSPdeJOuWj152F/OKYyT0pxz+O3EbnA/eOcHNhmxH0y/BaKPc8AsBPCp187BpWwpwdpOFCJP73pi4YRfVrsPlL04C5nzfqXxJ4VgEGzdHzqcFP7r5GRZOTO3l5ZNO4PZSrzZnhsYglfSZEPrBFzduCmSvTtbd3qCaDRGPFvI+RY6BJz4JbK+veGTzKKBlKJtwn0kKIHu3ag1h+Et6BizwrAIKnG/GPDoTdkA+vDHRzYVtV6crG8u4CJhO1Z0dm0nxH/YbKCrMCBOTtwVXjjfumJU/uSLgCpsLy7oBL9BgHHIpeeA26zqd2Lmvagwlj/ifi9o+jBLh3YOmzXPzT2nAAMjOXdBaX4N07v7UY20IPbO3BtJe5ZTCPsjLJ2rzWitzIz5CYOzNmBDcbqc0gagIROwStFLyLcWOCSdKDWH4zU7mFFQ+DTkQScoXbZg1LcR2LPCMCgMKv03kb8t8gEcnEnDmwqx90DmEbYbYZY93j2BSQ/WEN640Bl9btN+k0H0Gqq8Q33Y4FjgUt4gdsaTmhbVK8bKlKm051vRG9MoF8UPdiZA9eHByKxxwRgcJ/x6a/JAtaDO/8hRH/EIUgwU8KD4vDAmCwhS3CgJw5sM7V+qrK+IoUAIjNi/ZM4FY/FLfHF7SYj7l0LV244rEiQsJgl0COKHuzKgX+IPSMAgyT84Yg8YE24gwOfW9hZezcmEfaEaszdqxL/Y/KEPMGBnjlws6n1X5Z2pu5OGgFEpBR3IsHG4tYAB66rrOuktmhMP0SP3RuKHuzMgSuaerAOwGwxo5Mj4WApMoF1YbsDVk8Nb+ozUTAbwoNjU+tXyBPyBAd66sBvqtq/uGl7vgO0CiP6foKNxa1Jp0sNrdi8f5EAPECP7gO1yx640djzARCDstbTyAaysRJ9OxMIc2VZx+1jxJ9BppApONBzB35S1fpwUgogAsecsHZvI+4cgo3FrUEOXFKJe2VwN2ZoGNFTEugFRQ/u6MBv+ORtMAdqmXH/iNK65VXtXhr+IFCJt1WtJ5fi/jn8scKIvqay/iXG+ueG05gXy8aDB/CfljVDqzcsYm/WvNeFcAp0bA+hRSzvLjBWPx7ba4oetNCBrUb0Q0vHpw6PPeYAef6QEZ1KIAgoerAnDkwZ8S+M9YlPJfoNrhczm5oDZe1eG2Me2sz2/T6tO96I/ocR/V74xHoO1ygcVLFm+5tqtXvRcD25MPa/r22EvWNjzyEVpQfbjPjXx/YPWkinO5+3i8k1cr1vDlwd/ohadNbsFXvUAXI8Gfhawo0FrnEOWF1Xin/GoGfGiP4i+r+dogd/4IC/LJVP5JtM6GEp7nlG9P8Z0Yv7P2fOh+04Qo7FfrO5DSwZXXcEbwFmmI3Wr4ztHrSZ7ryqdh+M7jlFD1rqQCV6YWX942JPOkBWVOKfbES3xA4Aih7M0oFzdzwI7P/Gsts3h97x6jq+0oNkHChr/6Z+u99euvNGRB8z/ZZfzD+GXV5Z/97hevIhsTvSZMJnRbHnkRpg9vHZLwyCTne+Ef1PZpt8w4H+OVCJfnbJ6olhQg1gQITPxwg1Fram/wVpx6fB/XuVfETWPzj2v5OiB3dw4Eb2UZn1234nGnEbE5yp9WEPQd4K3HNKcct2fBIa/RpS/V/zOfADBkdnzV5G9Exmm2zDgb46cIOpdfXSE6f2Jd4ABgCvuLOotWNRcxsr615VdTbt1+sZKWt3bPx/H0UP/s+BUvwHeu15mzmqnjyoFK2N6KUN8GhTJfp3/ciyNlOKOyuBa0f1swdWT43tGWR6OnDtv8Z8k2840G8HnK/G/GNjzzxA6+FkYBa0li1ovzFWx8IP/l7NiBE3msC/i6IHv3dg20g9eVSv/G41nTV7VbWeZESvaqA/v6pq/+JBbHPQBirrnpLANaP61INS9POxDgEDOLzjDjSi5zPjZBwO9N2BbaW4jywa94eSPAB9ZLFsPDgcrkCosbC1yIHrwt5aZpXee67zYURPSeDfQ9GD3ztwdm+Sv92EffUq8T9ugTffGxL/wNj9bALcx7S21h45uu6A2H5B3oT7SQ6Ei54FVD49uLq0/gT+CArQRxaLK43oJQkMPEUPeunA78KDwOD3bGfDiH6Za8JcpuJAOLG2t+nfLqqTLrjH9Im+bdoT7lZTu3dy6vNdXHvrXpXAtaJ624NN1Zi714DiA2C3jIxOPsiI/pY5J+dwYFAOuHP46gWgj0wfdsDCxsLWxoXt1tLqR411R+/pXBhRl8B/P0UPggOXh/2I+rMCNJ9hu/6hRvTi1rpidR03wnf58Pem6NeJ6lUPfluOuwcMMEIAZrrdwBbmnKzDgYE5cHNlXYdDQgD6RGX948KgEWosbC12YG0petxMTw7efjpV/P9mih50jfh3sPjtnPCpiBG9JQNPrje1exEe7OIeRvzpCVwjau492Gasfy6eQ4pU4i1zTs7hwMAdmCprfWLs+QdoJeHHRcs+n6Lowc4c2GSsXxneGtnVLCwZXXcEvWN+UnGgGt9wv8GuBukTPos14v4r9rUZdJW1njbTP2LkRDnmnx772lC96IF/W2yXAHZNd54R/W9mnbzDgYE7sC180bVw5YbDSCiAHlNaFUKNhS0TB64vxX8gfAJ/xzmoxt2fJvDfR9GD4MB5LHR3XKcuvGc4JCNXPyrrv8C+gH/IMSes3duIvyz2taFm34NS9OvF8u4C8g5SZmln6u5GnGfWyTsciOLAFZX1L4idAwCtw1j3PkKNhS0rB2r9Qfg8uOps2i/MQGX1r6P/N1H0IPwotiqx14SUGK4nF5paJ5gP/V54EBr7eqREKfqveNHQ3LT680Xj/tDYDgHMBFNP3J9tYhLIDSrnHnyOg6IAesny7gJecY8ebFScHlwZTt00Vk/lGuBgAg5sM6OTIyxwOyjtRYu3f8If/7qkUbVOhO0K8GMHZkz/JPo1oWbTgy3DY+7ReAxNwlh9OfNO5uFAVAeuKsX9TewsAGgN4U2onD+xougBDuBAbAdK0R/FXgtSYen41OFGdDL2NUmurK7b3X6mucED4kbWP8b2BmA2VKKfTWB+KHqQuwNfXVSvGyLFAHpA+ByDfS6ihxpFD3AgWwfcKIvZ7/dc0vPiX49k6+xlHbcPrmx/AHhKAteD2oM/coT9G3EXmvs7SS9h5sk8HIjuwNVV7V8cOxMAWkFlfWVEf02wRQ82ih7gQG4O1BP3LzJn6YlT+xrx34p+LRKvstbPFJ3u/CJzKuueEvtaUDPuwfVDY3qf2M4AzIWydn9hRLcy92QfDiThwJeHVm9YRKoBzBFj3dFG9LoEhpqiBziAA7k4sInFK/y40tMSuBbNqFrfkLsz09uX3BD9WlB32YOydq+N7QtAL2CdIvPI/KQcuMZYdzzpBjBHTD3550b0pgSGmqIHOIADrXcg/KDIfeEqrVse+zo0rLaGt1GKzAmnAyZwLajd9+CH4cC52K4A9G6bCreZuSf3cCAdB0pxZy0WV5JyAHNgxPonGdFbYg80RQ9wAAfa7sBw7Z6Z84I1PDaxxIheG/s6NLAurcbcvYqMMbW+LIHrQO26B7eU4pbF9gSglxjRpzH35B4OJOfAb0vrTyDtAOZAZf0L2OsiephR9AAH2u3ALYd33IG5LlbhQAsO/Zi9P6Xo14uiO6/IlHAaYAIzTO2iB5V1ndiOAPQDI/4MZp/sw4EEHaj1U9VJF9yD5AOYJeFJuhHdFn2YKXqAAzjQTgfOzXmBKq1KAteg0VWKHldkjLH689jXgNppD6bCwT6x/QDoB8P15MJwuA2zT/7hQJIO/KKy/nGkH8AsKUVfl8AgU/QAB3CgfQ7U7p25Lk5LVk8M8wOqFx75y0p74T2LTAl/7Y8+x9SdelCKf0ZsNwD6SVX7cWaf/MOBZB3YVln/Xv4QBTBLjNW3JjDIFD3AARxolwNWn5PrwsQBDj116ZQiU0zt/z76HFN36IH/VmwvAAaxhUUpehHzTwbiQMIO1DoxMjr5IBIRYBYY8f8WfYgpeoADONAiB3I9xKGy7imxe9+y2mKsO7rIkPDvTqD/1P/14LbhVfpHsb0AGATG+ucy/+QfDiTvwI3hj4U575kMMEu680qrH01giCl6gAM40AYHfpbrclSJ/3EC/W9VVdZ/ociR5d0FRvS62P2n/rcH74+tBMAgMaJrmH8yEAca4EDtv5brH94B5nqjfWb0AaboAQ7gQMMdKGv9TI7L0Yj1T4rd+5bWtlw/czGiZyfQfyq8ZbFK7x3bB4BBYsQ/kvkn/3CgMQ5cUdX6bFISYM/3vPh6AgNM0QMcwIEGO+BW5bj4GKvfjt/7dlauD5XDYTqxe0/lfagR5I0R/SoZQA7iQHMcKGs9reps2i92dgA0hoWdtXcztft+7OGl6AEO4EBTHcjxlMxyfPLPYve95bVl8bi/b5EZxrrjE+h97nX90vGpw2O7kAWd7vxF4/7Q0l60eETWPzi8gVbW+sTS6hOqWh8e9mA0o5MjOZ8OPmiG68mHhLewE5hDih7gwMwduGBoTO8TOz8AGsNi2XiwET2PoCVocQAHcGDPHRhZqabIDGP9J3Clz3mR4VtY4aEHXsVdh0rr/im2B21keGxiSSnub4z4txnxZ1SiFxrRm/fkwawRXWtq/VQp/o2luOfxmXZ/KEU/Tw5xP4wDjXPgukr0+X2KBYD2Ef4CGZ6eJzC8FD3AARxokgPX5HYa2VH15EFG9IYEet/2+nXYr7fI7w+Ssfuec90wtMIdEtuDNjBcTy4sRY+bPnTvF/27Zs4bq6eWtTuWNwV7AyeSR88hih7M2gE+CQbY44eA/qeEDgsPDuAADszQgVp/kNtCU1n/EvwYTEZU4p9cZIYRvQS/Yq1B/t9iX/8mc+TougPKWv92+jCbrRGuYXij8MxK3LOOOWHt3rH70WQ4kIj7YNahRjvAJ8EAM2Xhyg2H8RAwemhR9AAHGuOA+2RuK4wR/634fc+krP9EkRn88I7m25awF13s699EqjH3x9Nv+l2fRG7sqCsq698b9hCM3Z8mUln3lASuIUUPcGD2DvBJMMBM4SEgYcuCiwM4MFMH/NtyWl1GVk0cGenNllzrhqUnTu1bZER4Cy2BvmdY/ozY175phEM7jOiXEz804rZK9GPV+Ib7xe5Xs+jOM7VOJHD9KHqAA3NwoBT/AU4JBpjpQ0Cr6whdQhcHcAAHdntj8YqcFpXK6l/jw2AzoRrzjy0yohStcWzw6044gCX2tW8KI7V7mBF3TsM83VqJPz0cRhK7f02B7S6iO0vRg145cMHicX/f2JkCkDxLx6cOnz6ljACmBziAAziwEwfKWp9YZERVuw8yCwN+MGNdp8iI6T3UyJtBOib+x7GvexMIB6SEt0ka/hb0TWXt38QbMXdNePs6fEqdwDWj6AEOzN2B68IfsQewVAA0Gx4CErgsujiAA7t2ILe/KJaiF+HDYDOhsvrdIiNKq0/AscE6ltubzHtOd15Vu5ca0ctb5OZUjocM7SlG3LsSuFYUPcCBXjlQu3cWnTV7xc4WgOQfArIPBsHL4osDOHBnB6qTLrhHkQmL6nVDOBAlB25Z2Fl7tyIThlfpH+HZQP26/qh68qDY1z3lt/4qcV9srZPWvS+3fUb3BFNP3D/6NaLoAQ702oGzF437Q2PnC0DS8CYgiw+LDw7gwJ0cuDW8GVJkwnDtnokDcXJg2K5/aJEJ4aYczwbq14diX/NUCXNnRC/OwMfzhqxbGrvfqWJq9/0ErhFFD3Cghw6Utf4yp3srgFmxZHTdEUZ0PQFMAOMADuDAdgcuzWk5MVbHuO5xZj/si1dkQ3eeEb0Z1wbjFj+Ado6xfmX4I09GHl5rav9XAx72RlCKHpfA9aHoAQ703oHfVda/IHbGACTNyKqJI42oI4QJYRzAARxQV2SEEf0Prnmkubf61iIjwl/mcW0gbk3GvtbJ0enOD5/FZurf1lLcibEvQWoc3nEHhgcFCVwfih7gQD8cYF9AgBk9BORNQBYhFiEcyNqBUvQ7Oa0X4TCK2D3PtSrxpxcZYcT52D3PoSpxb459rVNiWcftU9b6mdjXJXpt/4NDPttbzISQwdGvC0UPcKCfDrAvIMDuWLhyw2FGdC1BxGKEAziQsQNfzmmlCJ88J9DzTMv/tMgII3pB/J63v0pxy2Jf61Q4cnTdAZXoN2Jfk2Sq1n8vlncXxL4uqVDW7tjo14SiBzjQbwc2VWPuj2PnDUDir8T7bxFGLEg4gAOZOnBmkRFG9LYEep5puc1FRhjRc+P3vOVV60Ts65wKx5ywdm9T+69FvyaJVSn+A7GvTSqEk9jDidmxrwlFD3Cg7w7cVFn/ktiZA5As/MWUhYiFCAcyduDTRSZUnU37JdDvnOvaIiOMuHMS6HmrqxT/xtjXOZk9/2r9VOzrkWrhyf8R1vzY14OiBzgwKAf8O8L6EHF1Akh7z5TwJgyBxKKEAziQkwOl1Y8WmbB0fOrw2P3OvLbktCeXEf1qAj1vdY3I+gfHvs4pYETfHftapF4cDLKDcFpo7GtB0QMcGKADtf/aUfXkQZGXKYBE6azZy4j7JKHEwoQDOJCRAx8qMqG0Fy1OoN9ZV3jjvsgEI/rfsfvd8vpVTg+Ud0VZu9cmcC2aUFtGrH9SkTmLxv0af2nNAAAgAElEQVShoRcJXA+KHuDA4BxYO1xPLoydPwDpfkYh+iFCmVDGARzIwgGrpxaZMCT+gdH7nXmNrJo4ssgEPsnsu0//r8icYbv+oUb0lthz3Zzyl/EjeHs2/SD+taDoAQ4M0oGy1l/y1jzALunOM7V7J8FMMOMADrTdgZw2SB8a0/vE7nfuldNnKEb8GbH73eYKJ5oWGVOddME9jLiNsa9D48rqt3M/Gbis/ZuiXweKHuBADAeuL8U/I3YGASRLJe7NhBMLFA7gQJsdKMV9pMiEaszdK3a/M69tOW1GXVn/hQR63ta6LTwAKzKGfavn4I/VtxQZM1K7hyUwwxQ9wIE4DoQtAF4TO4cAksXU/u/DjxZCmpDGARxopwPuk0UmhP3n4vc767q+yIhS3FkJ9Lyt9cMiY4z4FyZwDZpcW4x1Rxd5b3d0bQLXgaIHOBDJgap2HwznH8SOI4AkqcS90ohuJaQJaRzAgfY54M8osqE7j83Po/r2qyIjjOia+PPd2np3kSnhM/owSwlcg0ZXJf7HOb2RfEf4A0V8Byl6ENuBkANLO1N3j51HAElS1vq34ZOT2INK0QMcwIHe/ghyXywywohexQxFmyFXZIQR/R9c69OPloz3/6tE34NXvfrx619RZIqp9Q14xP0kDuCAET1/aPWGRbEzCSBJKnHPMqI3EpaEJQ7gQHsccOcUGRHe+ojf8zwru4fNVtfF7nlra5Xeu8iQasz9MX+M7qlLVy5cueGwIkOMdY+PPscUPcCBVBy4JKwvsXMJIEmqWh9uRK9IYFApeoADODB3B6yuKzLCWP8JvImVHf4dsa//IOEzzX555DYWmWJEv0x+9danSvTtRYZM74l7Kz5xL40DODDtwPVlrU+MnU0ASVKKW2bEbSYwCUwcwIEWOHBJkRF89hTPtdw+tzOiNyUw322sTxf5vv3HoXS99+m6oRXukCJDjOgFCcwzRQ9wIB0HbgpfPMbOJoAkGa4nF/J5T/SQougBDszdgZuLjCjFPY+5iTM3w2Pu0UUmHN5xB+JZfzyqxNsiQyrxp+NUn5yyrlNkSCX6MZziPhIHcOAODmzJ7Q+2ADMm/MWQTb4JTRZOHGi6A+FhRS7Rb+qJ+8fud6a1ddG4P7TIhJGVahLoeVvraUVmjNSTR4UZSqD3ba2rw+nKRWYYcaMJ9J6iBziQngPbStE6dkYBJMnQis37V9Z/IYFBpegBDuDArByorK+KbOjOM6K/wZWB58UFRUaU1h+DY/1xacnqieEiM4zoKfjU94x6TZEZI9Y/Ca+4d8YBHNj93s3debGzCiA9lncXlOI/QIASoDiAA010YET0MUVGmFo/Fbvn+ZV7V5ERpvZ/Fb/nrayrisxY1nH7cPjcQNz6YZEZ4TTtBGaaogc4kLIDVj9+zAlr946dVwCpvlXyj9GHlKIHOIADe+hAVfsXFxkR9jZhTgY7J+WYf3qREUb863Gs9x5VVr9bZEZV67NxaTA5FT61LjLDiF6JX9w34gAO7H7t9V8IXz3GziuAJDG1vjpsnkmQEqQ4gANNcaCs/ZuKjBgem1gSu+eZ1W1LO1N3LzLCWPe+BPreuiqtfrTIDCN6Zuy+Z1NW31pkRiX+x9H7TtEDHEjegVL0O4tl48GxMwsgSYz1zw3HaMceVIoe4AAOzGxRdx8pMoNT3Ad50+jOKjLDiH6Z/O29S7md1hp+bIWT2nFpYHl1cZEZlehn8Yt7RRzAgRk6cEE15u4VO7cAkqQa8481otcQqAQqDuBA8g5Y/XaRGcb6ldH7nkvV7kVFZhjR9dH73sKqrH9JkRHDtXtm7J7nVkNjep8iI6paT47dc4oe4ECjHJjK8TAugBkxMjr5ICN6SQKDStEDHMCB3Tnwm0w3P2e7hv7PxXULO2vvVuREZ81evLXVH58q6x9XZEQl+h7WrsHev5TWn1BkRGXdq3CMe2QcwIE9dOBni8WVsfMLIEmGVm9YZETPJ1gJVhzAgZQdWDK67ogiM8KnqbH73vbKcc+2ctw9IHbf21ojK9UUGcFWBREyq9bPFBlRiX9y7Lmm6AEONNKBi3NbkwFmzOEdd6Cx+qUEBpWiBziAAzv/0WP1CbnFelm7Y/Ghv5lQintUkRmV6PPxqj8+LT1xat8iE5aOTx1uRLfh0qDvW/xlRdGdV2TCiKx/MI5xb4wDODBLBzZV1lexcwwgTZZ3F1TWv5eAJWBxAAdSdKCyuqLIjU53vhGdjN37tlZl9btFhhjxb4vd+5bW9UVGlGP+6Qn0PMsKJ8UXeX2pFL3nFD3AgYY6YPXnZnRyJHaWASRL2FvEiN4WfVgpeoADOJD5ScCBcKgAs9CfWRix/klFhvDGf9+yNasTWo24UbIp1jrtnlpkQtXZtB+ecT+IAzgwFwfKWn85ZN3S2HkGkCxG9GlhY3TClrDFARxIxgGr64oMOeaEtXuHTxii979lVYn/cZEpHP7VN6/WFhlhRD8Ue45zrdzeiDeiN8TuOUUPcKDZDvAQEGBGe264zbGHlaIHOIAD0w5sWdqZunvGb2YzC728ERzzTy8yJGyIjUt9+nEh7qwiI0rR7+BStB+ypxUZsf0TPtZA7gFwAAfm7sAlQ2N6n9iZBpAsw/XkQiN6HoFL4OIADqTgQDgNsMiR5d0F4e2i2P1vTdX6lSJTjPgXRu9/Syu301mN6KWxe55vuXOKjDDifxq/5xQ9wIGWOPCrkXryqNi5BpAsR46uO6AU/XwCw0rRAxzI3IFK3JuLTBmuJx9iRLfGvgYtqJsXj/v7FpliRN+fwDVoZZVWP1rkdUARJwDH802LjChFfxR7vil6gAOtcuDSUtyy2NkGkDDdeZV1nQSGlaIHOJC3A98sMqYU/4EErkGjqxT/xiJjKtELY1+DFteHikwI2zEk0O+c65IiI8KJ7Qn0nKIHONAqB/xlOf9BGGBGmFpfzQnBscOKogdZO3D9so7bJ9fIHlrhDjGiv07gOjS0nA8nShaZUtoL78lbpH117JQiE4ZWb1gUf56zrt8WGWFEz06g5xQ9wIH2OfAzs0rvHTvjAJLGiHsqJwRHDyuKHuTrQD3550XGhH9/OBAl+nVoXt04Mjr5oCJjSnHPS+A6tLneXWRC2D8pgX7nXFvD1zlFJhjRLyfQc4oe4EALHQhfRgytcIfEzjmApAnfzBvRqdgDS9EDHMjPgUr07UXmlLV/U+zr0LQKJykXmWPEfzj2dWhzVbWeXOS1J2n0nudch3fcgUUmGNH/jt1vih7gQKsdODenTAWYFeFJeSX6jQQGlqIHOJCTA1bXZR/bne588nfmzlSin83emaI7L5x8F31+W1w5PQA01h0du9+5V07bGXAYYXzfKHrQfgf8t3LKVYDZ0VmzV/jkJf7AUvQABzJyYFvYfyr32B5ZNXGkEbcxgeuRep1/VD15UJE5I7L+wQlci1ZXKfqvRSYMWbc0dr8zr1uLjDCiX02g5xQ9wIH2O3Bmsby7IHbmASRPKXqcEb0pgaGl6AEO5OCA1ZfHzr10foT7y6Jfj3TrZ+FBaezrlAJV7ccTuB7tLqunFln9ASKBnudbVxUZEd7MSaDnFD3AgSwc8B/OaY9VgLl+DvKL+ENL0QMcaLsDpbiziOsdhIMtjOg1sa9JgnX54nF/XzyZXqNFz0/gmrS9PpSLb0eOrjsggX7nW1Z/XmSEEf2f6D2n6AEO5OOAde+LnXsAjWC4nlwYNtGMPrQUPcCBtjtw28KVGw6LnXmpUNbuL8Iptwlcl1TqGjOmfxL7uqTC0JjeJ4Fr0vqqRD9W5LWn5G2xe57zqZVFRhjRn8TuOUUPcCA7B/4hdvYBNIKlJ07tW4r7SAJDS9EDHGixA5zq+oeU4h5lRK+OfV0SqF8NiX9gpCUwSUytqxO4LhmUP6PICPYgjeia1S8VGWFqnYg/3xQ9wIEMHXhN7PwDaAxVrSfx1+HooUXRgzY78M3YOZcaw6v0j4zoJQlcm1g1ObJSTezrkBrh5OwErk0G5c4pMoKDGaK69q4iIzjBPHa2UfQgWwe2luKeFzsDARpDafUJYaPiBIaXogc40D4HtlRj7l6xcy41wgMwI6oJXJ9B17l8Fn5nynH3gASuTSblf1pkRDj1OH7P86zc3oA3orfE7jlFD3AgWwduCl/ZxM5BgMYwPDaxhFf3owcXRQ9a6UBpVWJnXIoc3nEHGus/Efv6DKqq2n1wWcftE7vvKWJq987Y1yefcpuLjKhE/y5+z/Osasw/tsiExbLx4Nj9pugBDmTvwJUj9eRRsfMQoDEcVU8eZEQ/R3hmH54soDjQawemwmb0sTMuVYzVl7f8cJBrjNXnxO5zqoSHouE05ASuUy71uyIjwkOoBHqeZS0ZXXdEkQlmdHIkdr8peoADOGBEf5ZT9gL06MQ4typ8tkeIEqI4gAM9c6Ce/HMieteMjE4+yIhe0LaZK0W/E34Ycu13Tdi3JvZ1yq2OHF13QC5ODq3YvL8RvTl2zzOsySIjhu36hybQc4oe4AAOdI3oDxd21t4tdi4CNHFfQN5IIERZSHGgNw7U+qnYuZY8y7sLStHXGdHftsC7S0vR43jz866pRL+RwPXKqsKei0VGGKvfjt3zDOv9RUYM1+6ZCfScogc4gAPd6R6cWXS682NnI0CjWFSvGwobthMkLCY4gAM9cOBmXsmfGWaV3js8MG3o3G0xVk+tTrrgHn1eolrBkHVLw+l1CVy3rKqy7ilFRpTi3xi757lVad3yIiOm/3gVve8UPcABHDD/l8P/FDsbAZpHZ81eVa0nE6aEKQ7gwFwdqKzrxI60xn0WbPXjDdmS4dbw31qNb7hf7L41ifCwNIFrl11V4l5ZZMTwmHt07J5nVttyO+28sv69CfSdogc4gAPd2/egrPVvY+cjQCMx4l9oRG8gVFhYcAAH5uDA5WE/qth51jTC54pG9D+N6G0Jzt/NZa2nVdZXsfvUNBaN+0PDgRQJXMPsKre3Ao45Ye3eRvTK2H3PqM4tMqMS98UE+k7RAxzAge4denCTEf/I2BkJ0EhKccvCpsYEC4sLDuDAbB3I7c2bXjJcTy404kYr0QsTmMGwPcRrwkOs2H1pKqbWNyRwHfOsDPckNaKnRO97PvWaIjOM6PoE+k7RAxzAge5OenDpyEo1sXMSoJEcVU8eZMSfQbiwwOAADszSgUk25Z07I7L+wUb8O6YfBm4bwDyGz5B/Yqy+ZfG4v28P/glZU3U27RduSMnRSDlqdV2RGdW4+1N8G4hft+T3h5HuPL4S4p6QfMGBpB2wuu7I0XUHxE5LgAYv9P71iX6ORtEDHEjdAeufGzvF2kQ4XKUU97zwKW64wQmf5fbgOoUtH84L+zpV4p7FoR69xdT66uhzmHfdHPY4LjKDrzgG4tbniswYHptYksBMU/QAB3Cgu7seVKKfDc8xYmcmQNM3lf41YcOCgwM4sIcOrOctwD7S6c43o5Mjlfgnl7V7bTjIafvDwR37CH7OiJ49Xf89fcjI+0tx/xweSpW1+4slqyeG+/mflzvTb/9dQm5Gzs164v5FZlS1H8e7/npV1frsIjOM1efgFfeBOIADTXCgFK1jZyZAo1k6PnX49A/J6ANN0QMcaI4DlfUviJ1fADGorK6IPX9UOBnQHZvbBCyWjQcb0Wu5/v2aAedz/ONWZV0Hp8hVHMCBhjiw1Yh7auzcBGg2nTV7hb2oBrQPFUUPcKAdDkwWy7sLYscXwCAJ+8+w918aVYl7c472hzeCY/e+rVWKHldkyPa3yRPoP0UPcAAHzMx6cFX4UiZ2dgI0HiP6NCN6BeFL+OIADszEgcr6l8TOLYBBEj49IR8Tycfafy1H+0dWTRxpRG+M3v/21cU57isZMOI2JtB/ih7gAA50Z94D/9OFnbV3i52fAI1naPWGRcbqtwkgFiEcwIG7dsBtZvGFzLbMuIZsTCYbr851M/BwuE8C/W9VldafUGRIOPGYL4Di+0fRAxzQWeS2fjR2hgK0g053fmlVjOgWwogFCQdwYHcO5PoZHuRHVbsPkodp5WE1vuF+RYYMrXCH8MVGD12qdeKYE9buXWRIOCU+9hxT9AAHcMDMtgfWHR87RwFaQzXmH8tJhwQyizIO3IUDN46sVBM7rwD6SSlumRG9jTxMKw9z3oagFP+K2P1vSW0L97tFppjavTOBa0DRAxzAge4se3CTse7o2FkK0BoWrtxwmBH9MqHEwoQDOLAbBz4dO6sA+okRdw4ZmF4GhrcyszW/051vRM+NfQ2aX/7DRcYY0R/GvwYUPcABHNC59GDDUfXkQbHzFKBFdOcZ8a83orcQTixQOIADO3Eg6zcooN2U4p5H7qWae25jkTHhrQfeTJ2TQ1eGvT2LTAl7+BrRW+PPMUUPcAAHdE49KGv9TOxMBWgdw3b9Q43ozwgoFikcwIGd/fWt6mzaL3ZOAfSS0l54TyP6GzIv3cwbHptYkrP1lXgb+xo0tLYN1+6ZRcaUtT4xgetA0QMcwIFuL3pQiXtl7FwFaB3h9Voj7pMEFYsVDuDAnR3wb4udUQC9xIj+B1mXdtZV1r0qb+u78ypxX4x9HZpX7l1F5hjRd8e/DhQ9wAEc0F714KYh8Q+Mna0AraQUPc6I3kBgsWjhAA7czoHbzJj+Sex8AujhQVjbyLjkM+7M3I2fPhX4Fwlci0ZUJf7HyzpunyJzjOhk7GtB0QMcwAHTyx7UOsEXSQD9PRVxPcFNcOMADtzOgZ8UnTV7EbzQgr2xpsi2RmTbtTzM2X5P9igjenMC1yPx8pdxcn1RhE/n418Lih7gAA5oz3tQiv5r7PtIgNYytGLz/mWtp/GWBAsYCxgO/K8DVt8SO5sA5sL0ukauNaQHlfgnY3w4FMQ/14huiX09Eq7rSuuPwZXtD4xPTOB6UPQAB3Cg24cebDPinkrWA/QRI/o0I3opIcZChgM4EH6AhrdRCF1oIuFgAHKsWTlW1e6Dsb1JhbAnYuzrkWjdUlp9QuzrkwqluLMSuCYUPcABHOj2qQeXLhldd0TsrAVoNWHIKuu/QJCxmOEADhjRTYtl48GxcwlgTzCr9N5G9HIyrGkZ5i8rlncXYPsOytq/Kf41Saq2VqLPx48dLFy54TAjemsC14WiBziAA90+9uBz5D7AACjFv8KIXk+gsajhQOYOWP8JQheaQ3deKfr16HNDzaoHI6KPiW1QShhxq9ieZbsbt1RW/zr29UgJ3hIlZ1lncCAXB0rR42JnLkAWDI3pfYzoD2MPPUUPcCCuA5W4V8bOI4CZYGp9A3nR4DXD6qmYfienX5b5noDX8dnvnSlFv5PAtaHoAQ7gQHcAPbhmUb1uiPsDgEHQWbNXaVX4zIAFjgUuawduLccn/4zQhZQpa31i5g9K2lBXLT1xat/YLqVGKf4ZRvTGBK7PoOtSDvy4M0tWTwyHT6ITuD4UPcABHOgOqAffDF95RFiCAfKkGnd/WopeRMix0OFAng6Utf6SjXghVczo5Eh4eBR7TqgeZI11y2P7lCLhQZgR/Vk2jtX6g8Xiyth9TxEjbjT69aHoAQ7ggAy2B5Xo38XOX4CsOLzjDjSiHyLwCXwcyNUBd054Kzh2FgHcnqEVm/c3oufHnw+qJz2o9SsYvnPCoUyV+NNb7to2I/4dx5ywdm882BndeUacT+A6UfQAB3CgO+AeXD+yUg1rA8CAGa7dM8NpfYQeCx8O5OdAWetphC4kQ6c734g/I/ZcUD3twW3D9eTC2GqljKn11S39JPjycsw/PXZ/U6Ya849N4DpR9AAHcKAbpQe1/1rsHAbIkvApYGX9Fwg/FkAcyNEBNxo7gwACptZ/iT8PVK97UIp/I4bvnrAheosefm8zVj++dHzqcK777jGin07gelH0AAdwoBurB6W4v2GtAIhCd15l3avC67iEIAshDmTlwNaydscSvBCT6fUn9ixQ/enBr5d13D5M2Ey/ytBNDXbxvLDPNNf6rgkPSI3ozQlcM4oe4AAOdCP24Ar+YAQQkdJetNiIriEIWQxxICsHbjTj/hGEL8TAjOtfhk9FE5gDqk89qKx/AdM1MxZ21t7NWL8yPDhtkJOTVe1fXCzvLuA6z4xStE7gulH0AAdwoBu/B+6TrB0A0Tcl1tfwNmDsMKTowUAduGpkdPJBhC8MkuEx92gj+jtmvfV5fy6TtWdUnU37bb8Xs/rzBK7fzsvqusrqX4f9O7m+Mye8EWtEL4l+/Sh6gAM4IIn0YFz/knUEIDK8DZhAGFL0YLAOXDpSTx4VO3sgD4bryYcY0d+Sc3nkHJ+Gzo5wgm54yBZOVE7kTdlwYMmnK+ueEv5g3ONYyAJj3fEJXEeKHuAADnTT6YHbOLRi8/6x8xkAeBswgUCk6MFAF+DNZnRyhPCDfjIk/oFG9EpmO598D4eNMVVzY2TVxJFVrScZ0bXbD9sY3PULDx7XhAdXSztTd+c6zvW0c+djzyNFD3AAB0xiPais67C+ACQCbwPGD0WKHgzOAbdxyeqJ4di5A+2kGt9wPyP6GzItu0zbNiLrHxzbv7YQNk0vrVte1npa2IOvx9dqqxG9wIh7lxF92lH15EGx/71twVh9TgKzSNEDHMCBboI9uGl4bGJJ7JwGgP+FvQETCEaKHgzKgU0swtBrwgMgI/4y5jjPLK/En85U9YdF4/5QI/6R4S29qtaTjeh/V+J/bES1rPWXRvTq2z3cu3p6b8H1RvSHlehnjeg/mtq9KHyaz1t+/aI7b/qaRJ9Fih7gAA6YFHtQ61f6FsEAMDt4GzCBcKTowWAc+FU57h5AVkIvCHvA3e4hBJVnD7aSKZArVa3PTmAGKXqAAziQtANVrc+OndcAcCd4GzB2OFL0YEAOXG6sO5oQhLlQWf84I3oduUVuGXH/xTRBdizvLph+45L7N3qAAziAA7LbHmyqOpv2ix3bALATeBuQAGcRz8KBa4x1jycEYTYY6587fXJobI+pNHqwrar14UwT5EQpelwCs0fRAxzAgUY4UNV+PHZuA8Au4W3A2CFJ0YMBOHBLVfsXE4SwJxhxo9N7jpFT9OD/buytfpdJglxY1nH7GNGLyQDWARzAARzQmfbgumrM3St2fgPAblgsrjRWv0SwsbjhQGsd2FaJe3N46E8Ywgw+d3t/As5SifagEvcspghyoKr1pNjzRtEDHMCBBjrwodj5DQAzoLRuuRH9dQKhQdEDHOiHA9Z/YmjF5v0JRNgZi2XjweEUN/KH/Nm9A86HN6OYImgzI6smjjSi15KH5CEO4AAO6J72YMvI6OSDYuc4AMyA6qQL7lHV7oPhjSHCjgUPB1rpwPlmdHKEQITbMyT+gaXoRQn4STWgB5V4ywRBmymtfjT2nFH0AAdwoMEOfDN2jgPAHlCOT/6ZEXUJhAdFD3Cg9w5cVVn3FEIRApXo843oDWQNWbMHDvyOPyRAWzHj/hH8IZw8ZE3EARzQOfWgEv/k2HkOAHvA0hOn9q2s6xjRmwlAFkEcaJ0D4YCHfyg63fkEY56Ezzgr0fck4CLVzB58LrbDAH3aB/W8BOaLogd34YA7hx4xJybtHpzH/uMADcTUE/cPJ/8lECIUPcCB3juwZsnqieHYOQODpRrfcD9+5JInc82Pcsw/ndmFFp6Azr0GPUjdgd+GByvhDauwL2sC/z0UPeju9D5B3PNi5zoAzIruvFL0OCN6JQHHIocDrXPgWlO7FxGOeTCd5XzyG3/u2lC/HlrhDontNEAvWDzu72tEb0xgrih6sFsHwosZv/f2mBPW7l2Kvo5Da5gbk2YPNhSdNXuxSgE0FLNK712JPz2BMKHoAQ702IFS3EfCSbCxcwb6w5LRdUeUop8nO8iOXjpQiX6MmYXG0+nON6LfIx/Jx4Y4cModFa7G3L3Cfdz0Fi+x//soetD93x5YfXmcYAeAnlGKf4YR/QXhxgKHA61z4FdVrc8mLttEd15l/UvC4S8J+EW1sAfhniC25QBzoazda2PPEUUP9iBzX7Erl0dq97BS9Ef4hE8mkR6Utf6y6mzaj1UKoOEcVU8eVIr+qxG9LXawUPQAB3rtgD8jvPEbO2dgbgyPTSwxomczH2Rknx349aJxfyjzCk3d65ptEcjIJq2Tw3b9Q3crdac7v6rdS43opbH/Wyl6YEIPan31wEIdAPrLkPgHckgI4c4C30oHrq6se1U4FZEcbeAp7uKtEf1dAh5RGfSgEvdFTvuDphHeSjFW18WeH4oe7IEDW4ZWbN5/Rn6fdME9jOh/GNFtOIZjJmYPrP487FfZ/1QHgAF/Fuw2s8CwwOBA2xxwvrLuKcRpo7Zo+Fl8b6jcelDVelJs/wH2BCP6/thzQ9GDPXTA7emUD4+5R5eiF+EarpmYPbDueFYogJZx5Oi6A6paT+azYBYYbjJa6IDVL4VPSmPnDOycanzD/UztvxbdEyrnHtxa1fpwZhSagKn9XyUwMxQ92DMHav3UbHwPbw1O/0bbQs+ZOxOnBz/jRGCAljIi6x/MaWosLtxgtNKBW4x17xuuJxfGzhnYQXgoG05i5aY++mxQ23vgNobPzphPaMD+qFczt+RW0xwI23vMxX0j/pFGVGP/O6g8e1CK+5veJTkAJEZ3XlX7F7MBbfywpehBHxy40Yi+e8nouiNiJ02ujKxUY2r99/DWFTPOjCflQO2/xt6hkPIhdqbWiehzQtGDWThQWn1CT/a+FP82vtjCQTP4Hmg4pKY3aQ4ASbJjA1r/b7ydwiLDzW4rHbjBiH/H0OoNi2JnTS4MWbe0rPW07W9jxr/+FD3YuQO1/kvsWQG4M915RvS/mVuyu6EObOvlG9bGuqON6AUJ/LuojHpQjvmnszoB5PJZcO2+Hzt0KHqAA31x4NZK/OnDdv1DY2dNWymtP8ZY/Th/sSfDmpJhVe1eGntuAG5PJe7NseeCogdzcGCy5xPdWbNXZV3HiG7FTdw0g+nBGlYmgGzoznXHytoAACAASURBVKusf4kRfxmLDIsMDrTSgW2l6NeN6NP4BHDuLOu4fUrrlvPHk+heU7PrwY3VuPvTHowCwJwpa3dsWKOYZ/KssQ5Y/4l+RUEl/sls25TANc6kSuuP6ZfLAJAgpb3wnkb0FD4Ljh/AFD3onwNuc3jbIuxVFztzGnmir/h38McS5rMFGX350JjeJ/ZMQd6MiD7GiN6UwDxQ9GDWDpSir+vrnKyaOLIUdxbXiDk1fe+B+2Q/XQaARBkS/0Aj+k0WGhYaHGi1A1vDoQCV9S84vOMOjJ07qTK0wh1ian1ZZfW7CVwzih708kZ/Y/hhGXvGIE+GV+kfGdFrmGlyvfkO+Ef2f2K684z1K9lnOPa1bn3dumT1xHD/fQaAJBmu3TNL0YsSCCOKHuBAfx240Yj7r1Lc8xZ21t6tyJywmff209Jr/Qqn+TJ7Lc/fteH01dgzB3kRfmCGt9ET8J+iB3N14LZB3jeFTzT5bcbcmj72oBJ9+6B8BoBE97oy4l/PX2lZbLhJzMaBG4zo5ypxr8zpM+HF4/6+4TOe8FakEb05getA0YNBOPCbnOYc4rN0fOpwI+qYbzK+HQ74nw56hsJXG6XVj8b/t1Mt7cEVS0+c2nfQXgNAYixcueEwY/VUTrqMHsoUPRi0A87U7p1mXP8yvBlXtIQlo+uOqGp99o5ccxvximzJ0IHfGuuOjj2LkN295LoE3KfoQa8c+FCseQp/qOWTYGbZ9KEHpbi/ieU1ACRGKW7Z9Gmi3DzRAxzIz4GtptaJstbTylr/dvshAp3u/CJ1Omv2KsfdA4x1xxvxHzaikwn0kqIHMR24xVj3+NijCZntpyp6AXNP9rfJgap2L405V6W4R3FKcHwPWle1+35MrwEgQYzo0/gRnUBAU/QgvgM3lKI/Cn8FL8WdaOrJP18sriyWdxcMOpeOOWHt3qW9aHFZ6xPDZtnTn8icxymT0R2h0urB1srqXw96PiFfSnvhPaezOLb7FD3oqQNh+5DY87WoXjdkRH/CtWW+TQ97EA4Fje02ACTG9h/bYc8s0atYdFh0cAAH7uDArUb0Z0b0bFPrv1fi3lzW7rWmdi+qrHvKcD35kPCwLty4hh+HocKberfff/T3//PwQHF4bGLJSO0eFj5FDm8fVrWeZET/cfqNvjXG6s/ZogAHyaG7diCs23HvHiAnwnYLPPwjm1uazZeH03mLBBhasXl/I/qfCfSEkpb0wOqpsb0GgERZNO4PNda9jx/fCYQ1RQ9wAAdwAAd24QCn+8EgMaOTI0Z0ikwik1rqwOdSS5Tw9YMR3ZJAbyhpfA9+O8gTrgGggZh64v7TJ2jGDiyKHuAADuAADuDA7RyoRD+Wytsq0H5GRicfZER/TQ6RQ611wOpY7DnbGdu3QeHrrPh+SPOLw0AAYGYLj9UnGNG1sUOLogc4gAM4gAM4sP2H6pfCth3cxsAgGBF9jBG9htkjf9vtgH9kqokyZN3S6W1YEugTZRrag0r0G7FdBoDG0J1XWre8FL0odnhR9AAHcAAHcCBjB86uOpv2i31XAHlgxL+Qg5eizzzV/x7cvPTEqX2LhBlZNXEk+28yC2ZuPdi6ZPXEcGyXAaBB7DiV059gRH/DDQmLEA7gAA7gAA4M1IHvHTm67oDY9wKQAcu7C6paT2a+yfgcHKisfrdoAId33IGl6Ndj94vSxvagEm9jewwADSQsQJV1HSN6fewgo+gBDuAADuBA6x2o3ffD2ht7/Yf2s7QzdXdT61eiO0/RgwE5UIr756IhhDcVK/GnMx/Mh5ldDzS2wwDQYJaMrjti+sTgW1mIWIhwAAdwAAdwoB8/TvVH4aFM7DUf2s+Q+AcacZ45JstzcqCy7ilFk+h05xvRU2L3jdJG9mC4nnxIbIUBoOGMrFRT1e6DYW+B2KFG0QMcwAEcwIH2OOB/OrTCHRJ7nYf2U4oeZ0RviO88RQ8G6sBtR9WTBxUNpLQqRnQbvpAZZg96ELZ3iO0uALSE0vpjwgblLEQsRDiAAziAAzgwRwdqnVi4csNhsdd2aDfh03Ij+p/MK5mdpQO1/qBoMKX4V/ACRgIeSaPqZ7G9BYCWMWL9k4zo+QkEHEUPcAAHcAAHGudAJXphOPUx9noO7WbYrn8on/zGn3cqZg/824qGY6w7noeAzJHZkx5Yd3RsbwGgbWzfn8K/0IhOcWPDooQDOIADOIADM3bgJ4vG/aGxl3FoL8s6bh9j9a3h80fmkmzO2YHS6hOKFlCJeyWfA8f3yTSmmv/gGwBSpdOdX1q3nAeBsYOeogc4gAM40AAHvrdYNh4ce+mG9jK8Sv/IiJ6XgOsUPYjtwC1Hjq47oGgJ058Dsycgc9W9qx6UohfF9hUAWs4xJ6zdu7T+BCP6C4I5+g0PRQ9wAAdwID0HvtmmH6OQFkMrNu9vrL4lPPRIwHWKHkR3oLL63aJlVFZXxO4rpY3oQfhjUGxfASCTz06mHwReEjv4KHqAAziAAziQhAO1fiU8oIm9RkM7KcU/w4heHN1zih4k5EBlXadoIUb862P3ltLkexBOkY7tKgBkRNXZtF8p+joj+pvYAUjRAxzAARzAgVgOVOJPD2/Jx16XoX2Y0cmRyvovkG/kGw7sJHutf1zRUozoP3DNmXuzuweAot+J7SkAZMjCztq7GetXGtHLWahYqHAAB3AAB3JyoLT60WJ5d0HstRjaRWkvvKep9V+M6E2xHafoQaIOXB++SipaTDjoIYE+U5JsD24La0VsTwEgU8K+R9NvBF6aQCBS9AAHcAAHcKCvDlTWvzcclBV7/YVWbrPCH1XJL9aw3eWvuC8Wrac7z4j+P2aBWTC76EEp7nmxLQWAzDmqnjzI1PoGI3o1CxYLFg7gAA7gQAsd2GJEXxN7vYX2ED4hN9Ydz0Fr0Webak4PXpNNNoh+NYF+U5JeDyrRj8V2FABgO9VJF9yjEvdmI/rb2OFI0QMcwAEcwIEeOXCjsfoclnro2cm+tf97HvyRT6xRe+bAkHVLc0mhwzvuQCP6ExwhJ8yde3BpeFM0tqMAAH/wIHB6I9srWLhYuHAAB3AABxrswJVG/CNZ4mGuLJaNB4cTHNk2JfpMU83swc9ySyGzSu9txG1OoPeUpNWDUtyy2H4CAOxmj0AWr9gLBUUPcAAHcGCPHdhUjW+4H8s7zIXF4/6+Va0nG9FrmEFyGAdm7cApOSaRse5oI3oD3pAdJsPP4QGgoSw9cWrfStwrjbiNLGAsYDiAAziAAw1w4LxqzN0r9voJDaWzZi9j/XONuHMScJmiB413oBT/jCJTytoda0S3xb4GlCbUA39GbC8BAO6azpq9SnF/Y0TXxw9Oih7gAA7gAA7s1IEzw/5LLOuwp4zU7mHhpGgj/jJmi3zFgZ45cEvumTy9xzpO0YPudA8uZx9AAGgOne78sKE6m9uykLOQ4wAO4EBCDmyrrOtwUw17wkg9eZSp9Q1GdDIBhyl60EIH3DnZp1L47VT7r8W/FpRJpAfsAwgAjWTE+icZq9+OHaIUPcABHMCBrB24IXxmFXtNhAawvLugHJ/8s0r07Tz0iz63VBY98K+PPfYpsGjcHxr2po1/PSiTQg9qfXVsJwEAZk0p7lGm1q+wx0UCCwpFD3AAB3JywOrPR2T9g1nCYZf3KPaixVXtXmqsfjx8ehXdWYoeZOTA0Jjeh3TawXA9+RAjelPsa0Jp/B5Y/ThzAQAtOe3Kn2FEt0YPVooe4AAO4ECrHaisfnfp+NThsdc+SIjOmr1GRicfZKw7vhL9mBH9RWxPKXqQrwPOx46E1Chr99r414Uy8XswGdtFAICeMWTdUiN6SvgsK4GApegBDuAADrTMgap2H1zWcfuwdOf9SV1V68PDwz5j9VQjeq4RvTG2mxQ9wIFpB2r9l9g5kR7deUb0yziSfU5sq0664B6xbQQA6ClDK9whVe3HjeivWeiyX+i4IcYBHMCBXjhwY3jgw3LdbhZ21t4tfLprxD+yqvXZRvQ120/StP4TpeiPjOhVzBOZigNpOzA85h4dO0tSJLy5zm+j+H6ayFVafUJsFwEA+kJ4S6Oq/YuN1XWxw5aiBziAAzjQWAc2hM87WaqbRyXuWUZ07a7LbTSilxjRq43olgRco+gBDszNgSvDoTuxsydVKvFPZu/03GfMrYrtIQBAn+nOK2t9YinuLBa92IsORQ9wAAea40BZ62eOqicPYpluHod33IFG3ObYDlH0AAcG6ACHHNwlRvRDOJlvLpWinx/EGgwAkASLx/19K+vfy3498Rcgih7gAA4k7MDNpejrYq9ZMHtM7d6ZgEcUPcCBATpQ1u5YcnP3LO1M3Z2DirKey4uZEQDIjmrM3ctYfWv4VCCBIKboAQ7gAA6k48DFw/XkQ2KvUzB7hsQ/0IjemoBLFD3AgcE5cHN4uEV23jUj1j+Jr6Kync1t4Q155gQAst3w29T66lL0ogQCmaIHOIADOBDXgTNLe+E9Y69NMOfTLr9HlpAlOJCZA1a/RHbOnEr0Y9GvGdWN0YNhu/6hzAoA5E2nO396s/Cz+YsYCzI3JDiAA9k5cF1Vu5fGXopg7lTWvyQBnyh6gAMDdiDMPhk6c5aMrjuCk801yzllVgAAbsfQmN6nqvVkFsX4CxRFD3AABwbgwLlD1i1lIWw+QyvcIUb0cnKD3MCB7By4Ncx/7AxqGpW4VyZw7SgZdA/8O2K7BwCQHGF/hEr070ytEyxOLM44gAM40DoHbjXiVhXLuwtirzfQG6rafTABryh6gAMDdqAUdxY5Ogs63fnhj2DMrOY1s7X/GvMCALC7HxXWP64Sf7oRvS16aFP0AAdwAAfm6IDzpfXHsPC1h2rc/akR3cpskI84kJ8DpfhXxM6gplLV+nC2P9LMym2M7R0AQCMYWr1hUSXuzUb0N/HDm6IHOIADOLCHDmwz1r1vaMXm/WOvJ9BDlncXGNHzyQPyAAeydGDL0vGpw8nU2WNEP53AdaRkYD24teis2YuZAQCYIcs6bp/SuuXTh4awYNEDHMABHEjeAbexrN1fsNC1j9KqxPeLogc4EMmBb8bOoKazZPXEsBH9HTOs2eTYyEo1sb0DAGgkI7L+wUb0Q0b0hthhTtEDHMABHLiTA7eFg51466+dlOPuAUb0Jrwn+3AgUwdqfXXsHGoD4WCI6NeS6g5ubib/PLZzAACNpjrpgnuUoq/j0BAWcG5gcAAHknHgAjOmfxJ7fYA+0VmzVyX+xwl4RtEDHIjjwJaRVRNHkrFzZ+HKDYcZ0euYZc1jlq07nrkBAOgRYXP5yvr3GtGrogc8RQ9wAAfyc+CmyrpO2K6Bha29mFrfkIBrFD3AgUgOcPpvjzPV6luZZ81jnq2+pcf6AADAkaPrDqhq91JTu+9HD3qKHuAADmTgQCn6nZF68ihWoHYzMjr5ICN6S2zfKHqAA/EcqKx/SewsahOlvfCeRvQanNbWz3Ul+rHYvgEAtJrwgzTsQ2VEL40d+hQ9wAEcaKEDvy5FjyuK7rzYeQ99prNmLyO6NgHnKHqAA/EcuGmxbDyYvO0tvAWoWcw0b88CAAyK5d0FpdUnVOJPD5vTx14AKHqAAzjQcAduDVsuLO1M3Z2FLA/Cp0sJeEfRAxyI6oA/I3YWtZElo+uOMKI3Mt/a9vk+P7ZrAADZMVxPLiytihG3MYGFgKIHOIADDXPAf6sUtyx2lsPgMNYdHR76xnePogc4ENWB2v8V2dsfSvEfYL617Rn3K+YHACAWne78stYnlrV+xojenMCiQNEDHMCBlB3YZKw+h0UrL5aeOLWvqXUiAf8oeoADcR24tups2i92JrWVxeP+vkZ0K3OubZ7zW9kyBQAgkQ14S+tPCBvZG9FtCSwQFD3AARxIxYHrwsmvQys27x87q2HwlOL+OQEHKXqAA9Ed8B8mg/uLqf3X4l9nyvSxB0Mr3CHMEQBAQoysVFPVftyIrmcR5CYAB3AgYwfCJ5+nhL2JYucyxMFY93jeSIk+hxQ9SMKBsJc2WdxfKnHPin2dKe1rD6rxDfdjjgAAEiXsc1VZ19n+6RuLIjcFOIADuThg9UtDY3qf2BkM0Tel/1V0Fyl6gAMpOHBJOFCPTO4zy7sLjNWfJ3C9KelPD0Zq9zDmCACgIacIl1Y/uv1zOBZGbgxwAAda6EBl9btVrQ+PHbmQwB654s6K7SNFD3AgFQf822LHUi6U4t8Y/3pTpl89sO7xsR0DAIA9IOyDVYk+P7whw6mI3CBwk4QDLXFg/XDtnsliAIFKvE3ASYoe4EAaDmzjjfDBUdqLFrMfuba2uNcCAGj64SGixxnRs1ms4y+qFD3AgT12wIUM49Mu+D3h8yT+uEWWkqU4cDsHvkdCDhYj+j/MoLY0h/wLmScAgBZgRidHjLhRI/pDHgbGXlwpeoADu3Wg1olS3PPCp56xsxPSIZxOaER/QX6QHziAA7dbL14WO5tyo7LuVcygtjKHSutPiO0XAAD0mCWrJ4arWk8ytfs+JyjGX2wpeoADOxyoRC8srVvOgz+4M915pejnmRXyEgdw4HYO/G5pZ+ruJOZgWTTuDzWitzGL2sI88q9nngAAWsxwPbnQ1P7vjegaI7ol/sJD0QMcyM8B/9Oydsfy4A92RVm718b3lKIHOJCUA1Y/TmrGYfp3Q3wHqG5ve+BWMVMAABn9RW/7noEcIMINBTdUODAIB2r3/VL8M8LbXbHzD9JlZHTyQUb0JnKJXMIBHPjDB4CcWBqL7V8S4WO3dT2odXU0qQAAIPLr/bW+zIh+1YjeEn1BougBDrTFgVuN9Z+oxtwfk/FwV1QnXXAPIzqVgLcUPcCBpBxwG3lrPPbe4rEdoEzve/APEbUCAICUThOe3n/pdyy43HDgAA7MwoHrjLh3hT1IY2caNITl3QXTf4Qic+gBDuDAHzhQ1X48dkTljhFVvNR2ZVOtb4jtFQAAJETV2bSfEfdUY/VUY/Xn0Rcqih7gQOoOXFyK1otl48Gx8wuaRVXryQn4S9EDHEjPgVtHVk0cGTujcsdY974EXKCkdz0oxb8xtlcAAJAwpb1ocSn6OiN69vZP+1iIuRHBARwQ3RoyYceJvmv2ip1T0DyM9c81otvIE/IEB3Dgjg5U4k+PnVEQclqfw3xqqzKqrP2bcBsAAGbEwpUbDitr/dtK9LNG9NrYixhFD3Bg0A74y0px/1xZXxGbMFuMdUez3QT5TX7jwC4fUlh9AgmbxhZBRnQLs6qtyavKuk5srwAAoIks7y4oxT1q+hOutbEXNIoe4EBfHVhbWn/C0IrN+8eOHmjBAVSiFzOvZDYO4MDOHeDwj5SoRC9kVrU1eVValdhOAQBACxga0/sY8a83ot80ojfFXuAoeoADc3XAbd7+tt/4hvvFzhdoB8ecsHZvY/XbzCb5jAM4sMsHFKJ17KyC/8OI/gfzqq3JrLJ2r8VvAADoKeEtofD5xva3A2v3fSN6W+wFj6IHODAjB24Key+V4p/B3n7Qa7YfLkUWkUU4gAO7duCWJaPrjiB906ES90pmVtszs1ZfHtspAADI4JOvsnbHluI/YESnoi9+FD3Agds7sG37g3qrL+ckX+gXptaXMXdkLw7gwO4cKGv9DCmc5J6tzK60pAe1e1FspwAAIDPCAQLTPwY/HQ4ViL4YUvQgTwfOr2o/Pjw2sSR2JkC7MeP+EUb05gScp+gBDiTsQNhbOnZewR+y9MSpfTkIRNtTtf8rHAcAgIh0543I+gcb61ca0a8a0RuiL44UPWivAxcYcavCnp3EHgyC0l602IhemoD7FD3AgbQdOJ9UTpNS9KIE/KCkFz1wT43tEwAAwP+yrOP2GR5zjw4PKUytXzGi17Doc9ODA3NxwP/U1Lqah34Q6cTfSeaXDMcBHLgrB6ravZSUTpPK+i8ww9qSHPOPjO0TAADArul05w+v0j+qrHuVEf1PI3px/MWTogdJO3CLET27FH0dn/dCzMOgjOj/JDAPFD3AgfQduKLqbNqPxE6T7Yf6xXeEkrn3YPG4v29snwAAAPaIaszdK5xSertThsMDD24M6EHODly54/RePY6DPCA6ne58I3pmAnNB0QMcaIQD/m2xYwt2TSX6d/EdoUwPelDaC++J6wAA0GgO77gDy9r9RVn7N1Wi3zCi13GjwI1Syx3YakTPK637p+2fcyzvLog9hwC/x1j3vgRmhKIHONAMB25bsnpimARNl3LMPz0BTyiZ+6yFvddj+wQAANBblncXVGPuj0vxryjFf6AS/2NOoOTGqQU3jxuM6PuN9c8Ne6sRG5AixupYArNC0QMcaIwD/ozYuQW7Z2R08kHxPaHM3HtwKa4DAEAWHHPC2r2NdUebWl8WHqKUoj8yojdxQ8ENVcIO/MpY/XhV+xfzdgQ0gUr0+UZ0WwKzQ9EDHGiIAyOij4mdXTCjA52iu0LpXHuwHtcBACDrh4Ijsv7BxrrjjdVTjei5RvRGbjC4yYrgwNZK9MKy1tPKWv+WwzugaVTWP443rclO1k8c2EMH1sbOLpjxvq5h6xFmXBrdgzX4DgAA8Ac3OWv2Cp86VLV76Y59rNw5RvxlCSzaVLt6cIMR/y1j9S1G3FM5uAOaTDip3Yhek8BcUfQABxrkQGX9C2LnF8wMI3ptbF8onVsPrH4c3wEAAGb4+UP4TKUS90oj/t+M6NnbP9HkhoQbsrt24Hoj+r3K+veGz3nDw5LwoJnBgzYQPk8va/0lWUgW4gAO7OHDiJ+zFjaHcL2YcW16zv1jbI8AAAAaTXXSBfcIp7Aaqy834t5VijvLiP4igUWeitODK8MnFsEFU7sXmXri/uHTmdieAvQDs0rvXYpeRN6QtziAA3vqQGV1BcncHMI2Jcy5NjvrrL48tkcAAACtZGln6u4jtXvYjk+J9a1G9NPTB49cEf0GgOrJg75S9Dthzz5T+7831j1+ZNXEkbG9AxgUC1duOCxsKE6ekKk4gAOzcODao+rJg0js5mBEz2PWtdF5V9b6xNgeAQAAZEfY7y2cSFzW7thStC7Ff6AS/YYR5zmEJKm6zoj/qRE909T6L5V1r+JBH8Dv33zW8xOYUYoe4EADHahE306WNovpP2JHd4fSWfdg8bi/b2yPAAAAYCdv1lRj7o9L8c8woq+paj3ZWP+JsI+cEb2Yh4Q9uwG8zoi68PC1FPeRUvwbt3+2O+4fsWR03RGICXBnwls70yem80OMHuAADszGgVuGVm9YRL42CyP6P/iuTc68bVVn036xPQIAAIBZ/ggfqSePGh5zj97xJqE7cceJsvoflbgvGtEfTu/NFfak25rAjcegaosRvXT7p4lWv23E/Zexeqqp9Q3hAI7w+UM57h7Ap0cAe87Qis3779jjMvqcU/QAB5rqACeRNhJTu+9Hd4fqzr4HbnNshwAAAGCAn+wNj00sGa4nHzJi/ZMq0eebWl9dWpVS3D9Xou+pavfBSvzpxuqXjPhvGdG1058lbzKiV0/XdX26Cfvt9P/9S424jUZ0avr//zmV9V8Ib0CG/fbC25BV7cfDvnuV9S+pxD2rHJ/8s/AwNJzUjFAA/WHpiVP7Th9yxI8weoADODBbB7YNiX8gOd082ANQG517Yf2O7RAAAAA0mGUdt09pL7xnqO2ngdqLFs+kwoO68L9z5Oi6A2L/GwDgrjnmhLV7l6Kfj/0DhqIHONBsB8If9MjcZmJENbY/lM6lB++O7RAAAAAAAKTM8u4CI+6T/PDixycO4MCcHRj3j4gdaTA7pr8GIQekoT2o9WW4DwAAAAAAO6fTnW/Efzj6DxeKHuBA4x0IB24Rtc3FiF4R2yFKZ98DHr4DAAAAAMBOWd5dUIl+jB9c/OjEARzoyQNA6x9H2jb5TfCsDpTrtqy2LZaNB8fWCAAAAAAAEtzzb/uBQPF/tFD0AAfa4cC5sXMNZs+S0XVHJOAQJbPuwQb8BwAAAACAOx3uY0Q/x48tfmziAA70zgH3VKK2uZTilpEH2txMtP4TsR0CAAAAAICECCdzG9Gzo/9YoegBDrTJgfOLojsvdr7B7ClrfWICHlEyux5UtZ6E/wAAAAAAsJ2lnam7V1a/y48sfmTiAA701AHrn0vMNhtT66vJBW1sNpbjk38W2yEAAAAAAEiAoRXukEr8j2P/SKHoAQ60zQH/U97+az6V6Hviu0SZ2fXgtoWdtXeL7RAAAAAAACSxubv/KT+u+HH5/9u78yhProLQ4z2ThQQChGAIZrrrVvcMSSCsjg+QTYT4ACWAHkcUeKiIo6BAhkzXrZ4A/jzKSzzqU1SQoLgEFdkUBAGNMMiqMDhOJvdWT4YQIBBZkpBA9mSm36nuBiEEMkt3V/1+/fmcc/97D/D2t5Z751dVwRyYgyVuoIzp6V2f4zh8IeZ3Oz/kYT0//LtjAAAAVrlyOt03VHl3DxYohjnQwOg18Em//hsNIeZP9aAnIx78HJRVPq/rfgAAgA6VdVNa1FlQWlRrYLkaKGJzppP8yHwZ/lbnijyU58uyTk/uuiEAAKAj5XR6aIj5C10vTAxzoIHRbKB9p6hf/42GcmbPaV33ZORDnYNbThyk47puCAAA6EBRpSeGmK+xoLKo1IAGlq+B9BQn+NFQVPn/OFfkYT1ffqTrfgAAgO4Wcrf0YFFimAMNjG4Dn/Drv9ERYn5ND5oy4sHPQVE1v951PwAAwAoLMW0LMe+3kLKQ1IAGlrOBoso/6gQ/OkLM/+mckYfyvFnUzcau+wEAAFbKprkjQp1f3fVCxDAHGlgFDdT5A07uo+Okrbvu5gMgeVjHFX6JCwAAq0Q5uOyYEJu39GAhYpgDDYx+A/vLKj+y6/MeSyfU6Qk96MqIhzQHr3MsAADAKjC+JZ0QqvRhiyeLRw1oYCUaKGPz5q7PeyytEPPLnD/yUJ5Dy5ie7ngAAIARNzG9e30R8yVdL0AMc6CBVdPArZPV7Kldn/tYWiE27+tB4vxUaQAAIABJREFUW0Y86Dn42viWy491PAAAwAibrNLDQ2y+ZNFk0agBDaxgAx43HDFFfdG9fDU+D+t59A1d9wMAACyjMuafCTFf34PFh2EONLB6GrhhXbVr3Ml9tBQxPacHbRnxUOYgPaXrfgAAgOWwae6IssrnWSxZLGpAAyvdQBHTuU7so6d9p6PzSR7Gc+pVpw/S0V33AwAALLHyrJ3Hh6p5Tw8WHYY50MDqa+Cq9lFRJ/bR0m4ghZiv7UFfRjy4OShi89qu+wEAAJbY+HS+X4g5WyRZJGpAA100UFTpxU7so6es05OdU/JQnlfb9wB33Q8AALCEQsw/HmK+puvFhmEONLBqG9izcfOOo5zYR08R01/0oC8jHuQc1HlX1+0AAABLqIj5JSHm2yyQLBA1oIGuGpio0tOc2EfPVLz0nj4mlYf13PqrXfcDAAAsgXJw2TFlzH/Vg0WGYQ40sLob2O6kPprKmF/Qg76MeNBzcIP3cQIAwAiYiqkIMX/SwsjCUAMa6LiBfaFOD+v6nMjyCDHvcI7JQzfax7YdEwAAMORCNfsjIeYvdr3AMMyBBjRgo2F0tRu7jvE8jOe5/ZNbZx/cdT8AAMChGsytLWLzCu/763xxZZgDDSw0cN34OXvWOamPphDzaxzreeiO9TLmf+m6HQAA4BBNbtt9Uoj5wq4XFoY50IAGvtlAlV/upD6a1m/ddR8f/8hDeb4r6/TkrvsBAAAO/ZHfK7peVBjmQAMa+J8G0qXth4ic1EdTEdO5jvc8fOe8Ku8eG5tb03U/AADAQZlbU8T8khDzrZ0vKgxzoAENfEsDE1V6mhP6aBrfkk4IMX/NMZ+H8JhvntV1PwAAwEHYMLP3xFA17+l+MWGYAw1o4Nsb8I6x0VbWaaD5PHTHfRHzJWOb5o7ouh8AAOAAldPND4eYv9D1YsIwBxrQwB00cPNkNXuqE/po2jDYe48Q89WO/TyE5z+//gMAgOEwmFsbYn6Zr/x2vYgyzIEGvvuvjNK5XZ8qWT5+/ZeH9PyXGr/+AwCAIfnKb/tYXfeLCMMcaEAD37WBz584SMd1fb5keUzFVPjybx7K47+I6acdFwAA0HNhJv+Yr/x2v4AyzIEGvncDZcw/0/X5kuVTxvwmx0AexvPAx3z5FwAAev6upbJK5/dg8WCYAw1o4Hs3UOcP2GQYXcXM7KNDzPsdB3nYzoX7279d1/0AAADfRYjNo0LMn+rB4sEwBxrQwJ01cFM5s+c0J/SRfv/sJxwHeRjPhW/sOh8AAOAOlIPLjgkx/78Q874eLBwMc6ABDdxpA0XV/LoT+ugKVX6h4yAP47nwhrJuyq77AQAAbmc8Ng8KsfmvHiwaDHOgAQ0cUANFzJe0/3DhhD6awtbZyRDz1xwPeQjPiWlb1/0AAADfarD9yKLOMcR8c/cLBsMcaEADB95AUaUnOqGP8KO/df6A4yEP4zlhdsOL9t6l64QAAIBv+XVFWecP9mCxYJgDDWjgoBooY/4rJ/PRFermbMdEHsbz4v5Qzf5I1/0AAADz5tYsvlfp+h4sFgxzoAENHGwDV26Y2XuiE/poCtXu+4eYb3Rc5GE8N76+634AAID2Qx8ze07zq7/OF0iGOdDA4TRQ5V90Qh/pj1HtcI4YvnNEUeXPTcVL79l1QwAAsKpt3LzjqMV3/d3U9SLBMAca0MChN5De3/6KuetzKssjxPxnzg9DeX7YX9bpyY4LAADoUDEz++gQ88U9WCAY5kADGjicBq6fmN693gVlNBV1s9nxMaTnyDr9Udf9AADAqlWetfP4IjavnX8pd9eLA8McaEADh9lAWeWzuj6vsjwm6ov/l1+oD+05Ys9JW3fdzbEBAAAdKGJzZojp8h4sDAxzoAENLEUD/z62ae4IF5TRM74lnRBi/rTjZCjPlTeFOj2s64YAAGDVWVftGi/r5h09WBQY5kADGli6TYZq9/27Pr+y9Ma3XH5siPlDjpXhPF+WMb/AcQEAACtqbs3i+5Ou7XpBYJgDDWhgaRtI21xQRtCmuSNCzG9zvhjW80Xzlq4TAgCAVaV9/KZ9PK77xYBhDjSggSVvYGf7FfOuz7MsvbJK5ztehvacuWcqXnpPxwUAAKyA9Vt33SfE/LoQ874eLAYMc6ABDSx1A7d4v9hoKus0cLwM7TnzmnJmz2ldNwQAACOv/TVMqJuz25vwHiwEDHOgAQ0sVwMv6/p8y9ILVX6hY2Zoz5v7Qsw/7rgAAIBlVtT5jBDzxT1YBBjmQAMaWM4GPunR39Gz+K7a/Y6d4Tx/FjFXXTcEAAAjbbKaPTXE/O6ub/4Nc6ABDaxAAzd4xHD0FFV6sc2/oT5/vL794FjXHQEAwEgqz9p5fFnl80LMN/Xg5t8wBxrQwLI3UFb5rK7PvSytss5bHDtDff5899hg+5GOCwAAWGqb5o5YfFTqyz248TfMgQY0sDIN1PkDY4O5tS4qo6N9bNTxM9Tn0B0nDtJxXXcEAAAjZzLmx4WYd/bgpt8wBxrQwEo28LWwdXay63MwS2Qwt7aI+fcdQ0N9Hv1UOZ3u65gAAIAlVMykBxQxv70HN/yGOdCABla+gTo/30VlNIxvufzYENNbHUdDfS79bFk3ZdctAQDAyBg/Z8+6skrnh5hv7cENv2EONKCBLhr4h67PxSyN8S3phLLOH3QcDfO5tPmSD/EAAMASfuCjiOnc9ouX3d/sG+ZAAxrorIHPr5tp7u3iMvzG67QhxLzHsTTU59Mvt08kdN0SAAAMvZMHO+4aYtoaYr6qBzf6hjnQgAa6bGBfqNMTuj4vc/jCTP6xEPPVjqehPqf+98S2/EDHAwAAHIaNm3cctfhl3y/04CbfMAca0EDnDZRVPs+FZdjNrSnqHOc3cx1TwzyuKGI6veuaAABguL+EWKdN7df0enCDb5gDDWigLw188vRBOrrrUzSH7tRq9u4h5rf1oCUjHsYc1PkzE9O71zsWAADgUAzm1oYqPbuI+RKLE4szDWhAA9/WwHVTM80pLi7DazJe/BDv+xuB81qVd6+rdo133RMAAAzlo75l3fx8iHm28xt7wxxoQAN9bKDKv9j1uZpDNbcmxOalIeabOu/ImDvMOdjefpDMsQAAAAehfZStiPm5Iea9FiUWZhrQgAbuuIEy5je5uAynyW27TwpV/idtj8L5Lb21HFx2TNdNAQDAcL0Dqc7T7dfzur+hN8yBBjTQ3wbaVyK058yuz9scvLLKzwgxf6Xrhoy8RB/fmVvjOAAAgAOwbqa5d1mnQYj5KgsSizINaEADd9rAjeV0eqgLzHAZ35JOKGP+K32PxDnuxiKm53TdFAAADIX1W3fdZ3Hj75oe3Mwb5kADGhiKBsqYX9D1+ZuDU8TmzBDz57tux8hLMQdXlDPpEY4BAAC4E2XdlCHm17T/gm4xYkGmAQ1o4GA2/5o3u8gMj7Atf3+I+e81PiLnuSp/dPycPeu67goAAHptPDYPWnz86ZbOb+INc6ABDQxfA5+aipfes+tzOQdgMLe2qJvNIeav9qAbIx72HOwvYv79jZt3HKV/AAC4Q3NrijqfEer8zvYG2kLEQkwDGtDAITVwS1nlR7rQ9N9ENfuDIeaP6XxkznVfK+v8zK67AgCAXhrfcvmxZUy/HGJqenDzbpgDDWhgqBvw3r/+K6fTfYs6/6V/7Bql4675+HidNnTdFgAA9E77bpwQm1eGmK/s+sbdMAca0MAoNNC+OqHrczvf3emDdHSom7NDzNd23YqxZHOwr4jpXI/8AgDA7RR1szHU+QLv97MAswDVgAaWsIE67zp5sOOuLjp9fcVF2tS+m1Hzo3PeK6r8ubJuHt91XQAA0Bsnbd11t/Yx3zLmi7q+YTfMgQY0MIINXF3Ul0x1fa7nO82/2zbmT/agEWMJ56CI6S/Ks3Yer3kAABgbG5usZk8tY/6DEPM1Fh8WXxrQgAaWpYF9IeYfd9HplzCdf6CI+Z81P4q/+ktP7rovAADoXDm47JgipucUMf+bF5x3v1gxzIEGRruBsk6Drs/7fPtrLsq6eYfr38iN/UVsXrthsPceegcAYFUrZ/acVlb5vBDzV3pwo26YAw1oYOQbKGP+l7FNc0d0ff5nbGwyXvyQMjZvtvE3gqPOu0JsHqVzAABWrVOr2buHOj0vxPyxzm/QDXOgAQ2srgb2FvVF9+r6OrDaFTOzjw5V/qce9GAs/Rx8vf1q89hg+5FddwYAACtvMLc21OkJi1/yvc6iw6JLAxrQwIo3cG0xkx7gEtiRwdzassrPCDF/RPsjef7bH2J+4/pzdk84xgAAWHXaG+GizjHEdGkPbs4Nc6ABDazWBvYVsTmz62vCanT6IB1dxPzcEHPuQQfG8szBjonp9NiuWwMAgBU1viWdUMTml8o6f9B7jSy2LDg1oIEeNFDlc1wKV1Y5ne5bVM2vh5j/u/O/v7FMc5Aubzd32193Or4AAFhFX/Ftzlx8mfnNFhsWXBrQgAZ608Dbxsbm1nR9nVhVX/St0vkh5ht78Lc3lmcOrm6fbhjfcvmxXfcGAADLb7D9yDCTfyzE/Ib5l15baFhoaUADGuhbAztP2rrrbi6Jy6vdCCrr5ufbR0F78Dc3lm8Ori9iOrc8a+fxjikAAEbbYPuRRZV/NMT8uhDzVyw0LLQ0oAEN9LaBL0+enUPXl41RNh6bB4XY/GH7i7Ae/L2N5ZuDr5Ux//bktt0ndd0cAAAsm42bdxxVxuZJIeY/CzFfaZFhkaUBDWig9w3cVMT0GJfGpdf+ojLU6Xkh5o/14O9sLO8cXFnE5hVFfdG9HEsAAIzsO/0WHu9t/jzEfJVFhkWWBjSggaFpYH+IzbO6vo6MlMHc2lCnJ4SYXx9ivrYHf2NjeefgilA3Z584SMd1nR4AACzP13vrtCnU+QILHIsrC0wNaGA4G2h/seQSuTSKmE4v6zQIMX+667+rsQJzUOfPFDG/xMc9AAAYOUV9yVRZ5y0h5u0h5tssMCyyNKABDQx1A2/wxd/DU9ZNGWLaFmJOPfh7GisxB1X6cFk3P9u+53iJbq8AAKBbpw/S0QuPMTW/Y3FjYWVxqQENjE4DZZ0/uOFFe+/iOnvw1s009y7r9Csh5g8tPELd/d/TWPY5uK6s0vmT8eKHOGYAABgJ68/ZPVHUzeYQ89+3X7KzqLCw0oAGNDByDextN7G6vt4Mk6l46T3bX32VMf1jiPnmHvwNjRWZg9QUVXpx+/fvukEAADgsJw923HXhq73N75QxX2RRYVGlAQ1oYKQbuGpqpjnFpfPOldPpvmVMv1zE9F6bfqtq3BpifltRpSd6RB4AgOG1ae6IySo9fOGdRen9IeabenCzbZgDDWhAA8vfwI1FTI/p+jLUZ+N12hDqPB1i/kiIeZ/jcjUdl81/hZi2jp+zZ13XHQJAP34pVDdl1/87gIMxt6b9MmGI+Vfbf9EOMV/d/U22YQ40oAENrHAD+0LV/KTr53cq6mZjiPk3Q5V3Oy5X13FZVPlzZZXPm9iWH+jYAIDbCXX6oxCbL4U6v7OIzSvKOj15fEs6wURBTwzm1rYvqW7fWbO44feVrm+wDXOgAQ1ooOMGqubXur489cX4lsuPXXz1xR+GmD+rzVV3fromxPxnZd08vr1n6rpHAOivwdzaMuY33cHFdG+I6W+KmF8SYvOo9uaq6/+psBq0X3EMM80Phdi8tKybd/iFX+cLC8McaEADvWqg/YXT2CrXvvdw/h61at4TYr6h67+JseJz8LUyNm8uqvRT5eCyY7ruEQCGxumDdHQR8z8fwAt0U6jzBUWdYxGbM/1SEJbmpeTt8VTWaRBivrB9p5OFhMWUBjSgAQ3cYQNV/tvV+CGD9rU1RZ3PaDc/Q8zZ8bEqj48vz69DYnNm+4+lXTcJAENrw2DvPULM/3mQF+L2Zcp7QsxvLGKu2huzdTPNvbv+vwX66sRBOm5iOj22rPOWdhEX6vyZHtxQG+ZAAxrQwHA0cGH7j7Zjq8R4bB608AGP5n2+2rtqRyrq9H/LmfQIj/cCwBL/EinE/KkluFi3719518K/0jbPat9ftppuWKHVPpLS3rC2H+soYvqLEPPFIebbenAzbZgDDWhAA8PXwH+2/1g7ylfY9sN0oU7PC3Xz1yHmK3ow58bKz0H7FMS/thu/49P5fl03CQAjbbxOG+Y/CrL0F/Rb2g2Qosp/F2LaNlGlp4Wts5Or8TEWRk/7OHyo0xPaX/YVdf7LEPPOxeYtHsyBBjSgAQ0cbgN71m/ddZ+xETO5bfdJZd38bKjyn4aYLtXJqjxX3FbG5uNFTOcWVXqi9/kBwAor6mZj+3LdFbrwXxdi/uT845Axv6yo06b2kQ/v9qCPNm7ecVQxkx7Qdhpi88r2l65FlT/XgxtowxxoQAMaGMEG2mvMVEzF2Agoz9p5fBnT08u6edXir+I7n1+jkznYE+r86lA1P1nUF92r6y4BYNVr3+fX8ftWblt8HPldITa/E+r8/HK6+eF11a5xvxpkubWPrLcb0UVMP13G9BshNm+Z/wiOX/VZLFkwakADGli5Br5Szuw5bViv+htm9p4Y6vwTRcy/H2Le4TUYq/LYaT8iuCPU6Y/a1wKtP2f3RNddAgB3oL1pW7xwz/Xw/SAXFzG/PcT0e6HKLyxj86SJ6d3rxwbbj/TH5IAM5ta27xua/6pgzC+Yb6nO71z8sE0fuzfMgQY0oIHV08C1E9XsDw7TFb3d3AlVenYRm9cu/qPZ/h7Mo7Gyc/DlMqZ/LKtmpv2H+5O27rpb110CAAeovZFb/NrvsNxA3brwHpn0/vZdbGWdBu0Lpdv3irQvE/Zo8Sr86u62/MBiunlqqJpfC1X63RDzPywuTG7qQa+GOdCABjSggds3cGNZN48f67n214lFbH4p1PkCX7VflcfxtSHmD7W/7itifq6PdgDACCjqZvOI/Stu+2W5j5Uxv2nhsZS0tYjpOe3Ndnsz618rh8Rg+5HtI+Fhpvmh+ffIVOnFCxt86a0h5k+0j071oDXDHGhAAxrQwME0cGv7obSxvtk0d0So08OKmF+yeJ39or/r6jm25993vPCUxG8WVfqp+adufMgPAEZT+3XTrm8+Vnh8PcTUhJi3h5jf0D4eWtQ5llX6hSI2Z5ZVfmRRXzLV/sKs67/NqGnndPFr1I9qXxY+vwFd5Ze3L4wu6+Ydi5t7VwzZL1MNc6ABDWhAA3fWwG3tkxdjPXkXbjEz++gyNnWo8j8t/tpLwyM/B82XQp0/EGJ+XYjNS9snaNbNNPfuukcAYIXNb8J0fmPSy3HD4pdgP1HE9N6iyn9XVPlPipjObTcN2w2s9quxRZV/tH2fT7u5dfLZe77v5MGOu45yxO2X3dpf6LUf05iM+XGLX/77+bLKZy1+WOMPFzZX87uKmP8jxPzZdi578Pc0zIEGNKABDax0A/vbV5Z0euEezK0tYnrM4jv8rtLAyJ4H2o/85RDz37f3qvP3ZjPpEb7ICwB8mzLm3+7BjcuojatDzF8IMe9tv5RWxPxvoWreU8bmzSE2f15W6fwQ8x+XVT5vYTQzC79GzGctbC42m9uv1c5vMt5+VOmn2o9c3H4s/LLuO//fl3V+5jf/M6v04va/J8S07Rv/3e3G5vz/njpfsPC/L18YYv5IiHnnwrsX5x+7vb4Hc2qYAw1oQAMaGJYG9rcfo+rbLef6rbvuU9TNxsUv+b5k8Wu+bwtV/ujiP3ze3IO5M75zDm5euCdr3tfeRxZV8+tl1fxc+6qbsHV20gfzAIAD1m5GueFyw6kBDWhAAxrQgAYOv4H2H/SG9Ta03SRsf+kfYnpK+0uyEPPL2o9CtBuF8/+gOf/RreZLI/Yu6S7Hl0PMFy/+A+wbFt593Lx04evL6THtkxftLzm77gIAGBlza9p/UezBTZBhDjSgAQ1oQAMaGNoG2nfsja0Gg7m1878onEkPaF8L0n68q/3VY/tUQ/t0Sft0QftkQRnzv5Sx+XgR8yWLTxXc2vXfaJlGuyF6Zfv0x8L/vem9IeY3hphfE2LzylA3Z7ePhLfvnZ6oL/5f7cZe+17Grv+MAMBqNJhbaxOw85tHwxxoQAMa0IAGhrSBsk6Drm/nhuXjYBtm9p7Yfnxtcuvsg9sNsfZ1Ju3XkhdeYfI/ry0pYvOKb7y2pH3X8PzG4sKrVN64sMH4zdeXHNRoP0L2Lf//3/iN/9yF/+z0e/P/nTH9xvwrWur0K+2vIedfrRKbJ5XTzQ+3j1JPzTSnTMVUlGftPL7rOQUAOIRNwPz6rm+gDXOgAQ1oQAMa0MAwNdB+fMFtJwAAQ/Y4cPu4Qvc304Y50IAGNKABDWig7w20j7x2ffcGAACHYG5NqPOru76hNsyBBjSgAQ1oQAN9bqB9VNStJgAAQ8wmYNeLCsMcaEADGtCABvrbgM0/AABGhE3ArhcXhjnQgAY0oAEN9K+B9uMUXd+lAQDAUr8T8I+7vtE2zIEGNKABDWhAA71ooMovd6sJAMAIsgnY+WLDMAca0IAGNKCBzhsoYq66visDAIBlVdZp0PWNt2EONKABDWhAAxrooIH9ZZ23uNUEAGBVKOocLbwsvDSgAQ1oQAMaWEUN3Bbq9Lyu78EAAGBFlXX6lRDzvh7ckBvmQAMa0IAGNKCB5Wzg5qJOm9xqAgCwKoUqPTvEfKtFh4WnBjSgAQ1oQAMj2sBNZZWf0fU9FwAAdGqiSk8LMd/Ygxt0wxxoQAMa0IAGNLCUDVxX1PkMt5oAAND+EjCmp4SYr7fosPDUgAY0oAENaGBEGrhqskoPd6MHAADfYjLmx4WYr+nBDbthDjSgAQ1oQAMaOJwGPj+xLT/QjR4AANyB9mY5xHS5RYeFpwY0oAENaEADw9lAaqZiKtzoAQDA91DWTdnePHd/A2+YAw1oQAMa0IAGDryBMjYf3zCz90Q3egAAcADGt6QTQpU+bNFh4akBDWhAAxrQwJA08K+nVrN3d6MHAAAH4eTBjruGOr+zBzf0hjnQgAY0oAENaOB7NfCGjZt3HOVGDwAADsVg+5Eh5tdbdFh4akADGtCABjTQywaq9LtjY3Nr3OgBAMBhmVtT1mnQ+Q2+YQ40oAENaEADGvifBvYXdY5u8gAAYAmFKr8wxHybxZfFlwY0oAENaEADHTdwfajzT7jRAwCAZVDW6ckh5mst/Cz8NKABDWhAAxroqIEri5nZR7vRAwCAZTS5dfbBIebPWvhZ+GlAAxrQgAY0sMINfGp8Ot/PjR4AAKyAiWr25BDzDgs/Cz8NaEADGtCABlakgSp/dMPM3hPd6AEAwAo6aeuuu5V18w4LPws/DWhAAxrQgAaWuYE3bnjR3ru40QMAgE74QrBFr0WvBjSgAQ1oYNka2F9W+byxwdxaN3oAANCxUOfnh5hvsQCyCNaABjSgAQ1oYIkauDFU6dld3+MAAADfoozNk3wh2KLPwl8DGtCABjSwBA18vqibjW60AACgh6ZmmlNCzNniz+JPAxrQgAY0oIFDbOA/15+ze6LrexoAAOB7OLWavXsR89st/Cz8NKABDWhAAxo4mAbKmN908mDHXd1oAQDAMNg0d0T70u725d0WfxZ/GtCABjSgAQ3cSQP7Qkzb2o+LdX0LAwAAHKSyzs8MMV9n4WfhpwENaEADGtDAd2ng2rLKz3CTBQAAQ2wyXvyQEPOnLfws/DSgAQ1oQAMauF0Ds6Haff+u71UAAIAlsG6muXeIzfss/Cz8NKABDWhAAxpYeN9f+sepeOk93WgBAMAI2bh5x1Eh5j+28LPw04AGNKABDazqBm4rq2bG+/4AAGCEhdg8y3sBO198GeZAAxrQgAa6aODaiSo9ret7EQAAYAUUMZ0eYmosvizANaABDWhAA6umgU8W9SVTbrQAAGAVOXGQjgsxv7EHCxLDHGhAAxrQgAaWs4E6XzC+5fJju773AAAAOlLUzeYQ880WnxafGtCABjSggZFr4IZQp+e5yQIAANpNwI0h5k/3YKFimAMNaEADGtDA0jQwO7EtP9BtDgAA8E3rZpp7h6p5j4WnhacGNKABDWhgyBuom79uX/XhNgcAAPhOg7m1IeaXhZhv7XzxYpgDDWhAAxrQwME2cEMZ0y+7xQEAAO7UZJUeHmL+lIWXxbcGNKABDWhgWBpIzWS8+CFucwAAgAO2YbD3HqHKf9v9gsYwBxrQgAY0oIHv2UCdLzhp6667uc0BAAAOSRHzc0PM11l8WnxqQAMa0IAGetfAtWXd/KxbHAAA4LCVM3tOCzHv7MFCxzAHGtCABjSggZjnyth8fGJ693q3OQAAwJIpB5cdE2LzhyHm/RafFp8a0IAGNKCBzhrYF2LzyrHB9iPd5gAAAMuiiM2ZIeYvW/hZ/GtAAxrQgAZWvIHLJmN+nFscAABg2a3fuus+Rcxvt/Cz+NeABjSgAQ2sTANlbN5c1Bfdy20OAACwooo6bQoxf9XizwaABjSgAQ1oYNka+GqIzbPc4gAAAJ2ZPDuHEPN2Cz+Lfw1oQAMa0MDSNlDE/M/j5+xZ5zYHAADogbk1Rd1sDjFfb/FnA0ADGtCABjRw2A3cUMT8kvb62vUVHgAA4NsUM+kBIeYdFn4W/xrQgAY0oIFDbuAjk9XsqW4xAACA3tq4ecdRoc6/FWK+1eLPBoAGNKABDWjggBu4vqjSi8cGc2u7vpYDAAAckMl48UP8GtDC38JfAxrQgAYOoIEqfXhqpjnFLQYAADB8BtuPLOocQ8w3WQDaBNCABjSgAQ3cwbv+6hz96g8AABiVdwN+zMLP4l8DGtCABjSw2ECdP1DUl0x1fY0GAABYOoO5tYtfCv66xZ8NAA1oQAMaWMUNXDP/hV/v+gMAAEZV2Do7GWK+sAcLMMMcaEADGtDAyjZQ53euq3aNd30tBgAAWAFza0Kdnx9i/qrFpw0IDWhAAxoY/QbS5RNVeppbDAAAYNWZ3Lb7pFDnC0LM+7uSomZAAAAKa0lEQVRfnBnmQAMa0IAGlryB/WWVzt8w2HuPrq+5AAAAnQp1ekKIedbC0+aDBjSgAQ2MSgNlzBeVVX6kWwwAAIBFGzfvOKp9KbqPhHS/aDXMgQY0oIHDauC6sk6D0wfpaBd5AACAO1DUl0yFKv+TxacNCA1oQAMaGLoG6vzOqZgKF3gAAIADUMTmzBDzZztfzBnmQAMa0IAG7ryBvWVsnuQCDwAAcJBOrWbvHmLzOyHmmy3ALcA1oAENaKCHDVwfYtrmcV8AAIDDNDXTnBJiflcPFnqGOdCABjSggcUG0lsnz87BRR4AAGAJFVV6YqjybotPGxAa0IAGNNBhA9njvgAAAMtpsP3Iom42h5i/YgPABoAGNKABDaxgA1e2X6sf2zR3hAs9AADAChjfkk4o6+ZVIebbbADYANCABjSggWVs4Jb2ejMVL72nCzwAAEAHyun00BDzdot/i38NaEADGljqBsqY/nF8Ot/PBR4AAKAHijqfEeq8ywaADQANaEADGliCBnaEOj2h62sbAAAAtzeYW1vE/NwQ8xdsANgA0IAGNKCBg22gqPLn2vfMttcTF1kAAIAeO3mw465FnWOI+RobADYANKABDWjgABq4qr1ulIPLjun6GgYAAMBBWDfT3Lus8nkh5ptsANgA0IAGNKCBO2jg5vYDH+VZO493gQUAABhiE9O715cxvynEvN8GgA0ADWhAAxoIMd8aYn79VExF19coAAAAltBENfuDIeZ3W/xb/GtAAxpYtQ3sC1X+26mZ5hQXWAAAgBFWVvmRoc7v7MFC1DAHGtCABlaugQtDnR7W9TUIAACAFRRi86gQm/fZgLABoQENaGCEG6jShyem02NdYAEAAFaxos5nhCp/tPNFqmEONKABDSxlA9snY35c19cYAAAAerYRWMbm4xbgNmE0oAENDHEDVfpwUaUndn1NAQAAoLfm1hTTzVP9IrAHi3jDHGhAAwe18de8p321Q9dXEQAAAIZIEdNjfCzEBoRNKA1ooPcNXFjOpEd0fc0AAABgiE1W6eFFzG8PMe/vwULXMAca0IAGYt4XYvOWMJ1/oOtrBAAAACOkiOn0UOcLQsy32oCwAaEBDWigkwZubs/D5cye07q+JgAAADDCJqZ3ry+rdH6I+SYbADaBNKABDaxIA9cUMZ1bTqf7dn0NAAAAYBVZv3XXfYo6xxDzFTYAbAJpQAMaWJYGvljWaVDUF92r63M+AAAAq9j4lsuPLepmc4ipsQFgE0gDGtDAUjTQ/FdZpV/Y8KK9d+n6HA8AAADfYm5NUeczFr8c7IMhNoJsBGlAAwfXwL72i75FbM5sz6cuLwAAAPTaZLz4IYvvCbzRJohNEA1oQAPfs4Gvz58vq9337/rcDQAAAActbMvfX8b0G94TaAPIBpAGNHC7Bur8mVDn6fKsnce7vAAAADD8Ns0d0T7W1j7e5vFgG0E2gjQQVvtjvnXaNDbYfmTXp2YAAABYFlMzzSlllc8LMV/Vg8W4YQ40oIGVaOCa9jHfYiY9wKUFAACAVaMcXHZMEfNzQ8w7bUDYhNKABka0gR3tV9JPHuy4a9fnXAAAAOhUEdNjQsxvCDHf0IMFu2EONKCBw/q1XxGb1xZ1s9GlBQAAAG5nw2DvPRZ/Fdi+K9AmjDnQgAaG7td+Jw7ScU7uAAAAcADKmT2nLbwrsPlSDxb2hjnQgAbuqIH/LuvmVeOxeZATOwAAAByi0wfp6FA1PxlifleI+TabEDaiNKCBjhu4paybd5QxPX3j5h1HObkDAADAEpqoZk8u6hzLmC+yCWQTSAMaWOEGPhZi/tWTz97zfU7sAAAAsAKKmE4v6zQIMX/KRpCNIA1oYJka+Gz7KoKpmeYUJ3YAAADozNyaien02BDza0LMV9oIshGkAQ0cZgNfWTifNI9qzy9O7gAAANAnm+aOKOp8RqjzBSHmr9sIshGkAQ0cYANXt+eNIjZneq8fAAAADIkTB+m4ss7PLGN+k81Am0A2AjVwBw18tYjpL0JMT7HpBwAAAENufMvlx7Zf7Fz8ZeDVNoNsBmlg1TbwzV/6bXjR3rt0fW4CAAAAlusx4ZgeU9bNq0LMV/RgQ8IwBxpYxgaKKn+urNL57abf6YN0tBMrAAAArCab5o4o6+bxC5uB6VIbUTaiNDAiDVR5d4j5N4u62ehDHgAAAMA3hWr3/UNMW0NM7w8x39r5JoZhDjRwoA3cHGL+1xCbl05M717vtAYAAADcqZO27rpb+8hg++hgiPkLNmJsxmmgbw00X5p/n1+dNk3FS+/ptAYAAAAcusHc2nImPaJ9pDDE/IkQ823db34Y5mDVNXBrWecPlrGpJ+PFD3FKAwAAAJbNiYN0XFHnM8oqnxdi3tGDjRHDHIxoA+nS+Q941GlTUV90L6c1AAAAoBPldLpvu0Gx8Lhwurz7TRPDHAxtA18sY/Pmom42T8VUOKUBAAAAPTS3ZnLr7IPLOm8p6+YdIeYre7CpYpiDvjbw+RDT35Qxv6Cc2XNa10cvAAAAwCEp6kumipifu/hBkdSDTRfDHHTVwBXf+IVfEdPpTikAAADASFpX7Rov6+ZnQ51fXcZ8UYh5nw0pm5Ij2MDNRcz/UdbNq8o6PzNsy9/f9bEHAAAA0InyrJ3HT9bN/w5VPifE/A8Lj0V2vnljmIODa6DOnymq/Hdllc8KM80PlYPLjnFKAQAAAPguJqrZk8uYnh5i/s0ipveGmK+yIWVTskcNfDHE/O4Qm1e2nbYfwnEwAwAAABymiend68uYf6as8nkLmy++ONyDjbDVMD5bxPz2IjavKKabp46fs2edgxkAAABghYxvSSeUdfP4okovDlX+0zI2Hw8xX9+DTSNj+Obg2hDzx0LMrytifklR5zNOPnvP9zmYAQAAAPpmMLd2aqY5pajST4UqvzzE9Dch5k8sbvB0vclkdD8HbQefKGP+qyLmKsT0lMmzc+g6WwAAAACWQFFfdK+ibjYWMT+3fZS4jM2bQ8w7Qsw39mBjyli6ObglxHRpiPnC9iu8Rd1sbn/RV9SXTI2Nza1xMAEAAACsMqcP0tHtrwbbrxEXsfmlUOffCnXz16FKH178KvE+G3S92qDct/gOyA+FOl/QfiwmVPkXiyo9cX6Tb9PcEV03BQAAAMCQbRCO12lD+yuydqOpjOk32ncOhjq/c/Hx4naT8JYebIyNwrh1cT7/vf34RqjTH4WYX1ZWzc+1G3zt36H9e3TdBAAAAACr0OS23SeNx+ZBZWye1D5mXNQ5ljH/QYj5DSHmd4UqfzTE1ITYfGlxo2tulYyrQsyzIeaPlHXzjhDz6+e/5lw3Z89v7E03Tw3T+QfCtvz97Tscu/47AgAAAMCSOLWavXv7AYpyOj001OkJ7YdL2keQ268at5uHITavDFX63bJK57ebiIvvLLywiPnfFt9duGfhXXfz77v7dIj56tuN6+5kY27/Hfz/acdnF/8zdy7896T3t/+9Iaa3zv9vqPKfFlX+k8X3KNahyi8sYnpOEZszy+nmh0OdHtY+itt+pXlssP1IuQAAAAAAAABjw+z/A9K0awSV2TUAAAAAAElFTkSuQmCC";
16
+
17
+ // Load Highcharts content from node_modules for inlining
18
+ const highchartsPath = path.resolve(
19
+ process.cwd(),
20
+ "node_modules/highcharts/highcharts.js",
21
+ );
22
+ let highchartsContent = "";
23
+ try {
24
+ highchartsContent = readFileSync(highchartsPath, "utf8");
25
+ } catch (e) {
26
+ // If not found in process.cwd(), try relative to the script
27
+ try {
28
+ highchartsContent = readFileSync(
29
+ path.resolve(__dirname, "../node_modules/highcharts/highcharts.js"),
30
+ "utf8",
31
+ );
32
+ } catch (e2) {
33
+ console.warn(
34
+ "Highcharts could not be loaded from node_modules. Falling back to CDN.",
35
+ );
36
+ }
37
+ }
9
38
 
10
39
  /**
11
40
  * Dynamically imports the 'chalk' library for terminal string styling.
@@ -27,18 +56,8 @@ try {
27
56
  };
28
57
  }
29
58
 
30
- /**
31
- * @constant {string} DEFAULT_OUTPUT_DIR
32
- * The default directory where the report will be generated.
33
- */
34
59
  const DEFAULT_OUTPUT_DIR = "pulse-report";
35
60
 
36
- /**
37
- * @constant {string} DEFAULT_JSON_FILE
38
- * The default name for the JSON file containing the test data.
39
- */
40
- const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
41
-
42
61
  /**
43
62
  * @constant {string} DEFAULT_HTML_FILE
44
63
  * The default name for the generated HTML report file.
@@ -338,6 +357,12 @@ function generateTestTrendsChart(trendData) {
338
357
  color: "var(--warning-color)",
339
358
  marker: { symbol: "circle" },
340
359
  },
360
+ {
361
+ name: "Flaky",
362
+ data: runs.map((r) => r.flaky || 0),
363
+ color: "#00ccd3",
364
+ marker: { symbol: "circle" },
365
+ },
341
366
  ];
342
367
  const runsForTooltip = runs.map((r) => ({
343
368
  runId: r.runId,
@@ -541,6 +566,9 @@ function generateTestHistoryChart(history) {
541
566
  case "skipped":
542
567
  color = "var(--warning-color)";
543
568
  break;
569
+ case "flaky":
570
+ color = "var(--neutral-500)";
571
+ break;
544
572
  default:
545
573
  color = "var(--dark-gray-color)";
546
574
  }
@@ -657,6 +685,9 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
657
685
  case "Failed":
658
686
  color = "var(--danger-color)";
659
687
  break;
688
+ case "Flaky":
689
+ color = "#00ccd3";
690
+ break;
660
691
  case "Skipped":
661
692
  color = "var(--warning-color)";
662
693
  break;
@@ -764,62 +795,264 @@ function generatePieChart(data, chartWidth = 300, chartHeight = 300) {
764
795
  * @param {number} [dashboardHeight=600] The height of the dashboard.
765
796
  * @returns {string} The HTML string for the environment dashboard.
766
797
  */
767
- function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
768
- // Format memory for display
769
- const formattedMemory = environment.memory.replace(/(\d+\.\d{2})GB/, "$1 GB");
798
+ function generateEnvironmentSection(environmentData) {
799
+ if (!environmentData) {
800
+ return '<div class="no-data">Environment data not available.</div>';
801
+ }
770
802
 
771
- // Generate a unique ID for the dashboard
772
- const dashboardId = `envDashboard-${Date.now()}-${Math.random()
773
- .toString(36)
774
- .substring(2, 7)}`;
803
+ if (Array.isArray(environmentData)) {
804
+ return `
805
+ <div class="sharded-env-section">
806
+ <div class="sharded-env-header">
807
+ <div class="sharded-env-title-row">
808
+ <div>
809
+ <div class="sharded-env-title">
810
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
811
+ <rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
812
+ <rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
813
+ <line x1="6" x2="6.01" y1="6" y2="6"></line>
814
+ <line x1="6" x2="6.01" y1="18" y2="18"></line>
815
+ </svg>
816
+ System Information
817
+ </div>
818
+ <div class="sharded-env-subtitle">Test execution environment details - ${environmentData.length} shard${environmentData.length > 1 ? "s" : ""}</div>
819
+ </div>
820
+ <div class="env-icon-badge">
821
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
822
+ <path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
823
+ </svg>
824
+ </div>
825
+ </div>
826
+ </div>
827
+ <div class="sharded-environments-container">
828
+ <div class="sharded-environments-wrapper">
829
+ ${environmentData
830
+ .map(
831
+ (env, index) => `
832
+ <div class="env-card-wrapper">
833
+ <div class="env-card-badge">Shard ${index + 1}</div>
834
+ ${generateEnvironmentDashboard(env, true)}
835
+ </div>
836
+ `,
837
+ )
838
+ .join("")}
839
+ </div>
840
+ </div>
841
+ </div>
842
+ <style>
843
+ .sharded-env-section {
844
+ border: 1px solid var(--border-light);
845
+ border-radius: 12px;
846
+ background: var(--bg-secondary);
847
+ overflow: hidden;
848
+ }
849
+ .sharded-env-header {
850
+ position: sticky;
851
+ top: 0;
852
+ z-index: 20;
853
+ background: linear-gradient(to bottom right, var(--bg-primary) 0%, var(--bg-secondary) 100%);
854
+ border-bottom: 1px solid var(--border-light);
855
+ padding: 24px 24px 16px;
856
+ }
857
+ .sharded-env-title-row {
858
+ display: flex;
859
+ justify-content: space-between;
860
+ align-items: center;
861
+ }
862
+ .sharded-env-title {
863
+ display: flex;
864
+ align-items: center;
865
+ font-size: 18px;
866
+ font-weight: 600;
867
+ color: var(--text-primary);
868
+ }
869
+ .sharded-env-title svg {
870
+ width: 18px;
871
+ height: 18px;
872
+ margin-right: 8px;
873
+ stroke: currentColor;
874
+ fill: none;
875
+ }
876
+ .sharded-env-subtitle {
877
+ font-size: 13px;
878
+ color: var(--text-secondary);
879
+ margin-top: 4px;
880
+ }
881
+ .sharded-environments-container {
882
+ max-height: 520px;
883
+ overflow-y: auto;
884
+ overflow-x: hidden;
885
+ padding: 16px;
886
+ }
887
+ .sharded-environments-container::-webkit-scrollbar {
888
+ width: 8px;
889
+ }
890
+ .sharded-environments-container::-webkit-scrollbar-track {
891
+ background: var(--bg-tertiary);
892
+ border-radius: 4px;
893
+ }
894
+ .sharded-environments-container::-webkit-scrollbar-thumb {
895
+ background: var(--border-medium);
896
+ border-radius: 4px;
897
+ }
898
+ .sharded-environments-container::-webkit-scrollbar-thumb:hover {
899
+ background: var(--border-color);
900
+ }
901
+ .sharded-environments-wrapper {
902
+ display: grid;
903
+ grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
904
+ gap: 24px;
905
+ }
906
+ @media (max-width: 768px) {
907
+ .sharded-environments-wrapper {
908
+ grid-template-columns: 1fr;
909
+ }
910
+ }
911
+ .env-card-wrapper {
912
+ position: relative;
913
+ }
914
+ .env-card-badge {
915
+ position: absolute;
916
+ top: -10px;
917
+ right: 16px;
918
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
919
+ color: white;
920
+ padding: 6px 14px;
921
+ border-radius: 20px;
922
+ font-size: 0.75em;
923
+ font-weight: 700;
924
+ text-transform: uppercase;
925
+ letter-spacing: 0.5px;
926
+ z-index: 10;
927
+ box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.3);
928
+ }
929
+ </style>
930
+ `;
931
+ }
775
932
 
776
- const cardHeight = Math.floor(dashboardHeight * 0.44);
777
- const cardContentPadding = 16; // px
933
+ return generateEnvironmentDashboard(environmentData);
934
+ }
778
935
 
779
- // Logic for Run Context
780
- const runContext = process.env.CI ? "CI" : "Local Test";
936
+ function generateEnvironmentDashboard(environment, hideHeader = false) {
937
+ const cpuModel = environment.cpu && environment.cpu.model ? environment.cpu.model : "N/A";
938
+ const cpuCores = environment.cpu && environment.cpu.cores ? environment.cpu.cores : "N/A";
939
+ const cpuInfo = `model: ${cpuModel}, cores: ${cpuCores}`;
940
+ const cwdInfo = environment.cwd || "N/A";
781
941
 
782
942
  return `
783
- <div class="environment-dashboard-wrapper" id="${dashboardId}">
943
+ <div class="env-modern-card${hideHeader ? " no-header" : ""}">
784
944
  <style>
785
- .environment-dashboard-wrapper *,
786
- .environment-dashboard-wrapper *::before,
787
- .environment-dashboard-wrapper *::after {
788
- box-sizing: border-box;
789
- }
790
-
791
- .environment-dashboard-wrapper {
792
- --primary-color: var(--primary-dark, #6366f1);
793
- --success-color: var(--success-dark, #10b981);
794
- --warning-color: var(--warning-dark, #f59e0b);
795
-
796
- background-color: var(--bg-tertiary);
797
- padding: 48px;
798
- border-bottom: 1px solid var(--border-light);
945
+ .env-modern-card {
946
+ background: linear-gradient(to bottom right, var(--bg-primary) 0%, var(--bg-secondary) 100%);
947
+ border: 0;
948
+ border-radius: 12px;
949
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
950
+ margin-top: 24px;
951
+ transition: all 0.3s ease;
799
952
  font-family: var(--font-family);
953
+ overflow: hidden;
954
+ }
955
+ .env-modern-card:hover {
956
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
957
+ }
958
+ .env-modern-card {
959
+ margin-bottom: 0;
960
+ }
961
+ .env-card-header {
962
+ display: flex;
963
+ flex-direction: column;
964
+ padding: 24px 24px 12px;
965
+ }
966
+ .env-modern-card.no-header .env-card-header {
967
+ display: none;
968
+ }
969
+ .env-modern-card.no-header {
970
+ margin-top: 0;
971
+ }
972
+ .env-modern-card.no-header .env-card-content {
973
+ padding-top: 24px;
974
+ }
975
+ .env-card-title-row {
976
+ display: flex;
977
+ justify-content: space-between;
978
+ align-items: center;
979
+ }
980
+ .env-card-title {
981
+ display: flex;
982
+ align-items: center;
983
+ font-size: 16px;
984
+ font-weight: 600;
800
985
  color: var(--text-primary);
986
+ transition: color 0.3s;
987
+ }
988
+ .env-modern-card:hover .env-card-title {
989
+ color: var(--primary-dark, #6366f1);
990
+ }
991
+ .env-card-title svg {
992
+ width: 16px;
993
+ height: 16px;
994
+ margin-right: 8px;
995
+ stroke: currentColor;
996
+ fill: none;
997
+ }
998
+ .env-card-subtitle {
999
+ font-size: 12px;
1000
+ color: var(--text-secondary);
1001
+ margin-top: 4px;
1002
+ }
1003
+ .env-card-content {
1004
+ padding: 0 24px 24px;
1005
+ }
1006
+ .env-items-grid {
801
1007
  display: grid;
802
1008
  grid-template-columns: repeat(2, 1fr);
803
- gap: 32px;
804
- font-size: 15px;
805
- transform: translateZ(0);
1009
+ gap: 10px;
806
1010
  }
807
-
808
- @media (max-width: 768px) {
809
- .environment-dashboard-wrapper {
810
- grid-template-columns: 1fr;
811
- padding: 32px 24px;
812
- }
1011
+ @media (min-width: 768px) {
1012
+ .env-items-grid {
1013
+ grid-template-columns: repeat(4, 1fr);
1014
+ }
813
1015
  }
814
- @media (max-width: 480px) {
815
- .environment-dashboard-wrapper {
816
- padding: 24px;
817
- }
1016
+ .env-item {
1017
+ display: flex;
1018
+ align-items: flex-start;
1019
+ gap: 8px;
1020
+ padding: 8px;
1021
+ border-radius: 8px;
1022
+ transition: background-color 0.2s;
1023
+ min-height: 48px;
818
1024
  }
819
-
820
- .env-dashboard-header {
821
- grid-column: 1 / -1;
822
- margin-bottom: 24px;
1025
+ .env-item:hover {
1026
+ background-color: var(--bg-hover);
1027
+ }
1028
+ .env-item-icon {
1029
+ flex-shrink: 0;
1030
+ }
1031
+ .env-item-icon svg {
1032
+ width: 16px;
1033
+ height: 16px;
1034
+ stroke: var(--primary-dark, #6366f1);
1035
+ fill: none;
1036
+ }
1037
+ .env-item-content {
1038
+ flex-grow: 1;
1039
+ min-width: 0;
1040
+ }
1041
+ .env-item-label {
1042
+ font-size: 12px;
1043
+ font-weight: 500;
1044
+ color: var(--text-secondary);
1045
+ white-space: nowrap;
1046
+ overflow: hidden;
1047
+ text-overflow: ellipsis;
1048
+ }
1049
+ .env-item-value {
1050
+ font-size: 12px;
1051
+ font-weight: 600;
1052
+ color: var(--text-primary);
1053
+ word-wrap: break-word;
1054
+ overflow-wrap: break-word;
1055
+ line-height: 1.4;
823
1056
  }
824
1057
 
825
1058
  .env-dashboard-title {
@@ -951,133 +1184,151 @@ function generateEnvironmentDashboard(environment, dashboardHeight = 600) {
951
1184
  }
952
1185
  </style>
953
1186
 
954
- <div class="env-dashboard-header">
955
- <div>
956
- <h3 class="env-dashboard-title">System Environment</h3>
957
- <p class="env-dashboard-subtitle">Snapshot of the execution environment</p>
958
- </div>
959
- <span class="env-chip env-chip-primary">${environment.host}</span>
960
- </div>
961
-
962
- <div class="env-card">
963
- <div class="env-card-header">
964
- <svg viewBox="0 0 24 24"><path d="M4 6h16V4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8h-2v10H4V6zm18-2h-4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2H6a2 2 0 0 0-2 2v2h20V6a2 2 0 0 0-2-2zM8 12h8v2H8v-2zm0 4h8v2H8v-2z"/></svg>
965
- Hardware
966
- </div>
967
- <div class="env-card-content">
968
- <div class="env-detail-row">
969
- <span class="env-detail-label">CPU Model</span>
970
- <span class="env-detail-value">${environment.cpu.model}</span>
971
- </div>
972
- <div class="env-detail-row">
973
- <span class="env-detail-label">CPU Cores</span>
974
- <span class="env-detail-value">
975
- <div class="env-cpu-cores">
976
- <span>${environment.cpu.cores || "N/A"} core${environment.cpu.cores !== 1 ? "s" : ""}</span>
977
- </div>
978
- </span>
979
- </div>
980
- <div class="env-detail-row">
981
- <span class="env-detail-label">Memory</span>
982
- <span class="env-detail-value">${formattedMemory}</span>
1187
+ <div class="env-card-header">
1188
+ <div class="env-card-title-row">
1189
+ <div>
1190
+ <div class="env-card-title">
1191
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1192
+ <rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect>
1193
+ <rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect>
1194
+ <line x1="6" x2="6.01" y1="6" y2="6"></line>
1195
+ <line x1="6" x2="6.01" y1="18" y2="18"></line>
1196
+ </svg>
1197
+ System Information
1198
+ </div>
1199
+ <div class="env-card-subtitle">Test execution environment details</div>
983
1200
  </div>
984
1201
  </div>
985
1202
  </div>
986
1203
 
987
- <div class="env-card">
988
- <div class="env-card-header">
989
- <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-0.01 18c-2.76 0-5.26-1.12-7.07-2.93A7.973 7.973 0 0 1 4 12c0-2.21.9-4.21 2.36-5.64A7.994 7.994 0 0 1 11.99 4c4.41 0 8 3.59 8 8 0 2.76-1.12 5.26-2.93 7.07A7.973 7.973 0 0 1 11.99 20zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/></svg>
990
- Operating System
991
- </div>
992
- <div class="env-card-content">
993
- <div class="env-detail-row">
994
- <span class="env-detail-label">OS Type</span>
995
- <span class="env-detail-value">${
996
- environment.os.split(" ")[0] === "darwin"
997
- ? "darwin (macOS)"
998
- : environment.os.split(" ")[0] || "Unknown"
999
- }</span>
1000
- </div>
1001
- <div class="env-detail-row">
1002
- <span class="env-detail-label">OS Version</span>
1003
- <span class="env-detail-value">${
1004
- environment.os.split(" ")[1] || "N/A"
1005
- }</span>
1006
- </div>
1007
- <div class="env-detail-row">
1008
- <span class="env-detail-label">Hostname</span>
1009
- <span class="env-detail-value" title="${environment.host}">${
1010
- environment.host
1011
- }</span>
1204
+ <div class="env-card-content">
1205
+ <div class="env-items-grid">
1206
+ <div class="env-item">
1207
+ <div class="env-item-icon">
1208
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1209
+ <path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
1210
+ </svg>
1211
+ </div>
1212
+ <div class="env-item-content">
1213
+ <p class="env-item-label">Host</p>
1214
+ <div class="env-item-value" title="${environment.host}">${environment.host}</div>
1215
+ </div>
1012
1216
  </div>
1013
- </div>
1014
- </div>
1015
-
1016
- <div class="env-card">
1017
- <div class="env-card-header">
1018
- <svg viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
1019
- Node.js Runtime
1020
- </div>
1021
- <div class="env-card-content">
1022
- <div class="env-detail-row">
1023
- <span class="env-detail-label">Node Version</span>
1024
- <span class="env-detail-value">${environment.node}</span>
1217
+
1218
+ <div class="env-item">
1219
+ <div class="env-item-icon">
1220
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1221
+ <path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"></path>
1222
+ </svg>
1223
+ </div>
1224
+ <div class="env-item-content">
1225
+ <p class="env-item-label">Os</p>
1226
+ <div class="env-item-value" title="${environment.os}">${environment.os}</div>
1227
+ </div>
1025
1228
  </div>
1026
- <div class="env-detail-row">
1027
- <span class="env-detail-label">V8 Engine</span>
1028
- <span class="env-detail-value">${environment.v8}</span>
1229
+
1230
+ <div class="env-item">
1231
+ <div class="env-item-icon">
1232
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1233
+ <rect width="16" height="16" x="4" y="4" rx="2"></rect>
1234
+ <rect width="6" height="6" x="9" y="9" rx="1"></rect>
1235
+ <path d="M15 2v2"></path>
1236
+ <path d="M15 20v2"></path>
1237
+ <path d="M2 15h2"></path>
1238
+ <path d="M2 9h2"></path>
1239
+ <path d="M20 15h2"></path>
1240
+ <path d="M20 9h2"></path>
1241
+ <path d="M9 2v2"></path>
1242
+ <path d="M9 20v2"></path>
1243
+ </svg>
1244
+ </div>
1245
+ <div class="env-item-content">
1246
+ <p class="env-item-label">Cpu</p>
1247
+ <div class="env-item-value" title='${JSON.stringify(environment.cpu)}'>${cpuInfo}</div>
1248
+ </div>
1029
1249
  </div>
1030
- <div class="env-detail-row">
1031
- <span class="env-detail-label">Working Dir</span>
1032
- <span class="env-detail-value" title="${environment.cwd}">${
1033
- environment.cwd.length > 25
1034
- ? "..." + environment.cwd.slice(-22)
1035
- : environment.cwd
1036
- }</span>
1250
+
1251
+ <div class="env-item">
1252
+ <div class="env-item-icon">
1253
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1254
+ <path d="M6 19v-3"></path>
1255
+ <path d="M10 19v-3"></path>
1256
+ <path d="M14 19v-3"></path>
1257
+ <path d="M18 19v-3"></path>
1258
+ <path d="M8 11V9"></path>
1259
+ <path d="M16 11V9"></path>
1260
+ <path d="M12 11V9"></path>
1261
+ <path d="M2 15h20"></path>
1262
+ <path d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0 0-3.837Z"></path>
1263
+ </svg>
1264
+ </div>
1265
+ <div class="env-item-content">
1266
+ <p class="env-item-label">Memory</p>
1267
+ <div class="env-item-value" title="${environment.memory}">${environment.memory}</div>
1268
+ </div>
1037
1269
  </div>
1038
- </div>
1039
- </div>
1040
-
1041
- <div class="env-card">
1042
- <div class="env-card-header">
1043
- <svg viewBox="0 0 24 24"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 8.69 9.48 7 12 7c2.76 0 5 2.24 5 5v1h2c1.66 0 3 1.34 3 3s-1.34 3-3 3z"/></svg>
1044
- System Summary
1045
- </div>
1046
- <div class="env-card-content">
1047
- <div class="env-detail-row">
1048
- <span class="env-detail-label">Platform Arch</span>
1049
- <span class="env-detail-value">
1050
- <span class="env-chip ${
1051
- environment.os.includes("darwin") &&
1052
- environment.cpu.model.toLowerCase().includes("apple")
1053
- ? "env-chip-success"
1054
- : "env-chip-warning"
1055
- }">
1056
- ${
1057
- environment.os.includes("darwin") &&
1058
- environment.cpu.model.toLowerCase().includes("apple")
1059
- ? "Apple Silicon"
1060
- : environment.cpu.model.toLowerCase().includes("arm") ||
1061
- environment.cpu.model.toLowerCase().includes("aarch64")
1062
- ? "ARM-based"
1063
- : "x86/Other"
1064
- }
1065
- </span>
1066
- </span>
1270
+
1271
+ <div class="env-item">
1272
+ <div class="env-item-icon">
1273
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1274
+ <path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
1275
+ <path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
1276
+ <path d="M12 2v2"></path>
1277
+ <path d="M12 22v-2"></path>
1278
+ <path d="m17 20.66-1-1.73"></path>
1279
+ <path d="M11 10.27 7 3.34"></path>
1280
+ <path d="m20.66 17-1.73-1"></path>
1281
+ <path d="m3.34 7 1.73 1"></path>
1282
+ <path d="M14 12h8"></path>
1283
+ <path d="M2 12h2"></path>
1284
+ <path d="m20.66 7-1.73 1"></path>
1285
+ <path d="m3.34 17 1.73-1"></path>
1286
+ <path d="m17 3.34-1 1.73"></path>
1287
+ <path d="m11 13.73-4 6.93"></path>
1288
+ </svg>
1289
+ </div>
1290
+ <div class="env-item-content">
1291
+ <p class="env-item-label">Node</p>
1292
+ <div class="env-item-value" title="${environment.node}">${environment.node}</div>
1293
+ </div>
1067
1294
  </div>
1068
- <div class="env-detail-row">
1069
- <span class="env-detail-label">Memory per Core</span>
1070
- <span class="env-detail-value">${
1071
- environment.cpu.cores > 0
1072
- ? (
1073
- parseFloat(environment.memory) / environment.cpu.cores
1074
- ).toFixed(2) + " GB"
1075
- : "N/A"
1076
- }</span>
1295
+
1296
+ <div class="env-item">
1297
+ <div class="env-item-icon">
1298
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1299
+ <path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"></path>
1300
+ <path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"></path>
1301
+ <path d="M12 2v2"></path>
1302
+ <path d="M12 22v-2"></path>
1303
+ <path d="m17 20.66-1-1.73"></path>
1304
+ <path d="M11 10.27 7 3.34"></path>
1305
+ <path d="m20.66 17-1.73-1"></path>
1306
+ <path d="m3.34 7 1.73 1"></path>
1307
+ <path d="M14 12h8"></path>
1308
+ <path d="M2 12h2"></path>
1309
+ <path d="m20.66 7-1.73 1"></path>
1310
+ <path d="m3.34 17 1.73-1"></path>
1311
+ <path d="m17 3.34-1 1.73"></path>
1312
+ <path d="m11 13.73-4 6.93"></path>
1313
+ </svg>
1314
+ </div>
1315
+ <div class="env-item-content">
1316
+ <p class="env-item-label">V8</p>
1317
+ <div class="env-item-value" title="${environment.v8}">${environment.v8}</div>
1318
+ </div>
1077
1319
  </div>
1078
- <div class="env-detail-row">
1079
- <span class="env-detail-label">Run Context</span>
1080
- <span class="env-detail-value">${runContext}</span>
1320
+
1321
+ <div class="env-item">
1322
+ <div class="env-item-icon">
1323
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1324
+ <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
1325
+ <polyline points="9 22 9 12 15 12 15 22"></polyline>
1326
+ </svg>
1327
+ </div>
1328
+ <div class="env-item-content">
1329
+ <p class="env-item-label">Working Dir</p>
1330
+ <div class="env-item-value" title="${cwdInfo}">${cwdInfo.length > 30 ? "..." + cwdInfo.slice(-27) : cwdInfo}</div>
1331
+ </div>
1081
1332
  </div>
1082
1333
  </div>
1083
1334
  </div>
@@ -1105,11 +1356,11 @@ function generateWorkerDistributionChart(results) {
1105
1356
  const workerId =
1106
1357
  typeof test.workerId !== "undefined" ? test.workerId : "N/A";
1107
1358
  if (!acc[workerId]) {
1108
- acc[workerId] = { passed: 0, failed: 0, skipped: 0, tests: [] };
1359
+ acc[workerId] = { passed: 0, failed: 0, skipped: 0, flaky: 0, tests: [] };
1109
1360
  }
1110
1361
 
1111
1362
  const status = String(test.status).toLowerCase();
1112
- if (status === "passed" || status === "failed" || status === "skipped") {
1363
+ if (status === "passed" || status === "failed" || status === "skipped" || status === "flaky") {
1113
1364
  acc[workerId][status]++;
1114
1365
  }
1115
1366
 
@@ -1154,6 +1405,7 @@ function generateWorkerDistributionChart(results) {
1154
1405
  const passedData = workerIds.map((id) => workerData[id].passed);
1155
1406
  const failedData = workerIds.map((id) => workerData[id].failed);
1156
1407
  const skippedData = workerIds.map((id) => workerData[id].skipped);
1408
+ const flakyData = workerIds.map((id) => workerData[id].flaky);
1157
1409
 
1158
1410
  const categoriesString = JSON.stringify(categories);
1159
1411
  const fullDataString = JSON.stringify(fullWorkerData);
@@ -1161,6 +1413,7 @@ function generateWorkerDistributionChart(results) {
1161
1413
  { name: "Passed", data: passedData, color: "var(--success-color)" },
1162
1414
  { name: "Failed", data: failedData, color: "var(--danger-color)" },
1163
1415
  { name: "Skipped", data: skippedData, color: "var(--warning-color)" },
1416
+ { name: "Flaky", data: flakyData, color: "#00ccd3" },
1164
1417
  ]);
1165
1418
 
1166
1419
  // The HTML now includes the chart container, the modal, and styles for the modal
@@ -1168,33 +1421,39 @@ function generateWorkerDistributionChart(results) {
1168
1421
  <style>
1169
1422
  .worker-modal-overlay {
1170
1423
  position: fixed; z-index: 1050; left: 0; top: 0; width: 100%; height: 100%;
1171
- overflow: auto; background-color: rgba(0,0,0,0.92);
1424
+ overflow: auto; background-color: rgba(0,0,0,0.85);
1172
1425
  display: none; align-items: center; justify-content: center;
1426
+ backdrop-filter: blur(4px);
1427
+ -webkit-backdrop-filter: blur(4px);
1173
1428
  }
1174
1429
  .worker-modal-content {
1175
- background-color: var(--bg-card);
1176
- color: var(--card-background-color);
1177
- margin: auto; padding: 20px; border: 1px solid var(--border-color, #888);
1178
- width: 80%; max-width: 700px; border-radius: 8px;
1179
- position: relative; box-shadow: var(--shadow-2xl);
1430
+ background-color: var(--bg-card, #ffffff);
1431
+ color: var(--text-color, #1f2937);
1432
+ padding: 30px; border: 1px solid var(--border-color, #e5e7eb);
1433
+ width: 80%; max-width: 700px; border-radius: 12px;
1434
+ position: relative; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
1435
+ flex-shrink: 0; margin: 20px;
1436
+ z-index: 1051;
1437
+ transform: translateZ(0); /* Force hardware acceleration/composite layer in safari */
1438
+ -webkit-transform: translateZ(0);
1180
1439
  }
1181
1440
  .worker-modal-close {
1182
1441
  position: absolute;
1183
- top: 15px;
1442
+ top: 20px;
1184
1443
  right: 25px;
1185
- font-size: 32px;
1186
- font-weight: bold;
1444
+ font-size: 28px;
1445
+ font-weight: 400;
1187
1446
  cursor: pointer;
1188
1447
  line-height: 1;
1189
1448
  z-index: 10;
1190
- color: #fff;
1191
- transition: color 0.2s ease;
1449
+ color: var(--text-color-secondary, #6b7280);
1450
+ transition: color 0.2s ease, transform 0.2s ease;
1192
1451
  user-select: none;
1193
1452
  -webkit-user-select: none;
1194
1453
  }
1195
1454
  .worker-modal-close:hover, .worker-modal-close:focus {
1196
- color: var(--danger-color);
1197
- transform: scale(1.1);
1455
+ color: var(--danger-color, #ef4444);
1456
+ transform: scale(1.15);
1198
1457
  }
1199
1458
  #worker-modal-body-${chartId} ul {
1200
1459
  list-style-type: none; padding-left: 0; margin-top: 15px; max-height: 45vh; overflow-y: auto;
@@ -1222,19 +1481,22 @@ function generateWorkerDistributionChart(results) {
1222
1481
  <div id="worker-modal-${chartId}" class="worker-modal-overlay">
1223
1482
  <div class="worker-modal-content">
1224
1483
  <span class="worker-modal-close" onclick="window.${modalJsNamespace}.close()">×</span>
1225
- <h3 id="worker-modal-title-${chartId}" style="text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: #fff"></h3>
1484
+ <h3 id="worker-modal-title-${chartId}" style="text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 1.25em; font-weight: 600; color: var(--text-color, #1f2937)"></h3>
1226
1485
  <div id="worker-modal-body-${chartId}"></div>
1227
1486
  </div>
1228
1487
  </div>
1229
1488
 
1230
1489
  <script>
1231
1490
  // Namespace for modal functions to avoid global scope pollution
1232
- window.${modalJsNamespace} = {};
1491
+ if (!window.${modalJsNamespace}) window.${modalJsNamespace} = {};
1233
1492
 
1234
1493
  window.${renderFunctionName} = function() {
1235
1494
  const chartContainer = document.getElementById('${chartId}');
1236
1495
  if (!chartContainer) { console.error("Chart container ${chartId} not found."); return; }
1237
1496
 
1497
+ // Ensure namespace exists when rendering
1498
+ if (!window.${modalJsNamespace}) window.${modalJsNamespace} = {};
1499
+
1238
1500
  // --- Modal Setup ---
1239
1501
  const modal = document.getElementById('worker-modal-${chartId}');
1240
1502
  const modalTitle = document.getElementById('worker-modal-title-${chartId}');
@@ -1252,6 +1514,7 @@ window.${renderFunctionName} = function() {
1252
1514
  if (test.status === 'passed') color = 'var(--success-color)';
1253
1515
  else if (test.status === 'failed') color = 'var(--danger-color)';
1254
1516
  else if (test.status === 'skipped') color = 'var(--warning-color)';
1517
+ else if (test.status === 'flaky') color = '#00ccd3';
1255
1518
 
1256
1519
  // Updated escaping logic
1257
1520
  const escapedName = test.name.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -1345,20 +1608,31 @@ window.${renderFunctionName} = function() {
1345
1608
  * @type {string}
1346
1609
  */
1347
1610
  const infoTooltip = `
1348
- <span class="info-tooltip" style="display: inline-block; margin-left: 8px;">
1611
+ <span class="info-tooltip" style="display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; vertical-align: middle;">
1349
1612
  <span class="info-icon"
1350
- style="cursor: pointer; font-size: 1.25rem;"
1351
- onclick="window.workerInfoPrompt()">ℹ️</span>
1613
+ style="cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-color-secondary, #6b7280); transition: color 0.2s ease, transform 0.2s ease;"
1614
+ onmouseover="this.style.color='var(--accent-color, #764ba2)'; this.style.transform='scale(1.1)';"
1615
+ onmouseout="this.style.color='var(--text-color-secondary, #6b7280)'; this.style.transform='scale(1)';"
1616
+ onclick="window.workerInfoPrompt()"
1617
+ title="Click to understand Worker -1">
1618
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1619
+ <circle cx="12" cy="12" r="10"></circle>
1620
+ <line x1="12" y1="16" x2="12" y2="12"></line>
1621
+ <line x1="12" y1="8" x2="12.01" y2="8"></line>
1622
+ </svg>
1623
+ </span>
1352
1624
  </span>
1353
1625
  <script>
1354
- window.workerInfoPrompt = function() {
1355
- const message = 'Why is worker -1 special?\\n\\n' +
1356
- 'Playwright assigns skipped tests to worker -1 because:\\n' +
1357
- '1. They don\\'t require browser execution\\n' +
1358
- '2. This keeps real workers focused on actual tests\\n' +
1359
- '3. Maintains clean reporting\\n\\n' +
1360
- 'This is an intentional optimization by Playwright.';
1361
- alert(message);
1626
+ if (!window.workerInfoPrompt) {
1627
+ window.workerInfoPrompt = function() {
1628
+ const message = 'Why is worker -1 special?\\n\\n' +
1629
+ 'Playwright assigns all pre-skipped tests/test.skip() to worker -1 because:\\n' +
1630
+ '1. They don\\'t require browser execution\\n' +
1631
+ '2. This keeps real workers focused on actual tests\\n' +
1632
+ '3. Maintains clean reporting\\n\\n' +
1633
+ 'This is an intentional optimization by Playwright.';
1634
+ alert(message);
1635
+ }
1362
1636
  }
1363
1637
  </script>
1364
1638
  `;
@@ -1427,6 +1701,7 @@ function generateTestHistoryContent(trendData) {
1427
1701
  <option value="">All Statuses</option>
1428
1702
  <option value="passed">Passed</option>
1429
1703
  <option value="failed">Failed</option>
1704
+ <option value="flaky">Flaky</option>
1430
1705
  <option value="skipped">Skipped</option>
1431
1706
  </select>
1432
1707
  <button id="clear-history-filters" class="clear-filters-btn">Clear Filters</button>
@@ -1499,6 +1774,8 @@ function getStatusClass(status) {
1499
1774
  return "status-failed";
1500
1775
  case "skipped":
1501
1776
  return "status-skipped";
1777
+ case "flaky":
1778
+ return "status-flaky";
1502
1779
  default:
1503
1780
  return "status-unknown";
1504
1781
  }
@@ -1516,6 +1793,8 @@ function getStatusIcon(status) {
1516
1793
  return "❌";
1517
1794
  case "skipped":
1518
1795
  return "⏭️";
1796
+ case "flaky":
1797
+ return "⚠️";
1519
1798
  default:
1520
1799
  return "❓";
1521
1800
  }
@@ -1556,6 +1835,7 @@ function getSuitesData(results) {
1556
1835
  browser: browser,
1557
1836
  passed: 0,
1558
1837
  failed: 0,
1838
+ flaky: 0,
1559
1839
  skipped: 0,
1560
1840
  count: 0,
1561
1841
  statusOverall: "passed",
@@ -1563,12 +1843,15 @@ function getSuitesData(results) {
1563
1843
  }
1564
1844
  const suite = suitesMap.get(key);
1565
1845
  suite.count++;
1566
- const currentStatus = String(test.status).toLowerCase();
1846
+ let currentStatus = String(test.status).toLowerCase();
1847
+ if (test.outcome === 'flaky') currentStatus = 'flaky';
1567
1848
  if (currentStatus && suite[currentStatus] !== undefined) {
1568
1849
  suite[currentStatus]++;
1569
1850
  }
1570
1851
  if (currentStatus === "failed") suite.statusOverall = "failed";
1571
- else if (currentStatus === "skipped" && suite.statusOverall !== "failed")
1852
+ else if (currentStatus === "flaky" && suite.statusOverall !== "failed")
1853
+ suite.statusOverall = "flaky";
1854
+ else if (currentStatus === "skipped" && suite.statusOverall !== "failed" && suite.statusOverall !== "flaky")
1572
1855
  suite.statusOverall = "skipped";
1573
1856
  });
1574
1857
  return Array.from(suitesMap.values());
@@ -1614,8 +1897,8 @@ function generateSuitesWidget(suitesData) {
1614
1897
 
1615
1898
  // Added inline styles for height consistency with Pie Chart (approx 450px) and scrolling
1616
1899
  return `
1617
- <div class="suites-widget" style="height: 450px; display: flex; flex-direction: column;">
1618
- <div class="suites-header" style="flex-shrink: 0;">
1900
+ <div class="suites-widget fixed-height-widget">
1901
+ <div class="suites-header">
1619
1902
  <h2>Test Suites</h2>
1620
1903
  <span class="summary-badge">${
1621
1904
  suitesData.length
@@ -1625,40 +1908,40 @@ function generateSuitesWidget(suitesData) {
1625
1908
  )} tests</span>
1626
1909
  </div>
1627
1910
 
1628
- <div class="suites-grid-container" style="flex-grow: 1; overflow-y: auto; padding-right: 5px;">
1911
+ <div class="suites-grid-container">
1629
1912
  <div class="suites-grid">
1630
1913
  ${suitesData
1631
1914
  .map(
1632
1915
  (suite) => `
1633
1916
  <div class="suite-card status-${suite.statusOverall}">
1634
1917
  <div class="suite-card-header">
1635
- <h3 class="suite-name" title="${sanitizeHTML(
1636
- suite.name,
1637
- )} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
1918
+ <h3 class="suite-name" title="${sanitizeHTML(suite.name)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
1919
+ <div class="status-indicator-dot status-${suite.statusOverall}" title="${suite.statusOverall.charAt(0).toUpperCase() + suite.statusOverall.slice(1)}"></div>
1920
+ </div>
1921
+
1922
+ <div class="browser-tag" title="🌐Browser: ${sanitizeHTML(suite.browser)}">
1923
+ <span style="font-size: 1.1em;">🌐</span> ${sanitizeHTML(suite.browser)}
1638
1924
  </div>
1639
- <div style="margin-bottom: 12px;"><span class="browser-tag" title="🌐 ${sanitizeHTML(suite.browser)}">🌐 ${sanitizeHTML(
1640
- suite.browser,
1641
- )}</span></div>
1925
+
1642
1926
  <div class="suite-card-body">
1643
- <span class="test-count">${suite.count} test${
1644
- suite.count !== 1 ? "s" : ""
1645
- }</span>
1927
+ <span class="test-count-label">${suite.count} Test${suite.count !== 1 ? "s" : ""}</span>
1646
1928
  <div class="suite-stats">
1647
- ${
1648
- suite.passed > 0
1649
- ? `<span class="stat-passed" title="Passed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg> ${suite.passed}</span>`
1650
- : ""
1651
- }
1652
- ${
1653
- suite.failed > 0
1654
- ? `<span class="stat-failed" title="Failed"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg> ${suite.failed}</span>`
1655
- : ""
1656
- }
1657
- ${
1658
- suite.skipped > 0
1659
- ? `<span class="stat-skipped" title="Skipped"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg> ${suite.skipped}</span>`
1660
- : ""
1661
- }
1929
+ <span class="stat-pill passed" title="Passed">
1930
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg>
1931
+ ${suite.passed}
1932
+ </span>
1933
+ <span class="stat-pill failed" title="Failed">
1934
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg>
1935
+ ${suite.failed}
1936
+ </span>
1937
+ <span class="stat-pill flaky" title="Flaky">
1938
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
1939
+ ${suite.flaky || 0}
1940
+ </span>
1941
+ <span class="stat-pill skipped" title="Skipped">
1942
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>
1943
+ ${suite.skipped}
1944
+ </span>
1662
1945
  </div>
1663
1946
  </div>
1664
1947
  </div>`,
@@ -2037,6 +2320,7 @@ function generateSeverityDistributionChart(results) {
2037
2320
  const data = {
2038
2321
  passed: [0, 0, 0, 0, 0],
2039
2322
  failed: [0, 0, 0, 0, 0],
2323
+ flaky: [0, 0, 0, 0, 0],
2040
2324
  skipped: [0, 0, 0, 0, 0],
2041
2325
  };
2042
2326
 
@@ -2055,6 +2339,8 @@ function generateSeverityDistributionChart(results) {
2055
2339
  status === "interrupted"
2056
2340
  ) {
2057
2341
  data.failed[index]++;
2342
+ } else if (status === "flaky") {
2343
+ data.flaky[index]++;
2058
2344
  } else {
2059
2345
  data.skipped[index]++;
2060
2346
  }
@@ -2068,6 +2354,7 @@ function generateSeverityDistributionChart(results) {
2068
2354
  const seriesData = [
2069
2355
  { name: "Passed", data: data.passed, color: "var(--success-color)" },
2070
2356
  { name: "Failed", data: data.failed, color: "var(--danger-color)" },
2357
+ { name: "Flaky", data: data.flaky, color: "#00ccd3" },
2071
2358
  { name: "Skipped", data: data.skipped, color: "var(--warning-color)" },
2072
2359
  ];
2073
2360
 
@@ -2211,32 +2498,85 @@ function generateHTML(reportData, trendData = null) {
2211
2498
  duration: 0,
2212
2499
  timestamp: new Date().toISOString(),
2213
2500
  };
2214
- const totalTestsOr1 = runSummary.totalTests || 1;
2215
- const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
2216
- const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
2217
- const skipPercentage = Math.round(
2218
- ((runSummary.skipped || 0) / totalTestsOr1) * 100,
2219
- );
2501
+
2220
2502
  const avgTestDuration =
2221
2503
  runSummary.totalTests > 0
2222
2504
  ? formatDuration(runSummary.duration / runSummary.totalTests)
2223
2505
  : "0.0s";
2224
2506
 
2507
+ const flakyCount = (results || []).filter(r => r.outcome === 'flaky').length;
2508
+
2225
2509
  // Calculate retry statistics
2510
+ let retriedTestsCount = 0;
2226
2511
  const totalRetried = (results || []).reduce((acc, test) => {
2227
- if (test.retries && test.retries > 0) {
2228
- return acc + 1;
2512
+ if (test.retryHistory && test.retryHistory.length > 0) {
2513
+ // Filter out any "passed" or "skipped" entries in the history
2514
+ // We only count attempts that actually failed or timed out, triggering a retry.
2515
+ const unsuccessfulRetries = test.retryHistory.filter(attempt =>
2516
+ attempt.status === 'failed' || attempt.status === 'timedout' || attempt.status === 'flaky'
2517
+ );
2518
+ if (unsuccessfulRetries.length > 0) {
2519
+ retriedTestsCount++;
2520
+ }
2521
+ return acc + unsuccessfulRetries.length;
2229
2522
  }
2230
2523
  return acc;
2231
2524
  }, 0);
2232
2525
 
2526
+ // --- RECALCULATE KPI METRICS BASED ON FINAL_STATUS ---
2527
+ let calculatedPassed = 0;
2528
+ let calculatedFailed = 0;
2529
+ let calculatedSkipped = 0;
2530
+ let calculatedFlaky = 0;
2531
+ let calculatedTotal = 0;
2532
+
2533
+ (results || []).forEach(test => {
2534
+ calculatedTotal++;
2535
+ // New Logic: If outcome is 'flaky', it overrides everything.
2536
+ let statusToUse = test.status;
2537
+ if (test.outcome === 'flaky') {
2538
+ statusToUse = 'flaky';
2539
+ } else if (test.status === 'flaky') {
2540
+ // Just in case outcome wasn't set but status was (unlikely with new reporter)
2541
+ statusToUse = 'flaky';
2542
+ } else if (test.retryHistory && test.retryHistory.length > 0 && test.final_status) {
2543
+ statusToUse = test.final_status;
2544
+ }
2545
+
2546
+ // Update test status in place for charts
2547
+ test.status = statusToUse;
2548
+
2549
+ const s = String(statusToUse).toLowerCase();
2550
+ if (s === 'passed') calculatedPassed++;
2551
+ else if (s === 'skipped') calculatedSkipped++;
2552
+ else if (s === 'flaky') calculatedFlaky++;
2553
+ else calculatedFailed++; // failed, timedout, interrupted
2554
+ });
2555
+
2556
+ // Override runSummary counts with our calculated ones if results exist
2557
+ if (results && results.length > 0) {
2558
+ runSummary.passed = calculatedPassed;
2559
+ runSummary.failed = calculatedFailed;
2560
+ runSummary.skipped = calculatedSkipped;
2561
+ runSummary.flaky = calculatedFlaky;
2562
+ runSummary.totalTests = calculatedTotal;
2563
+ }
2564
+
2565
+ const totalTestsOr1 = runSummary.totalTests || 1;
2566
+ const passPercentage = Math.round((runSummary.passed / totalTestsOr1) * 100);
2567
+ const failPercentage = Math.round((runSummary.failed / totalTestsOr1) * 100);
2568
+ const skipPercentage = Math.round(
2569
+ ((runSummary.skipped || 0) / totalTestsOr1) * 100,
2570
+ );
2571
+ const flakyPercentage = Math.round(((runSummary.flaky || 0) / totalTestsOr1) * 100);
2572
+
2573
+
2233
2574
  // Calculate browser distribution
2234
2575
  const browserStats = (results || []).reduce((acc, test) => {
2235
2576
  let browserName = "unknown";
2236
2577
  if (test.browser) {
2237
- // Extract browser name from strings like "Chrome v143 on Windows 10"
2238
- const match = test.browser.match(/^(\w+)/);
2239
- browserName = match ? match[1] : test.browser;
2578
+ // Use full browser name
2579
+ browserName = test.browser;
2240
2580
  }
2241
2581
  acc[browserName] = (acc[browserName] || 0) + 1;
2242
2582
  return acc;
@@ -2277,6 +2617,10 @@ function generateHTML(reportData, trendData = null) {
2277
2617
  const severity = test.severity || "Medium";
2278
2618
  const severityBadge = `<span class="severity-badge" data-severity="${severity.toLowerCase()}">${severity}</span>`;
2279
2619
 
2620
+ // --- Retry Count Badge (only show if retries occurred) ---
2621
+ const retryCount = (test.retryHistory && test.retryHistory.length > 0) ? test.retryHistory.length : 0;
2622
+ const retryBadge = (test.retryHistory && test.retryHistory.length > 0) ? `<span class="retry-badge">Retry Count: ${retryCount}</span>` : '';
2623
+
2280
2624
  // --- Step Generation ---
2281
2625
  const generateStepsHTML = (steps, depth = 0) => {
2282
2626
  if (!steps || steps.length === 0)
@@ -2285,17 +2629,20 @@ function generateHTML(reportData, trendData = null) {
2285
2629
  .map((step) => {
2286
2630
  const hasNestedSteps = step.steps && step.steps.length > 0;
2287
2631
  const isHook = step.hookType;
2632
+ const isFailedStep = step.isFailedStep === true;
2288
2633
  const stepClass = isHook
2289
2634
  ? `step-hook step-hook-${step.hookType}`
2290
2635
  : "";
2636
+ const failedStepClass = isFailedStep ? " failed-step-highlight" : "";
2291
2637
  const hookIndicator = isHook ? ` (${step.hookType} hook)` : "";
2638
+ const failedStepIndicator = isFailedStep ? ` <span class="failed-step-marker">⚠️ Failed at this step</span>` : "";
2292
2639
  return `
2293
- <div class="step-item" style="--depth: ${depth};">
2640
+ <div class="step-item${failedStepClass}" style="--depth: ${depth};">
2294
2641
  <div class="step-header ${stepClass}" role="button" aria-expanded="false">
2295
2642
  <span class="step-icon">${getStatusIcon(step.status)}</span>
2296
2643
  <span class="step-title">${sanitizeHTML(
2297
2644
  step.title,
2298
- )}${hookIndicator}</span>
2645
+ )}${hookIndicator}${failedStepIndicator}</span>
2299
2646
  <span class="step-duration">${formatDuration(
2300
2647
  step.duration,
2301
2648
  )}</span>
@@ -2308,6 +2655,13 @@ function generateHTML(reportData, trendData = null) {
2308
2655
  )}</div>`
2309
2656
  : ""
2310
2657
  }
2658
+ ${
2659
+ step.codeSnippet
2660
+ ? `<div class="code-snippet-section"><pre class="code-snippet">${sanitizeHTML(
2661
+ step.codeSnippet,
2662
+ )}</pre></div>`
2663
+ : ""
2664
+ }
2311
2665
  ${
2312
2666
  step.errorMessage
2313
2667
  ? `<div class="test-error-summary">
@@ -2318,7 +2672,7 @@ function generateHTML(reportData, trendData = null) {
2318
2672
  )}</div>`
2319
2673
  : ""
2320
2674
  }
2321
- <button class="copy-error-btn" onclick="copyErrorToClipboard(this)" style="margin-top: 8px; padding: 6px 12px; background: rgba(248, 113, 113, 0.15); border: 2px solid var(--danger-color); border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--danger-color); font-weight: 600; transition: all 0.2s;" onmouseover="this.style.background='rgba(248, 113, 113, 0.25)'" onmouseout="this.style.background='rgba(248, 113, 113, 0.15)'">Copy Error Prompt</button>
2675
+ <button class="copy-error-btn" onclick="copyErrorToClipboard(this)" style="margin-top: 8px; padding: 6px 12px; background: rgba(248, 113, 113, 0.15); border: 2px solid var(--danger-color); border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--danger-color); font-weight: 600; transition: all 0.2s; align-self: flex-end; width: auto;" onmouseover="this.style.background='rgba(248, 113, 113, 0.25)'" onmouseout="this.style.background='rgba(248, 113, 113, 0.15)'">Copy Error Prompt</button>
2322
2676
  </div>`
2323
2677
  : ""
2324
2678
  }
@@ -2336,43 +2690,36 @@ function generateHTML(reportData, trendData = null) {
2336
2690
  .join("");
2337
2691
  };
2338
2692
 
2339
- return `
2340
- <div class="test-case" data-status="${
2341
- test.status
2342
- }" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
2343
- .join(",")
2344
- .toLowerCase()}">
2345
- <div class="test-case-header" role="button" aria-expanded="false">
2346
- <div class="test-case-summary">
2347
- <span class="test-case-title" title="${sanitizeHTML(
2348
- test.name,
2349
- )}">${sanitizeHTML(testTitle)}</span>
2350
- <span class="test-case-browser">(${sanitizeHTML(browser)})</span>
2351
- </div>
2352
- <div class="test-case-meta">
2353
- ${severityBadge}
2354
- ${
2355
- test.tags && test.tags.length > 0
2356
- ? test.tags
2357
- .map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
2358
- .join(" ")
2359
- : ""
2360
- }
2361
- </div>
2362
- <div class="test-case-status-duration">
2363
- <span class="status-badge ${getStatusClass(test.status)}">${String(
2364
- test.status,
2365
- ).toUpperCase()}</span>
2366
- <span class="test-duration">${formatDuration(test.duration)}</span>
2367
- </div>
2368
- </div>
2369
- <div class="test-case-content" style="display: none;">
2370
- <p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
2693
+ // Helper for Tab Badges
2694
+ const getSmallStatusBadge = (status) => {
2695
+ const s = String(status).toLowerCase();
2696
+ let colorVar = 'var(--text-tertiary)';
2697
+ if(s === 'passed') colorVar = 'var(--success-color)';
2698
+ else if(s === 'failed') colorVar = 'var(--danger-color)';
2699
+ else if(s === 'skipped') colorVar = 'var(--warning-color)';
2700
+ else if(s === 'flaky') colorVar = '#00ccd3';
2701
+
2702
+ return `<span style="
2703
+ display: inline-block;
2704
+ width: 8px;
2705
+ height: 8px;
2706
+ border-radius: 50%;
2707
+ background-color: ${colorVar};
2708
+ margin-left: 6px;
2709
+ vertical-align: middle;
2710
+ " title="${s}"></span>`;
2711
+ };
2712
+
2713
+ // Function to generate test content HTML (used for base run and retry tabs)
2714
+ const getTestContentHTML = (testData, runSuffix) => {
2715
+ const logId = `stdout-log-${test.id || testIndex}-${runSuffix}`;
2716
+ return `
2717
+ <p><strong>Full Path:</strong> ${sanitizeHTML(testData.name)}</p>
2371
2718
  ${
2372
- test.annotations && test.annotations.length > 0
2373
- ? `<div class="annotations-section">
2374
- <h4 style="margin-top: 0; margin-bottom: 10px; font-size: 1.1em;">📌 Annotations</h4>
2375
- ${test.annotations
2719
+ testData.annotations && testData.annotations.length > 0
2720
+ ? `<div class="annotations-section" style="margin: 12px 0; padding: 12px; background-color: rgba(139, 92, 246, 0.1); border: 1px solid rgba(139, 92, 246, 0.3); border-left: 4px solid #8b5cf6; border-radius: 4px;">
2721
+ <h4 style="margin-top: 0; margin-bottom: 10px; color: #8b5cf6; font-size: 1.1em;">📌 Annotations</h4>
2722
+ ${testData.annotations
2376
2723
  .map((annotation, idx) => {
2377
2724
  const isIssueOrBug =
2378
2725
  annotation.type === "issue" ||
@@ -2380,176 +2727,312 @@ function generateHTML(reportData, trendData = null) {
2380
2727
  const descriptionText = annotation.description || "";
2381
2728
  const typeLabel = sanitizeHTML(annotation.type);
2382
2729
  const descriptionHtml =
2383
- isIssueOrBug && descriptionText.match(/^[A-Z]+-\d+$/)
2730
+ isIssueOrBug && descriptionText.match(/^[A-Z]+-\\d+$/)
2384
2731
  ? `<a href="#" class="annotation-link" data-annotation="${sanitizeHTML(
2385
2732
  descriptionText,
2386
- )}" style="color: var(--info-color); text-decoration: underline; cursor: pointer; font-weight: 600;">${sanitizeHTML(
2733
+ )}" style="color: #3b82f6; text-decoration: underline; cursor: pointer;">${sanitizeHTML(
2387
2734
  descriptionText,
2388
2735
  )}</a>`
2389
2736
  : sanitizeHTML(descriptionText);
2390
2737
  const locationText = annotation.location
2391
- ? `<div style="font-size: 0.85em; color: var(--text-tertiary); margin-top: 4px;">Location: ${sanitizeHTML(
2738
+ ? `<div style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Location: ${sanitizeHTML(
2392
2739
  annotation.location.file,
2393
2740
  )}:${annotation.location.line}:${
2394
2741
  annotation.location.column
2395
2742
  }</div>`
2396
2743
  : "";
2397
2744
  return `<div style="margin-bottom: ${
2398
- idx < test.annotations.length - 1 ? "10px" : "0"
2399
- };"><strong>Type:</strong> <span style="background-color: rgba(139, 92, 246, 0.2); padding: 2px 8px; border-radius: 4px; font-size: 0.9em;">${typeLabel}</span>${
2745
+ idx < testData.annotations.length - 1 ? "10px" : "0"
2746
+ };">
2747
+ <strong style="color: #8b5cf6;">Type:</strong> <span style="background-color: rgba(139, 92, 246, 0.2); padding: 2px 8px; border-radius: 4px; font-size: 0.9em;">${typeLabel}</span>
2748
+ ${
2400
2749
  descriptionText
2401
- ? `<br><strong>Description:</strong> ${descriptionHtml}`
2750
+ ? `<br><strong style="color: #8b5cf6;">Description:</strong> ${descriptionHtml}`
2402
2751
  : ""
2403
- }${locationText}</div>`;
2752
+ }
2753
+ ${locationText}
2754
+ </div>`;
2404
2755
  })
2405
2756
  .join("")}
2406
2757
  </div>`
2407
2758
  : ""
2408
2759
  }
2409
2760
  <p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
2410
- test.workerId,
2761
+ testData.workerId,
2411
2762
  )} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
2412
- test.totalWorkers,
2763
+ testData.totalWorkers,
2413
2764
  )}]</p>
2414
2765
  ${
2415
- test.errorMessage
2416
- ? `<div class="test-error-summary">${formatPlaywrightError(
2417
- test.errorMessage,
2418
- )}<button class="copy-error-btn" onclick="copyErrorToClipboard(this)" style="margin-top: 8px; padding: 4px 8px; background: #f0f0f0; border: 2px solid #ccc; border-radius: 4px; cursor: pointer; font-size: 12px; border-color: #8B0000; color: #8B0000;" onmouseover="this.style.background='#e0e0e0'" onmouseout="this.style.background='#f0f0f0'">Copy Error Prompt</button></div>`
2766
+ testData.errorMessage
2767
+ ? `<div class="test-error-summary"><div class="stack-trace">${formatPlaywrightError(
2768
+ testData.errorMessage,
2769
+ )}</div>
2770
+ <button
2771
+ class="copy-error-btn"
2772
+ onclick="copyErrorToClipboard(this)"
2773
+ style="
2774
+ margin-top: 8px;
2775
+ padding: 6px 12px;
2776
+ background: rgba(248, 113, 113, 0.15);
2777
+ border: 2px solid var(--danger-color);
2778
+ border-radius: 6px;
2779
+ cursor: pointer;
2780
+ font-size: 12px;
2781
+ color: var(--danger-color);
2782
+ font-weight: 600;
2783
+ transition: 0.2s;
2784
+ align-self: flex-end;
2785
+ width: auto;
2786
+ "
2787
+ onmouseover="this.style.background='#e0e0e0'"
2788
+ onmouseout="this.style.background='#f0f0f0'"
2789
+ >
2790
+ Copy Error Prompt
2791
+ </button>
2792
+ </div>`
2419
2793
  : ""
2420
2794
  }
2421
2795
  ${
2422
- test.snippet
2796
+ testData.snippet
2423
2797
  ? `<div class="code-section"><h4>Error Snippet</h4><pre><code>${formatPlaywrightError(
2424
- test.snippet,
2798
+ testData.snippet,
2425
2799
  )}</code></pre></div>`
2426
2800
  : ""
2427
2801
  }
2428
2802
  <h4>Steps</h4>
2429
- <div class="steps-list">${generateStepsHTML(test.steps)}</div>
2430
-
2431
- ${(() => {
2432
- if (!test.stdout || test.stdout.length === 0) return "";
2433
- // FIXED: Now using 'testIndex' which is guaranteed to be defined
2434
- const logId = `stdout-log-${test.id || testIndex}`;
2435
- return `<div class="console-output-section"><h4>Console Output (stdout) <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</button></h4><div class="log-wrapper"><pre id="${logId}" class="console-log stdout-log" style="background-color: var(--bg-tertiary); color: #f3e8c3; padding: 1.25em; border-radius: 0.85em; line-height: 1.2; border: 1px solid var(--border-light);">${formatPlaywrightError(
2436
- test.stdout.map((line) => sanitizeHTML(line)).join("\n"),
2437
- )}</pre></div></div>`;
2438
- })()}
2439
-
2440
- ${(() => {
2441
- if (!test.stderr || test.stderr.length === 0) return "";
2442
- // FIXED: Using 'testIndex'
2443
- const logId = `stderr-log-${test.id || testIndex}`;
2444
- return `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: var(--bg-tertiary); color: #f87171; padding: 1.25em; border-radius: 0.85em; line-height: 1.2; border: 1px solid var(--border-light);">${formatPlaywrightError(
2445
- test.stderr.map((line) => sanitizeHTML(line)).join("\n"),
2446
- )}</pre></div>`;
2447
- })()}
2448
-
2803
+ <div class="steps-list">${generateStepsHTML(testData.steps)}</div>
2449
2804
  ${(() => {
2450
- if (!test.screenshots || test.screenshots.length === 0) return "";
2451
- const screenshotsHTML = test.screenshots
2452
- .map((screenshotPath, sIndex) => {
2453
- try {
2454
- const imagePath = path.resolve(
2455
- DEFAULT_OUTPUT_DIR,
2456
- screenshotPath,
2457
- );
2458
- if (!fsExistsSync(imagePath))
2459
- return `<div class="attachment-item error">Screenshot not found</div>`;
2460
- const base64ImageData =
2461
- readFileSync(imagePath).toString("base64");
2462
- // LAZY LOAD: Using helper with unique ID
2463
- return createLazyMedia(
2464
- base64ImageData,
2465
- "image/png",
2466
- "image",
2467
- sIndex + 1,
2468
- `screenshot-${testIndex}-${sIndex}.png`,
2469
- );
2470
- } catch (e) {
2471
- return `<div class="attachment-item error">Error loading screenshot</div>`;
2472
- }
2473
- })
2474
- .join("");
2475
- return `<div class="attachments-section"><h4>Screenshots</h4><div class="attachments-grid">${screenshotsHTML}</div></div>`;
2476
- })()}
2477
-
2478
- ${(() => {
2479
- if (!test.videoPath || test.videoPath.length === 0) return "";
2480
- const videosHTML = test.videoPath
2481
- .map((videoPath, vIndex) => {
2482
- try {
2483
- const videoFilePath = path.resolve(
2484
- DEFAULT_OUTPUT_DIR,
2485
- videoPath,
2486
- );
2487
- if (!fsExistsSync(videoFilePath))
2488
- return `<div class="attachment-item error">Video not found</div>`;
2489
- const videoBase64 =
2490
- readFileSync(videoFilePath).toString("base64");
2491
- const ext = path.extname(videoPath).slice(1).toLowerCase();
2492
- const mime =
2493
- { mp4: "video/mp4", webm: "video/webm" }[ext] ||
2494
- "video/mp4";
2495
- // LAZY LOAD: Using helper with unique ID
2496
- return createLazyMedia(
2497
- videoBase64,
2498
- mime,
2499
- "video",
2500
- vIndex + 1,
2501
- `video-${testIndex}-${vIndex}.${ext}`,
2502
- );
2503
- } catch (e) {
2504
- return `<div class="attachment-item error">Error loading video</div>`;
2505
- }
2506
- })
2507
- .join("");
2508
- return `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${videosHTML}</div></div>`;
2805
+ if (!testData.stdout || testData.stdout.length === 0) return "";
2806
+ return `<div class="console-output-section">
2807
+ <h4>Console Output (stdout)
2808
+ <button class="copy-btn" onclick="copyLogContent('${logId}', this)">Copy</ button>
2809
+ </h4>
2810
+ <div class="log-wrapper">
2811
+ <pre id="${logId}" class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
2812
+ testData.stdout
2813
+ .map((line) => sanitizeHTML(line))
2814
+ .join("\n"),
2815
+ )}</pre>
2816
+ </div>
2817
+ </div>`;
2509
2818
  })()}
2510
-
2511
2819
  ${
2512
- test.tracePath
2513
- ? `<div class="attachments-section"><h4>Trace Files</h4><div class="attachments-grid"><div class="attachment-item trace-item"><div class="trace-preview"><span class="trace-icon">📄</span><span class="trace-name">${sanitizeHTML(
2514
- path.basename(test.tracePath),
2515
- )}</span></div><div class="attachment-info"><div class="trace-actions"><a href="${sanitizeHTML(
2516
- test.tracePath,
2517
- )}" target="_blank" download="${sanitizeHTML(
2518
- path.basename(test.tracePath),
2519
- )}" class="download-trace">Download Trace</a></div></div></div></div></div>`
2820
+ testData.stderr && testData.stderr.length > 0
2821
+ ? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
2822
+ testData.stderr.map((line) => sanitizeHTML(line)).join("\n"),
2823
+ )}</pre></div>`
2520
2824
  : ""
2521
2825
  }
2522
2826
  ${
2523
- test.attachments && test.attachments.length > 0
2524
- ? `<div class="attachments-section"><h4>Other Attachments</h4><div class="attachments-grid">${test.attachments
2827
+ testData.screenshots && testData.screenshots.length > 0
2828
+ ? `
2829
+ <div class="attachments-section">
2830
+ <h4>Screenshots</h4>
2831
+ <div class="attachments-grid">
2832
+ ${testData.screenshots
2525
2833
  .map(
2526
- (attachment) =>
2527
- `<div class="attachment-item generic-attachment"><div class="attachment-icon">${getAttachmentIcon(
2528
- attachment.contentType,
2529
- )}</div><div class="attachment-caption"><span class="attachment-name" title="${sanitizeHTML(
2530
- attachment.name,
2531
- )}">${sanitizeHTML(
2532
- attachment.name,
2533
- )}</span><span class="attachment-type">${sanitizeHTML(
2534
- attachment.contentType,
2535
- )}</span></div><div class="attachment-info"><div class="trace-actions"><a href="${sanitizeHTML(
2536
- attachment.path,
2537
- )}" target="_blank" class="view-full">View</a><a href="${sanitizeHTML(
2538
- attachment.path,
2539
- )}" target="_blank" download="${sanitizeHTML(
2540
- attachment.name,
2541
- )}" class="download-trace">Download</a></div></div></div>`,
2834
+ (screenshot, screenshotIndex) => `
2835
+ <div class="attachment-item">
2836
+ <img src="${sanitizeHTML(screenshot)}" alt="Screenshot ${
2837
+ screenshotIndex + 1
2838
+ }">
2839
+ <div class="attachment-info">
2840
+ <div class="trace-actions">
2841
+ <a href="${sanitizeHTML(
2842
+ screenshot,
2843
+ )}" target="_blank" class="view-full">View Full Image</a>
2844
+ <a href="${sanitizeHTML(
2845
+ screenshot,
2846
+ )}" target="_blank" download="screenshot-${Date.now()}-${screenshotIndex}.png">Download</a>
2847
+ </div>
2848
+ </div>
2849
+ </div>
2850
+ `,
2542
2851
  )
2852
+ .join("")}
2853
+ </div>
2854
+ </div>
2855
+ `
2856
+ : ""
2857
+ }
2858
+ ${
2859
+ testData.videoPath && testData.videoPath.length > 0
2860
+ ? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${testData.videoPath
2861
+ .map((videoUrl, videoIndex) => {
2862
+ const fixedVideoUrl = sanitizeHTML(videoUrl);
2863
+ const fileExtension = String(fixedVideoUrl)
2864
+ .split(".")
2865
+ .pop()
2866
+ .toLowerCase();
2867
+ const mimeType =
2868
+ {
2869
+ mp4: "video/mp4",
2870
+ webm: "video/webm",
2871
+ ogg: "video/ogg",
2872
+ mov: "video/quicktime",
2873
+ avi: "video/x-msvideo",
2874
+ }[fileExtension] || "video/mp4";
2875
+ return `<div class="attachment-item video-item">
2876
+ <video controls width="100%" height="auto" title="Video ${
2877
+ videoIndex + 1
2878
+ }">
2879
+ <source src="${sanitizeHTML(
2880
+ fixedVideoUrl,
2881
+ )}" type="${mimeType}">
2882
+ Your browser does not support the video tag.
2883
+ </video>
2884
+ <div class="attachment-info">
2885
+ <div class="trace-actions">
2886
+ <a href="${sanitizeHTML(
2887
+ fixedVideoUrl,
2888
+ )}" target="_blank" download="video-${Date.now()}-${videoIndex}.${fileExtension}">Download</a>
2889
+ </div>
2890
+ </div>
2891
+ </div>`;
2892
+ })
2543
2893
  .join("")}</div></div>`
2544
2894
  : ""
2545
2895
  }
2546
2896
  ${
2547
- test.codeSnippet
2548
- ? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
2549
- sanitizeHTML(test.codeSnippet),
2550
- )}</code></pre></div>`
2897
+ testData.tracePath
2898
+ ? `
2899
+ <div class="attachments-section">
2900
+ <h4>Trace Files</h4>
2901
+ <div class="attachments-grid">
2902
+ <div class="attachment-item trace-item">
2903
+ <div class="trace-preview">
2904
+ <span class="trace-icon">📄</span>
2905
+ <span class="trace-name">${sanitizeHTML(
2906
+ path.basename(testData.tracePath),
2907
+ )}</span>
2908
+ </div>
2909
+ <div class="attachment-info">
2910
+ <div class="trace-actions">
2911
+ <a href="${sanitizeHTML(
2912
+ sanitizeHTML(testData.tracePath),
2913
+ )}" target="_blank" download="${sanitizeHTML(
2914
+ path.basename(testData.tracePath),
2915
+ )}" class="download-trace">Download Trace</a>
2916
+ </div>
2917
+ </div>
2918
+ </div>
2919
+ </div>
2920
+ </div>
2921
+ `
2922
+ : ""
2923
+ }
2924
+ ${
2925
+ testData.attachments && testData.attachments.length > 0
2926
+ ? `
2927
+ <div class="attachments-section">
2928
+ <h4>Other Attachments</h4>
2929
+ <div class="attachments-grid">
2930
+ ${testData.attachments
2931
+ .map(
2932
+ (attachment) => `
2933
+ <div class="attachment-item generic-attachment">
2934
+ <div class="attachment-icon">${getAttachmentIcon(
2935
+ attachment.contentType,
2936
+ )}</div>
2937
+ <div class="attachment-caption">
2938
+ <span class="attachment-name" title="${sanitizeHTML(
2939
+ attachment.name,
2940
+ )}">${sanitizeHTML(attachment.name)}</span>
2941
+ <span class="attachment-type">${sanitizeHTML(
2942
+ attachment.contentType,
2943
+ )}</span>
2944
+ </div>
2945
+ <div class="attachment-info">
2946
+ <div class="trace-actions">
2947
+ <a href="${sanitizeHTML(
2948
+ sanitizeHTML(attachment.path),
2949
+ )}" target="_blank" class="view-full">View</a>
2950
+ <a href="${sanitizeHTML(
2951
+ sanitizeHTML(attachment.path),
2952
+ )}" target="_blank" download="${sanitizeHTML(
2953
+ attachment.name,
2954
+ )}" class="download-trace">Download</a>
2955
+ </div>
2956
+ </div>
2957
+ </div>
2958
+ `,
2959
+ )
2960
+ .join("")}
2961
+ </div>
2962
+ </div>
2963
+ `
2551
2964
  : ""
2552
2965
  }
2966
+ `;
2967
+ };
2968
+
2969
+
2970
+ // Determine header status: use final_status if retried, else normal status
2971
+ const headerStatus = (test.retryHistory && test.retryHistory.length > 0 && test.final_status)
2972
+ ? test.final_status
2973
+ : test.status;
2974
+
2975
+ const outcomeBadge = (test.outcome && test.outcome !== 'flaky')
2976
+ ? `<span class="outcome-badge ${test.outcome}">${test.outcome}</span>`
2977
+ : '';
2978
+
2979
+ return `
2980
+ <div class="test-case" data-status="${
2981
+ headerStatus
2982
+ }" data-browser="${sanitizeHTML(browser)}" data-tags="${(test.tags || [])
2983
+ .join(",")
2984
+ .toLowerCase()}">
2985
+ <div class="test-case-header" role="button" aria-expanded="false">
2986
+ <div class="test-case-summary">
2987
+ <span class="test-case-title" title="${sanitizeHTML(
2988
+ test.name,
2989
+ )}">${sanitizeHTML(testTitle)}</span>
2990
+ <span class="test-case-browser">(${sanitizeHTML(browser)})</span>
2991
+ </div>
2992
+ <div class="test-case-meta">
2993
+ ${severityBadge}
2994
+ ${retryBadge}
2995
+ ${outcomeBadge}
2996
+ ${
2997
+ test.tags && test.tags.length > 0
2998
+ ? test.tags
2999
+ .map((t) => `<span class="tag">${sanitizeHTML(t)}</span>`)
3000
+ .join(" ")
3001
+ : ""
3002
+ }
3003
+ </div>
3004
+ <div class="test-case-status-duration">
3005
+ <span class="status-badge ${getStatusClass(headerStatus)}">${String(
3006
+ headerStatus,
3007
+ ).toUpperCase()}</span>
3008
+ <span class="test-duration">${formatDuration(test.duration)}</span>
3009
+ </div>
3010
+ </div>
3011
+ <div class="test-case-content" style="display: none;">
3012
+ ${test.retryHistory && test.retryHistory.length > 0 ? `
3013
+ <div class="retry-tabs-container">
3014
+ <div class="retry-tabs-header">
3015
+ <button class="retry-tab active" onclick="switchRetryTab(event, 'base-run-${test.id}')">
3016
+ Base Run ${getSmallStatusBadge(test.final_status || test.status)}
3017
+ </button>
3018
+ ${test.retryHistory.map((retry, idx) => `
3019
+ <button class="retry-tab" onclick="switchRetryTab(event, 'retry-${idx + 1}-${test.id}')">
3020
+ Retry ${idx + 1} ${getSmallStatusBadge(retry.final_status || retry.status)}
3021
+ </button>
3022
+ `).join('')}
3023
+ </div>
3024
+
3025
+ <div id="base-run-${test.id}" class="retry-tab-content active">
3026
+ ${getTestContentHTML(test, 'base')}
3027
+ </div>
3028
+
3029
+ ${test.retryHistory.map((retry, idx) => `
3030
+ <div id="retry-${idx + 1}-${test.id}" class="retry-tab-content" style="display: none;">
3031
+ ${getTestContentHTML(retry, `retry-${idx + 1}`)}
3032
+ </div>
3033
+ `).join('')}
3034
+ </div>
3035
+ ` : getTestContentHTML(test, 'single')}
2553
3036
  </div>
2554
3037
  </div>`;
2555
3038
  })
@@ -2561,18 +3044,18 @@ function generateHTML(reportData, trendData = null) {
2561
3044
  <head>
2562
3045
  <meta charset="UTF-8">
2563
3046
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2564
- <link rel="icon" type="image/png" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
2565
- <link rel="apple-touch-icon" href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png">
3047
+ <link rel="icon" type="image/png" href=${logo}>
3048
+ <link rel="apple-touch-icon" href=${logo}>
2566
3049
  <!-- Preconnect to external domains -->
2567
3050
  <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
2568
3051
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
2569
- <link rel="preconnect" href="https://code.highcharts.com">
2570
3052
 
2571
3053
  <!-- Preload critical font -->
3054
+
2572
3055
  <link rel="preload" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
2573
3056
  <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap"></noscript>
2574
3057
 
2575
- <script src="https://code.highcharts.com/highcharts.js" defer></script>
3058
+ ${highchartsContent ? `<script>${highchartsContent}</script>` : '<script src="https://code.highcharts.com/highcharts.js" defer></script>'}
2576
3059
  <title>Pulse Static Report</title>
2577
3060
 
2578
3061
  <style>
@@ -2583,7 +3066,8 @@ function generateHTML(reportData, trendData = null) {
2583
3066
  --success-color: #34d399; --success-dark: #10b981; --success-light: #6ee7b7;
2584
3067
  --danger-color: #f87171; --danger-dark: #ef4444; --danger-light: #fca5a5;
2585
3068
  --warning-color: #fbbf24; --warning-dark: #f59e0b; --warning-light: #fcd34d;
2586
- --info-color: #9ca3af;
3069
+ --info-color: #9ca3af;
3070
+ --flaky-color: #00ccd3;
2587
3071
  --text-primary: #f9fafb; --text-secondary: #e5e7eb; --text-tertiary: #d1d5db;
2588
3072
  --bg-primary: #000000; --bg-secondary: #0a0a0a; --bg-tertiary: #050505;
2589
3073
  --bg-card: #0d0d0d; --bg-card-hover: #121212;
@@ -2591,6 +3075,8 @@ function generateHTML(reportData, trendData = null) {
2591
3075
  --light-gray-color: #262626; --medium-gray-color: #333333; --dark-gray-color: #a3a3a3;
2592
3076
  --text-color: #f9fafb; --text-color-secondary: #e5e7eb; --border-color: #262626;
2593
3077
  --card-background-color: #0d0d0d;
3078
+ --neutral-100: #171717; --neutral-200: #262626; --neutral-300: #404040;
3079
+ --bg-hover: #171717;
2594
3080
  --font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
2595
3081
  --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --radius-2xl: 24px;
2596
3082
  --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.8);
@@ -2682,7 +3168,6 @@ function generateHTML(reportData, trendData = null) {
2682
3168
  background: var(--gradient-card);
2683
3169
  border-radius: 14px;
2684
3170
  box-shadow: var(--shadow-md);
2685
- border: 1px solid var(--border-light);
2686
3171
  overflow: hidden;
2687
3172
  }
2688
3173
  .run-info-item {
@@ -2856,8 +3341,17 @@ function generateHTML(reportData, trendData = null) {
2856
3341
  color: #f9fafb;
2857
3342
  text-transform: capitalize;
2858
3343
  font-size: 1.05em;
3344
+ white-space: nowrap;
3345
+ overflow: hidden;
3346
+ text-overflow: ellipsis;
3347
+ flex: 1;
3348
+ min-width: 0;
3349
+ margin-right: 8px;
2859
3350
  }
2860
3351
  .browser-stats {
3352
+ color: #9ca3af;
3353
+ white-space: nowrap;
3354
+ flex-shrink: 0;
2861
3355
  color: #9ca3af;
2862
3356
  font-weight: 700;
2863
3357
  font-size: 0.95em;
@@ -2893,57 +3387,7 @@ function generateHTML(reportData, trendData = null) {
2893
3387
  padding: 10px !important;
2894
3388
  border-radius: 6px !important;
2895
3389
  }
2896
- .env-dashboard {
2897
- background: var(--gradient-card);
2898
- border: 1px solid var(--border-light);
2899
- border-radius: 12px;
2900
- padding: 24px;
2901
- margin-top: 20px;
2902
- box-shadow: var(--shadow-md);
2903
- }
2904
- .env-dashboard-title {
2905
- font-size: 1.2em;
2906
- font-weight: 700;
2907
- color: #f9fafb;
2908
- margin-bottom: 8px;
2909
- }
2910
- .env-dashboard-subtitle {
2911
- font-size: 0.9em;
2912
- color: #9ca3af;
2913
- margin-bottom: 16px;
2914
- }
2915
- .env-grid {
2916
- display: grid;
2917
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
2918
- gap: 12px;
2919
- }
2920
- .env-card {
2921
- background: var(--bg-secondary);
2922
- border: 1px solid var(--border-light);
2923
- border-radius: 8px;
2924
- padding: 14px;
2925
- transition: all 0.3s ease;
2926
- }
2927
- .env-card:hover {
2928
- transform: translateY(-3px);
2929
- border-color: var(--primary-color);
2930
- box-shadow: var(--shadow-lg), var(--glow-primary);
2931
- background: var(--bg-card);
2932
- }
2933
- .env-card-header {
2934
- font-size: 0.85em;
2935
- color: #9ca3af;
2936
- margin-bottom: 6px;
2937
- text-transform: uppercase;
2938
- letter-spacing: 0.5px;
2939
- font-weight: 600;
2940
- }
2941
- .env-card-value {
2942
- font-size: 1.1em;
2943
- color: #f9fafb;
2944
- font-weight: 700;
2945
- word-break: break-word;
2946
- }
3390
+
2947
3391
  .suites-widget {
2948
3392
  background: var(--bg-card);
2949
3393
  border: 1px solid var(--border-light);
@@ -2961,17 +3405,9 @@ function generateHTML(reportData, trendData = null) {
2961
3405
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
2962
3406
  gap: 20px;
2963
3407
  }
3408
+ /* Updated Suite Cards in Main Block */
2964
3409
  .suite-card {
2965
- background: var(--bg-secondary);
2966
- border: 1px solid var(--border-light);
2967
- border-radius: 8px;
2968
- padding: 20px;
2969
- transition: all 0.2s ease;
2970
- }
2971
- .suite-card:hover {
2972
- transform: translateY(-2px);
2973
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
2974
- border-color: var(--primary-color);
3410
+ /* See line ~3455 for main definition */
2975
3411
  }
2976
3412
  .suite-name {
2977
3413
  font-size: 1.1em;
@@ -3084,6 +3520,16 @@ function generateHTML(reportData, trendData = null) {
3084
3520
  .summary-card.status-skipped .value {
3085
3521
  color: #f59e0b;
3086
3522
  }
3523
+ .summary-card.flaky-status {
3524
+ background: rgba(0, 204, 211, 0.05);
3525
+ }
3526
+ .summary-card.flaky-status:hover {
3527
+ background: rgba(0, 204, 211, 0.15);
3528
+ box-shadow: 0 4px 12px rgba(0, 204, 211, 0.2);
3529
+ }
3530
+ .summary-card.flaky-status .value {
3531
+ color: #00ccd3;
3532
+ }
3087
3533
  .summary-card:not([class*='status-']) .value {
3088
3534
  color: #f9fafb;
3089
3535
  }
@@ -3146,70 +3592,171 @@ function generateHTML(reportData, trendData = null) {
3146
3592
  .status-badge-small-tooltip.status-unknown {
3147
3593
  background-color: #9ca3af;
3148
3594
  }
3149
- .suites-header {
3150
- display: flex;
3151
- justify-content: space-between;
3152
- align-items: center;
3153
- margin-bottom: 20px;
3154
- }
3155
- .summary-badge {
3156
- background-color: var(--border-light);
3157
- color: var(--text-secondary);
3158
- padding: 7px 14px;
3159
- border-radius: 16px;
3160
- font-size: 0.9em;
3595
+ .suites-header {
3596
+ flex-shrink: 0;
3597
+ display: flex;
3598
+ justify-content: space-between;
3599
+ align-items: center;
3600
+ margin-bottom: 20px;
3161
3601
  }
3162
- .suites-grid {
3163
- display: grid;
3164
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
3165
- gap: 20px;
3602
+ .summary-badge { background-color: var(--border-light); color: var(--text-secondary); padding: 7px 14px; border-radius: 16px; font-size: 0.9em; }
3603
+ .suites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
3604
+ .suites-widget {
3605
+ display: flex;
3606
+ flex-direction: column;
3166
3607
  }
3167
- .suite-card {
3168
- border: none;
3169
- border-left: 4px solid var(--border-light);
3170
- padding: 24px;
3171
- background-color: var(--bg-card);
3172
- transition: all 0.15s ease;
3608
+ .fixed-height-widget {
3609
+ height: 450px;
3173
3610
  }
3174
- .suite-card:hover {
3175
- background: rgba(255, 255, 255, 0.03);
3176
- border-left-color: var(--border-medium);
3611
+ .suites-grid-container {
3612
+ flex-grow: 1;
3613
+ overflow-y: auto;
3614
+ padding-right: 5px;
3177
3615
  }
3178
- .suite-card.status-passed {
3179
- border-left-color: #10b981;
3616
+
3617
+ @media (max-width: 768px) {
3618
+ .fixed-height-widget {
3619
+ height: auto;
3620
+ max-height: 600px;
3621
+ }
3180
3622
  }
3181
- .suite-card.status-passed:hover {
3182
- background: rgba(16, 185, 129, 0.05);
3623
+ .suite-card {
3624
+ background: var(--bg-card); /* Changed from #ffffff */
3625
+ border: 1px solid var(--border-medium); /* Changed from border-light for better contrast */
3626
+ border-radius: 16px;
3627
+ padding: 24px;
3628
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2); /* Darker shadow */
3629
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
3630
+ display: flex;
3631
+ flex-direction: column;
3632
+ height: 100%;
3633
+ position: relative;
3634
+ overflow: hidden;
3183
3635
  }
3184
- .suite-card.status-failed {
3185
- border-left-color: #ef4444;
3636
+ .suite-card:hover {
3637
+ transform: translateY(-4px);
3638
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4);
3639
+ background: var(--bg-card-hover);
3640
+ border-color: var(--primary-dark);
3186
3641
  }
3187
- .suite-card.status-failed:hover {
3188
- background: rgba(239, 68, 68, 0.05);
3642
+ .suite-card::before {
3643
+ content: '';
3644
+ position: absolute;
3645
+ top: 0;
3646
+ left: 0;
3647
+ width: 100%;
3648
+ height: 4px;
3649
+ background: var(--neutral-200);
3650
+ opacity: 0.8;
3651
+ transition: background 0.3s ease;
3652
+ }
3653
+ .suite-card.status-passed::before { background: var(--success-color); }
3654
+ .suite-card.status-failed::before { background: var(--danger-color); }
3655
+ .suite-card.status-flaky::before { background: #00ccd3; }
3656
+ .suite-card.status-skipped::before { background: var(--warning-color); }
3657
+
3658
+ .suite-card.status-skipped::before { background: var(--warning-color); }
3659
+
3660
+ /* Outcome Badge */
3661
+ .outcome-badge {
3662
+ background-color: var(--secondary-color);
3663
+ color: #000;
3664
+ padding: 2px 8px;
3665
+ border-radius: 4px;
3666
+ font-size: 0.75em;
3667
+ font-weight: 700;
3668
+ text-transform: uppercase;
3669
+ margin-right: 8px;
3670
+ letter-spacing: 0.5px;
3189
3671
  }
3190
- .suite-card.status-skipped {
3191
- border-left-color: #f59e0b;
3672
+ .outcome-badge.flaky {
3673
+ background-color: #00ccd3;
3674
+ color: #fff;
3192
3675
  }
3193
- .suite-card.status-skipped:hover {
3194
- background: rgba(245, 158, 11, 0.05);
3676
+
3677
+ .suite-card-header {
3678
+ display: flex;
3679
+ justify-content: space-between;
3680
+ align-items: flex-start;
3681
+ margin-bottom: 16px;
3195
3682
  }
3196
- .suite-card-header {
3197
- display: flex;
3198
- justify-content: space-between;
3199
- align-items: flex-start;
3200
- margin-bottom: 12px;
3683
+ .suite-name {
3684
+ font-size: 1.15em;
3685
+ font-weight: 700;
3686
+ color: var(--text-primary);
3687
+ line-height: 1.4;
3688
+ display: -webkit-box;
3689
+ -webkit-line-clamp: 2;
3690
+ -webkit-box-orient: vertical;
3691
+ overflow: hidden;
3692
+ margin-right: 12px;
3201
3693
  }
3202
- .suite-name {
3203
- font-weight: 600;
3204
- font-size: 1.05em;
3205
- color: #f9fafb;
3206
- margin-right: 10px;
3207
- word-break: break-word;
3694
+ .status-indicator-dot {
3695
+ width: 10px;
3696
+ height: 10px;
3697
+ border-radius: 50%;
3698
+ flex-shrink: 0;
3699
+ margin-top: 6px;
3208
3700
  }
3701
+ .status-indicator-dot.status-passed { background-color: var(--success-color); box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
3702
+ .status-indicator-dot.status-failed { background-color: var(--danger-color); box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.15); }
3703
+ .status-indicator-dot.status-skipped { background-color: var(--warning-color); box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.15); }
3704
+
3209
3705
  .browser-tag {
3706
+ font-size: 0.8em;
3707
+ font-weight: 600;
3708
+ background: var(--bg-secondary);
3709
+ color: var(--text-secondary);
3710
+ padding: 4px 10px;
3711
+ border-radius: 20px;
3712
+ border: 1px solid var(--border-light);
3713
+ display: inline-flex;
3714
+ align-items: center;
3715
+ gap: 6px;
3716
+ margin-bottom: 20px;
3717
+ align-self: flex-start;
3718
+ box-shadow: none;
3719
+ text-shadow: none;
3720
+ }
3721
+ .browser-tag:hover {
3722
+ /* Remove hover effect from previous */
3723
+ }
3724
+
3725
+ .suite-card-body {
3726
+ margin-top: auto;
3727
+ }
3728
+
3729
+ .test-count-label {
3210
3730
  font-size: 0.85em;
3211
3731
  font-weight: 600;
3212
- background: linear-gradient(135deg, rgba(96, 165, 250, 0.2) 0%, rgba(59, 130, 246, 0.15) 100%);
3732
+ color: var(--text-tertiary);
3733
+ text-transform: uppercase;
3734
+ letter-spacing: 0.05em;
3735
+ margin-bottom: 8px;
3736
+ display: block;
3737
+ }
3738
+
3739
+ .suite-stats {
3740
+ display: flex;
3741
+ gap: 8px;
3742
+ background: var(--bg-secondary);
3743
+ padding: 10px 14px;
3744
+ border-radius: 10px;
3745
+ justify-content: space-between;
3746
+ }
3747
+
3748
+ .stat-pill {
3749
+ display: flex;
3750
+ align-items: center;
3751
+ gap: 6px;
3752
+ font-size: 0.9em;
3753
+ font-weight: 600;
3754
+ }
3755
+ .stat-pill svg { width: 14px; height: 14px; }
3756
+ .stat-pill.passed { color: var(--success-dark); }
3757
+ .stat-pill.failed { color: var(--danger-dark); }
3758
+ .stat-pill.flaky { color: #00ccd3; }
3759
+ .stat-pill.skipped { color: var(--warning-dark); }
3213
3760
  color: #93c5fd;
3214
3761
  padding: 6px 12px;
3215
3762
  border-radius: var(--radius-sm);
@@ -3403,6 +3950,10 @@ function generateHTML(reportData, trendData = null) {
3403
3950
  .status-badge.status-skipped {
3404
3951
  background: var(--warning-color);
3405
3952
  }
3953
+ .status-badge.status-flaky {
3954
+ background-color: #00ccd3;
3955
+ color: #fff;
3956
+ }
3406
3957
  .status-badge.status-unknown {
3407
3958
  background: var(--dark-gray-color);
3408
3959
  }
@@ -3458,6 +4009,65 @@ function generateHTML(reportData, trendData = null) {
3458
4009
  border-color: rgba(148, 163, 184, 0.25);
3459
4010
  }
3460
4011
 
4012
+ /* --- RETRY COUNT BADGE --- */
4013
+ .retry-badge {
4014
+ display: inline-flex;
4015
+ align-items: center;
4016
+ padding: 5px 12px;
4017
+ border-radius: 12px;
4018
+ font-size: 0.75rem;
4019
+ font-weight: 600;
4020
+ background: rgba(147, 51, 234, 0.15);
4021
+ color: #a855f7;
4022
+ border: 1px solid rgba(147, 51, 234, 0.3);
4023
+ margin-left: 8px;
4024
+ }
4025
+
4026
+ /* --- RETRY TABS --- */
4027
+ .retry-tabs-container {
4028
+ margin-top: 16px;
4029
+ }
4030
+
4031
+ .retry-tabs-header {
4032
+ display: flex;
4033
+ gap: 8px;
4034
+ border-bottom: 2px solid var(--border-medium);
4035
+ margin-bottom: 20px;
4036
+ flex-wrap: wrap;
4037
+ }
4038
+
4039
+ .retry-tab {
4040
+ padding: 10px 20px;
4041
+ background: transparent;
4042
+ border: none;
4043
+ border-bottom: 3px solid transparent;
4044
+ cursor: pointer;
4045
+ font-size: 0.95rem;
4046
+ font-weight: 600;
4047
+ color: var(--text-color-secondary);
4048
+ transition: all 0.2s ease;
4049
+ }
4050
+
4051
+ .retry-tab:hover {
4052
+ color: var(--primary-color);
4053
+ background: rgba(147, 51, 234, 0.05);
4054
+ }
4055
+
4056
+ .retry-tab.active {
4057
+ color: #a855f7;
4058
+ border-bottom-color: #a855f7;
4059
+ background: rgba(147, 51, 234, 0.1);
4060
+ }
4061
+
4062
+ .retry-tab-content {
4063
+ animation: fadeIn 0.3s ease-in;
4064
+ }
4065
+
4066
+ @keyframes fadeIn {
4067
+ from { opacity: 0; }
4068
+ to { opacity: 1; }
4069
+ }
4070
+
3461
4071
  .tag {
3462
4072
  display: inline-flex;
3463
4073
  align-items: center;
@@ -3507,7 +4117,9 @@ function generateHTML(reportData, trendData = null) {
3507
4117
  background-color: rgba(248,113,113,0.08);
3508
4118
  border: 1px solid rgba(248,113,113,0.25);
3509
4119
  border-left: 4px solid var(--danger-color);
3510
- border-radius: 4px;
4120
+ border-radius: 4px;
4121
+ display: flex;
4122
+ flex-direction: column;
3511
4123
  }
3512
4124
  .test-error-summary h4 {
3513
4125
  color: #ef4444;
@@ -3758,6 +4370,40 @@ function generateHTML(reportData, trendData = null) {
3758
4370
  font-size: 0.9em;
3759
4371
  border: 1px solid var(--border-light);
3760
4372
  }
4373
+ .failed-step-highlight {
4374
+ border-left: 4px solid var(--danger-color) !important;
4375
+ background-color: rgba(244,67,54,0.03);
4376
+ }
4377
+ .failed-step-highlight .step-header {
4378
+ background-color: rgba(244,67,54,0.05);
4379
+ border-color: rgba(244,67,54,0.3);
4380
+ }
4381
+ .failed-step-marker {
4382
+ display: inline-block;
4383
+ margin-left: 10px;
4384
+ padding: 2px 8px;
4385
+ background-color: var(--danger-color);
4386
+ color: white;
4387
+ border-radius: 4px;
4388
+ font-size: 0.85em;
4389
+ font-weight: 600;
4390
+ }
4391
+ .code-snippet-section {
4392
+ margin: 12px 0;
4393
+ }
4394
+ .code-snippet {
4395
+ background-color: #f8f9fa;
4396
+ border: 1px solid #e1e4e8;
4397
+ border-radius: 6px;
4398
+ padding: 12px;
4399
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
4400
+ font-size: 0.9em;
4401
+ line-height: 1.5;
4402
+ overflow-x: auto;
4403
+ color: #24292e;
4404
+ margin: 0;
4405
+ white-space: pre;
4406
+ }
3761
4407
  .nested-steps {
3762
4408
  margin-top: 12px;
3763
4409
  }
@@ -3941,6 +4587,10 @@ function generateHTML(reportData, trendData = null) {
3941
4587
  .status-badge-small.status-skipped {
3942
4588
  background-color: #f59e0b;
3943
4589
  }
4590
+ .status-badge-small.status-flaky {
4591
+ background-color: #00ccd3;
4592
+ color: #fff;
4593
+ }
3944
4594
  .status-badge-small.status-unknown {
3945
4595
  background-color: var(--dark-gray-color);
3946
4596
  }
@@ -5166,8 +5816,17 @@ function generateHTML(reportData, trendData = null) {
5166
5816
  color: #f9fafb;
5167
5817
  text-transform: capitalize;
5168
5818
  font-size: 1.05em;
5819
+ white-space: nowrap;
5820
+ overflow: hidden;
5821
+ text-overflow: ellipsis;
5822
+ flex: 1;
5823
+ min-width: 0;
5824
+ margin-right: 8px;
5169
5825
  }
5170
5826
  .browser-stats {
5827
+ color: #9ca3af;
5828
+ white-space: nowrap;
5829
+ flex-shrink: 0;
5171
5830
  color: #9ca3af;
5172
5831
  font-weight: 700;
5173
5832
  font-size: 0.95em;
@@ -5598,7 +6257,7 @@ function generateHTML(reportData, trendData = null) {
5598
6257
  <div class="container">
5599
6258
  <header class="header">
5600
6259
  <div class="header-title">
5601
- <img id="report-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo">
6260
+ <img id="report-logo" src=${logo} alt="Report Logo">
5602
6261
  <h1>Pulse Static Report</h1>
5603
6262
  </div>
5604
6263
  <div class="run-info">
@@ -5612,6 +6271,17 @@ function generateHTML(reportData, trendData = null) {
5612
6271
  </div>
5613
6272
  </div>
5614
6273
  </header>
6274
+ ${
6275
+ reportData.metadata?.reportDescription
6276
+ ? `<div class="report-description" title="${sanitizeHTML(reportData.metadata.reportDescription)}" style="margin: 0 0 24px 0; padding: 18px 24px; background-color: var(--bg-card, var(--card-bg, #ffffff)); border: 1px solid var(--border-color, var(--border-medium, #e5e7eb)); border-left: 4px solid #764ba2; border-radius: 8px; display: flex; align-items: flex-start; gap: 16px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);">
6277
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#764ba2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 1px;"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
6278
+ <div style="flex: 1; min-width: 0;">
6279
+ <h4 style="margin: 0 0 6px 0; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.5px; color: #764ba2; font-weight: 700;">Report Description</h4>
6280
+ <p style="margin: 0; font-size: 0.95em; color: var(--text-color, #1f2937); line-height: 1.6; font-weight: 400; overflow-wrap: break-word;">${sanitizeHTML(reportData.metadata.reportDescription.length > 130 ? reportData.metadata.reportDescription.substring(0, 130) + "..." : reportData.metadata.reportDescription)}</p>
6281
+ </div>
6282
+ </div>`
6283
+ : ""
6284
+ }
5615
6285
  <div class="tabs">
5616
6286
  <button class="tab-button active" data-tab="dashboard">Dashboard</button>
5617
6287
  <button class="tab-button" data-tab="test-runs">Test Run Summary</button>
@@ -5632,31 +6302,33 @@ function generateHTML(reportData, trendData = null) {
5632
6302
  <div class="summary-card status-skipped"><h3>Skipped</h3><div class="value">${
5633
6303
  runSummary.skipped || 0
5634
6304
  }</div><div class="trend-percentage">${skipPercentage}%</div></div>
5635
- <div class="summary-card"><h3>Avg. Test Time</h3><div class="value">${avgTestDuration}</div></div>
6305
+ <div class="summary-card flaky-status"><h3>Flaky</h3><div class="value">${runSummary.flaky || 0}</div>
6306
+ <div class="trend-percentage">${flakyPercentage}%</div></div>
5636
6307
  <div class="summary-card"><h3>Run Duration</h3><div class="value">${formatDuration(
5637
6308
  runSummary.duration,
5638
- )}</div></div>
6309
+ )}</div><div class="trend-percentage">Avg. Test Duration ${avgTestDuration}</div></div>
5639
6310
  <div class="summary-card">
5640
- <h3>🔄 Retry Count</h3>
6311
+ <h3>Total Retry Count</h3>
5641
6312
  <div class="value">${totalRetried}</div>
6313
+ <div class="trend-percentage">Test Retried ${retriedTestsCount}</div>
5642
6314
  </div>
5643
6315
  <div class="summary-card">
5644
6316
  <h3>🌐 Browser Distribution <span style="font-size: 0.7em; color: var(--text-color-secondary); font-weight: 400;">(${browserBreakdown.length} total)</span></h3>
5645
6317
  <div class="browser-breakdown" style="max-height: 200px; overflow-y: auto; padding-right: 4px;">
5646
6318
  ${browserBreakdown
5647
- .slice(0, 5)
6319
+ .slice(0, 3)
5648
6320
  .map(
5649
6321
  (b) =>
5650
6322
  `<div class="browser-item">
5651
- <span class="browser-name">${sanitizeHTML(b.browser)}</span>
6323
+ <span class="browser-name" title="${sanitizeHTML(b.browser)}">${sanitizeHTML(b.browser)}</span>
5652
6324
  <span class="browser-stats">${b.percentage}% (${b.count})</span>
5653
6325
  </div>`,
5654
6326
  )
5655
6327
  .join("")}
5656
6328
  ${
5657
- browserBreakdown.length > 5
6329
+ browserBreakdown.length > 3
5658
6330
  ? `<div class="browser-item" style="opacity: 0.6; font-style: italic; justify-content: center; border-top: 1px solid var(--border-light); margin-top: 8px; padding-top: 8px;">
5659
- <span>+${browserBreakdown.length - 5} more browsers</span>
6331
+ <span>+${browserBreakdown.length - 3} more browsers</span>
5660
6332
  </div>`
5661
6333
  : ""
5662
6334
  }
@@ -5669,17 +6341,13 @@ function generateHTML(reportData, trendData = null) {
5669
6341
  [
5670
6342
  { label: "Passed", value: runSummary.passed },
5671
6343
  { label: "Failed", value: runSummary.failed },
6344
+ { label: "Flaky", value: runSummary.flaky || 0 },
5672
6345
  { label: "Skipped", value: runSummary.skipped || 0 },
5673
6346
  ],
5674
6347
  400,
5675
6348
  390,
5676
6349
  )}
5677
- ${
5678
- runSummary.environment &&
5679
- Object.keys(runSummary.environment).length > 0
5680
- ? generateEnvironmentDashboard(runSummary.environment)
5681
- : '<div class="no-data">Environment data not available.</div>'
5682
- }
6350
+ ${generateEnvironmentSection(runSummary.environment)}
5683
6351
  </div>
5684
6352
  <div style="display: flex; flex-direction: column; gap: 28px;">
5685
6353
  ${generateSuitesWidget(suitesData)}
@@ -5690,7 +6358,7 @@ function generateHTML(reportData, trendData = null) {
5690
6358
  <div id="test-runs" class="tab-content">
5691
6359
  <div class="filters" style="border-color: black; border-style: groove;">
5692
6360
  <input type="text" id="filter-name" placeholder="Filter by test name/path..." style="border-color: black; border-style: outset;">
5693
- <select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="skipped">Skipped</option></select>
6361
+ <select id="filter-status"><option value="">All Statuses</option><option value="passed">Passed</option><option value="failed">Failed</option><option value="flaky">Flaky</option><option value="skipped">Skipped</option></select>
5694
6362
  <select id="filter-browser"><option value="">All Browsers</option>${Array.from(
5695
6363
  new Set(
5696
6364
  (results || []).map((test) => test.browser || "unknown"),
@@ -5751,7 +6419,7 @@ function generateHTML(reportData, trendData = null) {
5751
6419
  ${generateWorkerDistributionChart(results)}
5752
6420
  </div>
5753
6421
  </div>
5754
- <div class="trend-chart test-history-trend-section" style="border-bottom: none;">
6422
+ <div class="trend-chart test-history-trend-section" style="border-bottom: none; background: none !important; box-shadow: none !important; border: none !important; border-radius: none !important;">
5755
6423
  <h3 class="chart-title-header">Individual Test History</h3>
5756
6424
  </div>
5757
6425
  ${
@@ -5768,6 +6436,7 @@ function generateHTML(reportData, trendData = null) {
5768
6436
  <footer style="padding: 0.5rem; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); text-align: center; font-family: 'Segoe UI', system-ui, sans-serif;">
5769
6437
  <div style="display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; font-weight: 600; letter-spacing: 0.5px;">
5770
6438
  <span>Created by</span>
6439
+ <img id="report-logo" src=${logo} alt="Pulse Report Logo" style="height: 20px;">
5771
6440
  <a href="https://www.npmjs.com/package/@arghajit/playwright-pulse-report" target="_blank" rel="noopener noreferrer" style="color: #7737BF; font-weight: 700; font-style: italic; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.color='#BF5C37'" onmouseout="this.style.color='#7737BF'">Pulse Report</a>
5772
6441
  </div>
5773
6442
  <div style="margin-top: 0.5rem; font-size: 0.75rem; color: #666;">Crafted with precision</div>
@@ -5781,6 +6450,33 @@ function generateHTML(reportData, trendData = null) {
5781
6450
  return (ms / 1000).toFixed(1) + "s";
5782
6451
  }
5783
6452
  }
6453
+ function switchRetryTab(event, tabId) {
6454
+ // Find container
6455
+ const container = event.target.closest('.retry-tabs-container');
6456
+ if (!container) return;
6457
+
6458
+ // Update tab buttons
6459
+ const buttons = container.querySelectorAll('.retry-tab');
6460
+ buttons.forEach(btn => btn.classList.remove('active'));
6461
+ const activeBtn = event.target.closest('.retry-tab') || event.target;
6462
+ activeBtn.classList.add('active');
6463
+
6464
+ // Update content
6465
+ const contents = container.querySelectorAll('.retry-tab-content');
6466
+ contents.forEach(content => {
6467
+ content.style.display = 'none';
6468
+ content.classList.remove('active');
6469
+ });
6470
+
6471
+ const activeContent = document.getElementById(tabId);
6472
+ if (activeContent) {
6473
+ activeContent.style.display = 'block';
6474
+ activeContent.classList.add('active');
6475
+ } else {
6476
+ console.error('Failed to find retry tab content for id:', tabId);
6477
+ }
6478
+ }
6479
+
5784
6480
  function copyLogContent(elementId, button) {
5785
6481
  const logElement = document.getElementById(elementId);
5786
6482
  if (!logElement) {
@@ -6437,6 +7133,8 @@ async function runScript(scriptPath, args = []) {
6437
7133
  * prepares the data, and then generates and writes the final HTML report file.
6438
7134
  */
6439
7135
  async function main() {
7136
+ await animate();
7137
+
6440
7138
  const __filename = fileURLToPath(import.meta.url);
6441
7139
  const __dirname = path.dirname(__filename);
6442
7140
 
@@ -6455,8 +7153,12 @@ async function main() {
6455
7153
  "generate-trend.mjs", // Keeping the filename as per your request
6456
7154
  );
6457
7155
 
6458
- const outputDir = await getOutputDir(customOutputDir);
6459
- const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE); // Current run's main JSON
7156
+ const config = await getReporterConfig(customOutputDir);
7157
+ const outputDir = config.outputDir;
7158
+ const outputFile = config.outputFile;
7159
+
7160
+ await mergeSequentialReportsIfNeeded(outputDir);
7161
+ const reportJsonPath = path.resolve(outputDir, outputFile); // Current run's main JSON
6460
7162
  const reportHtmlPath = path.resolve(outputDir, DEFAULT_HTML_FILE);
6461
7163
 
6462
7164
  const historyDir = path.join(outputDir, "history"); // Directory for historical JSON files
@@ -6497,6 +7199,32 @@ async function main() {
6497
7199
  try {
6498
7200
  const jsonData = await fs.readFile(reportJsonPath, "utf-8");
6499
7201
  currentRunReportData = JSON.parse(jsonData);
7202
+
7203
+ // Process custom logo if provided in metadata
7204
+ if (currentRunReportData.metadata?.logo) {
7205
+ const logoPath = path.resolve(
7206
+ process.cwd(),
7207
+ currentRunReportData.metadata.logo,
7208
+ );
7209
+ try {
7210
+ const ext = path.extname(logoPath).toLowerCase();
7211
+ let mimeType = "image/png";
7212
+ if (ext === ".svg") mimeType = "image/svg+xml";
7213
+ else if (ext === ".jpg" || ext === ".jpeg") mimeType = "image/jpeg";
7214
+ else if (ext === ".gif") mimeType = "image/gif";
7215
+ else if (ext === ".webp") mimeType = "image/webp";
7216
+
7217
+ const logoData = await fs.readFile(logoPath, "base64");
7218
+ logo = `data:${mimeType};base64,${logoData}`;
7219
+ } catch (error) {
7220
+ console.warn(
7221
+ chalk.yellow(
7222
+ `Warning: Could not read custom logo file at ${logoPath}. Falling back to default logo. Error: ${error.message}`,
7223
+ ),
7224
+ );
7225
+ }
7226
+ }
7227
+
6500
7228
  if (
6501
7229
  !currentRunReportData ||
6502
7230
  typeof currentRunReportData !== "object" ||
@@ -6605,6 +7333,7 @@ async function main() {
6605
7333
  passed: histRunReport.run.passed,
6606
7334
  failed: histRunReport.run.failed,
6607
7335
  skipped: histRunReport.run.skipped || 0,
7336
+ flaky: histRunReport.run.flaky || (histRunReport.results ? histRunReport.results.filter(r => r.status === 'flaky' || r.outcome === 'flaky').length : 0),
6608
7337
  });
6609
7338
 
6610
7339
  if (histRunReport.results && Array.isArray(histRunReport.results)) {
@@ -6613,7 +7342,7 @@ async function main() {
6613
7342
  (test) => ({
6614
7343
  testName: test.name,
6615
7344
  duration: test.duration,
6616
- status: test.status,
7345
+ status: test.final_status || test.status,
6617
7346
  timestamp: new Date(test.startTime),
6618
7347
  }),
6619
7348
  );
@@ -6631,7 +7360,7 @@ async function main() {
6631
7360
  await fs.writeFile(reportHtmlPath, htmlContent, "utf-8");
6632
7361
  console.log(
6633
7362
  chalk.green.bold(
6634
- `🎉 Pulse report generated successfully at: ${reportHtmlPath}`,
7363
+ `Pulse report generated successfully at: ${reportHtmlPath}`,
6635
7364
  ),
6636
7365
  );
6637
7366
  console.log(chalk.gray(`(You can open this file in your browser)`));